diff --git a/apps/circuitpusher/circuitpusher.py b/apps/circuitpusher/circuitpusher.py index 6c1af0e83a1dfdb4def897e2295051f606d296eb..344ecd96029ab9494d9672257affa358c58519d6 100755 --- a/apps/circuitpusher/circuitpusher.py +++ b/apps/circuitpusher/circuitpusher.py @@ -94,14 +94,26 @@ if args.action=='add': result = os.popen(command).read() parsedResult = json.loads(result) print command+"\n" - sourceSwitch = parsedResult[0]['attachmentPoint'][0]['switchDPID'] + + try: + sourceSwitch = parsedResult[0]['attachmentPoint'][0]['switchDPID'] + except IndexError: + print "ERROR : the specified end point (%s) must already been known to the controller (i.e., already have sent packets on the network, easy way to assure this is to do a ping (to any target) from the two hosts." % (args.srcAddress) + sys.exit() + sourcePort = parsedResult[0]['attachmentPoint'][0]['port'] command = "curl -s http://%s/wm/device/?ipv4=%s" % (args.controllerRestIp, args.dstAddress) result = os.popen(command).read() parsedResult = json.loads(result) print command+"\n" - destSwitch = parsedResult[0]['attachmentPoint'][0]['switchDPID'] + + try: + destSwitch = parsedResult[0]['attachmentPoint'][0]['switchDPID'] + except IndexError: + print "ERROR : the specified end point (%s) must already been known to the controller (i.e., already have sent packets on the network, easy way to assure this is to do a ping (to any target) from the two hosts." % (args.dstAddress) + sys.exit() + destPort = parsedResult[0]['attachmentPoint'][0]['port'] print "Creating circuit:" diff --git a/build.xml b/build.xml index bed6c6081d4964a5d764284d52b4dcfe6ad22b7a..1c0dfe8fa473331aaba42ac283c5e8b6134b92e5 100644 --- a/build.xml +++ b/build.xml @@ -44,8 +44,8 @@ <property name="floodlight-test-jar" location="${target}/floodlight-test.jar"/> <property name="thrift.dir" value="${basedir}/src/main/thrift"/> <property name="thrift.out.dir" value="lib/gen-java"/> - <property name="ant.build.javac.source" value="1.6"/> - <property name="ant.build.javac.target" value="1.6"/> + <property name="ant.build.javac.source" value="1.7"/> + <property name="ant.build.javac.target" value="1.7"/> <property name="findbugs.home" value="../build/findbugs-2.0.2"/> <property name="findbugs.results" value="findbugs-results" /> <property name="lib" location="lib"/> @@ -75,6 +75,12 @@ <include name="findbugs-annotations-2.0.1.jar" /> <include name="findbugs-jsr305-2.0.1.jar" /> <include name="derby-10.9.1.0.jar"/> + <include name="openflowj-0.3.5-SNAPSHOT.jar"/> + <include name="openflowj-0.3.5-SNAPSHOT-javadoc.jar"/> + <include name="hamcrest-core-1.3.jar"/> + <include name="hamcrest-integration-1.3.jar"/> + <include name="hamcrest-library-1.3.jar"/> + <include name="j3dutils.jar"/> </patternset> <path id="classpath"> diff --git a/floodlight.sh b/floodlight.sh index 5bc8564af7321c0a23968946edc347f4e861463a..9bdf2641155ee85bee34840e78a2c13d81e5deb7 100755 --- a/floodlight.sh +++ b/floodlight.sh @@ -25,10 +25,10 @@ JVM_OPTS="$JVM_OPTS -Dpython.security.respectJavaAccessibility=false" <root level="INFO"> <appender-ref ref="STDOUT" /> </root> - <logger name="org" level="WARN"/> - <logger name="LogService" level="WARN"/> <!-- Restlet access logging --> - <logger name="net.floodlightcontroller" level="INFO"/> - <logger name="net.floodlightcontroller.logging" level="ERROR"/> + <logger name="org" level=“ALLâ€/> + <logger name="LogService" level=“DEBUGâ€/> <!-- Restlet access logging --> + <logger name="net.floodlightcontroller" level=“ALLâ€/> + <logger name="net.floodlightcontroller.logging" level=“ALLâ€/> </configuration> EOF_LOGBACK diff --git a/lib/hamcrest-core-1.3.jar b/lib/hamcrest-core-1.3.jar new file mode 100644 index 0000000000000000000000000000000000000000..9d5fe16e3dd37ebe79a36f61f5d0e1a69a653a8a Binary files /dev/null and b/lib/hamcrest-core-1.3.jar differ diff --git a/lib/hamcrest-integration-1.3.jar b/lib/hamcrest-integration-1.3.jar new file mode 100644 index 0000000000000000000000000000000000000000..e07447a0cdcd5897de3556296e184ab8d956af4f Binary files /dev/null and b/lib/hamcrest-integration-1.3.jar differ diff --git a/lib/hamcrest-library-1.3.jar b/lib/hamcrest-library-1.3.jar new file mode 100644 index 0000000000000000000000000000000000000000..9eac80d7bae2bd61a36c1d0dcb27d2407430f316 Binary files /dev/null and b/lib/hamcrest-library-1.3.jar differ diff --git a/lib/j3dutils.jar b/lib/j3dutils.jar new file mode 100644 index 0000000000000000000000000000000000000000..6e5e44112affe0084c91be22645114ba9096ff76 Binary files /dev/null and b/lib/j3dutils.jar differ diff --git a/lib/openflowj-0.3.5-SNAPSHOT-javadoc.jar b/lib/openflowj-0.3.5-SNAPSHOT-javadoc.jar new file mode 100644 index 0000000000000000000000000000000000000000..49c3f4551b6588514f1b18581e274c688fbf5bce Binary files /dev/null and b/lib/openflowj-0.3.5-SNAPSHOT-javadoc.jar differ diff --git a/lib/openflowj-0.3.5-SNAPSHOT-sources.jar b/lib/openflowj-0.3.5-SNAPSHOT-sources.jar new file mode 100644 index 0000000000000000000000000000000000000000..5786ced23c2b240d20195b3acef6e18a3e676db5 Binary files /dev/null and b/lib/openflowj-0.3.5-SNAPSHOT-sources.jar differ diff --git a/lib/openflowj-0.3.5-SNAPSHOT-tests.jar b/lib/openflowj-0.3.5-SNAPSHOT-tests.jar new file mode 100644 index 0000000000000000000000000000000000000000..67646a8c35818bb017599121b16d7aff19718d9a Binary files /dev/null and b/lib/openflowj-0.3.5-SNAPSHOT-tests.jar differ diff --git a/lib/openflowj-0.3.5-SNAPSHOT.jar b/lib/openflowj-0.3.5-SNAPSHOT.jar new file mode 100644 index 0000000000000000000000000000000000000000..dcb33c10e467f75f74ea96062893d7608d6ce71c Binary files /dev/null and b/lib/openflowj-0.3.5-SNAPSHOT.jar differ diff --git a/logback.xml b/logback.xml index 29427f88cf4483609142d068b5eb0ae71bf18c69..a25cc7f639cbb65d64acfca5a5df190e83c674d4 100644 --- a/logback.xml +++ b/logback.xml @@ -4,12 +4,12 @@ <pattern>%date{yyyy-MM-dd HH:mm:ss.S} %-5level [%logger{15}] %msg%n</pattern> </encoder> </appender> - <root level="INFO"> + <root level="DEBUG"> <appender-ref ref="STDOUT" /> </root> - <logger name="org" level="WARN"/> - <logger name="LogService" level="WARN"/> <!-- Restlet access logging --> - <logger name="net.floodlightcontroller" level="INFO"/> - <logger name="net.floodlightcontroller.logging" level="WARN"/> - <logger name="org.sdnplatform" level="INFO"/> + <logger name="org" level="DEBUG"/> + <logger name="LogService" level="DEBUG"/> <!-- Restlet access logging --> + <logger name="net.floodlightcontroller" level="DEBUG"/> + <logger name="net.floodlightcontroller.logging" level="DEBUG"/> + <logger name="org.sdnplatform" level="DEBUG"/> </configuration> diff --git a/src/main/java/com/bigswitch/floodlight/vendor/OFActionBigSwitchVendor.java b/src/main/java/com/bigswitch/floodlight/vendor/OFActionBigSwitchVendor.java deleted file mode 100644 index 147820f479bea7596961e29225931f5856118648..0000000000000000000000000000000000000000 --- a/src/main/java/com/bigswitch/floodlight/vendor/OFActionBigSwitchVendor.java +++ /dev/null @@ -1,70 +0,0 @@ -package com.bigswitch.floodlight.vendor; - -import org.jboss.netty.buffer.ChannelBuffer; -import org.openflow.protocol.action.OFActionVendor; - -public abstract class OFActionBigSwitchVendor extends OFActionVendor { - public static int MINIMUM_LENGTH = 12; - public static int BSN_VENDOR_ID = OFBigSwitchVendorData.BSN_VENDOR_ID; - - protected int subtype; - - protected OFActionBigSwitchVendor(int subtype) { - super(); - super.setLength((short)MINIMUM_LENGTH); - super.setVendor(BSN_VENDOR_ID); - this.subtype = subtype; - } - - public int getSubtype() { - return this.subtype; - } - - public void setSubtype(int subtype) { - this.subtype = subtype; - } - - @Override - public void readFrom(ChannelBuffer data) { - super.readFrom(data); - this.subtype = data.readInt(); - } - - @Override - public void writeTo(ChannelBuffer data) { - super.writeTo(data); - data.writeInt(this.subtype); - } - - @Override - public int hashCode() { - final int prime = 379; - int result = super.hashCode(); - result = prime * result + vendor; - result = prime * result + subtype; - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (!super.equals(obj)) { - return false; - } - if (!(obj instanceof OFActionBigSwitchVendor)) { - return false; - } - OFActionBigSwitchVendor other = (OFActionBigSwitchVendor) obj; - if (subtype != other.subtype) { - return false; - } - return true; - } - - @Override - public String toString() { - return super.toString() + "; subtype=" + subtype; - } -} diff --git a/src/main/java/com/bigswitch/floodlight/vendor/OFActionMirror.java b/src/main/java/com/bigswitch/floodlight/vendor/OFActionMirror.java deleted file mode 100644 index 3b55ca7201234a7d39f76db2416fc3ac1a1e1298..0000000000000000000000000000000000000000 --- a/src/main/java/com/bigswitch/floodlight/vendor/OFActionMirror.java +++ /dev/null @@ -1,114 +0,0 @@ -package com.bigswitch.floodlight.vendor; - -import org.jboss.netty.buffer.ChannelBuffer; - - -public class OFActionMirror extends OFActionBigSwitchVendor { - public final static int MINIMUM_LENGTH = 12; - public final static int BSN_ACTION_MIRROR = 1; - - protected int destPort; - protected int vlanTag; - protected byte copyStage; - protected byte pad0; - protected byte pad1; - protected byte pad2; - - public OFActionMirror(short portNumber) { - super(BSN_ACTION_MIRROR); - super.setLength((short) (OFActionBigSwitchVendor.MINIMUM_LENGTH + OFActionMirror.MINIMUM_LENGTH)); - this.destPort = portNumber; - this.vlanTag = 0; - this.copyStage = 0; - } - - public int getDestPort() { - return destPort; - } - - public void setDestPort(int destPort) { - this.destPort = destPort; - } - - public int getVlanTag() { - return vlanTag; - } - - public void setVlanTag(int vlanTag) { - this.vlanTag = vlanTag; - } - - public byte getCopyStage() { - return copyStage; - } - - public void setCopyStage(byte copyStage) { - this.copyStage = copyStage; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = super.hashCode(); - result = prime * result + copyStage; - result = prime * result + destPort; - result = prime * result + pad0; - result = prime * result + pad1; - result = prime * result + pad2; - result = prime * result + vlanTag; - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (!super.equals(obj)) return false; - if (getClass() != obj.getClass()) return false; - OFActionMirror other = (OFActionMirror) obj; - if (copyStage != other.copyStage) return false; - if (destPort != other.destPort) return false; - if (pad0 != other.pad0) return false; - if (pad1 != other.pad1) return false; - if (pad2 != other.pad2) return false; - if (vlanTag != other.vlanTag) return false; - return true; - } - - @Override - public void readFrom(ChannelBuffer data) { - super.readFrom(data); - this.destPort = data.readInt(); - this.vlanTag = data.readInt(); - this.copyStage = data.readByte(); - this.pad0 = data.readByte(); - this.pad1 = data.readByte(); - this.pad2 = data.readByte(); - } - - @Override - public void writeTo(ChannelBuffer data) { - super.writeTo(data); - data.writeInt(this.destPort); - data.writeInt(this.vlanTag); - data.writeByte(this.copyStage); - data.writeByte(this.pad0); - data.writeByte(this.pad1); - data.writeByte(this.pad2); - } - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append(type); - builder.append("["); - builder.append("BSN-MIRROR"); - builder.append(", Dest Port: "); - builder.append(destPort); - builder.append(", Vlan: "); - builder.append(vlanTag); - builder.append(", Copy Stage: "); - builder.append(copyStage); - builder.append("]"); - return builder.toString(); - } -} diff --git a/src/main/java/com/bigswitch/floodlight/vendor/OFActionNiciraTtlDecrement.java b/src/main/java/com/bigswitch/floodlight/vendor/OFActionNiciraTtlDecrement.java deleted file mode 100644 index 51b829a815f5ee92e2972de55bfb1c94cee4a38a..0000000000000000000000000000000000000000 --- a/src/main/java/com/bigswitch/floodlight/vendor/OFActionNiciraTtlDecrement.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.bigswitch.floodlight.vendor; - -import org.jboss.netty.buffer.ChannelBuffer; - -public class OFActionNiciraTtlDecrement extends OFActionNiciraVendor { - public static int MINIMUM_LENGTH_TTL_DECREMENT = 16; - public static final short TTL_DECREMENT_SUBTYPE = 18; - - - public OFActionNiciraTtlDecrement() { - super(TTL_DECREMENT_SUBTYPE); - super.setLength((short)MINIMUM_LENGTH_TTL_DECREMENT); - } - - @Override - public void readFrom(ChannelBuffer data) { - super.readFrom(data); - data.skipBytes(6); // pad - } - - @Override - public void writeTo(ChannelBuffer data) { - super.writeTo(data); - data.writeZero(6); - } - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append(type); - builder.append("["); - builder.append("NICIRA-TTL-DECR"); - builder.append("]"); - return builder.toString(); - } -} diff --git a/src/main/java/com/bigswitch/floodlight/vendor/OFActionNiciraVendor.java b/src/main/java/com/bigswitch/floodlight/vendor/OFActionNiciraVendor.java deleted file mode 100644 index b4d9fa523baff8fb12150d2e5665300b7b4cd39a..0000000000000000000000000000000000000000 --- a/src/main/java/com/bigswitch/floodlight/vendor/OFActionNiciraVendor.java +++ /dev/null @@ -1,77 +0,0 @@ -package com.bigswitch.floodlight.vendor; - -import org.jboss.netty.buffer.ChannelBuffer; -import org.openflow.protocol.action.OFActionVendor; -import org.openflow.vendor.nicira.OFNiciraVendorData; - -/** - * FIXME: this should really be handled by a consistent parse tree for - * different vendor actions but for the time being this works and gets the - * job done. - * - * @author gregor - * - */ -public class OFActionNiciraVendor extends OFActionVendor { - public static int MINIMUM_LENGTH = 16; - public static int NICIRA_VENDOR_ID = OFNiciraVendorData.NX_VENDOR_ID; - - protected short subtype; - - protected OFActionNiciraVendor(short subtype) { - // We don't allow direct instantiation of this class because its - // minimum length is 16 and the only way to guarantee this is by - // having a subclass that properly adds padding. - super(); - super.setLength((short)MINIMUM_LENGTH); - super.setVendor(NICIRA_VENDOR_ID); - this.subtype = subtype; - } - - public short getSubtype() { - return this.subtype; - } - - public void setSubtype(short subtype) { - this.subtype = subtype; - } - - @Override - public void readFrom(ChannelBuffer data) { - super.readFrom(data); - this.subtype = data.readShort(); - } - - @Override - public void writeTo(ChannelBuffer data) { - super.writeTo(data); - data.writeShort(this.subtype); - } - - @Override - public int hashCode() { - final int prime = 379; - int result = super.hashCode(); - result = prime * result + vendor; - result = prime * result + subtype; - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (!super.equals(obj)) { - return false; - } - if (!(obj instanceof OFActionNiciraVendor)) { - return false; - } - OFActionNiciraVendor other = (OFActionNiciraVendor) obj; - if (subtype != other.subtype) { - return false; - } - return true; - } -} diff --git a/src/main/java/com/bigswitch/floodlight/vendor/OFActionTunnelDstIP.java b/src/main/java/com/bigswitch/floodlight/vendor/OFActionTunnelDstIP.java deleted file mode 100644 index 9cd851c634178c22af5edd467712f51104e53e88..0000000000000000000000000000000000000000 --- a/src/main/java/com/bigswitch/floodlight/vendor/OFActionTunnelDstIP.java +++ /dev/null @@ -1,76 +0,0 @@ -package com.bigswitch.floodlight.vendor; - -import net.floodlightcontroller.core.web.serializers.IPv4Serializer; -import net.floodlightcontroller.packet.IPv4; - -import org.jboss.netty.buffer.ChannelBuffer; - -import com.fasterxml.jackson.databind.annotation.JsonSerialize; - -public class OFActionTunnelDstIP extends OFActionBigSwitchVendor { - public final static int MINIMUM_LENGTH_TUNNEL_DST = 16; - public final static int SET_TUNNEL_DST_SUBTYPE = 2; - - protected int dstIPAddr; - - public OFActionTunnelDstIP() { - super(SET_TUNNEL_DST_SUBTYPE); - super.setLength((short)MINIMUM_LENGTH_TUNNEL_DST); - } - - public OFActionTunnelDstIP(int dstIPAddr) { - this(); - this.dstIPAddr = dstIPAddr; - } - - @JsonSerialize(using=IPv4Serializer.class) - public int getTunnelDstIP() { - return this.dstIPAddr; - } - - public void setTunnelDstIP(int ipAddr) { - this.dstIPAddr = ipAddr; - } - - @Override - public void readFrom(ChannelBuffer data) { - super.readFrom(data); - this.dstIPAddr = data.readInt(); - } - - @Override - public void writeTo(ChannelBuffer data) { - super.writeTo(data); - data.writeInt(this.dstIPAddr); - } - - @Override - public int hashCode() { - final int prime = 31; - int result = super.hashCode(); - result = prime * result + dstIPAddr; - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (!super.equals(obj)) return false; - if (getClass() != obj.getClass()) return false; - OFActionTunnelDstIP other = (OFActionTunnelDstIP) obj; - if (dstIPAddr != other.dstIPAddr) return false; - return true; - } - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append(type); - builder.append("["); - builder.append("BSN-SET-TUNNEL-DST-IP"); - builder.append(", IP: "); - builder.append(IPv4.fromIPv4Address(dstIPAddr)); - builder.append("]"); - return builder.toString(); - } -} diff --git a/src/main/java/com/bigswitch/floodlight/vendor/OFBigSwitchVendorActionFactory.java b/src/main/java/com/bigswitch/floodlight/vendor/OFBigSwitchVendorActionFactory.java deleted file mode 100644 index 3b58cabf632df06fbbc187e3f8e0bdf14efa05cd..0000000000000000000000000000000000000000 --- a/src/main/java/com/bigswitch/floodlight/vendor/OFBigSwitchVendorActionFactory.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.bigswitch.floodlight.vendor; - -import org.jboss.netty.buffer.ChannelBuffer; -import org.openflow.protocol.action.OFActionVendor; -import org.openflow.protocol.factory.OFVendorActionFactory; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class OFBigSwitchVendorActionFactory implements OFVendorActionFactory { - protected static Logger logger = - LoggerFactory.getLogger(OFBigSwitchVendorActionFactory.class); - - static class OFActionBigSwitchVendorDemux extends OFActionBigSwitchVendor { - OFActionBigSwitchVendorDemux() { - super((short) 0); - } - } - - @Override - public OFActionVendor readFrom(ChannelBuffer data) { - data.markReaderIndex(); - OFActionBigSwitchVendor demux = new OFActionBigSwitchVendorDemux(); - demux.readFrom(data); - data.resetReaderIndex(); - - switch(demux.getSubtype()) { - case OFActionMirror.BSN_ACTION_MIRROR: - OFActionMirror mirrorAction = new OFActionMirror((short) 0); - mirrorAction.readFrom(data); - return mirrorAction; - case OFActionTunnelDstIP.SET_TUNNEL_DST_SUBTYPE: - OFActionTunnelDstIP tunnelAction = new OFActionTunnelDstIP(); - tunnelAction.readFrom(data); - return tunnelAction; - default: - logger.error("Unknown BSN vendor action subtype: "+demux.getSubtype()); - return null; - } - } - -} diff --git a/src/main/java/com/bigswitch/floodlight/vendor/OFBigSwitchVendorData.java b/src/main/java/com/bigswitch/floodlight/vendor/OFBigSwitchVendorData.java deleted file mode 100644 index b5288f1381a5d5706a1813a4ff625fbcf94ffc3e..0000000000000000000000000000000000000000 --- a/src/main/java/com/bigswitch/floodlight/vendor/OFBigSwitchVendorData.java +++ /dev/null @@ -1,95 +0,0 @@ -package com.bigswitch.floodlight.vendor; - -import org.jboss.netty.buffer.ChannelBuffer; -import org.openflow.protocol.vendor.OFVendorData; - -/** - * Base class for vendor data corresponding to BigSwitch vendor extensions - * BigSwitch vendor data always starts with a 4-byte integer data type value - * - * @author Munish Mehta (munish.mehta@bigswitch.com) - */ -public class OFBigSwitchVendorData implements OFVendorData { - - public static final int BSN_VENDOR_ID = 0x005c16c7; - - /** - * The value of the integer data type at the beginning of the vendor data - */ - protected int dataType; - - /** - * Construct BigSwitch vendor data with the specified data type - * @param dataType : the data type value at the beginning (opcode) - */ - public OFBigSwitchVendorData(int dataType) { - super(); - this.dataType = dataType; - } - - /** - * Get the data type value at the beginning of the vendor data - * @return - */ - public int getDataType() { - return dataType; - } - - /** - * Set the data type value - * @param dataType - */ - public void setDataType(int dataType) { - this.dataType = dataType; - } - - /** - * Get the length of the vendor data. This implementation will normally be - * the superclass for another class that will override this to return the - * overall vendor data length. This implementation just returns the length - * of the part that includes the 4-byte integer data type value at the - * beginning of the vendor data - */ - @Override - public int getLength() { - return 4; - } - - /** - * Read the vendor data from the ChannelBuffer - * @param data the channel buffer from which we're deserializing - * @param length the length to the end of the enclosing message - */ - @Override - public void readFrom(ChannelBuffer data, int length) { - dataType = data.readInt(); - } - - /** - * Write the vendor data to the ChannelBuffer - * @param data the channel buffer to which we're serializing - */ - @Override - public void writeTo(ChannelBuffer data) { - data.writeInt(dataType); - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + dataType; - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (obj == null) return false; - if (getClass() != obj.getClass()) return false; - OFBigSwitchVendorData other = (OFBigSwitchVendorData) obj; - if (dataType != other.dataType) return false; - return true; - } - -} diff --git a/src/main/java/com/bigswitch/floodlight/vendor/OFBigSwitchVendorExtensions.java b/src/main/java/com/bigswitch/floodlight/vendor/OFBigSwitchVendorExtensions.java deleted file mode 100644 index 0b3d069c4a1af871433509094f7a73e001fda17e..0000000000000000000000000000000000000000 --- a/src/main/java/com/bigswitch/floodlight/vendor/OFBigSwitchVendorExtensions.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.bigswitch.floodlight.vendor; - -import org.openflow.protocol.vendor.OFBasicVendorDataType; -import org.openflow.protocol.vendor.OFBasicVendorId; -import org.openflow.protocol.vendor.OFVendorId; - -public class OFBigSwitchVendorExtensions { - private static boolean initialized = false; - - public static synchronized void initialize() { - if (initialized) - return; - - OFBasicVendorId bsnVendorId = - new OFBasicVendorId(OFBigSwitchVendorData.BSN_VENDOR_ID, 4); - OFVendorId.registerVendorId(bsnVendorId); - - // register data types used for big tap - OFBasicVendorDataType setEntryVendorData = - new OFBasicVendorDataType( - OFNetmaskSetVendorData.BSN_SET_IP_MASK_ENTRY, - OFNetmaskSetVendorData.getInstantiable()); - bsnVendorId.registerVendorDataType(setEntryVendorData); - - OFBasicVendorDataType getEntryVendorDataRequest = - new OFBasicVendorDataType( - OFNetmaskGetVendorDataRequest.BSN_GET_IP_MASK_ENTRY_REQUEST, - OFNetmaskGetVendorDataRequest.getInstantiable()); - bsnVendorId.registerVendorDataType(getEntryVendorDataRequest); - - OFBasicVendorDataType getEntryVendorDataReply = - new OFBasicVendorDataType( - OFNetmaskGetVendorDataReply.BSN_GET_IP_MASK_ENTRY_REPLY, - OFNetmaskGetVendorDataReply.getInstantiable()); - bsnVendorId.registerVendorDataType(getEntryVendorDataReply); - - // register data types used for tunneling - OFBasicVendorDataType getIntfIPVendorDataRequest = - new OFBasicVendorDataType( - OFInterfaceIPRequestVendorData.BSN_GET_INTERFACE_IP_REQUEST, - OFInterfaceIPRequestVendorData.getInstantiable()); - bsnVendorId.registerVendorDataType(getIntfIPVendorDataRequest); - - OFBasicVendorDataType getIntfIPVendorDataReply = - new OFBasicVendorDataType( - OFInterfaceIPReplyVendorData.BSN_GET_INTERFACE_IP_REPLY, - OFInterfaceIPReplyVendorData.getInstantiable()); - bsnVendorId.registerVendorDataType(getIntfIPVendorDataReply); - - - } -} diff --git a/src/main/java/com/bigswitch/floodlight/vendor/OFBsnL2TableSetVendorData.java b/src/main/java/com/bigswitch/floodlight/vendor/OFBsnL2TableSetVendorData.java deleted file mode 100644 index 3dd8310148e423889250a281ee240ef6e13f7f3c..0000000000000000000000000000000000000000 --- a/src/main/java/com/bigswitch/floodlight/vendor/OFBsnL2TableSetVendorData.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.bigswitch.floodlight.vendor; - -import org.openflow.protocol.Instantiable; -import org.openflow.protocol.vendor.OFVendorData; - -public class OFBsnL2TableSetVendorData extends OFBsnL2TableVendorData { - - protected static Instantiable<OFVendorData> instantiableSingleton = - new Instantiable<OFVendorData>() { - public OFVendorData instantiate() { - return new OFBsnL2TableSetVendorData(); - } - }; - - public static final int BSN_L2_TABLE_SET = 12; - - /** - * @return a subclass of Instantiable<OFVendorData> that instantiates - * an instance of OFBsnL2TableSetVendorData. - */ - public static Instantiable<OFVendorData> getInstantiable() { - return instantiableSingleton; - } - - public OFBsnL2TableSetVendorData() { - super(BSN_L2_TABLE_SET); - } - - public OFBsnL2TableSetVendorData(boolean l2TableEnabled, - short l2TablePriority) { - super(BSN_L2_TABLE_SET, l2TableEnabled, l2TablePriority); - } -} diff --git a/src/main/java/com/bigswitch/floodlight/vendor/OFBsnL2TableVendorData.java b/src/main/java/com/bigswitch/floodlight/vendor/OFBsnL2TableVendorData.java deleted file mode 100644 index 14e6178d1b4793855b1b6ee8d0946c6f87fa79d0..0000000000000000000000000000000000000000 --- a/src/main/java/com/bigswitch/floodlight/vendor/OFBsnL2TableVendorData.java +++ /dev/null @@ -1,81 +0,0 @@ -package com.bigswitch.floodlight.vendor; - -import org.jboss.netty.buffer.ChannelBuffer; - -public class OFBsnL2TableVendorData extends OFBigSwitchVendorData { - /* - * uint8_t l2_table_enable; // 1 == enabled, 0 == disabled - * uint8_t pad; - * uint16_t l2_table_priority; // priority of all flows in L2 table - * uint8_t pad[4]; - */ - protected boolean l2TableEnabled; - protected short l2TablePriority; - - - public OFBsnL2TableVendorData(int dataType) { - super(dataType); - this.l2TableEnabled = false; - this.l2TablePriority = (short)0; - } - - - public OFBsnL2TableVendorData(int dataType, boolean l2TableEnabled, - short l2TablePriority) { - super(dataType); - this.l2TableEnabled = l2TableEnabled; - this.l2TablePriority = l2TablePriority; - } - - - public boolean isL2TableEnabled() { - return l2TableEnabled; - } - - - public short getL2TablePriority() { - return l2TablePriority; - } - - - public void setL2TableEnabled(boolean l2TableEnabled) { - this.l2TableEnabled = l2TableEnabled; - } - - - public void setL2TablePriority(short l2TablePriority) { - this.l2TablePriority = l2TablePriority; - } - - - @Override - public int getLength() { - return super.getLength() + 8; // 8 additional bytes - } - - /* - * (non-Javadoc) - * @see com.bigswitch.floodlight.vendor.OFBigSwitchVendorData#readFrom(org.jboss.netty.buffer.ChannelBuffer, int) - */ - @Override - public void readFrom(ChannelBuffer data, int length) { - super.readFrom(data, length); - l2TableEnabled = (data.readByte() == 0) ? false : true; - data.readByte(); // pad - l2TablePriority = data.readShort(); - data.readInt(); // 4 bad bytes - } - - /* - * (non-Javadoc) - * @see com.bigswitch.floodlight.vendor.OFBigSwitchVendorData#writeTo(org.jboss.netty.buffer.ChannelBuffer) - */ - @Override - public void writeTo(ChannelBuffer data) { - super.writeTo(data); - data.writeByte(isL2TableEnabled() ? 1 : 0); - data.writeByte(0); // pad - data.writeShort(l2TablePriority); - data.writeInt(0); // 4 pad bytes - } -} diff --git a/src/main/java/com/bigswitch/floodlight/vendor/OFBsnPktinSuppressionSetRequestVendorData.java b/src/main/java/com/bigswitch/floodlight/vendor/OFBsnPktinSuppressionSetRequestVendorData.java deleted file mode 100644 index da5c33db03397b80d540c6d1c71f2f37e774ac3b..0000000000000000000000000000000000000000 --- a/src/main/java/com/bigswitch/floodlight/vendor/OFBsnPktinSuppressionSetRequestVendorData.java +++ /dev/null @@ -1,150 +0,0 @@ -package com.bigswitch.floodlight.vendor; - -import org.jboss.netty.buffer.ChannelBuffer; -import org.openflow.protocol.Instantiable; -import org.openflow.protocol.vendor.OFVendorData; - -public class OFBsnPktinSuppressionSetRequestVendorData - extends OFBigSwitchVendorData { - protected static Instantiable<OFVendorData> instantiableSingleton = - new Instantiable<OFVendorData>() { - @Override - public OFVendorData instantiate() { - return new OFBsnL2TableSetVendorData(); - } - }; - - /** - * @return a subclass of Instantiable<OFVendorData> that instantiates - * an instance of OFBsnL2TableSetVendorData. - */ - public static Instantiable<OFVendorData> getInstantiable() { - return instantiableSingleton; - } - - public static final int BSN_PKTIN_SUPPRESSION_SET_REQUEST = 11; - /* - * uint8_t enabled; // 0 to disable the extension, 1 to enable it - * uint8_t pad; - * uint16_t idle_timeout; // idle_timeout for new flows - * uint16_t hard_timeout; // idle_timeout for new flows - * uint16_t priority; // priority for new flows - * uint64_t cookie; // cookie for new flows - */ - - protected boolean suppressionEnabled; - protected short idleTimeout; - protected short hardTimeout; - protected short priority; - protected long cookie; - - public OFBsnPktinSuppressionSetRequestVendorData() { - super(BSN_PKTIN_SUPPRESSION_SET_REQUEST); - } - - public OFBsnPktinSuppressionSetRequestVendorData(boolean suppressionEnabled, - short idleTimeout, - short hardTimeout, - short priority, - long cookie) { - super(BSN_PKTIN_SUPPRESSION_SET_REQUEST); - this.suppressionEnabled = suppressionEnabled; - this.idleTimeout = idleTimeout; - this.hardTimeout = hardTimeout; - this.priority = priority; - this.cookie = cookie; - } - - public boolean isSuppressionEnabled() { - return suppressionEnabled; - } - - public short getIdleTimeout() { - return idleTimeout; - } - - public short getHardTimeout() { - return hardTimeout; - } - - public short getPriority() { - return priority; - } - - public long getCookie() { - return cookie; - } - - public void setSuppressionEnabled(boolean suppressionEnabled) { - this.suppressionEnabled = suppressionEnabled; - } - - public void setIdleTimeout(short idleTimeout) { - this.idleTimeout = idleTimeout; - } - - public void setHardTimeout(short hardTimeout) { - this.hardTimeout = hardTimeout; - } - - public void setPriority(short priority) { - this.priority = priority; - } - - public void setCookie(long cookie) { - this.cookie = cookie; - } - - @Override - public int getLength() { - return super.getLength() + 16; - } - - @Override - public void readFrom(ChannelBuffer data, int length) { - super.readFrom(data, length); - suppressionEnabled = (data.readByte() != 0); - data.readByte(); - idleTimeout = data.readShort(); - hardTimeout = data.readShort(); - priority = data.readShort(); - cookie = data.readLong(); - } - - @Override - public void writeTo(ChannelBuffer data) { - super.writeTo(data); - data.writeByte(suppressionEnabled ? 1 : 0); - data.writeByte(0); // pad - data.writeShort(idleTimeout); - data.writeShort(hardTimeout); - data.writeShort(priority); - data.writeLong(cookie); - } - - @Override - public int hashCode() { - final int prime = 31; - int result = super.hashCode(); - result = prime * result + (int) (cookie ^ (cookie >>> 32)); - result = prime * result + hardTimeout; - result = prime * result + idleTimeout; - result = prime * result + priority; - result = prime * result + (suppressionEnabled ? 1231 : 1237); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (!super.equals(obj)) return false; - if (getClass() != obj.getClass()) return false; - OFBsnPktinSuppressionSetRequestVendorData other = (OFBsnPktinSuppressionSetRequestVendorData) obj; - if (cookie != other.cookie) return false; - if (hardTimeout != other.hardTimeout) return false; - if (idleTimeout != other.idleTimeout) return false; - if (priority != other.priority) return false; - if (suppressionEnabled != other.suppressionEnabled) return false; - return true; - } -} diff --git a/src/main/java/com/bigswitch/floodlight/vendor/OFInterfaceIPReplyVendorData.java b/src/main/java/com/bigswitch/floodlight/vendor/OFInterfaceIPReplyVendorData.java deleted file mode 100644 index 380ba43c299c7ccaaccafbdd846e92ea72a848ef..0000000000000000000000000000000000000000 --- a/src/main/java/com/bigswitch/floodlight/vendor/OFInterfaceIPReplyVendorData.java +++ /dev/null @@ -1,124 +0,0 @@ -package com.bigswitch.floodlight.vendor; - -import java.util.ArrayList; -import java.util.List; - -import org.jboss.netty.buffer.ChannelBuffer; -import org.openflow.protocol.Instantiable; -import org.openflow.protocol.vendor.OFVendorData; - -public class OFInterfaceIPReplyVendorData extends OFBigSwitchVendorData { - - protected List<OFInterfaceVendorData> interfaces; - protected int length; - - protected static Instantiable<OFVendorData> instantiable = - new Instantiable<OFVendorData>() { - public OFVendorData instantiate() { - return new OFInterfaceIPReplyVendorData(); - } - }; - - /** - * @return a subclass of Instantiable<OFVendorData> that instantiates - * an instance of OFInterfaceIPReplyVendorData. - */ - public static Instantiable<OFVendorData> getInstantiable() { - return instantiable; - } - - /** - * Opcode/dataType to reply with IP addresses of all interfaces - */ - public static final int BSN_GET_INTERFACE_IP_REPLY = 10; - - /** - * Construct an interface IP reply vendor data - */ - public OFInterfaceIPReplyVendorData() { - super(BSN_GET_INTERFACE_IP_REPLY); - } - - /** - * @return the total length of the vendor-data part of the interface IP reply - * message. The OF header (8B) and vendor (4B) are taken care of by the - * OFVendor class MINIMUM_LENGTH. This method returns the length of the - * vendor-extension-subtype (4B) + the length of the interfaces - */ - @Override - public int getLength() { - return length; - } - - /** - * Set the length of this message - * - * @param length - */ - public void setLength(int length) { - this.length = length; - - } - - /** - * @return the interfaces - */ - public List<OFInterfaceVendorData> getInterfaces() { - return interfaces; - } - - /** - * @param intfs the ones to set - */ - public void setInterfaces(List<OFInterfaceVendorData> intfs) { - this.interfaces = intfs; - if (intfs == null) { - this.setLength(super.getLength()); - } else { - this.setLength(super.getLength() + intfs.size() - * OFInterfaceVendorData.MINIMUM_LENGTH); - } - } - - /** - * Read from the ChannelBuffer - * @param data the channel buffer from which we're deserializing - * @param length the length to the end of the enclosing message - */ - @Override - public void readFrom(ChannelBuffer data, int length) { - //datatype read by super class - super.readFrom(data, length); - - if (this.interfaces == null) { - this.interfaces = new ArrayList<OFInterfaceVendorData>(); - } else { - this.interfaces.clear(); - } - int intfCount = (length - 4) - / OFInterfaceVendorData.MINIMUM_LENGTH; - - OFInterfaceVendorData intf; - for (int i = 0; i < intfCount; ++i) { - intf = new OFInterfaceVendorData(); - intf.readFrom(data); - this.interfaces.add(intf); - } - } - - /** - * Write to the ChannelBuffer - * @param data the channel buffer to which we're serializing - */ - @Override - public void writeTo(ChannelBuffer data) { - // datatype written by super class - super.writeTo(data); - if (this.interfaces != null) { - for (OFInterfaceVendorData intf : this.interfaces) { - intf.writeTo(data); - } - } - } - -} diff --git a/src/main/java/com/bigswitch/floodlight/vendor/OFInterfaceIPRequestVendorData.java b/src/main/java/com/bigswitch/floodlight/vendor/OFInterfaceIPRequestVendorData.java deleted file mode 100644 index 9caf6e54ad299e488b217722941d7b4ae057fc72..0000000000000000000000000000000000000000 --- a/src/main/java/com/bigswitch/floodlight/vendor/OFInterfaceIPRequestVendorData.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.bigswitch.floodlight.vendor; - -import org.jboss.netty.buffer.ChannelBuffer; -import org.openflow.protocol.Instantiable; -import org.openflow.protocol.vendor.OFVendorData; - -public class OFInterfaceIPRequestVendorData extends OFBigSwitchVendorData { - - protected static Instantiable<OFVendorData> instantiable = - new Instantiable<OFVendorData>() { - public OFVendorData instantiate() { - return new OFInterfaceIPRequestVendorData(); - } - }; - - /** - * @return a subclass of Instantiable<OFVendorData> that instantiates - * an instance of OFInterfaceIPRequestVendorData. - */ - public static Instantiable<OFVendorData> getInstantiable() { - return instantiable; - } - - /** - * Opcode/dataType to request IP addresses of all interfaces - */ - public static final int BSN_GET_INTERFACE_IP_REQUEST = 9; - - /** - * Construct an interface IP request vendor data - */ - public OFInterfaceIPRequestVendorData() { - super(BSN_GET_INTERFACE_IP_REQUEST); - } - - /** - * @return the total length of the interface IP request message - * the length is already accounted for in the super class - */ - @Override - public int getLength() { - return super.getLength(); - } - - /** - * Read from the ChannelBuffer - * @param data the channel buffer from which we're deserializing - * @param length the length to the end of the enclosing message - */ - @Override - public void readFrom(ChannelBuffer data, int length) { - super.readFrom(data, length); - } - - /** - * Write to the ChannelBuffer - * @param data the channel buffer to which we're serializing - */ - @Override - public void writeTo(ChannelBuffer data) { - super.writeTo(data); - } - -} diff --git a/src/main/java/com/bigswitch/floodlight/vendor/OFInterfaceVendorData.java b/src/main/java/com/bigswitch/floodlight/vendor/OFInterfaceVendorData.java deleted file mode 100644 index 0b6f30de4c677b5c694fb2037b759462c1827c29..0000000000000000000000000000000000000000 --- a/src/main/java/com/bigswitch/floodlight/vendor/OFInterfaceVendorData.java +++ /dev/null @@ -1,123 +0,0 @@ -package com.bigswitch.floodlight.vendor; - -import java.io.UnsupportedEncodingException; -import java.nio.charset.Charset; -import java.util.Arrays; - -import net.floodlightcontroller.core.web.serializers.ByteArrayMACSerializer; - -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import org.jboss.netty.buffer.ChannelBuffer; - -public class OFInterfaceVendorData { - public static int MINIMUM_LENGTH = 32; - private static int OFP_ETH_ALEN = 6; - private static int OFP_MAX_PORT_NAME_LEN = 16; - - protected byte[] hardwareAddress; - protected String name; - protected int ipv4Addr; - protected int ipv4AddrMask; - - /** - * @return the hardwareAddress - */ - @JsonSerialize(using=ByteArrayMACSerializer.class) - public byte[] getHardwareAddress() { - return hardwareAddress; - } - - /** - * @param hardwareAddress the hardwareAddress to set - */ - public void setHardwareAddress(byte[] hardwareAddress) { - if (hardwareAddress.length != OFP_ETH_ALEN) - throw new RuntimeException("Hardware address must have length " - + OFP_ETH_ALEN); - this.hardwareAddress = hardwareAddress; - } - - public int getIpv4Addr() { - return ipv4Addr; - } - - public void setIpv4Addr(int ipv4Addr) { - this.ipv4Addr = ipv4Addr; - } - - public int getIpv4AddrMask() { - return ipv4AddrMask; - } - - public void setIpv4AddrMask(int ipv4AddrMask) { - this.ipv4AddrMask = ipv4AddrMask; - } - - /** - * @return the name - */ - public String getName() { - return name; - } - - /** - * @param name the name to set - */ - public void setName(String name) { - this.name = name; - } - - /** - * Write this message's binary format to the specified ByteBuffer - * @param data - */ - public void writeTo(ChannelBuffer data) { - data.writeBytes(hardwareAddress); - data.writeBytes(new byte[] {0, 0}); - - try { - byte[] name = this.name.getBytes("ASCII"); - if (name.length < OFP_MAX_PORT_NAME_LEN) { - data.writeBytes(name); - for (int i = name.length; i < OFP_MAX_PORT_NAME_LEN; ++i) { - data.writeByte((byte) 0); - } - } else { - data.writeBytes(name, 0, 15); - data.writeByte((byte) 0); - } - } catch (UnsupportedEncodingException e) { - throw new RuntimeException(e); - } - - data.writeInt(ipv4Addr); - data.writeInt(ipv4AddrMask); - } - - /** - * Read this message off the wire from the specified ByteBuffer - * @param data - */ - public void readFrom(ChannelBuffer data) { - if (this.hardwareAddress == null) - this.hardwareAddress = new byte[OFP_ETH_ALEN]; - data.readBytes(this.hardwareAddress); - data.readBytes(new byte[2]); - - byte[] name = new byte[16]; - data.readBytes(name); - // find the first index of 0 - int index = 0; - for (byte b : name) { - if (0 == b) - break; - ++index; - } - this.name = new String(Arrays.copyOf(name, index), - Charset.forName("ascii")); - ipv4Addr = data.readInt(); - ipv4AddrMask = data.readInt(); - } - - -} \ No newline at end of file diff --git a/src/main/java/com/bigswitch/floodlight/vendor/OFMirrorGetVendorDataReply.java b/src/main/java/com/bigswitch/floodlight/vendor/OFMirrorGetVendorDataReply.java deleted file mode 100644 index d8d83b404eb8b3a31d369e3e49fb9a15ed3760c6..0000000000000000000000000000000000000000 --- a/src/main/java/com/bigswitch/floodlight/vendor/OFMirrorGetVendorDataReply.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.bigswitch.floodlight.vendor; - -import org.openflow.protocol.Instantiable; -import org.openflow.protocol.vendor.OFVendorData; - - -/** - * Subclass of OFVendorData - */ -public class OFMirrorGetVendorDataReply extends OFNetmaskVendorData { - - - protected static Instantiable<OFVendorData> instantiable = - new Instantiable<OFVendorData>() { - public OFVendorData instantiate() { - return new OFMirrorGetVendorDataReply(); - } - }; - - /** - * @return a subclass of Instantiable<OFVendorData> that instantiates - * an instance of OFNetmaskGetVendorData. - */ - public static Instantiable<OFVendorData> getInstantiable() { - return instantiable; - } - - /** - * Opcode/dataType to represent REPLY of GET_MASK request - */ - public static final int BSN_GET_MIRRORING_REPLY = 5; - - /** - * Construct a get network mask vendor data - */ - public OFMirrorGetVendorDataReply() { - super(BSN_GET_MIRRORING_REPLY); - } - - /** - * Construct a get network mask vendor data for a specific table entry - */ - public OFMirrorGetVendorDataReply(byte tableIndex, int netMask) { - super(BSN_GET_MIRRORING_REPLY, tableIndex, netMask); - } -} diff --git a/src/main/java/com/bigswitch/floodlight/vendor/OFMirrorGetVendorDataRequest.java b/src/main/java/com/bigswitch/floodlight/vendor/OFMirrorGetVendorDataRequest.java deleted file mode 100644 index 6692cdfdde002140a87256f62cdb8cfa7a26f52d..0000000000000000000000000000000000000000 --- a/src/main/java/com/bigswitch/floodlight/vendor/OFMirrorGetVendorDataRequest.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.bigswitch.floodlight.vendor; - -import org.openflow.protocol.Instantiable; -import org.openflow.protocol.vendor.OFVendorData; - - -/** - * Subclass of OFVendorData - */ -public class OFMirrorGetVendorDataRequest extends OFNetmaskVendorData { - - - protected static Instantiable<OFVendorData> instantiable = - new Instantiable<OFVendorData>() { - public OFVendorData instantiate() { - return new OFMirrorGetVendorDataRequest(); - } - }; - - /** - * @return a subclass of Instantiable<OFVendorData> that instantiates - * an instance of OFNetmaskGetVendorData. - */ - public static Instantiable<OFVendorData> getInstantiable() { - return instantiable; - } - - /** - * Opcode/dataType to request an entry in the switch netmask table - */ - public static final int BSN_GET_MIRRORING_REQUEST = 4; - - /** - * Construct a get network mask vendor data - */ - public OFMirrorGetVendorDataRequest() { - super(BSN_GET_MIRRORING_REQUEST); - } - - /** - * Construct a get network mask vendor data for a specific table entry - */ - public OFMirrorGetVendorDataRequest(byte tableIndex, int netMask) { - super(BSN_GET_MIRRORING_REQUEST, tableIndex, netMask); - } -} diff --git a/src/main/java/com/bigswitch/floodlight/vendor/OFMirrorSetVendorData.java b/src/main/java/com/bigswitch/floodlight/vendor/OFMirrorSetVendorData.java deleted file mode 100644 index 5eba6512b7c4f07f9087aabe4f52e5cd0e300bee..0000000000000000000000000000000000000000 --- a/src/main/java/com/bigswitch/floodlight/vendor/OFMirrorSetVendorData.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.bigswitch.floodlight.vendor; - -import org.jboss.netty.buffer.ChannelBuffer; - -public class OFMirrorSetVendorData extends OFBigSwitchVendorData { - - /** - * Opcode/dataType to set mirroring - */ - public static final int BSN_SET_MIRRORING = 3; - - protected byte reportMirrorPorts; - protected byte pad1; - protected byte pad2; - protected byte pad3; - - public OFMirrorSetVendorData() { - super(BSN_SET_MIRRORING); - this.reportMirrorPorts=1; - } - - public byte getReportMirrorPorts() { - return reportMirrorPorts; - } - - public void setReportMirrorPorts(byte report) { - this.reportMirrorPorts = report; - } - - /** - * @return the total length vendor date - */ - @Override - public int getLength() { - return super.getLength() + 4; // 4 extra bytes - } - - /** - * Read the vendor data from the channel buffer - * @param data: the channel buffer from which we are deserializing - * @param length: the length to the end of the enclosing message - */ - public void readFrom(ChannelBuffer data, int length) { - super.readFrom(data, length); - reportMirrorPorts = data.readByte(); - pad1 = data.readByte(); - pad2 = data.readByte(); - pad3 = data.readByte(); - } - - /** - * Write the vendor data to the channel buffer - */ - public void writeTo(ChannelBuffer data) { - super.writeTo(data); - data.writeByte(reportMirrorPorts); - data.writeByte(pad1); - data.writeByte(pad2); - data.writeByte(pad3); - } - -} diff --git a/src/main/java/com/bigswitch/floodlight/vendor/OFNetmaskGetVendorDataReply.java b/src/main/java/com/bigswitch/floodlight/vendor/OFNetmaskGetVendorDataReply.java deleted file mode 100644 index 62d711020b71f8c962ff2fd068451acb3d9f9ab7..0000000000000000000000000000000000000000 --- a/src/main/java/com/bigswitch/floodlight/vendor/OFNetmaskGetVendorDataReply.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.bigswitch.floodlight.vendor; - -import org.openflow.protocol.Instantiable; -import org.openflow.protocol.vendor.OFVendorData; - - -/** - * Subclass of OFVendorData - * - * @author munish_mehta - */ -public class OFNetmaskGetVendorDataReply extends OFNetmaskVendorData { - - - protected static Instantiable<OFVendorData> instantiable = - new Instantiable<OFVendorData>() { - public OFVendorData instantiate() { - return new OFNetmaskGetVendorDataReply(); - } - }; - - /** - * @return a subclass of Instantiable<OFVendorData> that instantiates - * an instance of OFNetmaskGetVendorData. - */ - public static Instantiable<OFVendorData> getInstantiable() { - return instantiable; - } - - /** - * Opcode/dataType to represent REPLY of GET_MASK request - */ - public static final int BSN_GET_IP_MASK_ENTRY_REPLY = 2; - - /** - * Construct a get network mask vendor data - */ - public OFNetmaskGetVendorDataReply() { - super(BSN_GET_IP_MASK_ENTRY_REPLY); - } - - /** - * Construct a get network mask vendor data for a specific table entry - */ - public OFNetmaskGetVendorDataReply(byte tableIndex, int netMask) { - super(BSN_GET_IP_MASK_ENTRY_REPLY, tableIndex, netMask); - } -} diff --git a/src/main/java/com/bigswitch/floodlight/vendor/OFNetmaskGetVendorDataRequest.java b/src/main/java/com/bigswitch/floodlight/vendor/OFNetmaskGetVendorDataRequest.java deleted file mode 100644 index 1d075e9a3b9a87584f92b6ad7ea04442457f5ac9..0000000000000000000000000000000000000000 --- a/src/main/java/com/bigswitch/floodlight/vendor/OFNetmaskGetVendorDataRequest.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.bigswitch.floodlight.vendor; - -import org.openflow.protocol.Instantiable; -import org.openflow.protocol.vendor.OFVendorData; - - -/** - * Subclass of OFVendorData - * - * @author munish_mehta - */ -public class OFNetmaskGetVendorDataRequest extends OFNetmaskVendorData { - - - protected static Instantiable<OFVendorData> instantiable = - new Instantiable<OFVendorData>() { - public OFVendorData instantiate() { - return new OFNetmaskGetVendorDataRequest(); - } - }; - - /** - * @return a subclass of Instantiable<OFVendorData> that instantiates - * an instance of OFNetmaskGetVendorData. - */ - public static Instantiable<OFVendorData> getInstantiable() { - return instantiable; - } - - /** - * Opcode/dataType to request an entry in the switch netmask table - */ - public static final int BSN_GET_IP_MASK_ENTRY_REQUEST = 1; - - /** - * Construct a get network mask vendor data - */ - public OFNetmaskGetVendorDataRequest() { - super(BSN_GET_IP_MASK_ENTRY_REQUEST); - } - - /** - * Construct a get network mask vendor data for a specific table entry - */ - public OFNetmaskGetVendorDataRequest(byte tableIndex, int netMask) { - super(BSN_GET_IP_MASK_ENTRY_REQUEST, tableIndex, netMask); - } -} diff --git a/src/main/java/com/bigswitch/floodlight/vendor/OFNetmaskSetVendorData.java b/src/main/java/com/bigswitch/floodlight/vendor/OFNetmaskSetVendorData.java deleted file mode 100644 index 54421c10334acb5d7ca793c699f5005977a3a873..0000000000000000000000000000000000000000 --- a/src/main/java/com/bigswitch/floodlight/vendor/OFNetmaskSetVendorData.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.bigswitch.floodlight.vendor; - -import org.openflow.protocol.Instantiable; -import org.openflow.protocol.vendor.OFVendorData; - -public class OFNetmaskSetVendorData extends OFNetmaskVendorData { - - - protected static Instantiable<OFVendorData> instantiable = - new Instantiable<OFVendorData>() { - public OFVendorData instantiate() { - return new OFNetmaskSetVendorData(); - } - }; - - /** - * @return a subclass of Instantiable<OFVendorData> that instantiates - * an instance of OFNetmaskSetVendorData. - */ - public static Instantiable<OFVendorData> getInstantiable() { - return instantiable; - } - - /** - * Opcode/dataType to set an entry in the switch netmask table - */ - public static final int BSN_SET_IP_MASK_ENTRY = 0; - - /** - * Construct a get network mask vendor data - */ - public OFNetmaskSetVendorData() { - super(BSN_SET_IP_MASK_ENTRY); - } - - /** - * Construct a get network mask vendor data for a specific table entry - */ - public OFNetmaskSetVendorData(byte tableIndex, int netMask) { - super(BSN_SET_IP_MASK_ENTRY, tableIndex, netMask); - } -} diff --git a/src/main/java/com/bigswitch/floodlight/vendor/OFNetmaskVendorData.java b/src/main/java/com/bigswitch/floodlight/vendor/OFNetmaskVendorData.java deleted file mode 100644 index 66539f98e757e8b133eaf04c115557fad8904ebd..0000000000000000000000000000000000000000 --- a/src/main/java/com/bigswitch/floodlight/vendor/OFNetmaskVendorData.java +++ /dev/null @@ -1,88 +0,0 @@ -package com.bigswitch.floodlight.vendor; - -import org.jboss.netty.buffer.ChannelBuffer; - - -/** - * Class that represents the vendor data in the netmask table request - * extension implemented by Arista switches - * - * @author munish_mehta (munish.mehta@bigswitch.com) - */ - -public class OFNetmaskVendorData extends OFBigSwitchVendorData { - - /** - * Table index for set or get of the the entry from netmask table - */ - protected byte tableIndex; - protected byte pad1; - protected byte pad2; - protected byte pad3; - protected int netMask; - - public OFNetmaskVendorData(int dataType) { - super(dataType); - this.tableIndex = 0; - this.netMask = (int)0xffffffffL; - } - - public OFNetmaskVendorData(int dataType, byte table_index, int netmask) { - super(dataType); - this.tableIndex = table_index; - this.netMask = netmask; - } - - - public byte getTableIndex() { - return tableIndex; - } - - public void setTableIndex(byte tableIndex) { - this.tableIndex = tableIndex; - } - - public int getNetMask() { - return netMask; - } - - public void setNetMask(int netMask) { - this.netMask = netMask; - } - - /** - * @return the total length of the netmask vendor data - */ - @Override - public int getLength() { - return super.getLength() + 8; // 8 extra bytes - } - - /** - * Read the vendor data from the channel buffer - * @param data: the channel buffer from which we are deserializing - * @param length: the length to the end of the enclosing message - */ - public void readFrom(ChannelBuffer data, int length) { - super.readFrom(data, length); - tableIndex = data.readByte(); - pad1 = data.readByte(); - pad2 = data.readByte(); - pad3 = data.readByte(); - netMask = data.readInt(); - } - - /** - * Write the vendor data to the channel buffer - */ - public void writeTo(ChannelBuffer data) { - super.writeTo(data); - data.writeByte(tableIndex); - data.writeByte(pad1); - data.writeByte(pad2); - data.writeByte(pad3); - data.writeInt(netMask); - } - - -} diff --git a/src/main/java/com/bigswitch/floodlight/vendor/OFNiciraVendorActionFactory.java b/src/main/java/com/bigswitch/floodlight/vendor/OFNiciraVendorActionFactory.java deleted file mode 100644 index 716373794f263aa3db5a60d316d982f7ce668a5f..0000000000000000000000000000000000000000 --- a/src/main/java/com/bigswitch/floodlight/vendor/OFNiciraVendorActionFactory.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.bigswitch.floodlight.vendor; - -import org.jboss.netty.buffer.ChannelBuffer; -import org.openflow.protocol.action.OFActionVendor; -import org.openflow.protocol.factory.OFVendorActionFactory; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class OFNiciraVendorActionFactory implements OFVendorActionFactory { - protected static Logger logger = - LoggerFactory.getLogger(OFNiciraVendorActionFactory.class); - - static class OFActionNiciraVendorDemux extends OFActionNiciraVendor { - OFActionNiciraVendorDemux() { - super((short) 0); - } - } - - @Override - public OFActionVendor readFrom(ChannelBuffer data) { - data.markReaderIndex(); - OFActionNiciraVendorDemux demux = new OFActionNiciraVendorDemux(); - demux.readFrom(data); - data.resetReaderIndex(); - - switch(demux.getSubtype()) { - case OFActionNiciraTtlDecrement.TTL_DECREMENT_SUBTYPE: - OFActionNiciraTtlDecrement ttlAction = new OFActionNiciraTtlDecrement(); - ttlAction.readFrom(data); - return ttlAction; - default: - logger.error("Unknown Nicira vendor action subtype: "+demux.getSubtype()); - return null; - } - } - -} diff --git a/src/main/java/com/bigswitch/floodlight/vendor/OFVendorActions.java b/src/main/java/com/bigswitch/floodlight/vendor/OFVendorActions.java deleted file mode 100644 index 0b555340cea7abe3364d1c05b7feaa7be0b3ff0d..0000000000000000000000000000000000000000 --- a/src/main/java/com/bigswitch/floodlight/vendor/OFVendorActions.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.bigswitch.floodlight.vendor; - -import org.openflow.protocol.factory.OFVendorActionRegistry; - -public final class OFVendorActions { - public static final void registerStandardVendorActions() { - OFVendorActionRegistry registry = OFVendorActionRegistry.getInstance(); - registry.register(OFActionBigSwitchVendor.BSN_VENDOR_ID, new OFBigSwitchVendorActionFactory()); - registry.register(OFActionNiciraVendor.NICIRA_VENDOR_ID, new OFNiciraVendorActionFactory()); - } -} \ No newline at end of file diff --git a/src/main/java/net/floodlightcontroller/core/ControllerId.java b/src/main/java/net/floodlightcontroller/core/ControllerId.java new file mode 100644 index 0000000000000000000000000000000000000000..6fd347b234219ae4bee0b36923d5cf3893018b0c --- /dev/null +++ b/src/main/java/net/floodlightcontroller/core/ControllerId.java @@ -0,0 +1,61 @@ +package net.floodlightcontroller.core; + +import org.sdnplatform.sync.ISyncService; +import org.sdnplatform.sync.internal.config.ClusterConfig; + +import com.google.common.base.Optional; + +/** This class represents a unique id of this controller node. It is derived from + * the node id as returned by {@link ISyncService#getLocalNodeId()}. + * <p> + * Note that the unconfigured Node Id is not supported. Users are encouraged to + * represent an unconfigured Controller Node by {@link Optional#absent()}. + * + * @author Andreas Wundsam <andreas.wundsam@bigswitch.com> + */ +public class ControllerId { + private final short nodeId; + + private ControllerId(short nodeId) { + if(nodeId == ClusterConfig.NODE_ID_UNCONFIGURED) + throw new IllegalArgumentException("nodeId is unconfigured"); + + this.nodeId = nodeId; + } + + public short getNodeId() { + return nodeId; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + nodeId; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + ControllerId other = (ControllerId) obj; + if (nodeId != other.nodeId) + return false; + return true; + } + + @Override + public String toString() { + return Short.toString(nodeId); + } + + public static ControllerId of(short nodeId) { + return new ControllerId(nodeId); + } + +} diff --git a/src/main/java/net/floodlightcontroller/core/Deliverable.java b/src/main/java/net/floodlightcontroller/core/Deliverable.java new file mode 100644 index 0000000000000000000000000000000000000000..c82f31d85bd2ffab1567ac8fee25a926315f77b7 --- /dev/null +++ b/src/main/java/net/floodlightcontroller/core/Deliverable.java @@ -0,0 +1,38 @@ +package net.floodlightcontroller.core; + +/** + * abstracts the 'back side' of a Future that is being listened on, i.e., an + * object that receives a result or an error of the computaton once it is ready. + * A deliverable can accept multiple computation results, indicated by the + * return value of deliver. + * + * @author Andreas Wundsam <andreas.wundsam@bigswitch.com> + * @param <T> + * type of the result of the computation + */ +public interface Deliverable<T> { + public static enum Status { + DONE, + CONTINUE + } + + /** + * deliver the result after a successful computation has completed + * + * @param msg + * result + * @return whether the delivery is complete with this result. + **/ + public void deliver(T msg); + + /** deliver an error result for the computation + * @param cause throwable that describes the error + */ + void deliverError(Throwable cause); + + /** whether or not the deliverable has been completed before. + */ + boolean isDone(); + + boolean cancel(boolean mayInterruptIfRunning); +} diff --git a/src/main/java/net/floodlightcontroller/core/DeliverableListenableFuture.java b/src/main/java/net/floodlightcontroller/core/DeliverableListenableFuture.java new file mode 100644 index 0000000000000000000000000000000000000000..1e724ac51819fc6a52d26a649ded028f0bd917cf --- /dev/null +++ b/src/main/java/net/floodlightcontroller/core/DeliverableListenableFuture.java @@ -0,0 +1,22 @@ +package net.floodlightcontroller.core; + +import com.google.common.util.concurrent.AbstractFuture; + +/** Implementation of a ListenableFuture that provides a Deliverable interface to + * the provider. + * + * @author Andreas Wundsam <andreas.wundsam@bigswitch.com> + * @see Deliverable + * @param <T> + */ +public class DeliverableListenableFuture<T> extends AbstractFuture<T> implements Deliverable<T> { + @Override + public void deliver(final T result) { + set(result); + } + + @Override + public void deliverError(final Throwable cause) { + setException(cause); + } +} diff --git a/src/main/java/net/floodlightcontroller/core/HARole.java b/src/main/java/net/floodlightcontroller/core/HARole.java new file mode 100644 index 0000000000000000000000000000000000000000..0c2886f1e179579fc0b011317cdf8362a95cab9f --- /dev/null +++ b/src/main/java/net/floodlightcontroller/core/HARole.java @@ -0,0 +1,88 @@ +package net.floodlightcontroller.core; + +import org.projectfloodlight.openflow.protocol.OFControllerRole; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * HARole describes the role that a given controller node currently plays in the + * management of the SDN network. Controller nodes can be either + * <ul> + * <li>ACTIVE - currently controlling the network + * <li>STANDBY - standing by in case of a fail-over from the ACTIVE node + * </ul> + * At any given time there SHOULD be at most one ACTIVE node in the network + * (this invariant cannot be strictly guranteed for certain split-brain + * situtations). There can be multiple STANDBY controllers. There are other + * HA-related roles in the system. Try to not confuse them. + * <ul> + * <li>On the cluster management/bigsync layer {@link ISyncService} determines a + * DOMAIN LEADER / DOMAIN FOLLOWER (which are exposed via + * <li>On the OF layer, switch connections can be in either MASTER, SLAVE or + * EQUAL {@link Role} (exposed by {@link IOFSwitchListener}). + * </ul> + * Most applications and modules trying to decide something on the ACTIVE node + * should base that decision on the HARole. + * + * @author Andreas Wundsam <andreas.wundsam@bigswitch.com> + */ +public enum HARole { + /** This controller node is currently actively managing the SDN network. At any given + * time, there should be at most one ACTIVE node. When the ACTIVE node fails, a STANDBY + * node is determined to become ACTIVE. + */ + ACTIVE(OFControllerRole.ROLE_MASTER), + + /** This controller node is currently standing by and not managing the networking. There + * may be more than one STANDBY nodes in the network. + */ + STANDBY(OFControllerRole.ROLE_SLAVE); + + private static final Logger logger = LoggerFactory.getLogger(HARole.class); + private final OFControllerRole ofRole; + + HARole(OFControllerRole ofRole) { + this.ofRole = ofRole; + } + + /** a backwards-compatible {@link #valueOf} that accepts the old terms "MASTER" and "SLAVE" + * and normalizes them to ACTIVE and STANDBY. + * + * @param roleString + * @return an HARole + * @throws IllegalArgumentException - if no such role can be found. + */ + public static HARole valueOfBackwardsCompatible(String roleString) throws IllegalArgumentException { + roleString = roleString.trim().toUpperCase(); + if("MASTER".equals(roleString)) { + logger.warn("got legacy role name MASTER - normalized to ACTIVE", roleString); + if(logger.isDebugEnabled()) { + logger.debug("Legacy role call stack", new IllegalArgumentException()); + } + roleString = "ACTIVE"; + } else if ("SLAVE".equals(roleString)) { + logger.warn("got legacy role name SLAVE - normalized to STANDBY", roleString); + if(logger.isDebugEnabled()) { + logger.debug("Legacy role call stack", new IllegalArgumentException()); + } + roleString = "STANDBY"; + } + return valueOf(roleString); + } + + public OFControllerRole getOFRole() { + return ofRole; + } + + public static HARole ofOFRole(OFControllerRole role) { + switch(role) { + case ROLE_MASTER: + case ROLE_EQUAL: + return ACTIVE; + case ROLE_SLAVE: + return STANDBY; + default: + throw new IllegalArgumentException("Unmappable controller role: " + role); + } + } +} diff --git a/src/main/java/net/floodlightcontroller/core/IFloodlightProviderService.java b/src/main/java/net/floodlightcontroller/core/IFloodlightProviderService.java index c0aa1fd6d5faa9426c7580d7e02fc685c0a96021..c91e55fc1720c5e36307cfb108084fe9f156da0e 100644 --- a/src/main/java/net/floodlightcontroller/core/IFloodlightProviderService.java +++ b/src/main/java/net/floodlightcontroller/core/IFloodlightProviderService.java @@ -17,8 +17,6 @@ package net.floodlightcontroller.core; -import java.util.HashMap; - import java.util.List; import java.util.Set; import java.util.Map; @@ -26,11 +24,22 @@ import java.util.Map; import net.floodlightcontroller.core.module.IFloodlightService; import net.floodlightcontroller.packet.Ethernet; -import org.openflow.protocol.OFMessage; -import org.openflow.protocol.OFType; -import org.openflow.protocol.factory.BasicFactory; -import org.openflow.vendor.nicira.OFRoleVendorData; - +import org.jboss.netty.util.Timer; + +import net.floodlightcontroller.core.FloodlightContext; +import net.floodlightcontroller.core.HARole; +import net.floodlightcontroller.core.IHAListener; +import net.floodlightcontroller.core.IInfoProvider; +import net.floodlightcontroller.core.IOFMessageListener; +import net.floodlightcontroller.core.IOFSwitch; +import net.floodlightcontroller.core.RoleInfo; +import net.floodlightcontroller.core.internal.RoleManager; +import net.floodlightcontroller.core.internal.Controller.IUpdate; +import net.floodlightcontroller.core.internal.Controller.ModuleLoaderState; + +import net.floodlightcontroller.core.FloodlightContextStore; +import org.projectfloodlight.openflow.protocol.OFMessage; +import org.projectfloodlight.openflow.protocol.OFType; /** * The interface exposed by the core bundle that allows you to interact * with connected switches. @@ -47,37 +56,6 @@ public interface IFloodlightProviderService extends public static final String CONTEXT_PI_PAYLOAD = "net.floodlightcontroller.core.IFloodlightProvider.piPayload"; - /** - * The role of the controller as used by the OF 1.2 and OVS failover and - * load-balancing mechanism. - */ - public static enum Role { - EQUAL(OFRoleVendorData.NX_ROLE_OTHER), - MASTER(OFRoleVendorData.NX_ROLE_MASTER), - SLAVE(OFRoleVendorData.NX_ROLE_SLAVE); - - private final int nxRole; - - private Role(int nxRole) { - this.nxRole = nxRole; - } - - private static Map<Integer,Role> nxRoleToEnum - = new HashMap<Integer,Role>(); - static { - for(Role r: Role.values()) - nxRoleToEnum.put(r.toNxRole(), r); - } - public int toNxRole() { - return nxRole; - } - // Return the enum representing the given nxRole or null if no - // such role exists - public static Role fromNxRole(int nxRole) { - return nxRoleToEnum.get(nxRole); - } - }; - /** * A FloodlightContextStore object that can be used to retrieve the * packet-in payload @@ -85,6 +63,15 @@ public interface IFloodlightProviderService extends public static final FloodlightContextStore<Ethernet> bcStore = new FloodlightContextStore<Ethernet>(); + /** + * Service name used in the service directory representing + * the OpenFlow controller-switch channel + * + * @see ILocalServiceAddressTracker + * @see IClusterServiceAddressDirectory + */ + public static final String SERVICE_DIRECTORY_SERVICE_NAME = "openflow"; + /** * Adds an OpenFlow message listener * @param type The OFType the component wants to listen for @@ -106,77 +93,52 @@ public interface IFloodlightProviderService extends public Map<OFType, List<IOFMessageListener>> getListeners(); /** - * If the switch with the given DPID is known to any controller in the - * cluster, this method returns the associated IOFSwitch instance. As such - * the returned switches not necessarily connected or in master role for - * the local controller. - * - * Multiple calls to this method with the same DPID may return different - * IOFSwitch references. A caller must not store or otherwise rely on - * IOFSwitch references to be constant over the lifecycle of a switch. - * - * @param dpid the dpid of the switch to query - * @return the IOFSwitch instance associated with the dpid, null if no - * switch with the dpid is known to the cluster + * Get the current role of the controller */ - public IOFSwitch getSwitch(long dpid); + public HARole getRole(); /** - * Returns a snapshot of the set DPIDs for all known switches. - * - * The returned set is owned by the caller: the caller can modify it at - * will and changes to the known switches are not reflected in the returned - * set. The caller needs to call getAllSwitchDpids() if an updated - * version is needed. - * - * See {@link #getSwitch(long)} for what "known" switch is. - * @return the set of DPIDs of all known switches + * Get the current role of the controller */ - public Set<Long> getAllSwitchDpids(); + public RoleInfo getRoleInfo(); /** - * Return a snapshot - * FIXME: asdf - * @return + * Get the current mapping of controller IDs to their IP addresses + * Returns a copy of the current mapping. + * @see IHAListener */ - public Map<Long,IOFSwitch> getAllSwitchMap(); + public Map<String,String> getControllerNodeIPs(); /** - * Get the current role of the controller + * Gets the ID of the controller */ - public Role getRole(); + public String getControllerId(); /** - * Get the current role of the controller + * Gets the controller hostname + * @return the controller hostname */ - public RoleInfo getRoleInfo(); + public String getOFHostname(); /** - * Get the current mapping of controller IDs to their IP addresses - * Returns a copy of the current mapping. - * @see IHAListener + * Gets the controller's openflow port + * @return the controller's openflow port */ - public Map<String,String> getControllerNodeIPs(); - + public int getOFPort(); /** * Set the role of the controller * @param role The new role for the controller node * @param changeDescription The reason or other information for this role change */ - public void setRole(Role role, String changeDescription); - - /** - * Add a switch listener - * @param listener The module that wants to listen for events - */ - public void addOFSwitchListener(IOFSwitchListener listener); + public void setRole(HARole role, String changeDescription); /** - * Remove a switch listener - * @param listener The The module that no longer wants to listen for events + * Add an update task for asynchronous, serialized execution + * + * @param update */ - public void removeOFSwitchListener(IOFSwitchListener listener); + public void addUpdateToQueue(IUpdate update); /** * Adds a listener for HA role events @@ -190,53 +152,13 @@ public interface IFloodlightProviderService extends */ public void removeHAListener(IHAListener listener); - /** - * Add a listener for ready-for-flow-reconcile events - * @param l - */ - public void addReadyForReconcileListener(IReadyForReconcileListener l); - - /** - * Terminate the process - */ - public void terminate(); - - /** - * Re-injects an OFMessage back into the packet processing chain - * @param sw The switch to use for the message - * @param msg the message to inject - * @return True if successfully re-injected, false otherwise - * @throws NullPointerException if switch or msg is null - */ - public boolean injectOfMessage(IOFSwitch sw, OFMessage msg); - - /** - * Re-injects an OFMessage back into the packet processing chain - * @param sw The switch to use for the message - * @param msg the message to inject - * @param bContext a floodlight context to use if required. Can be null - * @return True if successfully re-injected, false otherwise - * @throws NullPointerException if switch or msg is null - */ - public boolean injectOfMessage(IOFSwitch sw, OFMessage msg, - FloodlightContext bContext); - /** * Process written messages through the message listeners for the controller * @param sw The switch being written to * @param m the message - * @param bc any accompanying context object. Can be null in which case a - * new context will be allocated and passed to listeners * @throws NullPointerException if switch or msg is null */ - public void handleOutgoingMessage(IOFSwitch sw, OFMessage m, - FloodlightContext bc); - - /** - * Gets the BasicFactory - * @return an OpenFlow message factory - */ - public BasicFactory getOFMessageFactory(); + public void handleOutgoingMessage(IOFSwitch sw, OFMessage m); /** * Run the main I/O loop of the Controller. @@ -265,21 +187,12 @@ public interface IFloodlightProviderService extends */ public Map<String, Object> getControllerInfo(String type); - /** * Return the controller start time in milliseconds * @return */ public long getSystemStartTime(); - /** - * Configure controller to always clear the flow table on the switch, - * when it connects to controller. This will be true for first time switch - * reconnect, as well as a switch re-attaching to Controller after HA - * switch over to ACTIVE role - */ - public void setAlwaysClearFlowsOnSwActivate(boolean value); - /** * Get controller memory information */ @@ -292,30 +205,38 @@ public interface IFloodlightProviderService extends public Long getUptime(); /** - * Adds an OFSwitch driver - * @param manufacturerDescriptionPrefix Register the given prefix - * with the driver. - * @param driver A IOFSwitchDriver instance to handle IOFSwitch instaniation - * for the given manufacturer description prefix - * @throws IllegalStateException If the the manufacturer description is - * already registered - * @throws NullPointerExeption if manufacturerDescriptionPrefix is null - * @throws NullPointerExeption if driver is null + * Get the set of port prefixes that will define an UPLINK port. + * @return The set of prefixes */ - public void addOFSwitchDriver(String desc, IOFSwitchDriver driver); + public Set<String> getUplinkPortPrefixSet(); + + + public void handleMessage(IOFSwitch sw, OFMessage m, + FloodlightContext bContext); /** - * Record a switch event in in-memory debug-event - * @param switchDPID - * @param reason Reason for this event - * @param flushNow see debug-event flushing in IDebugEventService + * Gets a hash wheeled timer to be used for for timeout scheduling + * @return a hash wheeled timer */ - public void addSwitchEvent(long switchDPID, String reason, boolean flushNow); + public Timer getTimer(); /** - * Get the set of port prefixes that will define an UPLINK port. - * @return The set of prefixes + * Gets the role manager + * @return the role manager */ - public Set<String> getUplinkPortPrefixSet(); + public RoleManager getRoleManager(); + + /** + * Gets the current module loading state. + * @return the current module loading state. + */ + ModuleLoaderState getModuleLoaderState(); + + /** + * Gets the current number of worker threads + * @return Used for netty setup + */ + public int getWorkerThreads(); } + diff --git a/src/main/java/net/floodlightcontroller/core/IHAListener.java b/src/main/java/net/floodlightcontroller/core/IHAListener.java index 2ffe82fd586b1012f29c24d397eb6c212a5aa02e..399a5b6a5968a8392305abefe6c5adf518f0f3ea 100644 --- a/src/main/java/net/floodlightcontroller/core/IHAListener.java +++ b/src/main/java/net/floodlightcontroller/core/IHAListener.java @@ -18,28 +18,43 @@ package net.floodlightcontroller.core; import java.util.Map; -public interface IHAListener extends IListener<HAListenerTypeMarker> { +/** Listener interface for the {@link HARole} of the local controller. Listeners + * are notified when the controller transitions to role {@link HARole#ACTIVE}. + * <p> + * <strong>NOTE:</strong> The floodlight platform currently does not support + * a transition to the STANDBY role. + * + * @author Andreas Wundsam <andreas.wundsam@bigswitch.com> + */ +public interface IHAListener extends IListener<HAListenerTypeMarker> { /** - * This notification is fired if the controller's initial role was SLAVE - * and the controller is now transitioning to MASTER. - * Modules need to read their initial role in startUp from floodlight - * provider. + * This notification is fired if the controller's initial role was STANDBY + * and the controller is now transitioning to ACTIVE. + * Clients can query the current (and initial) role from + * {@link IFloodlightProviderService#getRole()} (in startup). */ - public void transitionToMaster(); + public void transitionToActive(); /** - * Gets called when the IP addresses of the controller nodes in the - * controller cluster change. All parameters map controller ID to - * the controller's IP. - * - * @param curControllerNodeIPs The current mapping of controller IDs to IP - * @param addedControllerNodeIPs These IPs were added since the last update - * @param removedControllerNodeIPs These IPs were removed since the last update + * This notification is fired if the controller's initial role was ACTIVE + * and the controller is now transitioning to STANDBY. + * <strong>NOTE:</strong> The floodlight platform currently terminates + * after the transition to STANDBY. Clients should prepare for the shutdown + * in transitionToStandby (e.g., ensure that current updates to operational + * states are fully synced). + */ + public void transitionToStandby(); + + /** + * Gets called when the IP addresses of the controller nodes in the controller cluster + * change. All parameters map controller ID to the controller's IP. + * + * @param curControllerNodeIPs + * @param addedControllerNodeIPs + * @param removedControllerNodeIPs */ - public void controllerNodeIPsChanged( - Map<String, String> curControllerNodeIPs, - Map<String, String> addedControllerNodeIPs, - Map<String, String> removedControllerNodeIPs - ); + public void controllerNodeIPsChanged(Map<String, String> curControllerNodeIPs, + Map<String, String> addedControllerNodeIPs, + Map<String, String> removedControllerNodeIPs); } diff --git a/src/main/java/net/floodlightcontroller/core/IOFConnection.java b/src/main/java/net/floodlightcontroller/core/IOFConnection.java new file mode 100644 index 0000000000000000000000000000000000000000..a14021fbd47b087495b57bdd3b1736966df0a798 --- /dev/null +++ b/src/main/java/net/floodlightcontroller/core/IOFConnection.java @@ -0,0 +1,62 @@ +package net.floodlightcontroller.core; + +import java.net.SocketAddress; + +import java.util.Date; +import org.projectfloodlight.openflow.protocol.OFFactory; +import org.projectfloodlight.openflow.types.DatapathId; +import org.projectfloodlight.openflow.types.OFAuxId; + + +/** Contract for an openflow connection to a switch. + * Provides message write and request/response handling capabilities. + * + * @author Andreas Wundsam <andreas.wundsam@bigswitch.com> + */ +public interface IOFConnection extends IOFMessageWriter { + + /** + * Retrieves the date the connection connected to this controller + * @return the date + */ + Date getConnectedSince(); + + /** + * Flush all flows queued for this switch in the current thread. + * NOTE: The contract is limited to the current thread + */ + void flush(); + + /** @return the DatapathId of the switch associated with the connection */ + DatapathId getDatapathId(); + + /** @return the OFAuxId of the this connection */ + OFAuxId getAuxId(); + + /** + * Get the IP address of the remote (switch) end of the connection + * @return the inet address + */ + SocketAddress getRemoteInetAddress(); + + /** + * Get the IP address of the local end of the connection + * + * @return the inet address + */ + SocketAddress getLocalInetAddress(); + + /** + * Get's the OFFactory that this connection was constructed with. + * This is the factory that was found in the features reply during + * the channel handshake + * @return The connection's OFFactory + */ + OFFactory getOFFactory(); + + /** @return whether this connection is currently (still) connected to the controller. + */ + boolean isConnected(); + + +} diff --git a/src/main/java/net/floodlightcontroller/core/IOFConnectionBackend.java b/src/main/java/net/floodlightcontroller/core/IOFConnectionBackend.java new file mode 100644 index 0000000000000000000000000000000000000000..0ec7a9e1b2b081b34c813d5d9dd959028e1e49cc --- /dev/null +++ b/src/main/java/net/floodlightcontroller/core/IOFConnectionBackend.java @@ -0,0 +1,23 @@ +package net.floodlightcontroller.core; + +import net.floodlightcontroller.core.internal.IOFConnectionListener; + +public interface IOFConnectionBackend extends IOFConnection { + /** + * Disconnect the channel + */ + void disconnect(); + + /** + * Cancel all pending request + */ + void cancelAllPendingRequests(); + + /** @return whether the output stream associated with this connection + * is currently writeable (for throttling) + */ + boolean isWritable(); + + /** set the message/closing listener for this connection */ + void setListener(IOFConnectionListener listener); +} diff --git a/src/main/java/net/floodlightcontroller/core/IOFMessageListener.java b/src/main/java/net/floodlightcontroller/core/IOFMessageListener.java index 00fdac1f99379ee8aa1e5dfee00de6bd4da9a4f1..1fe71529c9e64d364c92a0d2255083169a737dc8 100644 --- a/src/main/java/net/floodlightcontroller/core/IOFMessageListener.java +++ b/src/main/java/net/floodlightcontroller/core/IOFMessageListener.java @@ -17,8 +17,8 @@ package net.floodlightcontroller.core; -import org.openflow.protocol.OFMessage; -import org.openflow.protocol.OFType; +import org.projectfloodlight.openflow.protocol.OFMessage; +import org.projectfloodlight.openflow.protocol.OFType; /** * diff --git a/src/main/java/net/floodlightcontroller/core/IOFMessageWriter.java b/src/main/java/net/floodlightcontroller/core/IOFMessageWriter.java new file mode 100644 index 0000000000000000000000000000000000000000..bd27181f19e9170fac71db1557942dcc8cef0510 --- /dev/null +++ b/src/main/java/net/floodlightcontroller/core/IOFMessageWriter.java @@ -0,0 +1,79 @@ +/** +* Copyright 2011, Big Switch Networks, Inc. +* Originally created by David Erickson, Stanford University +* +* Licensed under the Apache License, Version 2.0 (the "License"); you may +* not use this file except in compliance with the License. You may obtain +* a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +* License for the specific language governing permissions and limitations +* under the License. +**/ + +package net.floodlightcontroller.core; + +import java.util.List; + +import org.projectfloodlight.openflow.protocol.OFMessage; +import org.projectfloodlight.openflow.protocol.OFRequest; +import org.projectfloodlight.openflow.protocol.OFStatsReply; +import org.projectfloodlight.openflow.protocol.OFStatsRequest; + +import com.google.common.util.concurrent.ListenableFuture; + +/** + * An interface to describe classes that write OF messages. + * E.g. IOFSwitch, IOFConnection + */ + +public interface IOFMessageWriter{ + + /** + * Writes to the OFMessage to the output stream. + * + * <p><b>Note:</b> this method has fire-and-forget semantics. When the connection is + * not currently connected, it will silently discard the messages. + * + * @param m + */ + void write(OFMessage m); + + /** + * Writes the list of messages to the output stream. + * + * <p><b>Note:</b> this method has fire-and-forget semantics. When the connection is + * not currently connected, it will silently discard the messages. + * + * @param msglist + */ + void write(Iterable<OFMessage> msglist); + + /** write an OpenFlow Request message, register for a single corresponding reply message + * or error message. + * + * @param request + * @return a Future object that can be used to retrieve the asynchrounous + * response when available. + * + * If the connection is not currently connected, will + * return a Future that immediately fails with a @link{SwitchDisconnectedException}. + */ + <R extends OFMessage> ListenableFuture<R> writeRequest(OFRequest<R> request); + + /** write a Stats (Multipart-) request, register for all corresponding reply messages. + * Returns a Future object that can be used to retrieve the List of asynchronous + * OFStatsReply messages when it is available. + * + * @param request stats request + * @return Future object wrapping OFStatsReply + * If the connection is not currently connected, will + * return a Future that immediately fails with a @link{SwitchDisconnectedException}. + */ + <REPLY extends OFStatsReply> ListenableFuture<List<REPLY>> writeStatsRequest( + OFStatsRequest<REPLY> request); +} diff --git a/src/main/java/net/floodlightcontroller/core/IOFSwitch.java b/src/main/java/net/floodlightcontroller/core/IOFSwitch.java index fb27460401007cdc639d4c8ee38cc73101ce0c40..3b46ded638f72120733d9865bc58a37c82409512 100644 --- a/src/main/java/net/floodlightcontroller/core/IOFSwitch.java +++ b/src/main/java/net/floodlightcontroller/core/IOFSwitch.java @@ -1,5 +1,4 @@ /** -* Copyright 2011, Big Switch Networks, Inc. * Originally created by David Erickson, Stanford University * * Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -17,248 +16,112 @@ package net.floodlightcontroller.core; -import java.io.IOException; import java.net.SocketAddress; import java.util.Collection; -import java.util.Date; import java.util.List; import java.util.Map; -import java.util.concurrent.Future; -import net.floodlightcontroller.core.IFloodlightProviderService.Role; -import net.floodlightcontroller.core.internal.Controller; -import net.floodlightcontroller.debugcounter.IDebugCounterService; -import net.floodlightcontroller.debugcounter.IDebugCounterService.CounterException; -import net.floodlightcontroller.threadpool.IThreadPoolService; -import net.floodlightcontroller.util.OrderedCollection; - -import org.jboss.netty.channel.Channel; -import org.openflow.protocol.OFFeaturesReply; -import org.openflow.protocol.OFMessage; -import org.openflow.protocol.OFPortStatus; -import org.openflow.protocol.OFStatisticsReply; -import org.openflow.protocol.OFStatisticsRequest; -import org.openflow.protocol.statistics.OFDescriptionStatistics; -import org.openflow.protocol.statistics.OFStatistics; +import java.util.Set; +import java.util.Date; + +import org.projectfloodlight.openflow.protocol.OFActionType; +import org.projectfloodlight.openflow.protocol.OFCapabilities; +import org.projectfloodlight.openflow.protocol.OFControllerRole; +import org.projectfloodlight.openflow.protocol.OFFactory; +import org.projectfloodlight.openflow.protocol.OFMessage; +import org.projectfloodlight.openflow.protocol.OFPortDesc; +import org.projectfloodlight.openflow.protocol.OFRequest; +import org.projectfloodlight.openflow.protocol.OFStatsReply; +import org.projectfloodlight.openflow.protocol.OFStatsRequest; +import org.projectfloodlight.openflow.types.DatapathId; +import org.projectfloodlight.openflow.types.OFPort; + +import com.google.common.collect.ImmutableList; +import com.google.common.util.concurrent.ListenableFuture; /** - * - * - * @author David Erickson (daviderickson@cs.stanford.edu) + * An openflow switch connecting to the controller. This interface offers + * methods for interacting with switches using OpenFlow, and retrieving + * information about the switches. */ -public interface IOFSwitch { +public interface IOFSwitch extends IOFMessageWriter { // Attribute keys - public static final String SWITCH_DESCRIPTION_FUTURE = "DescriptionFuture"; - public static final String SWITCH_SUPPORTS_NX_ROLE = "supportsNxRole"; - public static final String SWITCH_IS_CORE_SWITCH = "isCoreSwitch"; - public static final String PROP_FASTWILDCARDS = "FastWildcards"; - public static final String PROP_REQUIRES_L3_MATCH = "requiresL3Match"; - public static final String PROP_SUPPORTS_OFPP_TABLE = "supportsOfppTable"; - public static final String PROP_SUPPORTS_OFPP_FLOOD = "supportsOfppFlood"; - public static final String PROP_SUPPORTS_NETMASK_TBL = "supportsNetmaskTbl"; - - public enum OFPortType { - NORMAL("normal"), // normal port (default) - TUNNEL("tunnel"), // tunnel port - UPLINK("uplink"), // uplink port (on a virtual switch) - MANAGEMENT("management"), // for in-band management - TUNNEL_LOOPBACK("tunnel-loopback"); - - private final String value; - OFPortType(String v) { - value = v; - } - - @Override - public String toString() { - return value; - } - - public static OFPortType fromString(String str) { - for (OFPortType m : OFPortType.values()) { - if (m.value.equals(str)) { - return m; - } - } - return OFPortType.NORMAL; - } - } - - /** - * Describes a change of an open flow port - */ - public static class PortChangeEvent { - public final ImmutablePort port; - public final PortChangeType type; - /** - * @param port - * @param type - */ - public PortChangeEvent(ImmutablePort port, - PortChangeType type) { - this.port = port; - this.type = type; - } - /* (non-Javadoc) - * @see java.lang.Object#hashCode() - */ - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((port == null) ? 0 : port.hashCode()); - result = prime * result + ((type == null) ? 0 : type.hashCode()); - return result; - } - /* (non-Javadoc) - * @see java.lang.Object#equals(java.lang.Object) - */ - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (obj == null) return false; - if (getClass() != obj.getClass()) return false; - PortChangeEvent other = (PortChangeEvent) obj; - if (port == null) { - if (other.port != null) return false; - } else if (!port.equals(other.port)) return false; - if (type != other.type) return false; - return true; - } - /* (non-Javadoc) - * @see java.lang.Object#toString() - */ - @Override - public String toString() { - return "[" + type + " " + port.toBriefString() + "]"; - } + // These match our YANG schema, make sure they are in sync + public static final String SWITCH_DESCRIPTION_FUTURE = "description-future"; + public static final String SWITCH_DESCRIPTION_DATA = "description-data"; + public static final String SWITCH_SUPPORTS_NX_ROLE = "supports-nx-role"; + public static final String PROP_FASTWILDCARDS = "fast-wildcards"; + public static final String PROP_REQUIRES_L3_MATCH = "requires-l3-match"; + public static final String PROP_SUPPORTS_OFPP_TABLE = "supports-ofpp-table"; + public static final String PROP_SUPPORTS_OFPP_FLOOD = "supports-ofpp-flood"; + public static final String PROP_SUPPORTS_NETMASK_TBL = "supports-netmask-table"; + public static final String PROP_SUPPORTS_BSN_SET_TUNNEL_DST_ACTION = + "supports-set-tunnel-dst-action"; + public static final String PROP_SUPPORTS_NX_TTL_DECREMENT = "supports-nx-ttl-decrement"; + + public enum SwitchStatus { + /** this switch is in the process of being handshaked. Initial State. */ + HANDSHAKE(false), + /** the OF channel to this switch is currently in SLAVE role - the switch will not accept + * state-mutating messages from this controller node. + */ + SLAVE(true), + /** the OF channel to this switch is currently in MASTER (or EQUALS) role - the switch is + * controllable from this controller node. + */ + MASTER(true), + /** the switch has been sorted out and quarantined by the handshake. It does not show up + * in normal switch listings + */ + QUARANTINED(false), + /** the switch has disconnected, and will soon be removed from the switch database */ + DISCONNECTED(false); + + private final boolean visible; + + SwitchStatus(boolean visible) { + this.visible = visible; + } + + /** wether this switch is currently visible for normal operation */ + public boolean isVisible() { + return visible; + } + + /** wether this switch is currently ready to be controlled by this controller */ + public boolean isControllable() { + return this == MASTER; + } } - /** - * the type of change that happened to an open flow port - */ - public enum PortChangeType { - ADD, OTHER_UPDATE, DELETE, UP, DOWN, - } - - /** - * Set IFloodlightProviderService for this switch instance - * Called immediately after instantiation - * - * @param controller - */ - public void setFloodlightProvider(Controller controller); - - /** - * Set IThreadPoolService for this switch instance - * Called immediately after instantiation - * - * @param threadPool - */ - public void setThreadPoolService(IThreadPoolService threadPool); - - /** - * Set debug counter service for per-switch counters - * Called immediately after instantiation - * @param debugCounters - * @throws CounterException - */ - public void setDebugCounterService(IDebugCounterService debugCounters) - throws CounterException; - - /** - * Set the netty Channel this switch instance is associated with - * Called immediately after instantiation - * - * @param channel - */ - public void setChannel(Channel channel); - - /** - * Called when OFMessage enters pipeline. Returning true cause the message - * to be dropped. - * @param ofm - * @return - */ - public boolean inputThrottled(OFMessage ofm); + SwitchStatus getStatus(); /** - * Return if the switch is currently overloaded. The definition of - * overload refers to excessive traffic in the control path, namely - * a high packet in rate. + * Returns switch features from features Reply * @return */ - boolean isOverloaded(); + long getBuffers(); /** - * Write OFMessage to the output stream, subject to switch rate limiting. - * The message will be handed to the floodlightProvider for possible filtering - * and processing by message listeners - * @param msg - * @param cntx - * @throws IOException + * Disconnect all the switch's channels and mark the switch as disconnected */ - public void writeThrottled(OFMessage msg, FloodlightContext cntx) throws IOException; + void disconnect(); - /** - * Writes the list of messages to the output stream, subject to rate limiting. - * The message will be handed to the floodlightProvider for possible filtering - * and processing by message listeners. - * @param msglist - * @param bc - * @throws IOException - */ - void writeThrottled(List<OFMessage> msglist, FloodlightContext bc) - throws IOException; + Set<OFActionType> getActions(); - /** - * Writes to the OFMessage to the output stream, bypassing rate limiting. - * The message will be handed to the floodlightProvider for possible filtering - * and processing by message listeners - * @param m - * @param bc - * @throws IOException - */ - public void write(OFMessage m, FloodlightContext bc) throws IOException; + Set<OFCapabilities> getCapabilities(); - /** - * Writes the list of messages to the output stream, bypassing rate limiting. - * The message will be handed to the floodlightProvider for possible filtering - * and processing by message listeners. - * @param msglist - * @param bc - * @throws IOException - */ - public void write(List<OFMessage> msglist, FloodlightContext bc) throws IOException; - - /** - * - * @throws IOException - */ - public void disconnectOutputStream(); - - /** - * Returns switch features from features Reply - * @return - */ - public int getBuffers(); - - public int getActions(); - - public int getCapabilities(); - - public byte getTables(); + short getTables(); /** * @return a copy of the description statistics for this switch */ - public OFDescriptionStatistics getDescriptionStatistics(); + SwitchDescription getSwitchDescription(); /** - * Set the OFFeaturesReply message returned by the switch during initial - * handshake. - * @param featuresReply + * Get the IP address of the remote (switch) end of the connection + * @return the inet address */ - public void setFeaturesReply(OFFeaturesReply featuresReply); + SocketAddress getInetAddress(); /** * Get list of all enabled ports. This will typically be different from @@ -268,7 +131,7 @@ public interface IOFSwitch { * been received. * @return Unmodifiable list of ports not backed by the underlying collection */ - public Collection<ImmutablePort> getEnabledPorts(); + Collection<OFPortDesc> getEnabledPorts(); /** * Get list of the port numbers of all enabled ports. This will typically @@ -278,7 +141,7 @@ public interface IOFSwitch { * messages that have been received. * @return Unmodifiable list of ports not backed by the underlying collection */ - public Collection<Short> getEnabledPortNumbers(); + Collection<OFPort> getEnabledPortNumbers(); /** * Retrieve the port object by the port number. The port object @@ -287,7 +150,7 @@ public interface IOFSwitch { * @param portNumber * @return port object */ - public ImmutablePort getPort(short portNumber); + OFPortDesc getPort(OFPort portNumber); /** * Retrieve the port object by the port name. The port object @@ -297,29 +160,7 @@ public interface IOFSwitch { * @param portName * @return port object */ - public ImmutablePort getPort(String portName); - - /** - * Add or modify a switch port. - * This is called by the core controller - * code in response to a OFPortStatus message. It should not typically be - * called by other floodlight applications. - * - * OFPPR_MODIFY and OFPPR_ADD will be treated as equivalent. The OpenFlow - * spec is not clear on whether portNames are portNumbers are considered - * authoritative identifiers. We treat portNames <-> portNumber mappings - * as fixed. If they change, we delete all previous conflicting ports and - * add all new ports. - * - * @param ps the port status message - * @return the ordered Collection of changes "applied" to the old ports - * of the switch according to the PortStatus message. A single PortStatus - * message can result in multiple changes. - * If portName <-> portNumber mappings have - * changed, the iteration order ensures that delete events for old - * conflicting appear before before events adding new ports - */ - public OrderedCollection<PortChangeEvent> processOFPortStatus(OFPortStatus ps); + OFPortDesc getPort(String portName); /** * Get list of all ports. This will typically be different from @@ -329,172 +170,67 @@ public interface IOFSwitch { * been received. * @return Unmodifiable list of ports */ - public Collection<ImmutablePort> getPorts(); + Collection<OFPortDesc> getPorts(); + + /** + * This is mainly for the benefit of the DB code which currently has the + * requirement that list elements be sorted by key. Hopefully soon the + * DB will handle the sorting automatically or not require it at all, in + * which case we could get rid of this method. + * @return + */ + Collection<OFPortDesc> getSortedPorts(); /** * @param portNumber * @return Whether a port is enabled per latest port status message * (not configured down nor link down nor in spanning tree blocking state) */ - public boolean portEnabled(short portNumber); + boolean portEnabled(OFPort portNumber); /** * @param portNumber * @return Whether a port is enabled per latest port status message * (not configured down nor link down nor in spanning tree blocking state) */ - public boolean portEnabled(String portName); + boolean portEnabled(String portName); /** - * Compute the changes that would be required to replace the old ports - * of this switch with the new ports - * @param ports new ports to set - * @return the ordered collection of changes "applied" to the old ports - * of the switch in order to set them to the new set. - * If portName <-> portNumber mappings have - * changed, the iteration order ensures that delete events for old - * conflicting appear before before events adding new ports + * Check is switch is connected + * @return Whether or not this switch is connected */ - public OrderedCollection<PortChangeEvent> - comparePorts(Collection<ImmutablePort> ports); + boolean isConnected(); /** - * Replace the ports of this switch with the given ports. - * @param ports new ports to set - * @return the ordered collection of changes "applied" to the old ports - * of the switch in order to set them to the new set. - * If portName <-> portNumber mappings have - * changed, the iteration order ensures that delete events for old - * conflicting appear before before events adding new ports + * Retrieves the date the switch connected to this controller + * @return the date */ - public OrderedCollection<PortChangeEvent> - setPorts(Collection<ImmutablePort> ports); - + Date getConnectedSince(); /** * Get the datapathId of the switch * @return */ - public long getId(); - - /** - * Get a string version of the ID for this switch - * @return - */ - public String getStringId(); - - /** - * Get the IP Address for the switch - * @return the inet address - */ - public SocketAddress getInetAddress(); + DatapathId getId(); /** * Retrieves attributes of this switch * @return */ - public Map<Object, Object> getAttributes(); - - /** - * Retrieves the date the switch connected to this controller - * @return the date - */ - public Date getConnectedSince(); - - /** - * Returns the next available transaction id - * @return - */ - public int getNextTransactionId(); - - /** - * Returns a Future object that can be used to retrieve the asynchronous - * OFStatisticsReply when it is available. - * - * @param request statistics request - * @return Future object wrapping OFStatisticsReply - * @throws IOException - */ - public Future<List<OFStatistics>> queryStatistics(OFStatisticsRequest request) - - throws IOException; - - /** - * Returns a Future object that can be used to retrieve the asynchronous - * OFStatisticsReply when it is available. - * - * @param request statistics request - * @return Future object wrapping OFStatisticsReply - * @throws IOException - */ - public Future<OFFeaturesReply> querySwitchFeaturesReply() - throws IOException; - - /** - * Deliver the featuresReply future reply - * @param reply the reply to deliver - */ - void deliverOFFeaturesReply(OFMessage reply); - - /* - * Cancel features reply with a specific transction ID - * @param transactionId the transaction ID - */ - public void cancelFeaturesReply(int transactionId); - - /** - * Check if the switch is connected to this controller. Whether a switch - * is connected is independent of whether the switch is active - * @return whether the switch is still disconnected - */ - public boolean isConnected(); + Map<Object, Object> getAttributes(); /** * Check if the switch is active. I.e., the switch is connected to this * controller and is in master role * @return */ - public boolean isActive(); - - /** - * Set whether the switch is connected - * @param connected whether the switch is connected - */ - public void setConnected(boolean connected); + boolean isActive(); /** * Get the current role of the controller for the switch * @return the role of the controller */ - public Role getHARole(); - - /** - * Set switch's HA role to role. The haRoleReplyReceived indicates - * if a reply was received from the switch (error replies excluded). - * - * If role is null, the switch should close the channel connection. - * - * @param role - * @param haRoleReplyReceived - */ - public void setHARole(Role role); - - /** - * Deliver the statistics future reply - * @param reply the reply to deliver - */ - public void deliverStatisticsReply(OFStatisticsReply reply); - - /** - * Cancel the statistics reply with the given transaction ID - * @param transactionId the transaction ID - */ - public void cancelStatisticsReply(int transactionId); - - /** - * Cancel all statistics replies - */ - public void cancelAllStatisticsReplies(); + OFControllerRole getControllerRole(); /** * Checks if a specific switch property exists for this switch @@ -535,135 +271,67 @@ public interface IOFSwitch { Object removeAttribute(String name); /** - * Clear all flowmods on this switch - */ - public void clearAllFlowMods(); - - /** - * Update broadcast cache - * @param data - * @return true if there is a cache hit - * false if there is no cache hit. - */ - public boolean updateBroadcastCache(Long entry, Short port); - - /** - * Get the portBroadcastCacheHits + * Returns a factory object that can be used to create OpenFlow messages. * @return */ - public Map<Short, Long> getPortBroadcastHits(); + OFFactory getOFFactory(); /** - * Send a flow statistics request to the switch. This call returns after - * sending the stats. request to the switch. - * @param request flow statistics request message - * @param xid transaction id, must be obtained by using the getXid() API. - * @param caller the caller of the API. receive() callback of this - * caller would be called when the reply from the switch is received. - * @return the transaction id for the message sent to the switch. The - * transaction id can be used to match the response with the request. Note - * that the transaction id is unique only within the scope of this switch. - * @throws IOException - */ - public void sendStatsQuery(OFStatisticsRequest request, int xid, - IOFMessageListener caller) throws IOException; - - /** - * Flush all flows queued for this switch in the current thread. - * NOTE: The contract is limited to the current thread - */ - public void flush(); - - /*********************************************** - * The following method can be overridden by - * specific types of switches - *********************************************** - */ - - /** - * Set the SwitchProperties based on it's description - * @param description - */ - public void setSwitchProperties(OFDescriptionStatistics description); - - /** - * Return the type of OFPort - * @param port_num - * @return - */ - public OFPortType getPortType(short port_num); - - /** - * Can the port be turned on without forming a new loop? - * @param port_num - * @return - */ - public boolean isFastPort(short port_num); - - /** - * Return whether write throttling is enabled on the switch - */ - public boolean isWriteThrottleEnabled(); - - /** - * Set the flow table full flag in the switch - */ - public void setTableFull(boolean isFull); - - /** - * Set the suggested priority to use when installing access flows in - * this switch. + * Flush all flows queued for this switch on all connections that were written by the current thread. + * + * */ - public void setAccessFlowPriority(short prio); + void flush(); /** - * Set the suggested priority to use when installing core flows in - * this switch. + * Gets the OF connections for this switch instance + * @return Collection of IOFConnection */ - public void setCoreFlowPriority(short prio); + ImmutableList<IOFConnection> getConnections(); /** - * Get the suggested priority to use when installing access flows in - * this switch. + * Writes a message to the connection specified by the logical OFMessage category + * @param m an OF Message + * @param category the category of the OF Message to be sent */ - public short getAccessFlowPriority(); + void write(OFMessage m, LogicalOFMessageCategory category); /** - * Get the suggested priority to use when installing core flows in - * this switch. + * Writes a message list to the connection specified by the logical OFMessage category + * @param msglist an OF Message list + * @param category the category of the OF Message list to be sent */ - public short getCoreFlowPriority(); + void write(Iterable<OFMessage> msglist, LogicalOFMessageCategory category); /** - * Start this switch driver's sub handshake. This might be a no-op but - * this method must be called at least once for the switch to be become - * ready. - * This method must only be called from the I/O thread - * @throws SwitchDriverSubHandshakeAlreadyStarted if the sub-handshake has - * already been started + * Get a connection specified by the logical OFMessage category + * @param category the category for the connection the user desires + * @return an OF Connection */ - public void startDriverHandshake(); + OFConnection getConnectionByCategory(LogicalOFMessageCategory category); - /** - * Check if the sub-handshake for this switch driver has been completed. - * This method can only be called after startDriverHandshake() + /** write a Stats (Multipart-) request, register for all corresponding reply messages. + * Returns a Future object that can be used to retrieve the List of asynchronous + * OFStatsReply messages when it is available. * - * This methods must only be called from the I/O thread - * @return true if the sub-handshake has been completed. False otherwise - * @throws SwitchDriverSubHandshakeNotStarted if startDriverHandshake() has - * not been called yet. + * @param request stats request + * @param category the category for the connection that this request should be written to + * @return Future object wrapping OFStatsReply + * If the connection is not currently connected, will + * return a Future that immediately fails with a @link{SwitchDisconnectedException}. */ - public boolean isDriverHandshakeComplete(); + <REPLY extends OFStatsReply> ListenableFuture<List<REPLY>> writeStatsRequest(OFStatsRequest<REPLY> request, LogicalOFMessageCategory category); - /** - * Pass the given OFMessage to the driver as part of this driver's - * sub-handshake. Must not be called after the handshake has been completed - * This methods must only be called from the I/O thread - * @param m The message that the driver should process - * @throws SwitchDriverSubHandshakeCompleted if isDriverHandshake() returns - * false before this method call - * @throws SwitchDriverSubHandshakeNotStarted if startDriverHandshake() has - * not been called yet. + /** write an OpenFlow Request message, register for a single corresponding reply message + * or error message. + * + * @param request + * @param categiry the category for the connection that this request should be written to + * @return a Future object that can be used to retrieve the asynchrounous + * response when available. + * + * If the connection is not currently connected, will + * return a Future that immediately fails with a @link{SwitchDisconnectedException}. */ - public void processDriverHandshakeMessage(OFMessage m); + <R extends OFMessage> ListenableFuture<R> writeRequest(OFRequest<R> request, LogicalOFMessageCategory category); } diff --git a/src/main/java/net/floodlightcontroller/core/IOFSwitchBackend.java b/src/main/java/net/floodlightcontroller/core/IOFSwitchBackend.java new file mode 100644 index 0000000000000000000000000000000000000000..d16c5e20f97b2420b4991b9ba252e450cdc3b6ee --- /dev/null +++ b/src/main/java/net/floodlightcontroller/core/IOFSwitchBackend.java @@ -0,0 +1,186 @@ +/** +* Copyright 2011, Big Switch Networks, Inc. +* Originally created by David Erickson, Stanford University +* +* Licensed under the Apache License, Version 2.0 (the "License"); you may +* not use this file except in compliance with the License. You may obtain +* a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +* License for the specific language governing permissions and limitations +* under the License. +**/ + +package net.floodlightcontroller.core; + +import java.util.Collection; + +import org.projectfloodlight.openflow.protocol.OFBsnControllerConnectionsReply; +import org.projectfloodlight.openflow.protocol.OFControllerRole; +import org.projectfloodlight.openflow.protocol.OFFeaturesReply; +import org.projectfloodlight.openflow.protocol.OFMessage; +import org.projectfloodlight.openflow.protocol.OFPortDesc; +import org.projectfloodlight.openflow.protocol.OFPortDescStatsReply; +import org.projectfloodlight.openflow.protocol.OFPortStatus; + +import net.floodlightcontroller.util.OrderedCollection; + +/** + * An openflow switch connecting to the controller. This interface offers + * methods for interacting with switches using OpenFlow, and retrieving + * information about the switches. + */ +public interface IOFSwitchBackend extends IOFSwitch { + /** + * Set the netty Channel this switch instance is associated with + * Called immediately after instantiation + * @param channel + */ + void registerConnection(IOFConnectionBackend connection); + + /** + * Remove the netty Channels associated with this switch + * @param channel + */ + void removeConnections(); + + /** + * Remove the netty Channel belonging to the specified connection + * @param connection + */ + void removeConnection(IOFConnectionBackend connection); + + /** + * Set the OFFeaturesReply message returned by the switch during initial + * handshake. + * @param featuresReply + */ + void setFeaturesReply(OFFeaturesReply featuresReply); + + /** + * Add or modify a switch port. + * This is called by the core controller + * code in response to a OFPortStatus message. It should not typically be + * called by other floodlight applications. + * + * OFPPR_MODIFY and OFPPR_ADD will be treated as equivalent. The OpenFlow + * spec is not clear on whether portNames are portNumbers are considered + * authoritative identifiers. We treat portNames <-> portNumber mappings + * as fixed. If they change, we delete all previous conflicting ports and + * add all new ports. + * + * @param ps the port status message + * @return the ordered Collection of changes "applied" to the old ports + * of the switch according to the PortStatus message. A single PortStatus + * message can result in multiple changes. + * If portName <-> portNumber mappings have + * changed, the iteration order ensures that delete events for old + * conflicting appear before before events adding new ports + */ + OrderedCollection<PortChangeEvent> processOFPortStatus(OFPortStatus ps); + + /** + * Compute the changes that would be required to replace the old ports + * of this switch with the new ports + * @param ports new ports to set + * @return the ordered collection of changes "applied" to the old ports + * of the switch in order to set them to the new set. + * If portName <-> portNumber mappings have + * changed, the iteration order ensures that delete events for old + * conflicting appear before before events adding new ports + */ + OrderedCollection<PortChangeEvent> + comparePorts(Collection<OFPortDesc> ports); + + /** + * Replace the ports of this switch with the given ports. + * @param ports new ports to set + * @return the ordered collection of changes "applied" to the old ports + * of the switch in order to set them to the new set. + * If portName <-> portNumber mappings have + * changed, the iteration order ensures that delete events for old + * conflicting appear before before events adding new ports + */ + OrderedCollection<PortChangeEvent> + setPorts(Collection<OFPortDesc> ports); + + /*********************************************** + * The following method can be overridden by + * specific types of switches + *********************************************** + */ + + /** + * Set the SwitchProperties based on it's description + * @param description + */ + void setSwitchProperties(SwitchDescription description); + + /** + * Set the flow table full flag in the switch + */ + void setTableFull(boolean isFull); + + /** + * Start this switch driver's sub handshake. This might be a no-op but + * this method must be called at least once for the switch to be become + * ready. + * This method must only be called from the I/O thread + * @throws SwitchDriverSubHandshakeAlreadyStarted if the sub-handshake has + * already been started + */ + void startDriverHandshake(); + + /** + * Check if the sub-handshake for this switch driver has been completed. + * This method can only be called after startDriverHandshake() + * + * This methods must only be called from the I/O thread + * @return true if the sub-handshake has been completed. False otherwise + * @throws SwitchDriverSubHandshakeNotStarted if startDriverHandshake() has + * not been called yet. + */ + boolean isDriverHandshakeComplete(); + + /** + * Pass the given OFMessage to the driver as part of this driver's + * sub-handshake. Must not be called after the handshake has been completed + * This methods must only be called from the I/O thread + * @param m The message that the driver should process + * @throws SwitchDriverSubHandshakeCompleted if isDriverHandshake() returns + * false before this method call + * @throws SwitchDriverSubHandshakeNotStarted if startDriverHandshake() has + * not been called yet. + */ + void processDriverHandshakeMessage(OFMessage m); + + void setPortDescStats(OFPortDescStatsReply portDescStats); + + /** + * Cancel all pending request + */ + void cancelAllPendingRequests(); + + /** the the current HA role of this switch */ + void setControllerRole(OFControllerRole role); + + void setStatus(SwitchStatus switchStatus); + + /** + * Updates the switch's mapping of controller connections + * @param controllerCxnsReply the controller connections message sent from the switch + */ + void updateControllerConnections(OFBsnControllerConnectionsReply controllerCxnsReply); + + /** + * Determines whether there is another master controller that the switches are + * connected to by looking at the controller connections. + * @return true if another viable master exists + */ + boolean hasAnotherMaster(); + +} diff --git a/src/main/java/net/floodlightcontroller/core/IOFSwitchDriver.java b/src/main/java/net/floodlightcontroller/core/IOFSwitchDriver.java index f7f720f914d34f6e812dc9032ccb135a8a3f100a..b56655535373e57b59e017791dce25834abe5649 100644 --- a/src/main/java/net/floodlightcontroller/core/IOFSwitchDriver.java +++ b/src/main/java/net/floodlightcontroller/core/IOFSwitchDriver.java @@ -16,7 +16,10 @@ package net.floodlightcontroller.core; -import org.openflow.protocol.statistics.OFDescriptionStatistics; +import net.floodlightcontroller.core.IOFSwitchBackend; +import org.projectfloodlight.openflow.protocol.OFFactory; + +import net.floodlightcontroller.core.SwitchDescription; public interface IOFSwitchDriver { /** @@ -26,5 +29,5 @@ public interface IOFSwitchDriver { * @return A IOFSwitch instance if the driver found an implementation * for the given description. Null otherwise */ - public IOFSwitch getOFSwitchImpl(OFDescriptionStatistics description); -} + public IOFSwitchBackend getOFSwitchImpl(SwitchDescription description, OFFactory factory); +} \ No newline at end of file diff --git a/src/main/java/net/floodlightcontroller/core/IOFSwitchListener.java b/src/main/java/net/floodlightcontroller/core/IOFSwitchListener.java index e0c7b53f8b29eb8349f28d9f42cc97b43b309c8f..57eb4d179d27ad38d7202991ac26799bd822e6e7 100644 --- a/src/main/java/net/floodlightcontroller/core/IOFSwitchListener.java +++ b/src/main/java/net/floodlightcontroller/core/IOFSwitchListener.java @@ -17,6 +17,9 @@ package net.floodlightcontroller.core; +import org.projectfloodlight.openflow.protocol.OFPortDesc; +import org.projectfloodlight.openflow.types.DatapathId; + /** * Switch lifecycle notifications. * @@ -45,20 +48,20 @@ public interface IOFSwitchListener { * the switch is connected at some controller in the cluster * @param switchId the datapath Id of the new switch */ - public void switchAdded(long switchId); + public void switchAdded(DatapathId switchId); /** * Fired when a switch disconnects from the cluster , * @param switchId the datapath Id of the switch */ - public void switchRemoved(long switchId); + public void switchRemoved(DatapathId switchId); /** * Fired when a switch becomes active *on the local controller*, I.e., * the switch is connected to the local controller and is in MASTER mode * @param switchId the datapath Id of the switch */ - public void switchActivated(long switchId); + public void switchActivated(DatapathId switchId); /** * Fired when a port on a known switch changes. @@ -74,9 +77,9 @@ public interface IOFSwitchListener { * @param port * @param type */ - public void switchPortChanged(long switchId, - ImmutablePort port, - IOFSwitch.PortChangeType type); + public void switchPortChanged(DatapathId switchId, + OFPortDesc port, + PortChangeType type); /** * Fired when any non-port related information (e.g., attributes, @@ -84,5 +87,5 @@ public interface IOFSwitchListener { * TODO: currently unused * @param switchId */ - public void switchChanged(long switchId); + public void switchChanged(DatapathId switchId); } diff --git a/src/main/java/net/floodlightcontroller/core/IShutdownListener.java b/src/main/java/net/floodlightcontroller/core/IShutdownListener.java new file mode 100644 index 0000000000000000000000000000000000000000..b3fe5108b866c9d383ef7388c6d220a7a46f2365 --- /dev/null +++ b/src/main/java/net/floodlightcontroller/core/IShutdownListener.java @@ -0,0 +1,13 @@ +package net.floodlightcontroller.core; + +/** + * This listener is called when floodlight is about to terminate to enable + * interested parties to perform final steps, e.g., persisting debug + * information. + * Listeners are unordered. + * @author gregor + * + */ +public interface IShutdownListener { + public void floodlightIsShuttingDown(); +} diff --git a/src/main/java/net/floodlightcontroller/core/IShutdownService.java b/src/main/java/net/floodlightcontroller/core/IShutdownService.java new file mode 100644 index 0000000000000000000000000000000000000000..81e8c6f45c9875902519e7900754e402cda547f3 --- /dev/null +++ b/src/main/java/net/floodlightcontroller/core/IShutdownService.java @@ -0,0 +1,43 @@ +package net.floodlightcontroller.core; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import net.floodlightcontroller.core.module.IFloodlightService; + +public interface IShutdownService extends IFloodlightService { + /** + * Terminate floodlight process by calling System.exit() with the given + * exitCode. If reason is non-null, reason will be logged. Before + * terminating the shutdownListeners are called + * If exitCode == 0 the termination cause is deemed "normal" and info + * level log is used. In all other cases the exit is abnormal and an error + * is logged. + * @param reason + * @param exitCode + */ + public void terminate(@Nullable String reason, int exitCode); + + /** + * Terminate floodlight process by calling System.exit() with the given + * exitCode. If reason is non-null, reason will be logged. In addition, + * the throwable will be logged as well. + * Before terminating the shutdownListeners are called + * + * This method is generally used to terminate floodlight due to an + * unhandled Exception. As such all messages are logged as error and it is + * recommended that an exitCode != 0 is used. + * @param reason + * @param e The throwable causing floodlight to terminate + * @param exitCode + */ + public void terminate(@Nullable String reason, + @Nonnull Throwable e, int exitCode); + + /** + * Register a shutdown listener. Registered listeners are called when + * floodlight is about to terminate due to a call to terminate() + * @param listener + */ + public void registerShutdownListener(@Nonnull IShutdownListener listener); +} diff --git a/src/main/java/net/floodlightcontroller/core/ImmutablePort.java b/src/main/java/net/floodlightcontroller/core/ImmutablePort.java deleted file mode 100644 index c015454cad15d3d24d85a0d0ddc89531062237d4..0000000000000000000000000000000000000000 --- a/src/main/java/net/floodlightcontroller/core/ImmutablePort.java +++ /dev/null @@ -1,561 +0,0 @@ -package net.floodlightcontroller.core; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.EnumSet; -import java.util.List; -import java.util.Set; - -import net.floodlightcontroller.util.EnumBitmaps; -import org.openflow.protocol.OFPhysicalPort; -import org.openflow.protocol.OFPhysicalPort.OFPortConfig; -import org.openflow.protocol.OFPhysicalPort.OFPortFeatures; -import org.openflow.protocol.OFPhysicalPort.OFPortState; -import org.openflow.protocol.OFPhysicalPort.PortSpeed; -import org.openflow.util.HexString; - - -/** - * An immutable version of an OFPhysical port. In addition, it uses EnumSets - * instead of integer bitmaps to represent - * OFPortConfig, OFPortState, and OFPortFeature bitmaps. - * - * Port names are stored with the original case but equals() and XXXX use - * case-insentivie comparisions for port names!! - * - * TODO: create a Builder so we can easily construct OFPhysicalPorts - * TODO: should we verify / ensure that the features make sense, i.e., that - * currentFeatures IsSubsetOf advertisedFeatures IsSubsetOf - * supportedFeatures - * - * @author gregor - * - */ -public class ImmutablePort { - private final short portNumber; - private final byte[] hardwareAddress; - private final String name; - private final EnumSet<OFPortConfig> config; - private final boolean portStateLinkDown; - private final OFPortState stpState; - private final EnumSet<OFPortFeatures> currentFeatures; - private final EnumSet<OFPortFeatures> advertisedFeatures; - private final EnumSet<OFPortFeatures> supportedFeatures; - private final EnumSet<OFPortFeatures> peerFeatures; - - /** - * A builder class to create ImmutablePort instances - * - * TODO: add methods to remove elements from the EnumSets - */ - public static class Builder { - private short portNumber; - private byte[] hardwareAddress; - private String name; - private EnumSet<OFPortConfig> config; - private boolean portStateLinkDown; - private OFPortState stpState; - private EnumSet<OFPortFeatures> currentFeatures; - private EnumSet<OFPortFeatures> advertisedFeatures; - private EnumSet<OFPortFeatures> supportedFeatures; - private EnumSet<OFPortFeatures> peerFeatures; - - public Builder() { - this.portNumber = (short)1; - this.hardwareAddress = new byte[] { 0, 0, 0, 0, 0, 0 }; - this.name = ""; - this.config = EnumSet.noneOf(OFPortConfig.class); - this.portStateLinkDown = false; - this.stpState = OFPortState.OFPPS_STP_LISTEN; - this.currentFeatures = EnumSet.noneOf(OFPortFeatures.class); - this.advertisedFeatures = EnumSet.noneOf(OFPortFeatures.class); - this.supportedFeatures = EnumSet.noneOf(OFPortFeatures.class); - this.peerFeatures = EnumSet.noneOf(OFPortFeatures.class); - } - - public Builder(ImmutablePort p) { - this.portNumber = p.getPortNumber(); - this.hardwareAddress = p.getHardwareAddress(); - this.name = p.getName(); - this.config = EnumSet.copyOf(p.getConfig()); - this.portStateLinkDown = p.isLinkDown(); - this.stpState = p.getStpState(); - this.currentFeatures = EnumSet.copyOf(p.getCurrentFeatures()); - this.advertisedFeatures = EnumSet.copyOf(p.getAdvertisedFeatures()); - this.supportedFeatures = EnumSet.copyOf(p.getSupportedFeatures()); - this.peerFeatures = EnumSet.copyOf(p.getPeerFeatures()); - } - - /** - * @param portNumber the portNumber to set - */ - public Builder setPortNumber(short portNumber) { - this.portNumber = portNumber; - return this; - } - /** - * @param hardwareAddress the hardwareAddress to set - */ - public Builder setHardwareAddress(byte[] hardwareAddress) { - if (hardwareAddress== null) { - throw new NullPointerException("Hardware address must not be null"); - } - if (hardwareAddress.length != 6) { - throw new IllegalArgumentException("Harware address must be 6 " + - "bytes long but hardware address is " + - Arrays.toString(hardwareAddress)); - } - this.hardwareAddress = Arrays.copyOf(hardwareAddress, 6); - return this; - } - /** - * @param name the name to set - */ - public Builder setName(String name) { - if (name == null) - throw new NullPointerException("Port name must not be null"); - this.name = name; - return this; - } - /** - * @param config the config to set - */ - public Builder addConfig(OFPortConfig config) { - if (config == null) - throw new NullPointerException("PortConfig must not be null"); - this.config.add(config); - return this; - } - /** - * @param portStateLinkDown the portStateLinkDown to set - */ - public Builder setPortStateLinkDown(boolean portStateLinkDown) { - this.portStateLinkDown = portStateLinkDown; - return this; - } - /** - * @param stpState the stpState to set - */ - public Builder setStpState(OFPortState stpState) { - if (stpState == null) - throw new NullPointerException("stpState must not be null"); - if (!stpState.isStpState()) { - String msg = String.format("OFPortState enum constant %s " + - "is not an STP state", stpState); - throw new IllegalArgumentException(msg); - } - this.stpState = stpState; - return this; - } - /** - * @param currentFeatures the currentFeatures to set - */ - public Builder addCurrentFeature(OFPortFeatures currentFeature) { - if (currentFeature == null) - throw new NullPointerException("CurrentFeature must not be null"); - this.currentFeatures.add(currentFeature); - return this; - } - /** - * @param advertisedFeatures the advertisedFeatures to set - */ - public Builder - addAdvertisedFeature(OFPortFeatures advertisedFeature) { - if (advertisedFeature == null) { - throw new - NullPointerException("AdvertisedFeature must not be null"); - } - this.advertisedFeatures.add(advertisedFeature); - return this; - } - /** - * @param supportedFeatures the supportedFeatures to set - */ - public Builder addSupportedFeature(OFPortFeatures supportedFeature) { - if (supportedFeature == null) { - throw new NullPointerException("SupportedFeature must not be null"); - } - this.supportedFeatures.add(supportedFeature); - return this; - } - /** - * @param peerFeatures the peerFeatures to set - */ - public Builder addPeerFeature(OFPortFeatures peerFeature) { - if (peerFeature == null) - throw new NullPointerException("PortFeature must not be null"); - this.peerFeatures.add(peerFeature); - return this; - } - - /** - * @return - */ - public ImmutablePort build() { - return new ImmutablePort(portNumber, - hardwareAddress, - name, - EnumSet.copyOf(config), - portStateLinkDown, - stpState, - EnumSet.copyOf(currentFeatures), - EnumSet.copyOf(advertisedFeatures), - EnumSet.copyOf(supportedFeatures), - EnumSet.copyOf(peerFeatures)); - } - } - - - public static ImmutablePort fromOFPhysicalPort(OFPhysicalPort p) { - if (p == null) { - throw new NullPointerException("OFPhysicalPort must not be null"); - } - if (p.getHardwareAddress() == null) { - throw new NullPointerException("Hardware address must not be null"); - } - if (p.getName() == null) { - throw new NullPointerException("Port name must not be null"); - } - - return new ImmutablePort( - - p.getPortNumber(), - Arrays.copyOf(p.getHardwareAddress(), 6), - p.getName(), - EnumBitmaps.toEnumSet(OFPortConfig.class, p.getConfig()), - OFPortState.isPortDown(p.getState()), - OFPortState.getStpState(p.getState()), - EnumBitmaps.toEnumSet(OFPortFeatures.class, - p.getCurrentFeatures()), - EnumBitmaps.toEnumSet(OFPortFeatures.class, - p.getAdvertisedFeatures()), - EnumBitmaps.toEnumSet(OFPortFeatures.class, - p.getSupportedFeatures()), - EnumBitmaps.toEnumSet(OFPortFeatures.class, - p.getPeerFeatures()) - ); - } - - public static ImmutablePort create(String name, Short portNumber) { - return new ImmutablePort(portNumber, - new byte[] { 0, 0, 0, 0, 0, 0 }, - name, - EnumSet.noneOf(OFPortConfig.class), - false, - OFPortState.OFPPS_STP_LISTEN, - EnumSet.noneOf(OFPortFeatures.class), - EnumSet.noneOf(OFPortFeatures.class), - EnumSet.noneOf(OFPortFeatures.class), - EnumSet.noneOf(OFPortFeatures.class)); - } - - /** - * Private constructor. Use factory methods. - * - * Verifies pre-conditions of arguments - * Does NOT make defensive copies. Calling factory methods are required - * to copy defensively if required. - * - * @param portNumber - * @param hardwareAddress - * @param name - * @param config - * @param portStateLinkDown - * @param portStateStp - * @param currentFeatures - * @param advertisedFeatures - * @param supportedFeatures - * @param peerFeatures - */ - private ImmutablePort(short portNumber, byte[] hardwareAddress, - String name, EnumSet<OFPortConfig> config, - boolean portStateLinkDown, - OFPortState portStateStp, - EnumSet<OFPortFeatures> currentFeatures, - EnumSet<OFPortFeatures> advertisedFeatures, - EnumSet<OFPortFeatures> supportedFeatures, - EnumSet<OFPortFeatures> peerFeatures) { - if (name == null) { - throw new NullPointerException("Port name must not be null"); - } - if (hardwareAddress== null) { - throw new NullPointerException("Hardware address must not be null"); - } - if (hardwareAddress.length != 6) { - throw new IllegalArgumentException("Harware address must be 6 " + - "bytes long but hardware address is " + - Arrays.toString(hardwareAddress)); - } - if (config == null) - throw new NullPointerException("portConfig must not be null"); - if (portStateStp == null) - throw new NullPointerException("portStateStp must not be null"); - if (currentFeatures == null) - throw new NullPointerException("currentFeatures must not be null"); - if (advertisedFeatures == null) - throw new NullPointerException("advertisedFeatures must not be null"); - if (supportedFeatures == null) - throw new NullPointerException("supportedFeatures must not be null"); - if (peerFeatures == null) - throw new NullPointerException("peerFeatures must not be null"); - - this.portNumber = portNumber; - this.hardwareAddress = hardwareAddress; - this.name = name; - this.config = config; - this.portStateLinkDown = portStateLinkDown; - this.stpState = portStateStp; - this.currentFeatures = currentFeatures; - this.advertisedFeatures = advertisedFeatures; - this.supportedFeatures = supportedFeatures; - this.peerFeatures = peerFeatures; - } - - public short getPortNumber() { - return portNumber; - } - - public byte[] getHardwareAddress() { - // FIXME: don't use arrays. - return Arrays.copyOf(hardwareAddress, 6); - } - - public String getName() { - return name; - } - - public Set<OFPortConfig> getConfig() { - return Collections.unmodifiableSet(config); - } - - /** - * Returns true if the OFPortState indicates the port is down - * @return - */ - public boolean isLinkDown() { - return portStateLinkDown; - } - - /** - * Returns the STP state portion of the OFPortState. The returned - * enum constant will be one of the four STP states and will have - * isStpState() return true - * @return - */ - public OFPortState getStpState() { - return this.stpState; - } - - public Set<OFPortFeatures> getCurrentFeatures() { - return Collections.unmodifiableSet(currentFeatures); - } - - public Set<OFPortFeatures> getAdvertisedFeatures() { - return Collections.unmodifiableSet(advertisedFeatures); - } - - public Set<OFPortFeatures> getSupportedFeatures() { - return Collections.unmodifiableSet(supportedFeatures); - } - - public Set<OFPortFeatures> getPeerFeatures() { - return Collections.unmodifiableSet(peerFeatures); - } - - - /** - * Returns true if the port is up, i.e., it's neither administratively - * down nor link down. It currently does NOT take STP state into - * consideration - * @return - */ - public boolean isEnabled() { - return (!portStateLinkDown && - !config.contains(OFPortConfig.OFPPC_PORT_DOWN)); - } - - /** - * @return the speed of the port (from currentFeatures) if the port is - * enabled, otherwise return SPEED_NONE - */ - public PortSpeed getCurrentPortSpeed() { - if (!isEnabled()) - return PortSpeed.SPEED_NONE; - PortSpeed maxSpeed = PortSpeed.SPEED_NONE; - for (OFPortFeatures f: currentFeatures) - PortSpeed.max(maxSpeed, f.getSpeed()); - return maxSpeed; - } - - public OFPhysicalPort toOFPhysicalPort() { - OFPhysicalPort ofpp = new OFPhysicalPort(); - ofpp.setPortNumber(this.getPortNumber()); - ofpp.setHardwareAddress(this.getHardwareAddress()); - ofpp.setName(this.getName()); - ofpp.setConfig(EnumBitmaps.toBitmap(this.getConfig())); - int state = this.getStpState().getValue(); - if (this.isLinkDown()) - state |= OFPortState.OFPPS_LINK_DOWN.getValue(); - ofpp.setState(state); - ofpp.setCurrentFeatures(EnumBitmaps.toBitmap(this.getCurrentFeatures())); - ofpp.setAdvertisedFeatures( - EnumBitmaps.toBitmap(this.getAdvertisedFeatures())); - ofpp.setSupportedFeatures( - EnumBitmaps.toBitmap(this.getSupportedFeatures())); - ofpp.setPeerFeatures(EnumBitmaps.toBitmap(this.getPeerFeatures())); - return ofpp; - } - - /** - * Return a brief String describing this port containing the port number - * and port name - * @return - */ - public String toBriefString() { - return String.format("%s (%d)", name, portNumber); - } - - - - /* (non-Javadoc) - * @see java.lang.Object#hashCode() - */ - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime - * result - + ((advertisedFeatures == null) ? 0 - : advertisedFeatures.hashCode()); - result = prime * result + ((config == null) ? 0 : config.hashCode()); - result = prime - * result - + ((currentFeatures == null) ? 0 - : currentFeatures.hashCode()); - result = prime * result + Arrays.hashCode(hardwareAddress); - result = prime * result + ((name == null) ? 0 : name.hashCode()); - result = prime * result - + ((peerFeatures == null) ? 0 : peerFeatures.hashCode()); - result = prime * result + portNumber; - result = prime * result + (portStateLinkDown ? 1231 : 1237); - result = prime * result - + ((stpState == null) ? 0 : stpState.hashCode()); - result = prime - * result - + ((supportedFeatures == null) ? 0 - : supportedFeatures.hashCode()); - return result; - } - - /* (non-Javadoc) - * @see java.lang.Object#equals(java.lang.Object) - */ - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (obj == null) return false; - if (getClass() != obj.getClass()) return false; - ImmutablePort other = (ImmutablePort) obj; - if (portNumber != other.portNumber) return false; - if (name == null) { - if (other.name != null) return false; - } else if (!name.equalsIgnoreCase(other.name)) return false; - if (advertisedFeatures == null) { - if (other.advertisedFeatures != null) return false; - } else if (!advertisedFeatures.equals(other.advertisedFeatures)) - return false; - if (config == null) { - if (other.config != null) return false; - } else if (!config.equals(other.config)) return false; - if (currentFeatures == null) { - if (other.currentFeatures != null) return false; - } else if (!currentFeatures.equals(other.currentFeatures)) - return false; - if (!Arrays.equals(hardwareAddress, other.hardwareAddress)) - return false; - if (peerFeatures == null) { - if (other.peerFeatures != null) return false; - } else if (!peerFeatures.equals(other.peerFeatures)) return false; - if (portStateLinkDown != other.portStateLinkDown) return false; - if (stpState != other.stpState) return false; - if (supportedFeatures == null) { - if (other.supportedFeatures != null) return false; - } else if (!supportedFeatures.equals(other.supportedFeatures)) - return false; - return true; - } - - /** - * Convert a Collection of OFPhysicalPorts to a list of ImmutablePorts. - * All OFPhysicalPorts in the Collection must be non-null and valid. - * No other checks (name / number uniqueness) are performed - * @param ports - * @return a list of {@link ImmutablePort}s. This is list is owned by - * the caller. The returned list is not thread-safe - * @throws NullPointerException if any OFPhysicalPort or important fields - * of any OFPhysicalPort are null - * @throws IllegalArgumentException - */ - public static List<ImmutablePort> - immutablePortListOf(Collection<OFPhysicalPort> ports) { - if (ports == null) { - throw new NullPointerException("Port list must not be null"); - } - ArrayList<ImmutablePort> immutablePorts = - new ArrayList<ImmutablePort>(ports.size()); - for (OFPhysicalPort p: ports) - immutablePorts.add(fromOFPhysicalPort(p)); - return immutablePorts; - } - - /** - * Convert a Collection of ImmutablePort to a list of OFPhyscialPorts. - * All ImmutablePorts in the Collection must be non-null. - * No other checks (name / number uniqueness) are performed - * @param ports - * @return a list of {@link OFPhysicalPort}s. This is list is owned by - * the caller. The returned list is not thread-safe - * @throws NullPointerException if any {@link ImmutablePort} or the port - * list is null - * @throws IllegalArgumentException - */ - public static List<OFPhysicalPort> - ofPhysicalPortListOf(Collection<ImmutablePort> ports) { - if (ports == null) { - throw new NullPointerException("Port list must not be null"); - } - ArrayList<OFPhysicalPort> ofppList= - new ArrayList<OFPhysicalPort>(ports.size()); - for (ImmutablePort p: ports) { - if (p == null) - throw new NullPointerException("Port must not be null"); - ofppList.add(p.toOFPhysicalPort()); - } - return ofppList; - } - - /* (non-Javadoc) - * @see java.lang.Object#toString() - */ - @Override - public String toString() { - StringBuilder builder2 = new StringBuilder(); - String linkState = (portStateLinkDown) ? "DOWN" : "UP"; - builder2.append("Port [") - .append(name) - .append("(").append(portNumber).append(")") - .append(", hardwareAddress=") - .append(HexString.toHexString(hardwareAddress)) - .append(", config=").append(config) - .append(", link=").append(linkState) - .append(", stpState=").append(stpState) - .append(", currentFeatures=").append(currentFeatures) - .append(", advertisedFeatures=").append(advertisedFeatures) - .append(", supportedFeatures=").append(supportedFeatures) - .append(", peerFeatures=").append(peerFeatures).append("]"); - return builder2.toString(); - } -} diff --git a/src/main/java/net/floodlightcontroller/core/LogicalOFMessageCategory.java b/src/main/java/net/floodlightcontroller/core/LogicalOFMessageCategory.java new file mode 100644 index 0000000000000000000000000000000000000000..514d3541cbfc06820205ad40e9f3499bf441ceba --- /dev/null +++ b/src/main/java/net/floodlightcontroller/core/LogicalOFMessageCategory.java @@ -0,0 +1,68 @@ +package net.floodlightcontroller.core; + +import javax.annotation.Nonnull; + +import org.projectfloodlight.openflow.types.OFAuxId; + +/** + * Immutable class for logical OF message category. + * Applications should use these to define the OF Aux connections + * that they desire. + * @author Jason Parraga <Jason.Parraga@bigswitch.com> + */ +public class LogicalOFMessageCategory { + + public static final LogicalOFMessageCategory MAIN = new LogicalOFMessageCategory("MAIN", OFAuxId.MAIN); + + final private String name; + final private OFAuxId auxId; + + + public LogicalOFMessageCategory(@Nonnull String name, int auxId) { + this(name, OFAuxId.of(auxId)); + } + + public LogicalOFMessageCategory(@Nonnull String name, OFAuxId auxId) { + if (name == null) + throw new NullPointerException("name must not be null"); + this.name = name; + this.auxId = auxId; + } + + public OFAuxId getAuxId(){ + return this.auxId; + } + + public String getName(){ + return this.name; + } + + @Override + public String toString(){ + return "LogicalOFMessageCategory [name=" + getName() + " OFAuxId=" + getAuxId() + "]"; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((auxId == null) ? 0 : auxId.hashCode()); + result = prime * result + ((name == null) ? 0 : name.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + LogicalOFMessageCategory other = (LogicalOFMessageCategory) obj; + if (auxId == null) { + if (other.auxId != null) return false; + } else if (!auxId.equals(other.auxId)) return false; + if (name == null) { + if (other.name != null) return false; + } else if (!name.equals(other.name)) return false; + return true; + } +} \ No newline at end of file diff --git a/src/main/java/net/floodlightcontroller/core/Main.java b/src/main/java/net/floodlightcontroller/core/Main.java index 627a56c1477f2191abc11f2f8dbc5304128c0bd6..0bda3c0c55abf9497a8dcb334e0b65141bccd1c2 100644 --- a/src/main/java/net/floodlightcontroller/core/Main.java +++ b/src/main/java/net/floodlightcontroller/core/Main.java @@ -18,8 +18,11 @@ package net.floodlightcontroller.core; import org.kohsuke.args4j.CmdLineException; import org.kohsuke.args4j.CmdLineParser; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import net.floodlightcontroller.core.internal.CmdLineSettings; +import net.floodlightcontroller.core.module.FloodlightModuleConfigFileNotFoundException; import net.floodlightcontroller.core.module.FloodlightModuleException; import net.floodlightcontroller.core.module.FloodlightModuleLoader; import net.floodlightcontroller.core.module.IFloodlightModuleContext; @@ -30,36 +33,50 @@ import net.floodlightcontroller.restserver.IRestApiService; * @author alexreimers */ public class Main { + private static final Logger logger = LoggerFactory.getLogger(Main.class); - /** - * Main method to load configuration and modules - * @param args - * @throws FloodlightModuleException - */ - public static void main(String[] args) throws FloodlightModuleException { - // Setup logger - System.setProperty("org.restlet.engine.loggerFacadeClass", - "org.restlet.ext.slf4j.Slf4jLoggerFacade"); - - CmdLineSettings settings = new CmdLineSettings(); - CmdLineParser parser = new CmdLineParser(settings); - try { - parser.parseArgument(args); - } catch (CmdLineException e) { - parser.printUsage(System.out); - System.exit(1); - } - - // Load modules - FloodlightModuleLoader fml = new FloodlightModuleLoader(); - IFloodlightModuleContext moduleContext = fml.loadModulesFromConfig(settings.getModuleFile()); - // Run REST server - IRestApiService restApi = moduleContext.getServiceImpl(IRestApiService.class); - restApi.run(); - // Run the main floodlight module - IFloodlightProviderService controller = - moduleContext.getServiceImpl(IFloodlightProviderService.class); - // This call blocks, it has to be the last line in the main - controller.run(); - } + /** + * Main method to load configuration and modules + * @param args + * @throws FloodlightModuleException + */ + public static void main(String[] args) throws FloodlightModuleException { + try { + // Setup logger + System.setProperty("org.restlet.engine.loggerFacadeClass", + "org.restlet.ext.slf4j.Slf4jLoggerFacade"); + + CmdLineSettings settings = new CmdLineSettings(); + CmdLineParser parser = new CmdLineParser(settings); + try { + parser.parseArgument(args); + } catch (CmdLineException e) { + parser.printUsage(System.out); + System.exit(1); + } + + // Load modules + FloodlightModuleLoader fml = new FloodlightModuleLoader(); + try { + IFloodlightModuleContext moduleContext = fml.loadModulesFromConfig(settings.getModuleFile()); + // @Ryan TODO This should probably be implemented and run as a normal service for consistency; + // although, it does need all modules to be loaded and their prior to running. + IRestApiService restApi = moduleContext.getServiceImpl(IRestApiService.class); + restApi.run(); + } catch (FloodlightModuleConfigFileNotFoundException e) { + // we really want to log the message, not the stack trace + logger.error("Could not read config file: {}", e.getMessage()); + System.exit(1); + } + try { + fml.runModules(); // run the controller module and all modules + } catch (FloodlightModuleException e) { + logger.error("Failed to run controller modules", e); + System.exit(1); + } + } catch (Exception e) { + logger.error("Exception in main", e); + System.exit(1); + } + } } diff --git a/src/main/java/net/floodlightcontroller/core/NullConnection.java b/src/main/java/net/floodlightcontroller/core/NullConnection.java new file mode 100644 index 0000000000000000000000000000000000000000..41a5a1591bf631e2bd419b2ed996e1294dc3748a --- /dev/null +++ b/src/main/java/net/floodlightcontroller/core/NullConnection.java @@ -0,0 +1,114 @@ +package net.floodlightcontroller.core; + +import java.net.SocketAddress; +import java.util.List; + +import java.util.Date; +import net.floodlightcontroller.core.internal.IOFConnectionListener; +import org.projectfloodlight.openflow.protocol.OFFactories; +import org.projectfloodlight.openflow.protocol.OFFactory; +import org.projectfloodlight.openflow.protocol.OFMessage; +import org.projectfloodlight.openflow.protocol.OFRequest; +import org.projectfloodlight.openflow.protocol.OFStatsReply; +import org.projectfloodlight.openflow.protocol.OFStatsRequest; +import org.projectfloodlight.openflow.protocol.OFVersion; +import org.projectfloodlight.openflow.types.DatapathId; +import org.projectfloodlight.openflow.types.OFAuxId; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; + +public class NullConnection implements IOFConnectionBackend, IOFMessageWriter { + private static final Logger logger = LoggerFactory.getLogger(NullConnection.class); + + @Override + public boolean isConnected() { + return false; + } + + @Override + public Date getConnectedSince() { + return null; + } + + private void warn() { + logger.debug("Switch {} not connected -- cannot send message", getDatapathId()); + } + + @Override + public void write(OFMessage m) { + warn(); + } + + @Override + public void write(Iterable<OFMessage> msglist) { + warn(); + } + + @Override + public SocketAddress getRemoteInetAddress() { + return null; + } + + @Override + public SocketAddress getLocalInetAddress() { + return null; + } + + @Override + public OFFactory getOFFactory() { + return OFFactories.getFactory(OFVersion.OF_13); + } + + @Override + public <REPLY extends OFStatsReply> ListenableFuture<List<REPLY>> writeStatsRequest( + OFStatsRequest<REPLY> request) { + return Futures.immediateFailedFuture(new SwitchDisconnectedException(getDatapathId())); + } + + @Override + public void cancelAllPendingRequests() { + // noop + } + + @Override + public void flush() { + // noop + } + + @Override + public <R extends OFMessage> ListenableFuture<R> writeRequest(OFRequest<R> request) { + return Futures.immediateFailedFuture(new SwitchDisconnectedException(getDatapathId())); + } + + @Override + public void disconnect(){ + // noop + } + + public void disconnected() { + // noop + } + + @Override + public boolean isWritable() { + return false; + } + + @Override + public DatapathId getDatapathId() { + return DatapathId.NONE; + } + + @Override + public OFAuxId getAuxId() { + return OFAuxId.MAIN; + } + + @Override + public void setListener(IOFConnectionListener listener) { + } + +} \ No newline at end of file diff --git a/src/main/java/net/floodlightcontroller/core/OFConnection.java b/src/main/java/net/floodlightcontroller/core/OFConnection.java new file mode 100644 index 0000000000000000000000000000000000000000..e53c7dfd0c2c61eefd7d8e98f95d599da701d092 --- /dev/null +++ b/src/main/java/net/floodlightcontroller/core/OFConnection.java @@ -0,0 +1,408 @@ +/** + * Copyright 2012, Big Switch Networks, Inc. + * Originally created by David Erickson, Stanford University + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + **/ + +package net.floodlightcontroller.core; + +import java.net.SocketAddress; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import javax.annotation.Nonnull; + +import org.jboss.netty.channel.Channel; +import org.jboss.netty.util.Timeout; +import org.jboss.netty.util.Timer; +import org.jboss.netty.util.TimerTask; + +import java.util.Date; + +import net.floodlightcontroller.core.annotations.LogMessageDoc; +import net.floodlightcontroller.core.internal.Controller; +import net.floodlightcontroller.core.internal.IOFConnectionListener; +import net.floodlightcontroller.debugcounter.IDebugCounterService; + +import org.projectfloodlight.openflow.protocol.OFErrorMsg; +import org.projectfloodlight.openflow.protocol.OFFactory; +import org.projectfloodlight.openflow.protocol.OFMessage; +import org.projectfloodlight.openflow.protocol.OFRequest; +import org.projectfloodlight.openflow.protocol.OFStatsReply; +import org.projectfloodlight.openflow.protocol.OFStatsReplyFlags; +import org.projectfloodlight.openflow.protocol.OFStatsRequest; +import org.projectfloodlight.openflow.protocol.OFType; +import org.projectfloodlight.openflow.types.DatapathId; +import org.projectfloodlight.openflow.types.OFAuxId; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; + +/** + * Implementation of an openflow connection to switch. Encapsulates a + * {@link Channel}, and provides message write and request/response handling + * capabilities. + * + * @author Andreas Wundsam <andreas.wundsam@bigswitch.com> + */ +public class OFConnection implements IOFConnection, IOFConnectionBackend{ + private static final Logger logger = LoggerFactory.getLogger(OFConnection.class); + private final DatapathId dpid; + private final OFFactory factory; + private final Channel channel; + private final OFAuxId auxId; + private final Timer timer; + + private final Date connectedSince; + + private final Map<Long, Deliverable<?>> xidDeliverableMap; + + protected final static ThreadLocal<List<OFMessage>> localMsgBuffer = + new ThreadLocal<List<OFMessage>>(); + + private static final long DELIVERABLE_TIME_OUT = 60; + private static final TimeUnit DELIVERABLE_TIME_OUT_UNIT = TimeUnit.SECONDS; + + + private final OFConnectionCounters counters; + private IOFConnectionListener listener; + + public OFConnection(@Nonnull DatapathId dpid, + @Nonnull OFFactory factory, + @Nonnull Channel channel, + @Nonnull OFAuxId auxId, + @Nonnull IDebugCounterService debugCounters, + @Nonnull Timer timer) { + Preconditions.checkNotNull(dpid, "dpid"); + Preconditions.checkNotNull(factory, "factory"); + Preconditions.checkNotNull(channel, "channel"); + Preconditions.checkNotNull(timer, "timer"); + Preconditions.checkNotNull(debugCounters); + + this.listener = NullConnectionListener.INSTANCE; + this.dpid = dpid; + this.factory = factory; + this.channel = channel; + this.auxId = auxId; + this.connectedSince = new Date(); + this.xidDeliverableMap = new ConcurrentHashMap<>(); + this.counters = new OFConnectionCounters(debugCounters, dpid, this.auxId); + this.timer = timer; + } + + @Override + public void write(OFMessage m) { + if (!isConnected()) { + if (logger.isDebugEnabled()) + logger.debug("{}: not connected - dropping message {}", this, m); + return; + } + if (logger.isDebugEnabled()) + logger.debug("{}: send {}", this, m); + List<OFMessage> msgBuffer = localMsgBuffer.get(); + if (msgBuffer == null) { + msgBuffer = new ArrayList<OFMessage>(); + localMsgBuffer.set(msgBuffer); + } + + counters.updateWriteStats(m); + msgBuffer.add(m); + + if ((msgBuffer.size() >= Controller.BATCH_MAX_SIZE) || ((m.getType() != OFType.PACKET_OUT) && (m.getType() != OFType.FLOW_MOD))) { + this.write(msgBuffer); + localMsgBuffer.set(null); + } + } + + @Override + public <R extends OFMessage> ListenableFuture<R> writeRequest(OFRequest<R> request) { + if (!isConnected()) + return Futures.immediateFailedFuture(new SwitchDisconnectedException(getDatapathId())); + + DeliverableListenableFuture<R> future = new DeliverableListenableFuture<R>(); + xidDeliverableMap.put(request.getXid(), future); + write(request); + return future; + } + + @Override + @LogMessageDoc(level = "WARN", + message = "Sending OF message that modifies switch " + + "state while in the slave role: {switch}", + explanation = "An application has sent a message to a switch " + + "that is not valid when the switch is in a slave role", + recommendation = LogMessageDoc.REPORT_CONTROLLER_BUG) + public void write(Iterable<OFMessage> msglist) { + if (!isConnected()) { + if (logger.isDebugEnabled()) + logger.debug(this.toString() + " : not connected - dropping {} element msglist {} ", + Iterables.size(msglist), + String.valueOf(msglist).substring(0, 80)); + return; + } + for (OFMessage m : msglist) { + if (logger.isTraceEnabled()) + logger.trace("{}: send {}", this, m); + counters.updateWriteStats(m); + } + this.channel.write(msglist); + } + + // Notifies the connection object that the channel has been disconnected + public void disconnected() { + SwitchDisconnectedException exception = new SwitchDisconnectedException(getDatapathId()); + for (Long xid : xidDeliverableMap.keySet()) { + // protect against other mechanisms running at the same time + // (timeout) + Deliverable<?> removed = xidDeliverableMap.remove(xid); + if (removed != null) { + removed.deliverError(exception); + } + } + } + + @Override + public void disconnect() { + this.channel.disconnect(); + } + + @Override + public String toString() { + String channelString = (channel != null) ? String.valueOf(channel.getRemoteAddress()): "?"; + return "OFConnection [" + getDatapathId() + "(" + getAuxId() + ")" + "@" + channelString + "]"; + } + + @Override + public Date getConnectedSince() { + return connectedSince; + } + + @Override + public <REPLY extends OFStatsReply> ListenableFuture<List<REPLY>> writeStatsRequest( + OFStatsRequest<REPLY> request) { + if (!isConnected()) + return Futures.immediateFailedFuture(new SwitchDisconnectedException(getDatapathId())); + + final DeliverableListenableFuture<List<REPLY>> future = + new DeliverableListenableFuture<List<REPLY>>(); + + Deliverable<REPLY> deliverable = new Deliverable<REPLY>() { + private final List<REPLY> results = Collections + .synchronizedList(new ArrayList<REPLY>()); + + @Override + public void deliver(REPLY reply) { + results.add(reply); + if (!reply.getFlags().contains(OFStatsReplyFlags.REPLY_MORE)) { + // done + future.deliver(results); + } + } + + @Override + public void deliverError(Throwable cause) { + future.deliverError(cause); + } + + @Override + public boolean isDone() { + return future.isDone(); + } + + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + return future.cancel(mayInterruptIfRunning); + } + }; + + registerDeliverable(request.getXid(), deliverable); + this.write(request); + return future; + } + + private void registerDeliverable(long xid, Deliverable<?> deliverable) { + this.xidDeliverableMap.put(xid, deliverable); + timer.newTimeout(new TimeOutDeliverable(xid), DELIVERABLE_TIME_OUT, DELIVERABLE_TIME_OUT_UNIT); + } + + public boolean handleGenericDeliverable(OFMessage reply) { + counters.updateReadStats(reply); + @SuppressWarnings("unchecked") + Deliverable<OFMessage> deliverable = + (Deliverable<OFMessage>) this.xidDeliverableMap.get(reply.getXid()); + if (deliverable != null) { + if(reply instanceof OFErrorMsg) { + deliverable.deliverError(new OFErrorMsgException((OFErrorMsg) reply)); + } else { + deliverable.deliver(reply); + } + if (deliverable.isDone()) + this.xidDeliverableMap.remove(reply.getXid()); + return true; + } else { + return false; + } + } + + @Override + public void cancelAllPendingRequests() { + /* + * we don't need to be synchronized here. Even if another thread + * modifies the map while we're cleaning up the future will eventually + * timeout + */ + for (Deliverable<?> d : xidDeliverableMap.values()) { + d.cancel(true); + } + xidDeliverableMap.clear(); + } + + @Override + public boolean isConnected() { + return channel.isConnected(); + } + + @Override + public void flush() { + List<OFMessage> msglist = localMsgBuffer.get(); + if ((msglist != null) && (msglist.size() > 0)) { + this.write(msglist); + localMsgBuffer.set(null); + } + } + + @Override + public SocketAddress getRemoteInetAddress() { + return channel.getRemoteAddress(); + } + + @Override + public SocketAddress getLocalInetAddress() { + return channel.getLocalAddress(); + } + + public boolean deliverResponse(OFMessage m) { + if (handleGenericDeliverable(m)) + return true; + else + return false; + } + + @Override + public boolean isWritable() { + return channel.isWritable(); + } + + @Override + public DatapathId getDatapathId() { + return dpid; + } + + @Override + public OFAuxId getAuxId() { + return auxId; + } + + Set<Long> getPendingRequestIds() { + return ImmutableSet.copyOf(xidDeliverableMap.keySet()); + } + + @Override + public OFFactory getOFFactory() { + return this.factory; + } + + /** + * Timeout class instantiated for deliverables. Will throw a timeout exception + * if proper responses are not received in time. + * + */ + private class TimeOutDeliverable implements TimerTask { + private final long xid; + + public TimeOutDeliverable(long xid) { + this.xid = xid; + } + + @Override + public void run(Timeout timeout) throws Exception { + Deliverable<?> removed = xidDeliverableMap.remove(xid); + if (removed != null && !removed.isDone()) { + removed.deliverError(new TimeoutException( + "timeout - did not receive answer for xid " + xid)); + } + + } + } + + public IOFConnectionListener getListener() { + return listener; + } + + /** set the connection listener + * <p> + * Note: this is assumed to be called from the Connection's IO Thread. + * + * @param listener + */ + @Override + public void setListener(IOFConnectionListener listener) { + this.listener = listener; + } + + public void messageReceived(OFMessage m) { + // Check if message was a response for a xid waiting at the switch + if(!deliverResponse(m)){ + listener.messageReceived(this, m); + } + } + + /** A dummy connection listener that just logs warn messages. Saves us a few null checks + * @author Andreas Wundsam <andreas.wundsam@bigswitch.com> + */ + private static class NullConnectionListener implements IOFConnectionListener { + public final static NullConnectionListener INSTANCE = new NullConnectionListener(); + + private NullConnectionListener() { } + + @Override + public void connectionClosed(IOFConnectionBackend connection) { + logger.warn("NullConnectionListener for {} - received connectionClosed", connection); + } + + @Override + public void messageReceived(IOFConnectionBackend connection, OFMessage m) { + logger.warn("NullConnectionListener for {} - received messageReceived: {}", connection, m); + } + + @Override + public boolean isSwitchHandshakeComplete(IOFConnectionBackend connection) { + return false; + } + + } + + +} diff --git a/src/main/java/net/floodlightcontroller/core/OFConnectionCounters.java b/src/main/java/net/floodlightcontroller/core/OFConnectionCounters.java new file mode 100644 index 0000000000000000000000000000000000000000..403fb4bbda1c3bd0ec87c8423df624e45b69ab96 --- /dev/null +++ b/src/main/java/net/floodlightcontroller/core/OFConnectionCounters.java @@ -0,0 +1,710 @@ +package net.floodlightcontroller.core; + +import net.floodlightcontroller.debugcounter.IDebugCounter; +import net.floodlightcontroller.debugcounter.IDebugCounterService; +import org.projectfloodlight.openflow.protocol.OFMessage; +import org.projectfloodlight.openflow.protocol.OFType; +import org.projectfloodlight.openflow.types.DatapathId; +import org.projectfloodlight.openflow.types.OFAuxId; +import org.python.google.common.base.Preconditions; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Implementation of Counters for per-connection statistics for OpenFlow + * messages. + * @author Alok Shankar <alok@bigswitch.com> + */ +public class OFConnectionCounters { + public static final String COUNTER_MODULE = OFConnectionCounters.class.getPackage().getName(); + + /** + * Counters for open flow message types + */ + // Write Counters + // + private final IDebugCounter ctrWriteHello; + private final IDebugCounter ctrWriteError; + private final IDebugCounter ctrWriteEchoRequest; + private final IDebugCounter ctrWriteEchoReply; + private final IDebugCounter ctrWriteExperimenter; + private final IDebugCounter ctrWriteFeaturesRequest; + private final IDebugCounter ctrWriteFeaturesReply; + private final IDebugCounter ctrWriteGetConfigRequest; + private final IDebugCounter ctrWriteGetConfigReply; + private final IDebugCounter ctrWriteSetConfig; + private final IDebugCounter ctrWritePacketIn; + private final IDebugCounter ctrWritePacketOut; + private final IDebugCounter ctrWriteFlowRemoved; + private final IDebugCounter ctrWritePortStatus; + private final IDebugCounter ctrWriteFlowMod; + private final IDebugCounter ctrWritePortMod; + private final IDebugCounter ctrWriteStatsRequest; + private final IDebugCounter ctrWriteStatsReply; + private final IDebugCounter ctrWriteBarrierRequest; + private final IDebugCounter ctrWriteBarrierReply; + private final IDebugCounter ctrWriteGetAsyncReply; + private final IDebugCounter ctrWriteGetAsyncRequest; + private final IDebugCounter ctrWriteGroupMod; + private final IDebugCounter ctrWriteMeterMod; + private final IDebugCounter ctrWriteQueueGetConfigReply; + private final IDebugCounter ctrWriteQueueGetConfigRequest; + private final IDebugCounter ctrWriteRoleRequest; + private final IDebugCounter ctrWriteRoleReply; + private final IDebugCounter ctrWriteSetAsync; + private final IDebugCounter ctrWriteTableMod; + + // Read Counters + // + private final IDebugCounter ctrReadHello; + private final IDebugCounter ctrReadError; + private final IDebugCounter ctrReadEchoRequest; + private final IDebugCounter ctrReadEchoReply; + private final IDebugCounter ctrReadExperimenter; + private final IDebugCounter ctrReadFeaturesRequest; + private final IDebugCounter ctrReadFeaturesReply; + private final IDebugCounter ctrReadGetConfigRequest; + private final IDebugCounter ctrReadGetConfigReply; + private final IDebugCounter ctrReadSetConfig; + private final IDebugCounter ctrReadPacketIn; + private final IDebugCounter ctrReadPacketOut; + private final IDebugCounter ctrReadFlowRemoved; + private final IDebugCounter ctrReadPortStatus; + private final IDebugCounter ctrReadFlowMod; + private final IDebugCounter ctrReadPortMod; + private final IDebugCounter ctrReadStatsRequest; + private final IDebugCounter ctrReadStatsReply; + private final IDebugCounter ctrReadBarrierRequest; + private final IDebugCounter ctrReadBarrierReply; + private final IDebugCounter ctrReadGetAsyncReply; + private final IDebugCounter ctrReadGetAsyncRequest; + private final IDebugCounter ctrReadGroupMod; + private final IDebugCounter ctrReadMeterMod; + private final IDebugCounter ctrReadQueueGetConfigReply; + private final IDebugCounter ctrReadQueueGetConfigRequest; + private final IDebugCounter ctrReadRoleRequest; + private final IDebugCounter ctrReadRoleReply; + private final IDebugCounter ctrReadSetAsync; + private final IDebugCounter ctrReadTableMod; + + private static final Logger logger = + LoggerFactory.getLogger(OFConnectionCounters.class); + + /** + * Utility function to create description string and do counter registration + * @param countersService + * @param stringId The string ID + * @param messageType Type of open flow message + * @return the registered DebugCounter + */ + IDebugCounter registerCounterLocal(IDebugCounterService countersService, + String hierarchy, + String stringId, + String messageType){ + String counterHierarchy = stringId + hierarchy + "/" + messageType; + String counterDescription = "Number of " + messageType + + " messages in this connection"; + + return countersService.registerCounter(COUNTER_MODULE, counterHierarchy, + counterDescription); + } + + public OFConnectionCounters(IDebugCounterService counters, + DatapathId dpid, + OFAuxId auxId) { + + Preconditions.checkNotNull(counters, "Counters must not be null"); + Preconditions.checkNotNull(dpid, "dpid must not be null"); + Preconditions.checkNotNull(auxId, "auxid must not be null"); + + String stringId = dpid.toString() +":" + auxId.toString(); + String hierarchy = "/write"; + + // every level of the hierarchical counter has to be registered + // even if they are not used + + counters.registerCounter(COUNTER_MODULE, stringId , + "Counter for this connection"); + + registerCounterLocal(counters, + hierarchy, + stringId, + ""); + + ctrWriteHello = + registerCounterLocal(counters, + hierarchy, + stringId, + OFType.HELLO.toString()); + ctrWriteError = + registerCounterLocal(counters, + hierarchy, + stringId, + OFType.ERROR.toString()); + ctrWriteEchoRequest = + registerCounterLocal(counters, + hierarchy, + stringId, + OFType.ECHO_REQUEST.toString()); + ctrWriteEchoReply = + registerCounterLocal(counters, + hierarchy, + stringId, + OFType.ECHO_REPLY.toString()); + ctrWriteExperimenter = + registerCounterLocal(counters, + hierarchy, + stringId, + OFType.EXPERIMENTER.toString()); + ctrWriteFeaturesRequest = + registerCounterLocal(counters, + hierarchy, + stringId, + OFType.FEATURES_REQUEST.toString()); + ctrWriteFeaturesReply = + registerCounterLocal(counters, + hierarchy, + stringId, + OFType.FEATURES_REPLY.toString()); + ctrWriteGetConfigRequest = + registerCounterLocal(counters, + hierarchy, + stringId, + OFType.GET_CONFIG_REQUEST.toString()); + ctrWriteGetConfigReply = + registerCounterLocal(counters, + hierarchy, + stringId, + OFType.GET_CONFIG_REPLY.toString()); + ctrWriteSetConfig = + registerCounterLocal(counters, + hierarchy, + stringId, + OFType.SET_CONFIG.toString()); + ctrWritePacketIn = + registerCounterLocal(counters, + hierarchy, + stringId, + OFType.PACKET_IN.toString()); + ctrWritePacketOut = + registerCounterLocal(counters, + hierarchy, + stringId, + OFType.PACKET_OUT.toString()); + ctrWriteFlowRemoved = + registerCounterLocal(counters, + hierarchy, + stringId, + OFType.FLOW_REMOVED.toString()); + ctrWritePortStatus = + registerCounterLocal(counters, + hierarchy, + stringId, + OFType.PORT_STATUS.toString()); + ctrWriteFlowMod = + registerCounterLocal(counters, + hierarchy, + stringId, + OFType.FLOW_MOD.toString()); + ctrWritePortMod = + registerCounterLocal(counters, + hierarchy, + stringId, + OFType.PORT_MOD.toString()); + ctrWriteStatsRequest = + registerCounterLocal(counters, + hierarchy, + stringId, + OFType.STATS_REQUEST.toString()); + ctrWriteStatsReply = + registerCounterLocal(counters, + hierarchy, + stringId, + OFType.STATS_REPLY.toString()); + ctrWriteBarrierRequest = + registerCounterLocal(counters, + hierarchy, + stringId, + OFType.BARRIER_REQUEST.toString()); + ctrWriteBarrierReply = + registerCounterLocal(counters, + hierarchy, + stringId, + OFType.BARRIER_REPLY.toString()); + ctrWriteGetAsyncReply = + registerCounterLocal(counters, + hierarchy, + stringId, + OFType.GET_ASYNC_REPLY.toString()); + ctrWriteGetAsyncRequest = + registerCounterLocal(counters, + hierarchy, + stringId, + OFType.GET_ASYNC_REQUEST.toString()); + ctrWriteGroupMod = + registerCounterLocal(counters, + hierarchy, + stringId, + OFType.GROUP_MOD.toString()); + ctrWriteMeterMod = + registerCounterLocal(counters, + hierarchy, + stringId, + OFType.METER_MOD.toString()); + ctrWriteQueueGetConfigReply = + registerCounterLocal(counters, + hierarchy, + stringId, + OFType.QUEUE_GET_CONFIG_REPLY.toString()); + ctrWriteQueueGetConfigRequest = + registerCounterLocal(counters, + hierarchy, + stringId, + OFType.QUEUE_GET_CONFIG_REQUEST.toString()); + ctrWriteRoleReply = + registerCounterLocal(counters, + hierarchy, + stringId, + OFType.ROLE_REPLY.toString()); + ctrWriteRoleRequest = + registerCounterLocal(counters, + hierarchy, + stringId, + OFType.ROLE_REQUEST.toString()); + ctrWriteSetAsync = + registerCounterLocal(counters, + hierarchy, + stringId, + OFType.SET_ASYNC.toString()); + ctrWriteTableMod = + registerCounterLocal(counters, + hierarchy, + stringId, + OFType.TABLE_MOD.toString()); + + // Register Read Counters + // + hierarchy = "/read"; + + registerCounterLocal(counters, + hierarchy, + stringId, + ""); + ctrReadHello = + registerCounterLocal(counters, + hierarchy, + stringId, + OFType.HELLO.toString()); + ctrReadError = + registerCounterLocal(counters, + hierarchy, + stringId, + OFType.ERROR.toString()); + ctrReadEchoRequest = + registerCounterLocal(counters, + hierarchy, + stringId, + OFType.ECHO_REQUEST.toString()); + ctrReadEchoReply = + registerCounterLocal(counters, + hierarchy, + stringId, + OFType.ECHO_REPLY.toString()); + ctrReadExperimenter = + registerCounterLocal(counters, + hierarchy, + stringId, + OFType.EXPERIMENTER.toString()); + ctrReadFeaturesRequest = + registerCounterLocal(counters, + hierarchy, + stringId, + OFType.FEATURES_REQUEST.toString()); + ctrReadFeaturesReply = + registerCounterLocal(counters, + hierarchy, + stringId, + OFType.FEATURES_REPLY.toString()); + ctrReadGetConfigRequest = + registerCounterLocal(counters, + hierarchy, + stringId, + OFType.GET_CONFIG_REQUEST.toString()); + ctrReadGetConfigReply = + registerCounterLocal(counters, + hierarchy, + stringId, + OFType.GET_CONFIG_REPLY.toString()); + ctrReadSetConfig = + registerCounterLocal(counters, + hierarchy, + stringId, + OFType.SET_CONFIG.toString()); + ctrReadPacketIn = + registerCounterLocal(counters, + hierarchy, + stringId, + OFType.PACKET_IN.toString()); + ctrReadPacketOut = + registerCounterLocal(counters, + hierarchy, + stringId, + OFType.PACKET_OUT.toString()); + ctrReadFlowRemoved = + registerCounterLocal(counters, + hierarchy, + stringId, + OFType.FLOW_REMOVED.toString()); + ctrReadPortStatus = + registerCounterLocal(counters, + hierarchy, + stringId, + OFType.PORT_STATUS.toString()); + ctrReadFlowMod = + registerCounterLocal(counters, + hierarchy, + stringId, + OFType.FLOW_MOD.toString()); + ctrReadPortMod = + registerCounterLocal(counters, + hierarchy, + stringId, + OFType.PORT_MOD.toString()); + ctrReadStatsRequest = + registerCounterLocal(counters, + hierarchy, + stringId, + OFType.STATS_REQUEST.toString()); + ctrReadStatsReply = + registerCounterLocal(counters, + hierarchy, + stringId, + OFType.STATS_REPLY.toString()); + ctrReadBarrierRequest = + registerCounterLocal(counters, + hierarchy, + stringId, + OFType.BARRIER_REQUEST.toString()); + ctrReadBarrierReply = + registerCounterLocal(counters, + hierarchy, + stringId, + OFType.BARRIER_REPLY.toString()); + ctrReadGetAsyncReply = + registerCounterLocal(counters, + hierarchy, + stringId, + OFType.GET_ASYNC_REPLY.toString()); + ctrReadGetAsyncRequest = + registerCounterLocal(counters, + hierarchy, + stringId, + OFType.GET_ASYNC_REQUEST.toString()); + ctrReadGroupMod = + registerCounterLocal(counters, + hierarchy, + stringId, + OFType.GROUP_MOD.toString()); + ctrReadMeterMod = + registerCounterLocal(counters, + hierarchy, + stringId, + OFType.METER_MOD.toString()); + ctrReadQueueGetConfigReply = + registerCounterLocal(counters, + hierarchy, + stringId, + OFType.QUEUE_GET_CONFIG_REPLY.toString()); + ctrReadQueueGetConfigRequest = + registerCounterLocal(counters, + hierarchy, + stringId, + OFType.QUEUE_GET_CONFIG_REQUEST.toString()); + ctrReadRoleReply = + registerCounterLocal(counters, + hierarchy, + stringId, + OFType.ROLE_REPLY.toString()); + ctrReadRoleRequest = + registerCounterLocal(counters, + hierarchy, + stringId, + OFType.ROLE_REQUEST.toString()); + ctrReadSetAsync = + registerCounterLocal(counters, + hierarchy, + stringId, + OFType.SET_ASYNC.toString()); + ctrReadTableMod = + registerCounterLocal(counters, + hierarchy, + stringId, + OFType.TABLE_MOD.toString()); + } + + /** + * Update Write Counters for Open flow messages + * @param ofm openflow message + */ + public void updateWriteStats(OFMessage ofm) { + switch(ofm.getType()){ + case BARRIER_REPLY: + ctrWriteBarrierReply.increment(); + break; + + case BARRIER_REQUEST: + ctrWriteBarrierRequest.increment(); + break; + + case ECHO_REPLY: + ctrWriteEchoReply.increment(); + break; + + case ECHO_REQUEST: + ctrWriteEchoRequest.increment(); + break; + + case ERROR: + ctrWriteError.increment(); + break; + + case EXPERIMENTER: + ctrWriteExperimenter.increment(); + break; + + case FEATURES_REPLY: + ctrWriteFeaturesReply.increment(); + break; + + case FEATURES_REQUEST: + ctrWriteFeaturesRequest.increment(); + break; + + case FLOW_MOD: + ctrWriteFlowMod.increment(); + break; + + case FLOW_REMOVED: + ctrWriteFlowRemoved.increment(); + break; + + case GET_ASYNC_REPLY: + ctrWriteGetAsyncReply.increment(); + break; + + case GET_ASYNC_REQUEST: + ctrWriteGetAsyncRequest.increment(); + break; + + case GET_CONFIG_REPLY: + ctrWriteGetConfigReply.increment(); + break; + + case GET_CONFIG_REQUEST: + ctrWriteGetConfigRequest.increment(); + break; + + case GROUP_MOD: + ctrWriteGroupMod.increment(); + break; + + case HELLO: + ctrWriteHello.increment(); + break; + + case METER_MOD: + ctrWriteMeterMod.increment(); + break; + + case PACKET_IN: + ctrWritePacketIn.increment(); + break; + + case PACKET_OUT: + ctrWritePacketOut.increment(); + break; + + case PORT_MOD: + ctrWritePortMod.increment(); + break; + + case PORT_STATUS: + ctrWritePortStatus.increment(); + break; + + case QUEUE_GET_CONFIG_REPLY: + ctrWriteQueueGetConfigReply.increment(); + break; + + case QUEUE_GET_CONFIG_REQUEST: + ctrWriteQueueGetConfigRequest.increment(); + break; + + case ROLE_REPLY: + ctrWriteRoleReply.increment(); + break; + + case ROLE_REQUEST: + ctrWriteRoleRequest.increment(); + break; + + case SET_ASYNC: + ctrWriteSetAsync.increment(); + break; + + case SET_CONFIG: + ctrWriteSetConfig.increment(); + break; + + case STATS_REPLY: + ctrWriteStatsReply.increment(); + break; + + case STATS_REQUEST: + ctrWriteStatsRequest.increment(); + break; + + case TABLE_MOD: + ctrWriteTableMod.increment(); + break; + + default: + logger.warn(ofm.getType().toString() + + ": Invalid OpenFlow Messaqe!"); + break; + } + } + + /** + * Update Read openflow counters for this connection + * @param ofm Open Flow Message + */ + public void updateReadStats(OFMessage ofm){ + switch(ofm.getType()){ + case BARRIER_REPLY: + ctrReadBarrierReply.increment(); + break; + + case BARRIER_REQUEST: + ctrReadBarrierRequest.increment(); + break; + + case ECHO_REPLY: + ctrReadEchoReply.increment(); + break; + + case ECHO_REQUEST: + ctrReadEchoRequest.increment(); + break; + + case ERROR: + ctrReadError.increment(); + break; + + case EXPERIMENTER: + ctrReadExperimenter.increment(); + break; + + case FEATURES_REPLY: + ctrReadFeaturesReply.increment(); + break; + + case FEATURES_REQUEST: + ctrReadFeaturesRequest.increment(); + break; + + case FLOW_MOD: + ctrReadFlowMod.increment(); + break; + + case FLOW_REMOVED: + ctrReadFlowRemoved.increment(); + break; + + case GET_ASYNC_REPLY: + ctrReadGetAsyncReply.increment(); + break; + + case GET_ASYNC_REQUEST: + ctrReadGetAsyncRequest.increment(); + break; + + case GET_CONFIG_REPLY: + ctrReadGetConfigReply.increment(); + break; + + case GET_CONFIG_REQUEST: + ctrReadGetConfigRequest.increment(); + break; + + case GROUP_MOD: + ctrReadGroupMod.increment(); + break; + + case HELLO: + ctrReadHello.increment(); + break; + + case METER_MOD: + ctrReadMeterMod.increment(); + break; + + case PACKET_IN: + ctrReadPacketIn.increment(); + break; + + case PACKET_OUT: + ctrReadPacketOut.increment(); + break; + + case PORT_MOD: + ctrReadPortMod.increment(); + break; + + case PORT_STATUS: + ctrReadPortStatus.increment(); + break; + + case QUEUE_GET_CONFIG_REPLY: + ctrReadQueueGetConfigReply.increment(); + break; + + case QUEUE_GET_CONFIG_REQUEST: + ctrReadQueueGetConfigRequest.increment(); + break; + + case ROLE_REPLY: + ctrReadRoleReply.increment(); + break; + + case ROLE_REQUEST: + ctrReadRoleRequest.increment(); + break; + + case SET_ASYNC: + ctrReadSetAsync.increment(); + break; + + case SET_CONFIG: + ctrReadSetConfig.increment(); + break; + + case STATS_REPLY: + ctrReadStatsReply.increment(); + break; + + case STATS_REQUEST: + ctrReadStatsRequest.increment(); + break; + + case TABLE_MOD: + ctrReadTableMod.increment(); + break; + + default: + logger.warn(ofm.getType().toString() + + ": Invalid OpenFlow Messaqe!"); + break; + } + } +} diff --git a/src/main/java/net/floodlightcontroller/core/OFErrorMsgException.java b/src/main/java/net/floodlightcontroller/core/OFErrorMsgException.java new file mode 100644 index 0000000000000000000000000000000000000000..f990f0a451052dfac060673e5bca7b20d6e802ad --- /dev/null +++ b/src/main/java/net/floodlightcontroller/core/OFErrorMsgException.java @@ -0,0 +1,28 @@ +package net.floodlightcontroller.core; + +import org.projectfloodlight.openflow.protocol.OFErrorMsg; +import org.projectfloodlight.openflow.protocol.OFRequest; + +/** raised/reported by Futures in @IOFConnection when a an + * {@link OFErrorMsg} is received in response to a {@link OFRequest} + * sent via {@link OFConnection#writeRequest(OFRequest)} or + * {@link OFConnection#writeStatsRequest(org.projectfloodlight.openflow.protocol.OFStatsRequest)}. + * + * @author Andreas Wundsam <andreas.wundsam@bigswitch.com> + */ +public class OFErrorMsgException extends Exception { + private static final long serialVersionUID = 1L; + + private final OFErrorMsg errorMessage; + + public OFErrorMsgException(final OFErrorMsg errorMessage) { + super("OF error received: " + errorMessage.toString()); + this.errorMessage = errorMessage; + } + + /** @return the received OFErrorMsg that caused the error to be raised. + */ + public OFErrorMsg getErrorMessage() { + return errorMessage; + } +} diff --git a/src/main/java/net/floodlightcontroller/core/OFMessageFilterManager.java b/src/main/java/net/floodlightcontroller/core/OFMessageFilterManager.java deleted file mode 100644 index c5adf4470355476526cfe896c92195760d5e8d0f..0000000000000000000000000000000000000000 --- a/src/main/java/net/floodlightcontroller/core/OFMessageFilterManager.java +++ /dev/null @@ -1,532 +0,0 @@ -/** - * Copyright 2011, Big Switch Networks, Inc. - * Originally created by David Erickson, Stanford University - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. You may obtain - * a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - **/ - -package net.floodlightcontroller.core; - -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Map; -import java.util.Timer; -import java.util.TimerTask; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ScheduledExecutorService; - -import org.jboss.netty.buffer.ChannelBuffer; -import org.jboss.netty.buffer.ChannelBuffers; -import org.openflow.protocol.OFFlowMod; -import org.openflow.protocol.OFMessage; -import org.openflow.protocol.OFPacketIn; -import org.openflow.protocol.OFPacketOut; -import org.openflow.protocol.OFType; -import org.openflow.util.HexString; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.List; -import java.util.ArrayList; -import org.apache.thrift.TException; -import org.apache.thrift.transport.TFramedTransport; -import org.apache.thrift.transport.TTransport; -import org.apache.thrift.transport.TSocket; -import org.apache.thrift.protocol.TBinaryProtocol; -import org.apache.thrift.protocol.TProtocol; - -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.packet.Ethernet; -import net.floodlightcontroller.packetstreamer.thrift.*; -import net.floodlightcontroller.threadpool.IThreadPoolService; - -@LogMessageCategory("OpenFlow Message Tracing") -public class OFMessageFilterManager - implements IOFMessageListener, IFloodlightModule, IOFMessageFilterManagerService { - - /** - * @author Srini - */ - protected static Logger log = LoggerFactory.getLogger(OFMessageFilterManager.class); - - // The port and client reference for packet streaming - protected int serverPort = 9090; - protected final int MaxRetry = 1; - protected static volatile TTransport transport = null; - protected static volatile PacketStreamer.Client packetClient = null; - - protected IFloodlightProviderService floodlightProvider = null; - protected IThreadPoolService threadPool = null; - // filter List is a key value pair. Key is the session id, - // value is the filter rules. - protected ConcurrentHashMap<String, - ConcurrentHashMap<String, - String>> filterMap = null; - protected ConcurrentHashMap<String, Long> filterTimeoutMap = null; - protected Timer timer = null; - - protected int MAX_FILTERS=5; - protected long MAX_FILTER_TIME= 300000; // maximum filter time is 5 minutes. - protected int TIMER_INTERVAL = 1000; // 1 second time interval. - - public static final String SUCCESS = "0"; - public static final String FILTER_SETUP_FAILED = "-1001"; - public static final String FILTER_NOT_FOUND = "-1002"; - public static final String FILTER_LIMIT_REACHED = "-1003"; - public static final String FILTER_SESSION_ID_NOT_FOUND = "-1004"; - public static final String SERVICE_UNAVAILABLE = "-1005"; - - public enum FilterResult { - /* - * FILTER_NOT_DEFINED: Filter is not defined - * FILTER_NO_MATCH: Filter is defined and the packet doesn't - * match the filter - * FILTER_MATCH: Filter is defined and the packet matches - * the filter - */ - FILTER_NOT_DEFINED, FILTER_NO_MATCH, FILTER_MATCH - } - - protected String addFilter(ConcurrentHashMap<String,String> f, long delta) { - - // Create unique session ID. - int prime = 33791; - String s = null; - int i; - - if ((filterMap == null) || (filterTimeoutMap == null)) - return String.format("%s", FILTER_SETUP_FAILED); - - for (i=0; i<MAX_FILTERS; ++i) { - Integer x = prime + i; - s = String.format("%d", x.hashCode()); - // implies you can use this key for session id. - if (!filterMap.containsKey(s)) break; - } - - if (i==MAX_FILTERS) { - return FILTER_LIMIT_REACHED; - } - - filterMap.put(s, f); - if (filterTimeoutMap.containsKey(s)) filterTimeoutMap.remove(s); - filterTimeoutMap.put(s, delta); - - // set the timer as there will be no existing timers. - if (filterMap.size() == 1) { - TimeoutFilterTask task = new TimeoutFilterTask(this); - Timer timer = new Timer(); - timer.schedule (task, TIMER_INTERVAL); - // Keep the listeners to avoid race condition - //startListening(); - } - return s; // the return string is the session ID. - } - - @Override - public String setupFilter(String sid, - ConcurrentHashMap<String,String> f, - int deltaInMilliSeconds) { - - if (sid == null) { - // Delta in filter needs to be milliseconds - log.debug("Adding new filter: {} for {} ms", f, deltaInMilliSeconds); - return addFilter(f, deltaInMilliSeconds); - } else {// this is the session id. - // we will ignore the hash map features. - if (deltaInMilliSeconds > 0) - return refreshFilter(sid, deltaInMilliSeconds); - else - return deleteFilter(sid); - } - } - - public int timeoutFilters() { - Iterator<String> i = filterTimeoutMap.keySet().iterator(); - - while(i.hasNext()) { - String s = i.next(); - - Long t = filterTimeoutMap.get(s); - if (t != null) { - i.remove(); - t -= TIMER_INTERVAL; - if (t > 0) { - filterTimeoutMap.put(s, t); - } else deleteFilter(s); - } else deleteFilter(s); - } - return filterMap.size(); - } - - protected String refreshFilter(String s, int delta) { - Long t = filterTimeoutMap.get(s); - if (t != null) { - filterTimeoutMap.remove(s); - t += delta; // time is in milliseconds - if (t > MAX_FILTER_TIME) t = MAX_FILTER_TIME; - filterTimeoutMap.put(s, t); - return SUCCESS; - } else return FILTER_SESSION_ID_NOT_FOUND; - } - - @LogMessageDoc(level="ERROR", - message="Error while terminating packet " + - "filter session", - explanation="An unknown error occurred while terminating " + - "a packet filter session.", - recommendation=LogMessageDoc.GENERIC_ACTION) - protected String deleteFilter(String sessionId) { - - if (filterMap.containsKey(sessionId)) { - filterMap.remove(sessionId); - try { - if (packetClient != null) - packetClient.terminateSession(sessionId); - } catch (TException e) { - log.error("Error while terminating packet " + - "filter session", e); - } - log.debug("Deleted Filter {}. # of filters" + - " remaining: {}", sessionId, filterMap.size()); - return SUCCESS; - } else return FILTER_SESSION_ID_NOT_FOUND; - } - - public HashSet<String> getMatchedFilters(OFMessage m, FloodlightContext cntx) { - - HashSet<String> matchedFilters = new HashSet<String>(); - - // This default function is written to match on packet ins and - // packet outs. - Ethernet eth = null; - - if (m.getType() == OFType.PACKET_IN) { - eth = IFloodlightProviderService.bcStore.get(cntx, - IFloodlightProviderService.CONTEXT_PI_PAYLOAD); - } else if (m.getType() == OFType.PACKET_OUT) { - eth = new Ethernet(); - OFPacketOut p = (OFPacketOut) m; - - // No MAC match if packetOut doesn't have the packet. - if (p.getPacketData() == null) return null; - - eth.deserialize(p.getPacketData(), 0, p.getPacketData().length); - } else if (m.getType() == OFType.FLOW_MOD) { - // flow-mod can't be matched by mac. - return null; - } - - if (eth == null) return null; - - Iterator<String> filterIt = filterMap.keySet().iterator(); - while (filterIt.hasNext()) { // for every filter - boolean filterMatch = false; - String filterSessionId = filterIt.next(); - Map<String,String> filter = filterMap.get(filterSessionId); - - // If the filter has empty fields, then it is not considered as a match. - if (filter == null || filter.isEmpty()) continue; - Iterator<String> fieldIt = filter.keySet().iterator(); - while (fieldIt.hasNext()) { - String filterFieldType = fieldIt.next(); - String filterFieldValue = filter.get(filterFieldType); - if (filterFieldType.equals("mac")) { - - String srcMac = HexString.toHexString(eth.getSourceMACAddress()); - String dstMac = HexString.toHexString(eth.getDestinationMACAddress()); - log.debug("srcMac: {}, dstMac: {}", srcMac, dstMac); - - if (filterFieldValue.equals(srcMac) || - filterFieldValue.equals(dstMac)){ - filterMatch = true; - } else { - filterMatch = false; - break; - } - } - } - if (filterMatch) { - matchedFilters.add(filterSessionId); - } - } - - if (matchedFilters.isEmpty()) - return null; - else - return matchedFilters; - } - - @LogMessageDoc(level="ERROR", - message="Failed to establish connection with the " + - "packetstreamer server.", - explanation="The message tracing server is not running " + - "or otherwise unavailable.", - recommendation=LogMessageDoc.CHECK_CONTROLLER) - public boolean connectToPSServer() { - int numRetries = 0; - if (transport != null && transport.isOpen()) { - return true; - } - - while (numRetries++ < MaxRetry) { - try { - TFramedTransport t = - new TFramedTransport(new TSocket("localhost", - serverPort)); - t.open(); - - TProtocol protocol = new TBinaryProtocol(t); - packetClient = new PacketStreamer.Client(protocol); - - log.debug("Have a connection to packetstreamer server " + - "localhost:{}", serverPort); - transport = t; - break; - } catch (TException x) { - try { - // Wait for 1 second before retry - if (numRetries < MaxRetry) { - Thread.sleep(1000); - } - } catch (Exception e) {} - } - } - - if (numRetries > MaxRetry) { - log.error("Failed to establish connection with the " + - "packetstreamer server."); - return false; - } - return true; - } - - public void disconnectFromPSServer() { - if (transport != null && transport.isOpen()) { - log.debug("Close the connection to packetstreamer server" + - " localhost:{}", serverPort); - transport.close(); - } - } - - @Override - public String getName() { - return "messageFilterManager"; - } - - @Override - public boolean isCallbackOrderingPrereq(OFType type, String name) { - return (type == OFType.PACKET_IN && name.equals("devicemanager")); - } - - @Override - public boolean isCallbackOrderingPostreq(OFType type, String name) { - return (type == OFType.PACKET_IN && name.equals("learningswitch")); - } - - @Override - @LogMessageDoc(level="ERROR", - message="Error while sending packet", - explanation="Failed to send a message to the message " + - "tracing server", - recommendation=LogMessageDoc.CHECK_CONTROLLER) - public Command receive(IOFSwitch sw, OFMessage msg, - FloodlightContext cntx) { - - if (filterMap == null || filterMap.isEmpty()) return Command.CONTINUE; - - HashSet<String> matchedFilters = null; - if (log.isDebugEnabled()) { - log.debug("Received packet {} from switch {}", - msg, sw.getStringId()); - } - - matchedFilters = getMatchedFilters(msg, cntx); - if (matchedFilters == null) { - return Command.CONTINUE; - } else { - try { - sendPacket(matchedFilters, sw, msg, cntx, true); - } catch (Exception e) { - log.error("Error while sending packet", e); - } - } - - return Command.CONTINUE; - } - - - public class TimeoutFilterTask extends TimerTask { - - OFMessageFilterManager filterManager; - ScheduledExecutorService ses = threadPool.getScheduledExecutor(); - - public TimeoutFilterTask(OFMessageFilterManager manager) { - filterManager = manager; - } - - public void run() { - int x = filterManager.timeoutFilters(); - - if (x > 0) { // there's at least one filter still active. - Timer timer = new Timer(); - timer.schedule(new TimeoutFilterTask(filterManager), - TIMER_INTERVAL); - } else { - // Don't stop the listener to avoid race condition - //stopListening(); - } - } - } - - public int getNumberOfFilters() { - return filterMap.size(); - } - - public int getMaxFilterSize() { - return MAX_FILTERS; - } - - protected void sendPacket(HashSet<String> matchedFilters, IOFSwitch sw, - OFMessage msg, FloodlightContext cntx, boolean sync) - throws TException { - Message sendMsg = new Message(); - Packet packet = new Packet(); - ChannelBuffer bb; - sendMsg.setPacket(packet); - - List<String> sids = new ArrayList<String>(matchedFilters); - - sendMsg.setSessionIDs(sids); - packet.setMessageType(OFMessageType.findByValue((msg.getType().ordinal()))); - - switch (msg.getType()) { - case PACKET_IN: - OFPacketIn pktIn = (OFPacketIn)msg; - packet.setSwPortTuple(new SwitchPortTuple(sw.getId(), - pktIn.getInPort())); - bb = ChannelBuffers.buffer(pktIn.getLength()); - pktIn.writeTo(bb); - packet.setData(OFMessage.getData(sw, msg, cntx)); - break; - case PACKET_OUT: - OFPacketOut pktOut = (OFPacketOut)msg; - packet.setSwPortTuple(new SwitchPortTuple(sw.getId(), - pktOut.getInPort())); - bb = ChannelBuffers.buffer(pktOut.getLength()); - pktOut.writeTo(bb); - packet.setData(OFMessage.getData(sw, msg, cntx)); - break; - case FLOW_MOD: - OFFlowMod offlowMod = (OFFlowMod)msg; - packet.setSwPortTuple(new SwitchPortTuple(sw.getId(), - offlowMod. - getOutPort())); - bb = ChannelBuffers.buffer(offlowMod.getLength()); - offlowMod.writeTo(bb); - packet.setData(OFMessage.getData(sw, msg, cntx)); - break; - default: - packet.setSwPortTuple(new SwitchPortTuple(sw.getId(), - (short)0)); - String strData = "Unknown packet"; - packet.setData(strData.getBytes()); - break; - } - - try { - if (transport == null || - !transport.isOpen() || - packetClient == null) { - if (!connectToPSServer()) { - // No need to sendPacket if can't make connection to - // the server - return; - } - } - if (sync) { - log.debug("Send packet sync: {}", packet.toString()); - packetClient.pushMessageSync(sendMsg); - } else { - log.debug("Send packet sync: ", packet.toString()); - packetClient.pushMessageAsync(sendMsg); - } - } catch (Exception e) { - log.error("Error while sending packet", e); - disconnectFromPSServer(); - connectToPSServer(); - } - } - - // IFloodlightModule methods - - @Override - public Collection<Class<? extends IFloodlightService>> getModuleServices() { - Collection<Class<? extends IFloodlightService>> l = - new ArrayList<Class<? extends IFloodlightService>>(); - l.add(IOFMessageFilterManagerService.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(IOFMessageFilterManagerService.class, this); - return m; - } - - @Override - public Collection<Class<? extends IFloodlightService>> getModuleDependencies() { - Collection<Class<? extends IFloodlightService>> l = - new ArrayList<Class<? extends IFloodlightService>>(); - l.add(IFloodlightProviderService.class); - l.add(IThreadPoolService.class); - return l; - } - - @Override - public void init(FloodlightModuleContext context) - throws FloodlightModuleException { - this.floodlightProvider = - context.getServiceImpl(IFloodlightProviderService.class); - this.threadPool = - context.getServiceImpl(IThreadPoolService.class); - } - - @Override - public void startUp(FloodlightModuleContext context) { - // This is our 'constructor' - - filterMap = new ConcurrentHashMap<String, ConcurrentHashMap<String,String>>(); - filterTimeoutMap = new ConcurrentHashMap<String, Long>(); - serverPort = - Integer.parseInt(System.getProperty("net.floodlightcontroller." + - "packetstreamer.port", "9090")); - - floodlightProvider.addOFMessageListener(OFType.PACKET_IN, this); - floodlightProvider.addOFMessageListener(OFType.PACKET_OUT, this); - floodlightProvider.addOFMessageListener(OFType.FLOW_MOD, this); - } -} diff --git a/src/main/java/net/floodlightcontroller/core/OFSwitch.java b/src/main/java/net/floodlightcontroller/core/OFSwitch.java new file mode 100644 index 0000000000000000000000000000000000000000..ee6d4c942ac235fb92c667fc7207d804630ba30c --- /dev/null +++ b/src/main/java/net/floodlightcontroller/core/OFSwitch.java @@ -0,0 +1,1106 @@ +/** +* Copyright 2012, Big Switch Networks, Inc. +* Originally created by David Erickson, Stanford University +* +* Licensed under the Apache License, Version 2.0 (the "License"); you may +* not use this file except in compliance with the License. You may obtain +* a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +* License for the specific language governing permissions and limitations +* under the License. +**/ + +package net.floodlightcontroller.core; + +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.net.URI; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import javax.annotation.Nonnull; + +import net.floodlightcontroller.core.annotations.LogMessageDoc; +import net.floodlightcontroller.core.internal.IOFSwitchManager; +import net.floodlightcontroller.core.util.AppCookie; +import net.floodlightcontroller.core.util.URIUtil; + +import org.projectfloodlight.openflow.protocol.OFActionType; +import org.projectfloodlight.openflow.protocol.OFBsnControllerConnection; +import org.projectfloodlight.openflow.protocol.OFBsnControllerConnectionState; +import org.projectfloodlight.openflow.protocol.OFBsnControllerConnectionsReply; +import org.projectfloodlight.openflow.protocol.OFCapabilities; +import org.projectfloodlight.openflow.protocol.OFControllerRole; +import org.projectfloodlight.openflow.protocol.OFFactory; +import org.projectfloodlight.openflow.protocol.OFFeaturesReply; +import org.projectfloodlight.openflow.protocol.OFFlowWildcards; +import org.projectfloodlight.openflow.protocol.OFMessage; +import org.projectfloodlight.openflow.protocol.OFPortConfig; +import org.projectfloodlight.openflow.protocol.OFPortDesc; +import org.projectfloodlight.openflow.protocol.OFPortDescStatsReply; +import org.projectfloodlight.openflow.protocol.OFPortReason; +import org.projectfloodlight.openflow.protocol.OFPortState; +import org.projectfloodlight.openflow.protocol.OFPortStatus; +import org.projectfloodlight.openflow.protocol.OFRequest; +import org.projectfloodlight.openflow.protocol.OFStatsReply; +import org.projectfloodlight.openflow.protocol.OFStatsRequest; +import org.projectfloodlight.openflow.protocol.OFVersion; +import org.projectfloodlight.openflow.types.DatapathId; +import org.projectfloodlight.openflow.types.OFAuxId; +import org.projectfloodlight.openflow.types.OFPort; + +import net.floodlightcontroller.util.LinkedHashSetWrapper; +import net.floodlightcontroller.util.OrderedCollection; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.util.concurrent.ListenableFuture; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +/** + * This is the internal representation of an openflow switch. + */ +public class OFSwitch implements IOFSwitchBackend { + protected static final Logger log = + LoggerFactory.getLogger(OFSwitch.class); + + protected final ConcurrentMap<Object, Object> attributes; + protected final IOFSwitchManager switchManager; + + /* Switch features from initial featuresReply */ + protected Set<OFCapabilities> capabilities; + protected long buffers; + protected Set<OFActionType> actions; + protected short tables; + protected final DatapathId datapathId; + + private boolean startDriverHandshakeCalled = false; + private final Map<OFAuxId, IOFConnectionBackend> connections; + private volatile Map<URI, Map<OFAuxId, OFBsnControllerConnection>> controllerConnections; + protected OFFactory factory; + + /** + * Members hidden from subclasses + */ + private final PortManager portManager; + + private volatile boolean connected; + + private volatile OFControllerRole role; + + private boolean flowTableFull = false; + + protected SwitchDescription description; + + private SwitchStatus status; + + public static final int OFSWITCH_APP_ID = ident(5); + + static { + AppCookie.registerApp(OFSwitch.OFSWITCH_APP_ID, "switch"); + } + + public OFSwitch(IOFConnectionBackend connection, @Nonnull OFFactory factory, @Nonnull IOFSwitchManager switchManager, + @Nonnull DatapathId datapathId) { + if(connection == null) + throw new NullPointerException("connection must not be null"); + if(!connection.getAuxId().equals(OFAuxId.MAIN)) + throw new IllegalArgumentException("connection must be the main connection"); + if(factory == null) + throw new NullPointerException("factory must not be null"); + if(switchManager == null) + throw new NullPointerException("switchManager must not be null"); + + this.connected = true; + this.factory = factory; + this.switchManager = switchManager; + this.datapathId = datapathId; + this.attributes = new ConcurrentHashMap<Object, Object>(); + this.role = null; + this.description = new SwitchDescription(); + this.portManager = new PortManager(); + this.status = SwitchStatus.HANDSHAKE; + + // Connections + this.connections = new ConcurrentHashMap<OFAuxId, IOFConnectionBackend>(); + this.connections.put(connection.getAuxId(), connection); + + // Switch's controller connection + this.controllerConnections = ImmutableMap.of(); + + // Defaults properties for an ideal switch + this.setAttribute(PROP_FASTWILDCARDS, EnumSet.allOf(OFFlowWildcards.class)); + this.setAttribute(PROP_SUPPORTS_OFPP_FLOOD, Boolean.TRUE); + this.setAttribute(PROP_SUPPORTS_OFPP_TABLE, Boolean.TRUE); + } + + private static int ident(int i) { + return i; + } + + @Override + public OFFactory getOFFactory() { + return factory; + } + + /** + * Manages the ports of this switch. + * + * Provides methods to query and update the stored ports. The class ensures + * that every port name and port number is unique. When updating ports + * the class checks if port number <-> port name mappings have change due + * to the update. If a new port P has number and port that are inconsistent + * with the previous mapping(s) the class will delete all previous ports + * with name or number of the new port and then add the new port. + * + * Port names are stored as-is but they are compared case-insensitive + * + * The methods that change the stored ports return a list of + * PortChangeEvents that represent the changes that have been applied + * to the port list so that IOFSwitchListeners can be notified about the + * changes. + * + * Implementation notes: + * - We keep several different representations of the ports to allow for + * fast lookups + * - Ports are stored in unchangeable lists. When a port is modified new + * data structures are allocated. + * - We use a read-write-lock for synchronization, so multiple readers are + * allowed. + */ + protected static class PortManager { + private final ReentrantReadWriteLock lock; + private List<OFPortDesc> portList; + private List<OFPortDesc> enabledPortList; + private List<OFPort> enabledPortNumbers; + private Map<OFPort,OFPortDesc> portsByNumber; + private Map<String,OFPortDesc> portsByName; + + public PortManager() { + this.lock = new ReentrantReadWriteLock(); + this.portList = Collections.emptyList(); + this.enabledPortList = Collections.emptyList(); + this.enabledPortNumbers = Collections.emptyList(); + this.portsByName = Collections.emptyMap(); + this.portsByNumber = Collections.emptyMap(); + } + + /** + * Set the internal data structure storing this switch's port + * to the ports specified by newPortsByNumber + * + * CALLER MUST HOLD WRITELOCK + * + * @param newPortsByNumber + * @throws IllegaalStateException if called without holding the + * writelock + */ + private void updatePortsWithNewPortsByNumber( + Map<OFPort,OFPortDesc> newPortsByNumber) { + if (!lock.writeLock().isHeldByCurrentThread()) { + throw new IllegalStateException("Method called without " + + "holding writeLock"); + } + Map<String,OFPortDesc> newPortsByName = + new HashMap<String, OFPortDesc>(); + List<OFPortDesc> newPortList = + new ArrayList<OFPortDesc>(); + List<OFPortDesc> newEnabledPortList = + new ArrayList<OFPortDesc>(); + List<OFPort> newEnabledPortNumbers = new ArrayList<OFPort>(); + + for(OFPortDesc p: newPortsByNumber.values()) { + newPortList.add(p); + newPortsByName.put(p.getName().toLowerCase(), p); + if (!p.getState().contains(OFPortState.LINK_DOWN) + && !p.getConfig().contains(OFPortConfig.PORT_DOWN)) { + newEnabledPortList.add(p); + newEnabledPortNumbers.add(p.getPortNo()); + } + } + portsByName = Collections.unmodifiableMap(newPortsByName); + portsByNumber = + Collections.unmodifiableMap(newPortsByNumber); + enabledPortList = + Collections.unmodifiableList(newEnabledPortList); + enabledPortNumbers = + Collections.unmodifiableList(newEnabledPortNumbers); + portList = Collections.unmodifiableList(newPortList); + } + + /** + * Handle a OFPortStatus delete message for the given port. + * Updates the internal port maps/lists of this switch and returns + * the PortChangeEvents caused by the delete. If the given port + * exists as it, it will be deleted. If the name<->number for the + * given port is inconsistent with the ports stored by this switch + * the method will delete all ports with the number or name of the + * given port. + * + * This method will increment error/warn counters and log + * + * @param delPort the port from the port status message that should + * be deleted. + * @return ordered collection of port changes applied to this switch + */ + private OrderedCollection<PortChangeEvent> + handlePortStatusDelete(OFPortDesc delPort) { + OrderedCollection<PortChangeEvent> events = + new LinkedHashSetWrapper<PortChangeEvent>(); + lock.writeLock().lock(); + try { + Map<OFPort,OFPortDesc> newPortByNumber = + new HashMap<OFPort, OFPortDesc>(portsByNumber); + OFPortDesc prevPort = + portsByNumber.get(delPort.getPortNo()); + if (prevPort == null) { + // so such port. Do we have a port with the name? + prevPort = portsByName.get(delPort.getName()); + if (prevPort != null) { + newPortByNumber.remove(prevPort.getPortNo()); + events.add(new PortChangeEvent(prevPort, + PortChangeType.DELETE)); + } + } else if (prevPort.getName().equals(delPort.getName())) { + // port exists with consistent name-number mapping + newPortByNumber.remove(delPort.getPortNo()); + events.add(new PortChangeEvent(delPort, + PortChangeType.DELETE)); + } else { + // port with same number exists but its name differs. This + // is weird. The best we can do is to delete the existing + // port(s) that have delPort's name and number. + newPortByNumber.remove(delPort.getPortNo()); + events.add(new PortChangeEvent(prevPort, + PortChangeType.DELETE)); + // is there another port that has delPort's name? + prevPort = portsByName.get(delPort.getName().toLowerCase()); + if (prevPort != null) { + newPortByNumber.remove(prevPort.getPortNo()); + events.add(new PortChangeEvent(prevPort, + PortChangeType.DELETE)); + } + } + updatePortsWithNewPortsByNumber(newPortByNumber); + return events; + } finally { + lock.writeLock().unlock(); + } + } + + /** + * Handle a OFPortStatus message, update the internal data structures + * that store ports and return the list of OFChangeEvents. + * + * This method will increment error/warn counters and log + * + * @param ps + * @return + */ + @SuppressFBWarnings(value="SF_SWITCH_FALLTHROUGH") + public OrderedCollection<PortChangeEvent> handlePortStatusMessage(OFPortStatus ps) { + if (ps == null) { + throw new NullPointerException("OFPortStatus message must " + + "not be null"); + } + lock.writeLock().lock(); + try { + OFPortDesc port = ps.getDesc(); + OFPortReason reason = ps.getReason(); + if (reason == null) { + throw new IllegalArgumentException("Unknown PortStatus " + + "reason code " + ps.getReason()); + } + + if (log.isDebugEnabled()) { + log.debug("Handling OFPortStatus: {} for {}", + reason, String.format("%s (%d)", port.getName(), port.getPortNo().getPortNumber())); + } + + if (reason == OFPortReason.DELETE) + return handlePortStatusDelete(port); + + // We handle ADD and MODIFY the same way. Since OpenFlow + // doesn't specify what uniquely identifies a port the + // notion of ADD vs. MODIFY can also be hazy. So we just + // compare the new port to the existing ones. + Map<OFPort,OFPortDesc> newPortByNumber = + new HashMap<OFPort, OFPortDesc>(portsByNumber); + OrderedCollection<PortChangeEvent> events = getSinglePortChanges(port); + for (PortChangeEvent e: events) { + switch(e.type) { + case DELETE: + newPortByNumber.remove(e.port.getPortNo()); + break; + case ADD: + if (reason != OFPortReason.ADD) { + // weird case + } + // fall through + case DOWN: + case OTHER_UPDATE: + case UP: + // update or add the port in the map + newPortByNumber.put(e.port.getPortNo(), e.port); + break; + } + } + updatePortsWithNewPortsByNumber(newPortByNumber); + return events; + } finally { + lock.writeLock().unlock(); + } + + } + + /** + * Given a new or modified port newPort, returns the list of + * PortChangeEvents to "transform" the current ports stored by + * this switch to include / represent the new port. The ports stored + * by this switch are <b>NOT</b> updated. + * + * This method acquires the readlock and is thread-safe by itself. + * Most callers will need to acquire the write lock before calling + * this method though (if the caller wants to update the ports stored + * by this switch) + * + * @param newPort the new or modified port. + * @return the list of changes + */ + public OrderedCollection<PortChangeEvent> + getSinglePortChanges(OFPortDesc newPort) { + lock.readLock().lock(); + try { + OrderedCollection<PortChangeEvent> events = + new LinkedHashSetWrapper<PortChangeEvent>(); + // Check if we have a port by the same number in our + // old map. + OFPortDesc prevPort = + portsByNumber.get(newPort.getPortNo()); + if (newPort.equals(prevPort)) { + // nothing has changed + return events; + } + + if (prevPort != null && + prevPort.getName().equals(newPort.getName())) { + // A simple modify of a exiting port + // A previous port with this number exists and it's name + // also matches the new port. Find the differences + if ((!prevPort.getState().contains(OFPortState.LINK_DOWN) + && !prevPort.getConfig().contains(OFPortConfig.PORT_DOWN)) + && (newPort.getState().contains(OFPortState.LINK_DOWN) + || newPort.getConfig().contains(OFPortConfig.PORT_DOWN))) { + events.add(new PortChangeEvent(newPort, + PortChangeType.DOWN)); + } else if ((prevPort.getState().contains(OFPortState.LINK_DOWN) + || prevPort.getConfig().contains(OFPortConfig.PORT_DOWN)) + && (!newPort.getState().contains(OFPortState.LINK_DOWN) + && !newPort.getConfig().contains(OFPortConfig.PORT_DOWN))) { + events.add(new PortChangeEvent(newPort, + PortChangeType.UP)); + } else { + events.add(new PortChangeEvent(newPort, + PortChangeType.OTHER_UPDATE)); + } + return events; + } + + if (prevPort != null) { + // There exists a previous port with the same port + // number but the port name is different (otherwise we would + // never have gotten here) + // Remove the port. Name-number mapping(s) have changed + events.add(new PortChangeEvent(prevPort, + PortChangeType.DELETE)); + } + + // We now need to check if there exists a previous port sharing + // the same name as the new/updated port. + prevPort = portsByName.get(newPort.getName().toLowerCase()); + if (prevPort != null) { + // There exists a previous port with the same port + // name but the port number is different (otherwise we + // never have gotten here). + // Remove the port. Name-number mapping(s) have changed + events.add(new PortChangeEvent(prevPort, + PortChangeType.DELETE)); + } + + // We always need to add the new port. Either no previous port + // existed or we just deleted previous ports with inconsistent + // name-number mappings + events.add(new PortChangeEvent(newPort, PortChangeType.ADD)); + return events; + } finally { + lock.readLock().unlock(); + } + } + + /** + * Compare the current ports of this switch to the newPorts list and + * return the changes that would be applied to transform the current + * ports to the new ports. No internal data structures are updated + * see {@link #compareAndUpdatePorts(List, boolean)} + * + * @param newPorts the list of new ports + * @return The list of differences between the current ports and + * newPortList + */ + public OrderedCollection<PortChangeEvent> + comparePorts(Collection<OFPortDesc> newPorts) { + return compareAndUpdatePorts(newPorts, false); + } + + /** + * Compare the current ports of this switch to the newPorts list and + * return the changes that would be applied to transform the current + * ports to the new ports. No internal data structures are updated + * see {@link #compareAndUpdatePorts(List, boolean)} + * + * @param newPorts the list of new ports + * @return The list of differences between the current ports and + * newPortList + */ + public OrderedCollection<PortChangeEvent> + updatePorts(Collection<OFPortDesc> newPorts) { + return compareAndUpdatePorts(newPorts, true); + } + + /** + * Compare the current ports stored in this switch instance with the + * new port list given and return the differences in the form of + * PortChangeEvents. If the doUpdate flag is true, newPortList will + * replace the current list of this switch (and update the port maps) + * + * Implementation note: + * Since this method can optionally modify the current ports and + * since it's not possible to upgrade a read-lock to a write-lock + * we need to hold the write-lock for the entire operation. If this + * becomes a problem and if compares() are common we can consider + * splitting in two methods but this requires lots of code duplication + * + * @param newPorts the list of new ports. + * @param doUpdate If true the newPortList will replace the current + * port list for this switch. If false this switch will not be changed. + * @return The list of differences between the current ports and + * newPorts + * @throws NullPointerException if newPortsList is null + * @throws IllegalArgumentException if either port names or port numbers + * are duplicated in the newPortsList. + */ + private OrderedCollection<PortChangeEvent> compareAndUpdatePorts( + Collection<OFPortDesc> newPorts, + boolean doUpdate) { + if (newPorts == null) { + throw new NullPointerException("newPortsList must not be null"); + } + lock.writeLock().lock(); + try { + OrderedCollection<PortChangeEvent> events = + new LinkedHashSetWrapper<PortChangeEvent>(); + + Map<OFPort,OFPortDesc> newPortsByNumber = + new HashMap<OFPort, OFPortDesc>(); + Map<String,OFPortDesc> newPortsByName = + new HashMap<String, OFPortDesc>(); + List<OFPortDesc> newEnabledPortList = + new ArrayList<OFPortDesc>(); + List<OFPort> newEnabledPortNumbers = + new ArrayList<OFPort>(); + List<OFPortDesc> newPortsList = + new ArrayList<OFPortDesc>(newPorts); + + for (OFPortDesc p: newPortsList) { + if (p == null) { + throw new NullPointerException("portList must not " + + "contain null values"); + } + + // Add the port to the new maps and lists and check + // that every port is unique + OFPortDesc duplicatePort; + duplicatePort = newPortsByNumber.put(p.getPortNo(), p); + if (duplicatePort != null) { + String msg = String.format("Cannot have two ports " + + "with the same number: %s <-> %s", + String.format("%s (%d)", p.getName(), p.getPortNo().getPortNumber()), + String.format("%s (%d)", duplicatePort.getName(), duplicatePort.getPortNo().getPortNumber())); + throw new IllegalArgumentException(msg); + } + duplicatePort = + newPortsByName.put(p.getName().toLowerCase(), p); + if (duplicatePort != null) { + String msg = String.format("Cannot have two ports " + + "with the same name: %s <-> %s", + String.format("%s (%d)", p.getName(), p.getPortNo().getPortNumber()), + String.format("%s (%d)", duplicatePort.getName(), duplicatePort.getPortNo().getPortNumber())); + throw new IllegalArgumentException(msg); + } + // Enabled = not down admin (config) or phys (state) + if (!p.getConfig().contains(OFPortConfig.PORT_DOWN) + && !p.getState().contains(OFPortState.LINK_DOWN)) { + newEnabledPortList.add(p); + newEnabledPortNumbers.add(p.getPortNo()); + } + + // get changes + events.addAll(getSinglePortChanges(p)); + } + // find deleted ports + // We need to do this after looping through all the new ports + // to we can handle changed name<->number mappings correctly + // We could pull it into the loop of we address this but + // it's probably not worth it + for (OFPortDesc oldPort: this.portList) { + if (!newPortsByNumber.containsKey(oldPort.getPortNo())) { + PortChangeEvent ev = + new PortChangeEvent(oldPort, + PortChangeType.DELETE); + events.add(ev); + } + } + + + if (doUpdate) { + portsByName = Collections.unmodifiableMap(newPortsByName); + portsByNumber = + Collections.unmodifiableMap(newPortsByNumber); + enabledPortList = + Collections.unmodifiableList(newEnabledPortList); + enabledPortNumbers = + Collections.unmodifiableList(newEnabledPortNumbers); + portList = Collections.unmodifiableList(newPortsList); + } + return events; + } finally { + lock.writeLock().unlock(); + } + } + + public OFPortDesc getPort(String name) { + if (name == null) { + throw new NullPointerException("Port name must not be null"); + } + lock.readLock().lock(); + try { + return portsByName.get(name.toLowerCase()); + } finally { + lock.readLock().unlock(); + } + } + + public OFPortDesc getPort(OFPort portNumber) { + lock.readLock().lock(); + try { + return portsByNumber.get(portNumber); + } finally { + lock.readLock().unlock(); + } + } + + public List<OFPortDesc> getPorts() { + lock.readLock().lock(); + try { + return portList; + } finally { + lock.readLock().unlock(); + } + } + + public List<OFPortDesc> getEnabledPorts() { + lock.readLock().lock(); + try { + return enabledPortList; + } finally { + lock.readLock().unlock(); + } + } + + public List<OFPort> getEnabledPortNumbers() { + lock.readLock().lock(); + try { + return enabledPortNumbers; + } finally { + lock.readLock().unlock(); + } + } + } + + @Override + public boolean attributeEquals(String name, Object other) { + Object attr = this.attributes.get(name); + if (attr == null) + return false; + return attr.equals(other); + } + + @Override + public Object getAttribute(String name) { + // returns null if key doesn't exist + return this.attributes.get(name); + } + + @Override + public void setAttribute(String name, Object value) { + this.attributes.put(name, value); + return; + } + + @Override + public Object removeAttribute(String name) { + return this.attributes.remove(name); + } + + @Override + public boolean hasAttribute(String name) { + return this.attributes.containsKey(name); + } + + @Override + public void registerConnection(IOFConnectionBackend connection) { + this.connections.put(connection.getAuxId(), connection); + } + + + @Override + public ImmutableList<IOFConnection> getConnections() { + return ImmutableList.<IOFConnection> copyOf(this.connections.values()); + } + + @Override + public void removeConnections() { + this.connections.clear(); + } + + @Override + public void removeConnection(IOFConnectionBackend connection) { + this.connections.remove(connection.getAuxId()); + } + + @Override + public void write(OFMessage m) { + log.debug("Channel info: {} {}", connections.get(OFAuxId.MAIN).getRemoteInetAddress(), connections.get(OFAuxId.MAIN).isConnected()); + connections.get(OFAuxId.MAIN).write(m); + } + + /** + * Gets a connection specified by aux Id. + * @param auxId the specified aux id for the connection desired. + * @return the aux connection specified by the auxId + */ + public IOFConnection getConnection(OFAuxId auxId) { + IOFConnection connection = this.connections.get(auxId); + if(connection == null){ + throw new IllegalArgumentException("OF Connection for " + this + " with " + auxId + " does not exist."); + } + return connection; + } + + public IOFConnection getConnection(LogicalOFMessageCategory category) { + if(switchManager.isCategoryRegistered(category)){ + return getConnection(category.getAuxId()); + } + else{ + throw new IllegalArgumentException(category + " is not registered with the floodlight provider service."); + } + } + + @Override + public void write(OFMessage m, LogicalOFMessageCategory category) { + this.getConnection(category).write(m); + } + + @Override + public void write(Iterable<OFMessage> msglist, LogicalOFMessageCategory category) { + this.getConnection(category).write(msglist); + } + + @Override + public OFConnection getConnectionByCategory(LogicalOFMessageCategory category){ + return (OFConnection) this.getConnection(category); + } + + @Override + public <R extends OFMessage> ListenableFuture<R> writeRequest(OFRequest<R> request, LogicalOFMessageCategory category) { + return getConnection(category).writeRequest(request); + } + + @Override + public <R extends OFMessage> ListenableFuture<R> writeRequest(OFRequest<R> request) { + return connections.get(OFAuxId.MAIN).writeRequest(request); + } + + @Override + @LogMessageDoc(level="WARN", + message="Sending OF message that modifies switch " + + "state while in the slave role: {switch}", + explanation="An application has sent a message to a switch " + + "that is not valid when the switch is in a slave role", + recommendation=LogMessageDoc.REPORT_CONTROLLER_BUG) + public void write(Iterable<OFMessage> msglist) { + connections.get(OFAuxId.MAIN).write(msglist); + } + + @Override + public void disconnect() { + + // Iterate through connections and perform cleanup + for(Entry<OFAuxId, IOFConnectionBackend> entry : this.connections.entrySet()){ + entry.getValue().disconnect(); + this.connections.remove(entry.getKey()); + } + log.debug("~~~~~~~SWITCH DISCONNECTED~~~~~~"); + connected = false; + } + + @Override + public void setFeaturesReply(OFFeaturesReply featuresReply) { + if (portManager.getPorts().isEmpty() && featuresReply.getVersion().compareTo(OFVersion.OF_13) < 0) { + /* ports are updated via port status message, so we + * only fill in ports on initial connection. + */ + List<OFPortDesc> OFPortDescs = featuresReply.getPorts(); + portManager.updatePorts(OFPortDescs); + } + this.capabilities = featuresReply.getCapabilities(); + this.buffers = featuresReply.getNBuffers(); + + if (featuresReply.getVersion().compareTo(OFVersion.OF_13) < 0 ) { + // FIXME:LOJI: OF1.3 has per table actions. This needs to be modeled / handled here + this.actions = featuresReply.getActions(); + } + this.tables = featuresReply.getNTables(); + } + + @Override + public void setPortDescStats(OFPortDescStatsReply reply) { + /* ports are updated via port status message, so we + * only fill in ports on initial connection. + */ + List<OFPortDesc> OFPortDescs = reply.getEntries(); + portManager.updatePorts(OFPortDescs); + } + + @Override + public Collection<OFPortDesc> getEnabledPorts() { + return portManager.getEnabledPorts(); + } + + @Override + public Collection<OFPort> getEnabledPortNumbers() { + return portManager.getEnabledPortNumbers(); + } + + @Override + public OFPortDesc getPort(OFPort portNumber) { + return portManager.getPort(portNumber); + } + + @Override + public OFPortDesc getPort(String portName) { + return portManager.getPort(portName); + } + + @Override + public OrderedCollection<PortChangeEvent> + processOFPortStatus(OFPortStatus ps) { + return portManager.handlePortStatusMessage(ps); + } + + @Override + public Collection<OFPortDesc> getSortedPorts() { + // FIXME: Hopefully BigDB will handle this automatically soon (or not + // have the sorting requirement), in which case we could get rid of this + List<OFPortDesc> sortedPorts = + new ArrayList<OFPortDesc>(portManager.getPorts()); + Collections.sort(sortedPorts, new Comparator<OFPortDesc>() { + @Override + public int compare(OFPortDesc o1, OFPortDesc o2) { + String name1 = o1.getName(); + String name2 = o2.getName(); + return name1.compareToIgnoreCase(name2); + } + }); + return sortedPorts; + } + + @Override + public Collection<OFPortDesc> getPorts() { + return portManager.getPorts(); + } + + @Override + public OrderedCollection<PortChangeEvent> + comparePorts(Collection<OFPortDesc> ports) { + return portManager.comparePorts(ports); + } + + @Override + public OrderedCollection<PortChangeEvent> + setPorts(Collection<OFPortDesc> ports) { + return portManager.updatePorts(ports); + } + + @Override + public boolean portEnabled(OFPort portNumber) { + OFPortDesc p = portManager.getPort(portNumber); + if (p == null) return false; + return (!p.getState().contains(OFPortState.BLOCKED) && !p.getState().contains(OFPortState.LINK_DOWN) && !p.getState().contains(OFPortState.STP_BLOCK)); + } + + @Override + public boolean portEnabled(String portName) { + OFPortDesc p = portManager.getPort(portName); + if (p == null) return false; + return (!p.getState().contains(OFPortState.BLOCKED) && !p.getState().contains(OFPortState.LINK_DOWN) && !p.getState().contains(OFPortState.STP_BLOCK)); + } + + @Override + public DatapathId getId() { + if (datapathId == null) + throw new RuntimeException("Features reply has not yet been set"); + return datapathId; + } + + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "OFSwitchBase DPID[" + ((datapathId != null) ? datapathId.toString() : "?") + "]"; + } + + @Override + public ConcurrentMap<Object, Object> getAttributes() { + return this.attributes; + } + + @Override + public Date getConnectedSince() { + return this.connections.get(OFAuxId.MAIN).getConnectedSince(); + } + + @Override + public <REPLY extends OFStatsReply> ListenableFuture<List<REPLY>> writeStatsRequest(OFStatsRequest<REPLY> request) { + return connections.get(OFAuxId.MAIN).writeStatsRequest(request); + } + + @Override + public <REPLY extends OFStatsReply> ListenableFuture<List<REPLY>> writeStatsRequest(OFStatsRequest<REPLY> request, LogicalOFMessageCategory category) { + return getConnection(category).writeStatsRequest(request); + } + + @Override + public void cancelAllPendingRequests() { + for(Entry<OFAuxId, IOFConnectionBackend> entry : this.connections.entrySet()){ + entry.getValue().cancelAllPendingRequests(); + } + } + + // If any connections are down consider a switch disconnected + @Override + public boolean isConnected() { + return connected; + } + + @Override + public boolean isActive() { + // no lock needed since we use volatile + return isConnected() && this.role == OFControllerRole.ROLE_MASTER; + } + + @Override + public OFControllerRole getControllerRole() { + return role; + } + + @Override + public void setControllerRole(OFControllerRole role) { + this.role = role; + } + + @Override + public void flush() { + for(Entry<OFAuxId, IOFConnectionBackend> entry : this.connections.entrySet()){ + entry.getValue().flush(); + } + } + + /** + * Get the IP Address for the switch + * @return the inet address + */ + @Override + public SocketAddress getInetAddress() { + return connections.get(OFAuxId.MAIN).getRemoteInetAddress(); + } + + @Override + public long getBuffers() { + return buffers; + } + + + @Override + public Set<OFActionType> getActions() { + return actions; + } + + + @Override + public Set<OFCapabilities> getCapabilities() { + return capabilities; + } + + + @Override + public short getTables() { + return tables; + } + + @Override + public SwitchDescription getSwitchDescription() { + return description; + } + + @Override + @LogMessageDoc(level="WARN", + message="Switch {switch} flow table is full", + explanation="The controller received flow table full " + + "message from the switch, could be caused by increased " + + "traffic pattern", + recommendation=LogMessageDoc.REPORT_CONTROLLER_BUG) + public void setTableFull(boolean isFull) { + if (isFull && !flowTableFull) { + switchManager.addSwitchEvent(this.datapathId, + "SWITCH_FLOW_TABLE_FULL " + + "Table full error from switch", false); + log.warn("Switch {} flow table is full", datapathId.toString()); + } + flowTableFull = isFull; + } + + @Override + public void startDriverHandshake() { + if (startDriverHandshakeCalled) + throw new SwitchDriverSubHandshakeAlreadyStarted(); + startDriverHandshakeCalled = true; + } + + @Override + public boolean isDriverHandshakeComplete() { + if (!startDriverHandshakeCalled) + throw new SwitchDriverSubHandshakeNotStarted(); + return true; + } + + @Override + public void processDriverHandshakeMessage(OFMessage m) { + if (startDriverHandshakeCalled) + throw new SwitchDriverSubHandshakeCompleted(m); + else + throw new SwitchDriverSubHandshakeNotStarted(); + } + + @Override + public void setSwitchProperties(SwitchDescription description) { + this.description = description; + } + + + @Override + public SwitchStatus getStatus() { + return status; + } + + @Override + public void setStatus(SwitchStatus switchStatus) { + this.status = switchStatus; + } + + @Override + public void updateControllerConnections(OFBsnControllerConnectionsReply controllerCxnsReply) { + + // Instantiate clean map, can't use a builder here since we need to call temp.get() + Map<URI,Map<OFAuxId, OFBsnControllerConnection>> temp = new ConcurrentHashMap<URI,Map<OFAuxId, OFBsnControllerConnection>>(); + + List<OFBsnControllerConnection> controllerCxnUpdates = controllerCxnsReply.getConnections(); + for(OFBsnControllerConnection update : controllerCxnUpdates) { + URI uri = URI.create(update.getUri()); + + Map<OFAuxId, OFBsnControllerConnection> cxns = temp.get(uri); + + // Add to nested map + if(cxns != null){ + cxns.put(update.getAuxiliaryId(), update); + } else{ + cxns = new ConcurrentHashMap<OFAuxId, OFBsnControllerConnection>(); + cxns.put(update.getAuxiliaryId(), update); + temp.put(uri, cxns); + } + } + + this.controllerConnections = ImmutableMap.<URI,Map<OFAuxId, OFBsnControllerConnection>>copyOf(temp); + } + + @Override + public boolean hasAnotherMaster() { + + //TODO: refactor get connection to not throw illegal arg exceptions + IOFConnection mainCxn = this.getConnection(OFAuxId.MAIN); + + if(mainCxn != null) { + + // Determine the local URI + InetSocketAddress address = (InetSocketAddress) mainCxn.getLocalInetAddress(); + URI localURI = URIUtil.createURI(address.getHostName(), address.getPort()); + + for(Entry<URI,Map<OFAuxId, OFBsnControllerConnection>> entry : this.controllerConnections.entrySet()) { + + // Don't check our own controller connections + URI uri = entry.getKey(); + if(!localURI.equals(uri)){ + + // We only care for the MAIN connection + Map<OFAuxId, OFBsnControllerConnection> cxns = this.controllerConnections.get(uri); + OFBsnControllerConnection controllerCxn = cxns.get(OFAuxId.MAIN); + + if(controllerCxn != null) { + // If the controller id disconnected or not master we know it is not connected + if(controllerCxn.getState() == OFBsnControllerConnectionState.BSN_CONTROLLER_CONNECTION_STATE_CONNECTED + && controllerCxn.getRole() == OFControllerRole.ROLE_MASTER){ + return true; + } + } else { + log.warn("Unable to find controller connection with aux id " + + "MAIN for switch {} on controller with URI {}.", + this, uri); + } + } + } + } + return false; + } +} diff --git a/src/main/java/net/floodlightcontroller/core/OFSwitchBase.java b/src/main/java/net/floodlightcontroller/core/OFSwitchBase.java deleted file mode 100644 index 1fb321baddd352b137c78e68b99fab388766fd27..0000000000000000000000000000000000000000 --- a/src/main/java/net/floodlightcontroller/core/OFSwitchBase.java +++ /dev/null @@ -1,1579 +0,0 @@ -/** -* Copyright 2012, Big Switch Networks, Inc. -* Originally created by David Erickson, Stanford University -* -* Licensed under the Apache License, Version 2.0 (the "License"); you may -* not use this file except in compliance with the License. You may obtain -* a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -* License for the specific language governing permissions and limitations -* under the License. -**/ - -package net.floodlightcontroller.core; - -import java.io.IOException; -import java.net.SocketAddress; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.Future; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicLong; -import java.util.concurrent.locks.ReentrantReadWriteLock; - -import net.floodlightcontroller.core.IFloodlightProviderService.Role; -import net.floodlightcontroller.core.annotations.LogMessageDoc; -import net.floodlightcontroller.core.annotations.LogMessageDocs; -import net.floodlightcontroller.core.internal.Controller; -import net.floodlightcontroller.core.internal.OFFeaturesReplyFuture; -import net.floodlightcontroller.core.internal.OFStatisticsFuture; -import net.floodlightcontroller.core.util.AppCookie; -import net.floodlightcontroller.core.web.serializers.DPIDSerializer; -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.devicemanager.SwitchPort; -import net.floodlightcontroller.packet.Ethernet; -import net.floodlightcontroller.routing.ForwardingBase; -import net.floodlightcontroller.threadpool.IThreadPoolService; -import net.floodlightcontroller.util.LinkedHashSetWrapper; -import net.floodlightcontroller.util.MACAddress; -import net.floodlightcontroller.util.OrderedCollection; -import net.floodlightcontroller.util.TimedCache; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; - -import org.jboss.netty.channel.Channel; -import org.openflow.protocol.OFFeaturesReply; -import org.openflow.protocol.OFFlowMod; -import org.openflow.protocol.OFMatch; -import org.openflow.protocol.OFMessage; -import org.openflow.protocol.OFPacketIn; -import org.openflow.protocol.OFPortStatus; -import org.openflow.protocol.OFPortStatus.OFPortReason; -import org.openflow.protocol.OFPort; -import org.openflow.protocol.OFStatisticsReply; -import org.openflow.protocol.OFStatisticsRequest; -import org.openflow.protocol.OFType; -import org.openflow.protocol.statistics.OFDescriptionStatistics; -import org.openflow.protocol.statistics.OFStatistics; -import org.openflow.protocol.statistics.OFStatisticsType; -import org.openflow.protocol.statistics.OFTableStatistics; -import org.openflow.util.HexString; -import org.openflow.util.U16; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * This is the internal representation of an openflow switch. - */ -public abstract class OFSwitchBase implements IOFSwitch { - // TODO: should we really do logging in the class or should we throw - // exception that can then be handled by callers? - protected static final Logger log = LoggerFactory.getLogger(OFSwitchBase.class); - - protected ConcurrentMap<Object, Object> attributes; - protected IFloodlightProviderService floodlightProvider; - protected IThreadPoolService threadPool; - protected IDebugCounterService debugCounters; - // FIXME: Don't use java.util.Date - protected volatile Date connectedSince; - - /* Switch features from initial featuresReply */ - protected int capabilities; - protected int buffers; - protected int actions; - protected byte tables; - protected long datapathId; - protected String stringId; - - protected short accessFlowPriority; - protected short coreFlowPriority; - - private boolean startDriverHandshakeCalled = false; - protected Channel channel; - - /** - * Members hidden from subclasses - */ - private final AtomicInteger transactionIdSource; - private final Map<Integer,OFStatisticsFuture> statsFutureMap; - private final Map<Integer, IOFMessageListener> iofMsgListenersMap; - private final Map<Integer,OFFeaturesReplyFuture> featuresFutureMap; - private volatile boolean connected; - private volatile Role role; - private final TimedCache<Long> timedCache; - private final ConcurrentMap<Short, AtomicLong> portBroadcastCacheHitMap; - - private final PortManager portManager; - - // Private members for throttling - private boolean writeThrottleEnabled = false; - protected boolean packetInThrottleEnabled = false; // used by test - private int packetInRateThresholdHigh = - Integer.parseInt(System.getProperty("input_threshold", "1000")); - private int packetInRateThresholdLow = 1; - private int packetInRatePerMacThreshold = 50; - private int packetInRatePerPortThreshold = 100; - private long messageCount = 0; - private long messageCountUniqueOFMatch = 0; - private long lastMessageTime; - private int currentRate = 0; - private TimedCache<OFMatch> ofMatchCache; - private TimedCache<Long> macCache; - private TimedCache<Long> macBlockedCache; - private TimedCache<Short> portCache; - private TimedCache<Short> portBlockedCache; - private boolean flowTableFull = false; - - protected OFDescriptionStatistics description; - - private boolean debugCountersRegistered; - @SuppressWarnings("unused") - private IDebugCounter ctrSwitch, ctrSwitchPktin, ctrSwitchWrite; - private IDebugCounter ctrSwitchPktinDrops, ctrSwitchWriteDrops; - - private static final String PACKAGE = OFSwitchBase.class.getPackage().getName(); - - - protected final static ThreadLocal<Map<IOFSwitch,List<OFMessage>>> local_msg_buffer = - new ThreadLocal<Map<IOFSwitch,List<OFMessage>>>() { - @Override - protected Map<IOFSwitch,List<OFMessage>> initialValue() { - return new HashMap<IOFSwitch,List<OFMessage>>(); - } - }; - - public static final int OFSWITCH_APP_ID = 5; - static { - AppCookie.registerApp(OFSwitchBase.OFSWITCH_APP_ID, "switch"); - } - - public OFSwitchBase() { - this.stringId = null; - this.attributes = new ConcurrentHashMap<Object, Object>(); - this.connectedSince = null; - this.transactionIdSource = new AtomicInteger(); - this.connected = false; - this.statsFutureMap = new ConcurrentHashMap<Integer,OFStatisticsFuture>(); - this.featuresFutureMap = new ConcurrentHashMap<Integer,OFFeaturesReplyFuture>(); - this.iofMsgListenersMap = new ConcurrentHashMap<Integer,IOFMessageListener>(); - this.role = null; - this.timedCache = new TimedCache<Long>(100, 5*1000 ); // 5 seconds interval - this.portBroadcastCacheHitMap = new ConcurrentHashMap<Short, AtomicLong>(); - this.description = new OFDescriptionStatistics(); - this.lastMessageTime = System.currentTimeMillis(); - - this.portManager = new PortManager(); - - // Defaults properties for an ideal switch - this.setAttribute(PROP_FASTWILDCARDS, OFMatch.OFPFW_ALL); - this.setAttribute(PROP_SUPPORTS_OFPP_FLOOD, Boolean.valueOf(true)); - this.setAttribute(PROP_SUPPORTS_OFPP_TABLE, Boolean.valueOf(true)); - if (packetInRateThresholdHigh == 0) { - packetInRateThresholdHigh = Integer.MAX_VALUE; - } else { - packetInRateThresholdLow = packetInRateThresholdHigh / 2; - } - } - - - - /** - * Manages the ports of this switch. - * - * Provides methods to query and update the stored ports. The class ensures - * that every port name and port number is unique. When updating ports - * the class checks if port number <-> port name mappings have change due - * to the update. If a new port P has number and port that are inconsistent - * with the previous mapping(s) the class will delete all previous ports - * with name or number of the new port and then add the new port. - * - * Port names are stored as-is but they are compared case-insensitive - * - * The methods that change the stored ports return a list of - * PortChangeEvents that represent the changes that have been applied - * to the port list so that IOFSwitchListeners can be notified about the - * changes. - * - * Implementation notes: - * - We keep several different representations of the ports to allow for - * fast lookups - * - Ports are stored in unchangeable lists. When a port is modified new - * data structures are allocated. - * - We use a read-write-lock for synchronization, so multiple readers are - * allowed. - */ - protected class PortManager { - private final ReentrantReadWriteLock lock; - private List<ImmutablePort> portList; - private List<ImmutablePort> enabledPortList; - private List<Short> enabledPortNumbers; - private Map<Short,ImmutablePort> portsByNumber; - private Map<String,ImmutablePort> portsByName; - - - - - public PortManager() { - this.lock = new ReentrantReadWriteLock(); - this.portList = Collections.emptyList(); - this.enabledPortList = Collections.emptyList(); - this.enabledPortNumbers = Collections.emptyList(); - this.portsByName = Collections.emptyMap(); - this.portsByNumber = Collections.emptyMap(); - } - - /** - * Set the internal data structure storing this switch's port - * to the ports specified by newPortsByNumber - * - * CALLER MUST HOLD WRITELOCK - * - * @param newPortsByNumber - * @throws IllegaalStateException if called without holding the - * writelock - */ - private void updatePortsWithNewPortsByNumber( - Map<Short,ImmutablePort> newPortsByNumber) { - if (!lock.writeLock().isHeldByCurrentThread()) { - throw new IllegalStateException("Method called without " + - "holding writeLock"); - } - Map<String,ImmutablePort> newPortsByName = - new HashMap<String, ImmutablePort>(); - List<ImmutablePort> newPortList = - new ArrayList<ImmutablePort>(); - List<ImmutablePort> newEnabledPortList = - new ArrayList<ImmutablePort>(); - List<Short> newEnabledPortNumbers = new ArrayList<Short>(); - - for(ImmutablePort p: newPortsByNumber.values()) { - newPortList.add(p); - newPortsByName.put(p.getName().toLowerCase(), p); - if (p.isEnabled()) { - newEnabledPortList.add(p); - newEnabledPortNumbers.add(p.getPortNumber()); - } - } - portsByName = Collections.unmodifiableMap(newPortsByName); - portsByNumber = - Collections.unmodifiableMap(newPortsByNumber); - enabledPortList = - Collections.unmodifiableList(newEnabledPortList); - enabledPortNumbers = - Collections.unmodifiableList(newEnabledPortNumbers); - portList = Collections.unmodifiableList(newPortList); - } - - /** - * Handle a OFPortStatus delete message for the given port. - * Updates the internal port maps/lists of this switch and returns - * the PortChangeEvents caused by the delete. If the given port - * exists as it, it will be deleted. If the name<->number for the - * given port is inconsistent with the ports stored by this switch - * the method will delete all ports with the number or name of the - * given port. - * - * This method will increment error/warn counters and log - * - * @param delPort the port from the port status message that should - * be deleted. - * @return ordered collection of port changes applied to this switch - */ - private OrderedCollection<PortChangeEvent> - handlePortStatusDelete(ImmutablePort delPort) { - lock.writeLock().lock(); - OrderedCollection<PortChangeEvent> events = - new LinkedHashSetWrapper<PortChangeEvent>(); - try { - Map<Short,ImmutablePort> newPortByNumber = - new HashMap<Short, ImmutablePort>(portsByNumber); - ImmutablePort prevPort = - portsByNumber.get(delPort.getPortNumber()); - if (prevPort == null) { - // so such port. Do we have a port with the name? - prevPort = portsByName.get(delPort.getName()); - if (prevPort != null) { - newPortByNumber.remove(prevPort.getPortNumber()); - events.add(new PortChangeEvent(prevPort, - PortChangeType.DELETE)); - } - } else if (prevPort.getName().equals(delPort.getName())) { - // port exists with consistent name-number mapping - newPortByNumber.remove(delPort.getPortNumber()); - events.add(new PortChangeEvent(delPort, - PortChangeType.DELETE)); - } else { - // port with same number exists but its name differs. This - // is weird. The best we can do is to delete the existing - // port(s) that have delPort's name and number. - newPortByNumber.remove(delPort.getPortNumber()); - events.add(new PortChangeEvent(prevPort, - PortChangeType.DELETE)); - // is there another port that has delPort's name? - prevPort = portsByName.get(delPort.getName().toLowerCase()); - if (prevPort != null) { - newPortByNumber.remove(prevPort.getPortNumber()); - events.add(new PortChangeEvent(prevPort, - PortChangeType.DELETE)); - } - } - updatePortsWithNewPortsByNumber(newPortByNumber); - return events; - } finally { - lock.writeLock().unlock(); - } - } - - /** - * Handle a OFPortStatus message, update the internal data structures - * that store ports and return the list of OFChangeEvents. - * - * This method will increment error/warn counters and log - * - * @param ps - * @return - */ - public OrderedCollection<PortChangeEvent> handlePortStatusMessage(OFPortStatus ps) { - if (ps == null) { - throw new NullPointerException("OFPortStatus message must " + - "not be null"); - } - lock.writeLock().lock(); - try { - ImmutablePort port = - ImmutablePort.fromOFPhysicalPort(ps.getDesc()); - OFPortReason reason = OFPortReason.fromReasonCode(ps.getReason()); - if (reason == null) { - throw new IllegalArgumentException("Unknown PortStatus " + - "reason code " + ps.getReason()); - } - - if (log.isDebugEnabled()) { - log.debug("Handling OFPortStatus: {} for {}", - reason, port.toBriefString()); - } - - if (reason == OFPortReason.OFPPR_DELETE) - return handlePortStatusDelete(port); - - // We handle ADD and MODIFY the same way. Since OpenFlow - // doesn't specify what uniquely identifies a port the - // notion of ADD vs. MODIFY can also be hazy. So we just - // compare the new port to the existing ones. - Map<Short,ImmutablePort> newPortByNumber = - new HashMap<Short, ImmutablePort>(portsByNumber); - OrderedCollection<PortChangeEvent> events = getSinglePortChanges(port); - for (PortChangeEvent e: events) { - switch(e.type) { - case DELETE: - newPortByNumber.remove(e.port.getPortNumber()); - break; - case ADD: - if (reason != OFPortReason.OFPPR_ADD) { - // weird case - } - // fall through - case DOWN: - case OTHER_UPDATE: - case UP: - // update or add the port in the map - newPortByNumber.put(e.port.getPortNumber(), e.port); - break; - } - } - updatePortsWithNewPortsByNumber(newPortByNumber); - return events; - } finally { - lock.writeLock().unlock(); - } - - } - - /** - * Given a new or modified port newPort, returns the list of - * PortChangeEvents to "transform" the current ports stored by - * this switch to include / represent the new port. The ports stored - * by this switch are <b>NOT</b> updated. - * - * This method acquires the readlock and is thread-safe by itself. - * Most callers will need to acquire the write lock before calling - * this method though (if the caller wants to update the ports stored - * by this switch) - * - * @param newPort the new or modified port. - * @return the list of changes - */ - public OrderedCollection<PortChangeEvent> - getSinglePortChanges(ImmutablePort newPort) { - lock.readLock().lock(); - try { - OrderedCollection<PortChangeEvent> events = - new LinkedHashSetWrapper<PortChangeEvent>(); - // Check if we have a port by the same number in our - // old map. - ImmutablePort prevPort = - portsByNumber.get(newPort.getPortNumber()); - if (newPort.equals(prevPort)) { - // nothing has changed - return events; - } - - if (prevPort != null && - prevPort.getName().equals(newPort.getName())) { - // A simple modify of a exiting port - // A previous port with this number exists and it's name - // also matches the new port. Find the differences - if (prevPort.isEnabled() && !newPort.isEnabled()) { - events.add(new PortChangeEvent(newPort, - PortChangeType.DOWN)); - } else if (!prevPort.isEnabled() && newPort.isEnabled()) { - events.add(new PortChangeEvent(newPort, - PortChangeType.UP)); - } else { - events.add(new PortChangeEvent(newPort, - PortChangeType.OTHER_UPDATE)); - } - return events; - } - - if (prevPort != null) { - // There exists a previous port with the same port - // number but the port name is different (otherwise we would - // never have gotten here) - // Remove the port. Name-number mapping(s) have changed - events.add(new PortChangeEvent(prevPort, - PortChangeType.DELETE)); - } - - // We now need to check if there exists a previous port sharing - // the same name as the new/updated port. - prevPort = portsByName.get(newPort.getName().toLowerCase()); - if (prevPort != null) { - // There exists a previous port with the same port - // name but the port number is different (otherwise we - // never have gotten here). - // Remove the port. Name-number mapping(s) have changed - events.add(new PortChangeEvent(prevPort, - PortChangeType.DELETE)); - } - - // We always need to add the new port. Either no previous port - // existed or we just deleted previous ports with inconsistent - // name-number mappings - events.add(new PortChangeEvent(newPort, PortChangeType.ADD)); - return events; - } finally { - lock.readLock().unlock(); - } - } - - /** - * Compare the current ports of this switch to the newPorts list and - * return the changes that would be applied to transfort the current - * ports to the new ports. No internal data structures are updated - * see {@link #compareAndUpdatePorts(List, boolean)} - * - * @param newPorts the list of new ports - * @return The list of differences between the current ports and - * newPortList - */ - public OrderedCollection<PortChangeEvent> - comparePorts(Collection<ImmutablePort> newPorts) { - return compareAndUpdatePorts(newPorts, false); - } - - /** - * Compare the current ports of this switch to the newPorts list and - * return the changes that would be applied to transform the current - * ports to the new ports. No internal data structures are updated - * see {@link #compareAndUpdatePorts(List, boolean)} - * - * @param newPorts the list of new ports - * @return The list of differences between the current ports and - * newPortList - */ - public OrderedCollection<PortChangeEvent> - updatePorts(Collection<ImmutablePort> newPorts) { - return compareAndUpdatePorts(newPorts, true); - } - - /** - * Compare the current ports stored in this switch instance with the - * new port list given and return the differences in the form of - * PortChangeEvents. If the doUpdate flag is true, newPortList will - * replace the current list of this switch (and update the port maps) - * - * Implementation note: - * Since this method can optionally modify the current ports and - * since it's not possible to upgrade a read-lock to a write-lock - * we need to hold the write-lock for the entire operation. If this - * becomes a problem and if compares() are common we can consider - * splitting in two methods but this requires lots of code duplication - * - * @param newPorts the list of new ports. - * @param doUpdate If true the newPortList will replace the current - * port list for this switch. If false this switch will not be changed. - * @return The list of differences between the current ports and - * newPorts - * @throws NullPointerException if newPortsList is null - * @throws IllegalArgumentException if either port names or port numbers - * are duplicated in the newPortsList. - */ - private OrderedCollection<PortChangeEvent> compareAndUpdatePorts( - Collection<ImmutablePort> newPorts, - boolean doUpdate) { - if (newPorts == null) { - throw new NullPointerException("newPortsList must not be null"); - } - lock.writeLock().lock(); - try { - OrderedCollection<PortChangeEvent> events = - new LinkedHashSetWrapper<PortChangeEvent>(); - - Map<Short,ImmutablePort> newPortsByNumber = - new HashMap<Short, ImmutablePort>(); - Map<String,ImmutablePort> newPortsByName = - new HashMap<String, ImmutablePort>(); - List<ImmutablePort> newEnabledPortList = - new ArrayList<ImmutablePort>(); - List<Short> newEnabledPortNumbers = - new ArrayList<Short>(); - List<ImmutablePort> newPortsList = - new ArrayList<ImmutablePort>(newPorts); - - for (ImmutablePort p: newPortsList) { - if (p == null) { - throw new NullPointerException("portList must not " + - "contain null values"); - } - - // Add the port to the new maps and lists and check - // that every port is unique - ImmutablePort duplicatePort; - duplicatePort = newPortsByNumber.put(p.getPortNumber(), p); - if (duplicatePort != null) { - String msg = String.format("Cannot have two ports " + - "with the same number: %s <-> %s", - p.toBriefString(), - duplicatePort.toBriefString()); - throw new IllegalArgumentException(msg); - } - duplicatePort = - newPortsByName.put(p.getName().toLowerCase(), p); - if (duplicatePort != null) { - String msg = String.format("Cannot have two ports " + - "with the same name: %s <-> %s", - p.toBriefString(), - duplicatePort.toBriefString()); - throw new IllegalArgumentException(msg); - } - if (p.isEnabled()) { - newEnabledPortList.add(p); - newEnabledPortNumbers.add(p.getPortNumber()); - } - - // get changes - events.addAll(getSinglePortChanges(p)); - } - // find deleted ports - // We need to do this after looping through all the new ports - // to we can handle changed name<->number mappings correctly - // We could pull it into the loop of we address this but - // it's probably not worth it - for (ImmutablePort oldPort: this.portList) { - if (!newPortsByNumber.containsKey(oldPort.getPortNumber())) { - PortChangeEvent ev = - new PortChangeEvent(oldPort, - PortChangeType.DELETE); - events.add(ev); - } - } - - - if (doUpdate) { - portsByName = Collections.unmodifiableMap(newPortsByName); - portsByNumber = - Collections.unmodifiableMap(newPortsByNumber); - enabledPortList = - Collections.unmodifiableList(newEnabledPortList); - enabledPortNumbers = - Collections.unmodifiableList(newEnabledPortNumbers); - portList = Collections.unmodifiableList(newPortsList); - } - return events; - } finally { - lock.writeLock().unlock(); - } - } - - public ImmutablePort getPort(String name) { - if (name == null) { - throw new NullPointerException("Port name must not be null"); - } - lock.readLock().lock(); - try { - return portsByName.get(name.toLowerCase()); - } finally { - lock.readLock().unlock(); - } - } - - public ImmutablePort getPort(Short portNumber) { - lock.readLock().lock(); - try { - return portsByNumber.get(portNumber); - } finally { - lock.readLock().unlock(); - } - } - - public List<ImmutablePort> getPorts() { - lock.readLock().lock(); - try { - return portList; - } finally { - lock.readLock().unlock(); - } - } - - public List<ImmutablePort> getEnabledPorts() { - lock.readLock().lock(); - try { - return enabledPortList; - } finally { - lock.readLock().unlock(); - } - } - - public List<Short> getEnabledPortNumbers() { - lock.readLock().lock(); - try { - return enabledPortNumbers; - } finally { - lock.readLock().unlock(); - } - } - } - - - @Override - public boolean attributeEquals(String name, Object other) { - Object attr = this.attributes.get(name); - if (attr == null) - return false; - return attr.equals(other); - } - - - @Override - public Object getAttribute(String name) { - // returns null if key doesn't exist - return this.attributes.get(name); - } - - @Override - public void setAttribute(String name, Object value) { - this.attributes.put(name, value); - return; - } - - @Override - public Object removeAttribute(String name) { - return this.attributes.remove(name); - } - - @Override - public boolean hasAttribute(String name) { - return this.attributes.containsKey(name); - } - - @Override - @JsonIgnore - public void setChannel(Channel channel) { - this.channel = channel; - } - - // For driver subclass to set throttling - protected void enableWriteThrottle(boolean enable) { - this.writeThrottleEnabled = enable; - } - - @Override - public boolean isWriteThrottleEnabled() { - return this.writeThrottleEnabled; - } - - @Override - @LogMessageDocs({ - @LogMessageDoc(level="WARN", - message="Drop throttled OF message to switch {switch}", - explanation="The controller is sending more messages" + - "than the switch can handle. Some messages are dropped" + - "to prevent switch outage", - recommendation=LogMessageDoc.REPORT_CONTROLLER_BUG) - }) - public void writeThrottled(OFMessage m, FloodlightContext bc) - throws IOException { - if (channel == null || !isConnected()) - return; - /** - * By default, channel uses an unbounded send queue. Enable throttling - * prevents the queue from growing big. - * - * channel.isWritable() returns true when queue length is less than - * high water mark (64 kbytes). Once exceeded, isWritable() becomes - * false after queue length drops below low water mark (32 kbytes). - */ - if (!writeThrottleEnabled || channel.isWritable()) { - write(m, bc); - } else { - // Let logback duplicate filtering take care of excessive logs - ctrSwitchWriteDrops.updateCounterNoFlush(); - log.warn("Drop throttled OF message to switch {}", this); - } - } - - @Override - public void writeThrottled(List<OFMessage> msglist, FloodlightContext bc) - throws IOException { - if (!writeThrottleEnabled || channel.isWritable()) { - write(msglist, bc); - } else { - // Let logback duplicate filtering take care of excessive logs - ctrSwitchWriteDrops.updateCounterNoFlush(msglist.size()); - log.warn("Drop throttled OF messages to switch {}", this); - } - } - - @Override - public void write(OFMessage m, FloodlightContext bc) { - if (channel == null || !isConnected()) - return; - //throws IOException { - Map<IOFSwitch,List<OFMessage>> msg_buffer_map = local_msg_buffer.get(); - List<OFMessage> msg_buffer = msg_buffer_map.get(this); - if (msg_buffer == null) { - msg_buffer = new ArrayList<OFMessage>(); - msg_buffer_map.put(this, msg_buffer); - } - - this.floodlightProvider.handleOutgoingMessage(this, m, bc); - msg_buffer.add(m); - - if ((msg_buffer.size() >= Controller.BATCH_MAX_SIZE) || - ((m.getType() != OFType.PACKET_OUT) && (m.getType() != OFType.FLOW_MOD))) { - this.write(msg_buffer); - msg_buffer.clear(); - } - } - @Override - @LogMessageDoc(level="WARN", - message="Sending OF message that modifies switch " + - "state while in the slave role: {switch}", - explanation="An application has sent a message to a switch " + - "that is not valid when the switch is in a slave role", - recommendation=LogMessageDoc.REPORT_CONTROLLER_BUG) - public void write(List<OFMessage> msglist, - FloodlightContext bc) { - if (channel == null || !isConnected()) - return; - for (OFMessage m : msglist) { - if (role == Role.SLAVE) { - switch (m.getType()) { - case PACKET_OUT: - case FLOW_MOD: - case PORT_MOD: - log.warn("Sending OF message that modifies switch " + - "state while in the slave role: {}", - m.getType().name()); - break; - default: - break; - } - } - this.floodlightProvider.handleOutgoingMessage(this, m, bc); - } - this.write(msglist); - } - - /** - * Not callable by writers, but allow IOFSwitch implementation to override - * @param msglist - * @throws IOException - */ - protected void write(List<OFMessage> msglist) { - if (channel == null || !isConnected()) - return; - this.channel.write(msglist); - } - - @Override - public void disconnectOutputStream() { - if (channel == null) - return; - channel.close(); - } - - @Override - @JsonIgnore - public void setFeaturesReply(OFFeaturesReply featuresReply) { - if (stringId == null) { - /* ports are updated via port status message, so we - * only fill in ports on initial connection. - */ - List<ImmutablePort> immutablePorts = ImmutablePort - .immutablePortListOf(featuresReply.getPorts()); - portManager.updatePorts(immutablePorts); - } - this.datapathId = featuresReply.getDatapathId(); - this.stringId = HexString.toHexString(featuresReply.getDatapathId()); - this.capabilities = featuresReply.getCapabilities(); - this.buffers = featuresReply.getBuffers(); - this.actions = featuresReply.getActions(); - this.tables = featuresReply.getTables(); -} - - @Override - @JsonIgnore - public Collection<ImmutablePort> getEnabledPorts() { - return portManager.getEnabledPorts(); - } - - @Override - @JsonIgnore - public Collection<Short> getEnabledPortNumbers() { - return portManager.getEnabledPortNumbers(); - } - - @Override - public ImmutablePort getPort(short portNumber) { - return portManager.getPort(portNumber); - } - - @Override - public ImmutablePort getPort(String portName) { - return portManager.getPort(portName); - } - - @Override - @JsonIgnore - public OrderedCollection<PortChangeEvent> - processOFPortStatus(OFPortStatus ps) { - return portManager.handlePortStatusMessage(ps); - } - - @Override - @JsonProperty("ports") - public Collection<ImmutablePort> getPorts() { - return portManager.getPorts(); - } - - @Override - public OrderedCollection<PortChangeEvent> - comparePorts(Collection<ImmutablePort> ports) { - return portManager.comparePorts(ports); - } - - @Override - @JsonIgnore - public OrderedCollection<PortChangeEvent> - setPorts(Collection<ImmutablePort> ports) { - return portManager.updatePorts(ports); - } - - @Override - public boolean portEnabled(short portNumber) { - ImmutablePort p = portManager.getPort(portNumber); - if (p == null) return false; - return p.isEnabled(); - } - - @Override - public boolean portEnabled(String portName) { - ImmutablePort p = portManager.getPort(portName); - if (p == null) return false; - return p.isEnabled(); - } - - @Override - @JsonSerialize(using=DPIDSerializer.class) - @JsonProperty("dpid") - public long getId() { - if (this.stringId == null) - throw new RuntimeException("Features reply has not yet been set"); - return this.datapathId; - } - - @JsonIgnore - @Override - public String getStringId() { - return stringId; - } - - /* (non-Javadoc) - * @see java.lang.Object#toString() - */ - @Override - public String toString() { - String channelString = - (channel != null) ? channel.getRemoteAddress().toString() : - "?"; - return "OFSwitchBase [" + channelString + " DPID[" + ((stringId != null) ? stringId : "?") + "]]"; - } - - @Override - public ConcurrentMap<Object, Object> getAttributes() { - return this.attributes; - } - - @Override - public Date getConnectedSince() { - return connectedSince; - } - - @JsonIgnore - @Override - public int getNextTransactionId() { - return this.transactionIdSource.incrementAndGet(); - } - - @Override - public void sendStatsQuery(OFStatisticsRequest request, int xid, - IOFMessageListener caller) throws IOException { - request.setXid(xid); - this.iofMsgListenersMap.put(xid, caller); - List<OFMessage> msglist = new ArrayList<OFMessage>(1); - msglist.add(request); - this.write(msglist); - return; - } - - @Override - public Future<List<OFStatistics>> queryStatistics(OFStatisticsRequest request) throws IOException { - request.setXid(getNextTransactionId()); - OFStatisticsFuture future = new OFStatisticsFuture(threadPool, this, request.getXid()); - this.statsFutureMap.put(request.getXid(), future); - List<OFMessage> msglist = new ArrayList<OFMessage>(1); - msglist.add(request); - this.write(msglist); - return future; - } - - @Override - public void deliverStatisticsReply(OFStatisticsReply reply) { - checkForTableStats(reply); - OFStatisticsFuture future = this.statsFutureMap.get(reply.getXid()); - if (future != null) { - future.deliverFuture(this, reply); - // The future will ultimately unregister itself and call - // cancelStatisticsReply - return; - } - /* Transaction id was not found in statsFutureMap.check the other map */ - IOFMessageListener caller = this.iofMsgListenersMap.get(reply.getXid()); - if (caller != null) { - caller.receive(this, reply, null); - } - } - - @LogMessageDocs({ - @LogMessageDoc(level="INFO", - message="Switch {switch} flow table is full", - explanation="The switch flow table at least 98% full, " + - "this requires attention if using reactive flow setup"), - @LogMessageDoc(level="INFO", - message="Switch {switch} flow table capacity back to normal", - explanation="The switch flow table is less than 90% full") - }) - private void checkForTableStats(OFStatisticsReply statReply) { - if (statReply.getStatisticType() != OFStatisticsType.TABLE) { - return; - } - List<? extends OFStatistics> stats = statReply.getStatistics(); - // Assume a single table only - OFStatistics stat = stats.get(0); - if (stat instanceof OFTableStatistics) { - OFTableStatistics tableStat = (OFTableStatistics) stat; - int activeCount = tableStat.getActiveCount(); - int maxEntry = tableStat.getMaximumEntries(); - log.debug("Switch {} active entries {} max entries {}", - new Object[] { this.stringId, activeCount, maxEntry}); - int percentFull = activeCount * 100 / maxEntry; - if (flowTableFull && percentFull < 90) { - log.info("Switch {} flow table capacity is back to normal", - toString()); - floodlightProvider.addSwitchEvent(this.datapathId, - "SWITCH_FLOW_TABLE_NORMAL < 90% full", false); - } else if (percentFull >= 98) { - log.info("Switch {} flow table is almost full", toString()); - floodlightProvider.addSwitchEvent(this.datapathId, - "SWITCH_FLOW_TABLE_ALMOST_FULL >= 98% full", false); - } - } - } - - - @Override - public void cancelStatisticsReply(int transactionId) { - if (null == this.statsFutureMap.remove(transactionId)) { - this.iofMsgListenersMap.remove(transactionId); - } - } - - @Override - public void cancelAllStatisticsReplies() { - /* we don't need to be synchronized here. Even if another thread - * modifies the map while we're cleaning up the future will eventuall - * timeout */ - for (OFStatisticsFuture f : statsFutureMap.values()) { - f.cancel(true); - } - statsFutureMap.clear(); - iofMsgListenersMap.clear(); - } - - - /** - * @param floodlightProvider the floodlightProvider to set - */ - @JsonIgnore - public void setFloodlightProvider( - IFloodlightProviderService floodlightProvider) { - this.floodlightProvider = floodlightProvider; - } - - @Override - @JsonIgnore - public void setThreadPoolService(IThreadPoolService tp) { - this.threadPool = tp; - } - - @Override - @JsonIgnore - public void setDebugCounterService(IDebugCounterService debugCounters) - throws CounterException { - this.debugCounters = debugCounters; - registerOverloadCounters(); - } - - @JsonIgnore - @Override - public boolean isConnected() { - // no lock needed since we use volatile - return connected; - } - - @JsonIgnore - @Override - public boolean isActive() { - // no lock needed since we use volatile - return isConnected() && this.role == Role.MASTER; - } - - @Override - @JsonIgnore - public void setConnected(boolean connected) { - // No lock needed since we use volatile - if (connected && this.connectedSince == null) - this.connectedSince = new Date(); - else if (!connected) - this.connectedSince = null; - this.connected = connected; - } - - @Override - public Role getHARole() { - return role; - } - - @JsonIgnore - @Override - public void setHARole(Role role) { - this.role = role; - } - - @LogMessageDoc(level="INFO", - message="Switch {switch} flow cleared", - explanation="The switch flow table has been cleared, " + - "this normally happens on switch connection") - @Override - public void clearAllFlowMods() { - if (channel == null || !isConnected()) - return; - // Delete all pre-existing flows - log.info("Clearing all flows on switch {}", this); - OFMatch match = new OFMatch().setWildcards(OFMatch.OFPFW_ALL); - OFMessage fm = ((OFFlowMod) floodlightProvider.getOFMessageFactory() - .getMessage(OFType.FLOW_MOD)) - .setMatch(match) - .setCommand(OFFlowMod.OFPFC_DELETE) - .setOutPort(OFPort.OFPP_NONE) - .setLength(U16.t(OFFlowMod.MINIMUM_LENGTH)); - fm.setXid(getNextTransactionId()); - OFMessage barrierMsg = floodlightProvider.getOFMessageFactory().getMessage( - OFType.BARRIER_REQUEST); - barrierMsg.setXid(getNextTransactionId()); - List<OFMessage> msglist = new ArrayList<OFMessage>(2); - msglist.add(fm); - msglist.add(barrierMsg); - channel.write(msglist); - } - - @Override - public boolean updateBroadcastCache(Long entry, Short port) { - if (timedCache.update(entry)) { - AtomicLong count = portBroadcastCacheHitMap.get(port); - if(count == null) { - AtomicLong newCount = new AtomicLong(0); - AtomicLong retrieved; - if((retrieved = portBroadcastCacheHitMap.putIfAbsent(port, newCount)) == null ) { - count = newCount; - } else { - count = retrieved; - } - } - count.incrementAndGet(); - return true; - } else { - return false; - } - } - - @Override - @JsonIgnore - public Map<Short, Long> getPortBroadcastHits() { - Map<Short, Long> res = new HashMap<Short, Long>(); - for (Map.Entry<Short, AtomicLong> entry : portBroadcastCacheHitMap.entrySet()) { - res.put(entry.getKey(), entry.getValue().get()); - } - return res; - } - - @Override - public void flush() { - Map<IOFSwitch,List<OFMessage>> msg_buffer_map = local_msg_buffer.get(); - List<OFMessage> msglist = msg_buffer_map.get(this); - if ((msglist != null) && (msglist.size() > 0)) { - /* ============================ BIG CAVEAT =============================== - * This code currently works, but relies on undocumented behavior of - * netty. - * - * The method org.jboss.netty.channel.Channel.write(Object) - * (invoked from this.write(List<OFMessage> msg) is currently - * documented to be <emph>asynchronous</emph>. If the method /were/ truely - * asynchronous, this would break our code (because we are clearing the - * msglist right after calling write. - * - * For now, Netty actually invokes the conversion pipeline before doing - * anything asynchronous, so we are safe. But we should probably change - * that behavior. - */ - this.write(msglist); - msglist.clear(); - } - } - - public static void flush_all() { - Map<IOFSwitch,List<OFMessage>> msg_buffer_map = local_msg_buffer.get(); - for (IOFSwitch sw : msg_buffer_map.keySet()) { - sw.flush(); - } - } - - - /** - * Get the IP Address for the switch - * @return the inet address - */ - @Override - @JsonSerialize(using=ToStringSerializer.class) - public SocketAddress getInetAddress() { - if (channel == null) - return null; - return channel.getRemoteAddress(); - } - - @Override - public Future<OFFeaturesReply> querySwitchFeaturesReply() - throws IOException { - OFMessage request = - floodlightProvider.getOFMessageFactory(). - getMessage(OFType.FEATURES_REQUEST); - request.setXid(getNextTransactionId()); - OFFeaturesReplyFuture future = - new OFFeaturesReplyFuture(threadPool, this, request.getXid()); - this.featuresFutureMap.put(request.getXid(), future); - List<OFMessage> msglist = new ArrayList<OFMessage>(1); - msglist.add(request); - this.write(msglist); - return future; - } - - @Override - public void deliverOFFeaturesReply(OFMessage reply) { - OFFeaturesReplyFuture future = this.featuresFutureMap.get(reply.getXid()); - if (future != null) { - future.deliverFuture(this, reply); - // The future will ultimately unregister itself and call - // cancelFeaturesReply - return; - } - log.error("Switch {}: received unexpected featureReply", this); - } - - @Override - public void cancelFeaturesReply(int transactionId) { - this.featuresFutureMap.remove(transactionId); - } - - - @Override - public int getBuffers() { - return buffers; - } - - - @Override - public int getActions() { - return actions; - } - - - @Override - public int getCapabilities() { - return capabilities; - } - - - @Override - public byte getTables() { - return tables; - } - - @Override - public OFDescriptionStatistics getDescriptionStatistics() { - return new OFDescriptionStatistics(description); - } - - - @Override - public void setFloodlightProvider(Controller controller) { - floodlightProvider = controller; - } - - - /** - * For switch drivers to set thresholds, all rates in per second - * @param pktInHigh - above this start throttling - * @param pktInLow - below this stop throttling - * @param pktInPerMac - block host if unique pktIn rate reaches this - * @param pktInPerPort - block port if unique pktIn rate reaches this - */ - @JsonIgnore - protected void setInputThrottleThresholds(int pktInHigh, int pktInLow, - int pktInPerMac, int pktInPerPort) { - packetInRateThresholdHigh = pktInHigh; - packetInRateThresholdLow = pktInLow; - packetInRatePerMacThreshold = pktInPerMac; - packetInRatePerPortThreshold = pktInPerPort; - } - - /** - * Return if switch has exceeded the high threshold of packet in rate. - * @return - */ - @Override - public boolean isOverloaded() { - return packetInThrottleEnabled; - } - - /** - * Determine if this message should be dropped. - * - * We compute the current rate by taking a timestamp every 100 messages. - * Could change to a more complex scheme if more accuracy is needed. - * - * Enable throttling if the rate goes above packetInRateThresholdHigh - * Disable throttling when the rate drops below packetInRateThresholdLow - * - * While throttling is enabled, we do the following: - * - Remove duplicate packetIn's mapped to the same OFMatch - * - After filtering, if packetIn rate per host (mac) is above - * packetInRatePerMacThreshold, push a flow mod to block mac on port - * - After filtering, if packetIn rate per port is above - * packetInRatePerPortThreshold, push a flow mod to block port - * - Allow blocking flow mods have a hard timeout and expires automatically - * - * TODO: keep a history of all events related in input throttling - * - * @param ofm - * @return - */ - @Override - public boolean inputThrottled(OFMessage ofm) { - if (ofm.getType() != OFType.PACKET_IN) { - return false; - } - ctrSwitchPktin.updateCounterNoFlush(); - // Compute current packet in rate - messageCount++; - if (messageCount % 1000 == 0) { - long now = System.currentTimeMillis(); - if (now != lastMessageTime) { - currentRate = (int) (1000000 / (now - lastMessageTime)); - lastMessageTime = now; - } else { - currentRate = Integer.MAX_VALUE; - } - } - if (!packetInThrottleEnabled) { - if (currentRate <= packetInRateThresholdHigh) { - return false; // most common case - } - enablePacketInThrottle(); - } else if (currentRate < packetInRateThresholdLow) { - disablePacketInThrottle(); - return false; - } - - // Now we are in the slow path where we need to do filtering - // First filter based on OFMatch - OFPacketIn pin = (OFPacketIn)ofm; - OFMatch match = new OFMatch(); - match.loadFromPacket(pin.getPacketData(), pin.getInPort()); - if (ofMatchCache.update(match)) { - ctrSwitchPktinDrops.updateCounterNoFlush(); - return true; - } - - // We have packet in with a distinct flow, check per mac rate - messageCountUniqueOFMatch++; - if ((messageCountUniqueOFMatch % packetInRatePerMacThreshold) == 1) { - checkPerSourceMacRate(pin); - } - - // Check per port rate - if ((messageCountUniqueOFMatch % packetInRatePerPortThreshold) == 1) { - checkPerPortRate(pin); - } - return false; - } - - /** - * We rely on the fact that packet in processing is single threaded - * per packet-in, so no locking is necessary. - */ - private void disablePacketInThrottle() { - ofMatchCache = null; - macCache = null; - macBlockedCache = null; - portCache = null; - portBlockedCache = null; - packetInThrottleEnabled = false; - floodlightProvider.addSwitchEvent(this.datapathId, - "SWITCH_OVERLOAD_THROTTLE_DISABLED ==>" + - "Pktin rate " + currentRate + "/s", false); - log.info("Packet in rate is {}, disable throttling on {}", - currentRate, this); - } - - private void enablePacketInThrottle() { - ofMatchCache = new TimedCache<OFMatch>(2048, 5000); // 5 second interval - macCache = new TimedCache<Long>(64, 1000 ); // remember last second - macBlockedCache = new TimedCache<Long>(256, 5000 ); // 5 second interval - portCache = new TimedCache<Short>(16, 1000 ); // rememeber last second - portBlockedCache = new TimedCache<Short>(64, 5000 ); // 5 second interval - packetInThrottleEnabled = true; - messageCountUniqueOFMatch = 0; - floodlightProvider.addSwitchEvent(this.datapathId, - "SWITCH_OVERLOAD_THROTTLE_ENABLED ==>" + - "Pktin rate " + currentRate + "/s", false); - log.info("Packet in rate is {}, enable throttling on {}", - currentRate, this); - } - - private void registerOverloadCounters() throws CounterException { - if (debugCountersRegistered) { - return; - } - if (debugCounters == null) { - log.error("Debug Counter Service not found"); - debugCounters = new NullDebugCounter(); - debugCountersRegistered = true; - } - // every level of the hierarchical counter has to be registered - // even if they are not used - ctrSwitch = debugCounters.registerCounter( - PACKAGE , stringId, - "Counter for this switch", - CounterType.ALWAYS_COUNT); - ctrSwitchPktin = debugCounters.registerCounter( - PACKAGE, stringId + "/pktin", - "Packet in counter for this switch", - CounterType.ALWAYS_COUNT); - ctrSwitchWrite = debugCounters.registerCounter( - PACKAGE, stringId + "/write", - "Write counter for this switch", - CounterType.ALWAYS_COUNT); - ctrSwitchPktinDrops = debugCounters.registerCounter( - PACKAGE, stringId + "/pktin/drops", - "Packet in throttle drop count", - CounterType.ALWAYS_COUNT, - IDebugCounterService.CTR_MDATA_WARN); - ctrSwitchWriteDrops = debugCounters.registerCounter( - PACKAGE, stringId + "/write/drops", - "Switch write throttle drop count", - CounterType.ALWAYS_COUNT, - IDebugCounterService.CTR_MDATA_WARN); - } - - /** - * Check if we have sampled this mac in the last second. - * Since we check every packetInRatePerMacThreshold packets, - * the presence of the mac in the macCache means the rate is - * above the threshold in a statistical sense. - * - * Take care not to block topology probing packets. Also don't - * push blocking flow mod if we have already done so within the - * last 5 seconds. - * - * @param pin - * @return - */ - private void checkPerSourceMacRate(OFPacketIn pin) { - byte[] data = pin.getPacketData(); - byte[] mac = Arrays.copyOfRange(data, 6, 12); - MACAddress srcMac = MACAddress.valueOf(mac); - short ethType = (short) (((data[12] & 0xff) << 8) + (data[13] & 0xff)); - if (ethType != Ethernet.TYPE_LLDP && ethType != Ethernet.TYPE_BSN && - macCache.update(srcMac.toLong())) { - // Check if we already pushed a flow in the last 5 seconds - if (macBlockedCache.update(srcMac.toLong())) { - return; - } - // write out drop flow per srcMac - int port = pin.getInPort(); - SwitchPort swPort = new SwitchPort(getId(), port); - ForwardingBase.blockHost(floodlightProvider, - swPort, srcMac.toLong(), (short) 5, - AppCookie.makeCookie(OFSWITCH_APP_ID, 0)); - floodlightProvider.addSwitchEvent(this.datapathId, - "SWITCH_PORT_BLOCKED_TEMPORARILY " + - "OFPort " + port + " mac " + srcMac, false); - log.info("Excessive packet in from {} on {}, block host for 5 sec", - srcMac.toString(), swPort); - } - } - - /** - * Works in a similar way as checkPerSourceMacRate(). - * - * TODO Don't block ports with links? - * - * @param pin - * @return - */ - private void checkPerPortRate(OFPacketIn pin) { - Short port = pin.getInPort(); - if (portCache.update(port)) { - // Check if we already pushed a flow in the last 5 seconds - if (portBlockedCache.update(port)) { - return; - } - // write out drop flow per port - SwitchPort swPort = new SwitchPort(getId(), port); - ForwardingBase.blockHost(floodlightProvider, - swPort, -1L, (short) 5, - AppCookie.makeCookie(OFSWITCH_APP_ID, 1)); - floodlightProvider.addSwitchEvent(this.datapathId, - "SWITCH_PORT_BLOCKED_TEMPORARILY " + - "OFPort " + port, false); - log.info("Excessive packet in from {}, block port for 5 sec", - swPort); - } - } - - @Override - @JsonIgnore - @LogMessageDoc(level="WARN", - message="Switch {switch} flow table is full", - explanation="The controller received flow table full " + - "message from the switch, could be caused by increased " + - "traffic pattern", - recommendation=LogMessageDoc.REPORT_CONTROLLER_BUG) - public void setTableFull(boolean isFull) { - if (isFull && !flowTableFull) { - floodlightProvider.addSwitchEvent(this.datapathId, - "SWITCH_FLOW_TABLE_FULL " + - "Table full error from switch", false); - log.warn("Switch {} flow table is full", stringId); - } - flowTableFull = isFull; - } - - - @Override - public short getAccessFlowPriority() { - return accessFlowPriority; - } - - - @Override - public short getCoreFlowPriority() { - return coreFlowPriority; - } - - - @Override - public void setAccessFlowPriority(short accessFlowPriority) { - this.accessFlowPriority = accessFlowPriority; - } - - - @Override - public void setCoreFlowPriority(short coreFlowPriority) { - this.coreFlowPriority = coreFlowPriority; - } - - @Override - public void startDriverHandshake() { - if (startDriverHandshakeCalled) - throw new SwitchDriverSubHandshakeAlreadyStarted(); - startDriverHandshakeCalled = true; - } - - @Override - public boolean isDriverHandshakeComplete() { - if (!startDriverHandshakeCalled) - throw new SwitchDriverSubHandshakeNotStarted(); - return true; - } - - @Override - public void processDriverHandshakeMessage(OFMessage m) { - if (startDriverHandshakeCalled) - throw new SwitchDriverSubHandshakeCompleted(m); - else - throw new SwitchDriverSubHandshakeNotStarted(); - } -} diff --git a/src/main/java/net/floodlightcontroller/core/PortChangeEvent.java b/src/main/java/net/floodlightcontroller/core/PortChangeEvent.java new file mode 100644 index 0000000000000000000000000000000000000000..557d8417db5dadb117202fd75fad0cab02efdba2 --- /dev/null +++ b/src/main/java/net/floodlightcontroller/core/PortChangeEvent.java @@ -0,0 +1,54 @@ +package net.floodlightcontroller.core; + +import org.projectfloodlight.openflow.protocol.OFPortDesc; + + +/** + * Describes a change of an open flow port + */ +public class PortChangeEvent { + public final OFPortDesc port; + public final PortChangeType type; + /** + * @param port + * @param type + */ + public PortChangeEvent(OFPortDesc port, + PortChangeType type) { + this.port = port; + this.type = type; + } + /* (non-Javadoc) + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((port == null) ? 0 : port.hashCode()); + result = prime * result + ((type == null) ? 0 : type.hashCode()); + return result; + } + /* (non-Javadoc) + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + PortChangeEvent other = (PortChangeEvent) obj; + if (port == null) { + if (other.port != null) return false; + } else if (!port.equals(other.port)) return false; + if (type != other.type) return false; + return true; + } + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "[" + type + " " + String.format("%s (%d)", port.getName(), port.getPortNo().getPortNumber()) + "]"; + } +} \ No newline at end of file diff --git a/src/main/java/net/floodlightcontroller/core/PortChangeType.java b/src/main/java/net/floodlightcontroller/core/PortChangeType.java new file mode 100644 index 0000000000000000000000000000000000000000..178d27b7603eebaa1c8400160843fd4e5d04be33 --- /dev/null +++ b/src/main/java/net/floodlightcontroller/core/PortChangeType.java @@ -0,0 +1,8 @@ +package net.floodlightcontroller.core; + +/** + * the type of change that happened to an open flow port + */ +public enum PortChangeType { + ADD, OTHER_UPDATE, DELETE, UP, DOWN, +} \ No newline at end of file diff --git a/src/main/java/net/floodlightcontroller/core/RoleInfo.java b/src/main/java/net/floodlightcontroller/core/RoleInfo.java index 1e6c186f759e2d7b81b529f91438d3b37210f46e..31ec6464c95932a094717a02b18f400d98764d8f 100644 --- a/src/main/java/net/floodlightcontroller/core/RoleInfo.java +++ b/src/main/java/net/floodlightcontroller/core/RoleInfo.java @@ -16,67 +16,34 @@ package net.floodlightcontroller.core; -import java.text.SimpleDateFormat; import java.util.Date; -import java.util.TimeZone; - -import net.floodlightcontroller.core.IFloodlightProviderService.Role; import com.fasterxml.jackson.annotation.JsonProperty; public class RoleInfo { - protected String role; - protected String roleChangeDescription; - protected Date roleChangeDateTime; - - public RoleInfo() { - } - - public RoleInfo(RoleInfo o) { - role = o.role; - roleChangeDescription = o.roleChangeDescription; - roleChangeDateTime = (Date)o.roleChangeDateTime.clone(); - } + private final HARole role; + private final String roleChangeDescription; + private final Date roleChangeDateTime; - public RoleInfo(String role) { - setRole(role); - } - - public RoleInfo(Role role, String description) { - this.role = (role != null) ? role.name() : "DISABLED"; - this.roleChangeDescription = description; - } - - public RoleInfo(Role role, String description, Date dt) { - this.role = (role != null) ? role.name() : "DISABLED"; + public RoleInfo(HARole role, String description, Date dt) { + this.role = role; this.roleChangeDescription = description; this.roleChangeDateTime = dt; } - public String getRole() { + public HARole getRole() { return role; } - public void setRole(String role) { - this.role = role; - } - @JsonProperty(value="change-description") public String getRoleChangeDescription() { return roleChangeDescription; } - @JsonProperty(value="change-description") - public void setRoleChangeDescription(String roleChangeDescription) { - this.roleChangeDescription = roleChangeDescription; - } + @JsonProperty(value="change-date-time") - public String getRoleChangeDateTime() { - SimpleDateFormat formatter = - new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); - formatter.setTimeZone(TimeZone.getTimeZone("UTC")); - return roleChangeDateTime == null ? - "" : formatter.format(roleChangeDateTime); + public Date getRoleChangeDateTime() { + return roleChangeDateTime; } -} \ No newline at end of file +} diff --git a/src/main/java/net/floodlightcontroller/core/SwitchDescription.java b/src/main/java/net/floodlightcontroller/core/SwitchDescription.java new file mode 100644 index 0000000000000000000000000000000000000000..57697d70916554da6cb73881b1237fadcd863101 --- /dev/null +++ b/src/main/java/net/floodlightcontroller/core/SwitchDescription.java @@ -0,0 +1,182 @@ +package net.floodlightcontroller.core; + +import org.projectfloodlight.openflow.protocol.OFDescStatsReply; + +/** + * Encapsulates the switch information return from the description stats request + * + * @author Rob Vaterlaus <rob.vaterlaus@bigswitch.com> + */ +public class SwitchDescription { + + public static class Builder { + + private String manufacturerDescription; + private String hardwareDescription; + private String softwareDescription; + private String serialNumber; + private String datapathDescription; + + public Builder() { + manufacturerDescription = ""; + hardwareDescription = ""; + softwareDescription = ""; + serialNumber = ""; + datapathDescription = ""; + } + + public Builder setManufacturerDescription(String manufacturerDescription) { + this.manufacturerDescription = manufacturerDescription; + return this; + } + + public Builder setHardwareDescription(String hardwareDescription) { + this.hardwareDescription = hardwareDescription; + return this; + } + + public Builder setSoftwareDescription(String softwareDescription) { + this.softwareDescription = softwareDescription; + return this; + } + + public Builder setSerialNumber(String serialNumber) { + this.serialNumber = serialNumber; + return this; + } + + public Builder setDatapathDescription(String datapathDescription) { + this.datapathDescription = datapathDescription; + return this; + } + + public SwitchDescription build() { + return new SwitchDescription(manufacturerDescription, + hardwareDescription, softwareDescription, serialNumber, + datapathDescription); + } + } + + private final String manufacturerDescription; + private final String hardwareDescription; + private final String softwareDescription; + private final String serialNumber; + private final String datapathDescription; + + public static Builder builder() { + return new Builder(); + } + + // FIXME: Should make this private + public SwitchDescription() { + this("", "", "", "", ""); + } + + // FIXME: Should make this private + public SwitchDescription(String manufacturerDescription, + String hardwareDescription, String softwareDescription, + String serialNumber, String datapathDescription) { + this.manufacturerDescription = manufacturerDescription; + this.hardwareDescription = hardwareDescription; + this.softwareDescription = softwareDescription; + this.serialNumber = serialNumber; + this.datapathDescription = datapathDescription; + } + + public SwitchDescription(OFDescStatsReply descStatsReply) { + this(descStatsReply.getMfrDesc(), descStatsReply.getHwDesc(), + descStatsReply.getSwDesc(), descStatsReply.getSerialNum(), + descStatsReply.getDpDesc()); + } + + public String getManufacturerDescription() { + return manufacturerDescription; + } + + public String getHardwareDescription() { + return hardwareDescription; + } + + public String getSoftwareDescription() { + return softwareDescription; + } + + public String getSerialNumber() { + return serialNumber; + } + + public String getDatapathDescription() { + return datapathDescription; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime + * result + + ((datapathDescription == null) ? 0 : datapathDescription + .hashCode()); + result = prime + * result + + ((hardwareDescription == null) ? 0 : hardwareDescription + .hashCode()); + result = prime + * result + + ((manufacturerDescription == null) ? 0 + : manufacturerDescription.hashCode()); + result = prime * result + + ((serialNumber == null) ? 0 : serialNumber.hashCode()); + result = prime + * result + + ((softwareDescription == null) ? 0 : softwareDescription + .hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + SwitchDescription other = (SwitchDescription) obj; + if (datapathDescription == null) { + if (other.datapathDescription != null) + return false; + } else if (!datapathDescription.equals(other.datapathDescription)) + return false; + if (hardwareDescription == null) { + if (other.hardwareDescription != null) + return false; + } else if (!hardwareDescription.equals(other.hardwareDescription)) + return false; + if (manufacturerDescription == null) { + if (other.manufacturerDescription != null) + return false; + } else if (!manufacturerDescription + .equals(other.manufacturerDescription)) + return false; + if (serialNumber == null) { + if (other.serialNumber != null) + return false; + } else if (!serialNumber.equals(other.serialNumber)) + return false; + if (softwareDescription == null) { + if (other.softwareDescription != null) + return false; + } else if (!softwareDescription.equals(other.softwareDescription)) + return false; + return true; + } + + @Override + public String toString() { + return "SwitchDescription [manufacturerDescription=" + manufacturerDescription + + ", hardwareDescription=" + hardwareDescription + ", softwareDescription=" + + softwareDescription + ", serialNumber=" + serialNumber + + ", datapathDescription=" + datapathDescription + "]"; + } +} diff --git a/src/main/java/net/floodlightcontroller/core/SwitchDisconnectedException.java b/src/main/java/net/floodlightcontroller/core/SwitchDisconnectedException.java new file mode 100644 index 0000000000000000000000000000000000000000..21b74ec7470071ee2fee36333c0316fd3381b5d7 --- /dev/null +++ b/src/main/java/net/floodlightcontroller/core/SwitchDisconnectedException.java @@ -0,0 +1,23 @@ +package net.floodlightcontroller.core; + +import org.projectfloodlight.openflow.types.DatapathId; + +public class SwitchDisconnectedException extends Exception { + private static final long serialVersionUID = 1L; + + private final DatapathId id; + + public SwitchDisconnectedException(DatapathId id) { + super(genMessage(id)); + this.id = id; + } + + private static String genMessage(DatapathId id) { + return String.format("Switch %s disconnected", id); + } + + public DatapathId getId() { + return id; + } + +} diff --git a/src/main/java/net/floodlightcontroller/core/SwitchDriverSubHandshakeCompleted.java b/src/main/java/net/floodlightcontroller/core/SwitchDriverSubHandshakeCompleted.java index ed39b5f44bc34597b41d3fc848d2b3410c7943b4..fff0a6598185e202042a296e8f67394ee46f9a3c 100644 --- a/src/main/java/net/floodlightcontroller/core/SwitchDriverSubHandshakeCompleted.java +++ b/src/main/java/net/floodlightcontroller/core/SwitchDriverSubHandshakeCompleted.java @@ -1,6 +1,6 @@ package net.floodlightcontroller.core; -import org.openflow.protocol.OFMessage; +import org.projectfloodlight.openflow.protocol.OFMessage; /** * Indicates that a message was passed to a switch driver's subhandshake diff --git a/src/main/java/net/floodlightcontroller/core/SwitchSyncRepresentation.java b/src/main/java/net/floodlightcontroller/core/SwitchSyncRepresentation.java index 6284ba3c38785c2e5dc21421d012147f74c165ed..ea583510844a32655d7019afe2a10d620a02c892 100644 --- a/src/main/java/net/floodlightcontroller/core/SwitchSyncRepresentation.java +++ b/src/main/java/net/floodlightcontroller/core/SwitchSyncRepresentation.java @@ -2,16 +2,20 @@ package net.floodlightcontroller.core; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.List; +import java.util.Set; -import net.floodlightcontroller.util.EnumBitmaps; -import net.floodlightcontroller.util.MACAddress; +import org.projectfloodlight.openflow.protocol.OFFactory; +import org.projectfloodlight.openflow.protocol.OFFeaturesReply; +import org.projectfloodlight.openflow.protocol.OFPortDesc; -import org.openflow.protocol.OFFeaturesReply; -import org.openflow.protocol.OFPhysicalPort; -import org.openflow.protocol.OFPhysicalPort.OFPortState; -import org.openflow.protocol.statistics.OFDescriptionStatistics; -import org.openflow.util.HexString; +import net.floodlightcontroller.core.SwitchDescription; + +import org.projectfloodlight.openflow.types.DatapathId; +import org.projectfloodlight.openflow.util.HexString; +import org.projectfloodlight.openflow.protocol.OFCapabilities; +import org.projectfloodlight.openflow.protocol.OFActionType; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnore; @@ -27,27 +31,11 @@ import com.fasterxml.jackson.annotation.JsonProperty; public class SwitchSyncRepresentation { public static class SyncedPort { @JsonProperty - public short portNumber; - @JsonProperty - public long hardwareAddress; - @JsonProperty - public String name; - @JsonProperty - public int config; - @JsonProperty - public int state; - @JsonProperty - public int currentFeatures; - @JsonProperty - public int advertisedFeatures; - @JsonProperty - public int supportedFeatures; - @JsonProperty - public int peerFeatures; + public OFPortDesc port; - public static SyncedPort fromImmutablePort(ImmutablePort p) { + /*public static SyncedPort fromImmutablePort(OFPortDesc p) { SyncedPort rv = new SyncedPort(); - rv.portNumber = p.getPortNumber(); + rv.port = OFPortDesc.of(p.getPortNumber()); if (p.getHardwareAddress() == null) { rv.hardwareAddress = 0; } else { @@ -66,32 +54,38 @@ public class SwitchSyncRepresentation { EnumBitmaps.toBitmap(p.getSupportedFeatures()); rv.peerFeatures = EnumBitmaps.toBitmap(p.getPeerFeatures()); return rv; - } - - public OFPhysicalPort toOFPhysicalPort() { - OFPhysicalPort p = new OFPhysicalPort(); - p.setPortNumber(portNumber); - p.setHardwareAddress(MACAddress.valueOf(hardwareAddress).toBytes()); - p.setName(name); - p.setConfig(config); - p.setState(state); - p.setCurrentFeatures(currentFeatures); - p.setAdvertisedFeatures(advertisedFeatures); - p.setSupportedFeatures(supportedFeatures); - p.setPeerFeatures(peerFeatures); - return p; + }*/ + + public static SyncedPort fromOFPortDesc(OFPortDesc ofpd) { + SyncedPort sp = new SyncedPort(); + sp.port = ofpd; + return sp; + } + + public OFPortDesc toOFPortDesc(OFFactory factory) { + OFPortDesc.Builder builder = factory.buildPortDesc(); + builder.setPortNo(port.getPortNo()); + builder.setHwAddr(port.getHwAddr()); + builder.setName(port.getName()); + builder.setConfig(port.getConfig()); + builder.setState(port.getState()); + builder.setCurr(port.getCurr()); + builder.setAdvertised(port.getAdvertised()); + builder.setSupported(port.getSupported()); + builder.setPeer(port.getPeer()); + return builder.build(); } } // From FeaturesReply - private final long dpid; - private final int buffers; - private final byte tables; - private final int capabilities; - private final int actions; + private final DatapathId dpid; + private final long buffers; + private final short tables; + private final Set<OFCapabilities> capabilities; + private final Set<OFActionType> actions; private final List<SyncedPort> ports; - // From OFDescriptionStatistics + // From OFDescStatsReply private final String manufacturerDescription; private final String hardwareDescription; private final String softwareDescription; @@ -115,11 +109,11 @@ public class SwitchSyncRepresentation { */ @JsonCreator public SwitchSyncRepresentation( - @JsonProperty("dpid") long dpid, + @JsonProperty("dpid") DatapathId dpid, @JsonProperty("buffers") int buffers, @JsonProperty("tables") byte tables, - @JsonProperty("capabilities") int capabilities, - @JsonProperty("actions") int actions, + @JsonProperty("capabilities") Set<OFCapabilities> capabilities, + @JsonProperty("actions") Set<OFActionType> actions, @JsonProperty("ports") List<SyncedPort> ports, @JsonProperty("manufacturerDescription") String manufacturerDescription, @JsonProperty("hardwareDescription") String hardwareDescription, @@ -147,7 +141,7 @@ public class SwitchSyncRepresentation { this.actions = sw.getActions(); this.ports = toSyncedPortList(sw.getPorts()); - OFDescriptionStatistics d = sw.getDescriptionStatistics(); + SwitchDescription d = sw.getSwitchDescription(); this.manufacturerDescription = d.getManufacturerDescription(); this.hardwareDescription = d.getHardwareDescription(); this.softwareDescription = d.getSoftwareDescription(); @@ -156,14 +150,13 @@ public class SwitchSyncRepresentation { } public SwitchSyncRepresentation(OFFeaturesReply fr, - OFDescriptionStatistics d) { + SwitchDescription d) { this.dpid = fr.getDatapathId(); - this.buffers = fr.getBuffers(); - this.tables = fr.getTables(); + this.buffers = fr.getNBuffers(); + this.tables = fr.getNTables(); this.capabilities = fr.getCapabilities(); this.actions = fr.getActions(); - this.ports = toSyncedPortList( - ImmutablePort.immutablePortListOf(fr.getPorts())); + this.ports = toSyncedPortList(fr.getPorts()); this.manufacturerDescription = d.getManufacturerDescription(); this.hardwareDescription = d.getHardwareDescription(); @@ -172,65 +165,73 @@ public class SwitchSyncRepresentation { this.datapathDescription = d.getDatapathDescription(); } - private static List<SyncedPort> toSyncedPortList(Collection<ImmutablePort> ports) { + private static List<SyncedPort> toSyncedPortList(Collection<OFPortDesc> ports) { List<SyncedPort> rv = new ArrayList<SyncedPort>(ports.size()); - for (ImmutablePort p: ports) { - rv.add(SyncedPort.fromImmutablePort(p)); + for (OFPortDesc p: ports) { + rv.add(SyncedPort.fromOFPortDesc(p)); } return rv; } - private static List<OFPhysicalPort> toOFPhysicalPortList(Collection<SyncedPort> ports) { - List<OFPhysicalPort> rv = new ArrayList<OFPhysicalPort>(ports.size()); + private static List<OFPortDesc> toOFPortDescList(OFFactory factory, Collection<SyncedPort> ports) { + List<OFPortDesc> rv = new ArrayList<OFPortDesc>(ports.size()); for (SyncedPort p: ports) { - rv.add(p.toOFPhysicalPort()); + rv.add(p.toOFPortDesc(factory)); } return rv; } @JsonIgnore - public OFFeaturesReply getFeaturesReply() { - OFFeaturesReply fr = new OFFeaturesReply(); - fr.setDatapathId(dpid); - fr.setBuffers(buffers); - fr.setTables(tables); - fr.setCapabilities(capabilities); - fr.setActions(actions); - fr.setPorts(toOFPhysicalPortList(ports)); - return fr; + public OFFeaturesReply getFeaturesReply(OFFactory factory) { + /** + * FIXME Icky work around; if a null actions got written to storage + * then fake up an empty one so the builder() doesn't throw + * a NPE. Need to root cause why someone would write a null actions. + * This code will all be removed shortly -- needed to unblock BVS team. + */ + Set<OFActionType> workAroundActions; + if (actions != null) + workAroundActions = actions; + else + workAroundActions = Collections.<OFActionType> emptySet(); + + OFFeaturesReply featuresReply = factory.buildFeaturesReply() + .setXid(0) + .setDatapathId(dpid) + .setNBuffers(buffers) + .setNTables(tables) + .setCapabilities(capabilities) + .setActions(workAroundActions) + .setPorts(toOFPortDescList(factory, ports)) + .build(); + return featuresReply; } @JsonIgnore - public OFDescriptionStatistics getDescription() { - OFDescriptionStatistics desc = new OFDescriptionStatistics(); - desc.setManufacturerDescription(manufacturerDescription); - desc.setHardwareDescription(hardwareDescription); - desc.setSoftwareDescription(softwareDescription); - desc.setSerialNumber(serialNumber); - desc.setDatapathDescription(datapathDescription); - return desc; + public SwitchDescription getDescription() { + return new SwitchDescription(manufacturerDescription, + hardwareDescription, softwareDescription, softwareDescription, + datapathDescription); } - - - public long getDpid() { + public DatapathId getDpid() { return dpid; } - public int getBuffers() { + public long getBuffers() { return buffers; } - public byte getTables() { + public short getTables() { return tables; } - public int getCapabilities() { + public Set<OFCapabilities> getCapabilities() { return capabilities; } - public int getActions() { + public Set<OFActionType> getActions() { return actions; } @@ -261,7 +262,7 @@ public class SwitchSyncRepresentation { @Override public String toString() { String dpidString; - dpidString = HexString.toHexString(dpid); + dpidString = HexString.toHexString(dpid.getLong()); return "SwitchSyncRepresentation [DPID=" + dpidString + "]"; } } diff --git a/src/main/java/net/floodlightcontroller/core/internal/CmdLineSettings.java b/src/main/java/net/floodlightcontroller/core/internal/CmdLineSettings.java index e22640a91f07b81fe1dba80dd4c81052d4e65610..972321f62e1f2323c5916340d96346ad7d240bc4 100644 --- a/src/main/java/net/floodlightcontroller/core/internal/CmdLineSettings.java +++ b/src/main/java/net/floodlightcontroller/core/internal/CmdLineSettings.java @@ -22,7 +22,7 @@ import org.kohsuke.args4j.Option; * Expresses the port settings of OpenFlow controller. */ public class CmdLineSettings { - public static final String DEFAULT_CONFIG_FILE = "config/floodlight.properties"; + public static final String DEFAULT_CONFIG_FILE = "src/main/resources/floodlightdefault.properties"; @Option(name="-cf", aliases="--configFile", metaVar="FILE", usage="Floodlight configuration file") private String configFile = DEFAULT_CONFIG_FILE; diff --git a/src/main/java/net/floodlightcontroller/core/internal/Controller.java b/src/main/java/net/floodlightcontroller/core/internal/Controller.java index 7d6227d108b6a1279056b450622c05750f77e90c..730287381acd161714fa4d8b8fc06e75d79d7017 100644 --- a/src/main/java/net/floodlightcontroller/core/internal/Controller.java +++ b/src/main/java/net/floodlightcontroller/core/internal/Controller.java @@ -17,71 +17,58 @@ package net.floodlightcontroller.core.internal; -import java.io.FileInputStream; -import java.io.IOException; import java.lang.management.ManagementFactory; import java.lang.management.RuntimeMXBean; -import java.net.InetSocketAddress; import java.util.ArrayList; -import java.util.Collection; -import java.util.Arrays; import java.util.Collections; -import java.util.Date; import java.util.HashMap; -import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; -import java.util.Properties; import java.util.Set; import java.util.Stack; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.CopyOnWriteArraySet; -import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; +import org.jboss.netty.util.HashedWheelTimer; +import org.jboss.netty.util.Timer; + +import net.floodlightcontroller.core.ControllerId; import net.floodlightcontroller.core.FloodlightContext; import net.floodlightcontroller.core.HAListenerTypeMarker; +import net.floodlightcontroller.core.HARole; import net.floodlightcontroller.core.IFloodlightProviderService; import net.floodlightcontroller.core.IHAListener; import net.floodlightcontroller.core.IInfoProvider; +import net.floodlightcontroller.core.IShutdownService; import net.floodlightcontroller.core.IListener.Command; import net.floodlightcontroller.core.IOFMessageListener; import net.floodlightcontroller.core.IOFSwitch; -import net.floodlightcontroller.core.IOFSwitch.PortChangeEvent; -import net.floodlightcontroller.core.IOFSwitch.PortChangeType; -import net.floodlightcontroller.core.IOFSwitchDriver; import net.floodlightcontroller.core.IOFSwitchListener; -import net.floodlightcontroller.core.IReadyForReconcileListener; -import net.floodlightcontroller.core.ImmutablePort; -import net.floodlightcontroller.core.OFSwitchBase; +import net.floodlightcontroller.core.LogicalOFMessageCategory; +import net.floodlightcontroller.core.PortChangeType; import net.floodlightcontroller.core.RoleInfo; -import net.floodlightcontroller.core.SwitchSyncRepresentation; import net.floodlightcontroller.core.annotations.LogMessageDoc; import net.floodlightcontroller.core.annotations.LogMessageDocs; import net.floodlightcontroller.core.module.FloodlightModuleException; +import net.floodlightcontroller.core.module.FloodlightModuleLoader; import net.floodlightcontroller.core.util.ListenerDispatcher; import net.floodlightcontroller.core.web.CoreWebRoutable; -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.debugevent.IDebugEventService; -import net.floodlightcontroller.debugevent.IDebugEventService.EventColumn; -import net.floodlightcontroller.debugevent.IDebugEventService.EventFieldType; -import net.floodlightcontroller.debugevent.IEventUpdater; -import net.floodlightcontroller.debugevent.NullDebugEvent; -import net.floodlightcontroller.debugevent.IDebugEventService.EventType; -import net.floodlightcontroller.debugevent.IDebugEventService.MaxEventsRegistered; import net.floodlightcontroller.notification.INotificationManager; import net.floodlightcontroller.notification.NotificationManagerFactory; + +import org.projectfloodlight.openflow.protocol.OFMessage; +import org.projectfloodlight.openflow.protocol.OFPacketIn; +import org.projectfloodlight.openflow.protocol.OFPortDesc; +import org.projectfloodlight.openflow.protocol.OFType; +import org.projectfloodlight.openflow.types.DatapathId; + import net.floodlightcontroller.packet.Ethernet; import net.floodlightcontroller.perfmon.IPktInProcessingTimeService; import net.floodlightcontroller.restserver.IRestApiService; @@ -90,107 +77,81 @@ import net.floodlightcontroller.storage.IStorageSourceListener; import net.floodlightcontroller.storage.IStorageSourceService; import net.floodlightcontroller.storage.StorageException; import net.floodlightcontroller.threadpool.IThreadPoolService; -import net.floodlightcontroller.util.LoadMonitor; -import net.floodlightcontroller.util.TimedCache; - -import org.jboss.netty.bootstrap.ServerBootstrap; -import org.jboss.netty.channel.ChannelPipelineFactory; -import org.jboss.netty.channel.group.ChannelGroup; -import org.jboss.netty.channel.group.DefaultChannelGroup; -import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory; -import org.openflow.protocol.OFMessage; -import org.openflow.protocol.OFPacketIn; -import org.openflow.protocol.OFType; -import org.openflow.protocol.factory.BasicFactory; -import org.openflow.protocol.statistics.OFDescriptionStatistics; -import org.openflow.util.HexString; -import org.openflow.vendor.nicira.OFNiciraVendorExtensions; -import org.sdnplatform.sync.IClosableIterator; -import org.sdnplatform.sync.IStoreClient; -import org.sdnplatform.sync.IStoreListener; + import org.sdnplatform.sync.ISyncService; import org.sdnplatform.sync.ISyncService.Scope; -import org.sdnplatform.sync.Versioned; -import org.sdnplatform.sync.error.ObsoleteVersionException; import org.sdnplatform.sync.error.SyncException; +import org.sdnplatform.sync.internal.config.ClusterConfig; + +import net.floodlightcontroller.util.LoadMonitor; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.bigswitch.floodlight.vendor.OFVendorActions; - +import com.google.common.base.Optional; +import com.google.common.base.Strings; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; /** * The main controller class. Handles all setup and network listeners */ -public class Controller implements IFloodlightProviderService, - IStorageSourceListener, IInfoProvider { +public class Controller implements IFloodlightProviderService, IStorageSourceListener, IInfoProvider { protected static final Logger log = LoggerFactory.getLogger(Controller.class); - protected static final INotificationManager notifier = - NotificationManagerFactory.getNotificationManager(Controller.class); - - static final String ERROR_DATABASE = - "The controller could not communicate with the system database."; - static final String SWITCH_SYNC_STORE_NAME = - Controller.class.getCanonicalName() + ".stateStore"; + protected static final INotificationManager notifier = NotificationManagerFactory.getNotificationManager(Controller.class); - protected BasicFactory factory; - protected ConcurrentMap<OFType, - ListenerDispatcher<OFType,IOFMessageListener>> - messageListeners; - - // OFSwitch driver binding map and order - private ISwitchDriverRegistry driverRegistry; + static final String ERROR_DATABASE = "The controller could not communicate with the system database."; + protected ConcurrentMap<OFType, ListenerDispatcher<OFType,IOFMessageListener>> messageListeners; + // The controllerNodeIPsCache maps Controller IDs to their IP address. // It's only used by handleControllerNodeIPsChanged protected HashMap<String, String> controllerNodeIPsCache; - protected Set<IOFSwitchListener> switchListeners; protected ListenerDispatcher<HAListenerTypeMarker,IHAListener> haListeners; - protected Set<IReadyForReconcileListener> readyForReconcileListeners; protected Map<String, List<IInfoProvider>> providerMap; protected BlockingQueue<IUpdate> updates; + protected ControllerCounters counters; + protected Timer timer; + + // Module Loader State + private ModuleLoaderState moduleLoaderState; + public enum ModuleLoaderState { + INIT, STARTUP, COMPLETE + } // Module dependencies - private IRestApiService restApi; - private ICounterStoreService counterStore = null; - private IDebugCounterService debugCounters; - protected IDebugEventService debugEvents; - private IStorageSourceService storageSource; - private IPktInProcessingTimeService pktinProcTime; - private IThreadPoolService threadPool; - private ScheduledExecutorService ses; + private IStorageSourceService storageSourceService; + private IOFSwitchService switchService; + private IDebugCounterService debugCounterService; + protected IDebugEventService debugEventService; + private IRestApiService restApiService; + private IPktInProcessingTimeService pktinProcTimeService; + private IThreadPoolService threadPoolService; private ISyncService syncService; - private IStoreClient<Long, SwitchSyncRepresentation> storeClient; + private IShutdownService shutdownService; // Configuration options - protected String openFlowHost = null; - protected int openFlowPort = 6633; + protected int openFlowPort = 6653; // new registered OF port number + private String openFlowHostname = null; protected int workerThreads = 0; - + + // The id for this controller node. Should be unique for each controller + // node in a controller cluster. + protected String controllerId = "my-floodlight-controller"; // This controller's current role that modules can use/query to decide - // if they should operate in master or slave mode. - // TODO: potentially we need to get rid of this field and modules must - // then rely on the role notifications alone... - protected volatile Role notifiedRole; + // if they should operate in ACTIVE / STANDBY + protected volatile HARole notifiedRole; private static final String INITIAL_ROLE_CHANGE_DESCRIPTION = "Controller startup."; + /** + * NOTE: roleManager is not 'final' because it's initialized at run time + * based on parameters that are only available in init() + */ private RoleManager roleManager; - private SwitchManager switchManager; - - private static final int DEFAULT_CONSOLIDATE_STORE_TIME_DELAY_MS = - 15*1000; // 15s - private int consolidateStoreTimeDelayMs = - DEFAULT_CONSOLIDATE_STORE_TIME_DELAY_MS; - - - // Flag to always flush flow table on switch reconnect (HA or otherwise) - private boolean alwaysClearFlowsOnSwActivate = false; - private TimedCache<Long> swConnectCache; // Storage table names protected static final String CONTROLLER_TABLE_NAME = "controller_controller"; @@ -218,15 +179,12 @@ public class Controller implements IFloodlightProviderService, FLOW_COLUMN_CORE_PRIORITY }; - private static final short DEFAULT_ACCESS_PRIORITY = 10; - private static final short DEFAULT_CORE_PRIORITY = 1000; - private short accessPriority = DEFAULT_ACCESS_PRIORITY; - private short corePriority = DEFAULT_CORE_PRIORITY; - - + // TODO @Ryan delete? not referenced anywhere private static short DEFAULT_ACCESS_PRIORITY = 10; + // TODO @Ryan delete? not referenced anywhere private static short DEFAULT_CORE_PRIORITY = 1000; + // Perf. related configuration protected static final int SEND_BUFFER_SIZE = 128 * 1024; - public static final int BATCH_MAX_SIZE = 100; + public static final int BATCH_MAX_SIZE = 1; //TODO @Ryan this was 100. Causes packet_out messages to stall until 100 accumulated... protected static final boolean ALWAYS_DECODE_ETH = true; // Set of port name prefixes that will be classified as uplink ports, @@ -242,1337 +200,59 @@ public class Controller implements IFloodlightProviderService, this.uplinkPortPrefixSet = prefixSet; } - // Event IDs for debug events - protected IEventUpdater<SwitchEvent> evSwitch; + @Override + public ModuleLoaderState getModuleLoaderState(){ + return this.moduleLoaderState; + } // Load monitor for overload protection - protected final boolean overload_drop = - Boolean.parseBoolean(System.getProperty("overload_drop", "false")); + protected final boolean overload_drop = Boolean.parseBoolean(System.getProperty("overload_drop", "false")); protected final LoadMonitor loadmonitor = new LoadMonitor(log); - - private class NotificationSwitchListener implements IOFSwitchListener { + + private static class NotificationSwitchListener implements IOFSwitchListener { @Override - public void switchAdded(long switchId) { - notifier.postNotification("Switch " + HexString.toHexString(switchId) + " connected."); + public void switchAdded(DatapathId switchId) { + notifier.postNotification("Switch " + switchId + " connected."); } @Override - public void switchRemoved(long switchId) { - notifier.postNotification("Switch " + HexString.toHexString(switchId) + " disconnected."); + public void switchRemoved(DatapathId switchId) { + notifier.postNotification("Switch " + switchId + " disconnected."); } @Override - public void switchActivated(long switchId) { + public void switchActivated(DatapathId switchId) { + } @Override - public void switchPortChanged(long switchId, ImmutablePort port, + public void switchPortChanged(DatapathId switchId, OFPortDesc port, PortChangeType type) { String msg = String.format("Switch %s port %s changed: %s", - HexString.toHexString(switchId), + switchId, port.getName(), type.toString()); notifier.postNotification(msg); } @Override - public void switchChanged(long switchId) { - } - } - public static class Counters { - public static final String prefix = Controller.class.getPackage().getName(); - public IDebugCounter setRoleEqual; - public IDebugCounter setSameRole; - public IDebugCounter setRoleMaster; - public IDebugCounter remoteStoreNotification; - public IDebugCounter invalidPortsChanged; - public IDebugCounter invalidSwitchActivatedWhileSlave; - public IDebugCounter invalidStoreEventWhileMaster; - public IDebugCounter switchDisconnectedWhileSlave; - public IDebugCounter switchActivated; - public IDebugCounter errorSameSwitchReactivated; // err - public IDebugCounter switchWithSameDpidActivated; // warn - public IDebugCounter newSwitchActivated; // new switch - public IDebugCounter syncedSwitchActivated; - public IDebugCounter readyForReconcile; - public IDebugCounter newSwitchFromStore; - public IDebugCounter updatedSwitchFromStore; - public IDebugCounter switchDisconnected; - public IDebugCounter syncedSwitchRemoved; - public IDebugCounter unknownSwitchRemovedFromStore; - public IDebugCounter consolidateStoreRunCount; - public IDebugCounter consolidateStoreInconsistencies; - public IDebugCounter storeSyncError; - public IDebugCounter switchesNotReconnectingToNewMaster; - public IDebugCounter switchPortChanged; - public IDebugCounter switchOtherChange; - public IDebugCounter dispatchMessageWhileSlave; - public IDebugCounter dispatchMessage; // does this cnt make sense? more specific?? per type? count stops? - public IDebugCounter controllerNodeIpsChanged; - public IDebugCounter messageReceived; - public IDebugCounter messageInputThrottled; - public IDebugCounter switchDisconnectReadTimeout; - public IDebugCounter switchDisconnectHandshakeTimeout; - public IDebugCounter switchDisconnectIOError; - public IDebugCounter switchDisconnectParseError; - public IDebugCounter switchDisconnectSwitchStateException; - public IDebugCounter rejectedExecutionException; - public IDebugCounter switchDisconnectOtherException; - public IDebugCounter switchConnected; - public IDebugCounter unhandledMessage; - public IDebugCounter packetInWhileSwitchIsSlave; - public IDebugCounter epermErrorWhileSwitchIsMaster; - public IDebugCounter roleNotResentBecauseRolePending; - public IDebugCounter roleRequestSent; - public IDebugCounter roleReplyTimeout; - public IDebugCounter roleReplyReceived; // expected RoleReply received - public IDebugCounter roleReplyErrorUnsupported; - public IDebugCounter switchCounterRegistrationFailed; - - void createCounters(IDebugCounterService debugCounters) throws CounterException { - setRoleEqual = - debugCounters.registerCounter( - prefix, "set-role-equal", - "Controller received a role request with role of "+ - "EQUAL which is unusual", - CounterType.ALWAYS_COUNT); - setSameRole = - debugCounters.registerCounter( - prefix, "set-same-role", - "Controller received a role request for the same " + - "role the controller already had", - CounterType.ALWAYS_COUNT, - IDebugCounterService.CTR_MDATA_WARN); - - setRoleMaster = - debugCounters.registerCounter( - prefix, "set-role-master", - "Controller received a role request with role of " + - "MASTER. This counter can be at most 1.", - CounterType.ALWAYS_COUNT); - - remoteStoreNotification = - debugCounters.registerCounter( - prefix, "remote-store-notification", - "Received a notification from the sync service " + - "indicating that switch information has changed", - CounterType.ALWAYS_COUNT); - - invalidPortsChanged = - debugCounters.registerCounter( - prefix, "invalid-ports-changed", - "Received an unexpected ports changed " + - "notification while the controller was in " + - "SLAVE role.", - CounterType.ALWAYS_COUNT, - IDebugCounterService.CTR_MDATA_WARN); - - invalidSwitchActivatedWhileSlave = - debugCounters.registerCounter( - prefix, "invalid-switch-activated-while-slave", - "Received an unexpected switchActivated " + - "notification while the controller was in " + - "SLAVE role.", - CounterType.ALWAYS_COUNT, - IDebugCounterService.CTR_MDATA_WARN); - - invalidStoreEventWhileMaster = - debugCounters.registerCounter( - prefix, "invalid-store-event-while-master", - "Received an unexpected notification from " + - "the sync store while the controller was in " + - "MASTER role.", - CounterType.ALWAYS_COUNT, - IDebugCounterService.CTR_MDATA_WARN); - - switchDisconnectedWhileSlave = - debugCounters.registerCounter( - prefix, "switch-disconnected-while-slave", - "A switch disconnected and the controller was " + - "in SLAVE role.", - CounterType.ALWAYS_COUNT, - IDebugCounterService.CTR_MDATA_WARN); - - switchActivated = - debugCounters.registerCounter( - prefix, "switch-activated", - "A switch connected to this controller is now " + - "in MASTER role", - CounterType.ALWAYS_COUNT); - - errorSameSwitchReactivated = // err - debugCounters.registerCounter( - prefix, "error-same-switch-reactivated", - "A switch that was already in active state " + - "was activated again. This indicates a " + - "controller defect", - CounterType.ALWAYS_COUNT, - IDebugCounterService.CTR_MDATA_ERROR); - - switchWithSameDpidActivated = // warn - debugCounters.registerCounter( - prefix, "switch-with-same-dpid-activated", - "A switch with the same DPID as another switch " + - "connected to the controller. This can be " + - "caused by multiple switches configured with " + - "the same DPID or by a switch reconnecting very " + - "quickly.", - CounterType.COUNT_ON_DEMAND, - IDebugCounterService.CTR_MDATA_WARN); - - newSwitchActivated = // new switch - debugCounters.registerCounter( - prefix, "new-switch-activated", - "A new switch has completed the handshake as " + - "MASTER. The switch was not known to any other " + - "controller in the cluster", - CounterType.ALWAYS_COUNT); - syncedSwitchActivated = - debugCounters.registerCounter( - prefix, "synced-switch-activated", - "A switch has completed the handshake as " + - "MASTER. The switch was known to another " + - "controller in the cluster", - CounterType.ALWAYS_COUNT); - - readyForReconcile = - debugCounters.registerCounter( - prefix, "ready-for-reconcile", - "Controller is ready for flow reconciliation " + - "after Slave to Master transition. Either all " + - "previously known switches are now active " + - "or they have timed out and have been removed." + - "This counter will be 0 or 1.", - CounterType.ALWAYS_COUNT); - - newSwitchFromStore = - debugCounters.registerCounter( - prefix, "new-switch-from-store", - "A new switch has connected to another " + - "another controller in the cluster. This " + - "controller instance has received a sync store " + - "notification for it.", - CounterType.ALWAYS_COUNT); - - updatedSwitchFromStore = - debugCounters.registerCounter( - prefix, "updated-switch-from-store", - "Information about a switch connected to " + - "another controller instance was updated in " + - "the sync store. This controller instance has " + - "received a notification for it", - CounterType.ALWAYS_COUNT); - - switchDisconnected = - debugCounters.registerCounter( - prefix, "switch-disconnected", - "FIXME: switch has disconnected", - CounterType.ALWAYS_COUNT); - - syncedSwitchRemoved = - debugCounters.registerCounter( - prefix, "synced-switch-removed", - "A switch connected to another controller " + - "instance has disconnected from the controller " + - "cluster. This controller instance has " + - "received a notification for it", - CounterType.ALWAYS_COUNT); - - unknownSwitchRemovedFromStore = - debugCounters.registerCounter( - prefix, "unknown-switch-removed-from-store", - "This controller instances has received a sync " + - "store notification that a switch has " + - "disconnected but this controller instance " + - "did not have the any information about the " + - "switch", // might be less than warning - CounterType.ALWAYS_COUNT, - IDebugCounterService.CTR_MDATA_WARN); - - consolidateStoreRunCount = - debugCounters.registerCounter( - prefix, "consolidate-store-run-count", - "This controller has transitioned from SLAVE " + - "to MASTER and waited for switches to reconnect. " + - "The controller has finished waiting and has " + - "reconciled switch entries in the sync store " + - "with live state", - CounterType.ALWAYS_COUNT); - - consolidateStoreInconsistencies = - debugCounters.registerCounter( - prefix, "consolidate-store-inconsistencies", - "During switch sync store consolidation: " + - "Number of switches that were in the store " + - "but not otherwise known plus number of " + - "switches that were in the store previously " + - "but are now missing plus number of " + - "connected switches that were absent from " + - "the store although this controller has " + - "written them. A non-zero count " + - "indicates a brief split-brain dual MASTER " + - "situation during fail-over", - CounterType.ALWAYS_COUNT); - - storeSyncError = - debugCounters.registerCounter( - prefix, "store-sync-error", - "Number of times a sync store operation failed " + - "due to a store sync exception or an entry in " + - "in the store had invalid data.", - CounterType.ALWAYS_COUNT, - IDebugCounterService.CTR_MDATA_ERROR); - - switchesNotReconnectingToNewMaster = - debugCounters.registerCounter( - prefix, "switches-not-reconnecting-to-new-master", - "Switches that were connected to another " + - "controller instance in the cluster but that " + - "did not reconnect to this controller after it " + - "transitioned to MASTER", // might be less than warning - CounterType.ALWAYS_COUNT); - - switchPortChanged = - debugCounters.registerCounter( - prefix, "switch-port-changed", - "Number of times switch ports have changed", - CounterType.ALWAYS_COUNT); - switchOtherChange = - debugCounters.registerCounter( - prefix, "switch-other-change", - "Number of times other information of a switch " + - "has changed.", - CounterType.ALWAYS_COUNT); - - dispatchMessageWhileSlave = - debugCounters.registerCounter( - prefix, "dispatch-message-while-slave", - "Number of times an OF message was received " + - "and supposed to be dispatched but the " + - "controller was in SLAVE role and the message " + - "was not dispatched", - CounterType.ALWAYS_COUNT); - - dispatchMessage = // does this cnt make sense? more specific?? per type? count stops? - debugCounters.registerCounter( - prefix, "dispatch-message", - "Number of times an OF message was dispatched " + - "to registered modules", - CounterType.ALWAYS_COUNT); - - controllerNodeIpsChanged = - debugCounters.registerCounter( - prefix, "controller-nodes-ips-changed", - "IP addresses of controller nodes have changed", - CounterType.ALWAYS_COUNT); - - //------------------------ - // channel handler counters. Factor them out ?? - messageReceived = - debugCounters.registerCounter( - prefix, "message-received", - "Number of OpenFlow messages received. Some of " + - "these might be throttled", - CounterType.ALWAYS_COUNT); - messageInputThrottled = - debugCounters.registerCounter( - prefix, "message-input-throttled", - "Number of OpenFlow messages that were " + - "throttled due to high load from the sender", - CounterType.ALWAYS_COUNT, - IDebugCounterService.CTR_MDATA_WARN); - // TODO: more counters in messageReceived ?? - - switchDisconnectReadTimeout = - debugCounters.registerCounter( - prefix, "switch-disconnect-read-timeout", - "Number of times a switch was disconnected due " + - "due the switch failing to send OpenFlow " + - "messages or responding to OpenFlow ECHOs", - CounterType.ALWAYS_COUNT, - IDebugCounterService.CTR_MDATA_ERROR); - switchDisconnectHandshakeTimeout = - debugCounters.registerCounter( - prefix, "switch-disconnect-handshake-timeout", - "Number of times a switch was disconnected " + - "because it failed to complete the handshake " + - "in time.", - CounterType.ALWAYS_COUNT, - IDebugCounterService.CTR_MDATA_ERROR); - switchDisconnectIOError = - debugCounters.registerCounter( - prefix, "switch-disconnect-io-error", - "Number of times a switch was disconnected " + - "due to IO errors on the switch connection.", - CounterType.ALWAYS_COUNT, - IDebugCounterService.CTR_MDATA_ERROR); - switchDisconnectParseError = - debugCounters.registerCounter( - prefix, "switch-disconnect-parse-error", - "Number of times a switch was disconnected " + - "because it sent an invalid packet that could " + - "not be parsed", - CounterType.ALWAYS_COUNT, - IDebugCounterService.CTR_MDATA_ERROR); - - switchDisconnectSwitchStateException = - debugCounters.registerCounter( - prefix, "switch-disconnect-switch-state-exception", - "Number of times a switch was disconnected " + - "because it sent messages that were invalid " + - "given the switch connection's state.", - CounterType.ALWAYS_COUNT, - IDebugCounterService.CTR_MDATA_ERROR); - rejectedExecutionException = - debugCounters.registerCounter( - prefix, "rejected-execution-exception", - "TODO", - CounterType.ALWAYS_COUNT, - IDebugCounterService.CTR_MDATA_ERROR); - - switchDisconnectOtherException = - debugCounters.registerCounter( - prefix, "switch-disconnect-other-exception", - "Number of times a switch was disconnected " + - "due to an exceptional situation not covered " + - "by other counters", - CounterType.ALWAYS_COUNT, - IDebugCounterService.CTR_MDATA_ERROR); - - switchConnected = - debugCounters.registerCounter( - prefix, "switch-connected", - "Number of times a new switch connection was " + - "established", - CounterType.ALWAYS_COUNT); - - unhandledMessage = - debugCounters.registerCounter( - prefix, "unhandled-message", - "Number of times an OpenFlow message was " + - "received that the controller ignored because " + - "it was inapproriate given the switch " + - "connection's state.", - CounterType.ALWAYS_COUNT, - IDebugCounterService.CTR_MDATA_WARN); - // might be less than warning - - packetInWhileSwitchIsSlave = - debugCounters.registerCounter( - prefix, "packet-in-while-switch-is-slave", - "Number of times a packet in was received " + - "from a switch that was in SLAVE role. " + - "Possibly inidicates inconsistent roles.", - CounterType.ALWAYS_COUNT); - epermErrorWhileSwitchIsMaster = - debugCounters.registerCounter( - prefix, "eperm-error-while-switch-is-master", - "Number of times a permission error was " + - "received while the switch was in MASTER role. " + - "Possibly inidicates inconsistent roles.", - CounterType.ALWAYS_COUNT, - IDebugCounterService.CTR_MDATA_WARN); - - roleNotResentBecauseRolePending = - debugCounters.registerCounter( - prefix, "role-not-resent-because-role-pending", - "The controller tried to reestablish a role " + - "with a switch but did not do so because a " + - "previous role request was still pending", - CounterType.ALWAYS_COUNT); - roleRequestSent = - debugCounters.registerCounter( - prefix, "role-request-sent", - "Number of times the controller sent a role " + - "request to a switch.", - CounterType.ALWAYS_COUNT); - roleReplyTimeout = - debugCounters.registerCounter( - prefix, "role-reply-timeout", - "Number of times a role request message did not " + - "receive the expected reply from a switch", - CounterType.ALWAYS_COUNT, - IDebugCounterService.CTR_MDATA_WARN); - - roleReplyReceived = // expected RoleReply received - debugCounters.registerCounter( - prefix, "role-reply-received", - "Number of times the controller received the " + - "expected role reply message from a switch", - CounterType.ALWAYS_COUNT); - - roleReplyErrorUnsupported = - debugCounters.registerCounter( - prefix, "role-reply-error-unsupported", - "Number of times the controller received an " + - "error from a switch in response to a role " + - "request indicating that the switch does not " + - "support roles.", - CounterType.ALWAYS_COUNT); - - switchCounterRegistrationFailed = - debugCounters.registerCounter(prefix, - "switch-counter-registration-failed", - "Number of times the controller failed to " + - "register per-switch debug counters", - CounterType.ALWAYS_COUNT, - IDebugCounterService.CTR_MDATA_WARN); - } - } - - private Counters counters; - - Counters getCounters() { - return this.counters; - } - - /** - * A utility class to manage the <i>controller roles</i>. - * - * A utility class to manage the <i>controller roles</i> as opposed - * to the switch roles. The class manages the controllers current role, - * handles role change requests, and maintains the list of connected - * switch(-channel) so it can notify the switches of role changes. - * - * We need to ensure that every connected switch is always send the - * correct role. Therefore, switch add, sending of the intial role, and - * changing role need to use mutexes to ensure this. This has the ugly - * side-effect of requiring calls between controller and OFChannelHandler - * - * This class is fully thread safe. Its method can safely be called from - * any thread. - * - * @author gregor - * - */ - private class RoleManager { - // This role represents the role that has been set by setRole. This - // role might or might now have been notified to listeners just yet. - // This is updated by setRole. doSetRole() will use this value as - private Role role; - private String roleChangeDescription; - - // The current role info. This is updated /after/ dampening - // switches and - // listener notifications have been enqueued (but potentially before - // they have been dispatched) - private RoleInfo currentRoleInfo; - private final Set<OFChannelHandler> connectedChannelHandlers; - - /** - * @param role initial role - * @param roleChangeDescription initial value of the change description - * @throws NullPointerException if role or roleChangeDescription is null - * @throws IllegalArgumentException if role is EQUAL - */ - public RoleManager(Role role, String roleChangeDescription) { - if (role == null) - throw new NullPointerException("role must not be null"); - if (role == Role.EQUAL) - throw new IllegalArgumentException("role must not be EQUAL"); - if (roleChangeDescription == null) { - throw new NullPointerException("roleChangeDescription must " + - "not be null"); - } - - this.role = role; - this.roleChangeDescription = roleChangeDescription; - this.connectedChannelHandlers = new HashSet<OFChannelHandler>(); - this.currentRoleInfo = new RoleInfo(this.role, - this.roleChangeDescription, - new Date()); - } - - /** - * Add a newly connected OFChannelHandler. The channel handler is added - * we send the current role to the channel handler. All subsequent role - * changes will be send to all connected - * @param h The OFChannelHandler to add - */ - public synchronized void - addOFChannelHandlerAndSendRole(OFChannelHandler h) { - connectedChannelHandlers.add(h); - h.sendRoleRequest(this.role); - } - - /** - * Remove OFChannelHandler. E.g., due do disconnect. - * @param h The OFChannelHandler to remove. - */ - public synchronized void removeOFChannelHandler(OFChannelHandler h) { - connectedChannelHandlers.remove(h); - } - - /** - * Re-assert a role for the given channel handler. - * - * The caller specifies the role that should be reasserted. We only - * reassert the role if the controller's current role matches the - * reasserted role and there is no role request for the reasserted role - * pending. - * @param h The OFChannelHandler on which we should reassert. - * @param role The role to reassert - */ - public synchronized void reassertRole(OFChannelHandler h, Role role) { - // check if the requested reassertion actually makes sense - if (this.role != role) - return; - h.sendRoleRequestIfNotPending(this.role); - } - - /** - * Set the controller's new role and notify switches. - * - * This method updates the controllers current role and notifies all - * connected switches of the new role is different from the current - * role. We dampen calls to this method. See class description for - * details. - * - * @param role The new role. - * @param roleChangeDescription A textual description of why the role - * was changed. For information purposes only. - * @throws NullPointerException if role or roleChangeDescription is null - */ - public synchronized void setRole(Role role, String roleChangeDescription) { - if (role == null) - throw new NullPointerException("role must not be null"); - if (roleChangeDescription == null) { - throw new NullPointerException("roleChangeDescription must " + - "not be null"); - } - if (role == Role.EQUAL) { - counters.setRoleEqual.updateCounterWithFlush(); - log.debug("Received role request for EQUAL, setting to MASTER" - + " instead"); - role = Role.MASTER; - } - if (role == this.role) { - counters.setSameRole.updateCounterWithFlush(); - log.debug("Received role request for {} but controller is " - + "already {}. Ignoring it.", role, this.role); - return; - } - if (this.role == Role.MASTER && role == Role.SLAVE) { - log.info("Received role request to transition from MASTER to " - + " SLAVE (reason: {}). Terminating floodlight.", - roleChangeDescription); - System.exit(0); - } - - // At this point we are guaranteed that we will execute the code - // below exactly once during the lifetime of this process! And - // it will be a to MASTER transition - counters.setRoleMaster.updateCounterWithFlush(); - log.info("Received role request for {} (reason: {})." - + " Initiating transition", role, roleChangeDescription); - - this.role = role; - this.roleChangeDescription = roleChangeDescription; - - // TODO: we currently notify switches synchronously from the REST - // API handler. We could (should?) do this asynchronously. - currentRoleInfo = new RoleInfo(this.role, - this.roleChangeDescription, - new Date()); - Controller.this.switchManager.setRole(this.role); - for (OFChannelHandler h: connectedChannelHandlers) - h.sendRoleRequest(this.role); - - Controller.this.addUpdateToQueue(new HARoleUpdate(this.role)); - } - - /** - * Return the RoleInfo object describing the current role. - * - * Return the RoleInfo object describing the current role. The - * RoleInfo object is used by REST API users. We need to return - * a defensive copy. - * @return the current RoleInfo object - */ - public synchronized RoleInfo getRoleInfo() { - return new RoleInfo(currentRoleInfo); - } - } - - - /** - * This is a utility class to encapsulate code that deals with switch - * life cycles. It interacts with the sync store to read/write switches - * to/from the store and it maintains the switch maps. - * @author gregor - * - */ - private class SwitchManager implements IStoreListener<Long> { - private Role role; - private final ConcurrentHashMap<Long,IOFSwitch> activeSwitches; - private final ConcurrentHashMap<Long,IOFSwitch> syncedSwitches; - - public SwitchManager(Role role) { - this.role = role; - this.activeSwitches = new ConcurrentHashMap<Long, IOFSwitch>(); - this.syncedSwitches = new ConcurrentHashMap<Long, IOFSwitch>(); - } - - @Override - public void keysModified(Iterator<Long> keys, UpdateType type) { - if (type == UpdateType.LOCAL) { - // We only care for remote updates - return; - } - counters.remoteStoreNotification.updateCounterWithFlush(); - while(keys.hasNext()) { - Long key = keys.next(); - Versioned<SwitchSyncRepresentation> versionedSwitch = null; - try { - versionedSwitch = storeClient.get(key); - } catch (SyncException e) { - counters.storeSyncError.updateCounterWithFlush(); - log.error("Exception while retrieving switch " + - HexString.toHexString(key) + - " from sync store. Skipping", e); - continue; - } - if (log.isTraceEnabled()) { - log.trace("Reveiced switch store notification: key={}, " + - "entry={}", key, versionedSwitch.getValue()); - } - // versionedSwtich won't be null. storeClient.get() always - // returns a non-null or throws an exception - if (versionedSwitch.getValue() == null) { - switchRemovedFromStore(key); - continue; - } - SwitchSyncRepresentation storedSwitch = - versionedSwitch.getValue(); - IOFSwitch sw = getOFSwitchInstance(storedSwitch.getDescription()); - sw.setFeaturesReply(storedSwitch.getFeaturesReply()); - if (!key.equals(storedSwitch.getFeaturesReply().getDatapathId())) { - counters.storeSyncError.updateCounterWithFlush(); - log.error("Inconsistent DPIDs from switch sync store: " + - "key is {} but sw.getId() says {}. Ignoring", - HexString.toHexString(key), sw.getStringId()); - continue; - } - switchAddedToStore(sw); - } - } - - - public synchronized void setRole(Role role) { - this.role = role; - Runnable consolidateStoreTask = new Runnable() { - @Override - public void run() { - consolidateStore(); - } - }; - if ((role == Role.MASTER) && - this.syncedSwitches.isEmpty()) - addUpdateToQueue(new ReadyForReconcileUpdate()); - - Controller.this.ses.schedule(consolidateStoreTask, - consolidateStoreTimeDelayMs, - TimeUnit.MILLISECONDS); - } - - @LogMessageDocs({ - @LogMessageDoc(level="ERROR", - message="Switch {switch} activated but was already active", - explanation="A switch that was already activated was " + - "activated again. This should not happen.", - recommendation=LogMessageDoc.REPORT_CONTROLLER_BUG - ), - @LogMessageDoc(level="WARN", - message="New switch added {switch} for already-added switch {switch}", - explanation="A switch with the same DPID as another switch " + - "connected to the controller. This can be caused by " + - "multiple switches configured with the same DPID, or " + - "by a switch reconnected very quickly after " + - "disconnecting.", - recommendation="If this happens repeatedly, it is likely there " + - "are switches with duplicate DPIDs on the network. " + - "Reconfigure the appropriate switches. If it happens " + - "very rarely, then it is likely this is a transient " + - "network problem that can be ignored." - ) - }) - /** - * Called when a switch is activated, i.e., when it enters master - * role relative to this controller. - * @param sw - */ - public synchronized void switchActivated(IOFSwitch sw) { - if (role != Role.MASTER) { - counters.invalidSwitchActivatedWhileSlave.updateCounterWithFlush(); - return; // only react to switch connections when master - // FIXME: should we disconnect the switch? When can this happen? - } - Long dpid = sw.getId(); - counters.switchActivated.updateCounterWithFlush(); - IOFSwitch oldSw = this.activeSwitches.put(dpid, sw); - // Update event history - evSwitch.updateEventWithFlush(new SwitchEvent(dpid, "connected")); - - if (oldSw == sw) { - // Note == for object equality, not .equals for value - // TODO: should we wipe the flow table if - // alwaysClearFlowsOnSwAdd is set? OTOH this case should - // really never happen. - counters.errorSameSwitchReactivated.updateCounterWithFlush(); - log.error("Switch {} activated but was already active", sw); - addSwitchToStore(sw); - return; - } - - if (oldSw != null) { - // This happens either when we have switches with duplicate - // DPIDs or when a switch reconnects before we saw the - // disconnect - counters.switchWithSameDpidActivated.updateCounterWithFlush(); - log.warn("New switch added {} for already-added switch {}", - sw, oldSw); - // We need to disconnect and remove the old switch - // TODO: we notify switch listeners that the switch has been - // removed and then we notify them that the new one has been - // added. One could argue that a switchChanged notification - // might be more appropriate in this case.... - oldSw.cancelAllStatisticsReplies(); - addUpdateToQueue(new SwitchUpdate(dpid, - SwitchUpdateType.REMOVED)); - oldSw.disconnectOutputStream(); - // Add the new switch and clear FlowMods - // TODO: if this is the same switch re-connecting rather than - // a DPID collision it would make sense to not wipe the flow - // table. - sw.clearAllFlowMods(); - addUpdateToQueue(new SwitchUpdate(dpid, - SwitchUpdateType.ADDED)); - addUpdateToQueue(new SwitchUpdate(dpid, - SwitchUpdateType.ACTIVATED)); - addSwitchToStore(sw); - return; - } - - IOFSwitch storedSwitch = this.syncedSwitches.remove(sw.getId()); - if (storedSwitch == null) { - // The switch isn't known to the controller cluster. We - // need to send a switchAdded notification and clear all - // flows. - if (!swConnectCache.update(sw.getId())) - sw.clearAllFlowMods(); - addUpdateToQueue(new SwitchUpdate(dpid, - SwitchUpdateType.ADDED)); - addUpdateToQueue(new SwitchUpdate(dpid, - SwitchUpdateType.ACTIVATED)); - counters.newSwitchActivated.updateCounterWithFlush(); - } else { - // FIXME: switch was in store. check if ports or anything else - // has changed and send update. - if (alwaysClearFlowsOnSwActivate) { - sw.clearAllFlowMods(); - } - if (sw.attributeEquals(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE, true)) { - // We have a stored switch and the newly activated switch - // supports roles. This indicates that the switch was - // previously connected as slave. Since we don't update - // ports while slave, we need to set the ports on the - // new switch from the ports on the stored switch - // No need to send notifications, since we've dispatched - // them as we receive them from the store - sw.setPorts(storedSwitch.getPorts()); - } - addUpdateToQueue(new SwitchUpdate(dpid, - SwitchUpdateType.ACTIVATED)); - sendNotificationsIfSwitchDiffers(storedSwitch, sw); - counters.syncedSwitchActivated.updateCounterWithFlush(); - if (this.syncedSwitches.isEmpty()) { - // we have just activated the last synced switch. I.e., - // all previously known switch are now active. Send - // notification - // update dispatcher will increment counter - addUpdateToQueue(new ReadyForReconcileUpdate()); - } - } - addSwitchToStore(sw); - } - - /** - * Called when ports on the given switch have changed. Writes the - * updated switch to the sync store and queues a switch notification - * to listeners - * @param sw - */ - public synchronized void switchPortsChanged(IOFSwitch sw, - ImmutablePort port, - PortChangeType type) { - if (role != Role.MASTER) { - counters.invalidPortsChanged.updateCounterWithFlush(); - return; - } - if (!this.activeSwitches.containsKey(sw.getId())) { - counters.invalidPortsChanged.updateCounterWithFlush(); - return; - } - // update switch in store - addSwitchToStore(sw); - // no need to count here. SwitchUpdate.dispatch will count - // the portchanged - SwitchUpdate update = new SwitchUpdate(sw.getId(), - SwitchUpdateType.PORTCHANGED, - port, type); - addUpdateToQueue(update); - } - - /** - * Called when we receive a store notification about a new or updated - * switch. - * @param sw - */ - private synchronized void switchAddedToStore(IOFSwitch sw) { - if (role != Role.SLAVE) { - counters.invalidStoreEventWhileMaster.updateCounterWithFlush(); - return; // only read from store if slave - } - Long dpid = sw.getId(); - - IOFSwitch oldSw = syncedSwitches.put(dpid, sw); - if (oldSw == null) { - counters.newSwitchFromStore.updateCounterWithFlush(); - addUpdateToQueue(new SwitchUpdate(dpid, SwitchUpdateType.ADDED)); - } else { - // The switch already exists in storage, see if anything - // has changed - sendNotificationsIfSwitchDiffers(oldSw, sw); - counters.updatedSwitchFromStore.updateCounterWithFlush(); - } - } - - /** - * Called when we receive a store notification about a switch that - * has been removed from the sync store - * @param dpid - */ - private synchronized void switchRemovedFromStore(long dpid) { - if (role != Role.SLAVE) { - counters.invalidStoreEventWhileMaster.updateCounterWithFlush(); - return; // only read from store if slave - } - IOFSwitch oldSw = syncedSwitches.remove(dpid); - if (oldSw != null) { - counters.syncedSwitchRemoved.updateCounterWithFlush(); - addUpdateToQueue(new SwitchUpdate(dpid, - SwitchUpdateType.REMOVED)); - } else { - // TODO: the switch was deleted (tombstone) before we ever - // knew about it (or was deleted repeatedly). Can this - // happen? When/how? - counters.unknownSwitchRemovedFromStore.updateCounterWithFlush(); - } - } - - public synchronized void switchDeactivated(IOFSwitch sw) { - // ignore. we don't handle MASTER -> SLAVE transitions. We - // expect a restart - } - - /** - * Called when a switch disconnects - * @param sw - */ - public synchronized void switchDisconnected(IOFSwitch sw) { - if (role == Role.SLAVE) { - counters.switchDisconnectedWhileSlave.updateCounterWithFlush(); - return; // only react to switch connections when master - } - long dpid = sw.getId(); - // Update event history - // TODO: this is asymmetric with respect to connect event - // in switchActivated(). Should we have events on the - // slave as well? - evSwitch.updateEventWithFlush(new SwitchEvent(dpid, "disconnected")); - counters.switchDisconnected.updateCounterWithFlush(); - IOFSwitch oldSw = this.activeSwitches.get(dpid); - if (oldSw != sw) { - // This can happen if the disconnected switch was inactive - // (SLAVE) then oldSw==null. Or if we previously had the - // "added switch for already added switch case". - // Either way we shouldn't notify or do anything else - log.debug("removeSwitch called for switch {} but have {} in" - + " activeSwitches map. Ignoring", sw, oldSw); - return; - } - log.debug("removeSwitch {}", sw); - swConnectCache.update(sw.getId()); - this.activeSwitches.remove(sw.getId()); - removeSwitchFromStore(sw.getId()); - // We cancel all outstanding statistics replies if the switch transition - // from active. In the future we might allow statistics requests - // from slave controllers. Then we need to move this cancelation - // to switch disconnect - sw.cancelAllStatisticsReplies(); - addUpdateToQueue(new SwitchUpdate(sw.getId(), - SwitchUpdateType.REMOVED)); - } - - /** - * Write the given switch to the sync store. - * @param sw - */ - private synchronized void addSwitchToStore(IOFSwitch sw) { - // Add to store - // FIXME: do we need to use a put that takes a versioned here? - // need to verify - try { - storeClient.put(sw.getId(), new SwitchSyncRepresentation(sw)); - } catch (ObsoleteVersionException e) { - // FIXME: what's the right behavior here. Can the store client - // even throw this error? Should not since all local store - // access is synchronized - } catch (SyncException e) { - counters.storeSyncError.updateCounterWithFlush(); - log.error("Could not write switch " + sw.getStringId() + - " to sync store:", e); - } + public void switchChanged(DatapathId switchId) { } - - /** - * Write the given switch to the sync store if it's not already - * there - * TODO: should this be merged with addSwitchToStore - * @param sw - * @return true if the switch was absent, false otherwise - */ - private synchronized boolean addSwitchToStoreIfAbsent(IOFSwitch sw) { - try { - Versioned<SwitchSyncRepresentation> versionedSSr = - storeClient.get(sw.getId()); - if (versionedSSr.getValue() == null) { - // switch is absent - versionedSSr.setValue(new SwitchSyncRepresentation(sw)); - storeClient.put(sw.getId(), versionedSSr); - return true; - } else { - return false; - } - } catch (ObsoleteVersionException e) { - // FIXME: what's the right behavior here. Can the store client - // even throw this error? Should not since all local store - // access is synchronized - } catch (SyncException e) { - counters.storeSyncError.updateCounterWithFlush(); - log.error("Could not write switch " + sw.getStringId() + - " to sync store:", e); - } - return false; - } - - /** - * Remove the given switch from the sync store. - * @param dpid - */ - private synchronized void removeSwitchFromStore(long dpid) { - try { - storeClient.delete(dpid); - } catch (SyncException e) { - counters.storeSyncError.updateCounterWithFlush(); - // ObsoleteVerisonException can't happend because all - // store modifications are synchronized - log.error("Could not remove switch " + - HexString.toHexString(dpid) + - " from sync store:", e); - } - } - - /** - * Check if the two switches differ in their ports or in other - * fields and if they differ enqueue a switch update - * @param oldSw - * @param newSw - */ - private synchronized void - sendNotificationsIfSwitchDiffers(IOFSwitch oldSw, - IOFSwitch newSw) { - Collection<PortChangeEvent> portDiffs = - oldSw.comparePorts(newSw.getPorts()); - for (PortChangeEvent ev: portDiffs) { - SwitchUpdate update = - new SwitchUpdate(newSw.getId(), - SwitchUpdateType.PORTCHANGED, - ev.port, ev.type); - addUpdateToQueue(update); - } - } - /** - * Remove all entries from the store that don't correspond to an - * active switch. - * TODO: is it a problem that this is fully synchronized - */ - private synchronized void consolidateStore() { - if (role == Role.SLAVE) - return; - boolean shouldNotifyReadyForReconcile = false; - counters.consolidateStoreRunCount.updateCounterWithFlush(); - log.info("Consolidating synced switches after MASTER transition"); - IClosableIterator<Map.Entry<Long,Versioned<SwitchSyncRepresentation>>> - iter = null; - try { - iter = storeClient.entries(); - } catch (SyncException e) { - counters.storeSyncError.updateCounterWithFlush(); - log.error("Failed to read switches from sync store", e); - return; - } - try { - while(iter.hasNext()) { - Entry<Long, Versioned<SwitchSyncRepresentation>> entry = - iter.next(); - if (!this.activeSwitches.containsKey(entry.getKey())) { - removeSwitchFromStore(entry.getKey()); - if (this.syncedSwitches.remove(entry.getKey()) != null) { - // a switch that's in the store and in synced - // switches but that is not active. I.e., a - // switch known to the old master that hasn't - // reconnected to this controller. - counters.switchesNotReconnectingToNewMaster - .updateCounterWithFlush(); - shouldNotifyReadyForReconcile = true; - addUpdateToQueue(new SwitchUpdate(entry.getKey(), - SwitchUpdateType.REMOVED)); - } else { - // A switch was in the store but it's neither in - // activeSwitches nor syncedSwitches. This could - // happen if the old Master has added this entry - // to the store after this controller has - // stopped reacting to store notifications (due - // to MASTER transition) - counters.consolidateStoreInconsistencies - .updateCounterWithFlush(); - } - } - } - } finally { - if (iter != null) - iter.close(); - } - // In general, syncedSwitches should now be empty. However, - // the old Master could have removed a switch from the store - // after this controller has stopped reacting to store - // notification (because it's now MASTER). We need to remove - // these switches. - Iterator<Long> it = this.syncedSwitches.keySet().iterator(); - while (it.hasNext()) { - counters.switchesNotReconnectingToNewMaster.updateCounterWithFlush(); - counters.consolidateStoreInconsistencies.updateCounterWithFlush(); - Long dpid = it.next(); - shouldNotifyReadyForReconcile = true; - addUpdateToQueue(new SwitchUpdate(dpid, - SwitchUpdateType.REMOVED)); - it.remove(); - } - if (shouldNotifyReadyForReconcile) { - // at least one previously known switch has been removed. - addUpdateToQueue(new ReadyForReconcileUpdate()); - } - - // FIXME: do we need this final check here. - // Now iterate through all active switches and determine if - // any of them are missing from the sync store. This can only - // happen if another controller has removed them (because we know - // that we have written them to the store). - for (IOFSwitch sw: this.activeSwitches.values()) { - if (addSwitchToStoreIfAbsent(sw)) - counters.consolidateStoreInconsistencies.updateCounterWithFlush(); - } - } - - // FIXME: remove this method - public Map<Long,IOFSwitch> getAllSwitchMap() { - // this.syncedSwitches will be empty after the master transition - Map<Long,IOFSwitch> switches = - new HashMap<Long, IOFSwitch>(this.syncedSwitches); - if (this.role != Role.SLAVE) - switches.putAll(this.activeSwitches); - return switches; - } - - public Set<Long> getAllSwitchDpids() { - // this.syncedSwitches will be empty after the master transition - Set<Long> dpids = new HashSet<Long>(this.syncedSwitches.keySet()); - if (this.role != Role.SLAVE) - dpids.addAll(this.activeSwitches.keySet()); - return dpids; - } - - public IOFSwitch getSwitch(long dpid) { - if (this.role == Role.SLAVE) - return this.syncedSwitches.get(dpid); - // MASTER: if the switch is found in the active map return - // otherwise look up the switch in the bigSync map. The bigSync map - // wil be cleared after the transition is complete. - IOFSwitch sw = this.activeSwitches.get(dpid); - if (sw != null) - return sw; - return this.syncedSwitches.get(dpid); - } - - public void addSwitchEvent(long dpid, String reason, boolean flushNow) { - if (flushNow) - evSwitch.updateEventWithFlush(new SwitchEvent(dpid, reason)); - else - evSwitch.updateEventNoFlush(new SwitchEvent(dpid, reason)); - } - } - - + /** * Updates handled by the main loop */ - interface IUpdate { + public interface IUpdate { /** * Calls the appropriate listeners */ public void dispatch(); } - - /** - * Update message that indicates that the controller can now start - * flow reconciliation after a SLAVE->MASTER transition - */ - private class ReadyForReconcileUpdate implements IUpdate { - @Override - public void dispatch() { - counters.readyForReconcile.updateCounterWithFlush(); - if (readyForReconcileListeners != null) { - for (IReadyForReconcileListener listener: - readyForReconcileListeners) { - listener.readyForReconcile(); - } - } - } - } - - enum SwitchUpdateType { - ADDED, - REMOVED, - ACTIVATED, - DEACTIVATED, - PORTCHANGED, - OTHERCHANGE - } - /** - * Update message indicating a switch was added or removed - */ - private class SwitchUpdate implements IUpdate { - private final long swId; - private final SwitchUpdateType switchUpdateType; - private final ImmutablePort port; - private final PortChangeType changeType; - - - public SwitchUpdate(long swId, SwitchUpdateType switchUpdateType) { - this(swId, switchUpdateType, null, null); - } - public SwitchUpdate(long swId, - SwitchUpdateType switchUpdateType, - ImmutablePort port, - PortChangeType changeType) { - if (switchUpdateType == SwitchUpdateType.PORTCHANGED) { - if (port == null) { - throw new NullPointerException("Port must not be null " + - "for PORTCHANGED updates"); - } - if (changeType == null) { - throw new NullPointerException("ChangeType must not be " + - "null for PORTCHANGED updates"); - } - } else { - if (port != null || changeType != null) { - throw new IllegalArgumentException("port and changeType " + - "must be null for " + switchUpdateType + - " updates"); - } - } - this.swId = swId; - this.switchUpdateType = switchUpdateType; - this.port = port; - this.changeType = changeType; - } - @Override - public void dispatch() { - if (log.isTraceEnabled()) { - log.trace("Dispatching switch update {} {}", - HexString.toHexString(swId), switchUpdateType); - } - if (switchListeners != null) { - for (IOFSwitchListener listener : switchListeners) { - switch(switchUpdateType) { - case ADDED: - // don't count here. We have more specific - // counters before the update is created - listener.switchAdded(swId); - break; - case REMOVED: - // don't count here. We have more specific - // counters before the update is created - listener.switchRemoved(swId); - break; - case PORTCHANGED: - counters.switchPortChanged.updateCounterWithFlush(); - listener.switchPortChanged(swId, port, changeType); - break; - case ACTIVATED: - // don't count here. We have more specific - // counters before the update is created - listener.switchActivated(swId); - break; - case DEACTIVATED: - // ignore - break; - case OTHERCHANGE: - counters.switchOtherChange.updateCounterWithFlush(); - listener.switchChanged(swId); - break; - } - } - } - } - } - - /** - * Update message indicating controller's role has changed. - * RoleManager, which enqueues these updates gurantees that we will - * only have a single transition from SLAVE to MASTER. - */ - private class HARoleUpdate implements IUpdate { - private final Role newRole; - public HARoleUpdate(Role newRole) { - if (newRole != Role.MASTER) - throw new IllegalArgumentException("Only legal role change is" - + "to MASTER. Got to " - + newRole); - this.newRole = newRole; - } - @Override - public void dispatch() { - if (log.isDebugEnabled()) { - log.debug("Dispatching HA Role update newRole = {}", - newRole); - } - for (IHAListener listener : haListeners.getOrderedListeners()) { - if (log.isTraceEnabled()) { - log.trace("Calling HAListener {} with transitionToMaster", - listener.getName()); - } - listener.transitionToMaster(); - } - if (newRole != Role.SLAVE) { - Controller.this.notifiedRole = newRole; - } - } - } - + + //TODO @Ryan ReadyForReconcileUpdate was here. Obsolete? + /** * Update message indicating * IPs of controllers in controller cluster have changed. @@ -1612,51 +292,67 @@ public class Controller implements IFloodlightProviderService, // *************** void setStorageSourceService(IStorageSourceService storageSource) { - this.storageSource = storageSource; + this.storageSourceService = storageSource; } IStorageSourceService getStorageSourceService() { - return this.storageSource; + return this.storageSourceService; } - - void setCounterStore(ICounterStoreService counterStore) { - this.counterStore = counterStore; + + IShutdownService getShutdownService() { + return this.shutdownService; } - - void setDebugCounter(IDebugCounterService debugCounters) { - this.debugCounters = debugCounters; + + void setShutdownService(IShutdownService shutdownService) { + this.shutdownService = shutdownService; } public void setDebugEvent(IDebugEventService debugEvent) { - this.debugEvents = debugEvent; + this.debugEventService = debugEvent; + } + + void setDebugCounter(IDebugCounterService debugCounters) { + this.debugCounterService = debugCounters; } IDebugCounterService getDebugCounter() { - return this.debugCounters; + return this.debugCounterService; } void setSyncService(ISyncService syncService) { this.syncService = syncService; } void setPktInProcessingService(IPktInProcessingTimeService pits) { - this.pktinProcTime = pits; + this.pktinProcTimeService = pits; } - + void setRestApiService(IRestApiService restApi) { - this.restApi = restApi; + this.restApiService = restApi; } + void setThreadPoolService(IThreadPoolService tp) { - this.threadPool = tp; + this.threadPoolService = tp; } - IThreadPoolService getThreadPoolService() { - return this.threadPool; + return this.threadPoolService; + } + + + public void setSwitchService(IOFSwitchService switchService) { + this.switchService = switchService; + } + public IOFSwitchService getSwitchService() { + return this.switchService; } @Override - public Role getRole() { - // FIXME: + public int getWorkerThreads() { + return this.workerThreads; + } + + @Override + public HARole getRole() { return notifiedRole; } @@ -1666,39 +362,15 @@ public class Controller implements IFloodlightProviderService, } @Override - public void setRole(Role role, String roleChangeDescription) { - roleManager.setRole(role, roleChangeDescription); + public void setRole(HARole role, String changeDescription) { + roleManager.setRole(role, changeDescription); } // **************** // Message handlers // **************** - - /** - * Indicates that ports on the given switch have changed. Enqueue a - * switch update. - * @param sw - */ - void notifyPortChanged(IOFSwitch sw, - ImmutablePort port, - PortChangeType changeType) { - if (sw == null) { - String msg = String.format("Switch must not be null. " + - "port=%s, changeType=%s", port, changeType); - throw new NullPointerException(msg); - } - if (port == null) { - String msg = String.format("Port must not be null. " + - "switch=%s, changeType=%s", sw, changeType); - throw new NullPointerException(msg); - } - if (changeType == null) { - String msg = String.format("ChangeType must not be null. " + - "switch=%s, port=%s", sw, port); - throw new NullPointerException(msg); - } - this.switchManager.switchPortsChanged(sw, port, changeType); - } + + // Handler for SwitchPortsChanged was here (notifyPortChanged). Handled in OFSwitchManager /** * flcontext_cache - Keep a thread local stack of contexts @@ -1765,33 +437,33 @@ public class Controller implements IFloodlightProviderService, explanation="The switch sent a message not handled by " + "the controller") }) - protected void handleMessage(IOFSwitch sw, OFMessage m, - FloodlightContext bContext) - throws IOException { + @SuppressFBWarnings(value="SF_SWITCH_NO_DEFAULT", + justification="False positive -- has default") + @Override + public void handleMessage(IOFSwitch sw, OFMessage m, + FloodlightContext bContext) { Ethernet eth = null; - - if (this.notifiedRole == Role.SLAVE) { - counters.dispatchMessageWhileSlave.updateCounterNoFlush(); + log.debug("~~~~~~~~GOT PACKET IN MESSAGE FROM SWITCH~~~~~~~~~"); + if (this.notifiedRole == HARole.STANDBY) { + counters.dispatchMessageWhileStandby.increment(); // We are SLAVE. Do not dispatch messages to listeners. return; } - counters.dispatchMessage.updateCounterNoFlush(); + counters.dispatchMessage.increment(); switch (m.getType()) { case PACKET_IN: + counters.packetIn.increment(); OFPacketIn pi = (OFPacketIn)m; - if (pi.getPacketData().length <= 0) { - log.error("Ignoring PacketIn (Xid = " + pi.getXid() + - ") because the data field is empty."); + if (pi.getData().length <= 0) { + log.error("Ignoring PacketIn (Xid = " + pi.getXid() + ") because the data field is empty."); return; } if (Controller.ALWAYS_DECODE_ETH) { eth = new Ethernet(); - eth.deserialize(pi.getPacketData(), 0, - pi.getPacketData().length); - counterStore.updatePacketInCountersLocal(sw, m, eth); + eth.deserialize(pi.getData(), 0, pi.getData().length); } // fall through to default case... @@ -1799,8 +471,7 @@ public class Controller implements IFloodlightProviderService, List<IOFMessageListener> listeners = null; if (messageListeners.containsKey(m.getType())) { - listeners = messageListeners.get(m.getType()). - getOrderedListeners(); + listeners = messageListeners.get(m.getType()).getOrderedListeners(); } FloodlightContext bc = null; @@ -1822,18 +493,19 @@ public class Controller implements IFloodlightProviderService, // Get the starting time (overall and per-component) of // the processing chain for this packet if performance // monitoring is turned on - pktinProcTime.recordStartTimePktIn(); + pktinProcTimeService.bootstrap(listeners); + pktinProcTimeService.recordStartTimePktIn(); Command cmd; for (IOFMessageListener listener : listeners) { - pktinProcTime.recordStartTimeComp(listener); + pktinProcTimeService.recordStartTimeComp(listener); cmd = listener.receive(sw, m, bc); - pktinProcTime.recordEndTimeComp(listener); + pktinProcTimeService.recordEndTimeComp(listener); if (Command.STOP.equals(cmd)) { break; } } - pktinProcTime.recordEndTimePktIn(sw, m, bc); + pktinProcTimeService.recordEndTimePktIn(sw, m, bc); } else { if (m.getType() != OFType.BARRIER_REPLY) log.warn("Unhandled OF Message: {} from {}", m, sw); @@ -1844,77 +516,36 @@ public class Controller implements IFloodlightProviderService, if ((bContext == null) && (bc != null)) flcontext_free(bc); } } - - void switchActivated(IOFSwitch sw) { - this.switchManager.switchActivated(sw); - } - - void switchDeactivated(IOFSwitch sw) { - this.switchManager.switchDeactivated(sw); - } - - void switchDisconnected(IOFSwitch sw) { - this.switchManager.switchDisconnected(sw); - } - + // *************** // IFloodlightProvider // *************** - /** - * Forward to RoleManager - * @param h - */ - void addSwitchChannelAndSendInitialRole(OFChannelHandler h) { - roleManager.addOFChannelHandlerAndSendRole(h); - } - /** * Forwards to RoleManager - * @param h - */ - void removeSwitchChannel(OFChannelHandler h) { - roleManager.removeOFChannelHandler(h); - } - - /** - * Forwards to RoleManager - * @param h + * @param ofSwitchHandshakeHandler * @param role */ - void reassertRole(OFChannelHandler h, Role role) { - roleManager.reassertRole(h, role); + void reassertRole(OFSwitchHandshakeHandler ofSwitchHandshakeHandler, HARole role) { + roleManager.reassertRole(ofSwitchHandshakeHandler, role); } - // FIXME: remove this method @Override - public Map<Long,IOFSwitch> getAllSwitchMap() { - return this.switchManager.getAllSwitchMap(); + public String getControllerId() { + return controllerId; } - + @Override - public Set<Long> getAllSwitchDpids() { - return this.switchManager.getAllSwitchDpids(); + public String getOFHostname() { + return openFlowHostname; } - - @Override - public IOFSwitch getSwitch(long dpid) { - return this.switchManager.getSwitch(dpid); - } - - @Override - public void addOFSwitchListener(IOFSwitchListener listener) { - this.switchListeners.add(listener); - } - @Override - public void removeOFSwitchListener(IOFSwitchListener listener) { - this.switchListeners.remove(listener); + public int getOFPort() { + return openFlowPort; } @Override - public synchronized void addOFMessageListener(OFType type, - IOFMessageListener listener) { + public synchronized void addOFMessageListener(OFType type, IOFMessageListener listener) { ListenerDispatcher<OFType, IOFMessageListener> ldd = messageListeners.get(type); if (ldd == null) { @@ -1925,8 +556,7 @@ public class Controller implements IFloodlightProviderService, } @Override - public synchronized void removeOFMessageListener(OFType type, - IOFMessageListener listener) { + public synchronized void removeOFMessageListener(OFType type, IOFMessageListener listener) { ListenerDispatcher<OFType, IOFMessageListener> ldd = messageListeners.get(type); if (ldd != null) { @@ -1935,14 +565,9 @@ public class Controller implements IFloodlightProviderService, } private void logListeners() { - for (Map.Entry<OFType, - ListenerDispatcher<OFType, - IOFMessageListener>> entry - : messageListeners.entrySet()) { - + for (Map.Entry<OFType, ListenerDispatcher<OFType, IOFMessageListener>> entry : messageListeners.entrySet()) { OFType type = entry.getKey(); - ListenerDispatcher<OFType, IOFMessageListener> ldd = - entry.getValue(); + ListenerDispatcher<OFType, IOFMessageListener> ldd = entry.getValue(); StringBuilder sb = new StringBuilder(); sb.append("OFListeners for "); @@ -1972,76 +597,25 @@ public class Controller implements IFloodlightProviderService, public Map<OFType, List<IOFMessageListener>> getListeners() { Map<OFType, List<IOFMessageListener>> lers = new HashMap<OFType, List<IOFMessageListener>>(); - for(Entry<OFType, ListenerDispatcher<OFType, IOFMessageListener>> e : - messageListeners.entrySet()) { + for(Entry<OFType, ListenerDispatcher<OFType, IOFMessageListener>> e : messageListeners.entrySet()) { lers.put(e.getKey(), e.getValue().getOrderedListeners()); } return Collections.unmodifiableMap(lers); } @Override - @LogMessageDocs({ - @LogMessageDoc(level="ERROR", - message="Error reinjecting OFMessage on switch {switch}", - explanation="An I/O error occured while attempting to " + - "process an OpenFlow message", - recommendation=LogMessageDoc.CHECK_SWITCH) - }) - public boolean injectOfMessage(IOFSwitch sw, OFMessage msg, - FloodlightContext bc) { - if (sw == null) - throw new NullPointerException("Switch must not be null"); - if (msg == null) - throw new NullPointerException("OFMessage must not be null"); - - // FIXME: Do we need to be able to inject messages from switches - // where we're the slave controller (i.e. they're connected but - // not active)? - if (!sw.isActive()) return false; - - try { - // Pass Floodlight context to the handleMessages() - handleMessage(sw, msg, bc); - } catch (IOException e) { - log.error("Error reinjecting OFMessage on switch {}", - sw.getStringId()); - return false; - } - return true; - } - - @Override - @LogMessageDoc(message="Calling System.exit", - explanation="The controller is terminating") - public synchronized void terminate() { - log.info("Calling System.exit"); - System.exit(1); - } - - @Override - public boolean injectOfMessage(IOFSwitch sw, OFMessage msg) { - // call the overloaded version with floodlight context set to null - return injectOfMessage(sw, msg, null); - } - - @Override - public void handleOutgoingMessage(IOFSwitch sw, OFMessage m, - FloodlightContext bc) { + public void handleOutgoingMessage(IOFSwitch sw, OFMessage m) { if (sw == null) throw new NullPointerException("Switch must not be null"); if (m == null) throw new NullPointerException("OFMessage must not be null"); - if (bc == null) - bc = new FloodlightContext(); - if (log.isTraceEnabled()) { - String str = OFMessage.getDataAsString(sw, m, bc); - log.trace("{}", str); - } + + // FIXME floodlight context not supported any more + FloodlightContext bc = new FloodlightContext(); List<IOFMessageListener> listeners = null; if (messageListeners.containsKey(m.getType())) { - listeners = - messageListeners.get(m.getType()).getOrderedListeners(); + listeners = messageListeners.get(m.getType()).getOrderedListeners(); } if (listeners != null) { @@ -2053,16 +627,10 @@ public class Controller implements IFloodlightProviderService, } } - @Override - public BasicFactory getOFMessageFactory() { - return factory; - } - // ************** // Initialization // ************** - /** * Sets the initial role based on properties in the config params. * It looks for two different properties. @@ -2092,42 +660,19 @@ public class Controller implements IFloodlightProviderService, "properties file", recommendation=LogMessageDoc.CHECK_CONTROLLER) }) - protected Role getInitialRole(Map<String, String> configParams) { - Role role = Role.MASTER; + protected HARole getInitialRole(Map<String, String> configParams) { + HARole role = HARole.STANDBY; String roleString = configParams.get("role"); - if (roleString == null) { - String rolePath = configParams.get("rolepath"); - if (rolePath != null) { - Properties properties = new Properties(); - try { - properties.load(new FileInputStream(rolePath)); - roleString = properties.getProperty("floodlight.role"); - } - catch (IOException exc) { - // Don't treat it as an error if the file specified by the - // rolepath property doesn't exist. This lets us enable the - // HA mechanism by just creating/setting the floodlight.role - // property in that file without having to modify the - // floodlight properties. - } - } - } - if (roleString != null) { - // Canonicalize the string to the form used for the enum constants - roleString = roleString.trim().toUpperCase(); try { - role = Role.valueOf(roleString); + role = HARole.valueOfBackwardsCompatible(roleString); } catch (IllegalArgumentException exc) { log.error("Invalid current role value: {}", roleString); } } - if (role == Role.EQUAL) - role = Role.MASTER; log.info("Controller role set to {}", role); - return role; } @@ -2150,34 +695,12 @@ public class Controller implements IFloodlightProviderService, recommendation=LogMessageDoc.GENERIC_ACTION) }) public void run() { + this.moduleLoaderState = ModuleLoaderState.COMPLETE; + if (log.isDebugEnabled()) { logListeners(); } - try { - final ServerBootstrap bootstrap = createServerBootStrap(); - - bootstrap.setOption("reuseAddr", true); - bootstrap.setOption("child.keepAlive", true); - bootstrap.setOption("child.tcpNoDelay", true); - bootstrap.setOption("child.sendBufferSize", Controller.SEND_BUFFER_SIZE); - - ChannelPipelineFactory pfact = - new OpenflowPipelineFactory(this, null); - bootstrap.setPipelineFactory(pfact); - InetSocketAddress sa = - (openFlowHost == null) - ? new InetSocketAddress(openFlowPort) - : new InetSocketAddress(openFlowHost, openFlowPort); - final ChannelGroup cg = new DefaultChannelGroup(); - cg.add(bootstrap.bind(sa)); - - log.info("Listening for switch connections on {}", sa); - } catch (Exception e) { - throw new RuntimeException(e); - } - - // main loop while (true) { try { IUpdate update = updates.take(); @@ -2185,170 +708,113 @@ public class Controller implements IFloodlightProviderService, } catch (InterruptedException e) { log.error("Received interrupted exception in updates loop;" + "terminating process"); - terminate(); + log.info("Calling System.exit"); + System.exit(1); } catch (StorageException e) { log.error("Storage exception in controller " + "updates loop; terminating process", e); - terminate(); + log.info("Calling System.exit"); + System.exit(1); } catch (Exception e) { log.error("Exception in controller updates loop", e); } } } - - private ServerBootstrap createServerBootStrap() { - if (workerThreads == 0) { - return new ServerBootstrap( - new NioServerSocketChannelFactory( - Executors.newCachedThreadPool(), - Executors.newCachedThreadPool())); - } else { - return new ServerBootstrap( - new NioServerSocketChannelFactory( - Executors.newCachedThreadPool(), - Executors.newCachedThreadPool(), workerThreads)); + + private void setConfigParams(Map<String, String> configParams) throws FloodlightModuleException { + String ofPort = configParams.get("openflowPort"); + if (!Strings.isNullOrEmpty(ofPort)) { + try { + this.openFlowPort = Integer.parseInt(ofPort); + } catch (NumberFormatException e) { + log.error("invalid openflow port specifier", e); + throw new FloodlightModuleException("invalid port specifier in cofig"); + } + log.debug("OpenFlow port set to {}", this.openFlowPort); } - } - private void setConfigParams(Map<String, String> configParams) { - String ofPort = configParams.get("openflowport"); - if (ofPort != null) { - this.openFlowPort = Integer.parseInt(ofPort); - } - log.debug("OpenFlow port set to {}", this.openFlowPort); String threads = configParams.get("workerthreads"); - if (threads != null) { + if (!Strings.isNullOrEmpty(threads)) { this.workerThreads = Integer.parseInt(threads); } log.debug("Number of worker threads set to {}", this.workerThreads); - - } - - private void initVendorMessages() { - // Configure openflowj to be able to parse the role request/reply - // vendor messages. - OFNiciraVendorExtensions.initialize(); - - // Register the standard Vendor actions that we support - OFVendorActions.registerStandardVendorActions(); } /** * Initialize internal data structures */ - public void init(Map<String, String> configParams) { + public void init(Map<String, String> configParams) throws FloodlightModuleException { + + this.moduleLoaderState = ModuleLoaderState.INIT; + // These data structures are initialized here because other - // module's startUp() might be called before ours - this.messageListeners = - new ConcurrentHashMap<OFType, - ListenerDispatcher<OFType, - IOFMessageListener>>(); - this.switchListeners = new CopyOnWriteArraySet<IOFSwitchListener>(); - // add switch notification listener - this.addOFSwitchListener(new NotificationSwitchListener()); - this.readyForReconcileListeners = - new CopyOnWriteArraySet<IReadyForReconcileListener>(); - this.haListeners = - new ListenerDispatcher<HAListenerTypeMarker, IHAListener>(); - this.driverRegistry = new NaiiveSwitchDriverRegistry(); + // module's startUp() might be called before ours + this.messageListeners = new ConcurrentHashMap<OFType, ListenerDispatcher<OFType, IOFMessageListener>>(); + this.haListeners = new ListenerDispatcher<HAListenerTypeMarker, IHAListener>(); this.controllerNodeIPsCache = new HashMap<String, String>(); this.updates = new LinkedBlockingQueue<IUpdate>(); - this.factory = BasicFactory.getInstance(); this.providerMap = new HashMap<String, List<IInfoProvider>>(); + setConfigParams(configParams); - Role initialRole = getInitialRole(configParams); - this.notifiedRole = initialRole; - initVendorMessages(); - String option = configParams.get("flushSwitchesOnReconnect"); + HARole initialRole = getInitialRole(configParams); + this.notifiedRole = initialRole; + this.shutdownService = new ShutdownServiceImpl(); - if (option != null && option.equalsIgnoreCase("true")) { - this.setAlwaysClearFlowsOnSwActivate(true); - log.info("Flush switches on reconnect -- Enabled."); - } else { - this.setAlwaysClearFlowsOnSwActivate(false); - log.info("Flush switches on reconnect -- Disabled"); - } + this.roleManager = new RoleManager(this, this.shutdownService, + this.notifiedRole, + INITIAL_ROLE_CHANGE_DESCRIPTION); + this.timer = new HashedWheelTimer(); - uplinkPortPrefixSet = new HashSet<String>(); - uplinkPortPrefixSet.add("eth"); - uplinkPortPrefixSet.add("bond"); - String str = configParams.get("uplinkPortPrefix"); - if (str != null) { - List<String> items = Arrays.asList(str.split("\\s*,\\s*")); - if (items != null) { - for (String s: items) { - if (s.length() > 0) { - uplinkPortPrefixSet.add(s); - } - } - } - } + // Switch Service Startup + this.switchService.registerLogicalOFMessageCategory(LogicalOFMessageCategory.MAIN); + this.switchService.addOFSwitchListener(new NotificationSwitchListener()); - this.roleManager = new RoleManager(this.notifiedRole, - INITIAL_ROLE_CHANGE_DESCRIPTION); - this.switchManager = new SwitchManager(this.notifiedRole); - this.counters = new Counters(); - this.swConnectCache = - new TimedCache<Long>(100, 5*1000 ); // 5 seconds interval + this.counters = new ControllerCounters(debugCounterService); } /** * Startup all of the controller's components + * @param floodlightModuleLoader */ @LogMessageDoc(message="Waiting for storage source", explanation="The system database is not yet ready", recommendation="If this message persists, this indicates " + "that the system database has failed to start. " + LogMessageDoc.CHECK_CONTROLLER) - public void startupComponents() throws FloodlightModuleException { + public void startupComponents(FloodlightModuleLoader floodlightModuleLoader) throws FloodlightModuleException { + + this.moduleLoaderState = ModuleLoaderState.STARTUP; + // Create the table names we use - storageSource.createTable(CONTROLLER_TABLE_NAME, null); - storageSource.createTable(CONTROLLER_INTERFACE_TABLE_NAME, null); - storageSource.createTable(SWITCH_CONFIG_TABLE_NAME, null); - storageSource.setTablePrimaryKeyName(CONTROLLER_TABLE_NAME, - CONTROLLER_ID); - storageSource.addListener(CONTROLLER_INTERFACE_TABLE_NAME, this); - - storageSource.createTable(FLOW_PRIORITY_TABLE_NAME, null); - storageSource.setTablePrimaryKeyName(FLOW_PRIORITY_TABLE_NAME, - FLOW_COLUMN_PRIMARY_KEY); - storageSource.addListener(FLOW_PRIORITY_TABLE_NAME, this); + storageSourceService.createTable(CONTROLLER_TABLE_NAME, null); + storageSourceService.createTable(CONTROLLER_INTERFACE_TABLE_NAME, null); + storageSourceService.createTable(SWITCH_CONFIG_TABLE_NAME, null); + storageSourceService.setTablePrimaryKeyName(CONTROLLER_TABLE_NAME, CONTROLLER_ID); + storageSourceService.addListener(CONTROLLER_INTERFACE_TABLE_NAME, this); + + storageSourceService.createTable(FLOW_PRIORITY_TABLE_NAME, null); + storageSourceService.setTablePrimaryKeyName(FLOW_PRIORITY_TABLE_NAME, FLOW_COLUMN_PRIMARY_KEY); + storageSourceService.addListener(FLOW_PRIORITY_TABLE_NAME, this); readFlowPriorityConfigurationFromStorage(); - + // Startup load monitoring if (overload_drop) { - this.loadmonitor.startMonitoring( - this.threadPool.getScheduledExecutor()); + this.loadmonitor.startMonitoring(this.threadPoolService.getScheduledExecutor()); } // Add our REST API - restApi.addRestletRoutable(new CoreWebRoutable()); - - this.ses = threadPool.getScheduledExecutor(); - + restApiService.addRestletRoutable(new CoreWebRoutable()); + try { - this.syncService.registerStore(SWITCH_SYNC_STORE_NAME, Scope.LOCAL); - this.storeClient = this.syncService - .getStoreClient(SWITCH_SYNC_STORE_NAME, - Long.class, - SwitchSyncRepresentation.class); - this.storeClient.addStoreListener(this.switchManager); + this.syncService.registerStore(OFSwitchManager.SWITCH_SYNC_STORE_NAME, Scope.LOCAL); } catch (SyncException e) { throw new FloodlightModuleException("Error while setting up sync service", e); } - - try { - this.counters.createCounters(debugCounters); - } catch (CounterException e) { - throw new FloodlightModuleException(e.getMessage()); - } - + addInfoProvider("summary", this); - - registerControllerDebugEvents(); } - + @LogMessageDoc(level="ERROR", message="failed to access storage: {reason}", explanation="Could not retrieve forwarding configuration", @@ -2356,7 +822,7 @@ public class Controller implements IFloodlightProviderService, private void readFlowPriorityConfigurationFromStorage() { try { Map<String, Object> row; - IResultSet resultSet = storageSource.executeQuery( + IResultSet resultSet = storageSourceService.executeQuery( FLOW_PRIORITY_TABLE_NAME, FLOW_COLUMN_NAMES, null, null); if (resultSet == null) return; @@ -2367,20 +833,17 @@ public class Controller implements IFloodlightProviderService, String primary_key = (String) row.get(FLOW_COLUMN_PRIMARY_KEY); if (primary_key.equals(FLOW_VALUE_PRIMARY_KEY)) { if (row.containsKey(FLOW_COLUMN_ACCESS_PRIORITY)) { - accessPriority = - Short.valueOf((String) row.get(FLOW_COLUMN_ACCESS_PRIORITY)); + //TODO @Ryan delete? Not referenced anywhere DEFAULT_ACCESS_PRIORITY = Short.valueOf((String) row.get(FLOW_COLUMN_ACCESS_PRIORITY)); } if (row.containsKey(FLOW_COLUMN_CORE_PRIORITY)) { - corePriority = - Short.valueOf((String) row.get(FLOW_COLUMN_CORE_PRIORITY)); + //TODO @Ryan delete? Not referenced anywhere DEFAULT_CORE_PRIORITY = Short.valueOf((String) row.get(FLOW_COLUMN_CORE_PRIORITY)); } } } } } catch (StorageException e) { - log.error("Failed to access storage for forwarding configuration: {}", - e.getMessage()); + log.error("Failed to access storage for forwarding configuration: {}", e.getMessage()); } catch (NumberFormatException e) { // log error, no stack-trace @@ -2389,34 +852,6 @@ public class Controller implements IFloodlightProviderService, } } - - private void registerControllerDebugEvents() throws FloodlightModuleException { - if (debugEvents == null) { - debugEvents = new NullDebugEvent(); - } - try { - evSwitch = debugEvents.registerEvent( - Counters.prefix, "switchevent", - "Switch connected, disconnected or port changed", - EventType.ALWAYS_LOG, SwitchEvent.class, 100); - } catch (MaxEventsRegistered e) { - throw new FloodlightModuleException("Max events registered", e); - } - } - - public class SwitchEvent { - @EventColumn(name = "dpid", description = EventFieldType.DPID) - long dpid; - - @EventColumn(name = "reason", description = EventFieldType.STRING) - String reason; - - public SwitchEvent(long dpid, String reason) { - this.dpid = dpid; - this.reason = reason; - } - } - @Override public void addInfoProvider(String type, IInfoProvider provider) { if (!providerMap.containsKey(type)) { @@ -2431,7 +866,6 @@ public class Controller implements IFloodlightProviderService, log.debug("Provider type {} doesn't exist.", type); return; } - providerMap.get(type).remove(provider); } @@ -2443,7 +877,6 @@ public class Controller implements IFloodlightProviderService, for (IInfoProvider provider : providerMap.get(type)) { result.putAll(provider.getInfo(type)); } - return result; } @@ -2457,12 +890,6 @@ public class Controller implements IFloodlightProviderService, this.haListeners.removeListener(listener); } - @Override - public void addReadyForReconcileListener(IReadyForReconcileListener l) { - this.readyForReconcileListeners.add(l); - } - - /** * Handle changes to the controller nodes IPs and dispatch update. */ @@ -2481,7 +908,7 @@ public class Controller implements IFloodlightProviderService, // predicates, but creating the individual and compound predicate // seems more overhead then just checking every row. Particularly, // since the number of rows is small and changes infrequent - IResultSet res = storageSource.executeQuery(CONTROLLER_INTERFACE_TABLE_NAME, + IResultSet res = storageSourceService.executeQuery(CONTROLLER_INTERFACE_TABLE_NAME, colNames,null, null); while (res.next()) { if (res.getString(CONTROLLER_INTERFACE_TYPE).equals("Ethernet") && @@ -2513,7 +940,7 @@ public class Controller implements IFloodlightProviderService, controllerNodeIPsCache.get(removedControllerID)); controllerNodeIPsCache.clear(); controllerNodeIPsCache.putAll(curControllerNodeIPs); - counters.controllerNodeIpsChanged.updateCounterWithFlush(); + //counters.controllerNodeIpsChanged.updateCounterWithFlush(); HAControllerNodeIPUpdate update = new HAControllerNodeIPUpdate( curControllerNodeIPs, addedControllerNodeIPs, removedControllerNodeIPs); @@ -2522,7 +949,7 @@ public class Controller implements IFloodlightProviderService, } } } - + @Override public Map<String, String> getControllerNodeIPs() { // We return a copy of the mapping so we can guarantee that @@ -2534,7 +961,7 @@ public class Controller implements IFloodlightProviderService, } return retval; } - + private static final String FLOW_PRIORITY_CHANGED_AFTER_STARTUP = "Flow priority configuration has changed after " + "controller startup. Restart controller for new " + @@ -2553,8 +980,6 @@ public class Controller implements IFloodlightProviderService, } else if (tableName.equals(FLOW_PRIORITY_TABLE_NAME)) { log.warn(FLOW_PRIORITY_CHANGED_AFTER_STARTUP); } - - } @Override @@ -2572,12 +997,6 @@ public class Controller implements IFloodlightProviderService, return rb.getStartTime(); } - @Override - public void setAlwaysClearFlowsOnSwActivate(boolean value) { - this.alwaysClearFlowsOnSwActivate = value; - } - - @Override public Map<String, Long> getMemory() { Map<String, Long> m = new HashMap<String, Long>(); @@ -2593,37 +1012,13 @@ public class Controller implements IFloodlightProviderService, return rb.getUptime(); } - - - @Override - public void addOFSwitchDriver(String manufacturerDescriptionPrefix, - IOFSwitchDriver driver) { - driverRegistry.addSwitchDriver(manufacturerDescriptionPrefix, driver); - } - - /** - * Forward to the registry to get an IOFSwitch instance. - * @param desc - * @return - */ - IOFSwitch getOFSwitchInstance(OFDescriptionStatistics desc) { - return driverRegistry.getOFSwitchInstance(desc); - } - - /** - * Switch Added/Deleted Events - */ - @Override - public void addSwitchEvent(long switchDPID, String reason, boolean flushNow) { - switchManager.addSwitchEvent(switchDPID, reason, flushNow); - } - @LogMessageDoc(level="WARN", message="Failure adding update {} to queue", explanation="The controller tried to add an internal notification" + " to its message queue but the add failed.", recommendation=LogMessageDoc.REPORT_CONTROLLER_BUG) - private void addUpdateToQueue(IUpdate update) { + @Override + public void addUpdateToQueue(IUpdate update) { try { this.updates.put(update); } catch (InterruptedException e) { @@ -2632,22 +1027,6 @@ public class Controller implements IFloodlightProviderService, } } - void flushAll() { - // Flush all flow-mods/packet-out/stats generated from this "train" - OFSwitchBase.flush_all(); - counterStore.updateFlush(); - debugCounters.flushCounters(); - debugEvents.flushEvents(); - } - - short getAccessFlowPriority() { - return accessPriority; - } - - short getCoreFlowPriority() { - return corePriority; - } - /** * FOR TESTING ONLY. * Dispatch all updates in the update queue until queue is empty @@ -2670,18 +1049,17 @@ public class Controller implements IFloodlightProviderService, /** * FOR TESTING ONLY - * @param update */ - void setConsolidateStoreTaskDelay(int consolidateStoreTaskDelayMs) { - this.consolidateStoreTimeDelayMs = consolidateStoreTaskDelayMs; + void resetModuleState() { + this.moduleLoaderState = ModuleLoaderState.INIT; } /** * FOR TESTING ONLY - * returns the store listener so we can send events to the listener + * sets module loader state */ - IStoreListener<Long> getStoreListener() { - return this.switchManager; + void setModuleLoaderStateForTesting(ModuleLoaderState state) { + this.moduleLoaderState = state; } @Override @@ -2690,7 +1068,35 @@ public class Controller implements IFloodlightProviderService, Map<String, Object> info = new HashMap<String, Object>(); - info.put("# Switches", this.getAllSwitchDpids().size()); + info.put("# Switches", this.switchService.getAllSwitchDpids().size()); return info; } + + protected void setNotifiedRole(HARole newRole) { + notifiedRole = newRole; + } + + @Override + public RoleManager getRoleManager() { + return this.roleManager; + } + + public Optional<ControllerId> getId() { + short nodeId = this.syncService.getLocalNodeId(); + if(nodeId == ClusterConfig.NODE_ID_UNCONFIGURED) + return Optional.absent(); + else + return Optional.of(ControllerId.of(nodeId)); + } + + @Override + public Timer getTimer() { + return this.timer; + } + + public ControllerCounters getCounters() { + return this.counters; + } } + + diff --git a/src/main/java/net/floodlightcontroller/core/internal/ControllerCounters.java b/src/main/java/net/floodlightcontroller/core/internal/ControllerCounters.java new file mode 100644 index 0000000000000000000000000000000000000000..de5b99255c8e2968a7d914b50dc7c0115f330d4c --- /dev/null +++ b/src/main/java/net/floodlightcontroller/core/internal/ControllerCounters.java @@ -0,0 +1,50 @@ +package net.floodlightcontroller.core.internal; + +import net.floodlightcontroller.core.IOFSwitchBackend; +import net.floodlightcontroller.core.OFConnectionCounters; +import net.floodlightcontroller.debugcounter.IDebugCounter; +import net.floodlightcontroller.debugcounter.IDebugCounterService; +import net.floodlightcontroller.debugcounter.IDebugCounterService.MetaData; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +@SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD", + justification = "It is ok to predefine Debug Counters that are not yet used") +public class ControllerCounters { + + public final String prefix = ControllerCounters.class.getSimpleName(); + public final String statsPrefix = IOFSwitchBackend.class.getPackage().getName(); + + public final IDebugCounter packetParsingError; + public final IDebugCounter dispatchMessageWhileStandby; + public final IDebugCounter dispatchMessage; + public final IDebugCounter packetIn; + + public ControllerCounters(IDebugCounterService debugCounters) { + debugCounters.registerModule(prefix); + debugCounters.registerModule(OFConnectionCounters.COUNTER_MODULE); + + dispatchMessageWhileStandby = debugCounters.registerCounter(prefix, + "dispatch-message-while-slave", + "Number of times an OF message was received " + + "and supposed to be dispatched but the " + + "controller was in SLAVE role and the message " + + "was not dispatched"); + // does this cnt make sense? more specific?? per type? + // count stops? + dispatchMessage = debugCounters.registerCounter(prefix, + "dispatch-message", + "Number of times an OF message was dispatched " + + "to registered modules"); + + // TODO: FIXME + // Need a better way to handle these + packetParsingError = debugCounters.registerCounter(prefix, + "packet-parsing-error", + "Number of times the packet parsing " + + "encountered an error", + MetaData.ERROR); + + packetIn = debugCounters.registerCounter(prefix, "packet-in", "Number of packet_in's seen"); + } +} diff --git a/src/main/java/net/floodlightcontroller/core/internal/FloodlightProvider.java b/src/main/java/net/floodlightcontroller/core/internal/FloodlightProvider.java index 8299bb2c223f6c93437813db9dc8963acf386859..e28d428a2a2cb210600a3ea700bdb112648c70f6 100644 --- a/src/main/java/net/floodlightcontroller/core/internal/FloodlightProvider.java +++ b/src/main/java/net/floodlightcontroller/core/internal/FloodlightProvider.java @@ -21,6 +21,10 @@ import java.util.Collection; import java.util.HashMap; import java.util.Map; +import net.floodlightcontroller.core.internal.IOFSwitchService; +import net.floodlightcontroller.core.internal.Controller; +import net.floodlightcontroller.core.module.Run; + import org.sdnplatform.sync.ISyncService; import net.floodlightcontroller.core.IFloodlightProviderService; @@ -28,7 +32,6 @@ 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.counter.ICounterStoreService; import net.floodlightcontroller.debugcounter.IDebugCounterService; import net.floodlightcontroller.debugevent.IDebugEventService; import net.floodlightcontroller.perfmon.IPktInProcessingTimeService; @@ -39,6 +42,10 @@ import net.floodlightcontroller.threadpool.IThreadPoolService; public class FloodlightProvider implements IFloodlightModule { Controller controller; + public FloodlightProvider() { + controller = new Controller(); + } + @Override public Collection<Class<? extends IFloodlightService>> getModuleServices() { Collection<Class<? extends IFloodlightService>> services = @@ -67,9 +74,9 @@ public class FloodlightProvider implements IFloodlightModule { dependencies.add(IStorageSourceService.class); dependencies.add(IPktInProcessingTimeService.class); dependencies.add(IRestApiService.class); - dependencies.add(ICounterStoreService.class); dependencies.add(IDebugCounterService.class); dependencies.add(IDebugEventService.class); + dependencies.add(IOFSwitchService.class); dependencies.add(IThreadPoolService.class); dependencies.add(ISyncService.class); return dependencies; @@ -81,8 +88,6 @@ public class FloodlightProvider implements IFloodlightModule { context.getServiceImpl(IStorageSourceService.class)); controller.setPktInProcessingService( context.getServiceImpl(IPktInProcessingTimeService.class)); - controller.setCounterStore( - context.getServiceImpl(ICounterStoreService.class)); controller.setDebugCounter( context.getServiceImpl(IDebugCounterService.class)); controller.setDebugEvent( @@ -93,12 +98,19 @@ public class FloodlightProvider implements IFloodlightModule { context.getServiceImpl(IThreadPoolService.class)); controller.setSyncService( context.getServiceImpl(ISyncService.class)); + controller.setSwitchService( + context.getServiceImpl(IOFSwitchService.class)); controller.init(context.getConfigParams(this)); } @Override public void startUp(FloodlightModuleContext context) throws FloodlightModuleException { - controller.startupComponents(); + controller.startupComponents(context.getModuleLoader()); + } + + @Run(mainLoop=true) + public void run() throws FloodlightModuleException { + controller.run(); } } diff --git a/src/main/java/net/floodlightcontroller/core/internal/HandshakeTimeoutHandler.java b/src/main/java/net/floodlightcontroller/core/internal/HandshakeTimeoutHandler.java index e89b3895c7e77ff7fde72e83e61aad25e1598fe1..89ef68109bdc715eb2c16ea3f59f6c2c7b33fc67 100644 --- a/src/main/java/net/floodlightcontroller/core/internal/HandshakeTimeoutHandler.java +++ b/src/main/java/net/floodlightcontroller/core/internal/HandshakeTimeoutHandler.java @@ -1,7 +1,7 @@ /** -* Copyright 2011, Big Switch Networks, Inc. +* Copyright 2011, Big Switch Networks, Inc. * Originally created by David Erickson, Stanford University -* +* * Licensed under the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. You may obtain * a copy of the License at @@ -30,36 +30,37 @@ import org.jboss.netty.util.TimerTask; /** * Trigger a timeout if a switch fails to complete handshake soon enough */ -public class HandshakeTimeoutHandler +public class HandshakeTimeoutHandler extends SimpleChannelUpstreamHandler { - static final HandshakeTimeoutException EXCEPTION = + + static final HandshakeTimeoutException EXCEPTION = new HandshakeTimeoutException(); - - final OFChannelHandler channelHandler; + + final OFChannelHandler handshakeHandler; final Timer timer; final long timeoutNanos; volatile Timeout timeout; - - public HandshakeTimeoutHandler(OFChannelHandler channelHandler, + + public HandshakeTimeoutHandler(OFChannelHandler handshakeHandler, Timer timer, long timeoutSeconds) { super(); - this.channelHandler = channelHandler; + this.handshakeHandler = handshakeHandler; this.timer = timer; this.timeoutNanos = TimeUnit.SECONDS.toNanos(timeoutSeconds); } - + @Override public void channelOpen(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception { if (timeoutNanos > 0) { - timeout = timer.newTimeout(new HandshakeTimeoutTask(ctx), + timeout = timer.newTimeout(new HandshakeTimeoutTask(ctx), timeoutNanos, TimeUnit.NANOSECONDS); } ctx.sendUpstream(e); } - + @Override public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception { @@ -68,7 +69,7 @@ public class HandshakeTimeoutHandler timeout = null; } } - + private final class HandshakeTimeoutTask implements TimerTask { private final ChannelHandlerContext ctx; @@ -86,7 +87,7 @@ public class HandshakeTimeoutHandler if (!ctx.getChannel().isOpen()) { return; } - if (!channelHandler.isHandshakeComplete()) + if (!handshakeHandler.isSwitchHandshakeComplete()) Channels.fireExceptionCaught(ctx, EXCEPTION); } } diff --git a/src/main/java/net/floodlightcontroller/core/internal/IAppHandshakePluginFactory.java b/src/main/java/net/floodlightcontroller/core/internal/IAppHandshakePluginFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..fd45309f30208a9b965de54fa92c7b1e4f5859e2 --- /dev/null +++ b/src/main/java/net/floodlightcontroller/core/internal/IAppHandshakePluginFactory.java @@ -0,0 +1,19 @@ +package net.floodlightcontroller.core.internal; + +/** + * This interface creates a contract used by the switch handshake handler. Each + * switch that is connected needs it's own running instance of the registered + * plugins. Thus is depends on a factory to churn out these instances. + * @author Jason Parraga <Jason.Parraga@bigswitch.com> + * + */ +public interface IAppHandshakePluginFactory { + + /** + * Create an instance of OFSwitchAppHandshakePlugin + * @return an instance of OFSwitchAppHandshakePlugin + */ + OFSwitchAppHandshakePlugin createPlugin(); +} + + diff --git a/src/main/java/net/floodlightcontroller/core/internal/INewOFConnectionListener.java b/src/main/java/net/floodlightcontroller/core/internal/INewOFConnectionListener.java new file mode 100644 index 0000000000000000000000000000000000000000..9f03bc38e422bb3da8623b5e8bf2eddf245d7d90 --- /dev/null +++ b/src/main/java/net/floodlightcontroller/core/internal/INewOFConnectionListener.java @@ -0,0 +1,19 @@ +package net.floodlightcontroller.core.internal; + +import net.floodlightcontroller.core.IOFConnectionBackend; +import org.projectfloodlight.openflow.protocol.OFFeaturesReply; + +/** a listener that is notified when a new OFConnection has been opened and + * handshaked (i.e., the {@link OFFeaturesReply} has been received. + * + * @author Andreas Wundsam <andreas.wundsam@bigswitch.com> + */ +public interface INewOFConnectionListener { + /** notify this listener that a new connection has been opened + * + * @param connection - the new connection + * @param featuresReply - the {@link OFFeaturesReply} that was received. + */ + void connectionOpened(IOFConnectionBackend connection, + OFFeaturesReply featuresReply); +} diff --git a/src/main/java/net/floodlightcontroller/core/internal/IOFConnectionListener.java b/src/main/java/net/floodlightcontroller/core/internal/IOFConnectionListener.java new file mode 100644 index 0000000000000000000000000000000000000000..f537e2d4fff00de8405346326f91fff9a93cba42 --- /dev/null +++ b/src/main/java/net/floodlightcontroller/core/internal/IOFConnectionListener.java @@ -0,0 +1,12 @@ +package net.floodlightcontroller.core.internal; + +import net.floodlightcontroller.core.IOFConnectionBackend; +import org.projectfloodlight.openflow.protocol.OFMessage; + +public interface IOFConnectionListener { + void connectionClosed(IOFConnectionBackend connection); + + void messageReceived(IOFConnectionBackend connection, OFMessage m); + + boolean isSwitchHandshakeComplete(IOFConnectionBackend connection); +} diff --git a/src/main/java/net/floodlightcontroller/core/internal/IOFSwitchManager.java b/src/main/java/net/floodlightcontroller/core/internal/IOFSwitchManager.java new file mode 100644 index 0000000000000000000000000000000000000000..81a85346ca78e944cf2cf6dd4d52d6fb7d74ee16 --- /dev/null +++ b/src/main/java/net/floodlightcontroller/core/internal/IOFSwitchManager.java @@ -0,0 +1,124 @@ +package net.floodlightcontroller.core.internal; + +import java.util.List; + +import net.floodlightcontroller.core.FloodlightContext; +import net.floodlightcontroller.core.IOFConnectionBackend; +import net.floodlightcontroller.core.IOFSwitch.SwitchStatus; +import net.floodlightcontroller.core.IOFSwitchBackend; +import net.floodlightcontroller.core.IOFSwitchDriver; +import net.floodlightcontroller.core.LogicalOFMessageCategory; +import net.floodlightcontroller.core.PortChangeType; +import net.floodlightcontroller.core.SwitchDescription; + +import org.projectfloodlight.openflow.protocol.OFFactory; +import org.projectfloodlight.openflow.protocol.OFMessage; +import org.projectfloodlight.openflow.protocol.OFPortDesc; +import org.projectfloodlight.openflow.types.DatapathId; + +import com.google.common.collect.ImmutableList; + +public interface IOFSwitchManager { + + /** + * Called when a switch is added. + * @param sw the added switch + */ + void switchAdded(IOFSwitchBackend sw); + + /** + * Called when a switch disconnects + * @param sw the added switch + */ + void switchDisconnected(IOFSwitchBackend sw); + + /** + * Indicates that ports on the given switch have changed. Enqueue a + * switch update. + * @param sw the added switch + */ + void notifyPortChanged(IOFSwitchBackend sw, OFPortDesc port, + PortChangeType type); + + /** + * Relays to ISwitchDriverRegistry + */ + IOFSwitchBackend getOFSwitchInstance(IOFConnectionBackend connection, + SwitchDescription description, + OFFactory factory, + DatapathId datapathId); + + /** + * Relays an upstream message to the controller to dispatch to listeners. + * @param sw The switch the message was received on. + * @param m The message received. + * @param bContext the Floodlight context of the message, normally null in this case. + */ + void handleMessage(IOFSwitchBackend sw, OFMessage m, FloodlightContext bContext); + + /** + * Gets an unmodifiable collection of OFSwitchHandshakeHandlers + * @return an unmodifiable collection of OFSwitchHandshakeHandlers + */ + ImmutableList<OFSwitchHandshakeHandler> getSwitchHandshakeHandlers(); + + /** + * Adds an OFSwitch driver + * @param manufacturerDescriptionPrefix Register the given prefix + * with the driver. + * @param driver A IOFSwitchDriver instance to handle IOFSwitch instantiation + * for the given manufacturer description prefix + * @throws IllegalStateException If the the manufacturer description is + * already registered + * @throws NullPointerExeption if manufacturerDescriptionPrefix is null + * @throws NullPointerExeption if driver is null + */ + void addOFSwitchDriver(String manufacturerDescriptionPrefix, + IOFSwitchDriver driver); + + /** + * Handles all changes to the switch status. Will alert listeners and manage + * the internal switch map appropriately. + * @param sw the switch that has changed + * @param oldStatus the switch's old status + * @param newStatus the switch's new status + */ + void switchStatusChanged(IOFSwitchBackend sw, SwitchStatus oldStatus, + SwitchStatus newStatus); + + /** + * Gets the number of connections required by the application + * @return the number of connections required by the applications + */ + int getNumRequiredConnections(); + + /** + * Record a switch event in in-memory debug-event + * @param switchDpid + * @param reason Reason for this event + * @param flushNow see debug-event flushing in IDebugEventService + */ + public void addSwitchEvent(DatapathId switchDpid, String reason, boolean flushNow); + + /** + * Get the list of handshake plugins necessary for the switch handshake. + * @return the list of handshake plugins registered by applications. + */ + List<IAppHandshakePluginFactory> getHandshakePlugins(); + + /** + * Get the switch manager's counters + * @return the switch manager's counters + */ + SwitchManagerCounters getCounters(); + + /** + * Checks to see if the supplied category has been registered with the controller + * @param category the logical OF Message category to check or + * @return true if registered + */ + boolean isCategoryRegistered(LogicalOFMessageCategory category); + + void handshakeDisconnected(DatapathId dpid); + +} diff --git a/src/main/java/net/floodlightcontroller/core/internal/IOFSwitchService.java b/src/main/java/net/floodlightcontroller/core/internal/IOFSwitchService.java new file mode 100644 index 0000000000000000000000000000000000000000..24e105ed06a96824231e3e1de5aeee5be6010672 --- /dev/null +++ b/src/main/java/net/floodlightcontroller/core/internal/IOFSwitchService.java @@ -0,0 +1,120 @@ +package net.floodlightcontroller.core.internal; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +import net.floodlightcontroller.core.IOFSwitch; +import net.floodlightcontroller.core.IOFSwitchDriver; +import net.floodlightcontroller.core.IOFSwitchListener; +import net.floodlightcontroller.core.LogicalOFMessageCategory; +import net.floodlightcontroller.core.module.IFloodlightService; +import net.floodlightcontroller.core.rest.SwitchRepresentation; + +import org.projectfloodlight.openflow.types.DatapathId; + +public interface IOFSwitchService extends IFloodlightService { + + /** + * Get's the switch map stored in the switch manager + * @return the map of switches known by the switch manager + */ + Map<DatapathId, IOFSwitch> getAllSwitchMap(); + + /** + * If the switch with the given DPID is known to any controller in the + * cluster, this method returns the associated IOFSwitch instance. As such + * the returned switches not necessarily connected or in master role for + * the local controller. + * + * Multiple calls to this method with the same DPID may return different + * IOFSwitch references. A caller must not store or otherwise rely on + * IOFSwitch references to be constant over the lifecycle of a switch. + * + * @param dpid the dpid of the switch to query + * @return the IOFSwitch instance associated with the dpid, null if no + * switch with the dpid is known to the cluster + */ + IOFSwitch getSwitch(DatapathId dpid); + + /** + * If the switch with the given DPID is known to any controller in the + * cluster, this method returns the associated IOFSwitch instance. As such + * the returned switches not necessarily connected or in master role for + * the local controller. + * + * Multiple calls to this method with the same DPID may return different + * IOFSwitch references. A caller must not store or otherwise rely on + * IOFSwitch references to be constant over the lifecycle of a switch. + * + * @param dpid the dpid of the switch to query + * @return the IOFSwitch instance associated with the dpid, null if no + * switch with the dpid is known to the cluster OR if the switch status + * is not considered visible. + */ + IOFSwitch getActiveSwitch(DatapathId dpid); + + /** + * Add a switch listener + * @param listener The module that wants to listen for events + */ + void addOFSwitchListener(IOFSwitchListener listener); + + /** + * Add a switch driver + * @param manufacturerDescriptionPrefix + * @param driver + */ + void addOFSwitchDriver(String manufacturerDescriptionPrefix, IOFSwitchDriver driver); + + /** + * Remove a switch listener + * @param listener The The module that no longer wants to listen for events + */ + void removeOFSwitchListener(IOFSwitchListener listener); + + /** + * Registers a logical OFMessage category to be used by an application + * @param category the logical OFMessage category + */ + void registerLogicalOFMessageCategory(LogicalOFMessageCategory category); + + /** + * Registers an app handshake plugin to be used during switch handshaking. + * @param plugin the app handshake plugin to be used during switch handshaking. + */ + void registerHandshakePlugin(IAppHandshakePluginFactory plugin); + + /** + * Get the REST representations of the active switches. + * @return Representation wrappers of the active switches. + */ + List<SwitchRepresentation> getSwitchRepresentations(); + + /** + * Get the REST representation of a switch. + * @param dpid the dpid of the desired switch representation. + * @return The switch representation. + */ + SwitchRepresentation getSwitchRepresentation(DatapathId dpid); + + /** + * Returns a snapshot of the set DPIDs for all known switches. + * + * The returned set is owned by the caller: the caller can modify it at + * will and changes to the known switches are not reflected in the returned + * set. The caller needs to call getAllSwitchDpids() if an updated + * version is needed. + * + * See {@link #getSwitch(long)} for what "known" switch is. + * @return the set of DPIDs of all known switches + */ + Set<DatapathId> getAllSwitchDpids(); + + /** + * Gets an immutable list of handshake handlers. + * @return an immutable list of handshake handlers. + */ + List<OFSwitchHandshakeHandler> getSwitchHandshakeHandlers(); + +} diff --git a/src/main/java/net/floodlightcontroller/core/internal/ISwitchDriverRegistry.java b/src/main/java/net/floodlightcontroller/core/internal/ISwitchDriverRegistry.java index 6251fd26db9eab73cef4824c28ef20100d3b756f..a0cdf915338a43ad93536c6b19f37227b43dd1f2 100644 --- a/src/main/java/net/floodlightcontroller/core/internal/ISwitchDriverRegistry.java +++ b/src/main/java/net/floodlightcontroller/core/internal/ISwitchDriverRegistry.java @@ -1,9 +1,11 @@ package net.floodlightcontroller.core.internal; -import net.floodlightcontroller.core.IOFSwitch; +import net.floodlightcontroller.core.IOFConnectionBackend; +import net.floodlightcontroller.core.IOFSwitchBackend; import net.floodlightcontroller.core.IOFSwitchDriver; - -import org.openflow.protocol.statistics.OFDescriptionStatistics; +import net.floodlightcontroller.core.SwitchDescription; +import org.projectfloodlight.openflow.protocol.OFFactory; +import org.projectfloodlight.openflow.types.DatapathId; /** * Maintain a registry for SwitchDrivers. Drivers can register with the @@ -27,7 +29,7 @@ public interface ISwitchDriverRegistry { * * @param manufacturerDescriptionPrefix Register the given prefix * with the driver. - * @param driver A IOFSwitchDriver instance to handle IOFSwitch instaniation + * @param driver A IOFSwitchDriver instance to handle IOFSwitch instantiation * for the given manufacturer description prefix * @throws IllegalStateException If the the manufacturer description is * already registered @@ -37,7 +39,7 @@ public interface ISwitchDriverRegistry { void addSwitchDriver(String manufacturerDescriptionPrefix, IOFSwitchDriver driver); /** - * Return an IOFSwitch instance according to the description stats. + * Return an IOFSwitch instance according to the switch description. * * The driver with the <i>longest matching prefix</i> will be picked first. * The description is then handed over to the choosen driver to return an @@ -47,14 +49,17 @@ public interface ISwitchDriverRegistry { * IOFSwitch instance the registry returns a default OFSwitchImpl instance. * * The returned switch will have its description reply and - * switch properties set according to the DescriptionStats passed in + * switch properties set according to the SwitchDescription passed in * - * @param description The OFDescriptionStatistics for which to return an + *@param connection The main OF connection + * @param description The SwitchDescription for which to return an * IOFSwitch implementation + * @param factory The factory to use to create OF messages. + * @param datapathId * @return A IOFSwitch implementation matching the description or an * OFSwitchImpl if no driver returned a more concrete instance. - * @throws NullPointerException If the OFDescriptionStatistics or any - * of its members is null. + * @throws NullPointerException If the SwitchDescription or any of its + * members is null. */ - IOFSwitch getOFSwitchInstance(OFDescriptionStatistics description); + IOFSwitchBackend getOFSwitchInstance(IOFConnectionBackend connection, SwitchDescription description, OFFactory factory, DatapathId datapathId); } diff --git a/src/main/java/net/floodlightcontroller/core/internal/NaiiveSwitchDriverRegistry.java b/src/main/java/net/floodlightcontroller/core/internal/NaiiveSwitchDriverRegistry.java deleted file mode 100644 index 8109e3add7ebc6f580596eccac975e14b3bf7c6c..0000000000000000000000000000000000000000 --- a/src/main/java/net/floodlightcontroller/core/internal/NaiiveSwitchDriverRegistry.java +++ /dev/null @@ -1,105 +0,0 @@ -package net.floodlightcontroller.core.internal; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.SortedSet; -import java.util.TreeSet; - -import org.openflow.protocol.statistics.OFDescriptionStatistics; - -import net.floodlightcontroller.core.IOFSwitch; -import net.floodlightcontroller.core.IOFSwitchDriver; - -/** - * This implementation of ISwitchDriverRegistry uses a naiive algorithm to - * perform longest prefix matching on the manufacturere description prefixes - * - * We maintain a map that maps prefixes to the drivers as well as a sorted - * set that contains the prefixes sorted by their length. We exploit the fact - * that lexicographical order defines that shorter strings are always less - * than longer strings with the same prefix). Thus we can use reverse order for - * our purposes. - * To perform a lookup we iterate through the sorted set until we find a prefix - * that matches the manufacturer description. Since the set is sorted this - * will be the longest matching prefix. - * - * @author gregor - */ -class NaiiveSwitchDriverRegistry implements ISwitchDriverRegistry { - private final SortedSet<String> switchDescSorted; - private final Map<String,IOFSwitchDriver> switchBindingMap; - - public NaiiveSwitchDriverRegistry() { - switchBindingMap = new HashMap<String, IOFSwitchDriver>(); - switchDescSorted = new TreeSet<String>(Collections.reverseOrder()); - } - - @Override - public synchronized void addSwitchDriver(String manufacturerDescPrefix, - IOFSwitchDriver driver) { - if (manufacturerDescPrefix == null) { - throw new NullPointerException("manufacturerDescrptionPrefix" + - " must not be null"); - } - if (driver == null) { - throw new NullPointerException("driver must not be null"); - } - - IOFSwitchDriver existingDriver = switchBindingMap.get(manufacturerDescPrefix); - if (existingDriver != null ) { - throw new IllegalStateException("Failed to add OFSwitch driver for " - + manufacturerDescPrefix + "already registered"); - } - switchBindingMap.put(manufacturerDescPrefix, driver); - switchDescSorted.add(manufacturerDescPrefix); - } - - @Override - // TODO: instead of synchronized we could actually use a r/w lock - // but it's probably not worth it. - public synchronized IOFSwitch - getOFSwitchInstance(OFDescriptionStatistics description) { - if (description == null) - throw new NullPointerException("description must not be null"); - if (description.getHardwareDescription() == null) { - throw new NullPointerException( - "hardware description must not be null"); - } - if (description.getManufacturerDescription() == null) { - throw new NullPointerException( - "manufacturer description must not be null"); - } - if (description.getSerialNumber() == null) { - throw new NullPointerException( - "serial number must not be null"); - } - if (description.getDatapathDescription() == null) { - throw new NullPointerException( - "datapath description must not be null"); - } - if (description.getSoftwareDescription() == null) { - throw new NullPointerException( - "software description must not be null"); - } - - - // Find the appropriate driver - for (String descPrefix: switchDescSorted) { - if (description.getManufacturerDescription() - .startsWith(descPrefix)) { - IOFSwitchDriver driver = switchBindingMap.get(descPrefix); - IOFSwitch sw = driver.getOFSwitchImpl(description); - if (sw != null) { - sw.setSwitchProperties(description); - return sw; - } - } - } - // no switch found - IOFSwitch sw = new OFSwitchImpl(); - sw.setSwitchProperties(description); - return sw; - } - -} diff --git a/src/main/java/net/floodlightcontroller/core/internal/NaiveSwitchDriverRegistry.java b/src/main/java/net/floodlightcontroller/core/internal/NaiveSwitchDriverRegistry.java new file mode 100644 index 0000000000000000000000000000000000000000..6f2fffed183dd3e58ab59729a5f8d636ad3a9009 --- /dev/null +++ b/src/main/java/net/floodlightcontroller/core/internal/NaiveSwitchDriverRegistry.java @@ -0,0 +1,102 @@ +package net.floodlightcontroller.core.internal; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.SortedSet; +import java.util.TreeSet; + +import javax.annotation.Nonnull; + +import net.floodlightcontroller.core.IOFConnectionBackend; +import net.floodlightcontroller.core.IOFSwitchBackend; +import net.floodlightcontroller.core.IOFSwitchDriver; +import net.floodlightcontroller.core.OFSwitch; +import net.floodlightcontroller.core.SwitchDescription; +import org.projectfloodlight.openflow.protocol.OFFactory; +import org.projectfloodlight.openflow.types.DatapathId; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.Preconditions; + +/** + * This implementation of ISwitchDriverRegistry uses a naive algorithm to + * perform longest prefix matching on the manufacturer description prefixes + * + * We maintain a map that maps prefixes to the drivers as well as a sorted + * set that contains the prefixes sorted by their length. We exploit the fact + * that lexicographical order defines that shorter strings are always less + * than longer strings with the same prefix). Thus we can use reverse order for + * our purposes. + * To perform a lookup we iterate through the sorted set until we find a prefix + * that matches the manufacturer description. Since the set is sorted this + * will be the longest matching prefix. + * + * @author gregor + */ +class NaiveSwitchDriverRegistry implements ISwitchDriverRegistry { + + protected static final Logger log = LoggerFactory.getLogger(NaiveSwitchDriverRegistry.class); + private final SortedSet<String> switchDescSorted; + private final Map<String,IOFSwitchDriver> switchBindingMap; + private final IOFSwitchManager switchManager; + + public NaiveSwitchDriverRegistry(@Nonnull IOFSwitchManager switchManager) { + Preconditions.checkNotNull(switchManager, "switchManager must not be null"); + this.switchManager = switchManager; + switchBindingMap = new HashMap<String, IOFSwitchDriver>(); + switchDescSorted = new TreeSet<String>(Collections.reverseOrder()); + } + + @Override + public synchronized void addSwitchDriver(@Nonnull String manufacturerDescPrefix, + @Nonnull IOFSwitchDriver driver) { + Preconditions.checkNotNull(manufacturerDescPrefix, "manufactererDescProfix"); + Preconditions.checkNotNull(driver, "driver"); + + IOFSwitchDriver existingDriver = switchBindingMap.get(manufacturerDescPrefix); + if (existingDriver != null ) { + throw new IllegalStateException("Failed to add OFSwitch driver for " + + manufacturerDescPrefix + "already registered"); + } + switchBindingMap.put(manufacturerDescPrefix, driver); + switchDescSorted.add(manufacturerDescPrefix); + } + + @Override + // TODO: instead of synchronized we could actually use a r/w lock + // but it's probably not worth it. + public synchronized IOFSwitchBackend + getOFSwitchInstance(@Nonnull IOFConnectionBackend connection, @Nonnull SwitchDescription description, + @Nonnull OFFactory factory, @Nonnull DatapathId id) { + Preconditions.checkNotNull(connection, "connection"); + Preconditions.checkNotNull(description, "description"); + Preconditions.checkNotNull(factory, "factory"); + Preconditions.checkNotNull(id, "id"); + + Preconditions.checkNotNull(description.getHardwareDescription(), "hardware description"); + Preconditions.checkNotNull(description.getManufacturerDescription(), "manufacturer description"); + Preconditions.checkNotNull(description.getSerialNumber(), "serial number"); + Preconditions.checkNotNull(description.getDatapathDescription(), "datapath description"); + Preconditions.checkNotNull(description.getSoftwareDescription(), "software description"); + + // Find the appropriate driver + for (String descPrefix: switchDescSorted) { + if (description.getManufacturerDescription() + .startsWith(descPrefix)) { + IOFSwitchDriver driver = switchBindingMap.get(descPrefix); + IOFSwitchBackend sw = driver.getOFSwitchImpl(description, factory); + if (sw != null) { + sw.setSwitchProperties(description); + return sw; + } + } + } + // no switch found + IOFSwitchBackend sw = new OFSwitch(connection, factory, switchManager, id); + sw.setSwitchProperties(description); + return sw; + } + +} diff --git a/src/main/java/net/floodlightcontroller/core/internal/NiciraRoleUtils.java b/src/main/java/net/floodlightcontroller/core/internal/NiciraRoleUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..bf84d89a9047bf4bdc91bb635e6aecd11d0cbf69 --- /dev/null +++ b/src/main/java/net/floodlightcontroller/core/internal/NiciraRoleUtils.java @@ -0,0 +1,40 @@ +package net.floodlightcontroller.core.internal; + +import org.projectfloodlight.openflow.protocol.OFControllerRole; +import org.projectfloodlight.openflow.protocol.OFNiciraControllerRole; +import org.projectfloodlight.openflow.protocol.OFNiciraControllerRoleReply; + +/** static utilities to convert between Pre-OF1.2 "Nicira Style" roles and OF1.2+ OpenFlow + * standard roles. + * @author Andreas Wundsam <andreas.wundsam@bigswitch.com> + */ +public class NiciraRoleUtils { + private NiciraRoleUtils() {} + + public static OFControllerRole niciraToOFRole(OFNiciraControllerRoleReply roleReply) { + switch(roleReply.getRole()) { + case ROLE_MASTER: + return OFControllerRole.ROLE_MASTER; + case ROLE_OTHER: + return OFControllerRole.ROLE_EQUAL; + case ROLE_SLAVE: + return OFControllerRole.ROLE_SLAVE; + default: + throw new IllegalArgumentException("unknown Nicira role value: " + roleReply.getRole()); + } + } + + public static OFNiciraControllerRole ofRoleToNiciraRole(OFControllerRole role) { + switch(role) { + case ROLE_EQUAL: + return OFNiciraControllerRole.ROLE_OTHER; + case ROLE_MASTER: + return OFNiciraControllerRole.ROLE_MASTER; + case ROLE_SLAVE: + return OFNiciraControllerRole.ROLE_SLAVE; + default: + throw new IllegalArgumentException("Unknown role: " + role); + } + } + +} diff --git a/src/main/java/net/floodlightcontroller/core/internal/OFAuxException.java b/src/main/java/net/floodlightcontroller/core/internal/OFAuxException.java new file mode 100644 index 0000000000000000000000000000000000000000..e2a6ccfdd8c0e1a6af30bfe91f4fecea3b6035d5 --- /dev/null +++ b/src/main/java/net/floodlightcontroller/core/internal/OFAuxException.java @@ -0,0 +1,42 @@ +/** +* Copyright 2011, Big Switch Networks, Inc. +* Originally created by David Erickson, Stanford University +* +* Licensed under the Apache License, Version 2.0 (the "License"); you may +* not use this file except in compliance with the License. You may obtain +* a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +* License for the specific language governing permissions and limitations +* under the License. +**/ + +package net.floodlightcontroller.core.internal; + +/** + * This exception indicates an error or unexpected message during + * OF Aux handshaking. E.g., if a switch reports that it cannot supply us + * with the number of OF Aux connections needed. + * @author Jason Parraga <Jason.Parraga@bigswitch.com> + */ +public class OFAuxException extends SwitchStateException{ + + private static final long serialVersionUID = 8452081020837079086L; + + public OFAuxException() { + super(); + } + + public OFAuxException(String arg0) { + super(arg0); + } + + public OFAuxException(Throwable arg0) { + super(arg0); + } + +} \ 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 4af5444cae8317b0cee0fb811fcd487e0b78cb94..ca76c6af55d27536b3741538e47d8a86056d73d3 100644 --- a/src/main/java/net/floodlightcontroller/core/internal/OFChannelHandler.java +++ b/src/main/java/net/floodlightcontroller/core/internal/OFChannelHandler.java @@ -2,1783 +2,758 @@ package net.floodlightcontroller.core.internal; import java.io.IOException; import java.nio.channels.ClosedChannelException; -import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; -import java.util.Iterator; import java.util.List; import java.util.concurrent.RejectedExecutionException; -import net.floodlightcontroller.core.FloodlightContext; -import net.floodlightcontroller.core.IOFSwitch; -import net.floodlightcontroller.core.IFloodlightProviderService.Role; -import net.floodlightcontroller.core.IOFSwitch.PortChangeEvent; -import net.floodlightcontroller.core.annotations.LogMessageDoc; -import net.floodlightcontroller.core.annotations.LogMessageDocs; -import net.floodlightcontroller.core.internal.Controller.Counters; -import net.floodlightcontroller.debugcounter.IDebugCounterService.CounterException; -import net.floodlightcontroller.storage.IResultSet; -import net.floodlightcontroller.storage.StorageException; -import net.floodlightcontroller.util.LoadMonitor; - -import org.jboss.netty.buffer.ChannelBuffer; -import org.jboss.netty.buffer.ChannelBuffers; +import javax.annotation.Nonnull; + import org.jboss.netty.channel.Channel; import org.jboss.netty.channel.ChannelHandlerContext; +import org.jboss.netty.channel.ChannelPipeline; import org.jboss.netty.channel.ChannelStateEvent; import org.jboss.netty.channel.Channels; import org.jboss.netty.channel.ExceptionEvent; import org.jboss.netty.channel.MessageEvent; import org.jboss.netty.handler.timeout.IdleStateAwareChannelHandler; import org.jboss.netty.handler.timeout.IdleStateEvent; +import org.jboss.netty.handler.timeout.IdleStateHandler; import org.jboss.netty.handler.timeout.ReadTimeoutException; -import org.openflow.protocol.OFBarrierReply; -import org.openflow.protocol.OFBarrierRequest; -import org.openflow.protocol.OFEchoReply; -import org.openflow.protocol.OFEchoRequest; -import org.openflow.protocol.OFError; -import org.openflow.protocol.OFFeaturesReply; -import org.openflow.protocol.OFFlowRemoved; -import org.openflow.protocol.OFGetConfigReply; -import org.openflow.protocol.OFGetConfigRequest; -import org.openflow.protocol.OFHello; -import org.openflow.protocol.OFMessage; -import org.openflow.protocol.OFPacketIn; -import org.openflow.protocol.OFPortStatus; -import org.openflow.protocol.OFQueueGetConfigReply; -import org.openflow.protocol.OFSetConfig; -import org.openflow.protocol.OFStatisticsReply; -import org.openflow.protocol.OFStatisticsRequest; -import org.openflow.protocol.OFSwitchConfig; -import org.openflow.protocol.OFType; -import org.openflow.protocol.OFVendor; -import org.openflow.protocol.OFError.OFBadActionCode; -import org.openflow.protocol.OFError.OFBadRequestCode; -import org.openflow.protocol.OFError.OFErrorType; -import org.openflow.protocol.OFError.OFFlowModFailedCode; -import org.openflow.protocol.OFError.OFHelloFailedCode; -import org.openflow.protocol.OFError.OFPortModFailedCode; -import org.openflow.protocol.OFError.OFQueueOpFailedCode; -import org.openflow.protocol.factory.BasicFactory; -import org.openflow.protocol.factory.MessageParseException; -import org.openflow.protocol.statistics.OFDescriptionStatistics; -import org.openflow.protocol.statistics.OFStatistics; -import org.openflow.protocol.statistics.OFStatisticsType; -import org.openflow.util.HexString; -import org.openflow.vendor.nicira.OFNiciraVendorData; -import org.openflow.vendor.nicira.OFRoleReplyVendorData; -import org.openflow.vendor.nicira.OFRoleRequestVendorData; +import org.jboss.netty.util.Timer; +import net.floodlightcontroller.core.IOFConnectionBackend; +import net.floodlightcontroller.core.OFConnection; +import net.floodlightcontroller.core.annotations.LogMessageDoc; +import net.floodlightcontroller.core.annotations.LogMessageDocs; +import net.floodlightcontroller.core.internal.OpenflowPipelineFactory.PipelineHandler; +import net.floodlightcontroller.core.internal.OpenflowPipelineFactory.PipelineHandshakeTimeout; +import net.floodlightcontroller.core.internal.OpenflowPipelineFactory.PipelineIdleReadTimeout; +import net.floodlightcontroller.core.internal.OpenflowPipelineFactory.PipelineIdleWriteTimeout; +import net.floodlightcontroller.debugcounter.IDebugCounterService; +import org.projectfloodlight.openflow.exceptions.OFParseError; +import org.projectfloodlight.openflow.protocol.OFEchoReply; +import org.projectfloodlight.openflow.protocol.OFEchoRequest; +import org.projectfloodlight.openflow.protocol.OFErrorMsg; +import org.projectfloodlight.openflow.protocol.OFExperimenter; +import org.projectfloodlight.openflow.protocol.OFFactories; +import org.projectfloodlight.openflow.protocol.OFFactory; +import org.projectfloodlight.openflow.protocol.OFFeaturesReply; +import org.projectfloodlight.openflow.protocol.OFFeaturesRequest; +import org.projectfloodlight.openflow.protocol.OFHello; +import org.projectfloodlight.openflow.protocol.OFMessage; +import org.projectfloodlight.openflow.protocol.OFVersion; +import org.projectfloodlight.openflow.types.OFAuxId; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.bigswitch.floodlight.vendor.OFBigSwitchVendorData; -import com.bigswitch.floodlight.vendor.OFBsnL2TableSetVendorData; - - +import com.google.common.base.Preconditions; /** * Channel handler deals with the switch connection and dispatches - * switch messages to the appropriate locations. - * @author readams + * messages to the higher orders of control. + * @author Jason Parraga <Jason.Parraga@Bigswitch.com> */ -class OFChannelHandler - extends IdleStateAwareChannelHandler { - - private static final Logger log = LoggerFactory.getLogger(OFChannelHandler.class); - - private static final long DEFAULT_ROLE_TIMEOUT_MS = 10*1000; // 10 sec - - private final Controller controller; - private final Counters counters; - private IOFSwitch sw; - private Channel channel; - // State needs to be volatile because the HandshakeTimeoutHandler - // needs to check if the handshake is complete - private volatile ChannelState state; - private RoleChanger roleChanger; - private OFFeaturesReply featuresReply; - - private final ArrayList<OFPortStatus> pendingPortStatusMsg; - - /** transaction Ids to use during handshake. Since only one thread - * calls into the OFChannelHandler we don't need atomic. - * We will count down - */ - private int handshakeTransactionIds = -1; - - - - /** - * When we remove a pending role request and set the role on the switch - * we use this enum to indicate how we arrived at the decision. - * @author gregor - */ - private enum RoleRecvStatus { - /** We receveived a role reply message from the switch */ - RECEIVED_REPLY, - /** The switch returned an error indicated that roles are not - * supported*/ - UNSUPPORTED, - /** The request timed out */ - NO_REPLY; - } - /** - * A utility class to handle role requests and replies for this channel. - * After a role request is submitted the role changer keeps track of the - * pending request, collects the reply (if any) and times out the request - * if necessary. - * - * To simplify role handling we only keep track of the /last/ pending - * role reply send to the switch. If multiple requests are pending and - * we receive replies for earlier requests we ignore them. However, this - * way of handling pending requests implies that we could wait forever if - * a new request is submitted before the timeout triggers. If necessary - * we could work around that though. - * @author gregor - */ - private class RoleChanger { - // indicates that a request is currently pending - // needs to be volatile to allow correct double-check idiom - private volatile boolean requestPending; - // the transactiong Id of the pending request - private int pendingXid; - // the role that's pending - private Role pendingRole; - // system time in MS when we send the request - private long roleSubmitTime; - // the timeout to use - private final long roleTimeoutMs; - - public RoleChanger(long roleTimeoutMs) { - this.requestPending = false; - this.roleSubmitTime = 0; - this.pendingXid = -1; - this.pendingRole = null; - this.roleTimeoutMs = roleTimeoutMs; - } - - /** - * Send NX role request message to the switch requesting the specified - * role. - * - * @param sw switch to send the role request message to - * @param role role to request - */ - private int sendNxRoleRequest(Role role) - throws IOException { - - int xid = sw.getNextTransactionId(); - // Convert the role enum to the appropriate integer constant used - // in the NX role request message - int nxRole = role.toNxRole(); - - // Construct the role request message - OFVendor roleRequest = (OFVendor)BasicFactory.getInstance() - .getMessage(OFType.VENDOR); - roleRequest.setXid(xid); - roleRequest.setVendor(OFNiciraVendorData.NX_VENDOR_ID); - OFRoleRequestVendorData roleRequestData = new OFRoleRequestVendorData(); - roleRequestData.setRole(nxRole); - roleRequest.setVendorData(roleRequestData); - roleRequest.setLengthU(OFVendor.MINIMUM_LENGTH + - roleRequestData.getLength()); - - // Send it to the switch - sw.write(Collections.<OFMessage>singletonList(roleRequest), - new FloodlightContext()); - - return xid; - } - - /** - * Send a role request for the given role only if no other role - * request is currently pending. - * @param role The role to send to the switch. - * @throws IOException - */ - synchronized void sendRoleRequestIfNotPending(Role role) - throws IOException { - if (!requestPending) - sendRoleRequest(role); - else - counters.roleNotResentBecauseRolePending.updateCounterWithFlush(); - } - - /** - * Send a role request with the given role to the switch. - * - * Send a role request with the given role to the switch and update - * the pending request and timestamp. - * - * @param role - * @throws IOException - */ - synchronized void sendRoleRequest(Role role) throws IOException { - /* - * There are three cases to consider for SUPPORTS_NX_ROLE: - * - * 1) unset. We have neither received a role reply from the - * switch nor has a request timed out. Send a request. - * 2) TRUE: We've already send a request earlier and received - * a reply. The switch supports role and we should send one. - * 3) FALSE: We have already send a role and received an error. - * The switch does not support roles. Don't send a role request, - * set the switch's role directly. - */ - Boolean supportsNxRole = (Boolean) - sw.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE); - if ((supportsNxRole != null) && !supportsNxRole) { - setSwitchRole(role, RoleRecvStatus.UNSUPPORTED); - } else { - pendingXid = sendNxRoleRequest(role); - pendingRole = role; - roleSubmitTime = System.currentTimeMillis(); - requestPending = true; - } - } - - /** - * Deliver a received role reply and set SWITCH_SUPPORTS_NX_ROLE. - * - * Check if a request is pending and if the received reply matches the - * the expected pending reply (we check both role and xid) we set - * the role for the switch/channel. - * - * If a request is pending but doesn't match the reply we ignore it. - * - * If no request is pending we disconnect. - * - * @param xid - * @param role - * @throws SwitchStateException if no request is pending - */ - synchronized void deliverRoleReply(int xid, Role role) { - if (!requestPending) { - // Maybe don't disconnect if the role reply we received is - // for the same role we are already in. - String msg = String.format("Switch: [%s], State: [%s], " - + "received unexpected RoleReply[%s]. " - + "No roles are pending", - OFChannelHandler.this.getSwitchInfoString(), - OFChannelHandler.this.state.toString(), - role); - throw new SwitchStateException(msg); - } - - if (pendingXid == xid && pendingRole == role) { - log.debug("Received role reply message from {}, setting role to {}", - getSwitchInfoString(), role); - counters.roleReplyReceived.updateCounterWithFlush(); - setSwitchRole(role, RoleRecvStatus.RECEIVED_REPLY); - } else { - log.debug("Received stale or unexpected role reply from " + - "switch {} ({}, xid={}). Ignoring. " + - "Waiting for {}, xid={}", - new Object[] { getSwitchInfoString(), role, xid, - pendingRole, pendingXid }); - } - } - - /** - * Called if we receive an error message. If the xid matches the - * pending request we handle it otherwise we ignore it. We also - * set SWITCH_SUPPORTS_NX_ROLE to false. - * - * Note: since we only keep the last pending request we might get - * error messages for earlier role requests that we won't be able - * to handle - * @param xid - * @return true if the error was handled by us, false otherwise - * @throws SwitchStateException if the error was for the pending - * role request but was unexpected - */ - synchronized boolean deliverError(OFError error) { - if (!requestPending) - return false; - - if (pendingXid == error.getXid()) { - boolean isBadRequestError = - (error.getErrorType() == OFError.OFErrorType. - OFPET_BAD_REQUEST.getValue()); - if (isBadRequestError) { - counters.roleReplyErrorUnsupported.updateCounterWithFlush(); - setSwitchRole(pendingRole, RoleRecvStatus.UNSUPPORTED); - } else { - // TODO: Is this the right thing to do if we receive - // some other error besides a bad request error? - // Presumably that means the switch did actually - // understand the role request message, but there - // was some other error from processing the message. - // OF 1.2 specifies a OFPET_ROLE_REQUEST_FAILED - // error code, but it doesn't look like the Nicira - // role request has that. Should check OVS source - // code to see if it's possible for any other errors - // to be returned. - // If we received an error the switch is not - // in the correct role, so we need to disconnect it. - // We could also resend the request but then we need to - // check if there are other pending request in which - // case we shouldn't resend. If we do resend we need - // to make sure that the switch eventually accepts one - // of our requests or disconnect the switch. This feels - // cumbersome. - String msg = String.format("Switch: [%s], State: [%s], " - + "Unexpected error %s in respone to our " - + "role request for %s.", - OFChannelHandler.this.getSwitchInfoString(), - OFChannelHandler.this.state.toString(), - getErrorString(error), - pendingRole); - throw new SwitchStateException(msg); - } - return true; - } - return false; - } - - /** - * Check if a pending role request has timed out. - */ - void checkTimeout() { - if (!requestPending) - return; - synchronized(this) { - if (!requestPending) - return; - long now = System.currentTimeMillis(); - if (now - roleSubmitTime > roleTimeoutMs) { - // timeout triggered. - counters.roleReplyTimeout.updateCounterWithFlush(); - setSwitchRole(pendingRole, RoleRecvStatus.NO_REPLY); - } - } - } - - /** - * Set the role for this switch / channel. - * - * If the status indicates that we received a reply we set the role. - * If the status indicates otherwise we disconnect the switch if - * the role is SLAVE. - * - * "Setting a role" means setting the appropriate ChannelState, - * setting the flags on the switch and - * notifying Controller.java about new role of the switch - * - * @param role The role to set. - * @param status How we derived at the decision to set this status. - */ - synchronized private void setSwitchRole(Role role, RoleRecvStatus status) { - requestPending = false; - if (status == RoleRecvStatus.RECEIVED_REPLY) - sw.setAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE, true); - else - sw.setAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE, false); - sw.setHARole(role); - - if (role != Role.SLAVE) { - OFChannelHandler.this.setState(ChannelState.MASTER); - // TODO: should we really activate the switch again if it's - // already master?? - if (log.isDebugEnabled()) { - log.debug("Switch {} activated. Role is now MASTER", - getSwitchInfoString()); - } - controller.switchActivated(OFChannelHandler.this.sw); - } else { - OFChannelHandler.this.setState(ChannelState.SLAVE); - if (status != RoleRecvStatus.RECEIVED_REPLY) { - if (log.isDebugEnabled()) { - log.debug("Disconnecting switch {}. Doesn't support role" - + "({}) request and controller is now SLAVE", - getSwitchInfoString(), status); - } - // the disconnect will trigger a switch removed to - // controller so no need to signal anything else - sw.disconnectOutputStream(); - } else { - if (log.isDebugEnabled()) { - log.debug("Switch {} is now SLAVE", - getSwitchInfoString()); - } - controller.switchDeactivated(OFChannelHandler.this.sw); - } - } - } - } - - - /** - * The state machine for handling the switch/channel state. - * @author gregor - */ - enum ChannelState { - /** - * Initial state before channel is connected. - */ - INIT(false) { - @Override - void - processOFMessage(OFChannelHandler h, OFMessage m) - throws IOException { - illegalMessageReceived(h, m); - } - - @Override - void processOFError(OFChannelHandler h, OFError m) - throws IOException { - // need to implement since its abstract but it will never - // be called - } - - @Override - void processOFPortStatus(OFChannelHandler h, OFPortStatus m) - throws IOException { - unhandledMessageReceived(h, m); - } - }, - - /** - * We send a HELLO to the switch and wait for a reply. - * Once we receive the reply we send an OFFeaturesRequest and - * a request to clear all FlowMods. - * Next state is WAIT_FEATURES_REPLY - */ - WAIT_HELLO(false) { - @Override - void processOFHello(OFChannelHandler h, OFHello m) - throws IOException { - h.sendHandShakeMessage(OFType.FEATURES_REQUEST); - h.setState(WAIT_FEATURES_REPLY); - } - @Override - void processOFFeaturesReply(OFChannelHandler h, OFFeaturesReply m) - throws IOException { - illegalMessageReceived(h, m); - } - @Override - void processOFStatisticsReply(OFChannelHandler h, - OFStatisticsReply m) - throws IOException { - illegalMessageReceived(h, m); - } - @Override - void processOFError(OFChannelHandler h, OFError m) { - logErrorDisconnect(h, m); - } - - @Override - void processOFPortStatus(OFChannelHandler h, OFPortStatus m) - throws IOException { - unhandledMessageReceived(h, m); - } - }, - - /** - * We are waiting for a features reply message. Once we receive it - * we send a SetConfig request, barrier, and GetConfig request. - * Next stats is WAIT_CONFIG_REPLY or WAIT_SET_L2_TABLE_REPLY - */ - WAIT_FEATURES_REPLY(false) { - @Override - void processOFFeaturesReply(OFChannelHandler h, OFFeaturesReply m) - throws IOException { - h.featuresReply = m; - if (m.getTables() > 1) { - log.debug("Have {} table for switch {}", m.getTables(), - h.getSwitchInfoString()); - // likely supports L2 table extensions. Send set - h.sendHandshakeL2TableSet(); - // TODO: no L2 SET reply yet, so fire and forget the set - // table message and move directly to sendHandshakeConfig - h.sendHandshakeSetConfig(); - h.setState(WAIT_CONFIG_REPLY); - //h.setState(WAIT_SET_L2_TABLE_REPLY); - } else { - h.sendHandshakeSetConfig(); - h.setState(WAIT_CONFIG_REPLY); - } - } - @Override - void processOFStatisticsReply(OFChannelHandler h, - OFStatisticsReply m) - throws IOException { - illegalMessageReceived(h, m); - } - @Override - void processOFError(OFChannelHandler h, OFError m) { - logErrorDisconnect(h, m); - } - - @Override - void processOFPortStatus(OFChannelHandler h, OFPortStatus m) - throws IOException { - unhandledMessageReceived(h, m); - } - }, - - WAIT_SET_L2_TABLE_REPLY(false) { - @Override void processOFVendor(OFChannelHandler h, OFVendor m) - throws IOException { - // TODO: actually parse the response - h.sendHandshakeSetConfig(); - h.setState(WAIT_CONFIG_REPLY); - }; - - @Override - void processOFBarrierReply(OFChannelHandler h, OFBarrierReply m) { - // do nothing; - } - - @Override - void processOFFeaturesReply(OFChannelHandler h, OFFeaturesReply m) - throws IOException { - // TODO: we could re-set the features reply - illegalMessageReceived(h, m); - } - @Override - void processOFStatisticsReply(OFChannelHandler h, - OFStatisticsReply m) - throws IOException { - illegalMessageReceived(h, m); - } - - @Override - void processOFError(OFChannelHandler h, OFError m) { - logErrorDisconnect(h, m); - } - - @Override - void processOFPortStatus(OFChannelHandler h, OFPortStatus m) - throws IOException { - h.pendingPortStatusMsg.add(m); - } - }, - - /** - * We are waiting for a config reply message. Once we receive it - * we send a DescriptionStatsRequest to the switch. - * Next state: WAIT_DESCRIPTION_STAT_REPLY - */ - WAIT_CONFIG_REPLY(false) { - @Override - @LogMessageDocs({ - @LogMessageDoc(level="WARN", - message="Config Reply from {switch} has " + - "miss length set to {length}", - explanation="The controller requires that the switch " + - "use a miss length of 0xffff for correct " + - "function", - recommendation="Use a different switch to ensure " + - "correct function") - }) - void processOFGetConfigReply(OFChannelHandler h, OFGetConfigReply m) - throws IOException { - if (m.getMissSendLength() == (short)0xffff) { - log.trace("Config Reply from switch {} confirms " - + "miss length set to 0xffff", - h.getSwitchInfoString()); - } else { - // FIXME: we can't really deal with switches that don't send - // full packets. Shouldn't we drop the connection here? - // FIXME: count?? - log.warn("Config Reply from switch {} has" - + "miss length set to {}", - h.getSwitchInfoString(), - m.getMissSendLength()); - } - h.sendHandshakeDescriptionStatsRequest(); - h.setState(WAIT_DESCRIPTION_STAT_REPLY); - } - - @Override - void processOFBarrierReply(OFChannelHandler h, OFBarrierReply m) { - // do nothing; - } - - @Override - void processOFFeaturesReply(OFChannelHandler h, OFFeaturesReply m) - throws IOException { - // TODO: we could re-set the features reply - illegalMessageReceived(h, m); - } - @Override - void processOFStatisticsReply(OFChannelHandler h, - OFStatisticsReply m) - throws IOException { - illegalMessageReceived(h, m); - } - - @Override - void processOFError(OFChannelHandler h, OFError m) { - if (m.getErrorType() == OFErrorType.OFPET_BAD_REQUEST.getValue() - && m.getErrorCode() == - OFBadRequestCode.OFPBRC_BAD_VENDOR.ordinal()) { - log.debug("Switch {} has multiple tables but does not " + - "support L2 table extension", - h.getSwitchInfoString()); - return; - } - logErrorDisconnect(h, m); - } - - @Override - void processOFPortStatus(OFChannelHandler h, OFPortStatus m) - throws IOException { - h.pendingPortStatusMsg.add(m); - } - }, - - /** - * We are waiting for a OFDescriptionStat message from teh switch. - * Once we receive any stat message we try to parse it. If it's not - * a description stats message we disconnect. If its the expected - * description stats message, we: - * - use the switch driver to bind the switch and get an IOFSwitch - * instance, setup the switch instance - * - setup the IOFSwitch instance - * - add switch to FloodlightProvider and send the intial role - * request to the switch. - * Next state: WAIT_INITIAL_ROLE - * All following states will have a h.sw instance! - */ - WAIT_DESCRIPTION_STAT_REPLY(false) { - @LogMessageDoc(message="Switch {switch info} bound to class " + - "{switch driver}, description {switch description}", - explanation="The specified switch has been bound to " + - "a switch driver based on the switch description" + - "received from the switch") - @Override - void processOFStatisticsReply(OFChannelHandler h, - OFStatisticsReply m) { - // Read description, if it has been updated - OFDescriptionStatistics description = - new OFDescriptionStatistics(); - ChannelBuffer data = - ChannelBuffers.buffer(description.getLength()); - OFStatistics f = m.getFirstStatistics(); - f.writeTo(data); - description.readFrom(data); - h.sw = h.controller.getOFSwitchInstance(description); - // set switch information - // set features reply and channel first so we a DPID and - // channel info. - h.sw.setFeaturesReply(h.featuresReply); - h.sw.setConnected(true); - h.sw.setChannel(h.channel); - h.sw.setFloodlightProvider(h.controller); - h.sw.setThreadPoolService(h.controller.getThreadPoolService()); - try { - h.sw.setDebugCounterService(h.controller.getDebugCounter()); - } catch (CounterException e) { - h.counters.switchCounterRegistrationFailed - .updateCounterNoFlush(); - log.warn("Could not register counters for switch {} ", - h.getSwitchInfoString(), e); - } - h.sw.setAccessFlowPriority(h.controller.getAccessFlowPriority()); - h.sw.setCoreFlowPriority(h.controller.getCoreFlowPriority()); - for (OFPortStatus ps: h.pendingPortStatusMsg) - handlePortStatusMessage(h, ps, false); - h.pendingPortStatusMsg.clear(); - h.readPropertyFromStorage(); - log.info("Switch {} bound to class {}, writeThrottle={}," + - " description {}", - new Object[] { h.sw, h.sw.getClass(), - h.sw.isWriteThrottleEnabled(), - description }); - h.sw.startDriverHandshake(); - if (h.sw.isDriverHandshakeComplete()) - h.gotoWaitInitialRoleState(); - else - h.setState(WAIT_SWITCH_DRIVER_SUB_HANDSHAKE); - } - - @Override - void processOFError(OFChannelHandler h, OFError m) { - logErrorDisconnect(h, m); - } - - @Override - void processOFFeaturesReply(OFChannelHandler h, OFFeaturesReply m) - throws IOException { - // TODO: we could re-set the features reply - illegalMessageReceived(h, m); - } - - @Override - void processOFPortStatus(OFChannelHandler h, OFPortStatus m) - throws IOException { - h.pendingPortStatusMsg.add(m); - } - }, - - WAIT_SWITCH_DRIVER_SUB_HANDSHAKE(false) { - @Override - void processOFError(OFChannelHandler h, OFError m) - throws IOException { - // will never be called. We override processOFMessage - } - - @Override - void processOFMessage(OFChannelHandler h, OFMessage m) - throws IOException { - if (m.getType() == OFType.ECHO_REQUEST) - processOFEchoRequest(h, (OFEchoRequest)m); - else { - // FIXME: other message to handle here? - h.sw.processDriverHandshakeMessage(m); - if (h.sw.isDriverHandshakeComplete()) { - h.gotoWaitInitialRoleState(); - } - } - } - - @Override - void processOFPortStatus(OFChannelHandler h, OFPortStatus m) - throws IOException { - handlePortStatusMessage(h, m, false); - } - }, - - /** - * We are waiting for the intial role reply message (or error - * indication) from the switch. - * Next State: MASTER or SLAVE - */ - WAIT_INITIAL_ROLE(false) { - @Override - void processOFError(OFChannelHandler h, OFError m) { - // role changer will ignore the error if it isn't for it - boolean didHandle = h.roleChanger.deliverError(m); - if (!didHandle) { - logError(h, m); - } - } - - @Override - void processOFVendor(OFChannelHandler h, OFVendor m) - throws IOException { - Role role = extractNiciraRoleReply(h, m); - // If role == null it measn the message wasn't really a - // Nicira role reply. We ignore this case. - if (role != null) - h.roleChanger.deliverRoleReply(m.getXid(), role); - else - unhandledMessageReceived(h, m); - } - - @Override - void processOFFeaturesReply(OFChannelHandler h, OFFeaturesReply m) - throws IOException { - // TODO: we could re-set the features reply - illegalMessageReceived(h, m); - } - - @Override - void processOFStatisticsReply(OFChannelHandler h, - OFStatisticsReply m) { - illegalMessageReceived(h, m); - } - - @Override - void processOFPortStatus(OFChannelHandler h, OFPortStatus m) - throws IOException { - handlePortStatusMessage(h, m, false); - - } - }, - - /** - * The switch is in MASTER role. We enter this state after a role - * reply from the switch is received (or the controller is MASTER - * and the switch doesn't support roles). The handshake is complete at - * this point. We only leave this state if the switch disconnects or - * if we send a role request for SLAVE /and/ receive the role reply for - * SLAVE. - */ - MASTER(true) { - @LogMessageDoc(level="WARN", - message="Received permission error from switch {} while" + - "being master. Reasserting master role.", - explanation="The switch has denied an operation likely " + - "indicating inconsistent controller roles", - recommendation="This situation can occurs transiently during role" + - " changes. If, however, the condition persists or happens" + - " frequently this indicates a role inconsistency. " + - LogMessageDoc.CHECK_CONTROLLER ) - @Override - void processOFError(OFChannelHandler h, OFError m) - throws IOException { - // role changer will ignore the error if it isn't for it - boolean didHandle = h.roleChanger.deliverError(m); - if (didHandle) - return; - if (m.getErrorType() == - OFErrorType.OFPET_BAD_REQUEST.getValue() && - m.getErrorCode() == - OFBadRequestCode.OFPBRC_EPERM.ordinal()) { - // We are the master controller and the switch returned - // a permission error. This is a likely indicator that - // the switch thinks we are slave. Reassert our - // role - // FIXME: this could be really bad during role transitions - // if two controllers are master (even if its only for - // a brief period). We might need to see if these errors - // persist before we reassert - h.counters.epermErrorWhileSwitchIsMaster.updateCounterWithFlush(); - log.warn("Received permission error from switch {} while" + - "being master. Reasserting master role.", - h.getSwitchInfoString()); - h.controller.reassertRole(h, Role.MASTER); - } - else if (m.getErrorType() == - OFErrorType.OFPET_PORT_MOD_FAILED.getValue() && - m.getErrorCode() == - OFFlowModFailedCode.OFPFMFC_ALL_TABLES_FULL.ordinal()) { - h.sw.setTableFull(true); - } - else { - logError(h, m); - } - h.dispatchMessage(m); - } - - @Override - void processOFStatisticsReply(OFChannelHandler h, - OFStatisticsReply m) { - h.sw.deliverStatisticsReply(m); - } - - @Override - void processOFFeaturesReply(OFChannelHandler h, OFFeaturesReply m) - throws IOException { - h.sw.setFeaturesReply(m); - h.sw.deliverOFFeaturesReply(m); - } - - @Override - void processOFVendor(OFChannelHandler h, OFVendor m) - throws IOException { - Role role = extractNiciraRoleReply(h, m); - // If role == null it means the message wasn't really a - // Nicira role reply. We ignore just dispatch it to the - // OFMessage listenersa in this case. - if (role != null) - h.roleChanger.deliverRoleReply(m.getXid(), role); - else - h.dispatchMessage(m); - } - - @Override - void processOFPortStatus(OFChannelHandler h, OFPortStatus m) - throws IOException { - handlePortStatusMessage(h, m, true); - } - - @Override - void processOFPacketIn(OFChannelHandler h, OFPacketIn m) throws IOException { - h.dispatchMessage(m); - } - - @Override - void processOFFlowRemoved(OFChannelHandler h, - OFFlowRemoved m) throws IOException { - h.dispatchMessage(m); - } - }, - - /** - * The switch is in SLAVE role. We enter this state after a role - * reply from the switch is received. The handshake is complete at - * this point. We only leave this state if the switch disconnects or - * if we send a role request for MASTER /and/ receive the role reply for - * MASTER. - * TODO: CURRENTLY, WE DO NOT DISPATCH ANY MESSAGE IN SLAVE. - */ - SLAVE(true) { - @Override - void processOFError(OFChannelHandler h, OFError m) - throws IOException { - // role changer will ignore the error if it isn't for it - boolean didHandle = h.roleChanger.deliverError(m); - if (!didHandle) { - logError(h, m); - } - } - - - - @Override - void processOFStatisticsReply(OFChannelHandler h, - OFStatisticsReply m) { - // FIXME. - h.sw.deliverStatisticsReply(m); - } - - @Override - void processOFVendor(OFChannelHandler h, OFVendor m) - throws IOException { - Role role = extractNiciraRoleReply(h, m); - // If role == null it means the message wasn't really a - // Nicira role reply. We ignore it. - if (role != null) - h.roleChanger.deliverRoleReply(m.getXid(), role); - else - unhandledMessageReceived(h, m); - } - - @Override - void processOFFeaturesReply(OFChannelHandler h, OFFeaturesReply m) - throws IOException { - // do nothing - } - - @Override - void processOFPortStatus(OFChannelHandler h, OFPortStatus m) - throws IOException { - // do nothing - } - - @Override - @LogMessageDoc(level="WARN", - message="Received PacketIn from switch {} while" + - "being slave. Reasserting slave role.", - explanation="The switch has receive a PacketIn despite being " + - "in slave role indicating inconsistent controller roles", - recommendation="This situation can occurs transiently during role" + - " changes. If, however, the condition persists or happens" + - " frequently this indicates a role inconsistency. " + - LogMessageDoc.CHECK_CONTROLLER ) - void processOFPacketIn(OFChannelHandler h, OFPacketIn m) throws IOException { - // we don't expect packetIn while slave, reassert we are slave - h.counters.packetInWhileSwitchIsSlave.updateCounterNoFlush(); - log.warn("Received PacketIn from switch {} while" + - "being slave. Reasserting slave role.", h.sw); - h.controller.reassertRole(h, Role.SLAVE); - } - }; - - private final boolean handshakeComplete; - ChannelState(boolean handshakeComplete) { - this.handshakeComplete = handshakeComplete; - } - - /** - * Is this a state in which the handshake has completed? - * @return true if the handshake is complete - */ - public boolean isHandshakeComplete() { - return handshakeComplete; - } - - /** - * Get a string specifying the switch connection, state, and - * message received. To be used as message for SwitchStateException - * or log messages - * @param h The channel handler (to get switch information_ - * @param m The OFMessage that has just been received - * @param details A string giving more details about the exact nature - * of the problem. - * @return - */ - // needs to be protected because enum members are acutally subclasses - protected String getSwitchStateMessage(OFChannelHandler h, - OFMessage m, - String details) { - return String.format("Switch: [%s], State: [%s], received: [%s]" - + ", details: %s", - h.getSwitchInfoString(), - this.toString(), - m.getType().toString(), - details); - } - - /** - * We have an OFMessage we didn't expect given the current state and - * we want to treat this as an error. - * We currently throw an exception that will terminate the connection - * However, we could be more forgiving - * @param h the channel handler that received the message - * @param m the message - * @throws SwitchStateExeption we always through the execption - */ - // needs to be protected because enum members are acutally subclasses - protected void illegalMessageReceived(OFChannelHandler h, OFMessage m) { - String msg = getSwitchStateMessage(h, m, - "Switch should never send this message in the current state"); - throw new SwitchStateException(msg); - - } - - /** - * We have an OFMessage we didn't expect given the current state and - * we want to ignore the message - * @param h the channel handler the received the message - * @param m the message - */ - protected void unhandledMessageReceived(OFChannelHandler h, - OFMessage m) { - h.counters.unhandledMessage.updateCounterNoFlush(); - if (log.isDebugEnabled()) { - String msg = getSwitchStateMessage(h, m, - "Ignoring unexpected message"); - log.debug(msg); - } - } - - /** - * Log an OpenFlow error message from a switch - * @param sw The switch that sent the error - * @param error The error message - */ - @LogMessageDoc(level="ERROR", - message="Error {error type} {error code} from {switch} " + - "in state {state}", - explanation="The switch responded with an unexpected error" + - "to an OpenFlow message from the controller", - recommendation="This could indicate improper network operation. " + - "If the problem persists restarting the switch and " + - "controller may help." - ) - protected void logError(OFChannelHandler h, OFError error) { - log.error("{} from switch {} in state {}", - new Object[] { - getErrorString(error), - h.getSwitchInfoString(), - this.toString()}); - } - - /** - * Log an OpenFlow error message from a switch and disconnect the - * channel - * @param sw The switch that sent the error - * @param error The error message - */ - protected void logErrorDisconnect(OFChannelHandler h, OFError error) { - logError(h, error); - h.channel.disconnect(); - } - - - /** - * Extract the role from an OFVendor message. - * - * Extract the role from an OFVendor message if the message is a - * Nicira role reply. Otherwise return null. - * - * @param h The channel handler receiving the message - * @param vendorMessage The vendor message to parse. - * @return The role in the message if the message is a Nicira role - * reply, null otherwise. - * @throws SwitchStateException If the message is a Nicira role reply - * but the numeric role value is unknown. - * FIXME: The message parser should make sure that the Nicira role is - * actually valid. Why do we need to take care of it ?!? - */ - protected Role extractNiciraRoleReply(OFChannelHandler h, - OFVendor vendorMessage) { - int vendor = vendorMessage.getVendor(); - if (vendor != OFNiciraVendorData.NX_VENDOR_ID) - return null; - if (! (vendorMessage.getVendorData() instanceof OFRoleReplyVendorData)) - return null; - OFRoleReplyVendorData roleReplyVendorData = - (OFRoleReplyVendorData) vendorMessage.getVendorData(); - Role role = Role.fromNxRole(roleReplyVendorData.getRole()); - if (role == null) { - String msg = String.format("Switch: [%s], State: [%s], " - + "received NX_ROLE_REPLY with invalid role " - + "value %d", - h.getSwitchInfoString(), - this.toString(), - roleReplyVendorData.getRole()); - throw new SwitchStateException(msg); - } - return role; - } - - /** - * Handle a port status message. - * - * Handle a port status message by updating the port maps in the - * IOFSwitch instance and notifying Controller about the change so - * it can dispatch a switch update. - * - * @param h The OFChannelHhandler that received the message - * @param m The PortStatus message we received - * @param doNotify if true switch port changed events will be - * dispatched - */ - protected void handlePortStatusMessage(OFChannelHandler h, - OFPortStatus m, - boolean doNotify) { - if (h.sw == null) { - String msg = getSwitchStateMessage(h, m, - "State machine error: switch is null. Should never " + - "happen"); - throw new SwitchStateException(msg); - } - Collection<PortChangeEvent> changes = h.sw.processOFPortStatus(m); - if (doNotify) { - for (PortChangeEvent ev: changes) - h.controller.notifyPortChanged(h.sw, ev.port, ev.type); - } - } - - /** - * Process an OF message received on the channel and - * update state accordingly. - * - * The main "event" of the state machine. Process the received message, - * send follow up message if required and update state if required. - * - * Switches on the message type and calls more specific event handlers - * for each individual OF message type. If we receive a message that - * is supposed to be sent from a controller to a switch we throw - * a SwitchStateExeption. - * - * The more specific handlers can also throw SwitchStateExceptions - * - * @param h The OFChannelHandler that received the message - * @param m The message we received. - * @throws SwitchStateException - * @throws IOException - */ - void processOFMessage(OFChannelHandler h, OFMessage m) throws IOException { - h.roleChanger.checkTimeout(); - switch(m.getType()) { - case HELLO: - processOFHello(h, (OFHello)m); - break; - case BARRIER_REPLY: - processOFBarrierReply(h, (OFBarrierReply)m); - break; - case ECHO_REPLY: - processOFEchoReply(h, (OFEchoReply)m); - break; - case ECHO_REQUEST: - processOFEchoRequest(h, (OFEchoRequest)m); - break; - case ERROR: - processOFError(h, (OFError)m); - break; - case FEATURES_REPLY: - processOFFeaturesReply(h, (OFFeaturesReply)m); - break; - case FLOW_REMOVED: - processOFFlowRemoved(h, (OFFlowRemoved)m); - break; - case GET_CONFIG_REPLY: - processOFGetConfigReply(h, (OFGetConfigReply)m); - break; - case PACKET_IN: - processOFPacketIn(h, (OFPacketIn)m); - break; - case PORT_STATUS: - processOFPortStatus(h, (OFPortStatus)m); - break; - case QUEUE_GET_CONFIG_REPLY: - processOFQueueGetConfigReply(h, (OFQueueGetConfigReply)m); - break; - case STATS_REPLY: - processOFStatisticsReply(h, (OFStatisticsReply)m); - break; - case VENDOR: - processOFVendor(h, (OFVendor)m); - break; - // The following messages are sent to switches. The controller - // should never receive them - case SET_CONFIG: - case GET_CONFIG_REQUEST: - case PACKET_OUT: - case PORT_MOD: - case QUEUE_GET_CONFIG_REQUEST: - case BARRIER_REQUEST: - case STATS_REQUEST: - case FEATURES_REQUEST: - case FLOW_MOD: - illegalMessageReceived(h, m); - break; - } - } - - /*----------------------------------------------------------------- - * Default implementation for message handlers in any state. - * - * Individual states must override these if they want a behavior - * that differs from the default. - * - * In general, these handlers simply ignore the message and do - * nothing. - * - * There are some exceptions though, since some messages really - * are handled the same way in every state (e.g., ECHO_REQUST) or - * that are only valid in a single state (e.g., HELLO, GET_CONFIG_REPLY - -----------------------------------------------------------------*/ - - void processOFHello(OFChannelHandler h, OFHello m) throws IOException { - // we only expect hello in the WAIT_HELLO state - illegalMessageReceived(h, m); - } - - void processOFBarrierReply(OFChannelHandler h, OFBarrierReply m) - throws IOException { - // Silently ignore. - } - - void processOFEchoRequest(OFChannelHandler h, OFEchoRequest m) - throws IOException { - OFEchoReply reply = (OFEchoReply) - BasicFactory.getInstance().getMessage(OFType.ECHO_REPLY); - reply.setXid(m.getXid()); - reply.setPayload(m.getPayload()); - reply.setLengthU(m.getLengthU()); - h.channel.write(Collections.singletonList(reply)); - } - - void processOFEchoReply(OFChannelHandler h, OFEchoReply m) - throws IOException { - // Do nothing with EchoReplies !! - } - - // no default implementation for OFError - // every state must override it - abstract void processOFError(OFChannelHandler h, OFError m) - throws IOException; - - - void processOFFeaturesReply(OFChannelHandler h, OFFeaturesReply m) - throws IOException { - unhandledMessageReceived(h, m); - } - - void processOFFlowRemoved(OFChannelHandler h, OFFlowRemoved m) - throws IOException { - unhandledMessageReceived(h, m); - } - - void processOFGetConfigReply(OFChannelHandler h, OFGetConfigReply m) - throws IOException { - // we only expect config replies in the WAIT_CONFIG_REPLY state - // TODO: might use two different strategies depending on whether - // we got a miss length of 64k or not. - illegalMessageReceived(h, m); - } - - void processOFPacketIn(OFChannelHandler h, OFPacketIn m) - throws IOException { - unhandledMessageReceived(h, m); - } - - // bi default implementation. Every state needs to handle it. - abstract void processOFPortStatus(OFChannelHandler h, OFPortStatus m) - throws IOException; - - void processOFQueueGetConfigReply(OFChannelHandler h, - OFQueueGetConfigReply m) - throws IOException { - unhandledMessageReceived(h, m); - } - - void processOFStatisticsReply(OFChannelHandler h, OFStatisticsReply m) - throws IOException { - unhandledMessageReceived(h, m); - } - - void processOFVendor(OFChannelHandler h, OFVendor m) - throws IOException { - // TODO: it might make sense to parse the vendor message here - // into the known vendor messages we support and then call more - // spefic event handlers - unhandledMessageReceived(h, m); - } - } - - - /** - * Create a new unconnecte OFChannelHandler. - * @param controller - */ - OFChannelHandler(Controller controller) { - this.controller = controller; - this.counters = controller.getCounters(); - this.roleChanger = new RoleChanger(DEFAULT_ROLE_TIMEOUT_MS); - this.state = ChannelState.INIT; - this.pendingPortStatusMsg = new ArrayList<OFPortStatus>(); - } - - /** - * Is this a state in which the handshake has completed? - * @return true if the handshake is complete - */ - boolean isHandshakeComplete() { - return this.state.isHandshakeComplete(); - } - - /** - * Forwards to RoleChanger. See there. - * @param role - */ - void sendRoleRequestIfNotPending(Role role) { - try { - roleChanger.sendRoleRequestIfNotPending(role); - } catch (IOException e) { - log.error("Disconnecting switch {} due to IO Error: {}", - getSwitchInfoString(), e.getMessage()); - channel.close(); - } - } - - /** - * Forwards to RoleChanger. See there. - * @param role - */ - void sendRoleRequest(Role role) { - try { - roleChanger.sendRoleRequest(role); - } catch (IOException e) { - log.error("Disconnecting switch {} due to IO Error: {}", - getSwitchInfoString(), e.getMessage()); - channel.close(); - } - } - - - @Override - @LogMessageDoc(message="New switch connection from {ip address}", - explanation="A new switch has connected from the " + - "specified IP address") - public void channelConnected(ChannelHandlerContext ctx, - ChannelStateEvent e) throws Exception { - counters.switchConnected.updateCounterWithFlush(); - channel = e.getChannel(); - log.info("New switch connection from {}", - channel.getRemoteAddress()); - sendHandShakeMessage(OFType.HELLO); - setState(ChannelState.WAIT_HELLO); - } - - @Override - @LogMessageDoc(message="Disconnected switch {switch information}", - explanation="The specified switch has disconnected.") - public void channelDisconnected(ChannelHandlerContext ctx, - ChannelStateEvent e) throws Exception { - controller.removeSwitchChannel(this); - if (this.sw != null) { - // TODO: switchDisconnected() will check if we've previously - // activated the switch. Nevertheless, we might want to check - // here as well. - controller.switchDisconnected(this.sw); - this.sw.setConnected(false); - } - - log.info("Disconnected switch {}", getSwitchInfoString()); - } - - @Override - @LogMessageDocs({ - @LogMessageDoc(level="ERROR", - message="Disconnecting switch {switch} due to read timeout", - explanation="The connected switch has failed to send any " + - "messages or respond to echo requests", - recommendation=LogMessageDoc.CHECK_SWITCH), - @LogMessageDoc(level="ERROR", - message="Disconnecting switch {switch}: failed to " + - "complete handshake", - explanation="The switch did not respond correctly " + - "to handshake messages", - recommendation=LogMessageDoc.CHECK_SWITCH), - @LogMessageDoc(level="ERROR", - message="Disconnecting switch {switch} due to IO Error: {}", - explanation="There was an error communicating with the switch", - recommendation=LogMessageDoc.CHECK_SWITCH), - @LogMessageDoc(level="ERROR", - message="Disconnecting switch {switch} due to switch " + - "state error: {error}", - explanation="The switch sent an unexpected message", - recommendation=LogMessageDoc.CHECK_SWITCH), - @LogMessageDoc(level="ERROR", - message="Disconnecting switch {switch} due to " + - "message parse failure", - explanation="Could not parse a message from the switch", - recommendation=LogMessageDoc.CHECK_SWITCH), - @LogMessageDoc(level="ERROR", - message="Terminating controller due to storage exception", - explanation=Controller.ERROR_DATABASE, - recommendation=LogMessageDoc.CHECK_CONTROLLER), - @LogMessageDoc(level="ERROR", - message="Could not process message: queue full", - explanation="OpenFlow messages are arriving faster than " + - " the controller can process them.", - recommendation=LogMessageDoc.CHECK_CONTROLLER), - @LogMessageDoc(level="ERROR", - message="Error while processing message " + - "from switch {switch} {cause}", - explanation="An error occurred processing the switch message", - recommendation=LogMessageDoc.GENERIC_ACTION) - }) - public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) - throws Exception { - if (e.getCause() instanceof ReadTimeoutException) { - // switch timeout - log.error("Disconnecting switch {} due to read timeout", - getSwitchInfoString()); - counters.switchDisconnectReadTimeout.updateCounterWithFlush(); - ctx.getChannel().close(); - } else if (e.getCause() instanceof HandshakeTimeoutException) { - log.error("Disconnecting switch {}: failed to complete handshake", - getSwitchInfoString()); - counters.switchDisconnectHandshakeTimeout.updateCounterWithFlush(); - ctx.getChannel().close(); - } else if (e.getCause() instanceof ClosedChannelException) { - log.debug("Channel for sw {} already closed", getSwitchInfoString()); - } else if (e.getCause() instanceof IOException) { - log.error("Disconnecting switch {} due to IO Error: {}", - getSwitchInfoString(), e.getCause().getMessage()); - if (log.isDebugEnabled()) { - // still print stack trace if debug is enabled - log.debug("StackTrace for previous Exception: ", e.getCause()); - } - counters.switchDisconnectIOError.updateCounterWithFlush(); - ctx.getChannel().close(); - } else if (e.getCause() instanceof SwitchStateException) { - log.error("Disconnecting switch {} due to switch state error: {}", - getSwitchInfoString(), e.getCause().getMessage()); - if (log.isDebugEnabled()) { - // still print stack trace if debug is enabled - log.debug("StackTrace for previous Exception: ", e.getCause()); - } - counters.switchDisconnectSwitchStateException.updateCounterWithFlush(); - ctx.getChannel().close(); - } else if (e.getCause() instanceof MessageParseException) { - log.error("Disconnecting switch " - + getSwitchInfoString() + - " due to message parse failure", - e.getCause()); - counters.switchDisconnectParseError.updateCounterWithFlush(); - ctx.getChannel().close(); - } else if (e.getCause() instanceof StorageException) { - log.error("Terminating controller due to storage exception", - e.getCause()); - this.controller.terminate(); - } else if (e.getCause() instanceof RejectedExecutionException) { - log.warn("Could not process message: queue full"); - counters.rejectedExecutionException.updateCounterWithFlush(); - } else { - log.error("Error while processing message from switch " - + getSwitchInfoString() - + "state " + this.state, e.getCause()); - counters.switchDisconnectOtherException.updateCounterWithFlush(); - ctx.getChannel().close(); - } - } - - @Override - public void channelIdle(ChannelHandlerContext ctx, IdleStateEvent e) - throws Exception { - OFMessage m = BasicFactory.getInstance().getMessage(OFType.ECHO_REQUEST); - e.getChannel().write(Collections.singletonList(m)); - } - - @Override - public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) - throws Exception { - if (e.getMessage() instanceof List) { - @SuppressWarnings("unchecked") - List<OFMessage> msglist = (List<OFMessage>)e.getMessage(); - - LoadMonitor.LoadLevel loadlevel; - int packets_dropped = 0; - int packets_allowed = 0; - int lldps_allowed = 0; - - if (this.controller.overload_drop) { - loadlevel = this.controller.loadmonitor.getLoadLevel(); - } - else { - loadlevel = LoadMonitor.LoadLevel.OK; - } - - for (OFMessage ofm : msglist) { - counters.messageReceived.updateCounterNoFlush(); - // Per-switch input throttling - if (sw != null && sw.inputThrottled(ofm)) { - counters.messageInputThrottled.updateCounterNoFlush(); - continue; - } - try { - if (this.controller.overload_drop && - !loadlevel.equals(LoadMonitor.LoadLevel.OK)) { - switch (ofm.getType()) { - case PACKET_IN: - switch (loadlevel) { - case VERYHIGH: - // Drop all packet-ins, including LLDP/BDDPs - packets_dropped++; - continue; - case HIGH: - // Drop all packet-ins, except LLDP/BDDPs - byte[] data = ((OFPacketIn)ofm).getPacketData(); - if (data.length > 14) { - if (((data[12] == (byte)0x88) && - (data[13] == (byte)0xcc)) || - ((data[12] == (byte)0x89) && - (data[13] == (byte)0x42))) { - lldps_allowed++; - packets_allowed++; - break; - } - } - packets_dropped++; - continue; - default: - // Load not high, go ahead and process msg - packets_allowed++; - break; - } - break; - default: - // Process all non-packet-ins - packets_allowed++; - break; - } - } - - // Do the actual packet processing - state.processOFMessage(this, ofm); - - } - catch (Exception ex) { - // We are the last handler in the stream, so run the - // exception through the channel again by passing in - // ctx.getChannel(). - Channels.fireExceptionCaught(ctx.getChannel(), ex); - } - } - - if (loadlevel != LoadMonitor.LoadLevel.OK) { - if (log.isDebugEnabled()) { - log.debug( - "Overload: Detected {}, packets dropped={}", - loadlevel.toString(), packets_dropped); - log.debug( - "Overload: Packets allowed={} (LLDP/BDDPs allowed={})", - packets_allowed, lldps_allowed); - } - } - // Flush all thread local queues etc. generated by this train - // of messages. - this.controller.flushAll(); - } - else { - Channels.fireExceptionCaught(ctx.getChannel(), - new AssertionError("Message received from Channel is not a list")); - } - } - - - /** - * Get a useable error string from the OFError. - * @param error - * @return - */ - public static String getErrorString(OFError error) { - // TODO: this really should be OFError.toString. Sigh. - int etint = 0xffff & error.getErrorType(); - if (etint < 0 || etint >= OFErrorType.values().length) { - return String.format("Unknown error type %d", etint); - } - OFErrorType et = OFErrorType.values()[etint]; - switch (et) { - case OFPET_HELLO_FAILED: - OFHelloFailedCode hfc = - OFHelloFailedCode.values()[0xffff & error.getErrorCode()]; - return String.format("Error %s %s", et, hfc); - case OFPET_BAD_REQUEST: - OFBadRequestCode brc = - OFBadRequestCode.values()[0xffff & error.getErrorCode()]; - return String.format("Error %s %s", et, brc); - case OFPET_BAD_ACTION: - OFBadActionCode bac = - OFBadActionCode.values()[0xffff & error.getErrorCode()]; - return String.format("Error %s %s", et, bac); - case OFPET_FLOW_MOD_FAILED: - OFFlowModFailedCode fmfc = - OFFlowModFailedCode.values()[0xffff & error.getErrorCode()]; - return String.format("Error %s %s", et, fmfc); - case OFPET_PORT_MOD_FAILED: - OFPortModFailedCode pmfc = - OFPortModFailedCode.values()[0xffff & error.getErrorCode()]; - return String.format("Error %s %s", et, pmfc); - case OFPET_QUEUE_OP_FAILED: - OFQueueOpFailedCode qofc = - OFQueueOpFailedCode.values()[0xffff & error.getErrorCode()]; - return String.format("Error %s %s", et, qofc); - case OFPET_VENDOR_ERROR: - // no codes known for vendor error - return String.format("Error %s", et); - } - return null; - } - - private void dispatchMessage(OFMessage m) throws IOException { - // handleMessage will count - this.controller.handleMessage(this.sw, m, null); - } - - /** - * Return a string describing this switch based on the already available - * information (DPID and/or remote socket) - * @return - */ - private String getSwitchInfoString() { - if (sw != null) - return sw.toString(); - String channelString; - if (channel == null || channel.getRemoteAddress() == null) { - channelString = "?"; - } else { - channelString = channel.getRemoteAddress().toString(); - } - String dpidString; - if (featuresReply == null) { - dpidString = "?"; - } else { - dpidString = HexString.toHexString(featuresReply.getDatapathId()); - } - return String.format("[%s DPID[%s]]", channelString, dpidString); - } - - /** - * Update the channels state. Only called from the state machine. - * TODO: enforce restricted state transitions - * @param state - */ - private void setState(ChannelState state) { - this.state = state; - } - - /** - * Send a message to the switch using the handshake transactions ids. - * @throws IOException - */ - private void sendHandShakeMessage(OFType type) throws IOException { - // Send initial Features Request - OFMessage m = BasicFactory.getInstance().getMessage(type); - m.setXid(handshakeTransactionIds--); - channel.write(Collections.singletonList(m)); - } - - /** - * Send an setL2TableSet message to the switch. - */ - private void sendHandshakeL2TableSet() { - OFVendor l2TableSet = (OFVendor) - BasicFactory.getInstance().getMessage(OFType.VENDOR); - l2TableSet.setXid(handshakeTransactionIds--); - OFBsnL2TableSetVendorData l2TableSetData = - new OFBsnL2TableSetVendorData(true, - controller.getCoreFlowPriority()); - l2TableSet.setVendor(OFBigSwitchVendorData.BSN_VENDOR_ID); - l2TableSet.setVendorData(l2TableSetData); - l2TableSet.setLengthU(OFVendor.MINIMUM_LENGTH + - l2TableSetData.getLength()); - channel.write(Collections.singletonList(l2TableSet)); - } - - - private void gotoWaitInitialRoleState() { - // We need to set the new state /before/ we call addSwitchChannel - // because addSwitchChannel will eventually call setRole - // which can in turn decide that the switch doesn't support - // roles and transition the state straight to MASTER. - setState(ChannelState.WAIT_INITIAL_ROLE); - controller.addSwitchChannelAndSendInitialRole(this); - } - - /** - * Send the configuration requests to tell the switch we want full - * packets - * @throws IOException - */ - private void sendHandshakeSetConfig() throws IOException { - List<OFMessage> msglist = new ArrayList<OFMessage>(3); - - // Ensure we receive the full packet via PacketIn - // FIXME: We don't set the reassembly flags. - OFSetConfig configSet = (OFSetConfig) BasicFactory.getInstance() - .getMessage(OFType.SET_CONFIG); - configSet.setMissSendLength((short) 0xffff) - .setLengthU(OFSwitchConfig.MINIMUM_LENGTH); - configSet.setXid(handshakeTransactionIds--); - msglist.add(configSet); - - // Barrier - OFBarrierRequest barrier = (OFBarrierRequest) BasicFactory.getInstance() - .getMessage(OFType.BARRIER_REQUEST); - barrier.setXid(handshakeTransactionIds--); - msglist.add(barrier); - - // Verify (need barrier?) - OFGetConfigRequest configReq = (OFGetConfigRequest) - BasicFactory.getInstance().getMessage(OFType.GET_CONFIG_REQUEST); - configReq.setXid(handshakeTransactionIds--); - msglist.add(configReq); - channel.write(msglist); - } - - /** - * send a description state request - * @throws IOException - */ - private void sendHandshakeDescriptionStatsRequest() throws IOException { - // Get Description to set switch-specific flags - OFStatisticsRequest req = new OFStatisticsRequest(); - req.setStatisticType(OFStatisticsType.DESC); - req.setXid(handshakeTransactionIds--); - - channel.write(Collections.singletonList(req)); - } - - - /** - * Read switch properties from storage and set switch attributes accordingly - */ - private void readPropertyFromStorage() { - // At this time, also set other switch properties from storage - boolean is_core_switch = false; - IResultSet resultSet = null; - try { - String swid = sw.getStringId(); - resultSet = this.controller.getStorageSourceService() - .getRow(Controller.SWITCH_CONFIG_TABLE_NAME, swid); - for (Iterator<IResultSet> it = - resultSet.iterator(); it.hasNext();) { - is_core_switch = it.next() - .getBoolean(Controller.SWITCH_CONFIG_CORE_SWITCH); - if (log.isDebugEnabled()) { - log.debug("Reading SWITCH_IS_CORE_SWITCH " + - "config for switch={}, is-core={}", - sw, is_core_switch); - } - } - } - finally { - if (resultSet != null) - resultSet.close(); - } - if (is_core_switch) { - sw.setAttribute(IOFSwitch.SWITCH_IS_CORE_SWITCH, - Boolean.valueOf(true)); - } - } - - ChannelState getStateForTesting() { - return state; - } - - void useRoleChangerWithOtherTimeoutForTesting(long roleTimeoutMs) { - roleChanger = new RoleChanger(roleTimeoutMs); - } +class OFChannelHandler extends IdleStateAwareChannelHandler { + + private static final Logger log = LoggerFactory.getLogger(OFChannelHandler.class); + + private final ChannelPipeline pipeline; + private final INewOFConnectionListener newConnectionListener; + private final SwitchManagerCounters counters; + private Channel channel; + private final Timer timer; + private volatile OFChannelState state; + private OFFactory factory = OFFactories.getFactory(OFVersion.OF_13); + private OFFeaturesReply featuresReply; + private volatile OFConnection connection; + private final IDebugCounterService debugCounters; + + /** transaction Ids to use during handshake. Since only one thread + * calls into the OFChannelHandler we don't need atomic. + * We will count down + */ + private long handshakeTransactionIds = 0x00FFFFFFFFL; + + + /** + * Default implementation for message handlers in any OFChannelState. + * + * Individual states must override these if they want a behavior + * that differs from the default. + */ + public abstract class OFChannelState { + + void processOFHello(OFHello m) + throws IOException { + // we only expect hello in the WAIT_HELLO state + illegalMessageReceived(m); + } + + void processOFEchoRequest(OFEchoRequest m) + throws IOException { + sendEchoReply(m); + } + + void processOFEchoReply(OFEchoReply m) + throws IOException { + // do nothing + } + + void processOFError(OFErrorMsg m) { + logErrorDisconnect(m); + } + + void processOFExperimenter(OFExperimenter m) { + unhandledMessageReceived(m); + } + + void processOFFeaturesReply(OFFeaturesReply m) + throws IOException { + // we only expect features reply in the WAIT_FEATURES_REPLY state + illegalMessageReceived(m); + } + + private final boolean channelHandshakeComplete; + + OFChannelState(boolean handshakeComplete) { + this.channelHandshakeComplete = handshakeComplete; + } + + void logState() { + log.debug("{} OFConnection Handshake - enter state {}", + getConnectionInfoString(), this.getClass().getSimpleName()); + } + + /** enter this state. Can initialize the handler, send + * the necessary messages, etc. + * @throws IOException + */ + void enterState() throws IOException{ + // Do Nothing + } + + /** + * Get a string specifying the switch connection, state, and + * message received. To be used as message for SwitchStateException + * or log messages + * @param h The channel handler (to get switch information_ + * @param m The OFMessage that has just been received + * @param details A string giving more details about the exact nature + * of the problem. + * @return + */ + // needs to be protected because enum members are acutally subclasses + protected String getSwitchStateMessage(OFMessage m, + String details) { + return String.format("Switch: [%s], State: [%s], received: [%s]" + + ", details: %s", + getConnectionInfoString(), + this.toString(), + m.getType().toString(), + details); + } + + /** + * We have an OFMessage we didn't expect given the current state and + * we want to treat this as an error. + * We currently throw an exception that will terminate the connection + * However, we could be more forgiving + * @param h the channel handler that received the message + * @param m the message + * @throws SwitchStateExeption we always through the execption + */ + // needs to be protected because enum members are acutally subclasses + protected void illegalMessageReceived(OFMessage m) { + String msg = getSwitchStateMessage(m, + "Switch should never send this message in the current state"); + throw new SwitchStateException(msg); + + } + + /** + * We have an OFMessage we didn't expect given the current state and + * we want to ignore the message + * @param h the channel handler the received the message + * @param m the message + */ + protected void unhandledMessageReceived(OFMessage m) { + counters.unhandledMessage.increment(); + if (log.isDebugEnabled()) { + String msg = getSwitchStateMessage(m, + "Ignoring unexpected message"); + log.debug(msg); + } + } + + /** + * Log an OpenFlow error message from a switch + * @param sw The switch that sent the error + * @param error The error message + */ + @LogMessageDoc(level="ERROR", + message="Error {error type} {error code} from {switch} " + + "in state {state}", + explanation="The switch responded with an unexpected error" + + "to an OpenFlow message from the controller", + recommendation="This could indicate improper network operation. " + + "If the problem persists restarting the switch and " + + "controller may help." + ) + protected void logError(OFErrorMsg error) { + log.error("{} from switch {} in state {}", + new Object[] { + error.toString(), + getConnectionInfoString(), + this.toString()}); + } + + /** + * Log an OpenFlow error message from a switch and disconnect the + * channel + * @param sw The switch that sent the error + * @param error The error message + */ + protected void logErrorDisconnect(OFErrorMsg error) { + logError(error); + channel.disconnect(); + } + + /** + * Process an OF message received on the channel and + * update state accordingly. + * + * The main "event" of the state machine. Process the received message, + * send follow up message if required and update state if required. + * + * Switches on the message type and calls more specific event handlers + * for each individual OF message type. If we receive a message that + * is supposed to be sent from a controller to a switch we throw + * a SwitchStateExeption. + * + * The more specific handlers can also throw SwitchStateExceptions + * + * @param h The OFChannelHandler that received the message + * @param m The message we received. + * @throws SwitchStateException + * @throws IOException + */ + void processOFMessage(OFMessage m) + throws IOException { + // Handle Channel Handshake + if (!state.channelHandshakeComplete) { + switch(m.getType()) { + case HELLO: + processOFHello((OFHello)m); + break; + case ERROR: + processOFError((OFErrorMsg)m); + break; + case FEATURES_REPLY: + processOFFeaturesReply((OFFeaturesReply)m); + break; + case EXPERIMENTER: + processOFExperimenter((OFExperimenter)m); + break; + default: + illegalMessageReceived(m); + break; + } + } + else{ + switch(m.getType()){ + // Always handle echos at the channel level! + // Echos should only be sent in the complete. + case ECHO_REPLY: + processOFEchoReply((OFEchoReply)m); + break; + case ECHO_REQUEST: + processOFEchoRequest((OFEchoRequest)m); + break; + // Send to SwitchManager and thus higher orders of control + default: + sendMessageToConnection(m); + break; + } + } + } + } + + /** + * Initial state before channel is connected. + */ + class InitState extends OFChannelState { + + InitState() { + super(false); + } + } + + /** + * We send a HELLO to the switch and wait for a reply. + * Once we receive the reply we send an OFFeaturesRequest + * Next state is WaitFeaturesReplyState + */ + class WaitHelloState extends OFChannelState { + + WaitHelloState() { + super(false); + } + + @Override + void processOFHello(OFHello m) throws IOException { + OFVersion version = m.getVersion(); + factory = OFFactories.getFactory(version); + OFMessageDecoder decoder = pipeline.get(OFMessageDecoder.class); + decoder.setVersion(version); + setState(new WaitFeaturesReplyState()); + } + + @Override + void enterState() throws IOException { + sendHelloMessage(); + } + } + + /** + * We are waiting for a features reply message. Once we receive it + * we send capture the features reply. + * Next state is CompleteState + */ + class WaitFeaturesReplyState extends OFChannelState{ + + WaitFeaturesReplyState() { + super(false); + } + @Override + void processOFFeaturesReply(OFFeaturesReply m) + throws IOException { + featuresReply = m; + + // Mark handshake as completed + setState(new CompleteState()); + + } + @Override + void enterState() throws IOException { + sendFeaturesRequest(); + } + }; + + /** + * This state denotes that the channel handshaking is complete. + * An OF connection is generated and passed to the switch manager + * for handling. + */ + class CompleteState extends OFChannelState{ + + CompleteState() { + super(true); + } + + @Override + void enterState() throws IOException{ + + setSwitchHandshakeTimeout(); + + // Handle non 1.3 connections + if(featuresReply.getVersion().compareTo(OFVersion.OF_13) < 0){ + connection = new OFConnection(featuresReply.getDatapathId(), factory, channel, OFAuxId.MAIN, debugCounters, timer); + } + // Handle 1.3 connections + else{ + connection = new OFConnection(featuresReply.getDatapathId(), factory, channel, featuresReply.getAuxiliaryId(), debugCounters, timer); + + // If this is an aux connection, we set a longer echo idle time + if (!featuresReply.getAuxiliaryId().equals(OFAuxId.MAIN)) { + setAuxChannelIdle(); + } + } + // Notify the connection broker + notifyConnectionOpened(connection); + + } + }; + + /** + * Creates a handler for interacting with the switch channel + * + * @param controller + * the controller + * @param newConnectionListener + * the class that listens for new OF connections (switchManager) + * @param pipeline + * the channel pipeline + * @param threadPool + * the thread pool + * @param idleTimer + * the hash wheeled timer used to send idle messages (echo). + * passed to constructor to modify in case of aux connection. + * @param debugCounters + */ + OFChannelHandler(@Nonnull IOFSwitchManager switchManager, + @Nonnull INewOFConnectionListener newConnectionListener, + @Nonnull ChannelPipeline pipeline, + @Nonnull IDebugCounterService debugCounters, + @Nonnull Timer timer) { + + Preconditions.checkNotNull(switchManager, "switchManager"); + Preconditions.checkNotNull(newConnectionListener, "connectionOpenedListener"); + Preconditions.checkNotNull(pipeline, "pipeline"); + Preconditions.checkNotNull(timer, "timer"); + Preconditions.checkNotNull(debugCounters, "debugCounters"); + + this.pipeline = pipeline; + this.debugCounters = debugCounters; + this.newConnectionListener = newConnectionListener; + this.counters = switchManager.getCounters(); + this.state = new InitState(); + this.timer = timer; + + log.debug("constructor on OFChannelHandler {}", String.format("%08x", System.identityHashCode(this))); + } + + /** + * Determines if the entire switch handshake is complete (channel+switch). + * If the channel handshake is complete the call is forwarded to the + * connection listener/switch manager to be handled by the appropriate + * switch handshake handler. + * + * @return whether or not complete switch handshake is complete + */ + public boolean isSwitchHandshakeComplete() { + if (this.state.channelHandshakeComplete) { + return connection.getListener().isSwitchHandshakeComplete(connection); + } else { + return false; + } + } + + /** + * Notifies the channel listener that we have a valid baseline connection + */ + private final void notifyConnectionOpened(OFConnection connection){ + this.connection = connection; + this.newConnectionListener.connectionOpened(connection, featuresReply); + } + + /** + * Notifies the channel listener that we our connection has been closed + */ + private final void notifyConnectionClosed(OFConnection connection){ + connection.getListener().connectionClosed(connection); + } + + /** + * Notifies the channel listener that we have a valid baseline connection + */ + private final void sendMessageToConnection(OFMessage m) { + connection.messageReceived(m); + } + + @Override + @LogMessageDoc(message="New switch connection from {ip address}", + explanation="A new switch has connected from the " + + "specified IP address") + public void channelConnected(ChannelHandlerContext ctx, + ChannelStateEvent e) throws Exception { + log.debug("channelConnected on OFChannelHandler {}", String.format("%08x", System.identityHashCode(this))); + counters.switchConnected.increment(); + channel = e.getChannel(); + log.info("New switch connection from {}", + channel.getRemoteAddress()); + setState(new WaitHelloState()); + } + + @Override + @LogMessageDoc(message="Disconnected switch {switch information}", + explanation="The specified switch has disconnected.") + public void channelDisconnected(ChannelHandlerContext ctx, + ChannelStateEvent e) throws Exception { + // Only handle cleanup connection is even known + if(this.connection != null){ + // Alert the connection object that the channel has been disconnected + this.connection.disconnected(); + // Punt the cleanup to the Switch Manager + notifyConnectionClosed(this.connection); + } + log.info("[{}] Disconnected connection", getConnectionInfoString()); + } + + @Override + @LogMessageDocs({ + @LogMessageDoc(level="ERROR", + message="Disconnecting switch {switch} due to read timeout", + explanation="The connected switch has failed to send any " + + "messages or respond to echo requests", + recommendation=LogMessageDoc.CHECK_SWITCH), + @LogMessageDoc(level="ERROR", + message="Disconnecting switch {switch}: failed to " + + "complete handshake", + explanation="The switch did not respond correctly " + + "to handshake messages", + recommendation=LogMessageDoc.CHECK_SWITCH), + @LogMessageDoc(level="ERROR", + message="Disconnecting switch {switch} due to IO Error: {}", + explanation="There was an error communicating with the switch", + recommendation=LogMessageDoc.CHECK_SWITCH), + @LogMessageDoc(level="ERROR", + message="Disconnecting switch {switch} due to switch " + + "state error: {error}", + explanation="The switch sent an unexpected message", + recommendation=LogMessageDoc.CHECK_SWITCH), + @LogMessageDoc(level="ERROR", + message="Disconnecting switch {switch} due to " + + "message parse failure", + explanation="Could not parse a message from the switch", + recommendation=LogMessageDoc.CHECK_SWITCH), + @LogMessageDoc(level="ERROR", + message="Terminating controller due to storage exception", + explanation=Controller.ERROR_DATABASE, + recommendation=LogMessageDoc.CHECK_CONTROLLER), + @LogMessageDoc(level="ERROR", + message="Could not process message: queue full", + explanation="OpenFlow messages are arriving faster than " + + " the controller can process them.", + recommendation=LogMessageDoc.CHECK_CONTROLLER), + @LogMessageDoc(level="ERROR", + message="Error while processing message " + + "from switch {switch} {cause}", + explanation="An error occurred processing the switch message", + recommendation=LogMessageDoc.GENERIC_ACTION) + }) + public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) + throws Exception { + if (e.getCause() instanceof ReadTimeoutException) { + + if (featuresReply.getVersion().compareTo(OFVersion.OF_13) < 0) { + log.error("Disconnecting switch {} due to read timeout on main cxn.", + getConnectionInfoString()); + ctx.getChannel().close(); + } else { + if (featuresReply.getAuxiliaryId().equals(OFAuxId.MAIN)) { + log.error("Disconnecting switch {} due to read timeout on main cxn.", + getConnectionInfoString()); + ctx.getChannel().close(); + } else { + // We only don't disconnect on aux connections + log.warn("Switch {} encountered read timeout on aux cxn.", + getConnectionInfoString()); + } + } + // Increment counters + counters.switchDisconnectReadTimeout.increment(); + + } else if (e.getCause() instanceof HandshakeTimeoutException) { + log.error("Disconnecting switch {}: failed to complete handshake. Channel handshake complete : {}", + getConnectionInfoString(), + this.state.channelHandshakeComplete); + counters.switchDisconnectHandshakeTimeout.increment(); + ctx.getChannel().close(); + } else if (e.getCause() instanceof ClosedChannelException) { + log.debug("Channel for sw {} already closed", getConnectionInfoString()); + } else if (e.getCause() instanceof IOException) { + log.error("Disconnecting switch {} due to IO Error: {}", + getConnectionInfoString(), e.getCause().getMessage()); + if (log.isDebugEnabled()) { + // still print stack trace if debug is enabled + log.debug("StackTrace for previous Exception: ", e.getCause()); + } + counters.switchDisconnectIOError.increment(); + ctx.getChannel().close(); + } else if (e.getCause() instanceof SwitchStateException) { + log.error("Disconnecting switch {} due to switch state error: {}", + getConnectionInfoString(), e.getCause().getMessage()); + if (log.isDebugEnabled()) { + // still print stack trace if debug is enabled + log.debug("StackTrace for previous Exception: ", e.getCause()); + } + counters.switchDisconnectSwitchStateException.increment(); + ctx.getChannel().close(); + } else if (e.getCause() instanceof OFAuxException) { + log.error("Disconnecting switch {} due to OF Aux error: {}", + getConnectionInfoString(), e.getCause().getMessage()); + if (log.isDebugEnabled()) { + // still print stack trace if debug is enabled + log.debug("StackTrace for previous Exception: ", e.getCause()); + } + counters.switchDisconnectSwitchStateException.increment(); + ctx.getChannel().close(); + } else if (e.getCause() instanceof OFParseError) { + log.error("Disconnecting switch " + + getConnectionInfoString() + + " due to message parse failure", + e.getCause()); + counters.switchDisconnectParseError.increment(); + ctx.getChannel().close(); + } else if (e.getCause() instanceof RejectedExecutionException) { + log.warn("Could not process message: queue full"); + counters.rejectedExecutionException.increment(); + } else { + log.error("Error while processing message from switch " + + getConnectionInfoString() + + "state " + this.state, e.getCause()); + counters.switchDisconnectOtherException.increment(); + ctx.getChannel().close(); + } + } + + @Override + public void channelIdle(ChannelHandlerContext ctx, IdleStateEvent e) + throws Exception { + + log.debug("channelIdle on OFChannelHandler {}", String.format("%08x", System.identityHashCode(this))); + OFChannelHandler handler = ctx.getPipeline().get(OFChannelHandler.class); + handler.sendEchoRequest(); + } + + @Override + public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) + throws Exception { + if (e.getMessage() instanceof List) { + @SuppressWarnings("unchecked") + List<OFMessage> msglist = (List<OFMessage>)e.getMessage(); + for (OFMessage ofm : msglist) { + try { + // Do the actual packet processing + state.processOFMessage(ofm); + } + catch (Exception ex) { + // We are the last handler in the stream, so run the + // exception through the channel again by passing in + // ctx.getChannel(). + Channels.fireExceptionCaught(ctx.getChannel(), ex); + } + } + } + else { + Channels.fireExceptionCaught(ctx.getChannel(), + new AssertionError("Message received from channel is not a list")); + } + } + + /** + * Sets the channel pipeline's idle (Echo) timeouts to a longer interval. + * This is specifically for aux channels. + */ + private void setAuxChannelIdle() { + + IdleStateHandler idleHandler = new IdleStateHandler( + this.timer, + PipelineIdleReadTimeout.AUX, + PipelineIdleWriteTimeout.AUX, + 0); + pipeline.replace(PipelineHandler.MAIN_IDLE, + PipelineHandler.AUX_IDLE, + idleHandler); + } + + /** + * Sets the channel pipeline's handshake timeout to a more appropriate value + * for the remaining part of the switch handshake. + */ + private void setSwitchHandshakeTimeout() { + + HandshakeTimeoutHandler handler = new HandshakeTimeoutHandler( + this, + this.timer, + PipelineHandshakeTimeout.SWITCH); + + pipeline.replace(PipelineHandler.CHANNEL_HANDSHAKE_TIMEOUT, + PipelineHandler.SWITCH_HANDSHAKE_TIMEOUT, handler); + } + + /** + * Return a string describing this switch based on the already available + * information (DPID and/or remote socket) + * @return + */ + private String getConnectionInfoString() { + + String channelString; + if (channel == null || channel.getRemoteAddress() == null) { + channelString = "?"; + } else { + channelString = channel.getRemoteAddress().toString(); + if(channelString.startsWith("/")) + channelString = channelString.substring(1); + } + String dpidString; + if (featuresReply == null) { + dpidString = "?"; + } else { + StringBuilder b = new StringBuilder(); + b.append(featuresReply.getDatapathId()); + if(featuresReply.getVersion().compareTo(OFVersion.OF_13) >= 0) { + b.append("(").append(featuresReply.getAuxiliaryId()).append(")"); + } + dpidString = b.toString(); + } + return String.format("[%s from %s]", dpidString, channelString ); + } + + /** + * Update the channels state. Only called from the state machine. + * @param state + * @throws IOException + */ + private void setState(OFChannelState state) throws IOException { + this.state = state; + state.logState(); + state.enterState(); + } + + /** + * Send a features request message to the switch using the handshake + * transactions ids. + * @throws IOException + */ + private void sendFeaturesRequest() throws IOException { + // Send initial Features Request + OFFeaturesRequest m = factory.buildFeaturesRequest() + .setXid(handshakeTransactionIds--) + .build(); + channel.write(Collections.singletonList(m)); + } + + /** + * Send a hello message to the switch using the handshake transactions ids. + * @throws IOException + */ + private void sendHelloMessage() throws IOException { + // Send initial hello message + // FIXME:LOJI: Haven't negotiated version yet, assume 1.3 + OFHello.Builder builder = factory.buildHello() + .setXid(handshakeTransactionIds--); + // FIXME: Need to add code here to set the version bitmap hello element + OFHello m = builder.build(); + channel.write(Collections.singletonList(m)); + log.debug("Send hello: {}", m); + } + + private void sendEchoRequest() { + OFEchoRequest request = factory.buildEchoRequest() + .setXid(handshakeTransactionIds--) + .build(); + channel.write(Collections.singletonList(request)); + } + + private void sendEchoReply(OFEchoRequest request) { + OFEchoReply reply = factory.buildEchoReply() + .setXid(request.getXid()) + .setData(request.getData()) + .build(); + channel.write(Collections.singletonList(reply)); + } + + OFChannelState getStateForTesting() { + return state; + } + + IOFConnectionBackend getConnectionForTesting() { + return connection; + } + + ChannelPipeline getPipelineForTesting() { + return this.pipeline; + } + } diff --git a/src/main/java/net/floodlightcontroller/core/internal/OFFeaturesReplyFuture.java b/src/main/java/net/floodlightcontroller/core/internal/OFFeaturesReplyFuture.java deleted file mode 100644 index eca67bd97ca289fa5246edbb4196d9f0216e98c5..0000000000000000000000000000000000000000 --- a/src/main/java/net/floodlightcontroller/core/internal/OFFeaturesReplyFuture.java +++ /dev/null @@ -1,72 +0,0 @@ -/** - * Copyright 2012, 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.core.internal; - -import java.util.concurrent.TimeUnit; - -import net.floodlightcontroller.core.IOFSwitch; -import net.floodlightcontroller.threadpool.IThreadPoolService; - -import org.openflow.protocol.OFFeaturesReply; -import org.openflow.protocol.OFMessage; -import org.openflow.protocol.OFType; - -/** - * A concrete implementation that handles asynchronously receiving - * OFFeaturesReply - * - * @author Shudong Zhou - */ -public class OFFeaturesReplyFuture extends - OFMessageFuture<OFFeaturesReply> { - - protected volatile boolean finished; - - public OFFeaturesReplyFuture(IThreadPoolService tp, - IOFSwitch sw, int transactionId) { - super(tp, sw, OFType.FEATURES_REPLY, transactionId); - init(); - } - - public OFFeaturesReplyFuture(IThreadPoolService tp, - IOFSwitch sw, int transactionId, long timeout, TimeUnit unit) { - super(tp, sw, OFType.FEATURES_REPLY, transactionId, timeout, unit); - init(); - } - - private void init() { - this.finished = false; - this.result = null; - } - - @Override - protected void handleReply(IOFSwitch sw, OFMessage msg) { - this.result = (OFFeaturesReply) msg; - this.finished = true; - } - - @Override - protected boolean isFinished() { - return finished; - } - - @Override - protected void unRegister() { - super.unRegister(); - sw.cancelFeaturesReply(transactionId); - } -} diff --git a/src/main/java/net/floodlightcontroller/core/internal/OFMessageDecoder.java b/src/main/java/net/floodlightcontroller/core/internal/OFMessageDecoder.java index 25edf396c943dfce728f79610910162ebd608a12..12faa3be6b49a43d70a8d8fbffec769184524b77 100644 --- a/src/main/java/net/floodlightcontroller/core/internal/OFMessageDecoder.java +++ b/src/main/java/net/floodlightcontroller/core/internal/OFMessageDecoder.java @@ -1,7 +1,7 @@ /** -* Copyright 2011, Big Switch Networks, Inc. +* Copyright 2011, Big Switch Networks, Inc. * Originally created by David Erickson, Stanford University -* +* * Licensed under the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. You may obtain * a copy of the License at @@ -17,44 +17,65 @@ package net.floodlightcontroller.core.internal; +import java.util.ArrayList; import java.util.List; import org.jboss.netty.buffer.ChannelBuffer; import org.jboss.netty.channel.Channel; import org.jboss.netty.channel.ChannelHandlerContext; import org.jboss.netty.handler.codec.frame.FrameDecoder; -import org.openflow.protocol.OFMessage; -import org.openflow.protocol.factory.BasicFactory; -import org.openflow.protocol.factory.OFMessageFactory; +import org.projectfloodlight.openflow.protocol.OFFactories; +import org.projectfloodlight.openflow.protocol.OFFactory; +import org.projectfloodlight.openflow.protocol.OFMessage; +import org.projectfloodlight.openflow.protocol.OFMessageReader; +import org.projectfloodlight.openflow.protocol.OFVersion; /** - * Decode an openflow message from a Channel, for use in a netty - * pipeline + * Decode an openflow message from a channel, for use in a netty pipeline. + * * @author readams */ public class OFMessageDecoder extends FrameDecoder { - OFMessageFactory factory = BasicFactory.getInstance(); - + private OFMessageReader<OFMessage> reader; + + public OFMessageDecoder() { + reader = OFFactories.getGenericReader(); + } + + public OFMessageDecoder(OFVersion version) { + setVersion(version); + } + + public void setVersion(OFVersion version) { + OFFactory factory = OFFactories.getFactory(version); + this.reader = factory.getReader(); + } + @Override protected Object decode(ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer) throws Exception { if (!channel.isConnected()) { // In testing, I see decode being called AFTER decode last. - // This check avoids that from reading curroupted frames + // This check avoids that from reading corrupted frames return null; } - List<OFMessage> message = factory.parseMessage(buffer); - return message; + List<OFMessage> messageList = new ArrayList<OFMessage>(); + for (;;) { + OFMessage message = reader.readFrom(buffer); + if (message == null) + break; + messageList.add(message); + } + return messageList.isEmpty() ? null : messageList; } @Override protected Object decodeLast(ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer) throws Exception { - // This is not strictly needed atthis time. It is used to detect + // This is not strictly needed at this time. It is used to detect // connection reset detection from netty (for debug) return null; } - } diff --git a/src/main/java/net/floodlightcontroller/core/internal/OFMessageEncoder.java b/src/main/java/net/floodlightcontroller/core/internal/OFMessageEncoder.java index 6be5f9a1d524b51e85af216f70d9a4800aed0ad6..4d6912ad69a9246da975063127fca3b8d97fe0b0 100644 --- a/src/main/java/net/floodlightcontroller/core/internal/OFMessageEncoder.java +++ b/src/main/java/net/floodlightcontroller/core/internal/OFMessageEncoder.java @@ -1,7 +1,7 @@ /** -* Copyright 2011, Big Switch Networks, Inc. +* Copyright 2011, Big Switch Networks, Inc. * Originally created by David Erickson, Stanford University -* +* * Licensed under the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. You may obtain * a copy of the License at @@ -17,14 +17,12 @@ package net.floodlightcontroller.core.internal; -import java.util.List; - import org.jboss.netty.buffer.ChannelBuffer; import org.jboss.netty.buffer.ChannelBuffers; import org.jboss.netty.channel.Channel; import org.jboss.netty.channel.ChannelHandlerContext; import org.jboss.netty.handler.codec.oneone.OneToOneEncoder; -import org.openflow.protocol.OFMessage; +import org.projectfloodlight.openflow.protocol.OFMessage; /** * Encode an openflow message for output into a ChannelBuffer, for use in a @@ -36,21 +34,16 @@ public class OFMessageEncoder extends OneToOneEncoder { @Override protected Object encode(ChannelHandlerContext ctx, Channel channel, Object msg) throws Exception { - if (!( msg instanceof List)) + if (!(msg instanceof Iterable)) return msg; @SuppressWarnings("unchecked") - List<OFMessage> msglist = (List<OFMessage>)msg; - int size = 0; - for (OFMessage ofm : msglist) { - size += ofm.getLengthU(); - } + Iterable<OFMessage> msgList = (Iterable<OFMessage>)msg; - ChannelBuffer buf = ChannelBuffers.buffer(size);; - for (OFMessage ofm : msglist) { + ChannelBuffer buf = ChannelBuffers.dynamicBuffer(); + for (OFMessage ofm : msgList) { ofm.writeTo(buf); } return buf; } - } diff --git a/src/main/java/net/floodlightcontroller/core/internal/OFMessageFuture.java b/src/main/java/net/floodlightcontroller/core/internal/OFMessageFuture.java deleted file mode 100644 index 1fc9c135330d6189652fca4b37aac903bce5bf0d..0000000000000000000000000000000000000000 --- a/src/main/java/net/floodlightcontroller/core/internal/OFMessageFuture.java +++ /dev/null @@ -1,171 +0,0 @@ -/** -* Copyright 2011, Big Switch Networks, Inc. -* Originally created by David Erickson, Stanford University -* -* Licensed under the Apache License, Version 2.0 (the "License"); you may -* not use this file except in compliance with the License. You may obtain -* a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -* License for the specific language governing permissions and limitations -* under the License. -**/ - -package net.floodlightcontroller.core.internal; - -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -import org.openflow.protocol.OFMessage; -import org.openflow.protocol.OFType; - -import net.floodlightcontroller.core.IOFSwitch; -import net.floodlightcontroller.threadpool.IThreadPoolService; - -/** - * A Future object used to retrieve asynchronous OFMessage replies. Unregisters - * and cancels itself by default after 60 seconds. This class is meant to be - * sub-classed and proper behavior added to the handleReply method, and - * termination of the Future to be handled in the isFinished method. - * - * @author David Erickson (daviderickson@cs.stanford.edu) - */ -public abstract class OFMessageFuture<V> implements Future<V> { - - protected IThreadPoolService threadPool; - protected volatile boolean canceled; - protected CountDownLatch latch; - protected OFType responseType; - protected volatile V result; - protected IOFSwitch sw; - protected Runnable timeoutTimer; - protected int transactionId; - protected static final long DEFAULT_TIMEOUT = 60; - protected static final TimeUnit DEFAULT_TIMEOUT_UNIT = TimeUnit.SECONDS; - - public OFMessageFuture(IThreadPoolService tp, - IOFSwitch sw, OFType responseType, int transactionId) { - this(tp, sw, responseType, transactionId, - DEFAULT_TIMEOUT, DEFAULT_TIMEOUT_UNIT); - } - - public OFMessageFuture(IThreadPoolService tp, - IOFSwitch sw, OFType responseType, int transactionId, long timeout, TimeUnit unit) { - this.threadPool = tp; - this.canceled = false; - this.latch = new CountDownLatch(1); - this.responseType = responseType; - this.sw = sw; - this.transactionId = transactionId; - - final OFMessageFuture<V> future = this; - timeoutTimer = new Runnable() { - @Override - public void run() { - if (timeoutTimer == this) - future.cancel(true); - } - }; - threadPool.getScheduledExecutor().schedule(timeoutTimer, timeout, unit); - } - - protected void unRegister() { - this.timeoutTimer = null; - } - - - // TODO: msg should be generic! - public void deliverFuture(IOFSwitch sw, OFMessage msg) { - if (transactionId == msg.getXid()) { - handleReply(sw, msg); - if (isFinished()) { - unRegister(); - this.latch.countDown(); - } - } - } - - /** - * Used to handle the specific expected message this Future was reigstered - * for, the specified msg parameter is guaranteed to match the type and - * transaction id specified. - * @param sw - * @param msg - * @return - */ - protected abstract void handleReply(IOFSwitch sw, OFMessage msg); - - /** - * Called directly after handleReply, subclasses implement this method to - * indicate when the future can deregister itself from receiving future - * messages, and when it is safe to return the results to any waiting - * threads. - * @return when this Future has completed its work - */ - protected abstract boolean isFinished(); - - /* (non-Javadoc) - * @see java.util.concurrent.Future#cancel(boolean) - */ - @Override - public boolean cancel(boolean mayInterruptIfRunning) { - if (isDone()) { - return false; - } else { - unRegister(); - canceled = true; - this.latch.countDown(); - return !isDone(); - } - } - - /* (non-Javadoc) - * @see java.util.concurrent.Future#isCancelled() - */ - @Override - public boolean isCancelled() { - return canceled; - } - - /* (non-Javadoc) - * @see java.util.concurrent.Future#isDone() - */ - @Override - public boolean isDone() { - return this.latch.getCount() == 0; - } - - /* (non-Javadoc) - * @see java.util.concurrent.Future#get() - */ - @Override - public V get() throws InterruptedException, ExecutionException { - this.latch.await(); - return result; - } - - /* (non-Javadoc) - * @see java.util.concurrent.Future#get(long, java.util.concurrent.TimeUnit) - */ - @Override - public V get(long timeout, TimeUnit unit) throws InterruptedException, - ExecutionException, TimeoutException { - this.latch.await(timeout, unit); - return result; - } - - public int getTransactionId() { - return transactionId; - } - - public void setTransactionId(int transactionId) { - this.transactionId = transactionId; - } -} diff --git a/src/main/java/net/floodlightcontroller/core/internal/OFStatisticsFuture.java b/src/main/java/net/floodlightcontroller/core/internal/OFStatisticsFuture.java deleted file mode 100644 index 4d3f733a6ed1d3e6b7dbba272643680877f9e982..0000000000000000000000000000000000000000 --- a/src/main/java/net/floodlightcontroller/core/internal/OFStatisticsFuture.java +++ /dev/null @@ -1,80 +0,0 @@ -/** -* Copyright 2011, Big Switch Networks, Inc. -* Originally created by David Erickson, Stanford University -* -* Licensed under the Apache License, Version 2.0 (the "License"); you may -* not use this file except in compliance with the License. You may obtain -* a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -* License for the specific language governing permissions and limitations -* under the License. -**/ - -package net.floodlightcontroller.core.internal; - -import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.TimeUnit; - -import net.floodlightcontroller.core.IOFSwitch; -import net.floodlightcontroller.threadpool.IThreadPoolService; - -import org.openflow.protocol.OFMessage; -import org.openflow.protocol.OFStatisticsReply; -import org.openflow.protocol.OFType; -import org.openflow.protocol.statistics.OFStatistics; - -/** - * A concrete implementation that handles asynchronously receiving OFStatistics - * - * @author David Erickson (daviderickson@cs.stanford.edu) - */ -public class OFStatisticsFuture extends - OFMessageFuture<List<OFStatistics>> { - - protected volatile boolean finished; - - public OFStatisticsFuture(IThreadPoolService tp, - IOFSwitch sw, int transactionId) { - super(tp, sw, OFType.STATS_REPLY, transactionId); - init(); - } - - public OFStatisticsFuture(IThreadPoolService tp, - IOFSwitch sw, int transactionId, long timeout, TimeUnit unit) { - super(tp, sw, OFType.STATS_REPLY, transactionId, timeout, unit); - init(); - } - - private void init() { - this.finished = false; - this.result = new CopyOnWriteArrayList<OFStatistics>(); - } - - @Override - protected void handleReply(IOFSwitch sw, OFMessage msg) { - OFStatisticsReply sr = (OFStatisticsReply) msg; - synchronized (this.result) { - this.result.addAll(sr.getStatistics()); - if ((sr.getFlags() & 0x1) == 0) { - this.finished = true; - } - } - } - - @Override - protected boolean isFinished() { - return finished; - } - - @Override - protected void unRegister() { - super.unRegister(); - sw.cancelStatisticsReply(transactionId); - } -} diff --git a/src/main/java/net/floodlightcontroller/core/internal/OFSwitchAppHandshakePlugin.java b/src/main/java/net/floodlightcontroller/core/internal/OFSwitchAppHandshakePlugin.java new file mode 100644 index 0000000000000000000000000000000000000000..ddfd008720a6853262905afeb7072cf91c99c3f5 --- /dev/null +++ b/src/main/java/net/floodlightcontroller/core/internal/OFSwitchAppHandshakePlugin.java @@ -0,0 +1,116 @@ +package net.floodlightcontroller.core.internal; + +import java.util.concurrent.TimeUnit; + +import org.jboss.netty.util.Timeout; +import org.jboss.netty.util.Timer; +import org.jboss.netty.util.TimerTask; +import net.floodlightcontroller.core.IOFSwitch; +import net.floodlightcontroller.core.internal.OFSwitchHandshakeHandler.WaitAppHandshakeState; +import org.projectfloodlight.openflow.protocol.OFMessage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.Preconditions; + +/** + * This class is a plugin that can be used by applications to tap into the + * switch handshake. It operates much like the switch handshake. Messages should + * be sent upon entering the plugin and OF response messages should be handled + * just like any part of the switch handshake. + * + * @author Jason Parraga <jason.parraga@bigswitch.com> + */ +public abstract class OFSwitchAppHandshakePlugin { + + private static final Logger log = LoggerFactory.getLogger(OFSwitchAppHandshakePlugin.class); + + private WaitAppHandshakeState state; + @edu.umd.cs.findbugs.annotations.SuppressWarnings("URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD") + private IOFSwitch sw; + private volatile Timeout timeout; + + private final PluginResult defaultResult; + private final int timeoutS; + + /** + * Constructor for OFSwitchAppHandshakePlugin + * @param defaultResult the default result in the event of a timeout + * @param defaultTimeoutS the timeout length in seconds + */ + protected OFSwitchAppHandshakePlugin(PluginResult defaultResult, int timeoutS){ + Preconditions.checkNotNull(defaultResult, "defaultResult"); + Preconditions.checkNotNull(timeoutS, "timeoutS"); + + this.defaultResult = defaultResult; + this.timeoutS = timeoutS; + } + + /** + * Process incoming OF Messages + * + * @param m The OF Message received + */ + protected abstract void processOFMessage(OFMessage m); + + /** + * Enter this plugin. Should be used to send messages. + */ + protected abstract void enterPlugin(); + + /** + * Gets the switch associated with the handshake for use by the plugin + * writer. + * + * @return the switch associated with the handshake. + */ + protected IOFSwitch getSwitch() { + return this.sw; + } + + /** + * Initialization for plugin called by the OFSwitchHandshakeHandler + * + * @param state the current state of the OFSwitchHandshakeHandler + * @param sw the current switch of the OFSwitchHandshakeHandler + */ + final void init(WaitAppHandshakeState state, IOFSwitch sw, Timer timer) { + this.state = state; + this.sw = sw; + this.timeout = timer.newTimeout(new PluginTimeoutTask(), timeoutS, TimeUnit.SECONDS); + } + + /** + * Called to denote that the plugin has finished + * + * @param result + * the result of the plugin in regards to handshaking + */ + protected final void exitPlugin(PluginResult result) { + timeout.cancel(); + state.exitPlugin(result); + } + + /** + * Plugin timeout task that will exit the plugin + * with the default result value when timed out. + * + */ + private final class PluginTimeoutTask implements TimerTask { + + @Override + public void run(Timeout timeout) throws Exception { + if (!timeout.isCancelled()) { + log.warn("App handshake plugin for {} timed out. Returning result {}.", + sw, defaultResult); + exitPlugin(defaultResult); + } + } + } + + public enum PluginResultType { + CONTINUE(), + DISCONNECT(), + QUARANTINE(); + } +} diff --git a/src/main/java/net/floodlightcontroller/core/internal/OFSwitchHandshakeHandler.java b/src/main/java/net/floodlightcontroller/core/internal/OFSwitchHandshakeHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..5acd44059d5a4bb5a2486e3923143f6cf7ad91b0 --- /dev/null +++ b/src/main/java/net/floodlightcontroller/core/internal/OFSwitchHandshakeHandler.java @@ -0,0 +1,1566 @@ +package net.floodlightcontroller.core.internal; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; + +import javax.annotation.Nonnull; + +import org.jboss.netty.util.Timer; + +import net.floodlightcontroller.core.HARole; +import net.floodlightcontroller.core.IOFConnection; +import net.floodlightcontroller.core.IOFConnectionBackend; +import net.floodlightcontroller.core.IOFSwitch; +import net.floodlightcontroller.core.IOFSwitch.SwitchStatus; +import net.floodlightcontroller.core.IOFSwitchBackend; +import net.floodlightcontroller.core.PortChangeEvent; +import net.floodlightcontroller.core.SwitchDescription; +import net.floodlightcontroller.core.annotations.LogMessageDoc; +import net.floodlightcontroller.core.annotations.LogMessageDocs; +import net.floodlightcontroller.core.internal.OFSwitchAppHandshakePlugin.PluginResultType; + +import org.projectfloodlight.openflow.protocol.OFBadRequestCode; +import org.projectfloodlight.openflow.protocol.OFBarrierReply; +import org.projectfloodlight.openflow.protocol.OFBarrierRequest; +import org.projectfloodlight.openflow.protocol.OFControllerRole; +import org.projectfloodlight.openflow.protocol.OFDescStatsReply; +import org.projectfloodlight.openflow.protocol.OFDescStatsRequest; +import org.projectfloodlight.openflow.protocol.OFErrorMsg; +import org.projectfloodlight.openflow.protocol.OFErrorType; +import org.projectfloodlight.openflow.protocol.OFExperimenter; +import org.projectfloodlight.openflow.protocol.OFFactories; +import org.projectfloodlight.openflow.protocol.OFFactory; +import org.projectfloodlight.openflow.protocol.OFFeaturesReply; +import org.projectfloodlight.openflow.protocol.OFFlowModFailedCode; +import org.projectfloodlight.openflow.protocol.OFFlowRemoved; +import org.projectfloodlight.openflow.protocol.OFGetConfigReply; +import org.projectfloodlight.openflow.protocol.OFGetConfigRequest; +import org.projectfloodlight.openflow.protocol.OFMessage; +import org.projectfloodlight.openflow.protocol.OFNiciraControllerRole; +import org.projectfloodlight.openflow.protocol.OFNiciraControllerRoleReply; +import org.projectfloodlight.openflow.protocol.OFNiciraControllerRoleRequest; +import org.projectfloodlight.openflow.protocol.OFPacketIn; +import org.projectfloodlight.openflow.protocol.OFPortDescStatsReply; +import org.projectfloodlight.openflow.protocol.OFPortStatus; +import org.projectfloodlight.openflow.protocol.OFQueueGetConfigReply; +import org.projectfloodlight.openflow.protocol.OFRoleReply; +import org.projectfloodlight.openflow.protocol.OFRoleRequest; +import org.projectfloodlight.openflow.protocol.OFSetConfig; +import org.projectfloodlight.openflow.protocol.OFStatsReply; +import org.projectfloodlight.openflow.protocol.OFStatsRequestFlags; +import org.projectfloodlight.openflow.protocol.OFStatsType; +import org.projectfloodlight.openflow.protocol.OFType; +import org.projectfloodlight.openflow.protocol.OFVersion; +import org.projectfloodlight.openflow.protocol.errormsg.OFBadRequestErrorMsg; +import org.projectfloodlight.openflow.protocol.errormsg.OFFlowModFailedErrorMsg; +import org.projectfloodlight.openflow.types.DatapathId; +import org.projectfloodlight.openflow.types.OFAuxId; +import org.projectfloodlight.openflow.types.U64; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; + +/** + * Switch handler deals with the switch connection and dispatches + * switch messages to the appropriate locations. These messages + * are typically received by the channel handler first and piped here. + * + * @author Jason Parraga <jason.parraga@bigswitch.com> + */ +public class OFSwitchHandshakeHandler implements IOFConnectionListener { + private static final Logger log = LoggerFactory.getLogger(OFSwitchHandshakeHandler.class); + + private final IOFSwitchManager switchManager; + private final RoleManager roleManager; + private final IOFConnectionBackend mainConnection; + private final SwitchManagerCounters switchManagerCounters; + private IOFSwitchBackend sw; + private final Map<OFAuxId, IOFConnectionBackend> auxConnections; + private volatile OFSwitchHandshakeState state; + private RoleChanger roleChanger; + // Default to 1.3 - This is overwritten by the features reply + private OFFactory factory = OFFactories.getFactory(OFVersion.OF_13); + private final OFFeaturesReply featuresReply; + private final Timer timer; + + private final ArrayList<OFPortStatus> pendingPortStatusMsg; + + /** transaction Ids to use during handshake. Since only one thread + * calls into the OFChannelHandler we don't need atomic. + * We will count down + */ + private long handshakeTransactionIds = 0x00FFFFFFFFL; + + /* Exponential backoff of master role assertion */ + private final long MAX_ASSERT_TIME_INTERVAL_NS = TimeUnit.SECONDS.toNanos(120); + private final long DEFAULT_ROLE_TIMEOUT_NS = TimeUnit.SECONDS.toNanos(10); + + protected OFPortDescStatsReply portDescStats; + + /** + * When we remove a pending role request and set the role on the switch + * we use this enum to indicate how we arrived at the decision. + * @author gregor + */ + private enum RoleRecvStatus { + /** We received a role reply message from the switch */ + RECEIVED_REPLY, + /** The switch returned an error indicated that roles are not + * supported*/ + UNSUPPORTED, + /** The request timed out */ + NO_REPLY; + } + /** + * A utility class to handle role requests and replies for this channel. + * After a role request is submitted the role changer keeps track of the + * pending request, collects the reply (if any) and times out the request + * if necessary. + * + * To simplify role handling we only keep track of the /last/ pending + * role reply send to the switch. If multiple requests are pending and + * we receive replies for earlier requests we ignore them. However, this + * way of handling pending requests implies that we could wait forever if + * a new request is submitted before the timeout triggers. If necessary + * we could work around that though. + * @author gregor + */ + private class RoleChanger { + // indicates that a request is currently pending + // needs to be volatile to allow correct double-check idiom + private volatile boolean requestPending; + // the transaction Id of the pending request + private long pendingXid; + // the role that's pending + private OFControllerRole pendingRole; + // system time in NS when we send the request + private long roleSubmitTimeNs; + // the timeout to use + private final long roleTimeoutNs; + private long lastAssertTimeNs; + private long assertTimeIntervalNs = TimeUnit.SECONDS.toNanos(1); + + public RoleChanger(long roleTimeoutNs) { + this.roleTimeoutNs = roleTimeoutNs; + // System.nanoTime() may be negative -- prime the roleSubmitTime as + // "long ago in the past" to be robust against it. + this.roleSubmitTimeNs = System.nanoTime() - (2 * roleTimeoutNs); + this.lastAssertTimeNs = System.nanoTime() - (2 * assertTimeIntervalNs); + this.requestPending = false; + this.pendingXid = -1; + this.pendingRole = null; + } + + /** + * Send Nicira role request message to the switch requesting the + * specified role. + * + * @param role role to request + */ + private long sendNiciraRoleRequest(OFControllerRole role){ + + long xid; + // Construct the role request message + if(factory.getVersion().compareTo(OFVersion.OF_12) < 0) { + OFNiciraControllerRoleRequest.Builder builder = + factory.buildNiciraControllerRoleRequest(); + xid = factory.nextXid(); + builder.setXid(xid); + + OFNiciraControllerRole niciraRole = NiciraRoleUtils.ofRoleToNiciraRole(role); + builder.setRole(niciraRole); + OFNiciraControllerRoleRequest roleRequest = builder.build(); + // Send it to the switch + mainConnection.write(roleRequest); + } else { + // send an OF 1.2+ role request + OFRoleRequest roleRequest = factory.buildRoleRequest() + // we don't use the generation id scheme for now, + // switch initializes to 0, we keep it at 0 + .setGenerationId(U64.of(0)) + .setRole(role) + .build(); + xid = roleRequest.getXid(); + mainConnection.write(roleRequest); + } + return xid; + } + + /** + * Send a role request for the given role only if no other role + * request is currently pending. + * @param role The role to send to the switch. + * @throws IOException + */ + @LogMessageDoc(level="WARN", + message="Reasserting master role on switch {SWITCH}, " + + "likely a configruation error with multiple masters", + explanation="The controller keeps getting permission error " + + "from switch, likely due to switch connected to another " + + "controller also in master mode", + recommendation=LogMessageDoc.CHECK_SWITCH) + synchronized void sendRoleRequestIfNotPending(OFControllerRole role) + throws IOException { + long now = System.nanoTime(); + if (now - lastAssertTimeNs < assertTimeIntervalNs) { + return; + } + + lastAssertTimeNs = now; + if (assertTimeIntervalNs < MAX_ASSERT_TIME_INTERVAL_NS) { // 2 minutes max + assertTimeIntervalNs <<= 1; + } else if (role == OFControllerRole.ROLE_MASTER){ + log.warn("Reasserting master role on switch {}, " + + "likely a switch config error with multiple masters", + role, sw); + } + if (!requestPending) + sendRoleRequest(role); + else + switchManagerCounters.roleNotResentBecauseRolePending.increment(); + } + + /** + * Send a role request with the given role to the switch. + * + * Send a role request with the given role to the switch and update + * the pending request and timestamp. + * + * @param role + * @throws IOException + */ + synchronized void sendRoleRequest(OFControllerRole role) throws IOException { + /* + * There are three cases to consider for SUPPORTS_NX_ROLE: + * + * 1) unset. We have neither received a role reply from the + * switch nor has a request timed out. Send a request. + * 2) TRUE: We've already send a request earlier and received + * a reply. The switch supports role and we should send one. + * 3) FALSE: We have already send a role and received an error. + * The switch does not support roles. Don't send a role request, + * set the switch's role directly. + */ + Boolean supportsNxRole = (Boolean) + sw.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE); + if ((supportsNxRole != null) && !supportsNxRole) { + setSwitchRole(role, RoleRecvStatus.UNSUPPORTED); + } else { + pendingXid = sendNiciraRoleRequest(role); + pendingRole = role; + this.roleSubmitTimeNs = System.nanoTime(); + requestPending = true; + } + } + + /** + * Deliver a received role reply and set SWITCH_SUPPORTS_NX_ROLE. + * + * Check if a request is pending and if the received reply matches the + * the expected pending reply (we check both role and xid) we set + * the role for the switch/channel. + * + * If a request is pending but doesn't match the reply we ignore it. + * + * If no request is pending we disconnect. + * + * @param xid + * @param role + * @throws SwitchStateException if no request is pending + */ + synchronized void deliverRoleReply(long xid, OFControllerRole role) { + if (!requestPending) { + // Maybe don't disconnect if the role reply we received is + // for the same role we are already in. + String msg = String.format("Switch: [%s], State: [%s], " + + "received unexpected RoleReply[%s]. " + + "No roles are pending", + OFSwitchHandshakeHandler.this.getSwitchInfoString(), + OFSwitchHandshakeHandler.this.state.toString(), + role); + throw new SwitchStateException(msg); + } + + if (pendingXid == xid && pendingRole == role) { + log.debug("[{}] Received role reply message setting role to {}", + getDpid(), role); + switchManagerCounters.roleReplyReceived.increment(); + setSwitchRole(role, RoleRecvStatus.RECEIVED_REPLY); + } else { + log.debug("[{}] Received stale or unexpected role reply " + + "{}, xid={}. Ignoring. " + + "Waiting for {}, xid={}", + new Object[] { getDpid(), role, xid, + pendingRole, pendingXid }); + } + } + + /** + * Called if we receive an error message. If the xid matches the + * pending request we handle it otherwise we ignore it. We also + * set SWITCH_SUPPORTS_NX_ROLE to false. + * + * Note: since we only keep the last pending request we might get + * error messages for earlier role requests that we won't be able + * to handle + * @param xid + * @return true if the error was handled by us, false otherwise + * @throws SwitchStateException if the error was for the pending + * role request but was unexpected + */ + synchronized boolean deliverError(OFErrorMsg error) { + if (!requestPending) + return false; + + if (pendingXid == error.getXid()) { + if (error.getErrType() == OFErrorType.BAD_REQUEST) { + switchManagerCounters.roleReplyErrorUnsupported.increment(); + setSwitchRole(pendingRole, RoleRecvStatus.UNSUPPORTED); + } else { + // TODO: Is this the right thing to do if we receive + // some other error besides a bad request error? + // Presumably that means the switch did actually + // understand the role request message, but there + // was some other error from processing the message. + // OF 1.2 specifies a ROLE_REQUEST_FAILED + // error code, but it doesn't look like the Nicira + // role request has that. Should check OVS source + // code to see if it's possible for any other errors + // to be returned. + // If we received an error the switch is not + // in the correct role, so we need to disconnect it. + // We could also resend the request but then we need to + // check if there are other pending request in which + // case we shouldn't resend. If we do resend we need + // to make sure that the switch eventually accepts one + // of our requests or disconnect the switch. This feels + // cumbersome. + String msg = String.format("Switch: [%s], State: [%s], " + + "Unexpected error %s in respone to our " + + "role request for %s.", + OFSwitchHandshakeHandler.this.getSwitchInfoString(), + OFSwitchHandshakeHandler.this.state.toString(), + error.toString(), + pendingRole); + throw new SwitchStateException(msg); + } + return true; + } + return false; + } + + /** + * Check if a pending role request has timed out. + */ + void checkTimeout() { + if (!requestPending) + return; + synchronized(this) { + if (!requestPending) + return; + long now = System.nanoTime(); + if (now - this.roleSubmitTimeNs > roleTimeoutNs) { + // timeout triggered. + switchManagerCounters.roleReplyTimeout.increment(); + setSwitchRole(pendingRole, RoleRecvStatus.NO_REPLY); + } + } + } + + /** + * Set the role for this switch / channel. + * + * If the status indicates that we received a reply we set the role. + * If the status indicates otherwise we disconnect the switch if + * the role is SLAVE. + * + * "Setting a role" means setting the appropriate ChannelState, + * setting the flags on the switch and + * notifying Controller.java about new role of the switch + * + * @param role The role to set. + * @param status How we derived at the decision to set this status. + */ + synchronized private void setSwitchRole(OFControllerRole role, RoleRecvStatus status) { + requestPending = false; + if (status == RoleRecvStatus.RECEIVED_REPLY) + sw.setAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE, true); + else + sw.setAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE, false); + sw.setControllerRole(role); + + if (role != OFControllerRole.ROLE_SLAVE) { + OFSwitchHandshakeHandler.this.setState(new MasterState()); + } else { + if (status != RoleRecvStatus.RECEIVED_REPLY) { + if (log.isDebugEnabled()) { + log.debug("Disconnecting switch {}. Doesn't support role" + + "({}) request and controller is now SLAVE", + getSwitchInfoString(), status); + } + // the disconnect will trigger a switch removed to + // controller so no need to signal anything else + sw.disconnect(); + } else { + OFSwitchHandshakeHandler.this.setState(new SlaveState()); + } + } + } + } + + /** + * Default implementation for message handlers in any state. + * + * Individual states must override these if they want a behavior + * that differs from the default. + * + * In general, these handlers simply ignore the message and do + * nothing. + * + * There are some exceptions though, since some messages really + * are handled the same way in every state (e.g., ECHO_REQUST) or + * that are only valid in a single state (e.g., HELLO, GET_CONFIG_REPLY + */ + public abstract class OFSwitchHandshakeState { + + void processOFBarrierReply(OFBarrierReply m) { + // do nothing + } + + void processOFError(OFErrorMsg m) { + logErrorDisconnect(m); + } + + void processOFFlowRemoved(OFFlowRemoved m) { + unhandledMessageReceived(m); + } + + void processOFGetConfigReply(OFGetConfigReply m) { + // we only expect config replies in the WAIT_CONFIG_REPLY state + // TODO: might use two different strategies depending on whether + // we got a miss length of 64k or not. + illegalMessageReceived(m); + } + + void processOFPacketIn(OFPacketIn m) { + unhandledMessageReceived(m); + } + + // By default add port status messages to a pending list + void processOFPortStatus(OFPortStatus m) { + pendingPortStatusMsg.add(m); + } + + void processOFQueueGetConfigReply(OFQueueGetConfigReply m) { + unhandledMessageReceived(m); + } + + void processOFStatsReply(OFStatsReply m) { + switch(m.getStatsType()) { + case PORT_DESC: + processPortDescStatsReply((OFPortDescStatsReply) m); + break; + default: + unhandledMessageReceived(m); + } + } + + void processOFExperimenter(OFExperimenter m) { + unhandledMessageReceived(m); + } + + void processPortDescStatsReply(OFPortDescStatsReply m) { + unhandledMessageReceived(m); + } + + void processOFRoleReply(OFRoleReply m) { + unhandledMessageReceived(m); + } + + private final boolean handshakeComplete; + OFSwitchHandshakeState(boolean handshakeComplete) { + this.handshakeComplete = handshakeComplete; + } + + void logState() { + if(log.isDebugEnabled()) + log.debug("[{}] - Switch Handshake - enter state {}", mainConnection.getDatapathId(), this.getClass().getSimpleName()); + } + + /** enter this state. Can initialize the handler, send + * the necessary messages, etc. + */ + void enterState(){ + } + + /** + * Is this a state in which the handshake has completed? + * @return true if the handshake is complete + */ + public boolean isHandshakeComplete() { + return handshakeComplete; + } + + /** + * Used to notify the WAIT OF AUX state that + * a new connection has been added + * @param connection + */ + public void auxConnectionOpened(IOFConnectionBackend connection) { + // Should only be handled in wait of aux + log.debug("[{}] - Switch Handshake - unhandled aux connection event", + getDpid()); + } + /** + * Get a string specifying the switch connection, state, and + * message received. To be used as message for SwitchStateException + * or log messages + * @param h The channel handler (to get switch information_ + * @param m The OFMessage that has just been received + * @param details A string giving more details about the exact nature + * of the problem. + * @return + */ + // needs to be protected because enum members are acutally subclasses + protected String getSwitchStateMessage(OFMessage m, + String details) { + return String.format("Switch: [%s], State: [%s], received: [%s]" + + ", details: %s", + getSwitchInfoString(), + this.toString(), + m.getType().toString(), + details); + } + + /** + * We have an OFMessage we didn't expect given the current state and + * we want to treat this as an error. + * We currently throw an exception that will terminate the connection + * However, we could be more forgiving + * @param h the channel handler that received the message + * @param m the message + * @throws SwitchStateExeption we always through the execption + */ + // needs to be protected because enum members are acutally subclasses + protected void illegalMessageReceived(OFMessage m) { + String msg = getSwitchStateMessage(m, + "Switch should never send this message in the current state"); + throw new SwitchStateException(msg); + + } + + /** + * We have an OFMessage we didn't expect given the current state and + * we want to ignore the message + * @param h the channel handler the received the message + * @param m the message + */ + protected void unhandledMessageReceived(OFMessage m) { + switchManagerCounters.unhandledMessage.increment(); + if (log.isDebugEnabled()) { + String msg = getSwitchStateMessage(m, + "Ignoring unexpected message"); + log.debug(msg); + } + } + + /** + * Log an OpenFlow error message from a switch + * @param error The error message + */ + @LogMessageDoc(level="ERROR", + message="Error {error type} {error code} from {switch} " + + "in state {state}", + explanation="The switch responded with an unexpected error" + + "to an OpenFlow message from the controller", + recommendation="This could indicate improper network operation. " + + "If the problem persists restarting the switch and " + + "controller may help." + ) + protected void logError(OFErrorMsg error) { + log.error("{} from switch {} in state {}", + new Object[] { + error.toString(), + getSwitchInfoString(), + this.toString()}); + } + + /** + * Log an OpenFlow error message from a switch and disconnect the + * channel + * @param error The error message + */ + protected void logErrorDisconnect(OFErrorMsg error) { + logError(error); + mainConnection.disconnect(); + } + + /** + * Extract the role from an OFVendor message. + * + * Extract the role from an OFVendor message if the message is a + * Nicira role reply. Otherwise return null. + * + * @param h The channel handler receiving the message + * @param vendorMessage The vendor message to parse. + * @return The role in the message if the message is a Nicira role + * reply, null otherwise. + */ + protected OFControllerRole extractNiciraRoleReply(OFMessage vendorMessage) { + if (!(vendorMessage instanceof OFNiciraControllerRoleReply)) + return null; + OFNiciraControllerRoleReply roleReply = + (OFNiciraControllerRoleReply) vendorMessage; + return NiciraRoleUtils.niciraToOFRole(roleReply); + } + + /** + * Handle a port status message. + * + * Handle a port status message by updating the port maps in the + * IOFSwitch instance and notifying Controller about the change so + * it can dispatch a switch update. + * + * @param h The OFChannelHhandler that received the message + * @param m The PortStatus message we received + * @param doNotify if true switch port changed events will be + * dispatched + */ + protected void handlePortStatusMessage(OFPortStatus m, boolean doNotify) { + if (sw == null) { + String msg = getSwitchStateMessage(m, "State machine error: switch is null. Should never happen"); + throw new SwitchStateException(msg); + } + Collection<PortChangeEvent> changes = sw.processOFPortStatus(m); + if (doNotify) { + for (PortChangeEvent ev: changes) + switchManager.notifyPortChanged(sw, ev.port, ev.type); + } + } + + /** + * Process an OF message received on the channel and + * update state accordingly. + * + * The main "event" of the state machine. Process the received message, + * send follow up message if required and update state if required. + * + * Switches on the message type and calls more specific event handlers + * for each individual OF message type. If we receive a message that + * is supposed to be sent from a controller to a switch we throw + * a SwitchStateExeption. + * + * The more specific handlers can also throw SwitchStateExceptions + * + * @param h The OFChannelHandler that received the message + * @param m The message we received. + * @throws SwitchStateException + * @throws IOException + */ + void processOFMessage(OFMessage m) { + roleChanger.checkTimeout(); + switch(m.getType()) { + case BARRIER_REPLY: + processOFBarrierReply((OFBarrierReply) m); + break; + case ERROR: + processOFError((OFErrorMsg) m); + break; + case FLOW_REMOVED: + processOFFlowRemoved((OFFlowRemoved) m); + break; + case GET_CONFIG_REPLY: + processOFGetConfigReply((OFGetConfigReply) m); + break; + case PACKET_IN: + processOFPacketIn((OFPacketIn) m); + break; + case PORT_STATUS: + processOFPortStatus((OFPortStatus) m); + break; + case QUEUE_GET_CONFIG_REPLY: + processOFQueueGetConfigReply((OFQueueGetConfigReply) m); + break; + case STATS_REPLY: + processOFStatsReply((OFStatsReply) m); + break; + case ROLE_REPLY: + processOFRoleReply((OFRoleReply) m); + break; + case EXPERIMENTER: + processOFExperimenter((OFExperimenter) m); + break; + default: + illegalMessageReceived(m); + break; + } + } + } + + /** + * Initial state before channel is connected. Should not handle any messages. + */ + public class InitState extends OFSwitchHandshakeState { + + InitState() { + super(false); + } + + @Override + public void logState() { + log.debug("[{}] - Switch Handshake - Initiating from {}", + getDpid(), mainConnection.getRemoteInetAddress()); + } + } + + /** + * We are waiting for a features reply message. Once we receive it + * we send a SetConfig request, barrier, and GetConfig request. + * Next stats is WAIT_CONFIG_REPLY or WAIT_SET_L2_TABLE_REPLY + */ + public class WaitPortDescStatsReplyState extends OFSwitchHandshakeState { + WaitPortDescStatsReplyState() { + super(false); + } + + @Override + void enterState(){ + sendPortDescRequest(); + } + + @Override + void processPortDescStatsReply(OFPortDescStatsReply m) { + portDescStats = m; + setState(new WaitConfigReplyState()); + } + + @Override + void processOFExperimenter(OFExperimenter m) { + unhandledMessageReceived(m); + } + } + + /** + * We are waiting for a config reply message. Once we receive it + * we send a DescriptionStatsRequest to the switch. + * Next state: WAIT_DESCRIPTION_STAT_REPLY + */ + public class WaitConfigReplyState extends OFSwitchHandshakeState { + + WaitConfigReplyState() { + super(false); + } + + @Override + @LogMessageDocs({ + @LogMessageDoc(level="WARN", + message="Config Reply from {switch} has " + + "miss length set to {length}", + explanation="The controller requires that the switch " + + "use a miss length of 0xffff for correct " + + "function", + recommendation="Use a different switch to ensure " + + "correct function") + }) + void processOFGetConfigReply(OFGetConfigReply m) { + if (m.getMissSendLen() == 0xffff) { + log.trace("Config Reply from switch {} confirms " + + "miss length set to 0xffff", + getSwitchInfoString()); + } else { + // FIXME: we can't really deal with switches that don't send + // full packets. Shouldn't we drop the connection here? + // FIXME: count?? + log.warn("Config Reply from switch {} has" + + "miss length set to {}", + getSwitchInfoString(), + m.getMissSendLen()); + } + setState(new WaitDescriptionStatReplyState()); + } + + @Override + void processOFStatsReply(OFStatsReply m) { + illegalMessageReceived(m); + } + + @Override + void processOFError(OFErrorMsg m) { + logErrorDisconnect(m); + } + + @Override + void enterState() { + sendHandshakeSetConfig(); + } + } + + /** + * We are waiting for a OFDescriptionStat message from the switch. + * Once we receive any stat message we try to parse it. If it's not + * a description stats message we disconnect. If its the expected + * description stats message, we: + * - use the switch driver to bind the switch and get an IOFSwitch + * instance, setup the switch instance + * - setup the IOFSwitch instance + * - add switch to FloodlightProvider and send the intial role + * request to the switch. + * + * Next state: WaitOFAuxCxnsReplyState (if OF1.3), else + * WaitInitialRoleState or WaitSwitchDriverSubHandshake + * + * All following states will have a h.sw instance! + */ + public class WaitDescriptionStatReplyState extends OFSwitchHandshakeState{ + + WaitDescriptionStatReplyState() { + super(false); + } + + @LogMessageDoc(message="Switch {switch info} bound to class " + + "{switch driver}, description {switch description}", + explanation="The specified switch has been bound to " + + "a switch driver based on the switch description" + + "received from the switch") + @Override + void processOFStatsReply(OFStatsReply m) { + // Read description, if it has been updated + if (m.getStatsType() != OFStatsType.DESC) { + illegalMessageReceived(m); + return; + } + + OFDescStatsReply descStatsReply = (OFDescStatsReply) m; + SwitchDescription description = new SwitchDescription(descStatsReply); + sw = switchManager.getOFSwitchInstance(mainConnection, description, factory, featuresReply.getDatapathId()); + switchManager.switchAdded(sw); + // set switch information + // set features reply and channel first so we a DPID and + // channel info. + sw.setFeaturesReply(featuresReply); + if (portDescStats != null) { + sw.setPortDescStats(portDescStats); + } + + // Handle pending messages now that we have a sw object + handlePendingPortStatusMessages(description); + + sw.startDriverHandshake(); + if (sw.isDriverHandshakeComplete()) { + setState(new WaitAppHandshakeState()); + } else { + setState(new WaitSwitchDriverSubHandshakeState()); + } + } + + void handlePendingPortStatusMessages(SwitchDescription description){ + for (OFPortStatus ps: pendingPortStatusMsg) { + handlePortStatusMessage(ps, false); + } + pendingPortStatusMsg.clear(); + log.info("Switch {} bound to class {}, description {}", new Object[] { sw, sw.getClass(), description }); + } + + @Override + void enterState() { + sendHandshakeDescriptionStatsRequest(); + } + } + + public class WaitSwitchDriverSubHandshakeState extends OFSwitchHandshakeState { + + WaitSwitchDriverSubHandshakeState() { + super(false); + } + + @Override + void processOFMessage(OFMessage m) { + // FIXME: other message to handle here? + sw.processDriverHandshakeMessage(m); + if (sw.isDriverHandshakeComplete()) { + setState(new WaitAppHandshakeState()); + } + } + + @Override + void processOFPortStatus(OFPortStatus m) { + handlePortStatusMessage(m, false); + } + } + + public class WaitAppHandshakeState extends OFSwitchHandshakeState { + + private final Iterator<IAppHandshakePluginFactory> pluginIterator; + private OFSwitchAppHandshakePlugin plugin; + + WaitAppHandshakeState() { + super(false); + this.pluginIterator = switchManager.getHandshakePlugins().iterator(); + } + + @Override + void processOFMessage(OFMessage m) { + if(m.getType() == OFType.PORT_STATUS){ + OFPortStatus status = (OFPortStatus) m; + handlePortStatusMessage(status, false); + } + else if(plugin != null){ + this.plugin.processOFMessage(m); + } + else{ + super.processOFMessage(m); + } + } + + /** + * Called by handshake plugins to signify that they have finished their + * sub handshake. + * + * @param result + * the result of the sub handshake + */ + void exitPlugin(PluginResult result) { + + // Proceed + if (result.getResultType() == PluginResultType.CONTINUE) { + if (log.isDebugEnabled()) { + log.debug("Switch " + getSwitchInfoString() + " app handshake plugin {} returned {}." + + " Proceeding normally..", + this.plugin.getClass().getSimpleName(), result); + } + + enterNextPlugin(); + + // Stop + } else if (result.getResultType() == PluginResultType.DISCONNECT) { + log.error("Switch " + getSwitchInfoString() + " app handshake plugin {} returned {}. " + + "Disconnecting switch.", + this.plugin.getClass().getSimpleName(), result); + mainConnection.disconnect(); + } else if (result.getResultType() == PluginResultType.QUARANTINE) { + log.warn("Switch " + getSwitchInfoString() + " app handshake plugin {} returned {}. " + + "Putting switch into quarantine state.", + this.plugin.getClass().getSimpleName(), + result); + setState(new QuarantineState(result.getReason())); + } + } + + @Override + public void enterState() { + enterNextPlugin(); + } + + /** + * Initialize the plugin and begin. + * + * @param plugin the of switch app handshake plugin + */ + public void enterNextPlugin() { + if(this.pluginIterator.hasNext()){ + this.plugin = pluginIterator.next().createPlugin(); + this.plugin.init(this, sw, timer); + this.plugin.enterPlugin(); + } + // No more plugins left... + else{ + setState(new WaitInitialRoleState()); + } + } + + @Override + void processOFPortStatus(OFPortStatus m) { + handlePortStatusMessage(m, false); + } + + OFSwitchAppHandshakePlugin getCurrentPlugin() { + return plugin; + } + + } + + /** + * Switch is in a quarantine state. Essentially the handshake is complete. + */ + public class QuarantineState extends OFSwitchHandshakeState { + + private final String quarantineReason; + + QuarantineState(String reason) { + super(true); + this.quarantineReason = reason; + } + + @Override + public void enterState() { + setSwitchStatus(SwitchStatus.QUARANTINED); + } + + @Override + void processOFPortStatus(OFPortStatus m) { + handlePortStatusMessage(m, false); + } + + public String getQuarantineReason() { + return this.quarantineReason; + } + } + + /** + * We are waiting for the initial role reply message (or error indication) + * from the switch. Next State: MASTER or SLAVE + */ + public class WaitInitialRoleState extends OFSwitchHandshakeState { + + WaitInitialRoleState() { + super(false); + } + + @Override + void processOFError(OFErrorMsg m) { + // role changer will ignore the error if it isn't for it + boolean didHandle = roleChanger.deliverError(m); + if (!didHandle) { + logError(m); + } + } + + @Override + void processOFExperimenter(OFExperimenter m) { + OFControllerRole role = extractNiciraRoleReply(m); + // If role == null it measn the message wasn't really a + // Nicira role reply. We ignore this case. + if (role != null) { + roleChanger.deliverRoleReply(m.getXid(), role); + } else { + unhandledMessageReceived(m); + } + } + + @Override + void processOFRoleReply(OFRoleReply m) { + roleChanger.deliverRoleReply(m.getXid(), m.getRole()); + } + + @Override + void processOFStatsReply(OFStatsReply m) { + illegalMessageReceived(m); + } + + @Override + void processOFPortStatus(OFPortStatus m) { + handlePortStatusMessage(m, false); + } + + @Override + void enterState(){ + sendRoleRequest(roleManager.getOFControllerRole()); + } + } + + /** + * The switch is in MASTER role. We enter this state after a role + * reply from the switch is received (or the controller is MASTER + * and the switch doesn't support roles). The handshake is complete at + * this point. We only leave this state if the switch disconnects or + * if we send a role request for SLAVE /and/ receive the role reply for + * SLAVE. + */ + public class MasterState extends OFSwitchHandshakeState { + + MasterState() { + super(true); + } + + @Override + void enterState() { + setSwitchStatus(SwitchStatus.MASTER); + } + + @LogMessageDoc(level="WARN", + message="Received permission error from switch {} while" + + "being master. Reasserting master role.", + explanation="The switch has denied an operation likely " + + "indicating inconsistent controller roles", + recommendation="This situation can occurs transiently during role" + + " changes. If, however, the condition persists or happens" + + " frequently this indicates a role inconsistency. " + + LogMessageDoc.CHECK_CONTROLLER ) + @Override + void processOFError(OFErrorMsg m) { + // role changer will ignore the error if it isn't for it + boolean didHandle = roleChanger.deliverError(m); + if (didHandle) + return; + if ((m.getErrType() == OFErrorType.BAD_REQUEST) && + (((OFBadRequestErrorMsg)m).getCode() == OFBadRequestCode.EPERM)) { + // We are the master controller and the switch returned + // a permission error. This is a likely indicator that + // the switch thinks we are slave. Reassert our + // role + // FIXME: this could be really bad during role transitions + // if two controllers are master (even if its only for + // a brief period). We might need to see if these errors + // persist before we reassert + switchManagerCounters.epermErrorWhileSwitchIsMaster.increment(); + log.warn("Received permission error from switch {} while" + + "being master. Reasserting master role.", + getSwitchInfoString()); + reassertRole(OFControllerRole.ROLE_MASTER); + } + else if ((m.getErrType() == OFErrorType.FLOW_MOD_FAILED) && + (((OFFlowModFailedErrorMsg)m).getCode() == OFFlowModFailedCode.ALL_TABLES_FULL)) { + sw.setTableFull(true); + } + else { + logError(m); + } + dispatchMessage(m); + } + + @Override + void processOFExperimenter(OFExperimenter m) { + OFControllerRole role = extractNiciraRoleReply(m); + // If role == null it means the message wasn't really a + // Nicira role reply. We ignore just dispatch it to the + // OFMessage listenersa in this case. + if (role != null) { + roleChanger.deliverRoleReply(m.getXid(), role); + } else { + dispatchMessage(m); + } + } + + + @Override + void processOFRoleReply(OFRoleReply m) { + roleChanger.deliverRoleReply(m.getXid(), m.getRole()); + } + + @Override + void processOFPortStatus(OFPortStatus m) { + handlePortStatusMessage(m, true); + } + + @Override + void processOFPacketIn(OFPacketIn m) { + dispatchMessage(m); + } + + @Override + void processOFFlowRemoved(OFFlowRemoved m) { + dispatchMessage(m); + } + } + + /** + * The switch is in SLAVE role. We enter this state after a role + * reply from the switch is received. The handshake is complete at + * this point. We only leave this state if the switch disconnects or + * if we send a role request for MASTER /and/ receive the role reply for + * MASTER. + * TODO: CURRENTLY, WE DO NOT DISPATCH ANY MESSAGE IN SLAVE. + */ + public class SlaveState extends OFSwitchHandshakeState { + + SlaveState() { + super(true); + } + + @Override + void enterState() { + setSwitchStatus(SwitchStatus.SLAVE); + } + + @Override + void processOFError(OFErrorMsg m) { + // role changer will ignore the error if it isn't for it + boolean didHandle = roleChanger.deliverError(m); + if (!didHandle) { + logError(m); + } + } + + @Override + void processOFStatsReply(OFStatsReply m) { + } + + @Override + void processOFPortStatus(OFPortStatus m) { + handlePortStatusMessage(m, true); + } + + @Override + void processOFExperimenter(OFExperimenter m) { + OFControllerRole role = extractNiciraRoleReply(m); + // If role == null it means the message wasn't really a + // Nicira role reply. We ignore it. + if (role != null) { + roleChanger.deliverRoleReply(m.getXid(), role); + } else { + unhandledMessageReceived(m); + } + } + + @Override + void processOFRoleReply(OFRoleReply m) { + roleChanger.deliverRoleReply(m.getXid(), m.getRole()); + } + + @Override + @LogMessageDoc(level="WARN", + message="Received PacketIn from switch {} while" + + "being slave. Reasserting slave role.", + explanation="The switch has receive a PacketIn despite being " + + "in slave role indicating inconsistent controller roles", + recommendation="This situation can occurs transiently during role" + + " changes. If, however, the condition persists or happens" + + " frequently this indicates a role inconsistency. " + + LogMessageDoc.CHECK_CONTROLLER ) + void processOFPacketIn(OFPacketIn m) { + // we don't expect packetIn while slave, reassert we are slave + switchManagerCounters.packetInWhileSwitchIsSlave.increment(); + log.warn("Received PacketIn from switch {} while" + + "being slave. Reasserting slave role.", sw); + reassertRole(OFControllerRole.ROLE_SLAVE); + } + }; + + + /** + * Create a new unconnected OFChannelHandler. + * @param controller + * @param broker + * @throws SwitchHandshakeHandlerException + */ + OFSwitchHandshakeHandler(@Nonnull IOFConnectionBackend connection, + @Nonnull OFFeaturesReply featuresReply, + @Nonnull IOFSwitchManager switchManager, + @Nonnull RoleManager roleManager, + @Nonnull Timer timer) { + Preconditions.checkNotNull(connection, "connection"); + Preconditions.checkNotNull(featuresReply, "featuresReply"); + Preconditions.checkNotNull(switchManager, "switchManager"); + Preconditions.checkNotNull(roleManager, "roleManager"); + Preconditions.checkNotNull(timer, "timer"); + Preconditions.checkArgument(connection.getAuxId().equals(OFAuxId.MAIN), + "connection must be MAIN connection but is %s", connection); + + this.switchManager = switchManager; + this.roleManager = roleManager; + this.mainConnection = connection; + this.auxConnections = new ConcurrentHashMap<OFAuxId, IOFConnectionBackend>(); + this.featuresReply = featuresReply; + this.timer = timer; + this.switchManagerCounters = switchManager.getCounters(); + this.factory = OFFactories.getFactory(featuresReply.getVersion()); + this.roleChanger = new RoleChanger(DEFAULT_ROLE_TIMEOUT_NS); + setState(new InitState()); + this.pendingPortStatusMsg = new ArrayList<OFPortStatus>(); + + connection.setListener(this); + } + + /** + * This begins the switch handshake. We start where the OFChannelHandler + * left off, right after receiving the OFFeaturesReply. + */ + public void beginHandshake() { + Preconditions.checkState(state instanceof InitState, "must be in InitState"); + + if (this.featuresReply.getNTables() > 1) { + log.debug("Have {} table(s) for switch {}", this.featuresReply.getNTables(), + getSwitchInfoString()); + } + + if (this.featuresReply.getVersion().compareTo(OFVersion.OF_13) < 0) { + setState(new WaitConfigReplyState()); + } else { + // OF 1.3. Ask for Port Descriptions + setState(new WaitPortDescStatsReplyState()); + } + } + + public DatapathId getDpid(){ + return this.featuresReply.getDatapathId(); + } + + public OFAuxId getOFAuxId(){ + return this.featuresReply.getAuxiliaryId(); + } + + /** + * Is this a state in which the handshake has completed? + * @return true if the handshake is complete + */ + public boolean isHandshakeComplete() { + return this.state.isHandshakeComplete(); + } + + /** + * Forwards to RoleChanger. See there. + * @param role + */ + void sendRoleRequestIfNotPending(OFControllerRole role) { + try { + roleChanger.sendRoleRequestIfNotPending(role); + } catch (IOException e) { + log.error("Disconnecting switch {} due to IO Error: {}", + getSwitchInfoString(), e.getMessage()); + mainConnection.disconnect(); + } + } + + + /** + * Forwards to RoleChanger. See there. + * @param role + */ + void sendRoleRequest(OFControllerRole role) { + try { + roleChanger.sendRoleRequest(role); + } catch (IOException e) { + log.error("Disconnecting switch {} due to IO Error: {}", + getSwitchInfoString(), e.getMessage()); + mainConnection.disconnect(); + } + } + + /** + * Dispatches the message to the controller packet pipeline + */ + private void dispatchMessage(OFMessage m) { + this.switchManager.handleMessage(this.sw, m, null); + } + + /** + * Return a string describing this switch based on the already available + * information (DPID and/or remote socket) + * @return + */ + private String getSwitchInfoString() { + if (sw != null) + return sw.toString(); + String channelString; + if (mainConnection == null || mainConnection.getRemoteInetAddress() == null) { + channelString = "?"; + } else { + channelString = mainConnection.getRemoteInetAddress().toString(); + } + String dpidString; + if (featuresReply == null) { + dpidString = "?"; + } else { + dpidString = featuresReply.getDatapathId().toString(); + } + return String.format("[%s DPID[%s]]", channelString, dpidString); + } + + /** + * Update the channels state. Only called from the state machine. + * TODO: enforce restricted state transitions + * @param state + */ + private void setState(OFSwitchHandshakeState state) { + this.state = state; + state.logState(); + state.enterState(); + } + + public void processOFMessage(OFMessage m) { + state.processOFMessage(m); + } + + /** + * Send the configuration requests to tell the switch we want full + * packets + * @throws IOException + */ + private void sendHandshakeSetConfig() { + // Ensure we receive the full packet via PacketIn + // FIXME: We don't set the reassembly flags. + OFSetConfig configSet = factory.buildSetConfig() + .setXid(handshakeTransactionIds--) + .setMissSendLen(0xffff) + .build(); + + // Barrier + OFBarrierRequest barrier = factory.buildBarrierRequest() + .setXid(handshakeTransactionIds--) + .build(); + + // Verify (need barrier?) + OFGetConfigRequest configReq = factory.buildGetConfigRequest() + .setXid(handshakeTransactionIds--) + .build(); + List<OFMessage> msgList = ImmutableList.<OFMessage>of(configSet, barrier, configReq); + mainConnection.write(msgList); + } + + protected void sendPortDescRequest() { + mainConnection.write(factory.portDescStatsRequest(ImmutableSet.<OFStatsRequestFlags>of())); + } + + /** + * send a description state request + */ + private void sendHandshakeDescriptionStatsRequest() { + // Send description stats request to set switch-specific flags + OFDescStatsRequest descStatsRequest = factory.buildDescStatsRequest() + .setXid(handshakeTransactionIds--) + .build(); + mainConnection.write(descStatsRequest); + } + + OFSwitchHandshakeState getStateForTesting() { + return state; + } + + void reassertRole(OFControllerRole role){ + this.roleManager.reassertRole(this, HARole.ofOFRole(role)); + } + + void useRoleChangerWithOtherTimeoutForTesting(long roleTimeoutMs) { + roleChanger = new RoleChanger(TimeUnit.MILLISECONDS.toNanos(roleTimeoutMs)); + } + + /** + * Called by the switch manager when new aux connections have connected. + * This alerts the state machine of an aux connection. + * + * @param connection + * the aux connection + */ + public synchronized void auxConnectionOpened(IOFConnectionBackend connection) { + if(log.isDebugEnabled()) + log.debug("[{}] - Switch Handshake - new aux connection {}", this.getDpid(), connection.getAuxId()); + + // Handle new Auxiliary connections if the main connection has completed (i.e. in ACTIVE or STANDBY state) + if (this.getState().equals("ACTIVE") || this.getState().equals("STANDBY")) { + auxConnections.put(connection.getAuxId(), connection); + connection.setListener(OFSwitchHandshakeHandler.this); + log.info("Auxiliary connection {} added for {}.", connection.getAuxId().getValue(), connection.getDatapathId().toString()); + } else { + log.info("Auxiliary connection {} initiated for {} before main connection handshake complete. Ignorning aux connection attempt.", connection.getAuxId().getValue(), connection.getDatapathId().toString()); + } + } + + /** + * Gets the main connection + * + * @return the main connection + */ + public IOFConnectionBackend getMainConnection() { + return this.mainConnection; + } + + /** + * Determines if this handshake handler is responsible for the supplied + * connection. + * + * @param connection + * an OF connection + * @return true if the handler has the connection + */ + public boolean hasConnection(IOFConnectionBackend connection) { + if (this.mainConnection.equals(connection) + || this.auxConnections.get(connection.getAuxId()) == connection) { + return true; + } else { + return false; + } + } + + void cleanup() { + for (IOFConnectionBackend conn : this.auxConnections.values()) { + conn.disconnect(); + } + + this.mainConnection.disconnect(); + } + + public String getState() { + return this.state.getClass().getSimpleName(); + } + + public String getQuarantineReason() { + if(this.state instanceof QuarantineState) { + QuarantineState qs = (QuarantineState) this.state; + return qs.getQuarantineReason(); + } + return null; + } + + /** + * Gets the current connections that this switch handshake handler is + * responsible for. Used primarily by the REST API. + * @return an immutable list of IOFConnections + */ + public ImmutableList<IOFConnection> getConnections() { + ImmutableList.Builder<IOFConnection> builder = ImmutableList.builder(); + + builder.add(mainConnection); + builder.addAll(auxConnections.values()); + + return builder.build(); + } + + + /** IOFConnectionListener */ + @Override + public void connectionClosed(IOFConnectionBackend connection) { + // Disconnect handler's remaining connections + cleanup(); + + // Only remove the switch handler when the main connection is + // closed + if (connection == this.mainConnection) { + switchManager.handshakeDisconnected(connection.getDatapathId()); + if(sw != null) { + log.debug("[{}] - main connection {} closed - disconnecting switch", + connection); + + setSwitchStatus(SwitchStatus.DISCONNECTED); + switchManager.switchDisconnected(sw); + } + } + } + + @Override + public void messageReceived(IOFConnectionBackend connection, OFMessage m) { + processOFMessage(m); + } + + @Override + public boolean isSwitchHandshakeComplete(IOFConnectionBackend connection) { + return state.isHandshakeComplete(); + } + + public void setSwitchStatus(SwitchStatus status) { + if(sw != null) { + SwitchStatus oldStatus = sw.getStatus(); + if(oldStatus != status) { + log.debug("[{}] SwitchStatus change to {} requested, switch is in status " + oldStatus, + mainConnection.getDatapathId(), status); + sw.setStatus(status); + switchManager.switchStatusChanged(sw, oldStatus, status); + } else { + log.warn("[{}] SwitchStatus change to {} requested, switch is already in status", + mainConnection.getDatapathId(), status); + } + } else { + log.warn("[{}] SwitchStatus change to {} requested, but switch is not allocated yet", + mainConnection.getDatapathId(), status); + } + } + +} diff --git a/src/main/java/net/floodlightcontroller/core/internal/OFSwitchImpl.java b/src/main/java/net/floodlightcontroller/core/internal/OFSwitchImpl.java deleted file mode 100644 index 51dfddc1a1625f530610f7ab00d407d57c9a9e70..0000000000000000000000000000000000000000 --- a/src/main/java/net/floodlightcontroller/core/internal/OFSwitchImpl.java +++ /dev/null @@ -1,46 +0,0 @@ -/** -* Copyright 2012, Big Switch Networks, Inc. -* Originally created by David Erickson, Stanford University -* -* Licensed under the Apache License, Version 2.0 (the "License"); you may -* not use this file except in compliance with the License. You may obtain -* a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -* License for the specific language governing permissions and limitations -* under the License. -**/ - -package net.floodlightcontroller.core.internal; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import org.openflow.protocol.statistics.OFDescriptionStatistics; - -import net.floodlightcontroller.core.OFSwitchBase; - -/** - * This is the internal representation of an openflow switch. - */ -public class OFSwitchImpl extends OFSwitchBase { - - @Override - @JsonIgnore - public void setSwitchProperties(OFDescriptionStatistics description) { - this.description = new OFDescriptionStatistics(description); - } - - @Override - public OFPortType getPortType(short port_num) { - return OFPortType.NORMAL; - } - - @Override - @JsonIgnore - public boolean isFastPort(short port_num) { - return false; - } -} diff --git a/src/main/java/net/floodlightcontroller/core/internal/OFSwitchManager.java b/src/main/java/net/floodlightcontroller/core/internal/OFSwitchManager.java new file mode 100644 index 0000000000000000000000000000000000000000..b62c6241cde68226d6ef6ecb18faa281a18334eb --- /dev/null +++ b/src/main/java/net/floodlightcontroller/core/internal/OFSwitchManager.java @@ -0,0 +1,867 @@ +package net.floodlightcontroller.core.internal; + +import java.net.InetSocketAddress; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.CopyOnWriteArraySet; +import java.util.concurrent.Executors; + +import org.jboss.netty.bootstrap.ServerBootstrap; +import org.jboss.netty.channel.ChannelPipelineFactory; +import org.jboss.netty.channel.group.ChannelGroup; +import org.jboss.netty.channel.group.DefaultChannelGroup; +import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory; + +import net.floodlightcontroller.core.FloodlightContext; +import net.floodlightcontroller.core.HAListenerTypeMarker; +import net.floodlightcontroller.core.HARole; +import net.floodlightcontroller.core.IFloodlightProviderService; +import net.floodlightcontroller.core.IHAListener; +import net.floodlightcontroller.core.IOFConnectionBackend; +import net.floodlightcontroller.core.IOFSwitch; +import net.floodlightcontroller.core.IOFSwitch.SwitchStatus; +import net.floodlightcontroller.core.IOFSwitchBackend; +import net.floodlightcontroller.core.IOFSwitchDriver; +import net.floodlightcontroller.core.IOFSwitchListener; +import net.floodlightcontroller.core.LogicalOFMessageCategory; +import net.floodlightcontroller.core.PortChangeType; +import net.floodlightcontroller.core.SwitchDescription; +import net.floodlightcontroller.core.SwitchSyncRepresentation; +import net.floodlightcontroller.core.annotations.LogMessageDoc; +import net.floodlightcontroller.core.annotations.LogMessageDocs; +import net.floodlightcontroller.core.internal.Controller.IUpdate; +import net.floodlightcontroller.core.internal.Controller.ModuleLoaderState; +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.rest.SwitchRepresentation; +import net.floodlightcontroller.debugcounter.IDebugCounterService; +import net.floodlightcontroller.debugevent.IDebugEventService; +import net.floodlightcontroller.debugevent.IDebugEventService.EventType; +import net.floodlightcontroller.debugevent.IEventCategory; +import net.floodlightcontroller.debugevent.MockDebugEventService; + +import org.projectfloodlight.openflow.protocol.OFControllerRole; +import org.projectfloodlight.openflow.protocol.OFFactory; +import org.projectfloodlight.openflow.protocol.OFFeaturesReply; +import org.projectfloodlight.openflow.protocol.OFMessage; +import org.projectfloodlight.openflow.protocol.OFPortDesc; +import org.projectfloodlight.openflow.types.DatapathId; +import org.projectfloodlight.openflow.types.OFAuxId; +import org.sdnplatform.sync.IStoreClient; +import org.sdnplatform.sync.IStoreListener; +import org.sdnplatform.sync.ISyncService; +import org.sdnplatform.sync.Versioned; +import org.sdnplatform.sync.error.SyncException; +import org.sdnplatform.sync.error.UnknownStoreException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; + +/** + * The Switch Manager class contains most of the code involved with dealing + * with switches. The Switch manager keeps track of the switches known to the controller, + * their status, and any important information about the switch lifecycle. The + * Switch Manager also provides the switch service, which allows other modules + * to hook in switch listeners and get basic access to switch information. + * + * @author gregor, capveg, sovietaced + * + */ +public class OFSwitchManager implements IOFSwitchManager, INewOFConnectionListener, IHAListener, IFloodlightModule, IOFSwitchService, IStoreListener<DatapathId> { + private static final Logger log = LoggerFactory.getLogger(OFSwitchManager.class); + + private volatile OFControllerRole role; + private SwitchManagerCounters counters; + + private ISyncService syncService; + private IStoreClient<DatapathId, SwitchSyncRepresentation> storeClient; + public static final String SWITCH_SYNC_STORE_NAME = OFSwitchManager.class.getCanonicalName() + ".stateStore"; + + + private ConcurrentHashMap<DatapathId, OFSwitchHandshakeHandler> switchHandlers; + private ConcurrentHashMap<DatapathId, IOFSwitchBackend> switches; + private ConcurrentHashMap<DatapathId, IOFSwitch> syncedSwitches; + + + private ISwitchDriverRegistry driverRegistry; + + private Set<LogicalOFMessageCategory> logicalOFMessageCategories = new CopyOnWriteArraySet<LogicalOFMessageCategory>(); + private final List<IAppHandshakePluginFactory> handshakePlugins = new CopyOnWriteArrayList<IAppHandshakePluginFactory>(); + private int numRequiredConnections = -1; + // Event IDs for debug events + protected IEventCategory<SwitchEvent> evSwitch; + + // ISwitchService + protected Set<IOFSwitchListener> switchListeners; + + // Module Dependencies + IFloodlightProviderService floodlightProvider; + IDebugEventService debugEventService; + IDebugCounterService debugCounterService; + + /** IHAListener Implementation **/ + @Override + public void transitionToActive() { + this.role = HARole.ACTIVE.getOFRole(); + } + + @Override + public void transitionToStandby() { + this.role = HARole.STANDBY.getOFRole(); + } + + /** IOFSwitchManager Implementation **/ + + @Override public SwitchManagerCounters getCounters() { + return this.counters; + } + + private void addUpdateToQueue(IUpdate iUpdate) { + floodlightProvider.addUpdateToQueue(iUpdate); + } + + @Override + public synchronized void switchAdded(IOFSwitchBackend sw) { + DatapathId dpid = sw.getId(); + IOFSwitchBackend oldSw = this.switches.put(dpid, sw); + // Update event history + evSwitch.newEventWithFlush(new SwitchEvent(dpid, "connected")); + + if (oldSw == sw) { + // Note == for object equality, not .equals for value + counters.errorActivatedSwitchNotPresent.increment(); + log.error("Switch {} added twice?", sw); + return; + } else if (oldSw != null) { + // This happens either when we have switches with duplicate + // DPIDs or when a switch reconnects before we saw the + // disconnect + counters.switchWithSameDpidActivated.increment(); + log.warn("New switch added {} for already-added switch {}", sw, oldSw); + // We need to disconnect and remove the old switch + // TODO: we notify switch listeners that the switch has been + // removed and then we notify them that the new one has been + // added. One could argue that a switchChanged notification + // might be more appropriate in this case.... + oldSw.cancelAllPendingRequests(); + addUpdateToQueue(new SwitchUpdate(dpid, SwitchUpdateType.REMOVED)); + oldSw.disconnect(); + } + + } + + @LogMessageDocs({ + @LogMessageDoc(level="ERROR", + message="Switch {switch} activated but was already active", + explanation="A switch that was already activated was " + + "activated again. This should not happen.", + recommendation=LogMessageDoc.REPORT_CONTROLLER_BUG + ), + @LogMessageDoc(level="WARN", + message="New switch added {switch} for already-added switch {switch}", + explanation="A switch with the same DPID as another switch " + + "connected to the controller. This can be caused by " + + "multiple switches configured with the same DPID, or " + + "by a switch reconnected very quickly after " + + "disconnecting.", + recommendation="If this happens repeatedly, it is likely there " + + "are switches with duplicate DPIDs on the network. " + + "Reconfigure the appropriate switches. If it happens " + + "very rarely, then it is likely this is a transient " + + "network problem that can be ignored." + ) + }) + @Override + public synchronized void switchStatusChanged(IOFSwitchBackend sw, SwitchStatus oldStatus, SwitchStatus newStatus) { + DatapathId dpid = sw.getId(); + IOFSwitchBackend presentSw = this.switches.get(dpid); + + if (presentSw != sw) { + // Note == for object equality, not .equals for value + counters.errorActivatedSwitchNotPresent + .increment(); + log.debug("Switch {} status change but not present in sync manager", sw); + return; + } + evSwitch.newEventWithFlush(new SwitchEvent(dpid, + String.format("%s -> %s", + oldStatus, + newStatus))); + + if(newStatus == SwitchStatus.MASTER && role != OFControllerRole.ROLE_MASTER) { + counters.invalidSwitchActivatedWhileSlave.increment(); + log.error("Switch {} activated but controller not MASTER", sw); + sw.disconnect(); + return; // only react to switch connections when master + } + + if(!oldStatus.isVisible() && newStatus.isVisible()) { + // the switch has just become visible. Send 'add' notification to our + // listeners + addUpdateToQueue(new SwitchUpdate(dpid, SwitchUpdateType.ADDED)); + } else if((oldStatus.isVisible() && !newStatus.isVisible())) { + addUpdateToQueue(new SwitchUpdate(dpid, SwitchUpdateType.REMOVED)); + } + + // note: no else if - both may be true + if(oldStatus != SwitchStatus.MASTER && newStatus == SwitchStatus.MASTER ) { + counters.switchActivated.increment(); + addUpdateToQueue(new SwitchUpdate(dpid, + SwitchUpdateType.ACTIVATED)); + } else if(oldStatus == SwitchStatus.MASTER && newStatus != SwitchStatus.MASTER ) { + counters.switchDeactivated.increment(); + addUpdateToQueue(new SwitchUpdate(dpid, SwitchUpdateType.DEACTIVATED)); + } + } + + @Override + public synchronized void switchDisconnected(IOFSwitchBackend sw) { + DatapathId dpid = sw.getId(); + IOFSwitchBackend presentSw = this.switches.get(dpid); + + if (presentSw != sw) { + // Note == for object equality, not .equals for value + counters.errorActivatedSwitchNotPresent + .increment(); + log.warn("Switch {} disconnect but not present in sync manager", sw); + return; + } + + counters.switchDisconnected.increment(); + this.switches.remove(dpid); + } + + @Override public void handshakeDisconnected(DatapathId dpid) { + this.switchHandlers.remove(dpid); + } + + public Iterable<IOFSwitch> getActiveSwitches() { + ImmutableList.Builder<IOFSwitch> builder = ImmutableList.builder(); + for(IOFSwitch sw: switches.values()) { + if(sw.getStatus().isControllable()) + builder.add(sw); + } + return builder.build(); + } + + public Map<DatapathId, IOFSwitch> getAllSwitchMap(boolean showInvisible) { + if(showInvisible) { + return ImmutableMap.<DatapathId, IOFSwitch>copyOf(switches); + } else { + ImmutableMap.Builder<DatapathId, IOFSwitch> builder = ImmutableMap.builder(); + for(IOFSwitch sw: switches.values()) { + if(sw.getStatus().isVisible()) + builder.put(sw.getId(), sw); + } + return builder.build(); + } + } + + @Override + public Map<DatapathId, IOFSwitch> getAllSwitchMap() { + return getAllSwitchMap(true); + } + + @Override + public Set<DatapathId> getAllSwitchDpids() { + return getAllSwitchMap().keySet(); + } + + public Set<DatapathId> getAllSwitchDpids(boolean showInvisible) { + return getAllSwitchMap(showInvisible).keySet(); + } + + @Override + public IOFSwitch getSwitch(DatapathId dpid) { + return this.switches.get(dpid); + } + + @Override + public IOFSwitch getActiveSwitch(DatapathId dpid) { + IOFSwitchBackend sw = this.switches.get(dpid); + if(sw != null && sw.getStatus().isVisible()) + return sw; + else + return null; + } + + enum SwitchUpdateType { + ADDED, + REMOVED, + ACTIVATED, + DEACTIVATED, + PORTCHANGED, + OTHERCHANGE + } + + /** + * Update message indicating a switch was added or removed + */ + class SwitchUpdate implements IUpdate { + private final DatapathId swId; + private final SwitchUpdateType switchUpdateType; + private final OFPortDesc port; + private final PortChangeType changeType; + + public SwitchUpdate(DatapathId swId, SwitchUpdateType switchUpdateType) { + this(swId, switchUpdateType, null, null); + } + + public SwitchUpdate(DatapathId swId, + SwitchUpdateType switchUpdateType, + OFPortDesc port, + PortChangeType changeType) { + if (switchUpdateType == SwitchUpdateType.PORTCHANGED) { + if (port == null) { + throw new NullPointerException("Port must not be null " + + "for PORTCHANGED updates"); + } + if (changeType == null) { + throw new NullPointerException("ChangeType must not be " + + "null for PORTCHANGED updates"); + } + } else { + if (port != null || changeType != null) { + throw new IllegalArgumentException("port and changeType " + + "must be null for " + switchUpdateType + + " updates"); + } + } + this.swId = swId; + this.switchUpdateType = switchUpdateType; + this.port = port; + this.changeType = changeType; + } + + @Override + public void dispatch() { + if (log.isTraceEnabled()) { + log.trace("Dispatching switch update {} {}", swId, switchUpdateType); + } + if (switchListeners != null) { + for (IOFSwitchListener listener : switchListeners) { + switch(switchUpdateType) { + case ADDED: + // don't count here. We have more specific + // counters before the update is created + listener.switchAdded(swId); + break; + case REMOVED: + // don't count here. We have more specific + // counters before the update is created + listener.switchRemoved(swId); + break; + case PORTCHANGED: + counters.switchPortChanged + .increment(); + listener.switchPortChanged(swId, port, changeType); + break; + case ACTIVATED: + // don't count here. We have more specific + // counters before the update is created + listener.switchActivated(swId); + break; + case DEACTIVATED: + // ignore + break; + case OTHERCHANGE: + counters.switchOtherChange + .increment(); + listener.switchChanged(swId); + break; + } + } + } + } + } + + /** + * Handles a new OF Connection + * @param IOFConnectionBackend connection an opened OF Connection + * @param OFFeaturesReply featuresReply the features reply received for the opened connection. + * It is needed for the rest of the switch handshake. + */ + @Override + public void connectionOpened(IOFConnectionBackend connection, OFFeaturesReply featuresReply) { + DatapathId dpid = connection.getDatapathId(); + OFAuxId auxId = connection.getAuxId(); + + log.debug("{} opened", connection); + + if(auxId.equals(OFAuxId.MAIN)) { + + // Create a new switch handshake handler + OFSwitchHandshakeHandler handler = + new OFSwitchHandshakeHandler(connection, featuresReply, this, + floodlightProvider.getRoleManager(), floodlightProvider.getTimer()); + + OFSwitchHandshakeHandler oldHandler = switchHandlers.put(dpid, handler); + + // Disconnect all the handler's connections + if(oldHandler != null){ + log.debug("{} is a new main connection, killing old handler connections", connection); + oldHandler.cleanup(); + } + + handler.beginHandshake(); + + } else { + OFSwitchHandshakeHandler handler = switchHandlers.get(dpid); + + if(handler != null) { + handler.auxConnectionOpened(connection); + } + // Connections have arrived before the switchhandler is ready + else { + log.warn("{} arrived before main connection, closing connection", connection); + connection.disconnect(); + } + } + } + + @Override + public void addSwitchEvent(DatapathId dpid, String reason, boolean flushNow) { + if (flushNow) + evSwitch.newEventWithFlush(new SwitchEvent(dpid, reason)); + else + evSwitch.newEventNoFlush(new SwitchEvent(dpid, reason)); + } + + @Override + public synchronized void notifyPortChanged(IOFSwitchBackend sw, + OFPortDesc port, + PortChangeType changeType) { + Preconditions.checkNotNull(sw, "switch must not be null"); + Preconditions.checkNotNull(port, "port must not be null"); + Preconditions.checkNotNull(changeType, "changeType must not be null"); + + if (role != OFControllerRole.ROLE_MASTER) { + counters.invalidPortsChanged.increment(); + return; + } + if (!this.switches.containsKey(sw.getId())) { + counters.invalidPortsChanged.increment(); + return; + } + + if(sw.getStatus().isVisible()) { + // no need to count here. SwitchUpdate.dispatch will count + // the portchanged + SwitchUpdate update = new SwitchUpdate(sw.getId(), + SwitchUpdateType.PORTCHANGED, + port, changeType); + addUpdateToQueue(update); + } + } + + @Override + public IOFSwitchBackend getOFSwitchInstance(IOFConnectionBackend connection, + SwitchDescription description, + OFFactory factory, DatapathId datapathId) { + return this.driverRegistry.getOFSwitchInstance(connection, description, factory, datapathId); + } + + @Override + public void handleMessage(IOFSwitchBackend sw, OFMessage m, FloodlightContext bContext) { + floodlightProvider.handleMessage(sw, m, bContext); + } + + @Override + public void addOFSwitchDriver(String manufacturerDescriptionPrefix, + IOFSwitchDriver driver) { + this.driverRegistry.addSwitchDriver(manufacturerDescriptionPrefix, driver); + } + + @Override + public ImmutableList<OFSwitchHandshakeHandler> getSwitchHandshakeHandlers() { + return ImmutableList.copyOf(this.switchHandlers.values()); + } + + @Override + public int getNumRequiredConnections() { + Preconditions.checkState(numRequiredConnections >= 0, "numRequiredConnections not calculated"); + return numRequiredConnections; + } + + public Set<LogicalOFMessageCategory> getLogicalOFMessageCategories() { + return logicalOFMessageCategories; + } + + private int calcNumRequiredConnections() { + if(!this.logicalOFMessageCategories.isEmpty()){ + // We use tree set here to maintain ordering + TreeSet<OFAuxId> auxConnections = new TreeSet<OFAuxId>(); + + for(LogicalOFMessageCategory category : this.logicalOFMessageCategories){ + auxConnections.add(category.getAuxId()); + } + + OFAuxId first = auxConnections.first(); + OFAuxId last = auxConnections.last(); + + // Check for contiguous set (1....size()) + if(first.equals(OFAuxId.MAIN)) { + if(last.getValue() != auxConnections.size() - 1){ + throw new IllegalStateException("Logical OF message categories must maintain contiguous OF Aux Ids! i.e. (0,1,2,3,4,5)"); + } + return auxConnections.size() - 1; + } else if(first.equals(OFAuxId.of(1))) { + if(last.getValue() != auxConnections.size()){ + throw new IllegalStateException("Logical OF message categories must maintain contiguous OF Aux Ids! i.e. (1,2,3,4,5)"); + } + return auxConnections.size(); + } else { + throw new IllegalStateException("Logical OF message categories must start at 0 (MAIN) or 1"); + } + } else { + return 0; + } + } + + /** ISwitchService Implementation **/ + @Override + public void addOFSwitchListener(IOFSwitchListener listener) { + this.switchListeners.add(listener); + } + + @Override + public void removeOFSwitchListener(IOFSwitchListener listener) { + this.switchListeners.remove(listener); + } + + @Override + public void registerLogicalOFMessageCategory(LogicalOFMessageCategory category) { + logicalOFMessageCategories.add(category); + } + + @Override + public boolean isCategoryRegistered(LogicalOFMessageCategory category) { + return logicalOFMessageCategories.contains(category); + } + + @Override + public SwitchRepresentation getSwitchRepresentation(DatapathId dpid) { + IOFSwitch sw = this.switches.get(dpid); + OFSwitchHandshakeHandler handler = this.switchHandlers.get(dpid); + + if(sw != null && handler != null) { + return new SwitchRepresentation(sw, handler); + } + return null; + } + + @Override + public List<SwitchRepresentation> getSwitchRepresentations() { + + List<SwitchRepresentation> representations = new ArrayList<SwitchRepresentation>(); + + for(DatapathId dpid : this.switches.keySet()) { + SwitchRepresentation representation = getSwitchRepresentation(dpid); + if(representation != null) { + representations.add(representation); + } + } + return representations; + } + + @Override + public void registerHandshakePlugin(IAppHandshakePluginFactory factory) { + Preconditions.checkState(floodlightProvider.getModuleLoaderState() == ModuleLoaderState.INIT, + "handshakeplugins can only be registered when the module loader is in state INIT!"); + handshakePlugins.add(factory); + } + + @Override + public List<IAppHandshakePluginFactory> getHandshakePlugins() { + return handshakePlugins; + } + + /* IFloodlightModule Implementation */ + @Override + public Collection<Class<? extends IFloodlightService>> + getModuleServices() { + Collection<Class<? extends IFloodlightService>> l = + new ArrayList<Class<? extends IFloodlightService>>(); + l.add(IOFSwitchService.class); + return l; + } + + @Override + public Map<Class<? extends IFloodlightService>, IFloodlightService> + getServiceImpls() { + Map<Class<? extends IFloodlightService>, IFloodlightService> m = + new HashMap<Class<? extends IFloodlightService>, IFloodlightService>(); + m.put(IOFSwitchService.class, this); + return m; + } + + @Override + public Collection<Class<? extends IFloodlightService>> + getModuleDependencies() { + Collection<Class<? extends IFloodlightService>> l = new ArrayList<Class<? extends IFloodlightService>>(); + + l.add(IFloodlightProviderService.class); + l.add(IDebugEventService.class); + l.add(IDebugCounterService.class); + l.add(ISyncService.class); + + return l; + } + + @Override + public void init(FloodlightModuleContext context) throws FloodlightModuleException { + // Module dependencies + floodlightProvider = context.getServiceImpl(IFloodlightProviderService.class); + debugEventService = context.getServiceImpl(IDebugEventService.class); + debugCounterService = context.getServiceImpl(IDebugCounterService.class); + syncService = context.getServiceImpl(ISyncService.class); + + // Module variables + switchHandlers = new ConcurrentHashMap<DatapathId, OFSwitchHandshakeHandler>(); + switches = new ConcurrentHashMap<DatapathId, IOFSwitchBackend>(); + syncedSwitches = new ConcurrentHashMap<DatapathId, IOFSwitch>(); + floodlightProvider.getTimer(); + counters = new SwitchManagerCounters(debugCounterService); + driverRegistry = new NaiveSwitchDriverRegistry(this); + + this.switchListeners = new CopyOnWriteArraySet<IOFSwitchListener>(); + + this.counters = new SwitchManagerCounters(debugCounterService); + /* TODO @Ryan + try { + this.storeClient = this.syncService.getStoreClient( + SWITCH_SYNC_STORE_NAME, + DatapathId.class, + SwitchSyncRepresentation.class); + this.storeClient.addStoreListener(this); + } catch (UnknownStoreException e) { + throw new FloodlightModuleException("Error while setting up sync store client", e); + } */ + + } + + @Override + public void startUp(FloodlightModuleContext context) throws FloodlightModuleException { + startUpBase(context); + bootstrapNetty(); + } + + /** + * Startup method that includes everything besides the netty boostrap. + * This has been isolated for testing. + * @param context floodlight module context + * @throws FloodlightModuleException + */ + public void startUpBase(FloodlightModuleContext context) throws FloodlightModuleException { + // Initial Role + role = floodlightProvider.getRole().getOFRole(); + + // IRoleListener + floodlightProvider.addHAListener(this); + + loadLogicalCategories(); + + registerDebugEvents(); + } + + /** + * Bootstraps netty, the server that handles all openflow connections + */ + public void bootstrapNetty() { + try { + final ServerBootstrap bootstrap = createServerBootStrap(); + + bootstrap.setOption("reuseAddr", true); + bootstrap.setOption("child.keepAlive", true); + bootstrap.setOption("child.tcpNoDelay", true); + bootstrap.setOption("child.sendBufferSize", Controller.SEND_BUFFER_SIZE); + + ChannelPipelineFactory pfact = + new OpenflowPipelineFactory(this, floodlightProvider.getTimer(), this, debugCounterService); + bootstrap.setPipelineFactory(pfact); + InetSocketAddress sa = new InetSocketAddress(floodlightProvider.getOFPort()); + final ChannelGroup cg = new DefaultChannelGroup(); + cg.add(bootstrap.bind(sa)); + + log.info("Listening for switch connections on {}", sa); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * Helper that bootstrapNetty. + * @return + */ + private ServerBootstrap createServerBootStrap() { + if (floodlightProvider.getWorkerThreads() == 0) { + return new ServerBootstrap( + new NioServerSocketChannelFactory( + Executors.newCachedThreadPool(), + Executors.newCachedThreadPool())); + } else { + return new ServerBootstrap( + new NioServerSocketChannelFactory( + Executors.newCachedThreadPool(), + Executors.newCachedThreadPool(), floodlightProvider.getWorkerThreads())); + } + } + + /** + * Performs startup related actions for logical OF message categories. + * Setting the categories list to immutable ensures that unsupported operation + * exceptions will be activated if modifications are attempted. + */ + public void loadLogicalCategories() { + logicalOFMessageCategories = ImmutableSet.copyOf(logicalOFMessageCategories); + numRequiredConnections = calcNumRequiredConnections(); + } + + /** + * Registers an event handler with the debug event service + * for switch events. + * @throws FloodlightModuleException + */ + private void registerDebugEvents() throws FloodlightModuleException { + if (debugEventService == null) { + debugEventService = new MockDebugEventService(); + } + evSwitch = debugEventService.buildEvent(SwitchEvent.class) + .setModuleName(this.counters.getPrefix()) + .setEventName("switch-event") + .setEventDescription("Switch connected, disconnected or port changed") + .setEventType(EventType.ALWAYS_LOG) + .setBufferCapacity(100) + .register(); + } + + @Override + public String getName() { + return null; + } + + @Override + public boolean isCallbackOrderingPrereq(HAListenerTypeMarker type, String name) { + return false; + } + + @Override + public boolean isCallbackOrderingPostreq(HAListenerTypeMarker type, String name) { + return false; + } + + @Override + public void controllerNodeIPsChanged(Map<String, String> curControllerNodeIPs, + Map<String, String> addedControllerNodeIPs, + Map<String, String> removedControllerNodeIPs) { + } + + @Override + public void keysModified(Iterator<DatapathId> keys, UpdateType type) { + if (type == UpdateType.LOCAL) { + // We only care for remote updates + return; + } + while(keys.hasNext()) { + DatapathId key = keys.next(); + Versioned<SwitchSyncRepresentation> versionedSwitch = null; + try { + versionedSwitch = storeClient.get(key); + } catch (SyncException e) { + log.error("Exception while retrieving switch " + key.toString() + + " from sync store. Skipping", e); + continue; + } + if (log.isTraceEnabled()) { + log.trace("Reveiced switch store notification: key={}, " + + "entry={}", key, versionedSwitch.getValue()); + } + // versionedSwtich won't be null. storeClient.get() always + // returns a non-null or throws an exception + if (versionedSwitch.getValue() == null) { + switchRemovedFromStore(key); + continue; + } + SwitchSyncRepresentation storedSwitch = versionedSwitch.getValue(); + IOFSwitch sw = getSwitch(storedSwitch.getDpid()); + //TODO @Ryan need to get IOFSwitchBackend setFeaturesReply(storedSwitch.getFeaturesReply(sw.getOFFactory())); + if (!key.equals(storedSwitch.getFeaturesReply(sw.getOFFactory()).getDatapathId())) { + log.error("Inconsistent DPIDs from switch sync store: " + + "key is {} but sw.getId() says {}. Ignoring", + key.toString(), sw.getId()); + continue; + } + switchAddedToStore(sw); + } + } + + /** + * Called when we receive a store notification about a switch that + * has been removed from the sync store + * @param dpid + */ + private synchronized void switchRemovedFromStore(DatapathId dpid) { + if (floodlightProvider.getRole() != HARole.STANDBY) { + return; // only read from store if slave + } + IOFSwitch oldSw = syncedSwitches.remove(dpid); + if (oldSw != null) { + addUpdateToQueue(new SwitchUpdate(dpid, SwitchUpdateType.REMOVED)); + } else { + // TODO: the switch was deleted (tombstone) before we ever + // knew about it (or was deleted repeatedly). Can this + // happen? When/how? + } + } + + /** + * Called when we receive a store notification about a new or updated + * switch. + * @param sw + */ + private synchronized void switchAddedToStore(IOFSwitch sw) { + if (floodlightProvider.getRole() != HARole.STANDBY) { + return; // only read from store if slave + } + DatapathId dpid = sw.getId(); + + IOFSwitch oldSw = syncedSwitches.put(dpid, sw); + if (oldSw == null) { + addUpdateToQueue(new SwitchUpdate(dpid, SwitchUpdateType.ADDED)); + } else { + // The switch already exists in storage, see if anything + // has changed + sendNotificationsIfSwitchDiffers(oldSw, sw); + } + } + + /** + * Check if the two switches differ in their ports or in other + * fields and if they differ enqueue a switch update + * @param oldSw + * @param newSw + */ + private synchronized void sendNotificationsIfSwitchDiffers(IOFSwitch oldSw, IOFSwitch newSw) { + /*TODO @Ryan Collection<PortChangeEvent> portDiffs = oldSw.comparePorts(newSw.getPorts()); + for (PortChangeEvent ev: portDiffs) { + SwitchUpdate update = new SwitchUpdate(newSw.getId(), + SwitchUpdateType.PORTCHANGED, + ev.port, ev.type); + addUpdateToQueue(update); + }*/ + } +} diff --git a/src/main/java/net/floodlightcontroller/core/internal/OpenflowPipelineFactory.java b/src/main/java/net/floodlightcontroller/core/internal/OpenflowPipelineFactory.java index 82a3c67d658ddee3b20f702f2019117a044ccb4b..23f3f01413dbf4926f431ae7ce113f11ef39310f 100644 --- a/src/main/java/net/floodlightcontroller/core/internal/OpenflowPipelineFactory.java +++ b/src/main/java/net/floodlightcontroller/core/internal/OpenflowPipelineFactory.java @@ -1,7 +1,7 @@ /** -* Copyright 2011, Big Switch Networks, Inc. +* Copyright 2011, Big Switch Networks, Inc. * Originally created by David Erickson, Stanford University -* +* * Licensed under the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. You may obtain * a copy of the License at @@ -17,61 +17,106 @@ package net.floodlightcontroller.core.internal; -import java.util.concurrent.ThreadPoolExecutor; - import org.jboss.netty.channel.ChannelPipeline; import org.jboss.netty.channel.ChannelPipelineFactory; import org.jboss.netty.channel.Channels; -import org.jboss.netty.handler.execution.ExecutionHandler; import org.jboss.netty.handler.timeout.IdleStateHandler; import org.jboss.netty.handler.timeout.ReadTimeoutHandler; import org.jboss.netty.util.ExternalResourceReleasable; -import org.jboss.netty.util.HashedWheelTimer; import org.jboss.netty.util.Timer; +import net.floodlightcontroller.debugcounter.IDebugCounterService; /** * Creates a ChannelPipeline for a server-side openflow channel - * @author readams + * @author readams, sovietaced */ -public class OpenflowPipelineFactory +public class OpenflowPipelineFactory implements ChannelPipelineFactory, ExternalResourceReleasable { - protected Controller controller; - protected ThreadPoolExecutor pipelineExecutor; + protected IOFSwitchManager switchManager; + protected INewOFConnectionListener connectionListener; protected Timer timer; protected IdleStateHandler idleHandler; protected ReadTimeoutHandler readTimeoutHandler; - - public OpenflowPipelineFactory(Controller controller, - ThreadPoolExecutor pipelineExecutor) { + protected IDebugCounterService debugCounters; + + public OpenflowPipelineFactory(IOFSwitchManager switchManager, Timer timer, + INewOFConnectionListener connectionListener, + IDebugCounterService debugCounters) { super(); - this.controller = controller; - this.pipelineExecutor = pipelineExecutor; - this.timer = new HashedWheelTimer(); - this.idleHandler = new IdleStateHandler(timer, 20, 25, 0); + this.switchManager = switchManager; + this.connectionListener = connectionListener; + this.timer = timer; + this.debugCounters = debugCounters; + this.idleHandler = new IdleStateHandler( + timer, + PipelineIdleReadTimeout.MAIN, + PipelineIdleWriteTimeout.MAIN, + 0); this.readTimeoutHandler = new ReadTimeoutHandler(timer, 30); } - + @Override public ChannelPipeline getPipeline() throws Exception { - OFChannelHandler handler = new OFChannelHandler(controller); - ChannelPipeline pipeline = Channels.pipeline(); - pipeline.addLast("ofmessagedecoder", new OFMessageDecoder()); - pipeline.addLast("ofmessageencoder", new OFMessageEncoder()); - pipeline.addLast("idle", idleHandler); - pipeline.addLast("timeout", readTimeoutHandler); - pipeline.addLast("handshaketimeout", - new HandshakeTimeoutHandler(handler, timer, 15)); - if (pipelineExecutor != null) - pipeline.addLast("pipelineExecutor", - new ExecutionHandler(pipelineExecutor)); - pipeline.addLast("handler", handler); + OFChannelHandler handler = new OFChannelHandler(switchManager, + connectionListener, + pipeline, + debugCounters, + timer); + + pipeline.addLast(PipelineHandler.OF_MESSAGE_DECODER, + new OFMessageDecoder()); + pipeline.addLast(PipelineHandler.OF_MESSAGE_ENCODER, + new OFMessageEncoder()); + pipeline.addLast(PipelineHandler.MAIN_IDLE, idleHandler); + pipeline.addLast(PipelineHandler.READ_TIMEOUT, readTimeoutHandler); + pipeline.addLast(PipelineHandler.CHANNEL_HANDSHAKE_TIMEOUT, + new HandshakeTimeoutHandler( + handler, + timer, + PipelineHandshakeTimeout.CHANNEL)); + pipeline.addLast(PipelineHandler.CHANNEL_HANDLER, handler); return pipeline; } @Override public void releaseExternalResources() { - timer.stop(); + timer.stop(); + } + + public static class PipelineHandler { + final static String CHANNEL_HANDSHAKE_TIMEOUT = "channelhandshaketimeout"; + final static String SWITCH_HANDSHAKE_TIMEOUT = "switchhandshaketimeout"; + final static String CHANNEL_HANDLER = "channelhandler"; + final static String MAIN_IDLE = "mainidle"; + final static String AUX_IDLE = "auxidle"; + final static String OF_MESSAGE_DECODER = "ofmessagedecoder"; + final static String OF_MESSAGE_ENCODER = "ofmessageencoder"; + final static String READ_TIMEOUT = "readtimeout"; + } + + /** + * Timeouts for parts of the handshake, in seconds + */ + public static class PipelineHandshakeTimeout { + final static int CHANNEL = 10; + final static int SWITCH = 30; + } + + /** + * Timeouts for writes on connections, in seconds + */ + public static class PipelineIdleWriteTimeout { + final static int MAIN = 2; + final static int AUX = 15; + } + + /** + * Timeouts for reads on connections, in seconds + */ + public static class PipelineIdleReadTimeout { + final static int MAIN = 3 * PipelineIdleWriteTimeout.MAIN; + final static int AUX = 3 * PipelineIdleWriteTimeout.AUX; } } diff --git a/src/main/java/net/floodlightcontroller/core/internal/PluginResult.java b/src/main/java/net/floodlightcontroller/core/internal/PluginResult.java new file mode 100644 index 0000000000000000000000000000000000000000..e11a527de0a4df91c500bfd5e421fae6c9dd94c9 --- /dev/null +++ b/src/main/java/net/floodlightcontroller/core/internal/PluginResult.java @@ -0,0 +1,40 @@ +package net.floodlightcontroller.core.internal; + +import javax.annotation.Nonnull; + +import net.floodlightcontroller.core.internal.OFSwitchAppHandshakePlugin.PluginResultType; + +import com.google.common.base.Preconditions; + +/** + * Class that represents the result of an app handshake plugin. + * + */ +public class PluginResult { + + private final PluginResultType result; + private final String reason; + + public PluginResult(@Nonnull PluginResultType result) { + this.result = result; + this.reason = null; + } + + public PluginResult(@Nonnull PluginResultType result, String reason) { + Preconditions.checkNotNull(result, "result must not be null"); + + if(result != PluginResultType.QUARANTINE && reason != null) + throw new IllegalStateException("Reason can only be set for Quarantine PluginResult"); + + this.result = result; + this.reason = reason; + } + + public PluginResultType getResultType() { + return this.result; + } + + public String getReason() { + return this.reason; + } +} \ No newline at end of file diff --git a/src/main/java/net/floodlightcontroller/core/internal/RoleManager.java b/src/main/java/net/floodlightcontroller/core/internal/RoleManager.java new file mode 100644 index 0000000000000000000000000000000000000000..92a106307f6c85b4a7e3b322826f1258d893f677 --- /dev/null +++ b/src/main/java/net/floodlightcontroller/core/internal/RoleManager.java @@ -0,0 +1,254 @@ +package net.floodlightcontroller.core.internal; + +import java.util.Map.Entry; + +import javax.annotation.Nonnull; + +import java.util.Date; +import net.floodlightcontroller.core.HARole; +import net.floodlightcontroller.core.IHAListener; +import net.floodlightcontroller.core.IOFSwitch; +import net.floodlightcontroller.core.IOFSwitchBackend; +import net.floodlightcontroller.core.IShutdownService; +import net.floodlightcontroller.core.RoleInfo; +import net.floodlightcontroller.core.internal.Controller.IUpdate; +import org.projectfloodlight.openflow.protocol.OFControllerRole; +import org.projectfloodlight.openflow.types.DatapathId; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.Preconditions; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +/** + * A utility class to manage the <i>controller roles</i>. + * + * A utility class to manage the <i>controller roles</i> as opposed + * to the switch roles. The class manages the controllers current role, + * handles role change requests, and maintains the list of connected + * switch(-channel) so it can notify the switches of role changes. + * + * We need to ensure that every connected switch is always send the + * correct role. Therefore, switch add, sending of the intial role, and + * changing role need to use mutexes to ensure this. This has the ugly + * side-effect of requiring calls between controller and OFChannelHandler + * + * This class is fully thread safe. Its method can safely be called from + * any thread. + * + * @author gregor + * + */ +public class RoleManager { + private volatile RoleInfo currentRoleInfo; + private final Controller controller; + private final IShutdownService shutdownService; + private final RoleManagerCounters counters; + + private static final Logger log = + LoggerFactory.getLogger(RoleManager.class); + + /** + * @param role initial role + * @param roleChangeDescription initial value of the change description + * @throws NullPointerException if role or roleChangeDescription is null + */ + public RoleManager(@Nonnull Controller controller, + @Nonnull IShutdownService shutdownService, + @Nonnull HARole role, + @Nonnull String roleChangeDescription) { + Preconditions.checkNotNull(controller, "controller must not be null"); + Preconditions.checkNotNull(role, "role must not be null"); + Preconditions.checkNotNull(roleChangeDescription, "roleChangeDescription must not be null"); + Preconditions.checkNotNull(shutdownService, "shutdownService must not be null"); + + this.currentRoleInfo = new RoleInfo(role, + roleChangeDescription, + new Date()); + this.controller = controller; + this.shutdownService = shutdownService; + this.counters = new RoleManagerCounters(controller.getDebugCounter()); + } + + /** + * Re-assert a role for the given channel handler. + * + * The caller specifies the role that should be reasserted. We only + * reassert the role if the controller's current role matches the + * reasserted role and there is no role request for the reasserted role + * pending. + * @param ofSwitchHandshakeHandler The OFChannelHandler on which we should reassert. + * @param role The role to reassert + */ + public synchronized void reassertRole(OFSwitchHandshakeHandler ofSwitchHandshakeHandler, HARole role) { + // check if the requested reassertion actually makes sense + if (this.getRole() != role) + return; + ofSwitchHandshakeHandler.sendRoleRequestIfNotPending(this.getRole().getOFRole()); + } + + /** + * Set the controller's new role and notify switches. + * + * This method updates the controllers current role and notifies all + * connected switches of the new role is different from the current + * role. We dampen calls to this method. See class description for + * details. + * + * @param role The new role. + * @param roleChangeDescription A textual description of why the role + * was changed. For information purposes only. + * @throws NullPointerException if role or roleChangeDescription is null + */ + public synchronized void setRole(@Nonnull HARole role, @Nonnull String roleChangeDescription) { + Preconditions.checkNotNull(role, "role must not be null"); + Preconditions.checkNotNull(roleChangeDescription, "roleChangeDescription must not be null"); + + if (role == getRole()) { + counters.setSameRole.increment(); + log.debug("Received role request for {} but controller is " + + "already {}. Ignoring it.", role, this.getRole()); + return; + } + + if (this.getRole() == HARole.STANDBY && role == HARole.ACTIVE) { + // At this point we are guaranteed that we will execute the code + // below exactly once during the lifetime of this process! And + // it will be a to MASTER transition + counters.setRoleMaster.increment(); + } + + log.info("Received role request for {} (reason: {})." + + " Initiating transition", role, roleChangeDescription); + + currentRoleInfo = + new RoleInfo(role, roleChangeDescription, new Date()); + + // NOTE: HARoleUpdate will terminate floodlight on transition to STANDBY + controller.addUpdateToQueue(new HARoleUpdate(role)); + controller.addUpdateToQueue(new SwitchRoleUpdate(role)); + + } + + @SuppressFBWarnings(value="UG_SYNC_SET_UNSYNC_GET", + justification = "setter is synchronized for mutual exclusion, " + + "currentRoleInfo is volatile, so no sync on getter needed") + public synchronized HARole getRole() { + return currentRoleInfo.getRole(); + } + + public synchronized OFControllerRole getOFControllerRole() { + return getRole().getOFRole(); + } + + /** + * Return the RoleInfo object describing the current role. + * + * Return the RoleInfo object describing the current role. The + * RoleInfo object is used by REST API users. + * @return the current RoleInfo object + */ + public RoleInfo getRoleInfo() { + return currentRoleInfo; + } + + private void attemptActiveTransition() { + if(!switchesHaveAnotherMaster()){ + // No valid cluster controller connections found, become ACTIVE! + setRole(HARole.ACTIVE, "Leader election assigned ACTIVE role"); + } + } + + /** + * Iterates over all the switches and checks to see if they have controller + * connections that points towards another master controller. + * @return + */ + private boolean switchesHaveAnotherMaster() { + IOFSwitchService switchService = controller.getSwitchService(); + + for(Entry<DatapathId, IOFSwitch> switchMap : switchService.getAllSwitchMap().entrySet()){ + IOFSwitchBackend sw = (IOFSwitchBackend) switchMap.getValue(); + if(sw.hasAnotherMaster()){ + return true; + } + } + return false; + } + + public void notifyControllerConnectionUpdate() { + if(currentRoleInfo.getRole() != HARole.ACTIVE) { + attemptActiveTransition(); + } + } + + /** + * Update message indicating controller's role has changed. + * RoleManager, which enqueues these updates guarantees that we will + * only have a single transition from SLAVE to MASTER. + * + * When the role update from master to slave is complete, the HARoleUpdate + * will terminate floodlight. + */ + private class HARoleUpdate implements IUpdate { + private final HARole newRole; + public HARoleUpdate(HARole newRole) { + this.newRole = newRole; + } + + @Override + public void dispatch() { + if (log.isDebugEnabled()) { + log.debug("Dispatching HA Role update newRole = {}", + newRole); + } + for (IHAListener listener : controller.haListeners.getOrderedListeners()) { + if (log.isTraceEnabled()) { + log.trace("Calling HAListener {} with transitionTo{}", + listener.getName(), newRole); + } + switch(newRole) { + case ACTIVE: + listener.transitionToActive(); + break; + case STANDBY: + listener.transitionToStandby(); + break; + } + } + + controller.setNotifiedRole(newRole); + + if(newRole == HARole.STANDBY) { + String reason = String.format("Received role request to " + + "transition from ACTIVE to STANDBY (reason: %s)", + getRoleInfo().getRoleChangeDescription()); + shutdownService.terminate(reason, 0); + } + } + } + + public class SwitchRoleUpdate implements IUpdate { + private final HARole role; + + public SwitchRoleUpdate(HARole role) { + this.role = role; + } + + @Override + public void dispatch() { + if (log.isDebugEnabled()) { + log.debug("Dispatching switch role update newRole = {}, switch role = {}", + this.role, this.role.getOFRole()); + } + + for (OFSwitchHandshakeHandler h: controller.getSwitchService().getSwitchHandshakeHandlers()) + h.sendRoleRequest(this.role.getOFRole()); + } + } + + public RoleManagerCounters getCounters() { + return this.counters; + } +} diff --git a/src/main/java/net/floodlightcontroller/core/internal/RoleManagerCounters.java b/src/main/java/net/floodlightcontroller/core/internal/RoleManagerCounters.java new file mode 100644 index 0000000000000000000000000000000000000000..04d948339ecedc0c487a57384481f155591eb328 --- /dev/null +++ b/src/main/java/net/floodlightcontroller/core/internal/RoleManagerCounters.java @@ -0,0 +1,32 @@ +package net.floodlightcontroller.core.internal; + +import net.floodlightcontroller.debugcounter.IDebugCounter; +import net.floodlightcontroller.debugcounter.IDebugCounterService; +import net.floodlightcontroller.debugcounter.IDebugCounterService.MetaData; + + public class RoleManagerCounters { + + public final String prefix = RoleManager.class.getSimpleName(); + public final IDebugCounter setSameRole; + public final IDebugCounter setRoleMaster; + + public RoleManagerCounters(IDebugCounterService debugCounters) { + debugCounters.registerModule(prefix); + setSameRole = + debugCounters.registerCounter( + prefix, "set-same-role", + "Controller received a role request for the same " + + "role the controller already had", + MetaData.WARN); + + setRoleMaster = + debugCounters.registerCounter( + prefix, "set-role-master", + "Controller received a role request with role of " + + "MASTER. This counter can be at most 1."); + } + + public String getPrefix(){ + return this.prefix; + } + } \ No newline at end of file diff --git a/src/main/java/net/floodlightcontroller/core/internal/ShutdownServiceImpl.java b/src/main/java/net/floodlightcontroller/core/internal/ShutdownServiceImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..0c1c8c6d25b28fd4bf5c3fb9c0e85399bd95ce6c --- /dev/null +++ b/src/main/java/net/floodlightcontroller/core/internal/ShutdownServiceImpl.java @@ -0,0 +1,170 @@ +package net.floodlightcontroller.core.internal; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CopyOnWriteArrayList; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import net.floodlightcontroller.core.IShutdownListener; +import net.floodlightcontroller.core.IShutdownService; +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 org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +public class ShutdownServiceImpl implements IFloodlightModule, + IShutdownService { + private static final Logger logger = + LoggerFactory.getLogger(ShutdownServiceImpl.class); + + /** Maximum amount of time for IShutdownListeners to complete before + * floodlight terminates in any case + */ + private static final int MAX_SHUTDOWN_WAIT_MS = 5_000; + + private final CopyOnWriteArrayList<IShutdownListener> shutdownListeners = + new CopyOnWriteArrayList<>(); + + @Override + public Collection<Class<? extends IFloodlightService>> getModuleServices() { + Collection<Class<? extends IFloodlightService>> l = + new ArrayList<>(); + l.add(IShutdownService.class); + return l; + } + + @Override + public Map<Class<? extends IFloodlightService>, IFloodlightService> + getServiceImpls() { + Map<Class<? extends IFloodlightService>, IFloodlightService> m = + new HashMap<>(); + m.put(IShutdownService.class, this); + return m; + } + + @Override + public Collection<Class<? extends IFloodlightService>> + getModuleDependencies() { + return new ArrayList<>(); + } + + @Override + public void init(FloodlightModuleContext context) + throws FloodlightModuleException { + // do nothing + } + + @Override + public void startUp(FloodlightModuleContext context) + throws FloodlightModuleException { + // do nothing + } + + @Override + public void registerShutdownListener(@Nonnull IShutdownListener listener) { + if (listener == null) { + throw new NullPointerException("listener must not be null"); + } + shutdownListeners.add(listener); + } + + @SuppressFBWarnings(value="DM_EXIT", justification="exit by design") + @Override + public void terminate(@Nullable final String reason, final int exitCode) { + final String paddedReason; + if (reason == null) { + paddedReason = ""; + } else { + paddedReason = " due to " + reason; + } + // A safety valve to make sure we really do indeed terminate floodlight + // We are using a Thread rather than a task to make sure nothing can + // cancel the shutdown (e.g., if an ExecutorService was already + // shutdown + Thread shutdownForHungListeners = new Thread(new Runnable() { + // Suppress findbugs warning that we call system.exit + @SuppressFBWarnings(value="DM_EXIT", justification="exit by design") + @Override + public void run() { + try { + Thread.sleep(MAX_SHUTDOWN_WAIT_MS); + } catch (InterruptedException e) { + // do nothing, we are about to exit anyways + } + logger.error("**************************************************"); + logger.error("* Floodlight is terminating {}", paddedReason); + logger.error("* ShutdownListeners failed to complete in time"); + logger.error("**************************************************"); + System.exit(exitCode); + } + }, "ShutdownSafetyValve"); + shutdownForHungListeners.start(); + logger.info("Floodlight about to terminate. Calling shutdown listeners"); + for (IShutdownListener listener: shutdownListeners) { + listener.floodlightIsShuttingDown(); + } + if (exitCode == 0) { + logger.info("**************************************************"); + logger.info("* Floodlight is terminating normally{}", paddedReason); + logger.info("**************************************************"); + } else { + logger.error("**************************************************"); + logger.error("* Floodlight is terminating abnormally{}", paddedReason); + logger.error("**************************************************"); + } + // Game Over. + System.exit(exitCode); + } + + @SuppressFBWarnings(value="DM_EXIT", justification="exit by design") + @Override + public void terminate(final String reason, final Throwable e, final int exitCode) { + final String paddedReason; + if (reason == null) { + paddedReason = ""; + } else { + paddedReason = " due to " + reason; + } + // A safety valve to make sure we really do indeed terminate floodlight + // We are using a Thread rather than a task to make sure nothing can + // cancel the shutdown (e.g., if an ExecutorService was already + // shutdown + Thread shutdownForHungListeners = new Thread(new Runnable() { + // Suppress findbugs warning that we call system.exit + @SuppressFBWarnings(value="DM_EXIT", justification="exit by design") + @Override + public void run() { + try { + Thread.sleep(MAX_SHUTDOWN_WAIT_MS); + } catch (InterruptedException e) { + // do nothing, we are about to exit anyways + } + logger.error("**************************************************"); + logger.error("Floodlight is terminating{}", paddedReason, e); + logger.error("ShutdownListeners failed to complete in time"); + logger.error("**************************************************"); + System.exit(exitCode); + } + }, "ShutdownSafetyValve"); + shutdownForHungListeners.start(); + logger.info("Floodlight about to terminate. Calling shutdown listeners"); + for (IShutdownListener listener: shutdownListeners) { + listener.floodlightIsShuttingDown(); + } + logger.error("**************************************************"); + logger.error("Floodlight is terminating abnormally{}", paddedReason, e); + logger.error("**************************************************"); + // Game Over. + System.exit(exitCode); + + } +} diff --git a/src/main/java/net/floodlightcontroller/core/internal/SwitchEvent.java b/src/main/java/net/floodlightcontroller/core/internal/SwitchEvent.java new file mode 100644 index 0000000000000000000000000000000000000000..7a4f36b37cc54e5c9c5d7698ffc04b9ac7adba2d --- /dev/null +++ b/src/main/java/net/floodlightcontroller/core/internal/SwitchEvent.java @@ -0,0 +1,18 @@ +package net.floodlightcontroller.core.internal; + +import net.floodlightcontroller.debugevent.IDebugEventService.EventColumn; +import net.floodlightcontroller.debugevent.IDebugEventService.EventFieldType; +import org.projectfloodlight.openflow.types.DatapathId; + +public class SwitchEvent { + @EventColumn(name = "dpid", description = EventFieldType.DPID) + DatapathId dpid; + + @EventColumn(name = "reason", description = EventFieldType.STRING) + String reason; + + public SwitchEvent(DatapathId dpid, String reason) { + this.dpid = dpid; + this.reason = reason; + } +} \ No newline at end of file diff --git a/src/main/java/net/floodlightcontroller/core/internal/SwitchManagerCounters.java b/src/main/java/net/floodlightcontroller/core/internal/SwitchManagerCounters.java new file mode 100644 index 0000000000000000000000000000000000000000..942809cd45d4a060c865a9355442830e713ae318 --- /dev/null +++ b/src/main/java/net/floodlightcontroller/core/internal/SwitchManagerCounters.java @@ -0,0 +1,208 @@ +package net.floodlightcontroller.core.internal; + +import net.floodlightcontroller.debugcounter.IDebugCounter; +import net.floodlightcontroller.debugcounter.IDebugCounterService; +import net.floodlightcontroller.debugcounter.IDebugCounterService.MetaData; + +public class SwitchManagerCounters { + + public final String prefix = OFSwitchManager.class.getSimpleName(); + public final IDebugCounter invalidPortsChanged; + public final IDebugCounter switchConnected; + public final IDebugCounter invalidSwitchActivatedWhileSlave; + public final IDebugCounter switchActivated; + public final IDebugCounter switchDeactivated; + public final IDebugCounter errorActivatedSwitchNotPresent; + public final IDebugCounter switchWithSameDpidActivated; + public final IDebugCounter switchDisconnected; + public final IDebugCounter switchPortChanged; + public final IDebugCounter switchOtherChange; + public final IDebugCounter switchDisconnectReadTimeout; + public final IDebugCounter switchDisconnectHandshakeTimeout; + public final IDebugCounter switchDisconnectIOError; + public final IDebugCounter switchDisconnectParseError; + public final IDebugCounter switchDisconnectSwitchStateException; + public final IDebugCounter rejectedExecutionException; + public final IDebugCounter switchDisconnectOtherException; + public final IDebugCounter unhandledMessage; + public final IDebugCounter packetInWhileSwitchIsSlave; + public final IDebugCounter roleNotResentBecauseRolePending; + public final IDebugCounter epermErrorWhileSwitchIsMaster; + public final IDebugCounter roleReplyTimeout; + public final IDebugCounter roleReplyReceived; + public final IDebugCounter roleReplyErrorUnsupported; + + public SwitchManagerCounters(IDebugCounterService debugCounters) { + debugCounters.registerModule(prefix); + invalidPortsChanged = + debugCounters.registerCounter( + prefix, "invalid-ports-changed", + "Received an unexpected ports changed " + + "notification while the controller was in " + + "SLAVE role.", + MetaData.WARN); + + invalidSwitchActivatedWhileSlave = + debugCounters.registerCounter( + prefix, "invalid-switch-activated-while-slave", + "Received an unexpected switchActivated " + + "notification while the controller was in " + + "SLAVE role.", + MetaData.WARN); + + switchActivated = + debugCounters.registerCounter( + prefix, "switch-activated", + "A switch connected to this controller is now " + + "in MASTER role"); + + switchDeactivated = + debugCounters.registerCounter( + prefix, "switch-activated", + "A switch connected to this controller is now " + + "in SLAVE role"); + + errorActivatedSwitchNotPresent = // err + debugCounters.registerCounter( + prefix, "error-same-switch-reactivated", + "A switch that was already in active state " + + "was activated again. This indicates a " + + "controller defect", + MetaData.ERROR); + + switchWithSameDpidActivated = // warn + debugCounters.registerCounter( + prefix, "switch-with-same-dpid-activated", + "A switch with the same DPID as another switch " + + "connected to the controller. This can be " + + "caused by multiple switches configured with " + + "the same DPID or by a switch reconnecting very " + + "quickly.", + MetaData.WARN); + + switchDisconnected = + debugCounters.registerCounter( + prefix, "switch-disconnected", + "FIXME: switch has disconnected"); + + switchPortChanged = + debugCounters.registerCounter( + prefix, "switch-port-changed", + "Number of times switch ports have changed"); + switchOtherChange = + debugCounters.registerCounter( + prefix, "switch-other-change", + "Number of times other information of a switch " + + "has changed."); + + switchDisconnectReadTimeout = + debugCounters.registerCounter( + prefix, "switch-disconnect-read-timeout", + "Number of times a switch was disconnected due " + + "due the switch failing to send OpenFlow " + + "messages or responding to OpenFlow ECHOs", + MetaData.ERROR); + switchDisconnectHandshakeTimeout = + debugCounters.registerCounter( + prefix, "switch-disconnect-handshake-timeout", + "Number of times a switch was disconnected " + + "because it failed to complete the handshake " + + "in time.", + MetaData.ERROR); + switchDisconnectIOError = + debugCounters.registerCounter( + prefix, "switch-disconnect-io-error", + "Number of times a switch was disconnected " + + "due to IO errors on the switch connection.", + MetaData.ERROR); + switchDisconnectParseError = + debugCounters.registerCounter( + prefix, "switch-disconnect-parse-error", + "Number of times a switch was disconnected " + + "because it sent an invalid packet that could " + + "not be parsed", + MetaData.ERROR); + + switchDisconnectSwitchStateException = + debugCounters.registerCounter( + prefix, "switch-disconnect-switch-state-exception", + "Number of times a switch was disconnected " + + "because it sent messages that were invalid " + + "given the switch connection's state.", + MetaData.ERROR); + rejectedExecutionException = + debugCounters.registerCounter( + prefix, "rejected-execution-exception", + "TODO", + MetaData.ERROR); + + switchDisconnectOtherException = + debugCounters.registerCounter( + prefix, "switch-disconnect-other-exception", + "Number of times a switch was disconnected " + + "due to an exceptional situation not covered " + + "by other counters", + MetaData.ERROR); + + switchConnected = + debugCounters.registerCounter( + prefix, "switch-connected", + "Number of times a new switch connection was " + + "established"); + + unhandledMessage = + debugCounters.registerCounter( + prefix, "unhandled-message", + "Number of times an OpenFlow message was " + + "received that the controller ignored because " + + "it was inapproriate given the switch " + + "connection's state.", + MetaData.WARN); + + packetInWhileSwitchIsSlave = + debugCounters.registerCounter( + prefix, "packet-in-while-switch-is-slave", + "Number of times a packet in was received " + + "from a switch that was in SLAVE role. " + + "Possibly inidicates inconsistent roles."); + epermErrorWhileSwitchIsMaster = + debugCounters.registerCounter( + prefix, "eperm-error-while-switch-is-master", + "Number of times a permission error was " + + "received while the switch was in MASTER role. " + + "Possibly inidicates inconsistent roles.", + MetaData.WARN); + + roleNotResentBecauseRolePending = + debugCounters.registerCounter( + prefix, "role-not-resent-because-role-pending", + "The controller tried to reestablish a role " + + "with a switch but did not do so because a " + + "previous role request was still pending"); + roleReplyTimeout = + debugCounters.registerCounter( + prefix, "role-reply-timeout", + "Number of times a role request message did not " + + "receive the expected reply from a switch", + MetaData.WARN); + + roleReplyReceived = // expected RoleReply received + debugCounters.registerCounter( + prefix, "role-reply-received", + "Number of times the controller received the " + + "expected role reply message from a switch"); + + roleReplyErrorUnsupported = + debugCounters.registerCounter( + prefix, "role-reply-error-unsupported", + "Number of times the controller received an " + + "error from a switch in response to a role " + + "request indicating that the switch does not " + + "support roles."); + } + + public String getPrefix(){ + return this.prefix; + } + +} diff --git a/src/main/java/net/floodlightcontroller/core/module/ApplicationLoader.java b/src/main/java/net/floodlightcontroller/core/module/ApplicationLoader.java deleted file mode 100644 index f56dfa996e90f9550a09fb3ae817ed4685431046..0000000000000000000000000000000000000000 --- a/src/main/java/net/floodlightcontroller/core/module/ApplicationLoader.java +++ /dev/null @@ -1,179 +0,0 @@ -package net.floodlightcontroller.core.module; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; -import java.util.Properties; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.ObjectReader; - -/** - * Load an application from a configuration directory - * @author readams - */ -public class ApplicationLoader - implements IFloodlightModule, IApplicationService { - - /** - * Representation for the application configuration - * @author readams - */ - public static class Application { - private String name; - private String[] modules; - private Map<String,String> config; - - public String getName() { - return name; - } - public void setName(String name) { - this.name = name; - } - public String[] getModules() { - return modules; - } - public void setModules(String[] modules) { - this.modules = modules; - } - public Map<String, String> getConfig() { - return config; - } - public void setConfig(Map<String, String> config) { - this.config = config; - } - } - - - protected static Logger logger = - LoggerFactory.getLogger(ApplicationLoader.class); - protected static ObjectMapper mapper = new ObjectMapper(); - protected static ObjectReader reader = mapper.reader(Application.class); - - IModuleService moduleService; - - private static String APP_RESOURCE_PATH = "apps/"; - - /** - * Path containing application description files - */ - protected String applicationPath; - - /** - * Application to load - */ - protected String application; - - // ***************** - // IFloodlightModule - // ***************** - - @Override - public Collection<Class<? extends IFloodlightService>> - getModuleServices() { - Collection<Class<? extends IFloodlightService>> l = - new ArrayList<Class<? extends IFloodlightService>>(); - l.add(IApplicationService.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(IApplicationService.class, this); - return m; - } - - @Override - public Collection<Class<? extends IFloodlightService>> - getModuleDependencies() { - return null; - } - - @Override - public void init(FloodlightModuleContext context) - throws FloodlightModuleException { - moduleService = context.getServiceImpl(IModuleService.class); - - Map<String,String> config = context.getConfigParams(this); - if (config.containsKey("appsd")) - applicationPath = config.get("appsd"); - if (config.containsKey("application")) - application = config.get("application"); - } - - @Override - public void startUp(FloodlightModuleContext context) - throws FloodlightModuleException { - if (application == null) { - throw new FloodlightModuleException("No application to load"); - } - - // attempt to load from application path - File appPath; - if (applicationPath != null && - (appPath = new File(applicationPath)).exists() && - appPath.isDirectory()) { - File[] files = appPath.listFiles(); - Arrays.sort(files); - for (File f : files) { - if (f.isFile() && f.getName().matches(".*\\.json$")); - try { - if (loadApplication(new FileInputStream(f), f.getPath())) - return; - } catch (FileNotFoundException e) { - throw new FloodlightModuleException(e); - } - } - } - - // attempt to load from classpath. Note here that the file needs - // to be named after the application to be successful - try { - String r = APP_RESOURCE_PATH + application + ".json"; - InputStream is = getClass().getClassLoader().getResourceAsStream(r); - loadApplication(is, "resource: " + r); - } catch (Exception e) { - throw new FloodlightModuleException(e); - } - - } - - private boolean loadApplication(InputStream is, String path) - throws FloodlightModuleException { - Application a; - try { - a = reader.readValue(is); - } catch (Exception e) { - throw new FloodlightModuleException("Could not read application " + - path, e); - } - if (application.equals(a.getName())) { - Properties p = new Properties(); - if (a.getConfig() != null) - p.putAll(a.getConfig()); - if (a.getModules() != null) { - logger.info("Loading application {}", a.getName()); - moduleService.loadModulesFromList(Arrays.asList(a.getModules()), - p); - return true; - } - } - return false; - } -} diff --git a/src/main/java/net/floodlightcontroller/core/module/FloodlightModuleConfigFileNotFoundException.java b/src/main/java/net/floodlightcontroller/core/module/FloodlightModuleConfigFileNotFoundException.java new file mode 100644 index 0000000000000000000000000000000000000000..e20d1e06f74d572590ca63d4660c799b4278a2fd --- /dev/null +++ b/src/main/java/net/floodlightcontroller/core/module/FloodlightModuleConfigFileNotFoundException.java @@ -0,0 +1,10 @@ +package net.floodlightcontroller.core.module; + +public class FloodlightModuleConfigFileNotFoundException extends + FloodlightModuleException { + private static final long serialVersionUID = 1L; + + public FloodlightModuleConfigFileNotFoundException(String fileName) { + super("No such file or directory: " + fileName); + } +} diff --git a/src/main/java/net/floodlightcontroller/core/module/FloodlightModuleContext.java b/src/main/java/net/floodlightcontroller/core/module/FloodlightModuleContext.java index 27c1f968ac0534ecf1bfcd293ce471669016719e..9772ee4456693b7924865963f42c6f19897fa13e 100644 --- a/src/main/java/net/floodlightcontroller/core/module/FloodlightModuleContext.java +++ b/src/main/java/net/floodlightcontroller/core/module/FloodlightModuleContext.java @@ -16,7 +16,6 @@ package net.floodlightcontroller.core.module; -import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Map; @@ -28,31 +27,33 @@ import java.util.Map; public class FloodlightModuleContext implements IFloodlightModuleContext { protected Map<Class<? extends IFloodlightService>, IFloodlightService> serviceMap; protected Map<Class<? extends IFloodlightModule>, Map<String, String>> configParams; - protected Collection<IFloodlightModule> moduleSet; + private final FloodlightModuleLoader moduleLoader; /** * Creates the ModuleContext for use with this IFloodlightProvider. * This will be used as a module registry for all IFloodlightModule(s). */ - public FloodlightModuleContext() { + public FloodlightModuleContext(FloodlightModuleLoader moduleLoader) { serviceMap = new HashMap<Class<? extends IFloodlightService>, IFloodlightService>(); configParams = new HashMap<Class<? extends IFloodlightModule>, Map<String, String>>(); + this.moduleLoader = moduleLoader; } - + /** - * Adds a IFloodlightModule for this Context. - * @param clazz the service class - * @param service The IFloodlightService to add to the registry + * Allocate a module context without a module loader; useful for testing */ - public void addService(Class<? extends IFloodlightService> clazz, - IFloodlightService service) { - serviceMap.put(clazz, service); + public FloodlightModuleContext() { + this(null); } - + + // ************************ + // IFloodlightModuleContext + // ************************ + @SuppressWarnings("unchecked") @Override public <T extends IFloodlightService> T getServiceImpl(Class<T> service) { @@ -64,17 +65,6 @@ public class FloodlightModuleContext implements IFloodlightModuleContext { public Collection<Class<? extends IFloodlightService>> getAllServices() { return serviceMap.keySet(); } - - @Override - public Collection<IFloodlightModule> getAllModules() { - return moduleSet; - } - - public void addModules(Collection<IFloodlightModule> modSet) { - if (this.moduleSet == null) - this.moduleSet = new ArrayList<IFloodlightModule>(); - this.moduleSet.addAll(modSet); - } @Override public Map<String, String> getConfigParams(IFloodlightModule module) { @@ -106,7 +96,11 @@ public class FloodlightModuleContext implements IFloodlightModuleContext { return retMap; } - + + // *********************** + // FloodlightModuleContext + // *********************** + /** * Adds a configuration parameter for a module * @param mod The fully qualified module name to add the parameter to @@ -121,4 +115,22 @@ public class FloodlightModuleContext implements IFloodlightModuleContext { } moduleParams.put(key, value); } + + /** + * Get the module loader associated with this context + * @return the {@link FloodlightModuleLoader} object + */ + public FloodlightModuleLoader getModuleLoader() { + return moduleLoader; + } + + /** + * Adds a IFloodlightModule for this Context. + * @param clazz the service class + * @param service The IFloodlightService to add to the registry + */ + public void addService(Class<? extends IFloodlightService> clazz, + IFloodlightService service) { + serviceMap.put(clazz, service); + } } diff --git a/src/main/java/net/floodlightcontroller/core/module/FloodlightModuleException.java b/src/main/java/net/floodlightcontroller/core/module/FloodlightModuleException.java index 5cddd37fccb8564ae7e31b6f2418a2339ce354c7..c496911c25fa6da121c2dfcc516b0e5782c4506b 100644 --- a/src/main/java/net/floodlightcontroller/core/module/FloodlightModuleException.java +++ b/src/main/java/net/floodlightcontroller/core/module/FloodlightModuleException.java @@ -17,11 +17,11 @@ package net.floodlightcontroller.core.module; public class FloodlightModuleException extends Exception { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 1L; - public FloodlightModuleException(String error) { - super(error); - } + public FloodlightModuleException(String error) { + super(error); + } public FloodlightModuleException() { super(); diff --git a/src/main/java/net/floodlightcontroller/core/module/FloodlightModuleLoader.java b/src/main/java/net/floodlightcontroller/core/module/FloodlightModuleLoader.java index 32e4fc031e26eafee38bd282e0a1b21e0b73ebfb..ded7b6d34b0a952de765d4713a83cc13ddd81ff2 100644 --- a/src/main/java/net/floodlightcontroller/core/module/FloodlightModuleLoader.java +++ b/src/main/java/net/floodlightcontroller/core/module/FloodlightModuleLoader.java @@ -18,142 +18,185 @@ package net.floodlightcontroller.core.module; import java.io.File; import java.io.FileInputStream; +import java.io.IOException; import java.io.InputStream; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.URI; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; -import java.util.LinkedList; +import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; -import java.util.Queue; import java.util.ServiceConfigurationError; import java.util.ServiceLoader; import java.util.Set; import net.floodlightcontroller.core.annotations.LogMessageDoc; import net.floodlightcontroller.core.annotations.LogMessageDocs; - +import net.floodlightcontroller.core.module.FloodlightModulePriority.Priority; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; + /** * Finds all Floodlight modules in the class path and loads/starts them. * @author alexreimers * */ -public class FloodlightModuleLoader implements IModuleService { - protected static Logger logger = +public class FloodlightModuleLoader { + protected static final Logger logger = LoggerFactory.getLogger(FloodlightModuleLoader.class); - protected static Map<Class<? extends IFloodlightService>, + private Map<Class<? extends IFloodlightService>, Collection<IFloodlightModule>> serviceMap; - protected static Map<IFloodlightModule, - Collection<Class<? extends + private Map<IFloodlightModule, + Collection<Class<? extends IFloodlightService>>> moduleServiceMap; - protected static Map<String, IFloodlightModule> moduleNameMap; - protected static Object lock = new Object(); - - protected static Set<String> initedSet = new HashSet<String>(); - protected static Set<String> startedSet = new HashSet<String>();; - - protected FloodlightModuleContext floodlightModuleContext; - - public static final String COMPILED_CONF_FILE = + private Map<String, IFloodlightModule> moduleNameMap; + private List<IFloodlightModule> loadedModuleList; + + private final FloodlightModuleContext floodlightModuleContext; + + protected boolean startupModules; + + private static URI configFile; + + public static final String COMPILED_CONF_FILE = "floodlightdefault.properties"; public static final String FLOODLIGHT_MODULES_KEY = "floodlight.modules"; - public static final String FLOODLIGHT_CONFD = - "floodlight.confd"; public FloodlightModuleLoader() { - floodlightModuleContext = new FloodlightModuleContext(); - floodlightModuleContext.addService(IModuleService.class, this); + loadedModuleList = Collections.emptyList(); + floodlightModuleContext = new FloodlightModuleContext(this); + startupModules = true; + } + + /** + * Gets the map of modules and their names. + * @return An UNMODIFIABLE map of the modules and their names. + */ + public synchronized Map<String, IFloodlightModule> getModuleNameMap() { + if(moduleNameMap == null) + return ImmutableMap.of(); + else + return Collections.unmodifiableMap(moduleNameMap); + } + + /** + * Gets the list of modules in the order that they will be/were initialized + * @return An UNMODIFIABLE list of loaded modules, or null if + * not initialized. + */ + public List<IFloodlightModule> getModuleList() { + if (loadedModuleList == null) + return Collections.emptyList(); + else + return Collections.unmodifiableList(loadedModuleList); + } + + /** + * Return the location of the config file that was used to initialize + * floodlight. If no config file was specified (i.e. floodlight was + * configured from the default resource), then the return value is null. + * @return location of the config file or null if no config file was + * specified + */ + public static URI getConfigFileURI() { + return configFile; } - + /** * Finds all IFloodlightModule(s) in the classpath. It creates 3 Maps. * serviceMap -> Maps a service to a module * moduleServiceMap -> Maps a module to all the services it provides * moduleNameMap -> Maps the string name to the module - * @throws FloodlightModuleException If two modules are specified in the configuration - * that provide the same service. + * @throws FloodlightModuleException If two modules are specified in the + * configuration that provide the same service. */ - protected static void findAllModules(Collection<String> mList) throws FloodlightModuleException { - synchronized (lock) { - if (serviceMap != null) return; - serviceMap = - new HashMap<Class<? extends IFloodlightService>, - Collection<IFloodlightModule>>(); - moduleServiceMap = - new HashMap<IFloodlightModule, - Collection<Class<? extends - IFloodlightService>>>(); - moduleNameMap = new HashMap<String, IFloodlightModule>(); - - // Get all the current modules in the classpath - ClassLoader cl = Thread.currentThread().getContextClassLoader(); - ServiceLoader<IFloodlightModule> moduleLoader - = ServiceLoader.load(IFloodlightModule.class, cl); - // Iterate for each module, iterate through and add it's services - Iterator<IFloodlightModule> moduleIter = moduleLoader.iterator(); - while (moduleIter.hasNext()) { - IFloodlightModule m = null; - try { - m = moduleIter.next(); - } catch (ServiceConfigurationError sce) { - logger.error("Could not find module: {}", sce.getMessage()); - continue; - } - //} - //for (IFloodlightModule m : moduleLoader) { - if (logger.isDebugEnabled()) { - logger.debug("Found module " + m.getClass().getName()); - } + protected synchronized void findAllModules(Collection<String> mList) + throws FloodlightModuleException { + if (serviceMap != null) + return; - // Set up moduleNameMap - moduleNameMap.put(m.getClass().getCanonicalName(), m); - - // Set up serviceMap - Collection<Class<? extends IFloodlightService>> servs = - m.getModuleServices(); - if (servs != null) { - moduleServiceMap.put(m, servs); - for (Class<? extends IFloodlightService> s : servs) { - Collection<IFloodlightModule> mods = - serviceMap.get(s); - if (mods == null) { - mods = new ArrayList<IFloodlightModule>(); - serviceMap.put(s, mods); - } - mods.add(m); - // Make sure they haven't specified duplicate modules in the config - int dupInConf = 0; - for (IFloodlightModule cMod : mods) { - if (mList.contains(cMod.getClass().getCanonicalName())) - dupInConf += 1; - } - - if (dupInConf > 1) { - String duplicateMods = ""; - for (IFloodlightModule mod : mods) { - duplicateMods += mod.getClass().getCanonicalName() + ", "; - } - throw new FloodlightModuleException("ERROR! The configuraiton" + - " file specifies more than one module that provides the service " + - s.getCanonicalName() +". Please specify only ONE of the " + - "following modules in the config file: " + duplicateMods); + serviceMap = new HashMap<>(); + moduleServiceMap = new HashMap<>(); + moduleNameMap = new HashMap<>(); + + // Get all the current modules in the classpath + ClassLoader cl = Thread.currentThread().getContextClassLoader(); + ServiceLoader<IFloodlightModule> moduleLoader = + ServiceLoader.load(IFloodlightModule.class, cl); + // Iterate for each module, iterate through and add it's services + Iterator<IFloodlightModule> moduleIter = moduleLoader.iterator(); + while (moduleIter.hasNext()) { + IFloodlightModule m = null; + try { + m = moduleIter.next(); + } catch (ServiceConfigurationError sce) { + logger.error("Could not find module: {}", sce.getMessage()); + continue; + } + if (logger.isDebugEnabled()) { + logger.debug("Found module " + m.getClass().getName()); + } + + // Set up moduleNameMap + moduleNameMap.put(m.getClass().getCanonicalName(), m); + + // Set up serviceMap + Collection<Class<? extends IFloodlightService>> servs = + m.getModuleServices(); + if (servs != null) { + moduleServiceMap.put(m, servs); + for (Class<? extends IFloodlightService> s : servs) { + Collection<IFloodlightModule> mods = + serviceMap.get(s); + if (mods == null) { + mods = new ArrayList<IFloodlightModule>(); + serviceMap.put(s, mods); + } + mods.add(m); + // Make sure they haven't specified duplicate modules in + // the config + int dupInConf = 0; + for (IFloodlightModule cMod : mods) { + if (mList.contains(cMod.getClass().getCanonicalName())) + dupInConf += 1; + } + + if (dupInConf > 1) { + StringBuilder sb = new StringBuilder(); + for (IFloodlightModule mod : mods) { + sb.append(mod.getClass().getCanonicalName()); + sb.append(", "); } + String duplicateMods = sb.toString(); + String mess = "ERROR! The configuration file " + + "specifies more than one module that " + + "provides the service " + + s.getCanonicalName() + + ". Please specify only ONE of the " + + "following modules in the config file: " + + duplicateMods; + throw new FloodlightModuleException(mess); } } } } } - + /** * Loads the modules from a specified configuration file. * @param fName The configuration file path @@ -162,9 +205,10 @@ public class FloodlightModuleLoader implements IModuleService { */ @LogMessageDocs({ @LogMessageDoc(level="INFO", - message="Loading modules from file {file name}", + message="Loading modules from {file name}", explanation="The controller is initializing its module " + - "configuration from the specified properties file"), + "configuration from the specified properties " + + "file or directory"), @LogMessageDoc(level="INFO", message="Loading default modules", explanation="The controller is initializing its module " + @@ -181,206 +225,217 @@ public class FloodlightModuleLoader implements IModuleService { "module configuration", recommendation=LogMessageDoc.CHECK_CONTROLLER) }) - public IFloodlightModuleContext loadModulesFromConfig(String fName) + public IFloodlightModuleContext loadModulesFromConfig(String fName) throws FloodlightModuleException { Properties prop = new Properties(); - Collection<String> configMods; - - File f = new File(fName); - if (f.isFile()) { - logger.info("Loading modules from file {}", f.getPath()); - configMods = loadProperties(null, f, prop); - } else { + Collection<String> configMods = new ArrayList<>(); + + if (fName == null) { logger.info("Loading default modules"); InputStream is = this.getClass().getClassLoader(). getResourceAsStream(COMPILED_CONF_FILE); - configMods = loadProperties(is, null, prop); - } + mergeProperties(is, null, configMods, prop); + } else { + File confFile = new File(fName); + if (! confFile.exists()) + throw new FloodlightModuleConfigFileNotFoundException(fName); + logger.info("Loading modules from {}", confFile.getPath()); + if (confFile.isFile()) { + mergeProperties(null, confFile, + configMods, prop); + } else { + File[] files = confFile.listFiles(); + Arrays.sort(files); + for (File f : files) { + logger.debug("Loading conf.d file {}", f.getPath()); + if (f.isFile() && + f.getName().matches(".*\\.properties$")) { + mergeProperties(null, f, configMods, prop); + } + } + } + } return loadModulesFromList(configMods, prop); } - - private Collection<String> loadProperties(InputStream is, - File confFile, - Properties prop) { + + private void mergeProperties(InputStream is, + File confFile, + Collection<String> configMods, + Properties prop) + throws FloodlightModuleException { try { Properties fprop = new Properties(); if (is != null) { fprop.load(is); } else { - fprop.load(new FileInputStream(confFile)); - } - prop.putAll(fprop); - } catch (Exception e) { - logger.error("Could not load module configuration file", e); - System.exit(1); - } - - Collection<String> configMods = new ArrayList<String>(); - String moduleList = prop.getProperty(FLOODLIGHT_MODULES_KEY); - if (moduleList != null) { - moduleList = moduleList.replaceAll("\\s", ""); - configMods.addAll(Arrays.asList(moduleList.split(","))); - prop.remove(FLOODLIGHT_MODULES_KEY); - } - - String confdStr = prop.getProperty(FLOODLIGHT_CONFD); - prop.remove(FLOODLIGHT_CONFD); - if (confdStr != null) { - File confd = new File(confdStr); - if (confd.exists() && confd.isDirectory()) { - File[] files = confd.listFiles(); - Arrays.sort(files); - for (File f : files) { - if (f.isFile() && - f.getName().matches(".*\\.properties$")) - configMods.addAll(loadProperties(null, f, prop)); + try (FileInputStream fis = new FileInputStream(confFile)) { + fprop.load(fis); } } - } + String moduleList = fprop.getProperty(FLOODLIGHT_MODULES_KEY); + if (moduleList != null) { + moduleList = moduleList.replaceAll("\\s", ""); + configMods.addAll(Arrays.asList(moduleList.split(","))); + } + fprop.remove(FLOODLIGHT_MODULES_KEY); - return configMods; + prop.putAll(fprop); + } catch (IOException e) { + throw new FloodlightModuleException(e); + } } - + /** * Loads modules (and their dependencies) specified in the list - * @param mList The array of fully qualified module names - * @param ignoreList The list of Floodlight services NOT to - * load modules for. Used for unit testing. + * @param configMods The fully-qualified module names * @return The ModuleContext containing all the loaded modules * @throws FloodlightModuleException */ - protected IFloodlightModuleContext loadModulesFromList(Collection<String> configMods, Properties prop, - Collection<IFloodlightService> ignoreList) throws FloodlightModuleException { + public synchronized IFloodlightModuleContext + loadModulesFromList(Collection<String> configMods, + Properties prop) + throws FloodlightModuleException { logger.debug("Starting module loader"); - if (logger.isDebugEnabled() && ignoreList != null) - logger.debug("Not loading module services " + ignoreList.toString()); findAllModules(configMods); - - Collection<IFloodlightModule> moduleSet = new ArrayList<IFloodlightModule>(); + + ArrayList<IFloodlightModule> moduleList = new ArrayList<>(); Map<Class<? extends IFloodlightService>, IFloodlightModule> moduleMap = - new HashMap<Class<? extends IFloodlightService>, - IFloodlightModule>(); - - Queue<String> moduleQ = new LinkedList<String>(); - // Add the explicitly configured modules to the q - moduleQ.addAll(configMods); - Set<String> modsVisited = new HashSet<String>(); - - while (!moduleQ.isEmpty()) { - String moduleName = moduleQ.remove(); - if (modsVisited.contains(moduleName)) - continue; - modsVisited.add(moduleName); - IFloodlightModule module = moduleNameMap.get(moduleName); - if (module == null) { - throw new FloodlightModuleException("Module " + - moduleName + " not found"); - } - // If the module provides a service that is in the - // services ignorelist don't load it. - if ((ignoreList != null) && (module.getModuleServices() != null)) { - for (IFloodlightService ifs : ignoreList) { - for (Class<?> intsIgnore : ifs.getClass().getInterfaces()) { - //System.out.println(intsIgnore.getName()); - // Check that the interface extends IFloodlightService - //if (intsIgnore.isAssignableFrom(IFloodlightService.class)) { - //System.out.println(module.getClass().getName()); - if (intsIgnore.isAssignableFrom(module.getClass())) { - // We now ignore loading this module. - logger.debug("Not loading module " + - module.getClass().getCanonicalName() + - " because interface " + - intsIgnore.getCanonicalName() + - " is in the ignore list."); - - continue; + new HashMap<>(); + HashSet<String> modsVisited = new HashSet<>(); + + ArrayDeque<String> modsToLoad = new ArrayDeque<>(configMods); + while (!modsToLoad.isEmpty()) { + String moduleName = modsToLoad.removeFirst(); + traverseDeps(moduleName, modsToLoad, + moduleList, moduleMap, modsVisited); + } + + parseConfigParameters(prop); + + loadedModuleList = moduleList; + + initModules(moduleList); + if(startupModules) + startupModules(moduleList); + + return floodlightModuleContext; + } + + private void traverseDeps(String moduleName, + Collection<String> modsToLoad, + ArrayList<IFloodlightModule> moduleList, + Map<Class<? extends IFloodlightService>, + IFloodlightModule> moduleMap, + Set<String> modsVisited) + throws FloodlightModuleException { + if (modsVisited.contains(moduleName)) return; + modsVisited.add(moduleName); + IFloodlightModule module = moduleNameMap.get(moduleName); + if (module == null) { + throw new FloodlightModuleException("Module " + + moduleName + " not found"); + } + + // Add its dependencies to the stack + Collection<Class<? extends IFloodlightService>> deps = + module.getModuleDependencies(); + if (deps != null) { + for (Class<? extends IFloodlightService> c : deps) { + IFloodlightModule m = moduleMap.get(c); + if (m == null) { + Collection<IFloodlightModule> mods = serviceMap.get(c); + // Make sure only one module is loaded + if ((mods == null) || (mods.size() == 0)) { + throw new FloodlightModuleException("ERROR! Could not " + + "find an IFloodlightModule that provides service " + + c.toString()); + } else if (mods.size() == 1) { + IFloodlightModule mod = mods.iterator().next(); + traverseDeps(mod.getClass().getCanonicalName(), + modsToLoad, moduleList, + moduleMap, modsVisited); + } else { + boolean found = false; + for (IFloodlightModule moduleDep : mods) { + String d = moduleDep.getClass().getCanonicalName(); + if (modsToLoad.contains(d)) { + modsToLoad.remove(d); + traverseDeps(d, + modsToLoad, moduleList, + moduleMap, modsVisited); + found = true; + break; + } } - //} - } - } - } - - // Add the module to be loaded - addModule(moduleMap, moduleSet, module); - // Add it's dep's to the queue - Collection<Class<? extends IFloodlightService>> deps = - module.getModuleDependencies(); - if (deps != null) { - for (Class<? extends IFloodlightService> c : deps) { - IFloodlightModule m = moduleMap.get(c); - if (m == null) { - Collection<IFloodlightModule> mods = serviceMap.get(c); - // Make sure only one module is loaded - if ((mods == null) || (mods.size() == 0)) { - throw new FloodlightModuleException("ERROR! Could not " + - "find an IFloodlightModule that provides service " + - c.toString()); - } else if (mods.size() == 1) { - IFloodlightModule mod = mods.iterator().next(); - if (!modsVisited.contains(mod.getClass().getCanonicalName())) - moduleQ.add(mod.getClass().getCanonicalName()); - } else { - boolean found = false; + if (!found) { + Priority maxp = Priority.MINIMUM; + ArrayList<IFloodlightModule> curMax = new ArrayList<>(); for (IFloodlightModule moduleDep : mods) { - if (configMods.contains(moduleDep.getClass().getCanonicalName())) { - // Module will be loaded, we can continue - found = true; - break; + FloodlightModulePriority fmp = + moduleDep.getClass(). + getAnnotation(FloodlightModulePriority.class); + Priority curp = Priority.NORMAL; + if (fmp != null) { + curp = fmp.value(); + } + if (curp.value() > maxp.value()) { + curMax.clear(); + curMax.add(moduleDep); + maxp = curp; + } else if (curp.value() == maxp.value()) { + curMax.add(moduleDep); } } - if (!found) { - String duplicateMods = ""; - for (IFloodlightModule mod : mods) { - duplicateMods += mod.getClass().getCanonicalName() + ", "; + + if (curMax.size() == 1) { + traverseDeps(curMax.get(0). + getClass().getCanonicalName(), + modsToLoad, moduleList, + moduleMap, modsVisited); + } else { + StringBuilder sb = new StringBuilder(); + for (IFloodlightModule mod : curMax) { + sb.append(mod.getClass().getCanonicalName()); + sb.append(", "); } - throw new FloodlightModuleException("ERROR! Found more " + - "than one (" + mods.size() + ") IFloodlightModules that provides " + - "service " + c.toString() + - ". This service is required for " + moduleName + - ". Please specify one of the following modules in the config: " + - duplicateMods); + String duplicateMods = sb.toString(); + + throw new FloodlightModuleException("ERROR! Found more " + + "than one (" + mods.size() + ") IFloodlightModules that provides " + + "service " + c.toString() + + ". This service is required for " + moduleName + + ". Please specify one of the following modules in the config: " + + duplicateMods); } } } } } } - - floodlightModuleContext.addModules(moduleSet); - parseConfigParameters(prop); - initModules(moduleSet); - startupModules(moduleSet); - - return floodlightModuleContext; - } - - @Override - public IFloodlightModuleContext loadModulesFromList(Collection<String> configMods, Properties prop) - throws FloodlightModuleException { - return loadModulesFromList(configMods, prop, null); + + // Add the module to be loaded + addModule(moduleMap, moduleList, module); } - + /** * Add a module to the set of modules to load and register its services * @param moduleMap the module map - * @param moduleSet the module set + * @param moduleList the module set * @param module the module to add */ - protected void addModule(Map<Class<? extends IFloodlightService>, + protected void addModule(Map<Class<? extends IFloodlightService>, IFloodlightModule> moduleMap, - Collection<IFloodlightModule> moduleSet, + Collection<IFloodlightModule> moduleList, IFloodlightModule module) { - if (!moduleSet.contains(module)) { - Collection<Class<? extends IFloodlightService>> servs = - moduleServiceMap.get(module); - if (servs != null) { - for (Class<? extends IFloodlightService> c : servs) - moduleMap.put(c, module); - } - moduleSet.add(module); + Collection<Class<? extends IFloodlightService>> servs = + moduleServiceMap.get(module); + if (servs != null) { + for (Class<? extends IFloodlightService> c : servs) + moduleMap.put(c, module); } + moduleList.add(module); } /** @@ -388,23 +443,20 @@ public class FloodlightModuleLoader implements IModuleService { * @param moduleSet The set of modules to call their init function on * @throws FloodlightModuleException If a module can not properly be loaded */ - protected void initModules(Collection<IFloodlightModule> moduleSet) + protected void initModules(Collection<IFloodlightModule> moduleSet) throws FloodlightModuleException { for (IFloodlightModule module : moduleSet) { - if (initedSet.contains(module.getClass().getCanonicalName())) - continue; - // Get the module's service instance(s) - Map<Class<? extends IFloodlightService>, + Map<Class<? extends IFloodlightService>, IFloodlightService> simpls = module.getServiceImpls(); // add its services to the context if (simpls != null) { - for (Entry<Class<? extends IFloodlightService>, + for (Entry<Class<? extends IFloodlightService>, IFloodlightService> s : simpls.entrySet()) { if (logger.isDebugEnabled()) { - logger.debug("Setting " + s.getValue() + - " as provider for " + + logger.debug("Setting " + s.getValue() + + " as provider for " + s.getKey().getCanonicalName()); } if (floodlightModuleContext.getServiceImpl(s.getKey()) == null) { @@ -422,40 +474,86 @@ public class FloodlightModuleLoader implements IModuleService { } } } - + for (IFloodlightModule module : moduleSet) { - if (initedSet.contains(module.getClass().getCanonicalName())) - continue; - initedSet.add(module.getClass().getCanonicalName()); - // init the module if (logger.isDebugEnabled()) { - logger.debug("Initializing " + + logger.debug("Initializing " + module.getClass().getCanonicalName()); } module.init(floodlightModuleContext); } } - + /** * Call each loaded module's startup method * @param moduleSet the module set to start up - * @throws FloodlightModuleException + * @throws FloodlightModuleException */ - protected void startupModules(Collection<IFloodlightModule> moduleSet) + protected void startupModules(Collection<IFloodlightModule> moduleSet) throws FloodlightModuleException { for (IFloodlightModule m : moduleSet) { - if (startedSet.contains(m.getClass().getCanonicalName())) - continue; - startedSet.add(m.getClass().getCanonicalName()); - if (logger.isDebugEnabled()) { logger.debug("Starting " + m.getClass().getCanonicalName()); } m.startUp(floodlightModuleContext); } } - + + /** Tuple of floodlight module and run method */ + private static class RunMethod { + private final IFloodlightModule module; + private final Method method; + public RunMethod(IFloodlightModule module, Method method) { + this.module = module; + this.method = method; + } + + public void run() throws FloodlightModuleException { + try { + if (logger.isDebugEnabled()) { + logger.debug("Running {}", this); + } + method.invoke(module); + } catch (IllegalAccessException | IllegalArgumentException + | InvocationTargetException e) { + throw new FloodlightModuleException("Failed to invoke " + + "module Run method " + this, e); + } + } + + @Override + public String toString() { + return module.getClass().getCanonicalName() + "." + method; + } + + + } + + public void runModules() throws FloodlightModuleException { + List<RunMethod> mainLoopMethods = Lists.newArrayList(); + + for (IFloodlightModule m : getModuleList()) { + for (Method method : m.getClass().getDeclaredMethods()) { + Run runAnnotation = method.getAnnotation(Run.class); + if (runAnnotation != null) { + RunMethod runMethod = new RunMethod(m, method); + if(runAnnotation.mainLoop()) { + mainLoopMethods.add(runMethod); + } else { + runMethod.run(); + } + } + } + } + if(mainLoopMethods.size() == 1) { + mainLoopMethods.get(0).run(); + } else if (mainLoopMethods.size() > 1) { + throw new FloodlightModuleException("Invalid module configuration -- " + + "multiple run methods annotated with mainLoop detected: " + mainLoopMethods); + } + } + /** * Parses configuration parameters for each module * @param prop The properties file to use @@ -467,15 +565,11 @@ public class FloodlightModuleLoader implements IModuleService { "module that is not loaded.") protected void parseConfigParameters(Properties prop) { if (prop == null) return; - + Enumeration<?> e = prop.propertyNames(); while (e.hasMoreElements()) { String key = (String) e.nextElement(); - // Ignore module list key - if (key.equals(FLOODLIGHT_MODULES_KEY)) { - continue; - } - + String configValue = null; int lastPeriod = key.lastIndexOf("."); String moduleName = key.substring(0, lastPeriod); @@ -487,15 +581,25 @@ public class FloodlightModuleLoader implements IModuleService { } else { configValue = prop.getProperty(key); } - + IFloodlightModule mod = moduleNameMap.get(moduleName); if (mod == null) { - logger.warn("Module {} not found or loaded. " + - "Not adding configuration option {} = {}", + logger.debug("Module {} not found or loaded. " + + "Not adding configuration option {} = {}", new Object[]{moduleName, configKey, configValue}); } else { + logger.debug("Adding configuration option {} = {} for module {}", + new Object[]{configKey, configValue, moduleName}); floodlightModuleContext.addConfigParam(mod, configKey, configValue); } } } + + public boolean isStartupModules() { + return startupModules; + } + + public void setStartupModules(boolean startupModules) { + this.startupModules = startupModules; + } } diff --git a/src/main/java/net/floodlightcontroller/core/module/FloodlightModulePriority.java b/src/main/java/net/floodlightcontroller/core/module/FloodlightModulePriority.java new file mode 100644 index 0000000000000000000000000000000000000000..d2e561fbf435049f16fe80c885941c3e50deae40 --- /dev/null +++ b/src/main/java/net/floodlightcontroller/core/module/FloodlightModulePriority.java @@ -0,0 +1,58 @@ +/** + * 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.core.module; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Set a module priority value so if there are multiple modules that provide + * a given service, and there is a unique module with maximum priority, + * we will use that module in preference to lower-priority modules rather + * than requiring users to manually specify a module to load. This makes it + * possible to define a default provider that uses the DEFAULT_PROVIDER priority + * while the normal modules omit the annotation or specify NORMAL priority. + * @author readams + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface FloodlightModulePriority { + public enum Priority { + MINIMUM(0), + TEST(10), + EXTRA_LOW(20), + LOW(30), + NORMAL(40), + DEFAULT_PROVIDER(50), + HIGH(60), + EXTRA_HIGH(70); + + private final int value; + + private Priority(int value) { + this.value = value; + } + + public int value() { + return value; + } + } + + public Priority value() default Priority.NORMAL; +} diff --git a/src/main/java/net/floodlightcontroller/core/module/FloodlightServices.java b/src/main/java/net/floodlightcontroller/core/module/FloodlightServices.java new file mode 100644 index 0000000000000000000000000000000000000000..c5d24b72313ee1234ca5beea1a7c9cafef229f38 --- /dev/null +++ b/src/main/java/net/floodlightcontroller/core/module/FloodlightServices.java @@ -0,0 +1,5 @@ +package net.floodlightcontroller.core.module; + +public @interface FloodlightServices { + +} diff --git a/src/main/java/net/floodlightcontroller/core/module/IFloodlightModule.java b/src/main/java/net/floodlightcontroller/core/module/IFloodlightModule.java index 982e4be26a7e073dd700599a96a4393697088ed3..8387e5d092c0a95f59426b493b20a982cba07716 100644 --- a/src/main/java/net/floodlightcontroller/core/module/IFloodlightModule.java +++ b/src/main/java/net/floodlightcontroller/core/module/IFloodlightModule.java @@ -19,7 +19,6 @@ package net.floodlightcontroller.core.module; import java.util.Collection; import java.util.Map; - /** * Defines an interface for loadable Floodlight modules. * @@ -27,7 +26,7 @@ import java.util.Map; * <ol> * <li> getServices() : what services does this module provide * <li> getDependencies() : list the dependencies - * <li> init() : internal initializations (don't touch other modules) + * <li> init() : internal initializations (<em>don't</em> touch other modules) * <li> startUp() : external initializations (<em>do</em> touch other modules) * </ol> * @@ -50,8 +49,7 @@ public interface IFloodlightModule { * same object or different objects for different exported services. * @return The map from service interface class to service implementation */ - public Map<Class<? extends IFloodlightService>, - IFloodlightService> getServiceImpls(); + public Map<Class<? extends IFloodlightService>, IFloodlightService> getServiceImpls(); /** * Get a list of Modules that this module depends on. The module system @@ -87,6 +85,5 @@ public interface IFloodlightModule { * @throws FloodlightModuleException */ - void startUp(FloodlightModuleContext context) - throws FloodlightModuleException; + void startUp(FloodlightModuleContext context) throws FloodlightModuleException; } diff --git a/src/main/java/net/floodlightcontroller/core/module/IFloodlightModuleContext.java b/src/main/java/net/floodlightcontroller/core/module/IFloodlightModuleContext.java index 46dea092c2eaa20b180b2176ddd3babbb2d24b26..d6780f6cf476a27c71786540b1f1bdfd830f4d36 100644 --- a/src/main/java/net/floodlightcontroller/core/module/IFloodlightModuleContext.java +++ b/src/main/java/net/floodlightcontroller/core/module/IFloodlightModuleContext.java @@ -19,8 +19,8 @@ package net.floodlightcontroller.core.module; import java.util.Collection; import java.util.Map; - -public interface IFloodlightModuleContext { + +public interface IFloodlightModuleContext { /** * Retrieves a casted version of a module from the registry. * @param name The IFloodlightService object type @@ -36,12 +36,6 @@ public interface IFloodlightModuleContext { */ public Collection<Class<? extends IFloodlightService>> getAllServices(); - /** - * Returns all loaded modules - * @return All Floodlight modules that are going to be loaded - */ - public Collection<IFloodlightModule> getAllModules(); - /** * Gets module specific configuration parameters. * @param module The module to get the configuration parameters for diff --git a/src/main/java/net/floodlightcontroller/core/module/IModuleService.java b/src/main/java/net/floodlightcontroller/core/module/IModuleService.java deleted file mode 100644 index 440ad2de6240709ef9e8a068e4b557f6b1444d82..0000000000000000000000000000000000000000 --- a/src/main/java/net/floodlightcontroller/core/module/IModuleService.java +++ /dev/null @@ -1,18 +0,0 @@ -package net.floodlightcontroller.core.module; - -import java.util.Collection; -import java.util.Properties; - -public interface IModuleService extends IFloodlightService { - /** - * Loads modules (and their dependencies) specified in the list. - * @param configMods The collection of fully qualified module names to load. - * @param prop The list of properties that are configuration options. - * @return The ModuleContext containing all the loaded modules. - * @throws FloodlightModuleException - */ - public IFloodlightModuleContext - loadModulesFromList(Collection<String> configMods, - Properties prop) throws FloodlightModuleException; - -} diff --git a/src/main/java/net/floodlightcontroller/core/module/ModuleLoaderResource.java b/src/main/java/net/floodlightcontroller/core/module/ModuleLoaderResource.java index b4406117effee39794485dcedac4885fbea0a3ad..bc1b74cd15a5671fd87dfc3d6646c85e80786308 100644 --- a/src/main/java/net/floodlightcontroller/core/module/ModuleLoaderResource.java +++ b/src/main/java/net/floodlightcontroller/core/module/ModuleLoaderResource.java @@ -22,7 +22,6 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; - import org.restlet.resource.Get; import org.restlet.resource.ServerResource; import org.slf4j.Logger; @@ -53,6 +52,9 @@ public class ModuleLoaderResource extends ServerResource { */ public Map<String, Object> retrieveInternal(boolean loadedOnly) { Map<String, Object> model = new HashMap<String, Object>(); + FloodlightModuleLoader floodlightModuleLoader = + (FloodlightModuleLoader) getContext().getAttributes(). + get(FloodlightModuleLoader.class.getCanonicalName()); Set<String> loadedModules = new HashSet<String>(); for (Object val : getContext().getAttributes().values()) { @@ -63,13 +65,11 @@ public class ModuleLoaderResource extends ServerResource { } } - for (String moduleName : - FloodlightModuleLoader.moduleNameMap.keySet() ) { + for (String moduleName : floodlightModuleLoader.getModuleNameMap().keySet() ) { Map<String,Object> moduleInfo = new HashMap<String, Object>(); IFloodlightModule module = - FloodlightModuleLoader.moduleNameMap.get( - moduleName); + floodlightModuleLoader.getModuleNameMap().get(moduleName); Collection<Class<? extends IFloodlightService>> deps = module.getModuleDependencies(); @@ -117,4 +117,4 @@ public class ModuleLoaderResource extends ServerResource { } return model; } -} +} \ No newline at end of file diff --git a/src/main/java/net/floodlightcontroller/core/module/Run.java b/src/main/java/net/floodlightcontroller/core/module/Run.java new file mode 100644 index 0000000000000000000000000000000000000000..4bf8a2ed41d270282b5dbda582a8ac7c32ec448f --- /dev/null +++ b/src/main/java/net/floodlightcontroller/core/module/Run.java @@ -0,0 +1,20 @@ +package net.floodlightcontroller.core.module; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Indicate the run() method of a floodlight module + * @author readams + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Run { + /** declares this run method as the application main method. Will be called last and is not expected to + * return. It is a configuration error to have more than one module declaring a main method. + * @return + */ + boolean mainLoop() default false; +} diff --git a/src/main/java/net/floodlightcontroller/core/rest/AggregateStatistics.java b/src/main/java/net/floodlightcontroller/core/rest/AggregateStatistics.java new file mode 100644 index 0000000000000000000000000000000000000000..3d0fe680e544ffa5a7701ad42f0037fe8de9037c --- /dev/null +++ b/src/main/java/net/floodlightcontroller/core/rest/AggregateStatistics.java @@ -0,0 +1,39 @@ +package net.floodlightcontroller.core.rest; + +import org.projectfloodlight.openflow.protocol.OFAggregateStatsReply; +import org.projectfloodlight.openflow.types.U64; + +public class AggregateStatistics { + + private final U64 packetCount; + private final U64 byteCount; + private final long flowCount; + + private AggregateStatistics(U64 packetCount, U64 byteCount, long flowCount) { + this.packetCount = packetCount; + this.byteCount = byteCount; + this.flowCount = flowCount; + } + + public static AggregateStatistics of(U64 packetCount, U64 byteCount, + long flowCount) { + return new AggregateStatistics(packetCount, byteCount, flowCount); + } + + public static AggregateStatistics of(OFAggregateStatsReply statsReply) { + return new AggregateStatistics(statsReply.getPacketCount(), + statsReply.getByteCount(), statsReply.getFlowCount()); + } + + public U64 getPacketCount() { + return packetCount; + } + + public U64 getByteCount() { + return byteCount; + } + + public long getFlowCount() { + return flowCount; + } +} diff --git a/src/main/java/net/floodlightcontroller/core/rest/SwitchRepresentation.java b/src/main/java/net/floodlightcontroller/core/rest/SwitchRepresentation.java new file mode 100644 index 0000000000000000000000000000000000000000..03a46f462b68300c61639052b03a7931bfcc4861 --- /dev/null +++ b/src/main/java/net/floodlightcontroller/core/rest/SwitchRepresentation.java @@ -0,0 +1,116 @@ +package net.floodlightcontroller.core.rest; + +import java.net.SocketAddress; +import java.util.Collection; +import java.util.Date; +import java.util.Map; +import java.util.Set; + +import javax.annotation.Nonnull; + +import net.floodlightcontroller.core.IOFConnection; +import net.floodlightcontroller.core.IOFSwitch; +import net.floodlightcontroller.core.internal.OFSwitchHandshakeHandler; +import org.projectfloodlight.openflow.protocol.OFCapabilities; +import org.projectfloodlight.openflow.protocol.OFPortDesc; +import org.projectfloodlight.openflow.types.DatapathId; + +import com.google.common.base.Preconditions; + +/** + * REST representation of an OF Switch. Stitches together data from different + * areas of the platform to provide a complete, centralized representation. + * @author Jason Parraga <Jason.Parraga@bigswitch.com> + * + */ +public class SwitchRepresentation { + + private final long buffers; + private final Set<OFCapabilities> capabilities; + private final Short tables; + private final SocketAddress inetAddress; + private final Collection<OFPortDesc> sortedPorts; + private final boolean isConnected; + private final Date connectedSince; + private final DatapathId dpid; + private final Map<Object, Object> attributes; + private final boolean isActive; + + private final Collection<IOFConnection> connections; + private final String handshakeState; + private final String quarantineReason; + + public SwitchRepresentation(@Nonnull IOFSwitch sw, @Nonnull OFSwitchHandshakeHandler handshakeHandler) { + Preconditions.checkNotNull(sw, "switch must not be null"); + Preconditions.checkNotNull(handshakeHandler, "handshakeHandler must not be null"); + + // IOFSwitch + this.buffers = sw.getBuffers(); + this.capabilities = sw.getCapabilities(); + this.tables = sw.getTables(); + this.inetAddress = sw.getInetAddress(); + this.sortedPorts = sw.getSortedPorts(); + this.isConnected = sw.isConnected(); + this.connectedSince = sw.getConnectedSince(); + this.dpid = sw.getId(); + this.attributes = sw.getAttributes(); + this.isActive = sw.isActive(); + + // OFSwitchHandshakeHandler + this.connections = handshakeHandler.getConnections(); + this.handshakeState = handshakeHandler.getState(); + this.quarantineReason = handshakeHandler.getQuarantineReason(); + } + + public long getBuffers() { + return this.buffers; + } + + public Short getTables() { + return this.tables; + } + + public Set<OFCapabilities> getCapabilities() { + return this.capabilities; + } + + public SocketAddress getInetAddress() { + return this.inetAddress; + } + + public Collection<OFPortDesc> getSortedPorts() { + return this.sortedPorts; + } + + public boolean isConnected() { + return this.isConnected; + } + + public Date getConnectedSince() { + return this.connectedSince; + } + + public DatapathId getDpid() { + return this.dpid; + } + + public Map<Object, Object> getAttributes() { + return this.attributes; + } + + public boolean isActive() { + return this.isActive; + } + + public Collection<IOFConnection> getConnections() { + return this.connections; + } + + public String getHandshakeState() { + return this.handshakeState; + } + + public String getQuarantineReason() { + return this.quarantineReason; + } +} diff --git a/src/main/java/net/floodlightcontroller/core/rest/TableStatistics.java b/src/main/java/net/floodlightcontroller/core/rest/TableStatistics.java new file mode 100644 index 0000000000000000000000000000000000000000..480ff2004e0eae6f133fb79a94adc318b6b57cf4 --- /dev/null +++ b/src/main/java/net/floodlightcontroller/core/rest/TableStatistics.java @@ -0,0 +1,45 @@ +package net.floodlightcontroller.core.rest; + +import org.projectfloodlight.openflow.protocol.OFTableStatsEntry; +import org.projectfloodlight.openflow.types.TableId; +import org.projectfloodlight.openflow.types.U64; + +public class TableStatistics { + + private final TableId tableId; + private final long activeCount; + private final U64 lookupCount; + private final U64 matchedCount; + + private TableStatistics(TableId tableId, long activeCount, U64 lookupCount, U64 matchedCount) { + this.tableId = tableId; + this.activeCount = activeCount; + this.lookupCount = lookupCount; + this.matchedCount = matchedCount; + } + + public static TableStatistics of(TableId tableId, long activeCount, U64 lookupCount, U64 matchedCount) { + return new TableStatistics(tableId, activeCount, lookupCount, matchedCount); + } + + public static TableStatistics of(OFTableStatsEntry entry) { + return new TableStatistics(entry.getTableId(), + entry.getActiveCount(), entry.getLookupCount(), entry.getMatchedCount()); + } + + public TableId getTableId() { + return tableId; + } + + public long getActiveCount() { + return activeCount; + } + + public U64 getLookupCount() { + return lookupCount; + } + + public U64 getMatchedCount() { + return matchedCount; + } +} \ No newline at end of file diff --git a/src/main/java/net/floodlightcontroller/core/types/MacVlanPair.java b/src/main/java/net/floodlightcontroller/core/types/MacVlanPair.java index 7a44f1d29f9c501bdbb55c0c64479e50a2f60573..ed7da2dff8d2ee9d33c7121a9a17ca651deca78d 100644 --- a/src/main/java/net/floodlightcontroller/core/types/MacVlanPair.java +++ b/src/main/java/net/floodlightcontroller/core/types/MacVlanPair.java @@ -17,20 +17,23 @@ package net.floodlightcontroller.core.types; +import org.projectfloodlight.openflow.types.MacAddress; +import org.projectfloodlight.openflow.types.VlanVid; + public class MacVlanPair { - public Long mac; - public Short vlan; - public MacVlanPair(Long mac, Short vlan) { - this.mac = mac; - this.vlan = vlan; + public MacAddress mac; + public VlanVid vlan; + public MacVlanPair(MacAddress mac2, VlanVid vlan2) { + this.mac = mac2; + this.vlan = vlan2; } - public long getMac() { - return mac.longValue(); + public MacAddress getMac() { + return mac; } - public short getVlan() { - return vlan.shortValue(); + public VlanVid getVlan() { + return vlan; } public boolean equals(Object o) { diff --git a/src/main/java/net/floodlightcontroller/core/types/SwitchMessagePair.java b/src/main/java/net/floodlightcontroller/core/types/SwitchMessagePair.java index 0e91bc9b488d6009eaaac15641aba1b0cde5a998..2f40532961b5912b2981f4ce5fbd75d668b1beb8 100644 --- a/src/main/java/net/floodlightcontroller/core/types/SwitchMessagePair.java +++ b/src/main/java/net/floodlightcontroller/core/types/SwitchMessagePair.java @@ -17,7 +17,7 @@ package net.floodlightcontroller.core.types; -import org.openflow.protocol.OFMessage; +import org.projectfloodlight.openflow.protocol.OFMessage; import net.floodlightcontroller.core.IOFSwitch; diff --git a/src/main/java/net/floodlightcontroller/core/util/AppCookie.java b/src/main/java/net/floodlightcontroller/core/util/AppCookie.java index 48ff7d9beb31921b2bfee97f52a85fc82bd94ae5..5406bbd8c9a6a2b92b423db0598d225713b83fa4 100644 --- a/src/main/java/net/floodlightcontroller/core/util/AppCookie.java +++ b/src/main/java/net/floodlightcontroller/core/util/AppCookie.java @@ -20,6 +20,8 @@ package net.floodlightcontroller.core.util; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import org.projectfloodlight.openflow.types.U64; + /*** * A static utility class to register flow cookiue AppIds and generating * flow cookies for a particular App` @@ -76,13 +78,13 @@ public class AppCookie { * @throws IllegalStateException if the application has not been registered */ - static public long makeCookie(int application, int user) { + static public U64 makeCookie(int application, int user) { if (!appIdMap.containsKey(application)) { throw new AppIDNotRegisteredException(application); } long longApp = application; long longUser = user & USER_MASK; // mask to prevent sign extend - return (longApp << APP_ID_SHIFT) | longUser; + return U64.of((longApp << APP_ID_SHIFT) | longUser); } /** @@ -91,54 +93,54 @@ public class AppCookie { * @param cookie * @return */ - static public int extractApp(long cookie) { - return (int)((cookie >>> APP_ID_SHIFT) & APP_ID_MASK); + static public int extractApp(U64 cookie) { + return (int)((cookie.getValue() >>> APP_ID_SHIFT) & APP_ID_MASK); } - static public int extractUser(long cookie) { - return (int)(cookie & USER_MASK); + static public int extractUser(U64 cookie) { + return (int)(cookie.getValue() & USER_MASK); } - static public boolean isRewriteFlagSet(long cookie) { - if ((cookie & REWRITE_MASK) !=0L) + static public boolean isRewriteFlagSet(U64 cookie) { + if ((cookie.getValue() & REWRITE_MASK) !=0L) return true; return false; } - static public boolean isSrcMacRewriteFlagSet(long cookie) { - if ((cookie & (1L << (SRC_MAC_REWRITE_BIT-1))) !=0L) + static public boolean isSrcMacRewriteFlagSet(U64 cookie) { + if ((cookie.getValue() & (1L << (SRC_MAC_REWRITE_BIT-1))) !=0L) return true; return false; } - static public boolean isDestMacRewriteFlagSet(long cookie) { - if ((cookie & (1L << (DEST_MAC_REWRITE_BIT-1))) !=0L) + static public boolean isDestMacRewriteFlagSet(U64 cookie) { + if ((cookie.getValue() & (1L << (DEST_MAC_REWRITE_BIT-1))) !=0L) return true; return false; } - static public boolean isSrcIpRewriteFlagSet(long cookie) { - if ((cookie & (1L << (SRC_IP_REWRITE_BIT-1))) !=0L) + static public boolean isSrcIpRewriteFlagSet(U64 cookie) { + if ((cookie.getValue() & (1L << (SRC_IP_REWRITE_BIT-1))) !=0L) return true; return false; } - static public boolean isDestIpRewriteFlagSet(long cookie) { - if ((cookie & (1L << (DEST_IP_REWRITE_BIT-1))) !=0L) + static public boolean isDestIpRewriteFlagSet(U64 cookie) { + if ((cookie.getValue() & (1L << (DEST_IP_REWRITE_BIT-1))) !=0L) return true; return false; } - static public long setSrcMacRewriteFlag(long cookie) { - return cookie | (1L << (SRC_MAC_REWRITE_BIT-1)); + static public U64 setSrcMacRewriteFlag(U64 cookie) { + return U64.of(cookie.getValue() | (1L << (SRC_MAC_REWRITE_BIT-1))); } - static public long setDestMacRewriteFlag(long cookie) { - return cookie | (1L << (DEST_MAC_REWRITE_BIT-1)); + static public U64 setDestMacRewriteFlag(U64 cookie) { + return U64.of(cookie.getValue() | (1L << (DEST_MAC_REWRITE_BIT-1))); } - static public long setSrcIpRewriteFlag(long cookie) { - return cookie | (1L << (SRC_IP_REWRITE_BIT-1)); + static public U64 setSrcIpRewriteFlag(U64 cookie) { + return U64.of(cookie.getValue() | (1L << (SRC_IP_REWRITE_BIT-1))); } - static public long setDestIpRewriteFlag(long cookie) { - return cookie | (1L << (DEST_IP_REWRITE_BIT-1)); + static public U64 setDestIpRewriteFlag(U64 cookie) { + return U64.of(cookie.getValue() | (1L << (DEST_IP_REWRITE_BIT-1))); } /** * A lame attempt to prevent duplicate application ID. - * TODO: Once bigdb is merged, we should expose appID->appName map + * TODO: We should expose appID->appName map * via REST API so CLI doesn't need a separate copy of the map. * * @param application diff --git a/src/main/java/net/floodlightcontroller/core/util/ListenerDispatcher.java b/src/main/java/net/floodlightcontroller/core/util/ListenerDispatcher.java index da3c16444582e01c2c93108c0e5678c9138db6fe..4a6b978c55e6f5c7a2eec59ccd984ab010f462d9 100644 --- a/src/main/java/net/floodlightcontroller/core/util/ListenerDispatcher.java +++ b/src/main/java/net/floodlightcontroller/core/util/ListenerDispatcher.java @@ -21,11 +21,10 @@ import java.util.ArrayList; import java.util.HashSet; import java.util.List; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import net.floodlightcontroller.core.IListener; import net.floodlightcontroller.core.annotations.LogMessageDoc; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Maintain lists of listeners ordered by dependency. @@ -34,7 +33,8 @@ import net.floodlightcontroller.core.annotations.LogMessageDoc; * */ public class ListenerDispatcher<U, T extends IListener<U>> { - protected static Logger logger = LoggerFactory.getLogger(ListenerDispatcher.class); + protected static final Logger logger = + LoggerFactory.getLogger(ListenerDispatcher.class); volatile List<T> listeners = new ArrayList<T>(); private void visit(List<T> newlisteners, U type, HashSet<T> visited, @@ -64,10 +64,10 @@ public class ListenerDispatcher<U, T extends IListener<U>> { message="No listener dependency solution: " + "No listeners without incoming dependencies", explanation="The set of listeners installed " + - "have dependencies with no solution", + "have dependencies with no solution", recommendation="Install a different set of listeners " + - "or install all dependencies. This is a defect in " + - "the controller installation.") + "or install all dependencies. This is a defect in " + + "the controller installation.") public void addListener(U type, T listener) { List<T> newlisteners = new ArrayList<T>(); if (listeners != null) @@ -91,7 +91,7 @@ public class ListenerDispatcher<U, T extends IListener<U>> { if (terminals.size() == 0) { logger.error("No listener dependency solution: " + - "No listeners without incoming dependencies"); + "No listeners without incoming dependencies"); listeners = newlisteners; return; } diff --git a/src/main/java/net/floodlightcontroller/core/util/OFUtils.java b/src/main/java/net/floodlightcontroller/core/util/OFUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..5f39289fc2b2752bc58db5f6efe8891a40dcfdeb --- /dev/null +++ b/src/main/java/net/floodlightcontroller/core/util/OFUtils.java @@ -0,0 +1,151 @@ +package net.floodlightcontroller.core.util; + +import java.nio.ByteBuffer; +import java.util.Objects; + +import org.projectfloodlight.openflow.protocol.OFFactory; +import org.projectfloodlight.openflow.protocol.match.Match; +import org.projectfloodlight.openflow.protocol.match.MatchField; +import org.projectfloodlight.openflow.types.ArpOpcode; +import org.projectfloodlight.openflow.types.EthType; +import org.projectfloodlight.openflow.types.ICMPv4Code; +import org.projectfloodlight.openflow.types.ICMPv4Type; +import org.projectfloodlight.openflow.types.IPv4Address; +import org.projectfloodlight.openflow.types.IpDscp; +import org.projectfloodlight.openflow.types.IpEcn; +import org.projectfloodlight.openflow.types.IpProtocol; +import org.projectfloodlight.openflow.types.MacAddress; +import org.projectfloodlight.openflow.types.OFPort; +import org.projectfloodlight.openflow.types.OFVlanVidMatch; +import org.projectfloodlight.openflow.types.TransportPort; +import org.projectfloodlight.openflow.types.U8; +import org.projectfloodlight.openflow.types.VlanPcp; + +public class OFUtils { + public static Match loadFromPacket(byte[] packetData, OFPort inputPort, + OFFactory factory) { + short scratch; + int transportOffset = 34; + ByteBuffer packetDataBB = ByteBuffer.wrap(packetData); + int limit = packetDataBB.limit(); + assert (limit >= 14); + + Match.Builder builder = factory.buildMatch(); + + if (!Objects.equals(inputPort, OFPort.ALL)) + builder.setExact(MatchField.IN_PORT, inputPort); + + // dl dst + byte[] dataLayerDestination = new byte[6]; + packetDataBB.get(dataLayerDestination); + builder.setExact(MatchField.ETH_DST, MacAddress.of(dataLayerDestination)); + // dl src + byte[] dataLayerSource = new byte[6]; + packetDataBB.get(dataLayerSource); + builder.setExact(MatchField.ETH_SRC, MacAddress.of(dataLayerSource)); + // dl type + short dataLayerType = packetDataBB.getShort(); + builder.setExact(MatchField.ETH_TYPE, EthType.of(dataLayerType)); + + if (dataLayerType != (short) 0x8100) { // need cast to avoid signed + // bug + builder.setExact(MatchField.VLAN_VID, OFVlanVidMatch.UNTAGGED); + builder.setExact(MatchField.VLAN_PCP, VlanPcp.NONE); + } else { + // has vlan tag + scratch = packetDataBB.getShort(); + builder.setExact(MatchField.VLAN_VID, OFVlanVidMatch.ofVlan(0xfff & scratch)); + builder.setExact(MatchField.VLAN_PCP, VlanPcp.of((byte)((0xe000 & scratch) >> 13))); + dataLayerType = packetDataBB.getShort(); + } + + short networkProtocol; + int networkSource; + int networkDestination; + + switch (dataLayerType) { + case 0x0800: + // ipv4 + // check packet length + scratch = packetDataBB.get(); + scratch = (short) (0xf & scratch); + transportOffset = (packetDataBB.position() - 1) + (scratch * 4); + // nw tos (dscp+ecn) + scratch = packetDataBB.get(); + builder.setExact(MatchField.IP_ECN, IpEcn.of((byte)(scratch & 0x03))); + builder.setExact(MatchField.IP_DSCP, IpDscp.of((byte) ((0xfc & scratch) >> 2))); + // nw protocol + packetDataBB.position(packetDataBB.position() + 7); + networkProtocol = packetDataBB.get(); + builder.setExact(MatchField.IP_PROTO, IpProtocol.of(networkProtocol)); + // nw src + packetDataBB.position(packetDataBB.position() + 2); + networkSource = packetDataBB.getInt(); + builder.setExact(MatchField.IPV4_SRC, IPv4Address.of(networkSource)); + // nw dst + networkDestination = packetDataBB.getInt(); + builder.setExact(MatchField.IPV4_DST, IPv4Address.of(networkDestination)); + packetDataBB.position(transportOffset); + + int port; + switch (networkProtocol) { + case 0x01: + // icmp + // type + short type = U8.f(packetDataBB.get()); + builder.setExact(MatchField.ICMPV4_TYPE, ICMPv4Type.of(type)); + // code + short code = U8.f(packetDataBB.get()); + builder.setExact(MatchField.ICMPV4_CODE, ICMPv4Code.of(code)); + break; + case 0x06: + // tcp + // tcp src + port = packetDataBB.getShort(); + builder.setExact(MatchField.TCP_SRC, TransportPort.of(port)); + // tcp dest + port = packetDataBB.getShort(); + builder.setExact(MatchField.TCP_DST, TransportPort.of(port)); + break; + case 0x11: + // udp + // udp src + port = packetDataBB.getShort(); + builder.setExact(MatchField.UDP_SRC, TransportPort.of(port)); + // udp dest + port = packetDataBB.getShort(); + builder.setExact(MatchField.UDP_DST, TransportPort.of(port)); + break; + default: + // Unknown network proto. + break; + } + break; + case 0x0806: + // arp + int arpPos = packetDataBB.position(); + // opcode + scratch = packetDataBB.getShort(arpPos + 6); + builder.setExact(MatchField.ARP_OP, ArpOpcode.of(0xff & scratch)); + + scratch = packetDataBB.getShort(arpPos + 2); + // if ipv4 and addr len is 4 + if (scratch == 0x800 && packetDataBB.get(arpPos + 5) == 4) { + networkSource = packetDataBB.getInt(arpPos + 14); + networkDestination = packetDataBB.getInt(arpPos + 24); + } else { + networkSource = 0; + networkDestination = 0; + } + builder.setExact(MatchField.ARP_SPA, IPv4Address.of(networkSource)); + builder.setExact(MatchField.ARP_TPA, IPv4Address.of(networkDestination)); + break; + default: + // Not ARP or IP. + // Don't specify any network fields + break; + } + + return builder.build(); + } +} diff --git a/src/main/java/net/floodlightcontroller/core/util/SingletonTask.java b/src/main/java/net/floodlightcontroller/core/util/SingletonTask.java index 07729e533cacda933365652113f9a5ad1516a6aa..7a145f82f1f668b0415d353c8d04cc4ecc8a97a8 100644 --- a/src/main/java/net/floodlightcontroller/core/util/SingletonTask.java +++ b/src/main/java/net/floodlightcontroller/core/util/SingletonTask.java @@ -21,7 +21,6 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import net.floodlightcontroller.core.annotations.LogMessageDoc; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -37,7 +36,8 @@ import org.slf4j.LoggerFactory; * * If the task has begun, set a bit to restart it after the current task finishes */ public class SingletonTask { - protected static Logger logger = LoggerFactory.getLogger(SingletonTask.class); + protected static final Logger logger = + LoggerFactory.getLogger(SingletonTask.class); protected static class SingletonTaskContext { protected boolean taskShouldRun = false; @@ -74,6 +74,10 @@ public class SingletonTask { } catch (Exception e) { logger.error("Exception while executing task", e); } + catch (Error e) { + logger.error("Error while executing task", e); + throw e; + } synchronized (parent.context) { parent.context.taskRunning = false; @@ -134,6 +138,7 @@ public class SingletonTask { long then = now + TimeUnit.NANOSECONDS.convert(delay, unit); context.waitingTask.nextschedule = then; +// logger.debug("rescheduled task " + this + " for " + TimeUnit.SECONDS.convert(then, TimeUnit.NANOSECONDS) + "s. A bunch of these messages -may- indicate you have a blocked task."); } else { context.waitingTask.nextschedule = 0; } @@ -153,10 +158,10 @@ public class SingletonTask { } if (needQueue) { - if (delay <= 0) + if (delay <= 0) ses.execute(stw); else ses.schedule(stw, delay, unit); } } -} \ No newline at end of file +} diff --git a/src/main/java/net/floodlightcontroller/core/util/URIUtil.java b/src/main/java/net/floodlightcontroller/core/util/URIUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..01a44ccf0cda6782ebbcaef9c5874361c4c272b0 --- /dev/null +++ b/src/main/java/net/floodlightcontroller/core/util/URIUtil.java @@ -0,0 +1,10 @@ +package net.floodlightcontroller.core.util; + +import java.net.URI; + +public class URIUtil { + + public static URI createURI(String hostname, int port) { + return URI.create("tcp://" + hostname + ":" + port); + } +} diff --git a/src/main/java/net/floodlightcontroller/core/web/AllSwitchStatisticsResource.java b/src/main/java/net/floodlightcontroller/core/web/AllSwitchStatisticsResource.java index c957e6356ba9833bb2b40e331881cccd528b40a1..169faaf9c1ca8f04630d1b885e1bff47cba09611 100644 --- a/src/main/java/net/floodlightcontroller/core/web/AllSwitchStatisticsResource.java +++ b/src/main/java/net/floodlightcontroller/core/web/AllSwitchStatisticsResource.java @@ -24,11 +24,13 @@ import java.util.List; import java.util.Map; import java.util.Set; -import net.floodlightcontroller.core.IFloodlightProviderService; -import org.openflow.protocol.OFFeaturesReply; -import org.openflow.protocol.statistics.OFStatistics; -import org.openflow.protocol.statistics.OFStatisticsType; -import org.openflow.util.HexString; +import net.floodlightcontroller.core.internal.IOFSwitchService; + +import org.projectfloodlight.openflow.protocol.OFFeaturesReply; +import org.projectfloodlight.openflow.protocol.OFStatsReply; +import org.projectfloodlight.openflow.protocol.OFStatsType; +import org.projectfloodlight.openflow.types.DatapathId; +import org.projectfloodlight.openflow.util.HexString; import org.restlet.resource.Get; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -50,26 +52,26 @@ public class AllSwitchStatisticsResource extends SwitchResourceBase { public Map<String, Object> retrieveInternal(String statType) { HashMap<String, Object> model = new HashMap<String, Object>(); - OFStatisticsType type = null; + OFStatsType type = null; REQUESTTYPE rType = null; if (statType.equals("port")) { - type = OFStatisticsType.PORT; + type = OFStatsType.PORT; rType = REQUESTTYPE.OFSTATS; } else if (statType.equals("queue")) { - type = OFStatisticsType.QUEUE; + type = OFStatsType.QUEUE; rType = REQUESTTYPE.OFSTATS; } else if (statType.equals("flow")) { - type = OFStatisticsType.FLOW; + type = OFStatsType.FLOW; rType = REQUESTTYPE.OFSTATS; } else if (statType.equals("aggregate")) { - type = OFStatisticsType.AGGREGATE; + type = OFStatsType.AGGREGATE; rType = REQUESTTYPE.OFSTATS; } else if (statType.equals("desc")) { - type = OFStatisticsType.DESC; + type = OFStatsType.DESC; rType = REQUESTTYPE.OFSTATS; } else if (statType.equals("table")) { - type = OFStatisticsType.TABLE; + type = OFStatsType.TABLE; rType = REQUESTTYPE.OFSTATS; } else if (statType.equals("features")) { rType = REQUESTTYPE.OFFEATURES; @@ -77,14 +79,14 @@ public class AllSwitchStatisticsResource extends SwitchResourceBase { return model; } - IFloodlightProviderService floodlightProvider = - (IFloodlightProviderService)getContext().getAttributes(). - get(IFloodlightProviderService.class.getCanonicalName()); - Set<Long> switchDpids = floodlightProvider.getAllSwitchDpids(); + IOFSwitchService switchService = + (IOFSwitchService) getContext().getAttributes(). + get(IOFSwitchService.class.getCanonicalName()); + Set<DatapathId> switchDpids = switchService.getAllSwitchDpids(); List<GetConcurrentStatsThread> activeThreads = new ArrayList<GetConcurrentStatsThread>(switchDpids.size()); List<GetConcurrentStatsThread> pendingRemovalThreads = new ArrayList<GetConcurrentStatsThread>(); GetConcurrentStatsThread t; - for (Long l : switchDpids) { + for (DatapathId l : switchDpids) { t = new GetConcurrentStatsThread(l, rType, type); activeThreads.add(t); t.start(); @@ -98,9 +100,9 @@ public class AllSwitchStatisticsResource extends SwitchResourceBase { for (GetConcurrentStatsThread curThread : activeThreads) { if (curThread.getState() == State.TERMINATED) { if (rType == REQUESTTYPE.OFSTATS) { - model.put(HexString.toHexString(curThread.getSwitchId()), curThread.getStatisticsReply()); + model.put(HexString.toHexString(curThread.getSwitchId().getLong()), curThread.getStatisticsReply()); } else if (rType == REQUESTTYPE.OFFEATURES) { - model.put(HexString.toHexString(curThread.getSwitchId()), curThread.getFeaturesReply()); + model.put(HexString.toHexString(curThread.getSwitchId().getLong()), curThread.getFeaturesReply()); } pendingRemovalThreads.add(curThread); } @@ -130,13 +132,13 @@ public class AllSwitchStatisticsResource extends SwitchResourceBase { } protected class GetConcurrentStatsThread extends Thread { - private List<OFStatistics> switchReply; - private long switchId; - private OFStatisticsType statType; + private List<OFStatsReply> switchReply; + private DatapathId switchId; + private OFStatsType statType; private REQUESTTYPE requestType; private OFFeaturesReply featuresReply; - public GetConcurrentStatsThread(long switchId, REQUESTTYPE requestType, OFStatisticsType statType) { + public GetConcurrentStatsThread(DatapathId switchId, REQUESTTYPE requestType, OFStatsType statType) { this.switchId = switchId; this.requestType = requestType; this.statType = statType; @@ -144,7 +146,7 @@ public class AllSwitchStatisticsResource extends SwitchResourceBase { this.featuresReply = null; } - public List<OFStatistics> getStatisticsReply() { + public List<OFStatsReply> getStatisticsReply() { return switchReply; } @@ -152,7 +154,7 @@ public class AllSwitchStatisticsResource extends SwitchResourceBase { return featuresReply; } - public long getSwitchId() { + public DatapathId getSwitchId() { return switchId; } diff --git a/src/main/java/net/floodlightcontroller/core/web/ControllerRoleResource.java b/src/main/java/net/floodlightcontroller/core/web/ControllerRoleResource.java index 9ad593788f05401a717e32cfa5943ca1b508f909..9da5c493bc4be59c3443ec82db58d389f746bc9e 100644 --- a/src/main/java/net/floodlightcontroller/core/web/ControllerRoleResource.java +++ b/src/main/java/net/floodlightcontroller/core/web/ControllerRoleResource.java @@ -21,7 +21,7 @@ import org.restlet.resource.ServerResource; import net.floodlightcontroller.core.IFloodlightProviderService; import net.floodlightcontroller.core.RoleInfo; -import net.floodlightcontroller.core.IFloodlightProviderService.Role; +import net.floodlightcontroller.core.HARole; import net.floodlightcontroller.core.annotations.LogMessageDoc; import org.restlet.resource.Get; @@ -49,9 +49,9 @@ public class ControllerRoleResource extends ServerResource { recommendation=LogMessageDoc.CHECK_CONTROLLER) public void setRole(RoleInfo roleInfo) { //Role role = Role.lookupRole(roleInfo.getRole()); - Role role = null; + HARole role = null; try { - role = Role.valueOf(roleInfo.getRole().toUpperCase()); + role = roleInfo.getRole(); } catch (IllegalArgumentException e) { // The role value in the REST call didn't match a valid diff --git a/src/main/java/net/floodlightcontroller/core/web/ControllerSwitchesResource.java b/src/main/java/net/floodlightcontroller/core/web/ControllerSwitchesResource.java index eeaebe22c38aafb04834f2bac82e51be991e21be..d8564f5703ec17b30cf53642e3225dd7f549e9ca 100644 --- a/src/main/java/net/floodlightcontroller/core/web/ControllerSwitchesResource.java +++ b/src/main/java/net/floodlightcontroller/core/web/ControllerSwitchesResource.java @@ -23,14 +23,17 @@ import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Collection; +import java.util.Set; -import net.floodlightcontroller.core.IFloodlightProviderService; +import net.floodlightcontroller.core.HARole; import net.floodlightcontroller.core.IOFSwitch; -import net.floodlightcontroller.core.ImmutablePort; +import net.floodlightcontroller.core.internal.IOFSwitchService; import net.floodlightcontroller.util.FilterIterator; -import org.openflow.protocol.OFPhysicalPort; -import org.openflow.util.HexString; +import org.projectfloodlight.openflow.protocol.OFCapabilities; +import org.projectfloodlight.openflow.protocol.OFPortDesc; +import org.projectfloodlight.openflow.types.DatapathId; +import org.projectfloodlight.openflow.protocol.OFActionType; import org.restlet.data.Form; import org.restlet.data.Status; import org.restlet.resource.Get; @@ -52,7 +55,7 @@ public class ControllerSwitchesResource extends ServerResource { this.sw = sw; } - public int getActions() { + public Set<OFActionType> getActions() { return sw.getActions(); } @@ -62,7 +65,7 @@ public class ControllerSwitchesResource extends ServerResource { public Map<String,String> getDescription() { Map<String,String> rv = new HashMap<String, String>(); - if (sw.getDescriptionStatistics() == null) { + if (sw.getSwitchDescription() == null) { rv.put("manufacturer", ""); rv.put("hardware", ""); rv.put("software", ""); @@ -70,24 +73,24 @@ public class ControllerSwitchesResource extends ServerResource { rv.put("datapath", ""); } else { rv.put("manufacturer", - sw.getDescriptionStatistics().getManufacturerDescription()); + sw.getSwitchDescription().getManufacturerDescription()); rv.put("hardware", - sw.getDescriptionStatistics().getHardwareDescription()); + sw.getSwitchDescription().getHardwareDescription()); rv.put("software", - sw.getDescriptionStatistics().getSoftwareDescription()); + sw.getSwitchDescription().getSoftwareDescription()); rv.put("serialNum", - sw.getDescriptionStatistics().getSerialNumber()); + sw.getSwitchDescription().getSerialNumber()); rv.put("datapath", - sw.getDescriptionStatistics().getDatapathDescription()); + sw.getSwitchDescription().getDatapathDescription()); } return rv; } - public int getBuffers() { + public long getBuffers() { return sw.getBuffers(); } - public int getCapabilities() { + public Set<OFCapabilities> getCapabilities() { return sw.getCapabilities(); } @@ -98,13 +101,13 @@ public class ControllerSwitchesResource extends ServerResource { } public String getDpid() { - return sw.getStringId(); + return sw.getId().toString(); } public String getHarole() { - if (sw.getHARole() == null) + if (sw.getControllerRole() == null) return "null"; - return sw.getHARole().toString(); + return HARole.ofOFRole(sw.getControllerRole()).toString(); } public String getInetAddress() { @@ -114,8 +117,8 @@ public class ControllerSwitchesResource extends ServerResource { return addr.toString(); } - public Collection<OFPhysicalPort> getPorts() { - return ImmutablePort.ofPhysicalPortListOf(sw.getPorts()); + public Collection<OFPortDesc> getPorts() { + return sw.getPorts(); } } @@ -154,17 +157,17 @@ public class ControllerSwitchesResource extends ServerResource { @Get("json") public Iterator<SwitchJsonSerializerWrapper> retrieve() { - IFloodlightProviderService floodlightProvider = - (IFloodlightProviderService)getContext().getAttributes(). - get(IFloodlightProviderService.class.getCanonicalName()); + IOFSwitchService switchService = + (IOFSwitchService) getContext().getAttributes(). + get(IOFSwitchService.class.getCanonicalName()); - Long switchDPID = null; + DatapathId switchDPID = null; Form form = getQuery(); String dpid = form.getFirstValue("dpid", true); if (dpid != null) { try { - switchDPID = HexString.toLong(dpid); + switchDPID = DatapathId.of(dpid); } catch (Exception e) { setStatus(Status.CLIENT_ERROR_BAD_REQUEST, DPID_ERROR); return null; @@ -172,7 +175,7 @@ public class ControllerSwitchesResource extends ServerResource { } if (switchDPID != null) { IOFSwitch sw = - floodlightProvider.getSwitch(switchDPID); + switchService.getSwitch(switchDPID); if (sw != null) { SwitchJsonSerializerWrapper wrappedSw = new SwitchJsonSerializerWrapper(sw); @@ -184,7 +187,7 @@ public class ControllerSwitchesResource extends ServerResource { form.getFirstValue("dpid__startswith", true); Iterator<IOFSwitch> iofSwitchIter = - floodlightProvider.getAllSwitchMap().values().iterator(); + switchService.getAllSwitchMap().values().iterator(); Iterator<SwitchJsonSerializerWrapper> switer = new SwitchJsonSerializerWrapperIterator(iofSwitchIter); if (dpidStartsWith != null) { diff --git a/src/main/java/net/floodlightcontroller/core/web/CounterResource.java b/src/main/java/net/floodlightcontroller/core/web/CounterResource.java index fb680d7c94b4a6af0cfdad5fd288c095e2c3ffd1..c08e5b3aa4026a580d9e99abc162e92aeb9d7589 100644 --- a/src/main/java/net/floodlightcontroller/core/web/CounterResource.java +++ b/src/main/java/net/floodlightcontroller/core/web/CounterResource.java @@ -19,40 +19,36 @@ package net.floodlightcontroller.core.web; import java.util.HashMap; import java.util.Iterator; +import java.util.List; import java.util.Map; import java.util.Map.Entry; -import net.floodlightcontroller.counter.CounterValue; -import net.floodlightcontroller.counter.ICounter; +import net.floodlightcontroller.debugcounter.DebugCounterResource; +import net.floodlightcontroller.debugcounter.IDebugCounter; import org.restlet.resource.Get; public class CounterResource extends CounterResourceBase { @Get("json") public Map<String, Object> retrieve() { - String counterTitle = + /*TODO @Ryan String counterTitle = (String) getRequestAttributes().get("counterTitle"); Map<String, Object> model = new HashMap<String,Object>(); - CounterValue v; + long dc; if (counterTitle.equalsIgnoreCase("all")) { - Map<String, ICounter> counters = this.counterStore.getAll(); + List<DebugCounterResource> counters = this.debugCounterService.getAllCounterValues(); if (counters != null) { - Iterator<Map.Entry<String, ICounter>> it = - counters.entrySet().iterator(); + Iterator<DebugCounterResource> it = counters.iterator(); while (it.hasNext()) { - Entry<String, ICounter> entry = it.next(); - String counterName = entry.getKey(); - v = entry.getValue().getCounterValue(); - - if (CounterValue.CounterType.LONG == v.getType()) { - model.put(counterName, v.getLong()); - } else if (v.getType() == CounterValue.CounterType.DOUBLE) { - model.put(counterName, v.getDouble()); - } + DebugCounterResource dcr = it.next(); + String counterName = dcr.getCounterHierarchy(); + dc = dcr.getCounterValue(); + model.put(counterName, dc); } } } else { - ICounter counter = this.counterStore.getCounter(counterTitle); + List<DebugCounterResource> counter = this.debugCounterService.getCounterHierarchy(???, counterTitle)); + long v = 0; if (counter != null) { v = counter.getCounterValue(); } else { @@ -65,6 +61,6 @@ public class CounterResource extends CounterResourceBase { model.put(counterTitle, v.getDouble()); } } - return model; + return model;*/ return null; } } diff --git a/src/main/java/net/floodlightcontroller/core/web/CounterResourceBase.java b/src/main/java/net/floodlightcontroller/core/web/CounterResourceBase.java index 70e90ed231431b7c88e1377183671698686b5f75..ecf3935f313d6b8e520640ce4aa118f9ecb74148 100644 --- a/src/main/java/net/floodlightcontroller/core/web/CounterResourceBase.java +++ b/src/main/java/net/floodlightcontroller/core/web/CounterResourceBase.java @@ -17,19 +17,18 @@ package net.floodlightcontroller.core.web; -import net.floodlightcontroller.counter.ICounterStoreService; +import net.floodlightcontroller.debugcounter.IDebugCounterService; import org.restlet.resource.ResourceException; import org.restlet.resource.ServerResource; public class CounterResourceBase extends ServerResource { - protected ICounterStoreService counterStore; + protected IDebugCounterService debugCounterService; @Override protected void doInit() throws ResourceException { super.doInit(); - counterStore = - (ICounterStoreService)getContext().getAttributes(). - get(ICounterStoreService.class.getCanonicalName()); + debugCounterService = (IDebugCounterService) getContext().getAttributes(). + get(IDebugCounterService.class.getCanonicalName()); } } diff --git a/src/main/java/net/floodlightcontroller/core/web/LoadedModuleLoaderResource.java b/src/main/java/net/floodlightcontroller/core/web/LoadedModuleLoaderResource.java index ad3ea5640cf61a20f4698363410a76dce26106d4..1afa6e470bd4ee2df8d6523295b7f2a8eb3d44ed 100644 --- a/src/main/java/net/floodlightcontroller/core/web/LoadedModuleLoaderResource.java +++ b/src/main/java/net/floodlightcontroller/core/web/LoadedModuleLoaderResource.java @@ -30,6 +30,6 @@ public class LoadedModuleLoaderResource extends ModuleLoaderResource { */ @Get("json") public Map<String, Object> retrieve() { - return retrieveInternal(true); + return retrieveInternal(false); } -} +} \ No newline at end of file diff --git a/src/main/java/net/floodlightcontroller/core/web/SwitchCounterCategoriesResource.java b/src/main/java/net/floodlightcontroller/core/web/SwitchCounterCategoriesResource.java index 34aa2eaa763f2953fa6b03ad95a82c25683add11..66ba78641b569aacb6a4dedae26de4c2bf2551a2 100644 --- a/src/main/java/net/floodlightcontroller/core/web/SwitchCounterCategoriesResource.java +++ b/src/main/java/net/floodlightcontroller/core/web/SwitchCounterCategoriesResource.java @@ -23,12 +23,10 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import org.openflow.util.HexString; +import org.projectfloodlight.openflow.types.DatapathId; import org.restlet.resource.Get; -import net.floodlightcontroller.core.IFloodlightProviderService; -import net.floodlightcontroller.counter.CounterStore.NetworkLayer; -import net.floodlightcontroller.counter.ICounterStoreService; +import net.floodlightcontroller.core.internal.IOFSwitchService; /** * Get the counter categories for a particular switch @@ -37,9 +35,9 @@ import net.floodlightcontroller.counter.ICounterStoreService; public class SwitchCounterCategoriesResource extends CounterResourceBase { @Get("json") public Map<String, Object> retrieve() { - IFloodlightProviderService floodlightProvider = - (IFloodlightProviderService)getContext().getAttributes(). - get(IFloodlightProviderService.class.getCanonicalName()); + IOFSwitchService switchService = + (IOFSwitchService)getContext().getAttributes(). + get(IOFSwitchService.class.getCanonicalName()); HashMap<String,Object> model = new HashMap<String,Object>(); String switchID = (String) getRequestAttributes().get("switchId"); @@ -47,8 +45,8 @@ public class SwitchCounterCategoriesResource extends CounterResourceBase { String layer = (String) getRequestAttributes().get("layer"); if (switchID.equalsIgnoreCase("all")) { - for (Long dpid : floodlightProvider.getAllSwitchDpids()) { - switchID = HexString.toHexString(dpid); + for (DatapathId dpid : switchService.getAllSwitchDpids()) { + switchID = dpid.toString(); getOneSwitchCounterCategoriesJson(model, switchID, counterName, layer); } @@ -63,7 +61,7 @@ public class SwitchCounterCategoriesResource extends CounterResourceBase { String switchID, String counterName, String layer) { - String fullCounterName = ""; + /*TODO @Ryan String fullCounterName = ""; NetworkLayer nl = NetworkLayer.L3; try { @@ -80,6 +78,6 @@ public class SwitchCounterCategoriesResource extends CounterResourceBase { List<String> categories = this.counterStore.getAllCategories(fullCounterName, nl); if (categories != null) { model.put(fullCounterName + "." + layer, categories); - } + }*/ } } diff --git a/src/main/java/net/floodlightcontroller/core/web/SwitchCounterResource.java b/src/main/java/net/floodlightcontroller/core/web/SwitchCounterResource.java index 34755ea083536a430652842a231fd22944e27552..8f33337d8f696028bba9c2b3e930556c8b0abb97 100644 --- a/src/main/java/net/floodlightcontroller/core/web/SwitchCounterResource.java +++ b/src/main/java/net/floodlightcontroller/core/web/SwitchCounterResource.java @@ -22,12 +22,10 @@ import java.net.URLDecoder; import java.util.HashMap; import java.util.Map; -import org.openflow.util.HexString; +import org.projectfloodlight.openflow.types.DatapathId; import org.restlet.resource.Get; -import net.floodlightcontroller.core.IFloodlightProviderService; -import net.floodlightcontroller.counter.ICounter; -import net.floodlightcontroller.counter.ICounterStoreService; +import net.floodlightcontroller.core.internal.IOFSwitchService; /** * Get counters for a particular switch @@ -36,9 +34,9 @@ import net.floodlightcontroller.counter.ICounterStoreService; public class SwitchCounterResource extends CounterResourceBase { @Get("json") public Map<String, Object> retrieve() { - IFloodlightProviderService floodlightProvider = - (IFloodlightProviderService)getContext().getAttributes(). - get(IFloodlightProviderService.class.getCanonicalName()); + /*TODO @Ryan IOFSwitchService switchService = + (IOFSwitchService)getContext().getAttributes(). + get(IOFSwitchService.class.getCanonicalName()); HashMap<String,Object> model = new HashMap<String,Object>(); String switchID = (String) getRequestAttributes().get("switchId"); @@ -46,20 +44,20 @@ public class SwitchCounterResource extends CounterResourceBase { if (switchID.equalsIgnoreCase("all")) { getOneSwitchCounterJson(model, ICounterStoreService.CONTROLLER_NAME, counterName); - for (Long dpid : floodlightProvider.getAllSwitchDpids()) { - switchID = HexString.toHexString(dpid); + for (DatapathId dpid : switchService.getAllSwitchDpids()) { + switchID = dpid.toString(); getOneSwitchCounterJson(model, switchID, counterName); } } else { getOneSwitchCounterJson(model, switchID, counterName); } - return model; + return model;*/ return null; } protected void getOneSwitchCounterJson(Map<String, Object> model, String switchID, String counterName) { - String fullCounterName = ""; + /*TODO @Ryan String fullCounterName = ""; try { counterName = URLDecoder.decode(counterName, "UTF-8"); @@ -75,7 +73,7 @@ public class SwitchCounterResource extends CounterResourceBase { sample.put(counter.getCounterDate().toString(), counter.getCounterValue().getLong()); model.put(switchID, sample); - } + } */ } } diff --git a/src/main/java/net/floodlightcontroller/core/web/SwitchResourceBase.java b/src/main/java/net/floodlightcontroller/core/web/SwitchResourceBase.java index 6b4a46318fb7dbac07fd628ed23fcc44b8fb146a..9730a82b80ef14184371fff8f77792bf289b1582 100644 --- a/src/main/java/net/floodlightcontroller/core/web/SwitchResourceBase.java +++ b/src/main/java/net/floodlightcontroller/core/web/SwitchResourceBase.java @@ -25,23 +25,33 @@ import java.util.concurrent.TimeUnit; import net.floodlightcontroller.core.IFloodlightProviderService; import net.floodlightcontroller.core.IOFSwitch; import net.floodlightcontroller.core.annotations.LogMessageDoc; - -import org.openflow.protocol.OFFeaturesReply; -import org.openflow.protocol.OFMatch; -import org.openflow.protocol.OFPort; -import org.openflow.protocol.OFStatisticsRequest; -import org.openflow.protocol.statistics.OFAggregateStatisticsRequest; -import org.openflow.protocol.statistics.OFFlowStatisticsRequest; -import org.openflow.protocol.statistics.OFPortStatisticsRequest; -import org.openflow.protocol.statistics.OFQueueStatisticsRequest; -import org.openflow.protocol.statistics.OFStatistics; -import org.openflow.protocol.statistics.OFStatisticsType; -import org.openflow.util.HexString; +import net.floodlightcontroller.core.internal.IOFSwitchService; + +import org.projectfloodlight.openflow.protocol.OFFeaturesReply; +import org.projectfloodlight.openflow.protocol.match.Match; +import org.projectfloodlight.openflow.protocol.queueprop.OFQueueProp; +import org.projectfloodlight.openflow.types.DatapathId; +import org.projectfloodlight.openflow.types.OFPort; +import org.projectfloodlight.openflow.types.TableId; +import org.projectfloodlight.openflow.protocol.OFFeaturesRequest; +import org.projectfloodlight.openflow.protocol.OFFlowStatsReply; +import org.projectfloodlight.openflow.protocol.OFQueueProperties; +import org.projectfloodlight.openflow.protocol.OFStatsReply; +import org.projectfloodlight.openflow.protocol.OFStatsRequest; +import org.projectfloodlight.openflow.protocol.OFAggregateStatsRequest; +import org.projectfloodlight.openflow.protocol.OFFlowStatsRequest; +import org.projectfloodlight.openflow.protocol.OFPortStatsRequest; +import org.projectfloodlight.openflow.protocol.OFQueueStatsRequest; +import org.projectfloodlight.openflow.protocol.OFStatsType; +import org.projectfloodlight.openflow.util.HexString; import org.restlet.resource.ResourceException; import org.restlet.resource.ServerResource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.common.primitives.UnsignedLong; +import com.google.common.util.concurrent.ListenableFuture; + /** * Base class for server resources related to switches * @author readams @@ -67,58 +77,52 @@ public class SwitchResourceBase extends ServerResource { "from the switch", recommendation=LogMessageDoc.CHECK_SWITCH + " " + LogMessageDoc.GENERIC_ACTION) - protected List<OFStatistics> getSwitchStatistics(long switchId, - OFStatisticsType statType) { - IFloodlightProviderService floodlightProvider = - (IFloodlightProviderService)getContext().getAttributes(). - get(IFloodlightProviderService.class.getCanonicalName()); - - IOFSwitch sw = floodlightProvider.getSwitch(switchId); - Future<List<OFStatistics>> future; - List<OFStatistics> values = null; + protected List<OFStatsReply> getSwitchStatistics(DatapathId switchId, + OFStatsType statType) { + IOFSwitchService switchService = + (IOFSwitchService) getContext().getAttributes(). + get(IOFSwitchService.class.getCanonicalName()); + + IOFSwitch sw = switchService.getSwitch(switchId); + ListenableFuture<?> future; + List<OFStatsReply> values = null; if (sw != null) { - OFStatisticsRequest req = new OFStatisticsRequest(); - req.setStatisticType(statType); - int requestLength = req.getLengthU(); - if (statType == OFStatisticsType.FLOW) { - OFFlowStatisticsRequest specificReq = new OFFlowStatisticsRequest(); - OFMatch match = new OFMatch(); - match.setWildcards(0xffffffff); - specificReq.setMatch(match); - specificReq.setOutPort(OFPort.OFPP_NONE.getValue()); - specificReq.setTableId((byte) 0xff); - req.setStatistics(Collections.singletonList((OFStatistics)specificReq)); - requestLength += specificReq.getLength(); - } else if (statType == OFStatisticsType.AGGREGATE) { - OFAggregateStatisticsRequest specificReq = new OFAggregateStatisticsRequest(); - OFMatch match = new OFMatch(); - match.setWildcards(0xffffffff); - specificReq.setMatch(match); - specificReq.setOutPort(OFPort.OFPP_NONE.getValue()); - specificReq.setTableId((byte) 0xff); - req.setStatistics(Collections.singletonList((OFStatistics)specificReq)); - requestLength += specificReq.getLength(); - } else if (statType == OFStatisticsType.PORT) { - OFPortStatisticsRequest specificReq = new OFPortStatisticsRequest(); - specificReq.setPortNumber(OFPort.OFPP_NONE.getValue()); - req.setStatistics(Collections.singletonList((OFStatistics)specificReq)); - requestLength += specificReq.getLength(); - } else if (statType == OFStatisticsType.QUEUE) { - OFQueueStatisticsRequest specificReq = new OFQueueStatisticsRequest(); - specificReq.setPortNumber(OFPort.OFPP_ALL.getValue()); - // LOOK! openflowj does not define OFPQ_ALL! pulled this from openflow.h - // note that I haven't seen this work yet though... - specificReq.setQueueId(0xffffffff); - req.setStatistics(Collections.singletonList((OFStatistics)specificReq)); - requestLength += specificReq.getLength(); - } else if (statType == OFStatisticsType.DESC || - statType == OFStatisticsType.TABLE) { + OFStatsRequest<?> req; + if (statType == OFStatsType.FLOW) { + Match match = sw.getOFFactory().buildMatch().build(); + req = sw.getOFFactory().buildFlowStatsRequest() + .setMatch(match) + .setOutPort(OFPort.ANY) + .setTableId(TableId.ALL) + .build(); + } else if (statType == OFStatsType.AGGREGATE) { + Match match = sw.getOFFactory().buildMatch().build(); + req = sw.getOFFactory().buildAggregateStatsRequest() + .setMatch(match) + .setOutPort(OFPort.ANY) + .setTableId(TableId.ALL) + .build(); + } else if (statType == OFStatsType.PORT) { + req = sw.getOFFactory().buildPortStatsRequest() + .setPortNo(OFPort.ANY) + .build(); + } else if (statType == OFStatsType.QUEUE) { + req = sw.getOFFactory().buildQueueStatsRequest() + .setPortNo(OFPort.ANY) + .setQueueId(UnsignedLong.MAX_VALUE.longValue()) + .build(); + } else if (statType == OFStatsType.DESC || + statType == OFStatsType.TABLE) { // pass - nothing todo besides set the type above + req = sw.getOFFactory().buildDescStatsRequest() + .build(); + } else { + //TODO @Ryan what to do about no matches in the if...elseif statements? + req = sw.getOFFactory().buildDescStatsRequest().build(); } - req.setLengthU(requestLength); try { - future = sw.queryStatistics(req); - values = future.get(10, TimeUnit.SECONDS); + future = sw.writeStatsRequest(req); + values = (List<OFStatsReply>) future.get(10, TimeUnit.SECONDS); } catch (Exception e) { log.error("Failure retrieving statistics from switch " + sw, e); } @@ -126,21 +130,24 @@ public class SwitchResourceBase extends ServerResource { return values; } - protected List<OFStatistics> getSwitchStatistics(String switchId, OFStatisticsType statType) { - return getSwitchStatistics(HexString.toLong(switchId), statType); + protected List<OFStatsReply> getSwitchStatistics(String switchId, OFStatsType statType) { + return getSwitchStatistics(DatapathId.of(switchId), statType); } - protected OFFeaturesReply getSwitchFeaturesReply(long switchId) { - IFloodlightProviderService floodlightProvider = - (IFloodlightProviderService)getContext().getAttributes(). - get(IFloodlightProviderService.class.getCanonicalName()); + protected OFFeaturesReply getSwitchFeaturesReply(DatapathId switchId) { + IOFSwitchService switchService = + (IOFSwitchService) getContext().getAttributes(). + get(IOFSwitchService.class.getCanonicalName()); - IOFSwitch sw = floodlightProvider.getSwitch(switchId); + IOFSwitch sw = switchService.getSwitch(switchId); Future<OFFeaturesReply> future; OFFeaturesReply featuresReply = null; + //TODO @Ryan The only thing to set in an OFFeaturesRequest is the XID. I'm not sure + // if it matters what I set it to. Will it have a default value of the next available? + OFFeaturesRequest featuresRequest = sw.getOFFactory().buildFeaturesRequest().build(); if (sw != null) { try { - future = sw.querySwitchFeaturesReply(); + future = sw.writeRequest(featuresRequest); featuresReply = future.get(10, TimeUnit.SECONDS); } catch (Exception e) { log.error("Failure getting features reply from switch" + sw, e); @@ -151,7 +158,7 @@ public class SwitchResourceBase extends ServerResource { } protected OFFeaturesReply getSwitchFeaturesReply(String switchId) { - return getSwitchFeaturesReply(HexString.toLong(switchId)); + return getSwitchFeaturesReply(DatapathId.of(switchId)); } -} +} \ No newline at end of file diff --git a/src/main/java/net/floodlightcontroller/core/web/SwitchRoleResource.java b/src/main/java/net/floodlightcontroller/core/web/SwitchRoleResource.java index e6d66e2c24db29bad055be5d9a0132778e7ffe87..45d9bc12fefcf6c10dd683cb1e4f6bdc066bd9a0 100644 --- a/src/main/java/net/floodlightcontroller/core/web/SwitchRoleResource.java +++ b/src/main/java/net/floodlightcontroller/core/web/SwitchRoleResource.java @@ -18,12 +18,13 @@ package net.floodlightcontroller.core.web; import java.util.HashMap; -import org.openflow.util.HexString; +import org.projectfloodlight.openflow.types.DatapathId; import org.restlet.resource.ServerResource; -import net.floodlightcontroller.core.IFloodlightProviderService; +import net.floodlightcontroller.core.HARole; import net.floodlightcontroller.core.IOFSwitch; import net.floodlightcontroller.core.RoleInfo; +import net.floodlightcontroller.core.internal.IOFSwitchService; import org.restlet.resource.Get; import org.slf4j.Logger; @@ -35,9 +36,9 @@ public class SwitchRoleResource extends ServerResource { @Get("json") public Object getRole() { - IFloodlightProviderService floodlightProvider = - (IFloodlightProviderService)getContext().getAttributes(). - get(IFloodlightProviderService.class.getCanonicalName()); + IOFSwitchService switchService = + (IOFSwitchService)getContext().getAttributes(). + get(IOFSwitchService.class.getCanonicalName()); String switchId = (String) getRequestAttributes().get("switchId"); @@ -45,19 +46,21 @@ public class SwitchRoleResource extends ServerResource { if (switchId.equalsIgnoreCase("all")) { HashMap<String,RoleInfo> model = new HashMap<String,RoleInfo>(); - for (IOFSwitch sw: floodlightProvider.getAllSwitchMap().values()) { - switchId = sw.getStringId(); - roleInfo = new RoleInfo(sw.getHARole(), null); + for (IOFSwitch sw: switchService.getAllSwitchMap().values()) { + switchId = sw.getId().toString(); + //TODO @Ryan not sure what the changeDescription string should be here. + roleInfo = new RoleInfo(HARole.ofOFRole(sw.getControllerRole()), "", null); model.put(switchId, roleInfo); } return model; } - Long dpid = HexString.toLong(switchId); - IOFSwitch sw = floodlightProvider.getSwitch(dpid); + DatapathId dpid = DatapathId.of(switchId); + IOFSwitch sw = switchService.getSwitch(dpid); if (sw == null) return null; - roleInfo = new RoleInfo(sw.getHARole(), null); + //TODO @Ryan not sure what the changeDescription string should be here. + roleInfo = new RoleInfo(HARole.ofOFRole(sw.getControllerRole()), "", null); return roleInfo; } } diff --git a/src/main/java/net/floodlightcontroller/core/web/SwitchStatisticsResource.java b/src/main/java/net/floodlightcontroller/core/web/SwitchStatisticsResource.java index 57771f718bfa51295220deb2a3d9e0e88c4223e3..a70d120ae59ff618d8ae57a3b59601cb2e66bbec 100644 --- a/src/main/java/net/floodlightcontroller/core/web/SwitchStatisticsResource.java +++ b/src/main/java/net/floodlightcontroller/core/web/SwitchStatisticsResource.java @@ -20,7 +20,8 @@ package net.floodlightcontroller.core.web; import java.util.HashMap; import java.util.Map; -import org.openflow.protocol.statistics.OFStatisticsType; +import org.projectfloodlight.openflow.protocol.OFStatsType; +import org.projectfloodlight.openflow.types.DatapathId; import org.restlet.resource.Get; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -42,17 +43,17 @@ public class SwitchStatisticsResource extends SwitchResourceBase { String statType = (String) getRequestAttributes().get("statType"); if (statType.equals("port")) { - values = getSwitchStatistics(switchId, OFStatisticsType.PORT); + values = getSwitchStatistics(DatapathId.of(switchId), OFStatsType.PORT); } else if (statType.equals("queue")) { - values = getSwitchStatistics(switchId, OFStatisticsType.QUEUE); + values = getSwitchStatistics(DatapathId.of(switchId), OFStatsType.QUEUE); } else if (statType.equals("flow")) { - values = getSwitchStatistics(switchId, OFStatisticsType.FLOW); + values = getSwitchStatistics(DatapathId.of(switchId), OFStatsType.FLOW); } else if (statType.equals("aggregate")) { - values = getSwitchStatistics(switchId, OFStatisticsType.AGGREGATE); + values = getSwitchStatistics(DatapathId.of(switchId), OFStatsType.AGGREGATE); } else if (statType.equals("desc")) { - values = getSwitchStatistics(switchId, OFStatisticsType.DESC); + values = getSwitchStatistics(DatapathId.of(switchId), OFStatsType.DESC); } else if (statType.equals("table")) { - values = getSwitchStatistics(switchId, OFStatisticsType.TABLE); + values = getSwitchStatistics(DatapathId.of(switchId), OFStatsType.TABLE); } else if (statType.equals("features")) { values = getSwitchFeaturesReply(switchId); } diff --git a/src/main/java/net/floodlightcontroller/core/web/serializers/ByteArrayMACSerializer.java b/src/main/java/net/floodlightcontroller/core/web/serializers/ByteArrayMACSerializer.java index ce4d3ba3c51480ad3ab3b488c398727efb6d85b4..f5f3aea8c93f9a5769ad3eb8aa536755cf83e802 100644 --- a/src/main/java/net/floodlightcontroller/core/web/serializers/ByteArrayMACSerializer.java +++ b/src/main/java/net/floodlightcontroller/core/web/serializers/ByteArrayMACSerializer.java @@ -23,7 +23,7 @@ import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; -import org.openflow.util.HexString; +import org.projectfloodlight.openflow.util.HexString; /** * Serialize a MAC as colon-separated hexadecimal diff --git a/src/main/java/net/floodlightcontroller/core/web/serializers/DPIDSerializer.java b/src/main/java/net/floodlightcontroller/core/web/serializers/DPIDSerializer.java index 6df067ead245a209b12ce20347c237913a8f7b29..d07a254680074dc53bcd4095bf7dca98b39253e0 100644 --- a/src/main/java/net/floodlightcontroller/core/web/serializers/DPIDSerializer.java +++ b/src/main/java/net/floodlightcontroller/core/web/serializers/DPIDSerializer.java @@ -23,7 +23,7 @@ import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; -import org.openflow.util.HexString; +import org.projectfloodlight.openflow.util.HexString; /** * Serialize a DPID as colon-separated hexadecimal diff --git a/src/main/java/net/floodlightcontroller/core/web/serializers/MACSerializer.java b/src/main/java/net/floodlightcontroller/core/web/serializers/MACSerializer.java index c797681edf2e8779365293188e89f51b9b4c30a1..c7ea09b8364fa9b6e740da82b2a3f55ec6458fb8 100644 --- a/src/main/java/net/floodlightcontroller/core/web/serializers/MACSerializer.java +++ b/src/main/java/net/floodlightcontroller/core/web/serializers/MACSerializer.java @@ -23,7 +23,7 @@ import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; -import org.openflow.util.HexString; +import org.projectfloodlight.openflow.util.HexString; /** * Serialize a MAC as colon-separated hexadecimal diff --git a/src/main/java/net/floodlightcontroller/counter/CounterStore.java b/src/main/java/net/floodlightcontroller/counter/CounterStore.java deleted file mode 100644 index bfe3b963f82b61bc0529005e4edd5b1f0b4d5164..0000000000000000000000000000000000000000 --- a/src/main/java/net/floodlightcontroller/counter/CounterStore.java +++ /dev/null @@ -1,579 +0,0 @@ -/** - * Copyright 2011, Big Switch Networks, Inc. - * Originally created by David Erickson, Stanford University - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. You may obtain - * a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - **/ - -package net.floodlightcontroller.counter; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import net.floodlightcontroller.core.IOFSwitch; -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.counter.CounterValue.CounterType; -import net.floodlightcontroller.packet.Ethernet; -import net.floodlightcontroller.packet.IPv4; - -import org.openflow.protocol.OFMessage; -import org.openflow.protocol.OFPacketIn; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Implements a central store for system counters. These counters include - * overall packet-in, packet-out, and flow-mod counters. Additional packet-in - * counters are maintained for bcast/unicast/multicast traffic, as well as counters - * for traffic types based on ethertype and ip-proto (maintained on a per switch - * and controller level). These counters are maintained without the involvement of - * any other module in the system. For per-module counters and other detailed - * debug services, consider IDebugCounterService. - * - * @authors Kyle, Kanzhe, Mandeep and Saurav - */ -public class CounterStore implements IFloodlightModule, ICounterStoreService { - protected static Logger log = LoggerFactory.getLogger(CounterStore.class); - - public enum NetworkLayer { - L2, L3, L4 - } - - protected class CounterEntry { - protected ICounter counter; - String title; - } - - protected class MutableInt { - int value = 0; - public void increment() { value += 1; } - public int get() { return value; } - public void set(int val) { value = val; } - } - - protected class CounterKeyTuple { - byte msgType; - long dpid; - short l3type; - byte l4type; - - public CounterKeyTuple(byte msgType, long dpid, short l3type, byte l4type){ - this.msgType = msgType; - this.dpid = dpid; - this.l3type = l3type; - this.l4type = l4type; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (obj == null) return false; - if (!(obj instanceof CounterKeyTuple)) return false; - CounterKeyTuple other = (CounterKeyTuple) obj; - if (this.msgType == other.msgType && - this.dpid == other.dpid && - this.l3type == other.l3type && - this.l4type == other.l4type) - return true; - return false; - } - - @Override - public int hashCode() { - final int prime = 283; - int result = 1; - result = prime * result + msgType; - result = prime * result + (int) (dpid ^ (dpid >>> 32)); - result = prime * result + l3type; - result = prime * result + l4type; - return result; - } - } - - /** - * Counter storage across all threads. These are periodically updated from the - * local per thread counters by the updateFlush method. - */ - protected ConcurrentHashMap<CounterKeyTuple, List<ICounter>> - pktinCounters = new ConcurrentHashMap<CounterKeyTuple, List<ICounter>>(); - protected ConcurrentHashMap<CounterKeyTuple, List<ICounter>> - pktoutCounters = new ConcurrentHashMap<CounterKeyTuple, List<ICounter>>(); - - /** - * Thread local counter stores - */ - protected final ThreadLocal<Map<CounterKeyTuple,MutableInt>> pktin_local_buffer = - new ThreadLocal<Map<CounterKeyTuple,MutableInt>>() { - @Override - protected Map<CounterKeyTuple,MutableInt> initialValue() { - return new HashMap<CounterKeyTuple,MutableInt>(); - } - }; - - protected final ThreadLocal<Map<CounterKeyTuple,MutableInt>> pktout_local_buffer = - new ThreadLocal<Map<CounterKeyTuple,MutableInt>>() { - @Override - protected Map<CounterKeyTuple,MutableInt> initialValue() { - return new HashMap<CounterKeyTuple,MutableInt>(); - } - }; - - /** - * A cache of counterName --> Counter used to retrieve counters quickly via - * string-counter-keys - */ - protected ConcurrentHashMap<String, CounterEntry> nameToCEIndex = - new ConcurrentHashMap<String, CounterEntry>(); - - /** - * Counter Categories grouped by network layers - * NetworkLayer -> CounterToCategories - */ - protected static Map<NetworkLayer, Map<String, List<String>>> layeredCategories = - new ConcurrentHashMap<NetworkLayer, Map<String, List<String>>> (); - - //******************************* - // ICounterStoreService - //******************************* - - @Override - public void updatePacketInCountersLocal(IOFSwitch sw, OFMessage m, Ethernet eth) { - if (((OFPacketIn)m).getPacketData().length <= 0) { - return; - } - CounterKeyTuple countersKey = this.getCountersKey(sw, m, eth); - Map<CounterKeyTuple, MutableInt> pktin_buffer = this.pktin_local_buffer.get(); - MutableInt currval = pktin_buffer.get(countersKey); - - if (currval == null) { - this.createPacketInCounters(sw, m, eth); // create counters as side effect (if required) - currval = new MutableInt(); - pktin_buffer.put(countersKey, currval); - } - currval.increment(); - return; - } - - @Override - public void updatePktOutFMCounterStoreLocal(IOFSwitch sw, OFMessage m) { - CounterKeyTuple countersKey = this.getCountersKey(sw, m, null); - Map<CounterKeyTuple, MutableInt> pktout_buffer = this.pktout_local_buffer.get(); - MutableInt currval = pktout_buffer.get(countersKey); - - if (currval == null) { - this.getPktOutFMCounters(sw, m); // create counters as side effect (if required) - currval = new MutableInt(); - pktout_buffer.put(countersKey, currval); - } - currval.increment(); - return; - } - - @Override - public void updateFlush() { - Date date = new Date(); - Map<CounterKeyTuple, MutableInt> pktin_buffer = this.pktin_local_buffer.get(); - for (CounterKeyTuple key : pktin_buffer.keySet()) { - MutableInt currval = pktin_buffer.get(key); - int delta = currval.get(); - - if (delta > 0) { - List<ICounter> counters = this.pktinCounters.get(key); - if (counters != null) { - for (ICounter c : counters) { - c.increment(date, delta); - } - } - } - } - // We could do better "GC" of counters that have not been update "recently" - pktin_buffer.clear(); - - Map<CounterKeyTuple, MutableInt> pktout_buffer = this.pktout_local_buffer.get(); - for (CounterKeyTuple key : pktout_buffer.keySet()) { - MutableInt currval = pktout_buffer.get(key); - int delta = currval.get(); - - if (delta > 0) { - List<ICounter> counters = this.pktoutCounters.get(key); - if (counters != null) { - for (ICounter c : counters) { - c.increment(date, delta); - } - } - } - } - // We could do better "GC" of counters that have not been update "recently" - pktout_buffer.clear(); - } - - @Override - public ICounter createCounter(String key, CounterValue.CounterType type) { - CounterEntry ce; - ICounter c; - - c = SimpleCounter.createCounter(new Date(), type); - ce = new CounterEntry(); - ce.counter = c; - ce.title = key; - nameToCEIndex.putIfAbsent(key, ce); - - return nameToCEIndex.get(key).counter; - } - - @Override - public ICounter getCounter(String key) { - CounterEntry counter = nameToCEIndex.get(key); - if (counter != null) { - return counter.counter; - } else { - return null; - } - } - - /* (non-Javadoc) - * @see net.floodlightcontroller.counter.ICounterStoreService#getAll() - */ - @Override - public Map<String, ICounter> getAll() { - Map<String, ICounter> ret = new ConcurrentHashMap<String, ICounter>(); - for(Map.Entry<String, CounterEntry> counterEntry : this.nameToCEIndex.entrySet()) { - String key = counterEntry.getKey(); - ICounter counter = counterEntry.getValue().counter; - ret.put(key, counter); - } - return ret; - } - - @Override - public List<String> getAllCategories(String counterName, NetworkLayer layer) { - if (layeredCategories.containsKey(layer)) { - Map<String, List<String>> counterToCategories = layeredCategories.get(layer); - if (counterToCategories.containsKey(counterName)) { - return counterToCategories.get(counterName); - } - } - return null; - } - - /** - * Create a title based on switch ID, portID, vlanID, and counterName - * If portID is -1, the title represents the given switch only - * If portID is a non-negative number, the title represents the port on the given switch - */ - public static String createCounterName(String switchID, int portID, String counterName) { - if (portID < 0) { - return switchID + TitleDelimitor + counterName; - } else { - return switchID + TitleDelimitor + portID + TitleDelimitor + counterName; - } - } - - //******************************* - // Internal Methods - //******************************* - - protected CounterKeyTuple getCountersKey(IOFSwitch sw, OFMessage m, Ethernet eth) { - byte mtype = m.getType().getTypeValue(); - short l3type = 0; - byte l4type = 0; - - if (eth != null) { - l3type = eth.getEtherType(); - if (eth.getPayload() instanceof IPv4) { - IPv4 ipV4 = (IPv4)eth.getPayload(); - l4type = ipV4.getProtocol(); - } - } - return new CounterKeyTuple(mtype, sw.getId(), l3type, l4type); - } - - protected List<ICounter> createPacketInCounters(IOFSwitch sw, OFMessage m, Ethernet eth) { - /* If possible, find and return counters for this tuple */ - CounterKeyTuple countersKey = this.getCountersKey(sw, m, eth); - List<ICounter> counters = - this.pktinCounters.get(countersKey); - if (counters != null) { - return counters; - } - - /* - * Create the required counters - */ - counters = new ArrayList<ICounter>(); - - int l3type = eth.getEtherType() & 0xffff; - String switchIdHex = sw.getStringId(); - String etherType = String.format("%04x", eth.getEtherType()); - String packetName = m.getType().toClass().getName(); - packetName = packetName.substring(packetName.lastIndexOf('.')+1); - - // L2 Type - String l2Type = null; - if (eth.isBroadcast()) { - l2Type = BROADCAST; - } - else if (eth.isMulticast()) { - l2Type = MULTICAST; - } - else { - l2Type = UNICAST; - } - - /* - * Use alias for L3 type - * Valid EtherType must be greater than or equal to 0x0600 - * It is V1 Ethernet Frame if EtherType < 0x0600 - */ - if (l3type < 0x0600) { - etherType = "0599"; - } - if (TypeAliases.l3TypeAliasMap != null && - TypeAliases.l3TypeAliasMap.containsKey(etherType)) { - etherType = TypeAliases.l3TypeAliasMap.get(etherType); - } - else { - etherType = "L3_" + etherType; - } - - // overall controller packet counter names - String controllerCounterName = - CounterStore.createCounterName( - CONTROLLER_NAME, - -1, - packetName); - counters.add(createCounter(controllerCounterName, - CounterType.LONG)); - - String switchCounterName = - CounterStore.createCounterName( - switchIdHex, - -1, - packetName); - counters.add(createCounter(switchCounterName, - CounterType.LONG)); - - // L2 counter names - String controllerL2CategoryCounterName = - CounterStore.createCounterName( - CONTROLLER_NAME, - -1, - packetName, - l2Type, - NetworkLayer.L2); - counters.add(createCounter(controllerL2CategoryCounterName, - CounterType.LONG)); - - String switchL2CategoryCounterName = - CounterStore.createCounterName( - switchIdHex, - -1, - packetName, - l2Type, - NetworkLayer.L2); - counters.add(createCounter(switchL2CategoryCounterName, - CounterType.LONG)); - - // L3 counter names - String controllerL3CategoryCounterName = - CounterStore.createCounterName( - CONTROLLER_NAME, - -1, - packetName, - etherType, - NetworkLayer.L3); - counters.add(createCounter(controllerL3CategoryCounterName, - CounterType.LONG)); - - String switchL3CategoryCounterName = - CounterStore.createCounterName( - switchIdHex, - -1, - packetName, - etherType, - NetworkLayer.L3); - counters.add(createCounter(switchL3CategoryCounterName, - CounterType.LONG)); - - // L4 counters - if (eth.getPayload() instanceof IPv4) { - - // resolve protocol alias - IPv4 ipV4 = (IPv4)eth.getPayload(); - String l4name = String.format("%02x", ipV4.getProtocol()); - if (TypeAliases.l4TypeAliasMap != null && - TypeAliases.l4TypeAliasMap.containsKey(l4name)) { - l4name = TypeAliases.l4TypeAliasMap.get(l4name); - } - else { - l4name = "L4_" + l4name; - } - - // create counters - String controllerL4CategoryCounterName = - CounterStore.createCounterName( - CONTROLLER_NAME, - -1, - packetName, - l4name, - NetworkLayer.L4); - counters.add(createCounter(controllerL4CategoryCounterName, - CounterType.LONG)); - - String switchL4CategoryCounterName = - CounterStore.createCounterName( - switchIdHex, - -1, - packetName, - l4name, - NetworkLayer.L4); - counters.add(createCounter(switchL4CategoryCounterName, - CounterType.LONG)); - - } - - /* Add to map and return */ - this.pktinCounters.putIfAbsent(countersKey, counters); - return this.pktinCounters.get(countersKey); - } - - protected List<ICounter> getPktOutFMCounters(IOFSwitch sw, OFMessage m) { - /* If possible, find and return counters for this tuple */ - CounterKeyTuple countersKey = this.getCountersKey(sw, m, null); - List<ICounter> counters = - this.pktoutCounters.get(countersKey); - if (counters != null) { - return counters; - } - - /* - * Create the required counters - */ - counters = new ArrayList<ICounter>(); - - /* String values for names */ - String switchIdHex = sw.getStringId(); - String packetName = m.getType().toClass().getName(); - packetName = packetName.substring(packetName.lastIndexOf('.')+1); - - String controllerFMCounterName = - CounterStore.createCounterName( - CONTROLLER_NAME, - -1, - packetName); - counters.add(createCounter(controllerFMCounterName, - CounterValue.CounterType.LONG)); - - String switchFMCounterName = - CounterStore.createCounterName( - switchIdHex, - -1, - packetName); - counters.add(createCounter(switchFMCounterName, - CounterValue.CounterType.LONG)); - - /* Add to map and return */ - this.pktoutCounters.putIfAbsent(countersKey, counters); - return this.pktoutCounters.get(countersKey); - - } - - /** - * Create a title based on switch ID, portID, vlanID, counterName, and subCategory - * If portID is -1, the title represents the given switch only - * If portID is a non-negative number, the title represents the port on the given switch - * For example: PacketIns can be further categorized based on L2 etherType or L3 protocol - */ - protected static String createCounterName(String switchID, int portID, String counterName, - String subCategory, NetworkLayer layer) { - String fullCounterName = ""; - String groupCounterName = ""; - - if (portID < 0) { - groupCounterName = switchID + TitleDelimitor + counterName; - fullCounterName = groupCounterName + TitleDelimitor + subCategory; - } else { - groupCounterName = switchID + TitleDelimitor + portID + TitleDelimitor + counterName; - fullCounterName = groupCounterName + TitleDelimitor + subCategory; - } - - Map<String, List<String>> counterToCategories; - if (layeredCategories.containsKey(layer)) { - counterToCategories = layeredCategories.get(layer); - } else { - counterToCategories = new ConcurrentHashMap<String, List<String>> (); - layeredCategories.put(layer, counterToCategories); - } - - List<String> categories; - if (counterToCategories.containsKey(groupCounterName)) { - categories = counterToCategories.get(groupCounterName); - } else { - categories = new ArrayList<String>(); - counterToCategories.put(groupCounterName, categories); - } - - if (!categories.contains(subCategory)) { - categories.add(subCategory); - } - return fullCounterName; - } - - //******************************* - // IFloodlightProvider - //******************************* - - @Override - public Collection<Class<? extends IFloodlightService>> getModuleServices() { - Collection<Class<? extends IFloodlightService>> services = - new ArrayList<Class<? extends IFloodlightService>>(1); - services.add(ICounterStoreService.class); - return services; - } - - @Override - public Map<Class<? extends IFloodlightService>, IFloodlightService> - getServiceImpls() { - Map<Class<? extends IFloodlightService>, - IFloodlightService> m = - new HashMap<Class<? extends IFloodlightService>, - IFloodlightService>(); - m.put(ICounterStoreService.class, this); - return m; - } - - @Override - public Collection<Class<? extends IFloodlightService>> getModuleDependencies() { - // no-op, no dependencies - return null; - } - - @Override - public void init(FloodlightModuleContext context) - throws FloodlightModuleException { - // no-op for now - } - - @Override - public void startUp(FloodlightModuleContext context) { - // no-op for now - } - -} diff --git a/src/main/java/net/floodlightcontroller/counter/CounterValue.java b/src/main/java/net/floodlightcontroller/counter/CounterValue.java deleted file mode 100644 index 1852d5cc5b7a4dc9e7fcd841c4c9764e67f66084..0000000000000000000000000000000000000000 --- a/src/main/java/net/floodlightcontroller/counter/CounterValue.java +++ /dev/null @@ -1,102 +0,0 @@ -/** -* Copyright 2011, Big Switch Networks, Inc. -* Originally created by David Erickson, Stanford University -* -* Licensed under the Apache License, Version 2.0 (the "License"); you may -* not use this file except in compliance with the License. You may obtain -* a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -* License for the specific language governing permissions and limitations -* under the License. -**/ - -package net.floodlightcontroller.counter; - -/** - * The class defines the counter value type and value - * - * @author Kanzhe - * - */ -public class CounterValue { - public enum CounterType { - LONG, - DOUBLE - } - - protected CounterType type; - protected long longValue; - protected double doubleValue; - - public CounterValue(CounterType type) { - this.type = CounterType.LONG; - this.longValue = 0; - this.doubleValue = 0.0; - } - - /** - * This method is only applicable to type long. - * Setter() should be used for type double - */ - public void increment(long delta) { - if (this.type == CounterType.LONG) { - this.longValue += delta; - } else { - throw new IllegalArgumentException("Invalid counter type. This counter is not a long type."); - } - } - - public void setLongValue(long value) { - if (this.type == CounterType.LONG) { - this.longValue = value; - } else { - throw new IllegalArgumentException("Invalid counter type. This counter is not a long type."); - } - } - - public void setDoubleValue(double value) { - if (this.type == CounterType.DOUBLE) { - this.doubleValue = value; - } else { - throw new IllegalArgumentException("Invalid counter type. This counter is not a double type."); - } - } - - public long getLong() { - if (this.type == CounterType.LONG) { - return this.longValue; - } else { - throw new IllegalArgumentException("Invalid counter type. This counter is not a long type."); - } - } - - public double getDouble() { - if (this.type == CounterType.DOUBLE) { - return this.doubleValue; - } else { - throw new IllegalArgumentException("Invalid counter type. This counter is not a double type."); - } - } - - - public CounterType getType() { - return this.type; - } - - public String toString() { - String ret = "{type: "; - if (this.type == CounterType.DOUBLE) { - ret += "Double" + ", value: " + this.doubleValue + "}"; - } else { - ret += "Long" + ", value: " + this.longValue + "}"; - } - return ret; - } - - -} diff --git a/src/main/java/net/floodlightcontroller/counter/ICounter.java b/src/main/java/net/floodlightcontroller/counter/ICounter.java deleted file mode 100644 index 0e31fdecd6a75419b9ccab92cd4a94e33f6f8cda..0000000000000000000000000000000000000000 --- a/src/main/java/net/floodlightcontroller/counter/ICounter.java +++ /dev/null @@ -1,71 +0,0 @@ -/** -* Copyright 2011, Big Switch Networks, Inc. -* Originally created by David Erickson, Stanford University -* -* Licensed under the Apache License, Version 2.0 (the "License"); you may -* not use this file except in compliance with the License. You may obtain -* a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -* License for the specific language governing permissions and limitations -* under the License. -**/ - -/** - * Simple interface for a counter whose value can be retrieved in several different - * time increments (last x seconds, minutes, hours, days) - */ -package net.floodlightcontroller.counter; - -import java.util.Date; - -/** - * @author kyle - * - */ -public interface ICounter { - - /** - * Most commonly used method - */ - public void increment(); - - /** - * Used primarily for flushing thread local updates - */ - public void increment(Date d, long delta); - - /** - * Counter value setter - */ - public void setCounter(Date d, CounterValue value); - - /** - * Return the most current value - */ - public Date getCounterDate(); - - /** - * Return the most current value - */ - public CounterValue getCounterValue(); - - /** - * Reset the value - */ - public void reset(Date d); - - - public static enum DateSpan { - REALTIME, - SECONDS, - MINUTES, - HOURS, - DAYS, - WEEKS - } -} diff --git a/src/main/java/net/floodlightcontroller/counter/ICounterStoreService.java b/src/main/java/net/floodlightcontroller/counter/ICounterStoreService.java deleted file mode 100644 index c0cbd55916086103bb4df820ca1b33974ed3dcca..0000000000000000000000000000000000000000 --- a/src/main/java/net/floodlightcontroller/counter/ICounterStoreService.java +++ /dev/null @@ -1,93 +0,0 @@ -/** - * 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.counter; - -import java.util.List; -import java.util.Map; - -import org.openflow.protocol.OFMessage; - -import net.floodlightcontroller.core.IOFSwitch; -import net.floodlightcontroller.core.module.IFloodlightService; -import net.floodlightcontroller.counter.CounterStore.NetworkLayer; -import net.floodlightcontroller.packet.Ethernet; - -public interface ICounterStoreService extends IFloodlightService { - - public final static String CONTROLLER_NAME = "controller"; - public final static String TitleDelimitor = "__"; - - /** Broadcast and multicast */ - public final static String BROADCAST = "broadcast"; - public final static String MULTICAST = "multicast"; - public final static String UNICAST = "unicast"; - - /** L2 EtherType subCategories */ - public final static String L3ET_IPV4 = "L3_IPv4"; - - /** - * Update packetIn counters - * - * @param sw - * @param m - * @param eth - */ - public void updatePacketInCountersLocal(IOFSwitch sw, OFMessage m, Ethernet eth); - - /** - * This method can only be used to update packetOut and flowmod counters - * - * @param sw - * @param ofMsg - */ - public void updatePktOutFMCounterStoreLocal(IOFSwitch sw, OFMessage ofMsg); - - /** - * Flush Local Counter Updates - * - */ - public void updateFlush(); - - /** - * Retrieve a list of subCategories by counterName. - * null if nothing. - */ - public List<String> getAllCategories(String counterName, - NetworkLayer layer); - - /** - * Create a new ICounter and set the title. Note that the title must be - * unique, otherwise this will throw an IllegalArgumentException. - * - * @param key - * @param type - * @return - */ - public ICounter createCounter(String key, CounterValue.CounterType type); - - /** - * Retrieves a counter with the given title, or null if none can be found. - */ - public ICounter getCounter(String key); - - /** - * Returns an immutable map of title:counter with all of the counters in the store. - * - * (Note - this method may be slow - primarily for debugging/UI) - */ - public Map<String, ICounter> getAll(); -} diff --git a/src/main/java/net/floodlightcontroller/counter/NullCounterStore.java b/src/main/java/net/floodlightcontroller/counter/NullCounterStore.java deleted file mode 100644 index bdcc690b349c4497d8ca7b645b23a03d3af2ce85..0000000000000000000000000000000000000000 --- a/src/main/java/net/floodlightcontroller/counter/NullCounterStore.java +++ /dev/null @@ -1,125 +0,0 @@ -/** - * 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.counter; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.openflow.protocol.OFMessage; - -import net.floodlightcontroller.core.IOFSwitch; -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.counter.CounterStore.NetworkLayer; -import net.floodlightcontroller.counter.CounterValue.CounterType; -import net.floodlightcontroller.packet.Ethernet; - -/** - * An ICounsterStoreService implementation that does nothing. - * This is used mainly for performance testing or if you don't - * want to use the counterstore. - * @author alexreimers - * - */ -public class NullCounterStore implements IFloodlightModule, - ICounterStoreService { - - private ICounter emptyCounter; - private List<String> emptyList; - private Map<String, ICounter> emptyMap; - - @Override - public void updatePacketInCountersLocal(IOFSwitch sw, OFMessage m, Ethernet eth) { - // no-op - } - - @Override - public void updatePktOutFMCounterStoreLocal(IOFSwitch sw, OFMessage ofMsg) { - // no-op - } - - @Override - public void updateFlush() { - // no-op - } - - @Override - public List<String> - getAllCategories(String counterName, NetworkLayer layer) { - return emptyList; - } - - @Override - public ICounter createCounter(String key, CounterType type) { - return emptyCounter; - } - - @Override - public ICounter getCounter(String key) { - return emptyCounter; - } - - @Override - public Map<String, ICounter> getAll() { - return emptyMap; - } - - @Override - public Collection<Class<? extends IFloodlightService>> getModuleServices() { - Collection<Class<? extends IFloodlightService>> services = - new ArrayList<Class<? extends IFloodlightService>>(1); - services.add(ICounterStoreService.class); - return services; - } - - @Override - public Map<Class<? extends IFloodlightService>, IFloodlightService> - getServiceImpls() { - Map<Class<? extends IFloodlightService>, - IFloodlightService> m = - new HashMap<Class<? extends IFloodlightService>, - IFloodlightService>(); - m.put(ICounterStoreService.class, this); - return m; - } - - @Override - public Collection<Class<? extends IFloodlightService>> - getModuleDependencies() { - // None, return null - return null; - } - - @Override - public void init(FloodlightModuleContext context) - throws FloodlightModuleException { - emptyCounter = new SimpleCounter(new Date(), CounterType.LONG); - emptyList = new ArrayList<String>(); - emptyMap = new HashMap<String, ICounter>(); - } - - @Override - public void startUp(FloodlightModuleContext context) { - // no-op - } -} diff --git a/src/main/java/net/floodlightcontroller/counter/SimpleCounter.java b/src/main/java/net/floodlightcontroller/counter/SimpleCounter.java deleted file mode 100644 index dd0daf31797ace63ad0d1c8004ec8b016cb19dc9..0000000000000000000000000000000000000000 --- a/src/main/java/net/floodlightcontroller/counter/SimpleCounter.java +++ /dev/null @@ -1,129 +0,0 @@ -/** -* Copyright 2011, Big Switch Networks, Inc. -* Originally created by David Erickson, Stanford University -* -* Licensed under the Apache License, Version 2.0 (the "License"); you may -* not use this file except in compliance with the License. You may obtain -* a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -* License for the specific language governing permissions and limitations -* under the License. -**/ - -/** - * - */ -package net.floodlightcontroller.counter; - -import java.util.Date; - - - -/** - * This is a simple counter implementation that doesn't support data series. - * The idea is that floodlight only keeps the realtime value for each counter, - * statd, a statistics collection daemon, samples counters at a user-defined interval - * and pushes the values to a database, which keeps time-based data series. - * @author Kanzhe - * - */ -public class SimpleCounter implements ICounter { - - protected CounterValue counter; - protected Date samplingTime; - protected Date startDate; - - /** - * Factory method to create a new counter instance. - * - * @param startDate - * @return - */ - public static ICounter createCounter(Date startDate, CounterValue.CounterType type) { - SimpleCounter cc = new SimpleCounter(startDate, type); - return cc; - } - - /** - * Factory method to create a copy of a counter instance. - * - * @param startDate - * @return - */ - public static ICounter createCounter(ICounter copy) { - if (copy == null || - copy.getCounterDate() == null || - copy.getCounterValue() == null) { - return null; - } - - SimpleCounter cc = new SimpleCounter(copy.getCounterDate(), - copy.getCounterValue().getType()); - cc.setCounter(copy.getCounterDate(), copy.getCounterValue()); - return cc; - } - - /** - * Protected constructor - use createCounter factory method instead - * @param startDate - */ - protected SimpleCounter(Date startDate, CounterValue.CounterType type) { - init(startDate, type); - } - - protected void init(Date startDate, CounterValue.CounterType type) { - this.startDate = startDate; - this.samplingTime = new Date(); - this.counter = new CounterValue(type); - } - - /** - * This is the key method that has to be both fast and very thread-safe. - */ - @Override - synchronized public void increment() { - this.increment(new Date(), 1); - } - - @Override - synchronized public void increment(Date d, long delta) { - this.samplingTime = d; - this.counter.increment(delta); - } - - @Override -synchronized public void setCounter(Date d, CounterValue value) { - this.samplingTime = d; - this.counter = value; - } - - /** - * This is the method to retrieve the current value. - */ - @Override - synchronized public CounterValue getCounterValue() { - return this.counter; - } - - /** - * This is the method to retrieve the last sampling time. - */ - @Override - synchronized public Date getCounterDate() { - return this.samplingTime; - } - - /** - * Reset value. - */ - @Override - synchronized public void reset(Date startDate) { - init(startDate, this.counter.getType()); - } - -} diff --git a/src/main/java/net/floodlightcontroller/counter/TypeAliases.java b/src/main/java/net/floodlightcontroller/counter/TypeAliases.java deleted file mode 100644 index b88c50256be82274bd1cb4aaf68392ed315a87b3..0000000000000000000000000000000000000000 --- a/src/main/java/net/floodlightcontroller/counter/TypeAliases.java +++ /dev/null @@ -1,206 +0,0 @@ -/** - * 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.counter; - -import java.util.HashMap; -import java.util.Map; - -/** - * Class to contain some statically initialized data - * @author readams - * - */ -public class TypeAliases { - protected static final Map<String,String> l3TypeAliasMap = - new HashMap<String, String>(); - static { - l3TypeAliasMap.put("0599", "L3_V1Ether"); - l3TypeAliasMap.put("0800", "L3_IPv4"); - l3TypeAliasMap.put("0806", "L3_ARP"); - l3TypeAliasMap.put("8035", "L3_RARP"); - l3TypeAliasMap.put("809b", "L3_AppleTalk"); - l3TypeAliasMap.put("80f3", "L3_AARP"); - l3TypeAliasMap.put("8100", "L3_802_1Q"); - l3TypeAliasMap.put("8137", "L3_Novell_IPX"); - l3TypeAliasMap.put("8138", "L3_Novell"); - l3TypeAliasMap.put("86dd", "L3_IPv6"); - l3TypeAliasMap.put("8847", "L3_MPLS_uni"); - l3TypeAliasMap.put("8848", "L3_MPLS_multi"); - l3TypeAliasMap.put("8863", "L3_PPPoE_DS"); - l3TypeAliasMap.put("8864", "L3_PPPoE_SS"); - l3TypeAliasMap.put("886f", "L3_MSFT_NLB"); - l3TypeAliasMap.put("8870", "L3_Jumbo"); - l3TypeAliasMap.put("889a", "L3_HyperSCSI"); - l3TypeAliasMap.put("88a2", "L3_ATA_Ethernet"); - l3TypeAliasMap.put("88a4", "L3_EtherCAT"); - l3TypeAliasMap.put("88a8", "L3_802_1ad"); - l3TypeAliasMap.put("88ab", "L3_Ether_Powerlink"); - l3TypeAliasMap.put("88cc", "L3_LLDP"); - l3TypeAliasMap.put("88cd", "L3_SERCOS_III"); - l3TypeAliasMap.put("88e5", "L3_802_1ae"); - l3TypeAliasMap.put("88f7", "L3_IEEE_1588"); - l3TypeAliasMap.put("8902", "L3_802_1ag_CFM"); - l3TypeAliasMap.put("8906", "L3_FCoE"); - l3TypeAliasMap.put("9000", "L3_Loop"); - l3TypeAliasMap.put("9100", "L3_Q_in_Q"); - l3TypeAliasMap.put("cafe", "L3_LLT"); - } - - protected static final Map<String,String> l4TypeAliasMap = - new HashMap<String, String>(); - static { - l4TypeAliasMap.put("00", "L4_HOPOPT"); - l4TypeAliasMap.put("01", "L4_ICMP"); - l4TypeAliasMap.put("02", "L4_IGAP_IGMP_RGMP"); - l4TypeAliasMap.put("03", "L4_GGP"); - l4TypeAliasMap.put("04", "L4_IP"); - l4TypeAliasMap.put("05", "L4_ST"); - l4TypeAliasMap.put("06", "L4_TCP"); - l4TypeAliasMap.put("07", "L4_UCL"); - l4TypeAliasMap.put("08", "L4_EGP"); - l4TypeAliasMap.put("09", "L4_IGRP"); - l4TypeAliasMap.put("0a", "L4_BBN"); - l4TypeAliasMap.put("0b", "L4_NVP"); - l4TypeAliasMap.put("0c", "L4_PUP"); - l4TypeAliasMap.put("0d", "L4_ARGUS"); - l4TypeAliasMap.put("0e", "L4_EMCON"); - l4TypeAliasMap.put("0f", "L4_XNET"); - l4TypeAliasMap.put("10", "L4_Chaos"); - l4TypeAliasMap.put("11", "L4_UDP"); - l4TypeAliasMap.put("12", "L4_TMux"); - l4TypeAliasMap.put("13", "L4_DCN"); - l4TypeAliasMap.put("14", "L4_HMP"); - l4TypeAliasMap.put("15", "L4_Packet_Radio"); - l4TypeAliasMap.put("16", "L4_XEROX_NS_IDP"); - l4TypeAliasMap.put("17", "L4_Trunk_1"); - l4TypeAliasMap.put("18", "L4_Trunk_2"); - l4TypeAliasMap.put("19", "L4_Leaf_1"); - l4TypeAliasMap.put("1a", "L4_Leaf_2"); - l4TypeAliasMap.put("1b", "L4_RDP"); - l4TypeAliasMap.put("1c", "L4_IRTP"); - l4TypeAliasMap.put("1d", "L4_ISO_TP4"); - l4TypeAliasMap.put("1e", "L4_NETBLT"); - l4TypeAliasMap.put("1f", "L4_MFE"); - l4TypeAliasMap.put("20", "L4_MERIT"); - l4TypeAliasMap.put("21", "L4_DCCP"); - l4TypeAliasMap.put("22", "L4_Third_Party_Connect"); - l4TypeAliasMap.put("23", "L4_IDPR"); - l4TypeAliasMap.put("24", "L4_XTP"); - l4TypeAliasMap.put("25", "L4_Datagram_Delivery"); - l4TypeAliasMap.put("26", "L4_IDPR"); - l4TypeAliasMap.put("27", "L4_TP"); - l4TypeAliasMap.put("28", "L4_ILTP"); - l4TypeAliasMap.put("29", "L4_IPv6_over_IPv4"); - l4TypeAliasMap.put("2a", "L4_SDRP"); - l4TypeAliasMap.put("2b", "L4_IPv6_RH"); - l4TypeAliasMap.put("2c", "L4_IPv6_FH"); - l4TypeAliasMap.put("2d", "L4_IDRP"); - l4TypeAliasMap.put("2e", "L4_RSVP"); - l4TypeAliasMap.put("2f", "L4_GRE"); - l4TypeAliasMap.put("30", "L4_DSR"); - l4TypeAliasMap.put("31", "L4_BNA"); - l4TypeAliasMap.put("32", "L4_ESP"); - l4TypeAliasMap.put("33", "L4_AH"); - l4TypeAliasMap.put("34", "L4_I_NLSP"); - l4TypeAliasMap.put("35", "L4_SWIPE"); - l4TypeAliasMap.put("36", "L4_NARP"); - l4TypeAliasMap.put("37", "L4_Minimal_Encapsulation"); - l4TypeAliasMap.put("38", "L4_TLSP"); - l4TypeAliasMap.put("39", "L4_SKIP"); - l4TypeAliasMap.put("3a", "L4_ICMPv6"); - l4TypeAliasMap.put("3b", "L4_IPv6_No_Next_Header"); - l4TypeAliasMap.put("3c", "L4_IPv6_Destination_Options"); - l4TypeAliasMap.put("3d", "L4_Any_host_IP"); - l4TypeAliasMap.put("3e", "L4_CFTP"); - l4TypeAliasMap.put("3f", "L4_Any_local"); - l4TypeAliasMap.put("40", "L4_SATNET"); - l4TypeAliasMap.put("41", "L4_Kryptolan"); - l4TypeAliasMap.put("42", "L4_MIT_RVDP"); - l4TypeAliasMap.put("43", "L4_Internet_Pluribus"); - l4TypeAliasMap.put("44", "L4_Distributed_FS"); - l4TypeAliasMap.put("45", "L4_SATNET"); - l4TypeAliasMap.put("46", "L4_VISA"); - l4TypeAliasMap.put("47", "L4_IP_Core"); - l4TypeAliasMap.put("4a", "L4_Wang_Span"); - l4TypeAliasMap.put("4b", "L4_Packet_Video"); - l4TypeAliasMap.put("4c", "L4_Backroom_SATNET"); - l4TypeAliasMap.put("4d", "L4_SUN_ND"); - l4TypeAliasMap.put("4e", "L4_WIDEBAND_Monitoring"); - l4TypeAliasMap.put("4f", "L4_WIDEBAND_EXPAK"); - l4TypeAliasMap.put("50", "L4_ISO_IP"); - l4TypeAliasMap.put("51", "L4_VMTP"); - l4TypeAliasMap.put("52", "L4_SECURE_VMTP"); - l4TypeAliasMap.put("53", "L4_VINES"); - l4TypeAliasMap.put("54", "L4_TTP"); - l4TypeAliasMap.put("55", "L4_NSFNET_IGP"); - l4TypeAliasMap.put("56", "L4_Dissimilar_GP"); - l4TypeAliasMap.put("57", "L4_TCF"); - l4TypeAliasMap.put("58", "L4_EIGRP"); - l4TypeAliasMap.put("59", "L4_OSPF"); - l4TypeAliasMap.put("5a", "L4_Sprite_RPC"); - l4TypeAliasMap.put("5b", "L4_Locus_ARP"); - l4TypeAliasMap.put("5c", "L4_MTP"); - l4TypeAliasMap.put("5d", "L4_AX"); - l4TypeAliasMap.put("5e", "L4_IP_within_IP"); - l4TypeAliasMap.put("5f", "L4_Mobile_ICP"); - l4TypeAliasMap.put("61", "L4_EtherIP"); - l4TypeAliasMap.put("62", "L4_Encapsulation_Header"); - l4TypeAliasMap.put("64", "L4_GMTP"); - l4TypeAliasMap.put("65", "L4_IFMP"); - l4TypeAliasMap.put("66", "L4_PNNI"); - l4TypeAliasMap.put("67", "L4_PIM"); - l4TypeAliasMap.put("68", "L4_ARIS"); - l4TypeAliasMap.put("69", "L4_SCPS"); - l4TypeAliasMap.put("6a", "L4_QNX"); - l4TypeAliasMap.put("6b", "L4_Active_Networks"); - l4TypeAliasMap.put("6c", "L4_IPPCP"); - l4TypeAliasMap.put("6d", "L4_SNP"); - l4TypeAliasMap.put("6e", "L4_Compaq_Peer_Protocol"); - l4TypeAliasMap.put("6f", "L4_IPX_in_IP"); - l4TypeAliasMap.put("70", "L4_VRRP"); - l4TypeAliasMap.put("71", "L4_PGM"); - l4TypeAliasMap.put("72", "L4_0_hop"); - l4TypeAliasMap.put("73", "L4_L2TP"); - l4TypeAliasMap.put("74", "L4_DDX"); - l4TypeAliasMap.put("75", "L4_IATP"); - l4TypeAliasMap.put("76", "L4_ST"); - l4TypeAliasMap.put("77", "L4_SRP"); - l4TypeAliasMap.put("78", "L4_UTI"); - l4TypeAliasMap.put("79", "L4_SMP"); - l4TypeAliasMap.put("7a", "L4_SM"); - l4TypeAliasMap.put("7b", "L4_PTP"); - l4TypeAliasMap.put("7c", "L4_ISIS"); - l4TypeAliasMap.put("7d", "L4_FIRE"); - l4TypeAliasMap.put("7e", "L4_CRTP"); - l4TypeAliasMap.put("7f", "L4_CRUDP"); - l4TypeAliasMap.put("80", "L4_SSCOPMCE"); - l4TypeAliasMap.put("81", "L4_IPLT"); - l4TypeAliasMap.put("82", "L4_SPS"); - l4TypeAliasMap.put("83", "L4_PIPE"); - l4TypeAliasMap.put("84", "L4_SCTP"); - l4TypeAliasMap.put("85", "L4_Fibre_Channel"); - l4TypeAliasMap.put("86", "L4_RSVP_E2E_IGNORE"); - l4TypeAliasMap.put("87", "L4_Mobility_Header"); - l4TypeAliasMap.put("88", "L4_UDP_Lite"); - l4TypeAliasMap.put("89", "L4_MPLS"); - l4TypeAliasMap.put("8a", "L4_MANET"); - l4TypeAliasMap.put("8b", "L4_HIP"); - l4TypeAliasMap.put("8c", "L4_Shim6"); - l4TypeAliasMap.put("8d", "L4_WESP"); - l4TypeAliasMap.put("8e", "L4_ROHC"); - } -} diff --git a/src/main/java/net/floodlightcontroller/debugcounter/CounterNode.java b/src/main/java/net/floodlightcontroller/debugcounter/CounterNode.java new file mode 100644 index 0000000000000000000000000000000000000000..f44ea7a17b06556cd60e360c254ba9c1e1003279 --- /dev/null +++ b/src/main/java/net/floodlightcontroller/debugcounter/CounterNode.java @@ -0,0 +1,334 @@ +package net.floodlightcontroller.debugcounter; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.TreeMap; +import java.util.regex.Pattern; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableList; + +/** + * A node in the counter hierarchy tree. We use a single tree and "merge" the + * counter modules into this single tree. + * + * <li> Adding modules or counters to the tree must happen at the root of the tree. + * <li> The root and the first level of the tree (root and module) are a shim + * layers that con't have an actual counter value. We represent this with a + * null counter. + * + * @author gregor + */ +class CounterNode implements Iterable<DebugCounterImpl> { + private static final String QUOTED_SEP = Pattern.quote("/"); + + /** path/hierarchy of this counter without leading /. An empty string + * represents the root. A string without a / is a module name + */ + private final String hierarchy; + /** + * The path/hierarchy elements split into a list + */ + private final List<String> hierarchyElements; + /** + * The actual counter instance for this node. Can be null for + * root level and module level. + */ + private final DebugCounterImpl counter; + private final TreeMap<String, CounterNode> children = new TreeMap<>(); + + /** + * convert module name and counter hierarchy into list of + * hierarchy elements + * @param moduleName + * @param counterHierarchy + * @return + */ + static List<String> getHierarchyElements(String moduleName, String counterHierarchy) { + DebugCounterServiceImpl.verifyModuleNameSanity(moduleName); + List<String> ret = new ArrayList<>(); + ret.add(moduleName); + if (counterHierarchy == null || counterHierarchy.isEmpty()) { + return ret; + } + for (String element : counterHierarchy.split(QUOTED_SEP)) { + ret.add(element); + } + return ret; + } + + private CounterNode(List<String> hierarchyElements, DebugCounterImpl counter) { + super(); + this.hierarchyElements = ImmutableList.copyOf(hierarchyElements); + this.hierarchy = Joiner.on("/").join(hierarchyElements); + this.counter = counter; + } + + /** + * Create a new counter hierarchy tree and return the root + * @return + */ + public static CounterNode newTree() { + return new CounterNode(ImmutableList.<String>of(), null); + } + + /** verify that this node is the root */ + private void verifyIsRoot() { + if (hierarchyElements.size() != 0) { + throw new IllegalStateException("This is not the root. Can " + + "only call addCounter() on the root node. Current node: " + + hierarchy); + } + } + + /** + * return the full hierarchy as string, including module name + */ + @Nonnull + String getHierarchy() { + return hierarchy; + } + + /** + * @return the list of hierarchy elements for this node + */ + @Nonnull + List<String> getHierarchyElements() { + return hierarchyElements; + } + + /** + * @return this node's counters + */ + @Nullable + DebugCounterImpl getCounter() { + return counter; + } + + /** + * Reset this counter all counter below it in the hierarchy + */ + void resetHierarchy() { + for (DebugCounterImpl cur: this) { + cur.reset(); + } + } + + /** + * Return an Iterable over all DebugCounterImpls at and below this + * node. Note we return an Iterable<DebugCounterImpls> not + * Iterable<IDebugCounter> on purpose. + * @return + */ + Iterable<DebugCounterImpl> getCountersInHierarchy() { + return this; + } + + + /** + * Lookup the CounterNode identified by the hieraryElements if it exists. + * Returns null if no such CounterNode is found. Must only be called on + * the root of the tree. + * @param hierarchyElements + * @return + */ + CounterNode lookup(List<String> hierarchyElements) { + CounterNode cur = this; + for (String element: hierarchyElements) { + cur = cur.children.get(element); + if (cur == null) { + break; + } + } + return cur; + } + + /** + * Add the given moduleName to the tree. Can only be called on the root. + * If the module already exists, the all counters of the module will + * be reset. + * @param moduleName + * @return true if the module was newly added, false if the module already + * existed + */ + boolean addModule(@Nonnull String moduleName) { + verifyIsRoot(); + if (children.containsKey(moduleName)) { + children.get(moduleName).resetHierarchy(); + return false; + } else { + CounterNode newNode = + new CounterNode(ImmutableList.of(moduleName), null); + children.put(moduleName, newNode); + return true; + } + } + + /** + * Add the given Counter to the hierarchy. If the counterHierarcy already + * exists, reset the hierarchy + * @param counter + * @return null if the counterHierarchy is newly registered, otherwise + * returns the already registered DebugCounterImpl instance + * @throws IllegalArgumentException if the parent of the counter does not + * yet exist + */ + @Nullable + DebugCounterImpl addCounter(@Nonnull DebugCounterImpl counter) { + verifyIsRoot(); + ArrayList<String> path = new ArrayList<>(); + path.add(counter.getModuleName()); + for (String element: counter.getCounterHierarchy().split(QUOTED_SEP)) { + path.add(element); + } + String newCounterName = path.get(path.size()-1); + + CounterNode parent = lookup(path.subList(0, path.size()-1)); + if (parent == null) { + throw new IllegalArgumentException("Missing hierarchy level for " + + "counter: " + counter.getModuleName() + " " + + counter.getCounterHierarchy()); + } + if (parent.children.containsKey(newCounterName)) { + CounterNode old = parent.children.get(newCounterName); + // FIXME: we should check that old and new has the same + // description and meta-data, otherwise we should probably thrown + // and exception and refuse the operation. + old.resetHierarchy(); + return old.counter; + } else { + CounterNode newNode = new CounterNode(path, counter); + parent.children.put(newCounterName, newNode); + return counter; + } + } + + + /** + * Iterator over the counters in the counter hierarchy. + * Iteration order is a pre-order tree walk. Children of a node are + * visited in sorted order. + * @author gregor + */ + private final static class CounterIterator implements Iterator<DebugCounterImpl> { + // NOTE: since some counters + ArrayDeque<CounterNode> stack = new ArrayDeque<>(); + CounterNode curNode = null; + private CounterIterator(CounterNode root) { + stack.push(root); + gotoNextNode(); + } + + private void gotoNextNode() { + while (true) { + curNode = null; + if (stack.isEmpty()) { + break; + } + curNode = stack.pop(); + for (CounterNode child: curNode.children.descendingMap().values()) { + stack.push(child); + } + if (curNode.counter != null) { + break; + } + } + } + + @Override + public boolean hasNext() { + return curNode != null; + } + + @Override + public DebugCounterImpl next() { + if (curNode == null) { + throw new NoSuchElementException(); + } + DebugCounterImpl ret = curNode.counter; + gotoNextNode(); + return ret; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + } + + @Override + public Iterator<DebugCounterImpl> iterator() { + return new CounterIterator(this); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + + ((children == null) ? 0 : children.hashCode()); + result = prime * result + + ((counter == null) ? 0 : counter.hashCode()); + result = prime * result + + ((hierarchy == null) ? 0 : hierarchy.hashCode()); + result = prime + * result + + ((hierarchyElements == null) ? 0 + : hierarchyElements.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + CounterNode other = (CounterNode) obj; + if (children == null) { + if (other.children != null) return false; + } else if (!children.equals(other.children)) return false; + if (counter == null) { + if (other.counter != null) return false; + } else if (!counter.equals(other.counter)) return false; + if (hierarchy == null) { + if (other.hierarchy != null) return false; + } else if (!hierarchy.equals(other.hierarchy)) return false; + if (hierarchyElements == null) { + if (other.hierarchyElements != null) return false; + } else if (!hierarchyElements.equals(other.hierarchyElements)) + return false; + return true; + } + + @Override + public String toString() { + return toString(0); + } + + public String toString(int indent) { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < indent; i++) { + builder.append(" "); + } + builder.append("hierarchy="); + builder.append(hierarchy); + builder.append(", counter="); + builder.append(counter); + builder.append(", children="); + builder.append(children.keySet()); + builder.append("\n"); + for (CounterNode child: children.values()) { + builder.append(child.toString(indent + 3)); + } + return builder.toString(); + } + + + +} diff --git a/src/main/java/net/floodlightcontroller/debugcounter/DebugCounter.java b/src/main/java/net/floodlightcontroller/debugcounter/DebugCounter.java deleted file mode 100644 index 5bb1ce16757a4faa363205c346887ad1fbbc0d3e..0000000000000000000000000000000000000000 --- a/src/main/java/net/floodlightcontroller/debugcounter/DebugCounter.java +++ /dev/null @@ -1,741 +0,0 @@ -package net.floodlightcontroller.debugcounter; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.List; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicLong; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.common.collect.Sets; - -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.debugcounter.web.DebugCounterRoutable; -import net.floodlightcontroller.restserver.IRestApiService; - -/** - * This class implements a central store for all counters used for debugging the - * system. For counters based on traffic-type, see ICounterStoreService. - * - * @author Saurav - */ -public class DebugCounter implements IFloodlightModule, IDebugCounterService { - protected static Logger log = LoggerFactory.getLogger(DebugCounter.class); - - /** - * registered counters need a counter id - */ - protected AtomicInteger counterIdCounter = new AtomicInteger(); - - /** - * The counter value - */ - protected class MutableLong { - long value = 0; - public void increment() { value += 1; } - public void increment(long incr) { value += incr; } - public long get() { return value; } - public void set(long val) { value = val; } - } - - /** - * protected class to store counter information - */ - public static class CounterInfo { - String moduleCounterHierarchy; - String counterDesc; - CounterType ctype; - String moduleName; - String counterHierarchy; - int counterId; - boolean enabled; - String[] metaData; - - public CounterInfo(int counterId, boolean enabled, - String moduleName, String counterHierarchy, - String desc, CounterType ctype, String... metaData) { - this.moduleCounterHierarchy = moduleName + "/" + counterHierarchy; - this.moduleName = moduleName; - this.counterHierarchy = counterHierarchy; - this.counterDesc = desc; - this.ctype = ctype; - this.counterId = counterId; - this.enabled = enabled; - this.metaData = metaData; - } - - public String getModuleCounterHierarchy() { return moduleCounterHierarchy; } - public String getCounterDesc() { return counterDesc; } - public CounterType getCtype() { return ctype; } - public String getModuleName() { return moduleName; } - public String getCounterHierarchy() { return counterHierarchy; } - public int getCounterId() { return counterId; } - public boolean isEnabled() { return enabled; } - public String[] getMetaData() { return metaData; } - } - - //****************** - // Global stores - //****************** - - /** - * Counter info for a debug counter - */ - public class DebugCounterInfo { - CounterInfo cinfo; - AtomicLong cvalue; - - public DebugCounterInfo(CounterInfo cinfo) { - this.cinfo = cinfo; - this.cvalue = new AtomicLong(); - } - public CounterInfo getCounterInfo() { - return cinfo; - } - public Long getCounterValue() { - return cvalue.get(); - } - } - - /** - * Global debug-counter storage across all threads. These are - * updated from the local per thread counters by the flush counters method. - */ - protected static DebugCounterInfo[] allCounters = - new DebugCounterInfo[MAX_COUNTERS]; - - - /** - * per module counters, indexed by the module name and storing three levels - * of Counter information in the form of CounterIndexStore - */ - protected ConcurrentHashMap<String, ConcurrentHashMap<String, CounterIndexStore>> - moduleCounters = new ConcurrentHashMap<String, - ConcurrentHashMap<String, - CounterIndexStore>>(); - - protected class CounterIndexStore { - int index; - Map<String, CounterIndexStore> nextLevel; - - public CounterIndexStore(int index, Map<String,CounterIndexStore> cis) { - this.index = index; - this.nextLevel = cis; - } - } - - /** - * fast global cache for counter ids that are currently active - */ - protected Set<Integer> currentCounters = Collections.newSetFromMap( - new ConcurrentHashMap<Integer,Boolean>()); - - //****************** - // Thread local stores - //****************** - - /** - * Thread local storage of counter info - */ - protected class LocalCounterInfo { - boolean enabled; - MutableLong cvalue; - - public LocalCounterInfo(boolean enabled) { - this.enabled = enabled; - this.cvalue = new MutableLong(); - } - } - - /** - * Thread local debug counters used for maintaining counters local to a thread. - */ - protected final ThreadLocal<LocalCounterInfo[]> threadlocalCounters = - new ThreadLocal<LocalCounterInfo[]>() { - @Override - protected LocalCounterInfo[] initialValue() { - return new LocalCounterInfo[MAX_COUNTERS]; - } - }; - - /** - * Thread local cache for counter ids that are currently active. - */ - protected final ThreadLocal<Set<Integer>> threadlocalCurrentCounters = - new ThreadLocal<Set<Integer>>() { - @Override - protected Set<Integer> initialValue() { - return new HashSet<Integer>(); - } - }; - - //******************************* - // IDebugCounter - //******************************* - - protected class CounterImpl implements IDebugCounter { - private final int counterId; - - public CounterImpl(int counterId) { - this.counterId = counterId; - } - - @Override - public void updateCounterWithFlush() { - if (!validCounterId()) return; - updateCounter(counterId, 1, true); - } - - @Override - public void updateCounterNoFlush() { - if (!validCounterId()) return; - updateCounter(counterId, 1, false); - } - - @Override - public void updateCounterWithFlush(int incr) { - if (!validCounterId()) return; - updateCounter(counterId, incr, true); - } - - @Override - public void updateCounterNoFlush(int incr) { - if (!validCounterId()) return; - updateCounter(counterId, incr, false); - } - - @Override - public long getCounterValue() { - if (!validCounterId()) return -1; - return allCounters[counterId].cvalue.get(); - } - - private boolean validCounterId() { - if (counterId < 0 || counterId >= MAX_COUNTERS) { - log.error("Invalid counterId invoked"); - return false; - } - return true; - } - - } - - //******************************* - // IDebugCounterService - //******************************* - - @Override - public IDebugCounter registerCounter(String moduleName, String counterHierarchy, - String counterDescription, CounterType counterType, - String... metaData) - throws MaxCountersRegistered, MaxHierarchyRegistered, - MissingHierarchicalLevel { - // check if counter already exists - if (!moduleCounters.containsKey(moduleName)) { - moduleCounters.putIfAbsent(moduleName, - new ConcurrentHashMap<String, CounterIndexStore>()); - } - RetCtrInfo rci = getCounterId(moduleName, counterHierarchy); - if (rci.allLevelsFound) { - // counter exists - log.info("Counter exists for {}/{} -- resetting counters", moduleName, - counterHierarchy); - resetCounterHierarchy(moduleName, counterHierarchy); - return new CounterImpl(rci.ctrIds[rci.foundUptoLevel-1]); - } - // check for validity of counter - if (rci.levels.length > MAX_HIERARCHY) { - String err = "Registry of counterHierarchy " + counterHierarchy + - " exceeds max hierachy " + MAX_HIERARCHY + ".. aborting"; - throw new MaxHierarchyRegistered(err); - } - if (rci.foundUptoLevel < rci.levels.length-1) { - String needToRegister = ""; - for (int i=0; i<=rci.foundUptoLevel; i++) { - needToRegister += rci.levels[i]; - } - String err = "Attempting to register hierarchical counterHierarchy " + - counterHierarchy + " but parts of hierarchy missing. " + - "Please register " + needToRegister + " first"; - throw new MissingHierarchicalLevel(err); - } - - // get a new counter id - int counterId = counterIdCounter.getAndIncrement(); - if (counterId >= MAX_COUNTERS) { - throw new MaxCountersRegistered("max counters reached"); - } - // create storage for counter - boolean enabled = (counterType == CounterType.ALWAYS_COUNT) ? true : false; - CounterInfo ci = new CounterInfo(counterId, enabled, moduleName, - counterHierarchy, counterDescription, - counterType, metaData); - allCounters[counterId] = new DebugCounterInfo(ci); - - // account for the new counter in the module counter hierarchy - addToModuleCounterHierarchy(moduleName, counterId, rci); - - // finally add to active counters - if (enabled) { - currentCounters.add(counterId); - } - return new CounterImpl(counterId); - } - - private void updateCounter(int counterId, int incr, boolean flushNow) { - if (counterId < 0 || counterId >= MAX_COUNTERS) return; - - LocalCounterInfo[] thiscounters = this.threadlocalCounters.get(); - if (thiscounters[counterId] == null) { - // seeing this counter for the first time in this thread - create local - // store by consulting global store - DebugCounterInfo dc = allCounters[counterId]; - if (dc != null) { - thiscounters[counterId] = new LocalCounterInfo(dc.cinfo.enabled); - if (dc.cinfo.enabled) { - Set<Integer> thisset = this.threadlocalCurrentCounters.get(); - thisset.add(counterId); - } - } else { - log.error("updateCounter seen locally for counter {} but no global" - + "storage exists for it yet .. not updating", counterId); - return; - } - } - - // update local store if enabled locally for updating - LocalCounterInfo lc = thiscounters[counterId]; - if (lc.enabled) { - lc.cvalue.increment(incr); - if (flushNow) { - DebugCounterInfo dc = allCounters[counterId]; - if (dc.cinfo.enabled) { - // globally enabled - flush now - dc.cvalue.addAndGet(lc.cvalue.get()); - lc.cvalue.set(0); - } else { - // global counter is disabled - don't flush, disable locally - lc.enabled = false; - Set<Integer> thisset = this.threadlocalCurrentCounters.get(); - thisset.remove(counterId); - } - } - } - } - - @Override - public void flushCounters() { - LocalCounterInfo[] thiscounters = this.threadlocalCounters.get(); - Set<Integer> thisset = this.threadlocalCurrentCounters.get(); - ArrayList<Integer> temp = new ArrayList<Integer>(); - - for (int counterId : thisset) { - LocalCounterInfo lc = thiscounters[counterId]; - if (lc.cvalue.get() > 0) { - DebugCounterInfo dc = allCounters[counterId]; - if (dc.cinfo.enabled) { - // globally enabled - flush now - dc.cvalue.addAndGet(lc.cvalue.get()); - lc.cvalue.set(0); - } else { - // global counter is disabled - don't flush, disable locally - lc.enabled = false; - temp.add(counterId); - } - } - } - for (int cId : temp) { - thisset.remove(cId); - } - - // At this point it is possible that the thread-local set does not - // include a counter that has been enabled and is present in the global set. - // We need to sync thread-local currently enabled set of counterIds with - // the global set. - Sets.SetView<Integer> sv = Sets.difference(currentCounters, thisset); - for (int counterId : sv) { - if (thiscounters[counterId] != null) { - thiscounters[counterId].enabled = true; - thisset.add(counterId); - } - } - } - - @Override - public void resetCounterHierarchy(String moduleName, String counterHierarchy) { - RetCtrInfo rci = getCounterId(moduleName, counterHierarchy); - if (!rci.allLevelsFound) { - String missing = rci.levels[rci.foundUptoLevel]; - log.error("Cannot reset counter hierarchy - missing counter {}", missing); - return; - } - // reset at this level - allCounters[rci.ctrIds[rci.foundUptoLevel-1]].cvalue.set(0); - // reset all levels below - ArrayList<Integer> resetIds = getHierarchyBelow(moduleName, rci); - for (int index : resetIds) { - allCounters[index].cvalue.set(0); - } - } - - @Override - public void resetAllCounters() { - RetCtrInfo rci = new RetCtrInfo(); - rci.levels = "".split("/"); - for (String moduleName : moduleCounters.keySet()) { - ArrayList<Integer> resetIds = getHierarchyBelow(moduleName, rci); - for (int index : resetIds) { - allCounters[index].cvalue.set(0); - } - } - } - - @Override - public void resetAllModuleCounters(String moduleName) { - Map<String, CounterIndexStore> target = moduleCounters.get(moduleName); - RetCtrInfo rci = new RetCtrInfo(); - rci.levels = "".split("/"); - - if (target != null) { - ArrayList<Integer> resetIds = getHierarchyBelow(moduleName, rci); - for (int index : resetIds) { - allCounters[index].cvalue.set(0); - } - } else { - if (log.isDebugEnabled()) - log.debug("No module found with name {}", moduleName); - } - } - - @Override - public void enableCtrOnDemand(String moduleName, String counterHierarchy) { - RetCtrInfo rci = getCounterId(moduleName, counterHierarchy); - if (!rci.allLevelsFound) { - String missing = rci.levels[rci.foundUptoLevel]; - log.error("Cannot enable counter - counter not found {}", missing); - return; - } - // enable specific counter - DebugCounterInfo dc = allCounters[rci.ctrIds[rci.foundUptoLevel-1]]; - dc.cinfo.enabled = true; - currentCounters.add(dc.cinfo.counterId); - } - - @Override - public void disableCtrOnDemand(String moduleName, String counterHierarchy) { - RetCtrInfo rci = getCounterId(moduleName, counterHierarchy); - if (!rci.allLevelsFound) { - String missing = rci.levels[rci.foundUptoLevel]; - log.error("Cannot disable counter - counter not found {}", missing); - return; - } - // disable specific counter - DebugCounterInfo dc = allCounters[rci.ctrIds[rci.foundUptoLevel-1]]; - if (dc.cinfo.ctype == CounterType.COUNT_ON_DEMAND) { - dc.cinfo.enabled = false; - dc.cvalue.set(0); - currentCounters.remove(dc.cinfo.counterId); - } - } - - @Override - public List<DebugCounterInfo> getCounterHierarchy(String moduleName, - String counterHierarchy) { - RetCtrInfo rci = getCounterId(moduleName, counterHierarchy); - if (!rci.allLevelsFound) { - String missing = rci.levels[rci.foundUptoLevel]; - log.error("Cannot fetch counter - counter not found {}", missing); - return Collections.emptyList(); - } - ArrayList<DebugCounterInfo> dcilist = new ArrayList<DebugCounterInfo>(); - // get counter and all below it - DebugCounterInfo dc = allCounters[rci.ctrIds[rci.foundUptoLevel-1]]; - dcilist.add(dc); - ArrayList<Integer> belowIds = getHierarchyBelow(moduleName, rci); - for (int index : belowIds) { - dcilist.add(allCounters[index]); - } - return dcilist; - } - - @Override - public List<DebugCounterInfo> getAllCounterValues() { - List<DebugCounterInfo> dcilist = new ArrayList<DebugCounterInfo>(); - RetCtrInfo rci = new RetCtrInfo(); - rci.levels = "".split("/"); - - for (String moduleName : moduleCounters.keySet()) { - ArrayList<Integer> resetIds = getHierarchyBelow(moduleName, rci); - for (int index : resetIds) { - dcilist.add(allCounters[index]); - } - } - return dcilist; - } - - @Override - public List<DebugCounterInfo> getModuleCounterValues(String moduleName) { - List<DebugCounterInfo> dcilist = new ArrayList<DebugCounterInfo>(); - RetCtrInfo rci = new RetCtrInfo(); - rci.levels = "".split("/"); - - if (moduleCounters.containsKey(moduleName)) { - ArrayList<Integer> resetIds = getHierarchyBelow(moduleName, rci); - for (int index : resetIds) { - dcilist.add(allCounters[index]); - } - } - return dcilist; - } - - @Override - public boolean containsModuleCounterHierarchy(String moduleName, - String counterHierarchy) { - if (!moduleCounters.containsKey(moduleName)) return false; - RetCtrInfo rci = getCounterId(moduleName, counterHierarchy); - return rci.allLevelsFound; - } - - @Override - public boolean containsModuleName(String moduleName) { - return (moduleCounters.containsKey(moduleName)) ? true : false; - } - - @Override - public List<String> getModuleList() { - List<String> retval = new ArrayList<String>(); - retval.addAll(moduleCounters.keySet()); - return retval; - } - - @Override - public List<String> getModuleCounterList(String moduleName) { - if (!moduleCounters.containsKey(moduleName)) - return Collections.emptyList(); - - List<String> retval = new ArrayList<String>(); - RetCtrInfo rci = new RetCtrInfo(); - rci.levels = "".split("/"); - - ArrayList<Integer> cids = getHierarchyBelow(moduleName, rci); - for (int index : cids) { - retval.add(allCounters[index].cinfo.counterHierarchy); - } - return retval; - } - - //******************************* - // Internal Methods - //******************************* - - protected class RetCtrInfo { - boolean allLevelsFound; // counter indices found all the way down the hierarchy - boolean hierarchical; // true if counterHierarchy is hierarchical - int foundUptoLevel; - int[] ctrIds; - String[] levels; - - public RetCtrInfo() { - ctrIds = new int[MAX_HIERARCHY]; - for (int i=0; i<MAX_HIERARCHY; i++) { - ctrIds[i] = -1; - } - } - - @Override - public boolean equals(Object oth) { - if (!(oth instanceof RetCtrInfo)) return false; - RetCtrInfo other = (RetCtrInfo)oth; - if (other.allLevelsFound != this.allLevelsFound) return false; - if (other.hierarchical != this.hierarchical) return false; - if (other.foundUptoLevel != this.foundUptoLevel) return false; - if (!Arrays.equals(other.ctrIds, this.ctrIds)) return false; - if (!Arrays.equals(other.levels, this.levels)) return false; - return true; - } - - } - - protected RetCtrInfo getCounterId(String moduleName, String counterHierarchy) { - RetCtrInfo rci = new RetCtrInfo(); - Map<String, CounterIndexStore> templevel = moduleCounters.get(moduleName); - rci.levels = counterHierarchy.split("/"); - if (rci.levels.length > 1) rci.hierarchical = true; - if (templevel == null) { - log.error("moduleName {} does not exist in debugCounters", moduleName); - return rci; - } - - /* - if (rci.levels.length > MAX_HIERARCHY) { - // chop off all array elems greater that MAX_HIERARCHY - String[] temp = new String[MAX_HIERARCHY]; - System.arraycopy(rci.levels, 0, temp, 0, MAX_HIERARCHY); - rci.levels = temp; - } - */ - for (int i=0; i<rci.levels.length; i++) { - if (templevel != null) { - CounterIndexStore cis = templevel.get(rci.levels[i]) ; - if (cis == null) { - // could not find counterHierarchy part at this level - break; - } else { - rci.ctrIds[i] = cis.index; - templevel = cis.nextLevel; - rci.foundUptoLevel++; - if (i == rci.levels.length-1) { - rci.allLevelsFound = true; - } - } - } else { - // there are no more levels, which means that some part of the - // counterHierarchy has no corresponding map - break; - } - } - return rci; - } - - protected void addToModuleCounterHierarchy(String moduleName, int counterId, - RetCtrInfo rci) { - Map<String, CounterIndexStore> target = moduleCounters.get(moduleName); - if (target == null) return; - CounterIndexStore cis = null; - - for (int i=0; i<rci.foundUptoLevel; i++) { - cis = target.get(rci.levels[i]); - target = cis.nextLevel; - } - if (cis != null) { - if (cis.nextLevel == null) - cis.nextLevel = new ConcurrentHashMap<String, CounterIndexStore>(); - cis.nextLevel.put(rci.levels[rci.foundUptoLevel], - new CounterIndexStore(counterId, null)); - } else { - target.put(rci.levels[rci.foundUptoLevel], - new CounterIndexStore(counterId, null)); - } - } - - // given a partial hierarchical counter, return the rest of the hierarchy - protected ArrayList<Integer> getHierarchyBelow(String moduleName, RetCtrInfo rci) { - Map<String, CounterIndexStore> target = moduleCounters.get(moduleName); - CounterIndexStore cis = null; - ArrayList<Integer> retval = new ArrayList<Integer>(); - if (target == null) return retval; - - // get to the level given - for (int i=0; i<rci.foundUptoLevel; i++) { - cis = target.get(rci.levels[i]); - target = cis.nextLevel; - } - - if (target == null || rci.foundUptoLevel == MAX_HIERARCHY) { - // no more levels - return retval; - } else { - // recursively get all ids - getIdsAtLevel(target, retval, rci.foundUptoLevel+1); - } - - return retval; - } - - protected void getIdsAtLevel(Map<String, CounterIndexStore> hcy, - ArrayList<Integer> retval, int level) { - if (level > MAX_HIERARCHY) return; - if (hcy == null || retval == null) return; - - // Can return the counter names as well but for now ids are enough. - for (CounterIndexStore cistemp : hcy.values()) { - retval.add(cistemp.index); // value at this level - if (cistemp.nextLevel != null) { - getIdsAtLevel(cistemp.nextLevel, retval, level+1); - } - } - } - - protected void printAllCounterIds() { - log.info("<moduleCounterHierarchy>"); - Set<String> keys = moduleCounters.keySet(); - for (String key : keys) { - log.info("ModuleName: {}", key); - Map<String, CounterIndexStore> lev1 = moduleCounters.get(key); - for (String key1 : lev1.keySet()) { - CounterIndexStore cis1 = lev1.get(key1); - log.info(" L1 {}:{}", key1, new Object[] {cis1.index, cis1.nextLevel}); - if (cis1.nextLevel != null) { - Map<String, CounterIndexStore> lev2 = cis1.nextLevel; - for (String key2 : lev2.keySet()) { - CounterIndexStore cis2 = lev2.get(key2); - log.info(" L2 {}:{}", key2, new Object[] {cis2.index, - cis2.nextLevel}); - if (cis2.nextLevel != null) { - Map<String, CounterIndexStore> lev3 = cis2.nextLevel; - for (String key3 : lev3.keySet()) { - CounterIndexStore cis3 = lev3.get(key3); - log.info(" L3 {}:{}", key3, new Object[] {cis3.index, - cis3.nextLevel}); - } - } - } - } - } - } - log.info("<\\moduleCounterHierarchy>"); - } - - //******************************* - // IFloodlightModule - //******************************* - - @Override - public Collection<Class<? extends IFloodlightService>> getModuleServices() { - Collection<Class<? extends IFloodlightService>> l = - new ArrayList<Class<? extends IFloodlightService>>(); - l.add(IDebugCounterService.class); - return l; - } - - @Override - public Map<Class<? extends IFloodlightService>, IFloodlightService> getServiceImpls() { - Map<Class<? extends IFloodlightService>, IFloodlightService> m = - new HashMap<Class<? extends IFloodlightService>, IFloodlightService>(); - m.put(IDebugCounterService.class, this); - return m; - } - - @Override - public Collection<Class<? extends IFloodlightService>> getModuleDependencies() { - ArrayList<Class<? extends IFloodlightService>> deps = - new ArrayList<Class<? extends IFloodlightService>>(); - deps.add(IRestApiService.class); - return deps; - } - - @Override - public void init(FloodlightModuleContext context) throws FloodlightModuleException { - - } - - @Override - public void startUp(FloodlightModuleContext context) { - IRestApiService restService = - context.getServiceImpl(IRestApiService.class); - restService.addRestletRoutable(new DebugCounterRoutable()); - } - -} diff --git a/src/main/java/net/floodlightcontroller/debugcounter/DebugCounterImpl.java b/src/main/java/net/floodlightcontroller/debugcounter/DebugCounterImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..c0aa316d953dc90a65c565f1d34719c8d19ddd5f --- /dev/null +++ b/src/main/java/net/floodlightcontroller/debugcounter/DebugCounterImpl.java @@ -0,0 +1,141 @@ +package net.floodlightcontroller.debugcounter; + +import java.util.Collection; +import java.util.concurrent.atomic.AtomicLong; + +import javax.annotation.Nonnull; + +import net.floodlightcontroller.debugcounter.IDebugCounterService.MetaData; + +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Sets; + + +class DebugCounterImpl implements IDebugCounter { + private final String moduleName; + private final String counterHierarchy; + private final String description; + private final ImmutableSet<IDebugCounterService.MetaData> metaData; + + private final AtomicLong value = new AtomicLong(); + + + DebugCounterImpl(@Nonnull String moduleName, + @Nonnull String counterHierarchy, + @Nonnull String description, + @Nonnull Collection<MetaData> metaData) { + this.moduleName = moduleName; + this.counterHierarchy = counterHierarchy; + this.description = description; + this.metaData = Sets.immutableEnumSet(metaData); + } + + @Nonnull + String getModuleName() { + return moduleName; + } + + + @Nonnull + String getCounterHierarchy() { + return counterHierarchy; + } + + + @Nonnull + String getDescription() { + return description; + } + + @Nonnull + ImmutableSet<IDebugCounterService.MetaData> getMetaData() { + return metaData; + } + + public void reset() { + value.set(0); + } + + @Override + public void increment() { + value.incrementAndGet(); + } + + @Override + public void add(long increment) { + if (increment < 0) { + throw new IllegalArgumentException("increment must be > 0. Was " + + increment); + } + value.addAndGet(increment); + } + + @Override + public long getCounterValue() { + return value.get(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime + * result + + ((counterHierarchy == null) ? 0 + : counterHierarchy.hashCode()); + result = prime * result + + ((description == null) ? 0 : description.hashCode()); + result = prime * result + + ((metaData == null) ? 0 : metaData.hashCode()); + result = prime * result + + ((moduleName == null) ? 0 : moduleName.hashCode()); + result = prime * result + ((value == null) ? 0 : value.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + DebugCounterImpl other = (DebugCounterImpl) obj; + if (counterHierarchy == null) { + if (other.counterHierarchy != null) return false; + } else if (!counterHierarchy.equals(other.counterHierarchy)) + return false; + if (description == null) { + if (other.description != null) return false; + } else if (!description.equals(other.description)) return false; + if (metaData == null) { + if (other.metaData != null) return false; + } else if (!metaData.equals(other.metaData)) return false; + if (moduleName == null) { + if (other.moduleName != null) return false; + } else if (!moduleName.equals(other.moduleName)) return false; + if (value == null) { + if (other.value != null) return false; + } else if (value.get() != other.value.get()) return false; + return true; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("["); + builder.append(moduleName); + builder.append(" "); + builder.append(counterHierarchy); + builder.append(", description="); + builder.append(description); + builder.append(", metaData="); + builder.append(metaData); + builder.append(", value="); + builder.append(value); + builder.append("]"); + return builder.toString(); + } + + + + +} diff --git a/src/main/java/net/floodlightcontroller/debugcounter/DebugCounterResource.java b/src/main/java/net/floodlightcontroller/debugcounter/DebugCounterResource.java new file mode 100644 index 0000000000000000000000000000000000000000..941802c31d4bd2b536002f0a094b6b781af78e32 --- /dev/null +++ b/src/main/java/net/floodlightcontroller/debugcounter/DebugCounterResource.java @@ -0,0 +1,119 @@ +package net.floodlightcontroller.debugcounter; + +import java.util.Set; + +import javax.annotation.concurrent.Immutable; + +import net.floodlightcontroller.debugcounter.IDebugCounterService.MetaData; + +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableSet; + +/** + * Resource class for DebugCounter + * Serves the REST api with dynamic data + */ +@Immutable +public class DebugCounterResource { + + public static final String MODULE_NAME_PREDICATE = "modulename"; + public static final String HIERARCHY_PREDICATE = "hierarchy"; + private static final Joiner joiner = Joiner.on(", "); + + + private final Long counterValue; + private final String counterDesc; + private final String counterHierarchy; + private final String moduleName; + private final ImmutableSet<MetaData> metadata; + private final String metadataString; + + public DebugCounterResource(DebugCounterImpl counter) { + this.moduleName = counter.getModuleName(); + this.counterHierarchy = counter.getCounterHierarchy(); + this.counterDesc = counter.getDescription(); + this.metadata = counter.getMetaData(); + this.counterValue = counter.getCounterValue(); + this.metadataString = joiner.join(metadata); + } + + public Long getCounterValue() { + return counterValue; + } + + public String getCounterDesc() { + return counterDesc; + } + + public String getCounterHierarchy() { + return counterHierarchy; + } + + public String getModuleName() { + return moduleName; + } + + public Set<MetaData> getMetadata() { + return metadata; + } + + public String getMetadataString() { + return metadataString; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + + ((counterDesc == null) ? 0 : counterDesc.hashCode()); + result = prime + * result + + ((counterHierarchy == null) ? 0 + : counterHierarchy.hashCode()); + result = prime * result + + ((counterValue == null) ? 0 : counterValue.hashCode()); + result = prime * result + + ((metadata == null) ? 0 : metadata.hashCode()); + result = prime + * result + + ((metadataString == null) ? 0 : metadataString.hashCode()); + result = prime * result + + ((moduleName == null) ? 0 : moduleName.hashCode()); + return result; + } + + /** + * Compare all fields, not only the "key" fields + */ + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + DebugCounterResource other = (DebugCounterResource) obj; + if (counterDesc == null) { + if (other.counterDesc != null) return false; + } else if (!counterDesc.equals(other.counterDesc)) return false; + if (counterHierarchy == null) { + if (other.counterHierarchy != null) return false; + } else if (!counterHierarchy.equals(other.counterHierarchy)) + return false; + if (counterValue == null) { + if (other.counterValue != null) return false; + } else if (!counterValue.equals(other.counterValue)) return false; + if (metadata == null) { + if (other.metadata != null) return false; + } else if (!metadata.equals(other.metadata)) return false; + if (metadataString == null) { + if (other.metadataString != null) return false; + } else if (!metadataString.equals(other.metadataString)) + return false; + if (moduleName == null) { + if (other.moduleName != null) return false; + } else if (!moduleName.equals(other.moduleName)) return false; + return true; + } + + +} \ No newline at end of file diff --git a/src/main/java/net/floodlightcontroller/debugcounter/DebugCounterResourceBase.java b/src/main/java/net/floodlightcontroller/debugcounter/DebugCounterResourceBase.java deleted file mode 100644 index 9edd47473a75564334c217b240002612d31e6219..0000000000000000000000000000000000000000 --- a/src/main/java/net/floodlightcontroller/debugcounter/DebugCounterResourceBase.java +++ /dev/null @@ -1,16 +0,0 @@ -package net.floodlightcontroller.debugcounter; - -import org.restlet.resource.ResourceException; -import org.restlet.resource.ServerResource; - -public class DebugCounterResourceBase extends ServerResource { - - protected IDebugCounterService debugCounter; - - @Override - protected void doInit() throws ResourceException { - super.doInit(); - debugCounter = (IDebugCounterService)getContext().getAttributes(). - get(IDebugCounterService.class.getCanonicalName()); - } -} diff --git a/src/main/java/net/floodlightcontroller/debugcounter/DebugCounterServiceImpl.java b/src/main/java/net/floodlightcontroller/debugcounter/DebugCounterServiceImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..ae245deb872bdafa03f0b94a19091618ac76c4fc --- /dev/null +++ b/src/main/java/net/floodlightcontroller/debugcounter/DebugCounterServiceImpl.java @@ -0,0 +1,256 @@ +package net.floodlightcontroller.debugcounter; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import javax.annotation.Nonnull; +import javax.annotation.concurrent.GuardedBy; + +import net.floodlightcontroller.core.IShutdownListener; +import net.floodlightcontroller.core.IShutdownService; +import net.floodlightcontroller.core.module.FloodlightModuleContext; +import net.floodlightcontroller.core.module.IFloodlightModule; +import net.floodlightcontroller.core.module.IFloodlightService; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class DebugCounterServiceImpl implements IFloodlightModule, IDebugCounterService { + protected static final Logger logger = + LoggerFactory.getLogger(DebugCounterServiceImpl.class); + + /** + * The tree of counters. + */ + private final CounterNode root = CounterNode.newTree(); + + /** + * protects the counter hierarchy tree. The writeLock is required to + * change to hierarchy, i.e., adding nodes. The readLock is required + * to query the counters or to reset them. + */ + private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); + + static void verifyStringSanity(String str, String name) { + if (str == null) { + if (name == null) { + throw new NullPointerException(); + } else { + throw new NullPointerException(name + " must not be null"); + } + } + if (str.isEmpty()) { + if (name == null) { + throw new IllegalArgumentException(); + } else { + throw new IllegalArgumentException(name + " must not be empty"); + } + } + } + + static void verifyModuleNameSanity(String moduleName) { + verifyStringSanity(moduleName, "moduleName"); + if (moduleName.contains("/")) { + throw new IllegalArgumentException("moduleName must not contain /"); + } + } + + @Override + public boolean registerModule(String moduleName) { + verifyModuleNameSanity(moduleName); + lock.writeLock().lock(); + try { + return root.addModule(moduleName); + } finally { + lock.writeLock().unlock(); + } + } + + @Override + public IDebugCounter registerCounter(@Nonnull String moduleName, + @Nonnull String counterHierarchy, + @Nonnull String counterDescription, + @Nonnull MetaData... metaData) { + verifyModuleNameSanity(moduleName); + verifyStringSanity(counterHierarchy, "counterHierarchy"); + if (counterDescription == null) { + try { + throw new Exception("counterDescription must not be null"); + } catch (Exception e) { + e.printStackTrace(); + logger.error(e.getMessage()); + } + } + if (metaData == null) { + // somebody passing in a null array. sigh. + throw new NullPointerException("metaData must not be null"); + } + DebugCounterImpl counter = + new DebugCounterImpl(moduleName, counterHierarchy, + counterDescription, + Arrays.asList(metaData)); + lock.writeLock().lock(); + try { + DebugCounterImpl oldCounter = root.addCounter(counter); + if (oldCounter != null && logger.isDebugEnabled()) { + logger.debug("Counter {} {} already registered. Resetting hierarchy", + moduleName, counterHierarchy); + } + return oldCounter; + } finally { + lock.writeLock().unlock(); + } + } + + @GuardedBy("lock.readLock") + private boolean resetInternal(List<String> hierarchyElements) { + CounterNode node = root.lookup(hierarchyElements); + if (node == null) { + return false; + } + node.resetHierarchy(); + return true; + } + + @Override + public boolean resetCounterHierarchy(String moduleName, + String counterHierarchy) { + verifyModuleNameSanity(moduleName); + verifyStringSanity(counterHierarchy, "counterHierarchy"); + lock.readLock().lock(); + try { + return resetInternal(CounterNode.getHierarchyElements(moduleName, counterHierarchy)); + } finally { + lock.readLock().unlock(); + } + } + + @Override + public void resetAllCounters() { + lock.readLock().lock(); + try { + root.resetHierarchy(); + } finally { + lock.readLock().unlock(); + } + } + + + @Override + public boolean resetAllModuleCounters(String moduleName) { + verifyModuleNameSanity(moduleName); + lock.readLock().lock(); + try { + return resetInternal(Collections.singletonList(moduleName)); + } finally { + lock.readLock().unlock(); + } + } + + @GuardedBy("lock.readLock") + private List<DebugCounterResource> getCountersFromNode(CounterNode node) { + if (node == null) { + return Collections.emptyList(); + } + List<DebugCounterResource> ret = new ArrayList<>(); + for (DebugCounterImpl counter: node.getCountersInHierarchy()) { + ret.add(new DebugCounterResource(counter)); + } + return ret; + } + + @Override + public List<DebugCounterResource> + getCounterHierarchy(String moduleName, String counterHierarchy) { + verifyModuleNameSanity(moduleName); + verifyStringSanity(counterHierarchy, "counterHierarchy"); + List<String> hierarchyElements = + CounterNode.getHierarchyElements(moduleName, counterHierarchy); + lock.readLock().lock(); + try { + return getCountersFromNode(root.lookup(hierarchyElements)); + } finally { + lock.readLock().unlock(); + } + } + + @Override + public List<DebugCounterResource> getAllCounterValues() { + lock.readLock().lock(); + try { + return getCountersFromNode(root); + } finally { + lock.readLock().unlock(); + } + } + + @Override + public List<DebugCounterResource> getModuleCounterValues(String moduleName) { + verifyModuleNameSanity(moduleName); + List<String> hierarchyElements = Collections.singletonList(moduleName); + lock.readLock().lock(); + try { + return getCountersFromNode(root.lookup(hierarchyElements)); + } finally { + lock.readLock().unlock(); + } + } + + private class ShutdownListenenerDelegate implements IShutdownListener { + @Override + public void floodlightIsShuttingDown() { + for (DebugCounterResource counter: getAllCounterValues()) { + logger.info("Module {} counterHierarchy {} value " + counter.getCounterValue(), + counter.getModuleName(), + counter.getCounterHierarchy()); + } + } + } + + + //******************************* + // IFloodlightModule + //******************************* + + @Override + public Collection<Class<? extends IFloodlightService>> getModuleServices() { + Collection<Class<? extends IFloodlightService>> l = + new ArrayList<Class<? extends IFloodlightService>>(); + l.add(IDebugCounterService.class); + return l; + } + + @Override + public Map<Class<? extends IFloodlightService>, IFloodlightService> getServiceImpls() { + Map<Class<? extends IFloodlightService>, IFloodlightService> m = + new HashMap<Class<? extends IFloodlightService>, IFloodlightService>(); + m.put(IDebugCounterService.class, this); + return m; + } + + @Override + public Collection<Class<? extends IFloodlightService>> getModuleDependencies() { + ArrayList<Class<? extends IFloodlightService>> deps = + new ArrayList<Class<? extends IFloodlightService>>(); + deps.add(IShutdownService.class); + return deps; + } + + @Override + public void init(FloodlightModuleContext context) { + } + + @Override + public void startUp(FloodlightModuleContext context) { + IShutdownService shutdownService = + context.getServiceImpl(IShutdownService.class); + shutdownService.registerShutdownListener(new ShutdownListenenerDelegate()); + } + +} diff --git a/src/main/java/net/floodlightcontroller/debugcounter/IDebugCounter.java b/src/main/java/net/floodlightcontroller/debugcounter/IDebugCounter.java index dbde1854e9c6f7707e983eedf5bfe6fef299d7ec..00f259a54064db154011ce124ad39e4ce5a7d995 100644 --- a/src/main/java/net/floodlightcontroller/debugcounter/IDebugCounter.java +++ b/src/main/java/net/floodlightcontroller/debugcounter/IDebugCounter.java @@ -1,38 +1,28 @@ package net.floodlightcontroller.debugcounter; +/** + * A concurrent counter. + * The counter returned when registering a counter. The counter value + * is a positive long. The counter cannot be decremented, but it can be + * reset to 0. The counter does not protect against overflow. If the + * value exceeds MAX_LONG it will silently overflow to MIN_LONG + * @author gregor + * + */ public interface IDebugCounter { /** - * Increments the counter by 1 thread-locally, and immediately flushes to - * the global counter storage. This method should be used for counters that - * are updated outside the OF message processing pipeline. + * Increment this counter by 1. */ - void updateCounterWithFlush(); + void increment(); /** - * Increments the counter by 1 thread-locally. Flushing to the global - * counter storage is delayed (happens with flushCounters() in IDebugCounterService), - * resulting in higher performance. This method should be used for counters - * updated in the OF message processing pipeline. + * Add the given increment to this counter + * @param incr */ - void updateCounterNoFlush(); + void add(long incr); /** - * Increments the counter thread-locally by the 'incr' specified, and immediately - * flushes to the global counter storage. This method should be used for counters - * that are updated outside the OF message processing pipeline. - */ - void updateCounterWithFlush(int incr); - - /** - * Increments the counter thread-locally by the 'incr' specified. Flushing to the global - * counter storage is delayed (happens with flushCounters() in IDebugCounterService), - * resulting in higher performance. This method should be used for counters - * updated in the OF message processing pipeline. - */ - void updateCounterNoFlush(int incr); - - /** - * Retrieve the value of the counter from the global counter store + * Retrieve the value of the counter. */ long getCounterValue(); } diff --git a/src/main/java/net/floodlightcontroller/debugcounter/IDebugCounterService.java b/src/main/java/net/floodlightcontroller/debugcounter/IDebugCounterService.java index 538e60dad148c600e3802b53271ed6e111d8f6b9..524e7cea0e9968868704d95c490b693946cf137d 100644 --- a/src/main/java/net/floodlightcontroller/debugcounter/IDebugCounterService.java +++ b/src/main/java/net/floodlightcontroller/debugcounter/IDebugCounterService.java @@ -1,88 +1,26 @@ package net.floodlightcontroller.debugcounter; -import net.floodlightcontroller.core.module.IFloodlightService; -import net.floodlightcontroller.debugcounter.DebugCounter.DebugCounterInfo; - import java.util.List; -public interface IDebugCounterService extends IFloodlightService { - - /** - * Different counter types. Counters that are meant to be counted-on-demand - * need to be separately enabled/disabled. - */ - public enum CounterType { - ALWAYS_COUNT, - COUNT_ON_DEMAND - } - - /** - * Debug Counter Qualifiers - */ - public static final String CTR_MDATA_WARN = "warn"; - public static final String CTR_MDATA_ERROR = "error"; - - /** - * A limit on the maximum number of counters that can be created - */ - public static final int MAX_COUNTERS = 5000; - - /** - * Exception thrown when MAX_COUNTERS have been registered - */ - public class MaxCountersRegistered extends CounterException { - private static final long serialVersionUID = 3173747663719376745L; - String errormsg; - public MaxCountersRegistered(String errormsg) { - this.errormsg = errormsg; - } - @Override - public String getMessage() { - return this.errormsg; - } - } - /** - * Exception thrown when MAX_HIERARCHY has been reached - */ - public class MaxHierarchyRegistered extends CounterException { - private static final long serialVersionUID = 967431358683523871L; - String errormsg; - public MaxHierarchyRegistered(String errormsg) { - this.errormsg = errormsg; - } - @Override - public String getMessage() { - return this.errormsg; - } - } - /** - * Exception thrown when attempting to register a hierarchical counter - * where higher levels of the hierarchy have not been pre-registered - */ - public class MissingHierarchicalLevel extends CounterException { - private static final long serialVersionUID = 517315311533995739L; - String errormsg; - public MissingHierarchicalLevel(String errormsg) { - this.errormsg = errormsg; - } - @Override - public String getMessage() { - return this.errormsg; - } - } +import net.floodlightcontroller.core.module.IFloodlightService; - public class CounterException extends Exception { - private static final long serialVersionUID = 2219781500857866035L; +public interface IDebugCounterService extends IFloodlightService { + public enum MetaData { + WARN, + DROP, + ERROR } /** - * maximum levels of hierarchy - * Example of moduleName/counterHierarchy: - * switch/00:00:00:00:01:02:03:04/pktin/drops where - * moduleName ==> "switch" and - * counterHierarchy of 3 ==> "00:00:00:00:01:02:03:04/pktin/drops" + * All modules that wish to have the DebugCounterService count for them, must + * register themselves. If a module is registered multiple times subsequent + * registrations will reset all counter in the module. + * + * @param moduleName + * @return true if the module is registered for the first time, false if + * the modue was previously registered */ - public static final int MAX_HIERARCHY = 3; + public boolean registerModule(String moduleName); /** * All modules that wish to have the DebugCounterService count for them, must @@ -105,32 +43,15 @@ public interface IDebugCounterService extends IFloodlightService { * of what the counter is measuring. For example, * "Measures the number of incoming packets seen by * this module". - * @param counterType One of CounterType. On-demand counter types - * need to be explicitly enabled/disabled using other - * methods in this API -- i.e. registering them is - * not enough to start counting. * @param metaData variable arguments that qualify a counter * eg. warn, error etc. * @return IDebugCounter with update methods that can be * used to update a counter. - * @throws MaxCountersRegistered - * @throws MaxHierarchyRegistered - * @throws MissingHierarchicalLevel */ - public IDebugCounter registerCounter(String moduleName, String counterHierarchy, - String counterDescription, CounterType counterType, - String... metaData) - throws MaxCountersRegistered, MaxHierarchyRegistered, - MissingHierarchicalLevel; + public IDebugCounter + registerCounter(String moduleName, String counterHierarchy, + String counterDescription, MetaData... metaData); - /** - * Flush all thread-local counter values (from the current thread) - * to the global counter store. This method is not intended for use by any - * module. It's typical usage is from floodlight core and it is meant - * to flush those counters that are updated in the packet-processing pipeline, - * typically with the 'updateCounterNoFlush" methods in IDebugCounter. - */ - public void flushCounters(); /** * Resets the value of counters in the hierarchy to zero. Note that the reset @@ -142,8 +63,10 @@ public interface IDebugCounterService extends IFloodlightService { * while specifying a reset hierarchy: ""00:00:00:00:01:02:03:04/pktin" * will reset the pktin counter and all levels below it (like drops) * for the switch dpid specified. + * @return false if the given moduleName, counterHierarchy + * does not exist */ - void resetCounterHierarchy(String moduleName, String counterHierarchy); + public boolean resetCounterHierarchy(String moduleName, String counterHierarchy); /** * Resets the values of all counters in the system. @@ -153,38 +76,10 @@ public interface IDebugCounterService extends IFloodlightService { /** * Resets the values of all counters belonging * to a module with the given 'moduleName'. + * @return false if the given module name does not exists */ - public void resetAllModuleCounters(String moduleName); + public boolean resetAllModuleCounters(String moduleName); - /** - * This method applies only to CounterType.COUNT_ON_DEMAND. It is used to - * enable counting on the counter. Note that this step is necessary to start - * counting for these counter types - merely registering the counter is not - * enough (as is the case for CounterType.ALWAYS_COUNT). Newly - * enabled counters start from an initial value of zero. - * - * Enabling a counter in a counterHierarchy enables only THAT counter. It - * does not enable any other part of the counterHierarchy. For example, if - * a hierarchy exists like "00:00:00:00:01:02:03:04/pktin/drops", where the - * 'pktin' and 'drops' counters are CounterType.COUNT_ON_DEMAND, then enabling - * the 'pktin' counter by specifying the counterHierarchy as - * "00:00:00:00:01:02:03:04/pktin" does NOT enable the 'drops' counter. - */ - public void enableCtrOnDemand(String moduleName, String counterHierarchy); - - /** - * This method applies only to CounterType.COUNT_ON_DEMAND. It is used to - * enable counting on the counter. Note that disabling a counter results in a loss - * of the counter value. When re-enabled the counter will restart from zero. - * - * Disabling a counter in a counterHierarchy disables only THAT counter. It - * does not disable any other part of the counterHierarchy. For example, if - * a hierarchy exists like "00:00:00:00:01:02:03:04/pktin/drops", where the - * 'pktin' and 'drops' counters are CounterType.COUNT_ON_DEMAND, then disabling - * the 'pktin' counter by specifying the counterHierarchy as - * "00:00:00:00:01:02:03:04/pktin" does NOT disable the 'drops' counter. - */ - public void disableCtrOnDemand(String moduleName, String counterHierarchy); /** * Get counter value and associated information for the specified counterHierarchy. @@ -197,11 +92,13 @@ public interface IDebugCounterService extends IFloodlightService { * get call will return information on the 'pktin' as well as the 'drops' * counters for the switch dpid specified. * - * @return A list of DebugCounterInfo or an empty list if the counter + * If the module or hierarchy is not registered, returns an empty list + * + * @return A list of DebugCounterResource or an empty list if the counter * could not be found */ - public List<DebugCounterInfo> getCounterHierarchy(String moduleName, - String counterHierarchy); + public List<DebugCounterResource> + getCounterHierarchy(String moduleName, String counterHierarchy); /** * Get counter values and associated information for all counters in the @@ -209,51 +106,16 @@ public interface IDebugCounterService extends IFloodlightService { * * @return the list of values/info or an empty list */ - public List<DebugCounterInfo> getAllCounterValues(); + public List<DebugCounterResource> getAllCounterValues(); /** * Get counter values and associated information for all counters associated * with a module. + * If the module is not registered, returns an empty list * * @param moduleName * @return the list of values/info or an empty list */ - public List<DebugCounterInfo> getModuleCounterValues(String moduleName); - - /** - * Convenience method to figure out if the the given 'counterHierarchy' corresponds - * to a registered counterHierarchy for 'moduleName'. Note that the counter may or - * may not be enabled for counting, but if it is registered the method will - * return true. - * - * @param param - * @return false if moduleCounterHierarchy is not a registered counter - */ - public boolean containsModuleCounterHierarchy(String moduleName, - String counterHierarchy); - - /** - * Convenience method to figure out if the the given 'moduleName' corresponds - * to a registered moduleName or not. Note that the module may or may not have - * a counter enabled for counting, but if it is registered the method will - * return true. - * - * @param param - * @return false if moduleName is not a registered counter - */ - public boolean containsModuleName(String moduleName); - - /** - * Returns a list of moduleNames registered for debug counters or an empty - * list if no counters have been registered in the system - */ - public List<String> getModuleList(); - - /** - * Returns a list of all counters registered for a specific moduleName - * or a empty list - */ - public List<String> getModuleCounterList(String moduleName); - + public List<DebugCounterResource> getModuleCounterValues(String moduleName); } diff --git a/src/main/java/net/floodlightcontroller/debugcounter/NullDebugCounter.java b/src/main/java/net/floodlightcontroller/debugcounter/MockDebugCounterService.java similarity index 50% rename from src/main/java/net/floodlightcontroller/debugcounter/NullDebugCounter.java rename to src/main/java/net/floodlightcontroller/debugcounter/MockDebugCounterService.java index 5dc7b319219de1acb202ffdf3df1b347127bb10c..36a19d548fe60bda68455064224fdb4574ad089d 100644 --- a/src/main/java/net/floodlightcontroller/debugcounter/NullDebugCounter.java +++ b/src/main/java/net/floodlightcontroller/debugcounter/MockDebugCounterService.java @@ -11,9 +11,8 @@ 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.debugcounter.DebugCounter.DebugCounterInfo; -public class NullDebugCounter implements IFloodlightModule, IDebugCounterService { +public class MockDebugCounterService implements IFloodlightModule, IDebugCounterService { @Override @@ -53,110 +52,64 @@ public class NullDebugCounter implements IFloodlightModule, IDebugCounterService } - @Override - public void flushCounters() { - + public boolean registerModule(String moduleName) { + return true; } @Override - public void resetAllCounters() { - + public IDebugCounter registerCounter(String moduleName, + String counterHierarchy, + String counterDescription, + MetaData... metaData) { + return new MockCounterImpl(); } @Override - public void resetAllModuleCounters(String moduleName) { - + public boolean + resetCounterHierarchy(String moduleName, String counterHierarchy) { + return true; } - @Override - public void resetCounterHierarchy(String moduleName, String counterHierarchy) { - - } - - @Override - public void enableCtrOnDemand(String moduleName, String counterHierarchy) { - - } - - @Override - public void disableCtrOnDemand(String moduleName, String counterHierarchy) { - - } - - @Override - public List<DebugCounterInfo> getCounterHierarchy(String moduleName, - String counterHierarchy) { - return Collections.emptyList(); + public void resetAllCounters() { } @Override - public List<DebugCounterInfo> getAllCounterValues() { - return Collections.emptyList(); + public boolean resetAllModuleCounters(String moduleName) { + return true; } @Override - public List<DebugCounterInfo> getModuleCounterValues(String moduleName) { + public List<DebugCounterResource> + getCounterHierarchy(String moduleName, String counterHierarchy) { return Collections.emptyList(); } @Override - public boolean containsModuleCounterHierarchy(String moduleName, - String counterHierarchy) { - return false; - } - - @Override - public boolean containsModuleName(String moduleName) { - return false; - } - - @Override - public - IDebugCounter - registerCounter(String moduleName, String counterHierarchy, - String counterDescription, - CounterType counterType, String... metaData) - throws MaxCountersRegistered { - return new NullCounterImpl(); - } - - @Override - public List<String> getModuleList() { + public List<DebugCounterResource> getAllCounterValues() { return Collections.emptyList(); } @Override - public List<String> getModuleCounterList(String moduleName) { + public List<DebugCounterResource> + getModuleCounterValues(String moduleName) { return Collections.emptyList(); } - public class NullCounterImpl implements IDebugCounter { - + public static class MockCounterImpl implements IDebugCounter { @Override - public void updateCounterWithFlush() { - + public void increment() { } @Override - public void updateCounterNoFlush() { - - } - - @Override - public void updateCounterWithFlush(int incr) { - } - - @Override - public void updateCounterNoFlush(int incr) { - + public void add(long incr) { } @Override public long getCounterValue() { return -1; } - } + } diff --git a/src/main/java/net/floodlightcontroller/debugcounter/web/DebugCounterResource.java b/src/main/java/net/floodlightcontroller/debugcounter/web/DebugCounterResource.java deleted file mode 100644 index 3dd35bc07502d4db5f9b16ebe4da8978379b5497..0000000000000000000000000000000000000000 --- a/src/main/java/net/floodlightcontroller/debugcounter/web/DebugCounterResource.java +++ /dev/null @@ -1,382 +0,0 @@ -package net.floodlightcontroller.debugcounter.web; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - - -import org.restlet.resource.Get; -import org.restlet.resource.Post; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import net.floodlightcontroller.debugcounter.DebugCounter.DebugCounterInfo; -import net.floodlightcontroller.debugcounter.IDebugCounterService.CounterType; - -/** - * Web interface for Debug Counters - * - * @author Saurav - */ -public class DebugCounterResource extends DebugCounterResourceBase { - protected static Logger logger = - LoggerFactory.getLogger(DebugCounterResource.class); - - /** - * The output JSON model that contains the counter information - */ - public class DebugCounterInfoOutput { - protected class DCInfo { - private final Long counterValue; - private final CounterType counterType; - private final String counterDesc; - private final boolean enabled; - private final String counterHierarchy; - private final String moduleName; - private final String[] metaData; - - DCInfo(DebugCounterInfo dci) { - this.moduleName = dci.getCounterInfo().getModuleName(); - this.counterHierarchy = dci.getCounterInfo().getCounterHierarchy(); - this.counterDesc = dci.getCounterInfo().getCounterDesc(); - this.metaData = dci.getCounterInfo().getMetaData(); - this.enabled = dci.getCounterInfo().isEnabled(); - this.counterType = dci.getCounterInfo().getCtype(); - this.counterValue = dci.getCounterValue(); - } - - public Long getCounterValue() { - return counterValue; - } - public CounterType getCounterType() { - return counterType; - } - - public String getCounterDesc() { - return counterDesc; - } - - public boolean isEnabled() { - return enabled; - } - - public String getCounterHierarchy() { - return counterHierarchy; - } - - public String getModuleName() { - return moduleName; - } - - public String[] getMetaData() { - return metaData; - } - - } - // complete counter information - null if only names are requested or - // if an error occurs - public Map<String, DCInfo> counterMap; - // list of names could be just moduleNames or counter hierarchical names - // for a specific module - public List<String> names; - - public String error; - - DebugCounterInfoOutput(boolean getList) { - if (!getList) { - counterMap = new HashMap<String, DCInfo>(); - } - error = null; - } - public Map<String, DCInfo> getCounterMap() { - return counterMap; - } - - public String getError() { - return error; - } - - public List<String> getNames() { - return names; - } - - } - - public enum Option { - ALL, ONE_MODULE, MODULE_COUNTER_HIERARCHY, ERROR_BAD_MODULE_NAME, - ERROR_BAD_PARAM, - ERROR_BAD_MODULE_COUNTER_NAME - } - - public static class CounterPost { - public Boolean reset; - public Boolean enable; - - public Boolean getReset() { - return reset; - } - public void setReset(Boolean reset) { - this.reset = reset; - } - public Boolean getEnable() { - return enable; - } - public void setEnable(Boolean enable) { - this.enable = enable; - } - } - - public static class ResetOutput { - String error = null; - - public String getError() { - return error; - } - public void setError(String error) { - this.error = error; - } - } - - /** - * Reset or enable/disable counters - * - * If using curl: - * curl -X POST -d DATA -H "Content-Type: application/json" URL - * where DATA must be one of the following: - * {\"reset\":true} to reset counters - * {\"enable\":true} to enable counter - * {\"enable\":false} to disable counter - * and URL must be in one of the following forms: - * "http://{controller-hostname}:8080/wm/debugcounter/{param1}/{param2}/{param3}/{param4} - * - * {param1} can be null, 'all' or the name of a module {moduleName}. - * {param2}/{param3}/{param4} refer to hierarchical counter names. - * - * The Reset command will reset the counter specified as well as all counters - * in the hierarchical levels below. For example, if a counter hierarchy exists - * as switch/00:00:00:00:01:02:03:04/pktin/drops, then a reset command with just - * the moduleName (param1=switch) and counterHierarchy (param2=00:00:00:00:01:02:03:04) - * will reset all counters for that switch. Continuing the example - - * for a counterHierarchy (param2=00:00:00:00:01:02:03:04 and param3=pktin), the reset - * command will remove all pktin counters for that switch. - * - * The enable/disable command will ONLY disable a specific counter (and only if - * that counter is of CounterType.ON_DEMAND) - * It will not enable/disable counters at any other hierarchical level. - * - */ - @Post - public ResetOutput postHandler(CounterPost postData) { - ResetOutput output = new ResetOutput(); - Option choice = Option.ERROR_BAD_PARAM; - String param1 = (String)getRequestAttributes().get("param1"); - String param2 = (String)getRequestAttributes().get("param2"); - String param3 = (String)getRequestAttributes().get("param3"); - String param4 = (String)getRequestAttributes().get("param4"); - String moduleName = ""; - - if (param1 == null) { - moduleName = "all"; - choice = Option.ALL; - } else if (param1.equals("all")) { - moduleName = "all"; - choice = Option.ALL; - } else { - moduleName = param1; - } - - String counterHierarchy = ""; - if (param2 != null) { - counterHierarchy += param2; - if (param3 != null) { - counterHierarchy += "/" + param3; - if (param4 != null) { - counterHierarchy += "/" + param4; - } - } - } - - if (!moduleName.equals("all") && counterHierarchy.equals("")) { - // only module name specified - boolean isRegistered = debugCounter.containsModuleName(param1); - if (isRegistered) { - choice = Option.ONE_MODULE; - } else { - choice = Option.ERROR_BAD_MODULE_NAME; - } - } else if (!moduleName.equals("all") && !counterHierarchy.equals("")) { - // both module and counter names specified - boolean isRegistered = debugCounter. - containsModuleCounterHierarchy(moduleName, counterHierarchy); - if (isRegistered) { - choice = Option.MODULE_COUNTER_HIERARCHY; - } else { - choice = Option.ERROR_BAD_MODULE_COUNTER_NAME; - } - } - - boolean reset = false; - boolean turnOnOff = false; - if (postData.getReset() != null && postData.getReset()) { - reset = true; - } - if (postData.getEnable() != null) { - turnOnOff = true; - } - - switch (choice) { - case ALL: - if (reset) debugCounter.resetAllCounters(); - break; - case ONE_MODULE: - if (reset) debugCounter.resetAllModuleCounters(moduleName); - break; - case MODULE_COUNTER_HIERARCHY: - if (reset) - debugCounter.resetCounterHierarchy(moduleName, counterHierarchy); - else if (turnOnOff && postData.getEnable()) - debugCounter.enableCtrOnDemand(moduleName, counterHierarchy); - else if (turnOnOff && !postData.getEnable()) - debugCounter.disableCtrOnDemand(moduleName, counterHierarchy); - break; - case ERROR_BAD_MODULE_NAME: - output.error = "Module name has no corresponding registered counters"; - break; - case ERROR_BAD_MODULE_COUNTER_NAME: - output.error = "Counter not registered"; - break; - case ERROR_BAD_PARAM: - output.error = "Bad param"; - } - - return output; - } - - /** - * Return the debug counter data for the get rest-api call - * - * URI must be in one of the following forms: - * "http://{controller-hostname}:8080/wm/debugcounter/{param1}/{param2}/{param3}/{param4}" - * - * where {param1} must be one of (no quotes): - * null if nothing is given then by default all - * module names are returned for which counters - * have been registered - * "all" returns value/info on all counters. - * "{moduleName}" returns value/info on all counters for - * the specified module 'moduelName'. - * & {param2}/{param3}/{param4} refer to hierarchical counter names. - * eg. 00:00:00:00:01:02:03:04/pktin/drops - * where leaving out any of the params returns - * all counters in the hierarchical level below. - * So giving just the switch dpid will fetch - * all counters for that switch. - * A special case => if param2 is null, then - * all hierarchical counterNames are returned - * for the given moduleName (in param1) - */ - @Get - public DebugCounterInfoOutput handleCounterInfoQuery() { - DebugCounterInfoOutput output; - Option choice = Option.ERROR_BAD_PARAM; - String param1 = (String)getRequestAttributes().get("param1"); - String param2 = (String)getRequestAttributes().get("param2"); - String param3 = (String)getRequestAttributes().get("param3"); - String param4 = (String)getRequestAttributes().get("param4"); - - if (param1 == null) { - output = new DebugCounterInfoOutput(true); - return listCounters(output); - } else if (param1.equals("all")) { - output = new DebugCounterInfoOutput(false); - populateCounters(debugCounter.getAllCounterValues(), output); - return output; - } - - output = new DebugCounterInfoOutput(false); - String counterHierarchy = ""; - if (param2 == null) { - // param2 is null -- return list of counternames for param1 - boolean isRegistered = debugCounter.containsModuleName(param1); - output = new DebugCounterInfoOutput(true); - if (isRegistered) { - return listCounters(param1, output); - } else { - choice = Option.ERROR_BAD_MODULE_NAME; - } - } else if (param2.equals("all")) { - // get all counter info for a single module - boolean isRegistered = debugCounter.containsModuleName(param1); - if (isRegistered) { - choice = Option.ONE_MODULE; - } else { - choice = Option.ERROR_BAD_MODULE_NAME; - } - } else { - counterHierarchy += param2; - if (param3 != null) { - counterHierarchy += "/" + param3; - if (param4 != null) { - counterHierarchy += "/" + param4; - } - } - boolean isRegistered = debugCounter. - containsModuleCounterHierarchy(param1, counterHierarchy); - if (isRegistered) { - choice = Option.MODULE_COUNTER_HIERARCHY; - } else { - choice = Option.ERROR_BAD_MODULE_COUNTER_NAME; - } - } - - switch (choice) { - case ONE_MODULE: - populateCounters(debugCounter.getModuleCounterValues(param1), output); - break; - case MODULE_COUNTER_HIERARCHY: - populateCounters(debugCounter.getCounterHierarchy(param1, counterHierarchy), - output); - break; - case ERROR_BAD_MODULE_NAME: - output.error = "Module name is not registered for debug-counters"; - break; - case ERROR_BAD_MODULE_COUNTER_NAME: - output.error = "Counter not registered"; - break; - case ERROR_BAD_PARAM: - default: - output.error = "Bad param"; - } - - return output; - } - - private DebugCounterInfoOutput listCounters(String moduleName, - DebugCounterInfoOutput output) { - output.names = debugCounter.getModuleCounterList(moduleName); - return output; - } - - private DebugCounterInfoOutput listCounters(DebugCounterInfoOutput output) { - output.names = debugCounter.getModuleList(); - return output; - } - - private void populateSingleCounter(DebugCounterInfo debugCounterInfo, - DebugCounterInfoOutput output) { - if (debugCounterInfo != null) - output.counterMap.put(debugCounterInfo.getCounterInfo(). - getModuleCounterHierarchy(), - output.new DCInfo(debugCounterInfo)); - } - - private void populateCounters(List<DebugCounterInfo> counterValues, - DebugCounterInfoOutput output) { - for (DebugCounterInfo dci : counterValues) { - populateSingleCounter(dci, output); - } - } - - - -} diff --git a/src/main/java/net/floodlightcontroller/debugcounter/web/DebugCounterResourceBase.java b/src/main/java/net/floodlightcontroller/debugcounter/web/DebugCounterResourceBase.java deleted file mode 100644 index 48e469bc689b9c3cf44c2dcdd0c80caa428796b2..0000000000000000000000000000000000000000 --- a/src/main/java/net/floodlightcontroller/debugcounter/web/DebugCounterResourceBase.java +++ /dev/null @@ -1,18 +0,0 @@ -package net.floodlightcontroller.debugcounter.web; - -import net.floodlightcontroller.debugcounter.IDebugCounterService; - -import org.restlet.resource.ResourceException; -import org.restlet.resource.ServerResource; - -public class DebugCounterResourceBase extends ServerResource { - - protected IDebugCounterService debugCounter; - - @Override - protected void doInit() throws ResourceException { - super.doInit(); - debugCounter = (IDebugCounterService)getContext().getAttributes(). - get(IDebugCounterService.class.getCanonicalName()); - } -} diff --git a/src/main/java/net/floodlightcontroller/debugcounter/web/DebugCounterRoutable.java b/src/main/java/net/floodlightcontroller/debugcounter/web/DebugCounterRoutable.java deleted file mode 100644 index 55ba5bacf95b0257556bd5483a561ca40fa0a8cb..0000000000000000000000000000000000000000 --- a/src/main/java/net/floodlightcontroller/debugcounter/web/DebugCounterRoutable.java +++ /dev/null @@ -1,31 +0,0 @@ -package net.floodlightcontroller.debugcounter.web; - -import org.restlet.Context; -import org.restlet.Restlet; -import org.restlet.routing.Router; - -import net.floodlightcontroller.restserver.RestletRoutable; - -public class DebugCounterRoutable implements RestletRoutable { - - @Override - public String basePath() { - return "/wm/debugcounter"; - } - - @Override - public Restlet getRestlet(Context context) { - Router router = new Router(context); - router.attach("/{param1}/{param2}/{param3}/{param4}/", DebugCounterResource.class); - router.attach("/{param1}/{param2}/{param3}/{param4}", DebugCounterResource.class); - router.attach("/{param1}/{param2}/{param3}/", DebugCounterResource.class); - router.attach("/{param1}/{param2}/{param3}", DebugCounterResource.class); - router.attach("/{param1}/{param2}/", DebugCounterResource.class); - router.attach("/{param1}/{param2}", DebugCounterResource.class); - router.attach("/{param1}/", DebugCounterResource.class); - router.attach("/{param1}", DebugCounterResource.class); - router.attach("/", DebugCounterResource.class); - router.attach("", DebugCounterResource.class); - return router; - } -} diff --git a/src/main/java/net/floodlightcontroller/debugevent/CircularBuffer.java b/src/main/java/net/floodlightcontroller/debugevent/CircularBuffer.java deleted file mode 100644 index f1a6db33dd37dfb69d3635855a774e3b14ae3e5b..0000000000000000000000000000000000000000 --- a/src/main/java/net/floodlightcontroller/debugevent/CircularBuffer.java +++ /dev/null @@ -1,95 +0,0 @@ -package net.floodlightcontroller.debugevent; - - -import java.util.ArrayList; -import java.util.Iterator; -import java.util.concurrent.LinkedBlockingDeque; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class CircularBuffer<T> implements Iterable<T>{ - protected static Logger log = LoggerFactory.getLogger(CircularBuffer.class); - private final LinkedBlockingDeque<T> buffer; - - public CircularBuffer(int capacity) { - this.buffer = new LinkedBlockingDeque<T>(capacity); - } - - /** - * Adding an element to the circular buffer implies adding the element to - * the tail of the deque. In doing so, if the capacity of the buffer has - * been exhausted, then the deque's head should be removed to preserve the - * circular nature of the buffer. A LinkedBlockingDeque is used because of its - * concurrent nature and the fact that adding to tail and removing from head are - * both O(1) operations. The removed head is returned to the caller for reuse. - * - * @param e the element to be added - * @return removed element (for reuse) or null - */ - public T add(T e) { - T oldE = null; - while (!buffer.offerLast(e)) { - oldE = buffer.poll(); - } - return oldE; - } - - /** - * The basic idea here is that an ArrayList has been passed in, which may or may not - * have a size bigger that the actual number of elements that are meant to - * be flushed to the Circular Buffer. Thus the 'uptoIndex' parameter specifies - * the number of elements that need to be flushed starting from index 0. - * Note that after flushing, the circular buffer may return a memory unit (of type T) - * for reuse in the list, if the circular buffer popped off memory to preserve - * its circular nature. Or it may just return null if nothing was popped off. - * Either way, the list that is returned by this method, is of the SAME SIZE - * as the list passed in, as ArrayLists can hold null elements. The only difference - * is that the list returned will have elements that reference old popped-off memory - * from the circular-buffer or null. - * - * @param elist the ArrayList to flush into the circular buffer. - * @param uptoIndex flush starting from index 0 upto but not including - * index 'uptoIndex'. - * @return the 'elist' passed in with members now pointing to - * to null or old-memory for reuse. The returned list - * if of the same size as 'elist'. - */ - public ArrayList<T> addAll(ArrayList<T> elist, int uptoIndex) { - if (uptoIndex > elist.size()) { - log.error("uptoIndex is greater than elist size .. aborting addAll"); - return elist; - } - for (int index=0; index < uptoIndex; index++) { - T e = elist.get(index); - if (e != null) { - elist.set(index, add(e)); - } - } - return elist; - } - - /** - * Returns an iterator over the elements in the circular buffer in proper sequence. - * The elements will be returned in order from most-recent to oldest. - * The returned Iterator is a "weakly consistent" iterator that will never - * throw ConcurrentModificationException, and guarantees to traverse elements - * as they existed upon construction of the iterator, and may (but is not - * guaranteed to) reflect any modifications subsequent to construction. - */ - @Override - public Iterator<T> iterator() { - return buffer.descendingIterator(); - } - - public int size() { - return buffer.size(); - } - - /** - * Atomically removes all elements in the circular buffer - */ - public void clear() { - buffer.clear(); - } -} diff --git a/src/main/java/net/floodlightcontroller/debugevent/CustomFormatter.java b/src/main/java/net/floodlightcontroller/debugevent/CustomFormatter.java new file mode 100644 index 0000000000000000000000000000000000000000..947a291a1f33f6e6e0611f02ef4e3f2fe1ea5c77 --- /dev/null +++ b/src/main/java/net/floodlightcontroller/debugevent/CustomFormatter.java @@ -0,0 +1,13 @@ +package net.floodlightcontroller.debugevent; + +import net.floodlightcontroller.debugevent.EventResource.EventResourceBuilder; + +/** + * Format Event object based on its class and store accordingly in + * {@link EventResource} + */ +public interface CustomFormatter<T> { + + public abstract EventResourceBuilder + customFormat(T obj, String name, EventResourceBuilder edb); +} diff --git a/src/main/java/net/floodlightcontroller/debugevent/CustomFormatters.java b/src/main/java/net/floodlightcontroller/debugevent/CustomFormatters.java new file mode 100644 index 0000000000000000000000000000000000000000..6685461e10a68e54d52658756eebd2c77e8a4132 --- /dev/null +++ b/src/main/java/net/floodlightcontroller/debugevent/CustomFormatters.java @@ -0,0 +1,221 @@ +package net.floodlightcontroller.debugevent; + +import java.lang.ref.SoftReference; +import java.util.Collection; + +import javax.annotation.Nullable; + +import net.floodlightcontroller.debugevent.EventResource.EventResourceBuilder; +import net.floodlightcontroller.debugevent.EventResource.Metadata; +import net.floodlightcontroller.devicemanager.SwitchPort; + +import org.projectfloodlight.openflow.types.DatapathId; +import org.projectfloodlight.openflow.types.IPv4Address; +import org.projectfloodlight.openflow.util.HexString; + +import net.floodlightcontroller.packet.IPv4; + +import com.google.common.base.Joiner; + +class CustomFormatterCollectionAttachmentPoint implements + CustomFormatter<Collection<SwitchPort>> { + + @Override + public EventResourceBuilder + customFormat(@Nullable Collection<SwitchPort> aps2, String name, + EventResourceBuilder edb) { + if (aps2 != null) { + StringBuilder apsStr2 = new StringBuilder(); + if (aps2.size() == 0) { + apsStr2.append("--"); + } else { + for (SwitchPort ap : aps2) { + apsStr2.append(ap.getSwitchDPID().toString()); + apsStr2.append("/"); + apsStr2.append(ap.getPort()); + apsStr2.append(" "); + } + // remove trailing space + apsStr2.deleteCharAt(apsStr2.length()); + } + edb.dataFields.add(new Metadata(name, apsStr2.toString())); + } + return edb; + } +} + +class CustomFormatterCollectionIpv4 implements + CustomFormatter<Collection<IPv4Address>> { + + @Override + public EventResourceBuilder + customFormat(@Nullable Collection<IPv4Address> ipv4Addresses2, + String name, EventResourceBuilder edb) { + if (ipv4Addresses2 != null) { + String ipv4AddressesStr2 = "--"; + if (!ipv4Addresses2.isEmpty()) { + ipv4AddressesStr2 = Joiner.on(" ").join(ipv4Addresses2); + } + edb.dataFields.add(new Metadata(name, ipv4AddressesStr2)); + } + return edb; + } +} + +class CustomFormatterCollectionObject implements + CustomFormatter<Collection<Object>> { + + @Override + public EventResourceBuilder + customFormat(@Nullable Collection<Object> obl2, String name, + EventResourceBuilder edb) { + if (obl2 != null) { + StringBuilder sbldr2 = new StringBuilder(); + if (obl2.size() == 0) { + sbldr2.append("--"); + } else { + for (Object o : obl2) { + sbldr2.append(o.toString()); + sbldr2.append(" "); + } + } + edb.dataFields.add(new Metadata(name, sbldr2.toString())); + } + return edb; + } +} + +class CustomFormatterDpid implements CustomFormatter<DatapathId> { + + @Override + public EventResourceBuilder customFormat(@Nullable DatapathId dpid, + String name, + EventResourceBuilder edb) { + if (dpid != null) { + edb.dataFields.add(new Metadata(name, dpid.toString())); + } + return edb; + } + +} + +class CustomFormatterIpv4 implements CustomFormatter<Integer> { + + @Override + public EventResourceBuilder customFormat(@Nullable Integer obj, + String name, + EventResourceBuilder edb) { + if (obj != null) { + edb.dataFields.add(new Metadata(name, IPv4.fromIPv4Address(obj))); + } + return edb; + } + +} + +class CustomFormatterMac implements CustomFormatter<Long> { + + @Override + public EventResourceBuilder customFormat(@Nullable Long obj, + String name, + EventResourceBuilder edb) { + if (obj != null) { + edb.dataFields.add(new Metadata(name, HexString.toHexString(obj, + 6))); + } + return edb; + } + +} + +class CustomFormatterObject implements CustomFormatter<Object> { + + @Override + public EventResourceBuilder customFormat(@Nullable Object obj, + String name, + EventResourceBuilder edb) { + if (obj != null) { + edb.dataFields.add(new Metadata(name, obj.toString())); + } + return edb; + } + +} + +class CustomFormatterPrimitive implements CustomFormatter<Object> { + + @Override + public EventResourceBuilder customFormat(@Nullable Object obj, + String name, + EventResourceBuilder edb) { + if (obj != null) { + edb.dataFields.add(new Metadata(name, obj.toString())); + } + return edb; + } + +} + +class CustomFormatterSrefCollectionObject implements + CustomFormatter<SoftReference<Collection<Object>>> { + + @Override + public EventResourceBuilder customFormat(@Nullable + SoftReference<Collection<Object>> srefCollectionObj2, + String name, EventResourceBuilder edb) { + if (srefCollectionObj2 != null) { + Collection<Object> ol2 = srefCollectionObj2.get(); + if (ol2 != null) { + StringBuilder sb = new StringBuilder(); + if (ol2.size() == 0) { + sb.append("--"); + } else { + for (Object o : ol2) { + sb.append(o.toString()); + sb.append(" "); + } + } + edb.dataFields.add(new Metadata(name, sb.toString())); + } else { + edb.dataFields.add(new Metadata(name, + "-- reference not available --")); + } + } + return edb; + } + +} + +class CustomFormatterSrefObject implements CustomFormatter<SoftReference<Object>> { + + @Override + public EventResourceBuilder + customFormat(@Nullable SoftReference<Object> srefObj, + String name, EventResourceBuilder edb) { + if (srefObj != null) { + Object o = srefObj.get(); + if (o != null) { + edb.dataFields.add(new Metadata(name, o.toString())); + } else { + edb.dataFields.add(new Metadata(name, + "-- reference not available --")); + } + } + return edb; + } + +} + +class CustomFormatterString implements CustomFormatter<String> { + + @Override + public EventResourceBuilder customFormat(@Nullable String string, + String name, + EventResourceBuilder edb) { + if (string != null) { + edb.dataFields.add(new Metadata(name, string)); + } + return edb; + } + +} diff --git a/src/main/java/net/floodlightcontroller/debugevent/DebugEvent.java b/src/main/java/net/floodlightcontroller/debugevent/DebugEvent.java deleted file mode 100644 index 36e01d7c96ed549d593dc90ad1d5927d4d7d3a87..0000000000000000000000000000000000000000 --- a/src/main/java/net/floodlightcontroller/debugevent/DebugEvent.java +++ /dev/null @@ -1,513 +0,0 @@ -package net.floodlightcontroller.debugevent; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -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.debugevent.web.DebugEventRoutable; -import net.floodlightcontroller.restserver.IRestApiService; - -import com.google.common.collect.Sets; -/** - * This class implements a central store for all events used for debugging the - * system. The basic idea is that given the functionality provided by this class, - * it should be unnecessary to resort to scraping through system DEBUG/TRACE logs - * to understand behavior in a running system. - * - * @author Saurav - */ -public class DebugEvent implements IFloodlightModule, IDebugEventService { - protected static Logger log = LoggerFactory.getLogger(DebugEvent.class); - - /** - * Every registered event type gets an event id, the value for which is obtained - * while holding the lock. - */ - protected int eventIdCounter = 0; - protected Object eventIdLock = new Object(); - - private static final long MIN_FLUSH_DELAY = 100; //ms - private static final int PCT_LOCAL_CAP = 10; // % of global capacity - private static final int MIN_LOCAL_CAPACITY = 10; //elements - - /** - * Event Information - */ - public class EventInfo { - int eventId; - boolean enabled; - int bufferCapacity; - EventType etype; - String eventDesc; - String eventName; - String moduleName; - String moduleEventName; - Class<?> eventClass; - String[] metaData; - - public EventInfo(int eventId, boolean enabled, int bufferCapacity, - EventType etype, Class<?> eventClass, String eventDesc, - String eventName, String moduleName, String... metaData) { - this.enabled = enabled; - this.eventId = eventId; - this.bufferCapacity = bufferCapacity; - this.etype = etype; - this.eventClass = eventClass; - this.eventDesc = eventDesc; - this.eventName = eventName; - this.moduleName = moduleName; - this.moduleEventName = moduleName + "/" + eventName; - this.metaData = metaData; - } - - public int getEventId() { return eventId; } - public boolean isEnabled() { return enabled; } - public int getBufferCapacity() { return bufferCapacity; } - public EventType getEtype() { return etype; } - public String getEventDesc() { return eventDesc; } - public String getEventName() { return eventName; } - public String getModuleName() { return moduleName; } - public String getModuleEventName() { return moduleEventName; } - public String[] getMetaData() { return metaData; } - } - - //****************** - // Global stores - //****************** - - /** - * Event history for a particular event-id is stored in a circular buffer - */ - protected class DebugEventHistory { - EventInfo einfo; - CircularBuffer<Event> eventBuffer; - - public DebugEventHistory(EventInfo einfo, int capacity) { - this.einfo = einfo; - this.eventBuffer = new CircularBuffer<Event>(capacity); - } - } - - /** - * Global storage for all event types and their corresponding event buffers. - * A particular event type is accessed by directly indexing into the array - * with the corresponding event-id. - */ - protected DebugEventHistory[] allEvents = - new DebugEventHistory[MAX_EVENTS]; - - /** - * Global storage for all event ids registered for a module. The map is indexed - * by the module name and event name and returns the event-ids that correspond to the - * event types registered by that module (for example module 'linkdiscovery' - * may register events that have ids 0 and 1 that correspond to link up/down - * events, and receiving malformed LLDP packets, respectively). - */ - protected ConcurrentHashMap<String, ConcurrentHashMap<String, Integer>> - moduleEvents = new ConcurrentHashMap<String, - ConcurrentHashMap<String, Integer>>(); - - /** - * A collection of event ids that are currently enabled for logging - */ - protected Set<Integer> currentEvents = Collections.newSetFromMap( - new ConcurrentHashMap<Integer,Boolean>()); - - //****************** - // Thread local stores - //****************** - - /** - * Thread local storage for events - */ - protected class LocalEventHistory { - int nextIndex; - int maxCapacity; - boolean enabled; - ArrayList<Event> eventList; - long lastFlushTime; - boolean flushNow; - - public LocalEventHistory(boolean enabled, int maxCapacity, boolean flushNow) { - this.nextIndex = 0; - this.maxCapacity = maxCapacity; - this.enabled = enabled; - this.eventList = new ArrayList<Event>(); - this.flushNow = flushNow; - } - } - - /** - * Thread local event buffers used for maintaining event history local to - * a thread. Eventually this locally maintained information is flushed - * into the global event buffers. - */ - protected final ThreadLocal<LocalEventHistory[]> threadlocalEvents = - new ThreadLocal<LocalEventHistory[]>() { - @Override - protected LocalEventHistory[] initialValue() { - return new LocalEventHistory[MAX_EVENTS]; - } - }; - - /** - * Thread local cache for event-ids that are currently active. - */ - protected final ThreadLocal<Set<Integer>> threadlocalCurrentEvents = - new ThreadLocal<Set<Integer>>() { - @Override - protected Set<Integer> initialValue() { - return new HashSet<Integer>(); - } - }; - - //******************************* - // IEventUpdater - //******************************* - - protected class EventUpdaterImpl<T> implements IEventUpdater<T> { - private final int eventId; - - public EventUpdaterImpl(int evId) { - this.eventId = evId; - } - - @Override - public void updateEventNoFlush(Object event) { - if (!validEventId()) return; - updateEvent(eventId, false, event); - } - - @Override - public void updateEventWithFlush(Object event) { - if (!validEventId()) return; - updateEvent(eventId, true, event); - } - - private boolean validEventId() { - if (eventId < 0 || eventId >= MAX_EVENTS) { - throw new IllegalStateException(); - } - return true; - } - - } - - //******************************* - // IDebugEventService - //******************************* - - @Override - public <T> IEventUpdater<T> registerEvent(String moduleName, String eventName, - String eventDescription, EventType et, - Class<T> eventClass, int bufferCapacity, - String... metaData) throws MaxEventsRegistered { - int eventId = -1; - synchronized (eventIdLock) { - eventId = Integer.valueOf(eventIdCounter++); - } - if (eventId > MAX_EVENTS-1) { - throw new MaxEventsRegistered(); - } - - // register event id for moduleName - if (!moduleEvents.containsKey(moduleName)) { - moduleEvents.put(moduleName, new ConcurrentHashMap<String, Integer>()); - } - if (!moduleEvents.get(moduleName).containsKey(eventName)) { - moduleEvents.get(moduleName).put(eventName, new Integer(eventId)); - } else { - int existingEventId = moduleEvents.get(moduleName).get(eventName); - log.error("Duplicate event registration for moduleName {} eventName {}", - moduleName, eventName); - return new EventUpdaterImpl<T>(existingEventId); - } - - // create storage for event-type - boolean enabled = (et == EventType.ALWAYS_LOG) ? true : false; - EventInfo ei = new EventInfo(eventId, enabled, bufferCapacity, - et, eventClass, eventDescription, eventName, - moduleName, metaData); - allEvents[eventId] = new DebugEventHistory(ei, bufferCapacity); - if (enabled) { - currentEvents.add(eventId); - } - - return new EventUpdaterImpl<T>(eventId); - } - - private void updateEvent(int eventId, boolean flushNow, Object eventData) { - if (eventId < 0 || eventId > MAX_EVENTS-1) return; - - LocalEventHistory[] thishist = this.threadlocalEvents.get(); - if (thishist[eventId] == null) { - // seeing this event for the first time in this thread - create local - // store by consulting global store - DebugEventHistory de = allEvents[eventId]; - if (de != null) { - boolean enabled = de.einfo.enabled; - int localCapacity = de.einfo.bufferCapacity * PCT_LOCAL_CAP/ 100; - if (localCapacity < 10) localCapacity = MIN_LOCAL_CAPACITY; - thishist[eventId] = new LocalEventHistory(enabled, localCapacity, - flushNow); - if (enabled) { - Set<Integer> thisset = this.threadlocalCurrentEvents.get(); - thisset.add(eventId); - } - } else { - log.error("updateEvent seen locally for event {} but no global" - + "storage exists for it yet .. not updating", eventId); - return; - } - } - - // update local store if enabled locally for updating - LocalEventHistory le = thishist[eventId]; - if (le.enabled) { - long timestamp = System.currentTimeMillis(); - long thisthread = Thread.currentThread().getId(); - String thisthreadname = Thread.currentThread().getName(); - if (le.nextIndex < le.eventList.size()) { - if (le.eventList.get(le.nextIndex) == null) { - le.eventList.set(le.nextIndex, new Event(timestamp, thisthread, - thisthreadname, - eventData)); - } else { - Event e = le.eventList.get(le.nextIndex); - e.timestamp = timestamp; - e.threadId = thisthread; - e.eventData = eventData; - } - } else { - le.eventList.add(new Event(timestamp, thisthread, thisthreadname, eventData)); - } - le.nextIndex++; - - if (le.nextIndex >= le.maxCapacity || le.flushNow) { - // flush this buffer now - DebugEventHistory de = allEvents[eventId]; - if (de.einfo.enabled) { - le.eventList = de.eventBuffer.addAll(le.eventList, le.nextIndex); - } else { - // global buffer is disabled - don't flush, disable locally - le.enabled = false; - Set<Integer> thisset = this.threadlocalCurrentEvents.get(); - thisset.remove(eventId); - } - le.nextIndex = 0; - le.lastFlushTime = timestamp; - } - } - } - - @Override - public void flushEvents() { - LocalEventHistory[] thishist = this.threadlocalEvents.get(); - Set<Integer> thisset = this.threadlocalCurrentEvents.get(); - long timestamp = System.currentTimeMillis(); - ArrayList<Integer> temp = new ArrayList<Integer>(); - - for (int eventId : thisset) { - LocalEventHistory le = thishist[eventId]; - if (le != null && le.nextIndex > 0 && - (le.flushNow || (timestamp - le.lastFlushTime) > MIN_FLUSH_DELAY)) { - // flush this buffer now - DebugEventHistory de = allEvents[eventId]; - if (de.einfo.enabled) { - le.eventList = de.eventBuffer.addAll(le.eventList, le.nextIndex); - } else { - // global buffer is disabled - don't flush, disable locally - le.enabled = false; - temp.add(eventId); - } - le.nextIndex = 0; - le.lastFlushTime = timestamp; - } - } - for (int eId : temp) - thisset.remove(eId); - - // sync thread local currently enabled set of eventIds with global set. - Sets.SetView<Integer> sv = Sets.difference(currentEvents, thisset); - for (int eventId : sv) { - if (thishist[eventId] != null) { - thishist[eventId].enabled = true; - thisset.add(eventId); - } - } - - } - - @Override - public boolean containsModuleEventName(String moduleName, String eventName) { - if (!moduleEvents.containsKey(moduleName)) return false; - if (moduleEvents.get(moduleName).containsKey(eventName)) return true; - return false; - } - - @Override - public boolean containsModuleName(String moduleName) { - return moduleEvents.containsKey(moduleName); - } - - @Override - public List<DebugEventInfo> getAllEventHistory() { - List<DebugEventInfo> moduleEventList = new ArrayList<DebugEventInfo>(); - for (Map<String, Integer> modev : moduleEvents.values()) { - for (int eventId : modev.values()) { - DebugEventHistory de = allEvents[eventId]; - if (de != null) { - List<Map<String,String>> ret = new ArrayList<Map<String,String>>(); - for (Event e : de.eventBuffer) { - ret.add(e.getFormattedEvent(de.einfo.eventClass, - de.einfo.moduleEventName)); - } - moduleEventList.add(new DebugEventInfo(de.einfo, ret)); - } - } - } - return moduleEventList; - } - - @Override - public List<DebugEventInfo> getModuleEventHistory(String moduleName) { - if (!moduleEvents.containsKey(moduleName)) return Collections.emptyList(); - List<DebugEventInfo> moduleEventList = new ArrayList<DebugEventInfo>(); - for (int eventId : moduleEvents.get(moduleName).values()) { - DebugEventHistory de = allEvents[eventId]; - if (de != null) { - List<Map<String,String>> ret = new ArrayList<Map<String,String>>(); - for (Event e : de.eventBuffer) { - ret.add(e.getFormattedEvent(de.einfo.eventClass, - de.einfo.moduleEventName)); - } - moduleEventList.add(new DebugEventInfo(de.einfo, ret)); - } - } - return moduleEventList; - } - - @Override - public DebugEventInfo getSingleEventHistory(String moduleName, String eventName, - int last) { - if (!moduleEvents.containsKey(moduleName)) return null; - Integer eventId = moduleEvents.get(moduleName).get(eventName); - if (eventId == null) return null; - DebugEventHistory de = allEvents[eventId]; - if (de != null) { - int num = 1; - List<Map<String,String>> ret = new ArrayList<Map<String,String>>(); - for (Event e : de.eventBuffer) { - if (num > last) - break; - Map<String, String> temp = e.getFormattedEvent(de.einfo.eventClass, - de.einfo.moduleEventName); - temp.put("#", String.valueOf(num++)); - ret.add(temp); - } - return new DebugEventInfo(de.einfo, ret); - } - return null; - } - - @Override - public void resetAllEvents() { - for (Map<String, Integer> eventMap : moduleEvents.values()) { - for (Integer evId : eventMap.values()) { - allEvents[evId].eventBuffer.clear(); - } - } - } - - @Override - public void resetAllModuleEvents(String moduleName) { - if (!moduleEvents.containsKey(moduleName)) return; - Map<String, Integer> modEvents = moduleEvents.get(moduleName); - for (Integer evId : modEvents.values()) { - allEvents[evId].eventBuffer.clear(); - } - } - - @Override - public void resetSingleEvent(String moduleName, String eventName) { - if (!moduleEvents.containsKey(moduleName)) return; - Integer eventId = moduleEvents.get(moduleName).get(eventName); - if (eventId == null) return; - DebugEventHistory de = allEvents[eventId]; - if (de != null) { - de.eventBuffer.clear(); - } - } - - @Override - public List<String> getModuleList() { - List<String> el = new ArrayList<String>(); - el.addAll(moduleEvents.keySet()); - return el; - } - - @Override - public List<String> getModuleEventList(String moduleName) { - if (!moduleEvents.containsKey(moduleName)) - return Collections.emptyList(); - List<String> el = new ArrayList<String>(); - el.addAll(moduleEvents.get(moduleName).keySet()); - return el; - } - - //******************************* - // IFloodlightModule - //******************************* - - @Override - public Collection<Class<? extends IFloodlightService>> getModuleServices() { - Collection<Class<? extends IFloodlightService>> l = - new ArrayList<Class<? extends IFloodlightService>>(); - l.add(IDebugEventService.class); - return l; - } - - @Override - public Map<Class<? extends IFloodlightService>, IFloodlightService> getServiceImpls() { - Map<Class<? extends IFloodlightService>, IFloodlightService> m = - new HashMap<Class<? extends IFloodlightService>, IFloodlightService>(); - m.put(IDebugEventService.class, this); - return m; - } - - @Override - public Collection<Class<? extends IFloodlightService>> getModuleDependencies() { - ArrayList<Class<? extends IFloodlightService>> deps = - new ArrayList<Class<? extends IFloodlightService>>(); - deps.add(IRestApiService.class); - return deps; - } - - @Override - public void init(FloodlightModuleContext context) - throws FloodlightModuleException { - } - - @Override - public void startUp(FloodlightModuleContext context) - throws FloodlightModuleException { - IRestApiService restService = - context.getServiceImpl(IRestApiService.class); - restService.addRestletRoutable(new DebugEventRoutable()); - DebugEventAppender.setDebugEventServiceImpl(this); - } - -} diff --git a/src/main/java/net/floodlightcontroller/debugevent/DebugEventAppender.java b/src/main/java/net/floodlightcontroller/debugevent/DebugEventAppender.java index 3429675d7b60829c0fa18b9e106ac1cd09b75534..ea44f9af18a800fde6e84742cf28965bb8878b89 100644 --- a/src/main/java/net/floodlightcontroller/debugevent/DebugEventAppender.java +++ b/src/main/java/net/floodlightcontroller/debugevent/DebugEventAppender.java @@ -3,28 +3,32 @@ package net.floodlightcontroller.debugevent; 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 ch.qos.logback.classic.Level; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.UnsynchronizedAppenderBase; public class DebugEventAppender<E> extends UnsynchronizedAppenderBase<E> { static IDebugEventService debugEvent; - static IEventUpdater<WarnErrorEvent> evWarnError; - static Thread debugEventRegistryTask = new Thread() { - @Override - public void run() { - while(DebugEventAppender.debugEvent == null) { - try { - Thread.sleep(100); - } catch (InterruptedException e) { - return; + static IEventCategory<WarnErrorEvent> evWarnError; + static final Thread debugEventRegistryTask; + static { + debugEventRegistryTask = new Thread() { + @Override + public void run() { + while (DebugEventAppender.debugEvent == null) { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + return; + } } + // safe to register debugEvent + registerDebugEventQueue(); } - //safe to register debugEvent - registerDebugEventQueue(); - } - }; + }; + debugEventRegistryTask.setDaemon(true); + } @Override public void start() { @@ -32,16 +36,17 @@ public class DebugEventAppender<E> extends UnsynchronizedAppenderBase<E> { super.start(); } - public static void setDebugEventServiceImpl(IDebugEventService debugEvent) { + public static void + setDebugEventServiceImpl(IDebugEventService debugEvent) { DebugEventAppender.debugEvent = debugEvent; // It is now ok to register an event Q - but letting this thread go // since it was called from a startUp() routine } /** - * The logging system calls append for every log message. This method filters - * out the WARN and ERROR message and adds to a debug event queue that can - * be accessed via cli or rest-api or gui. + * The logging system calls append for every log message. This method + * filters out the WARN and ERROR message and adds to a debug event queue + * that can be accessed via cli or rest-api or gui. */ @Override protected void append(E eventObject) { @@ -50,24 +55,26 @@ public class DebugEventAppender<E> extends UnsynchronizedAppenderBase<E> { } if (evWarnError != null) { ILoggingEvent ev = ((ILoggingEvent) eventObject); - if (ev.getLevel().equals(Level.ERROR) || ev.getLevel().equals(Level.WARN)) { - evWarnError.updateEventWithFlush( - new WarnErrorEvent(ev.getFormattedMessage(), ev.getLevel(), - ev.getThreadName(), ev.getLoggerName())); + if (ev.getLevel().equals(Level.ERROR) + || ev.getLevel().equals(Level.WARN)) { + evWarnError + .newEventWithFlush(new WarnErrorEvent(ev.getFormattedMessage(), + ev.getLevel(), + ev.getThreadName(), + ev.getLoggerName())); } } } private static void registerDebugEventQueue() { - try { - evWarnError = debugEvent.registerEvent("net.floodlightcontroller.core", - "warn-error-queue", - "all WARN and ERROR logs", - EventType.ALWAYS_LOG, WarnErrorEvent.class, - 100); - } catch (MaxEventsRegistered e) { - e.printStackTrace(); - } + evWarnError = debugEvent.buildEvent(WarnErrorEvent.class) + .setModuleName("net.floodlightcontroller.core") + .setEventName("warn-error-queue") + .setEventDescription("all WARN and ERROR logs") + .setEventType(EventType.ALWAYS_LOG) + .setBufferCapacity(100) + .setAckable(false) + .register(); } @@ -78,14 +85,15 @@ public class DebugEventAppender<E> extends UnsynchronizedAppenderBase<E> { @EventColumn(name = "level", description = EventFieldType.OBJECT) Level level; - @EventColumn(name = "threadName", description = EventFieldType.STRING) + @EventColumn(name = "threadName", + description = EventFieldType.STRING) String threadName; @EventColumn(name = "logger", description = EventFieldType.OBJECT) String logger; - public WarnErrorEvent(String message, Level level, String threadName, - String logger) { + public WarnErrorEvent(String message, Level level, + String threadName, String logger) { this.message = message; this.level = level; this.threadName = threadName; diff --git a/src/main/java/net/floodlightcontroller/debugevent/DebugEventResource.java b/src/main/java/net/floodlightcontroller/debugevent/DebugEventResource.java new file mode 100644 index 0000000000000000000000000000000000000000..ff93c405e9d1ab1c274701de11bbadd3c3d21b1e --- /dev/null +++ b/src/main/java/net/floodlightcontroller/debugevent/DebugEventResource.java @@ -0,0 +1,196 @@ +package net.floodlightcontroller.debugevent; + +import java.util.List; + +import javax.annotation.concurrent.Immutable; + +import net.floodlightcontroller.debugevent.DebugEventService.EventInfo; +import net.floodlightcontroller.debugevent.IDebugEventService.EventType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.collect.ComparisonChain; +import com.google.common.collect.ImmutableList; + +/** + * All the Immutable Resource classes for REST API. + */ +@Immutable +public class DebugEventResource { + + protected static final Logger logger = LoggerFactory.getLogger(DebugEventResource.class); + public static final String MODULE_NAME_PREDICATE = "module-name"; + public static final String EVENT_NAME_PREDICATE = "event-name"; + public static final String LAST_PREDICATE = "num-of-events"; + public static final String EVENT_ID = "event-id"; + public static final String EVENT_INSTANCE_ID = "event-instance-id"; + public static final String ACKED = "acked"; + + /** + * Resource class for {@link EventInfo}. Used to create Immutable objects + * returned in response to REST calls. + */ + @Immutable + public static class EventInfoResource implements + Comparable<EventInfoResource> { + + private final int eventId; + private final boolean enabled; + private final int bufferCapacity; + private final EventType etype; + private final String eventDesc; + private final String eventName; + private final String moduleName; + private final int numOfEvents; + private final boolean ackable; + public final ImmutableList<EventResource> events; + + public EventInfoResource(EventInfo eventInfo, + List<EventResource> events) { + super(); + this.eventId = eventInfo.getEventId(); + this.enabled = eventInfo.isEnabled(); + this.bufferCapacity = eventInfo.getBufferCapacity(); + this.etype = eventInfo.getEtype(); + this.eventDesc = eventInfo.getEventDesc(); + this.eventName = eventInfo.getEventName(); + this.moduleName = eventInfo.getModuleName(); + this.numOfEvents = eventInfo.getNumOfEvents(); + this.ackable = eventInfo.isAckable(); + this.events = ImmutableList.copyOf(events); + } + + public boolean isEnabled() { + return enabled; + } + + public int getBufferCapacity() { + return bufferCapacity; + } + + public EventType getEtype() { + return etype; + } + + public String getEventDesc() { + return eventDesc; + } + + public String getEventName() { + return eventName; + } + + public String getModuleName() { + return moduleName; + } + + public int getNumOfEvents() { + return numOfEvents; + } + + public boolean isAckable() { + return ackable; + } + + public List<EventResource> getEvents() { + return events; + } + + public int getEventId() { + return eventId; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + (ackable ? 1231 : 1237); + result = prime * result + bufferCapacity; + result = prime * result + (enabled ? 1231 : 1237); + result = prime * result + + ((etype == null) ? 0 : etype.hashCode()); + result = prime * result + + ((eventDesc == null) ? 0 : eventDesc.hashCode()); + result = prime * result + eventId; + result = prime * result + + ((eventName == null) ? 0 : eventName.hashCode()); + result = prime * result + + ((events == null) ? 0 : events.hashCode()); + result = prime * result + + ((moduleName == null) ? 0 : moduleName.hashCode()); + result = prime * result + numOfEvents; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + EventInfoResource other = (EventInfoResource) obj; + if (ackable != other.ackable) return false; + if (bufferCapacity != other.bufferCapacity) return false; + if (enabled != other.enabled) return false; + if (etype != other.etype) return false; + if (eventDesc == null) { + if (other.eventDesc != null) return false; + } else if (!eventDesc.equals(other.eventDesc)) return false; + if (eventId != other.eventId) return false; + if (eventName == null) { + if (other.eventName != null) return false; + } else if (!eventName.equals(other.eventName)) return false; + if (events == null) { + if (other.events != null) return false; + } else if (!events.equals(other.events)) return false; + if (moduleName == null) { + if (other.moduleName != null) return false; + } else if (!moduleName.equals(other.moduleName)) return false; + if (numOfEvents != other.numOfEvents) return false; + return true; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("EventInfoResource [eventId="); + builder.append(eventId); + builder.append(", enabled="); + builder.append(enabled); + builder.append(", bufferCapacity="); + builder.append(bufferCapacity); + builder.append(", etype="); + builder.append(etype); + builder.append(", eventDesc="); + builder.append(eventDesc); + builder.append(", eventName="); + builder.append(eventName); + builder.append(", moduleName="); + builder.append(moduleName); + builder.append(", numOfEvents="); + builder.append(numOfEvents); + builder.append(", ackable="); + builder.append(ackable); + builder.append(", events="); + builder.append(events); + builder.append("]"); + return builder.toString(); + } + + /** + * The natural order of this class is ascending on eventId and + * consistent with equals. + */ + @Override + public int compareTo(EventInfoResource o) { + return ComparisonChain.start().compare(eventId, o.eventId) + .compareFalseFirst(enabled, o.enabled) + .compare(bufferCapacity, o.bufferCapacity) + .compare(etype, o.etype) + .compare(eventDesc, o.eventDesc) + .compare(eventName, o.eventName) + .compare(moduleName, o.moduleName) + .compare(numOfEvents, o.numOfEvents) + .result(); + } + } +} diff --git a/src/main/java/net/floodlightcontroller/debugevent/DebugEventService.java b/src/main/java/net/floodlightcontroller/debugevent/DebugEventService.java new file mode 100644 index 0000000000000000000000000000000000000000..47a4ad77bd3358f24f574056c080ddc7b7ea2139 --- /dev/null +++ b/src/main/java/net/floodlightcontroller/debugevent/DebugEventService.java @@ -0,0 +1,712 @@ +package net.floodlightcontroller.debugevent; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.LinkedBlockingDeque; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; + +import net.floodlightcontroller.core.IShutdownListener; +import net.floodlightcontroller.core.IShutdownService; +import net.floodlightcontroller.core.module.FloodlightModuleContext; +import net.floodlightcontroller.core.module.IFloodlightModule; +import net.floodlightcontroller.core.module.IFloodlightService; +import net.floodlightcontroller.debugevent.DebugEventResource.EventInfoResource; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Sets; + +/** + * This class implements a central store for all events used for debugging the + * system. The basic idea is that given the functionality provided by this + * class, it should be unnecessary to resort to scraping through system + * DEBUG/TRACE logs to understand behavior in a running system. + * + * @author Saurav + */ +public class DebugEventService implements IFloodlightModule, IDebugEventService { + protected static final Logger log = LoggerFactory.getLogger(DebugEventService.class); + + /** + * Every registered event type gets an event id AtomicInt to make the get + * operation thread-safe + */ + private final AtomicInteger eventIdCounter = new AtomicInteger(); + + /** + * Unique Event Instance Id for ack-ing + */ + private final AtomicLong eventInstanceId = new AtomicLong(Long.MAX_VALUE); + + private static final int PCT_LOCAL_CAP = 10; // % of global capacity + private static final int MIN_LOCAL_CAPACITY = 10; // elements + + /** + * EnumMap from {@link EventFieldType} to {@link CustomFormatter} + */ + static final ImmutableMap<EventFieldType, CustomFormatter<?>> customFormatter = + new ImmutableMap.Builder<EventFieldType, CustomFormatter<?>>() + .put(EventFieldType.DPID, new CustomFormatterDpid()) + .put(EventFieldType.IPv4, new CustomFormatterIpv4()) + .put(EventFieldType.MAC, new CustomFormatterMac()) + .put(EventFieldType.STRING, new CustomFormatterString()) + .put(EventFieldType.OBJECT, new CustomFormatterObject()) + .put(EventFieldType.PRIMITIVE, new CustomFormatterPrimitive()) + .put(EventFieldType.COLLECTION_IPV4, new CustomFormatterCollectionIpv4()) + .put(EventFieldType.COLLECTION_ATTACHMENT_POINT, new CustomFormatterCollectionAttachmentPoint()) + .put(EventFieldType.COLLECTION_OBJECT, new CustomFormatterCollectionObject()) + .put(EventFieldType.SREF_COLLECTION_OBJECT, new CustomFormatterSrefCollectionObject()) + .put(EventFieldType.SREF_OBJECT, new CustomFormatterSrefObject()) + .build(); + + /** + * Event Information + */ + public static class EventInfo { + private final int eventId; + private final boolean enabled; + private final int bufferCapacity; + private int numOfEvents; + private final EventType etype; + private final String eventDesc; + private final String eventName; + private final String moduleName; + private final String moduleEventName; + private final Class<?> eventClass; + private final boolean ackable; + + public EventInfo(int eventId, boolean enabled, boolean ackable, + int bufferCapacity, EventType etype, + Class<?> eventClass, String eventDesc, + String eventName, String moduleName) { + this.enabled = enabled; + this.ackable = ackable; + this.eventId = eventId; + this.bufferCapacity = bufferCapacity; + this.numOfEvents = bufferCapacity; + this.etype = etype; + this.eventClass = eventClass; + this.eventDesc = eventDesc; + this.eventName = eventName; + this.moduleName = moduleName; + this.moduleEventName = moduleName + "/" + eventName; + } + + public int getEventId() { + return eventId; + } + + public boolean isEnabled() { + return enabled; + } + + public boolean isAckable() { + return ackable; + } + + public int getBufferCapacity() { + return bufferCapacity; + } + + public int getNumOfEvents() { + return numOfEvents; + } + + public EventType getEtype() { + return etype; + } + + public String getEventDesc() { + return eventDesc; + } + + public String getEventName() { + return eventName; + } + + public String getModuleName() { + return moduleName; + } + + public String getModuleEventName() { + return moduleEventName; + } + } + + // ****************** + // Global stores + // ****************** + + /** + * Event history for a particular event-id is stored in a circular buffer + */ + protected static class DebugEventHistory { + EventInfo einfo; + LinkedBlockingDeque<Event> circularEventBuffer; + + public DebugEventHistory(EventInfo einfo, int capacity) { + this.einfo = einfo; + this.circularEventBuffer = new LinkedBlockingDeque<Event>( + capacity); + } + } + + /** + * Global storage for all event types and their corresponding event buffers. + * A particular event type is accessed by directly indexing into the Map + * with the corresponding event-id. <br/> + * It is a <b>Map</b> with <br/> + * <b>Key</b> Integer eventId <br/> + * <b>Value</b> DebugEventHistory + */ + protected final ConcurrentHashMap<Integer, DebugEventHistory> allEvents = new ConcurrentHashMap<Integer, DebugEventHistory>(); + + /** + * Global storage for all event ids registered for a module. The map is + * indexed by the module name and event name and returns the event-ids that + * correspond to the event types registered by that module (for example + * module 'linkdiscovery' may register events that have ids 0 and 1 that + * correspond to link up/down events, and receiving malformed LLDP packets, + * respectively). + */ + protected final ConcurrentHashMap<String, ConcurrentHashMap<String, Integer>> moduleEvents = new ConcurrentHashMap<String, ConcurrentHashMap<String, Integer>>(); + + /** + * A collection of event ids that are currently enabled for logging + */ + protected final Set<Integer> currentEvents = Collections.newSetFromMap(new ConcurrentHashMap<Integer, Boolean>()); + + // ****************** + // Thread local stores + // ****************** + + /** + * Thread local storage for events + */ + protected static class LocalEventHistory { + private boolean enabled; + private final int capacity; + private ArrayList<Event> eventList; + + public LocalEventHistory(boolean enabled, int maxCapacity) { + this.enabled = enabled; + this.eventList = new ArrayList<Event>(maxCapacity); + this.capacity = maxCapacity; + } + + public boolean add(Event e) { + if (this.eventList.size() < capacity) { + this.eventList.add(e); + return true; + } + return false; + } + + public int drainTo(List<Event> eventList) { + int size = this.eventList.size(); + Iterator<Event> iter = this.eventList.iterator(); + while (iter.hasNext()) { + eventList.add(iter.next()); + } + this.eventList.clear(); + return size; + } + + public boolean isFull() { + if (eventList.size() == capacity) return true; + return false; + } + + public boolean isEmpty() { + return this.eventList.isEmpty(); + } + } + + /** + * Thread local event buffers used for maintaining event history local to a + * thread. Eventually this locally maintained information is flushed into + * the global event buffers. <br/> + * It is a <b>Map</b> with <br/> + * <b>Key</b> Integer eventId <br/> + * <b>Value</b> LocalEventHistory + */ + protected final ThreadLocal<Map<Integer, LocalEventHistory>> threadlocalEvents = new ThreadLocal<Map<Integer, LocalEventHistory>>() { + @Override + protected Map<Integer, LocalEventHistory> initialValue() { + return new HashMap<Integer, LocalEventHistory>(); + } + }; + + /** + * Thread local cache for event-ids that are currently active. + */ + protected final ThreadLocal<Set<Integer>> threadlocalCurrentEvents = new ThreadLocal<Set<Integer>>() { + @Override + protected Set<Integer> initialValue() { + return new HashSet<Integer>(); + } + }; + + // ******************************* + // IEventCategory + // ******************************* + + protected class EventCategory<T> implements IEventCategory<T> { + private final int eventId; + + public EventCategory(int evId) { + this.eventId = evId; + } + + @Override + public void newEventNoFlush(Object event) { + if (!validEventId()) return; + newEvent(eventId, false, event); + } + + @Override + public void newEventWithFlush(Object event) { + if (!validEventId()) return; + newEvent(eventId, true, event); + } + + private boolean validEventId() { + if (eventId < 0) { + throw new IllegalStateException(); + } + return true; + } + } + + public class EventCategoryBuilder<T> { + private int eventId; + private String moduleName; + private String eventName; + private String eventDescription; + private EventType eventType; + private Class<T> eventClass; + private int bufferCapacity; + private boolean ackable; + + public EventCategoryBuilder(Class<T> evClass) { + this.eventId = eventIdCounter.incrementAndGet(); + this.eventClass = evClass; + } + + public EventCategoryBuilder<T> setModuleName(String moduleName) { + this.moduleName = moduleName; + return this; + } + + public EventCategoryBuilder<T> setEventName(String eventName) { + this.eventName = eventName; + return this; + } + + public EventCategoryBuilder<T> setEventDescription(String eventDescription) { + this.eventDescription = eventDescription; + return this; + } + + public EventCategoryBuilder<T> setEventType(EventType et) { + this.eventType = et; + return this; + } + + public EventCategoryBuilder<T> setBufferCapacity(int bufferCapacity) { + this.bufferCapacity = bufferCapacity; + return this; + } + + public EventCategoryBuilder<T> setAckable(boolean ackable) { + this.ackable = ackable; + return this; + } + + /** + * Build a new {@link EventCategory<T>} <br/> + * Requires the following parameters to be set before being called: + * + * @param moduleName + * module registering event eg. linkdiscovery, virtualrouting. + * @param eventName + * name given to event. + * @param eventDescription + * A descriptive string describing the event. + * @param eventType + * EventType for this event. On-demand events have to be + * explicitly enabled using other methods in this API + * @param eventClass + * A user defined class that annotates the fields with + * @EventColumn. This class specifies the fields/columns for this + * event. + * @param bufferCapacity + * Number of events to store for this event in a circular buffer. + * Older events will be discarded once the buffer is full. + * @param ackable + * is the event used as part of ackable-event framework boolean + * + * @return IEventCategory with <b>newEvent</b> method that can be used + * to create instances of event of the given eventClass + */ + public EventCategory<T> register() { + // register event id for moduleName + moduleEvents.putIfAbsent(moduleName, + new ConcurrentHashMap<String, Integer>()); + Integer eventExists = moduleEvents.get(moduleName) + .putIfAbsent(eventName, eventId); + if (eventExists != null) { + log.error("Duplicate event registration for moduleName {} eventName {}", + moduleName, eventName); + return new EventCategory<T>(eventExists); + } + + // create storage for event-type + boolean enabled = (eventType == EventType.ALWAYS_LOG) ? true : false; + EventInfo ei = new EventInfo(eventId, enabled, ackable, + bufferCapacity, eventType, eventClass, + eventDescription, eventName, moduleName); + allEvents.put(eventId, new DebugEventHistory(ei, bufferCapacity)); + if (enabled) { + currentEvents.add(eventId); + } + + return new EventCategory<T>(this.eventId); + } + } + + // ******************************* + // IDebugEventService + // ******************************* + + @Override + public <T> EventCategoryBuilder<T> buildEvent(Class<T> evClass) { + return new EventCategoryBuilder<T>(evClass); + } + + private void flushLocalToGlobal(int eventId, LocalEventHistory le) { + DebugEventHistory de = allEvents.get(eventId); + if (de.einfo.enabled) { + List<Event> transferEvents = new ArrayList<Event>(); + // drain local buffer to Collection + int size = le.drainTo(transferEvents); + // if global buffer doesn't have enough space, clear + // some space + int requiredSpace = size + - de.circularEventBuffer.remainingCapacity(); + if (requiredSpace > 0) { + for (int i = 0; i < requiredSpace; i++) { + de.circularEventBuffer.removeFirst(); + } + } + de.circularEventBuffer.addAll(transferEvents); + } else { + le.enabled = false; + this.threadlocalCurrentEvents.get().remove(eventId); + } + } + + private void newEvent(int eventId, boolean flushNow, Object eventData) { + if (eventId < 0) { + throw new IllegalStateException("Invalid eventId"); + } + + Map<Integer, LocalEventHistory> thishist = this.threadlocalEvents.get(); + if (!thishist.containsKey(eventId)) { + // seeing this event for the first time in this thread - create + // local + // store by consulting global store + if (allEvents.containsKey(eventId)) { + DebugEventHistory de = allEvents.get(eventId); + boolean enabled = de.einfo.enabled; + int localCapacity = de.einfo.bufferCapacity * PCT_LOCAL_CAP + / 100; + if (localCapacity < 10) localCapacity = MIN_LOCAL_CAPACITY; + thishist.put(eventId, new LocalEventHistory(enabled, + localCapacity)); + if (enabled) { + Set<Integer> thisset = this.threadlocalCurrentEvents.get(); + thisset.add(eventId); + } + } else { + log.error("updateEvent seen locally for event {} but no global" + + "storage exists for it yet .. not updating", + eventId); + return; + } + } + + // update local store if enabled locally for updating + LocalEventHistory le = thishist.get(eventId); + if (le.enabled) { + try { + le.add(new Event(System.currentTimeMillis(), + Thread.currentThread().getId(), + Thread.currentThread().getName(), + eventData, + /* + * the eventInstanceId is started in reverse + * order so BigDB gets the values in ascending + * order Initialization in startUp() + */ + eventInstanceId.decrementAndGet())); + + if (le.isFull() || flushNow) { + flushLocalToGlobal(eventId, le); + } + } catch (IllegalStateException ise) { + log.debug("Exception while adding event locally: " + + ise.getMessage()); + } + } + } + + @Override + public void flushEvents() { + Map<Integer, LocalEventHistory> thishist = this.threadlocalEvents.get(); + Set<Integer> thisset = this.threadlocalCurrentEvents.get(); + + for (int eventId : thisset) { + if (thishist.containsKey(eventId)) { + LocalEventHistory le = thishist.get(eventId); + if (!le.isEmpty()) { + flushLocalToGlobal(eventId, le); + } + } + } + + // sync thread local currently enabled set of eventIds with global set. + Sets.SetView<Integer> sv = Sets.difference(currentEvents, thisset); + for (int eventId : sv) { + if (thishist.containsKey(eventId)) { + thishist.get(eventId).enabled = true; + thisset.add(eventId); + } + } + + } + + @Override + public boolean containsModuleEventName(String moduleName, + String eventName) { + if (!moduleEvents.containsKey(moduleName)) return false; + if (moduleEvents.get(moduleName).containsKey(eventName)) + return true; + return false; + } + + @Override + public boolean containsModuleName(String moduleName) { + return moduleEvents.containsKey(moduleName); + } + + @Override + public List<EventInfoResource> getAllEventHistory() { + List<EventInfoResource> moduleEventList = new ArrayList<EventInfoResource>(); + for (Map<String, Integer> modev : moduleEvents.values()) { + for (int eventId : modev.values()) { + if (allEvents.containsKey(eventId)) { + DebugEventHistory de = allEvents.get(eventId); + + List<EventResource> eventData = new ArrayList<EventResource>(); + // take snapshot and iterate + Iterator<Event> iter = de.circularEventBuffer.descendingIterator(); + while (iter.hasNext()) { + Event e = iter.next(); + eventData.add(e.getFormattedEvent(de.einfo.eventClass, + de.einfo.moduleEventName)); + } + moduleEventList.add(new EventInfoResource(de.einfo, + eventData)); + } + } + } + traceLogDebugHistory(moduleEventList); + return moduleEventList; + } + + @Override + public List<EventInfoResource> getModuleEventHistory(String moduleName) { + if (!moduleEvents.containsKey(moduleName)) + return Collections.emptyList(); + List<EventInfoResource> moduleEventList = new ArrayList<EventInfoResource>(); + for (int eventId : moduleEvents.get(moduleName).values()) { + if (allEvents.containsKey(eventId)) { + DebugEventHistory de = allEvents.get(eventId); + + List<EventResource> eventData = new ArrayList<EventResource>(); + // take snapshot and iterate + Iterator<Event> iter = de.circularEventBuffer.descendingIterator(); + while (iter.hasNext()) { + Event e = iter.next(); + eventData.add(e.getFormattedEvent(de.einfo.eventClass, + de.einfo.moduleEventName)); + } + moduleEventList.add(new EventInfoResource(de.einfo, + eventData)); + } + } + traceLogDebugHistory(moduleEventList); + return moduleEventList; + } + + @Override + public EventInfoResource getSingleEventHistory(String moduleName, + String eventName, + int numOfEvents) { + if (!moduleEvents.containsKey(moduleName)) return null; + Integer eventId = moduleEvents.get(moduleName).get(eventName); + if (eventId == null) return null; + + if (!allEvents.containsKey(eventId)) return null; + + DebugEventHistory de = allEvents.get(eventId); + if (numOfEvents == 0) numOfEvents = de.einfo.bufferCapacity; + + de.einfo.numOfEvents = numOfEvents; + int num = 1; + List<EventResource> eventData = new ArrayList<EventResource>(); + // take snapshot and iterate + Iterator<Event> iter = de.circularEventBuffer.descendingIterator(); + while (iter.hasNext()) { + Event e = iter.next(); + if (num > numOfEvents) break; + eventData.add(e.getFormattedEvent(de.einfo.eventClass, + de.einfo.moduleEventName)); + num++; + } + EventInfoResource ret = new EventInfoResource(de.einfo, eventData); + traceLogDebugHistory(Collections.singletonList(ret)); + return ret; + } + + @Override + public void resetAllEvents() { + for (Map<String, Integer> eventMap : moduleEvents.values()) { + for (Integer evId : eventMap.values()) { + allEvents.get(evId).circularEventBuffer.clear(); + } + } + } + + @Override + public void resetAllModuleEvents(String moduleName) { + if (!moduleEvents.containsKey(moduleName)) return; + Map<String, Integer> modEvents = moduleEvents.get(moduleName); + for (Integer evId : modEvents.values()) { + allEvents.get(evId).circularEventBuffer.clear(); + } + } + + @Override + public void resetSingleEvent(String moduleName, String eventName) { + if (!moduleEvents.containsKey(moduleName)) return; + Integer eventId = moduleEvents.get(moduleName).get(eventName); + if (eventId == null) return; + + if (allEvents.containsKey(eventId)) { + allEvents.get(eventId).circularEventBuffer.clear(); + } + } + + @Override + public void setAck(int eventId, long eventInstanceId, boolean ack) { + if (allEvents.containsKey(eventId)) { + for (Event e : allEvents.get(eventId).circularEventBuffer) { + if (e.getEventInstanceId() == eventInstanceId) { + e.setAcked(ack); + } + } + } + } + + @Override + public List<String> getModuleList() { + List<String> el = new ArrayList<String>(); + el.addAll(moduleEvents.keySet()); + return el; + } + + @Override + public List<String> getModuleEventList(String moduleName) { + if (!moduleEvents.containsKey(moduleName)) + return Collections.emptyList(); + List<String> el = new ArrayList<String>(); + el.addAll(moduleEvents.get(moduleName).keySet()); + return el; + } + + + private void traceLogDebugHistory(List<EventInfoResource> l) { + if (!log.isTraceEnabled()) { + return; + } + for (EventInfoResource eir: l) { + for (EventResource der: eir.getEvents()) { + log.trace("{}", der); + } + } + } + + private class ShutdownListenenerDelegate implements IShutdownListener { + @Override + public void floodlightIsShuttingDown() { + for (EventInfoResource eir: getAllEventHistory()) { + for (EventResource der: eir.getEvents()) { + log.info("{}", der); + } + } + } + } + + // ******************************* + // IFloodlightModule + // ******************************* + + @Override + public Collection<Class<? extends IFloodlightService>> + getModuleServices() { + Collection<Class<? extends IFloodlightService>> l = new ArrayList<Class<? extends IFloodlightService>>(); + l.add(IDebugEventService.class); + return l; + } + + @Override + public Map<Class<? extends IFloodlightService>, IFloodlightService> + getServiceImpls() { + Map<Class<? extends IFloodlightService>, IFloodlightService> m = new HashMap<Class<? extends IFloodlightService>, IFloodlightService>(); + m.put(IDebugEventService.class, this); + return m; + } + + @Override + public Collection<Class<? extends IFloodlightService>> + getModuleDependencies() { + ArrayList<Class<? extends IFloodlightService>> deps = new ArrayList<Class<? extends IFloodlightService>>(); + deps.add(IShutdownService.class); + return deps; + } + + @Override + public void init(FloodlightModuleContext context) { + } + + @Override + public void startUp(FloodlightModuleContext context) { + IShutdownService shutdownService = + context.getServiceImpl(IShutdownService.class); + shutdownService.registerShutdownListener(new ShutdownListenenerDelegate()); + DebugEventAppender.setDebugEventServiceImpl(this); + } +} diff --git a/src/main/java/net/floodlightcontroller/debugevent/Event.java b/src/main/java/net/floodlightcontroller/debugevent/Event.java index 3e0ac6db32eba864cb27c335553ade8a1bf5cfc6..d3a0cc2d4041e12a45025f4f503041aa4ef9d258 100644 --- a/src/main/java/net/floodlightcontroller/debugevent/Event.java +++ b/src/main/java/net/floodlightcontroller/debugevent/Event.java @@ -1,219 +1,122 @@ package net.floodlightcontroller.debugevent; -import java.lang.ref.SoftReference; import java.lang.reflect.Field; -import java.text.SimpleDateFormat; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import net.floodlightcontroller.debugevent.DebugEventService.EventCategory; +import net.floodlightcontroller.debugevent.EventResource.EventResourceBuilder; +import net.floodlightcontroller.debugevent.EventResource.Metadata; import net.floodlightcontroller.debugevent.IDebugEventService.EventColumn; -import net.floodlightcontroller.devicemanager.SwitchPort; -import net.floodlightcontroller.packet.IPv4; - -import org.openflow.protocol.OFFlowMod; -import org.openflow.util.HexString; +/** + * Generic Event class used to store different categories of events. + * DebugEventService uses this class to store each Event instance. + * Each {@link EventCategory} has a different object of type <b>EventClass</b>. + * This is used internally for all DebugEvent processing. For display via REST + * and CLI, it is transformed to {@link EventResource} object. + */ public class Event { - long timestamp; - long threadId; - String threadName; - Object eventData; - private Map<String, String> returnMap; - public Event(long timestamp, long threadId, String threadName, Object eventData) { - super(); - this.timestamp = timestamp; + private final long eventInstanceId; + private volatile boolean acked; + private final long timeMs; + private final long threadId; + private final String threadName; + private final Object eventData; + + public Event(long timeMs, long threadId, String threadName, + Object eventData, long eventInstanceId) { + this.timeMs = timeMs; this.threadId = threadId; this.threadName = threadName; this.eventData = eventData; + this.eventInstanceId = eventInstanceId; + this.acked = false; } - public long getTimestamp() { - return timestamp; - } - - public void setTimestamp(long timestamp) { - this.timestamp = timestamp; + public long getTimeMs() { + return timeMs; } public long getThreadId() { return threadId; } - public void setThreadId(long threadId) { - this.threadId = threadId; - } - public String getThreadName() { return threadName; } - public void setThreadName(String threadName) { - this.threadName = threadName; - } - public Object geteventData() { return eventData; } - public void seteventData(Object eventData) { - this.eventData = eventData; + public long getEventInstanceId() { + return eventInstanceId; + } + + public boolean isAcked() { + return acked; + } + + public void setAcked(boolean acked) { + this.acked = acked; } @Override public String toString() { - return "Event [timestamp=" + timestamp + ", threadId=" + threadId - + ", eventData=" + eventData.toString() + "]"; + return "Event [eventInstanceId=" + eventInstanceId + ", acked=" + + acked + ", timeMs=" + timeMs + ", threadId=" + threadId + + ", threadName=" + threadName + ", eventData=" + eventData + + "]"; } - public Map<String, String> getFormattedEvent(Class<?> eventClass, String moduleEventName) { + public EventResource getFormattedEvent(Class<?> eventClass, + String moduleEventName) { if (eventClass == null || !eventClass.equals(eventData.getClass())) { - returnMap = new HashMap<String, String>(); - returnMap.put("Error", "null event data or event-class does not match event-data"); - return returnMap; + EventResourceBuilder edb = new EventResourceBuilder(); + edb.dataFields.add(new Metadata("Error", + "null event data or event-class does not match event-data")); + return edb.build(); } - // return cached value if there is one - if (returnMap != null) - return returnMap; - - returnMap = new HashMap<String, String>(); - returnMap.put("Timestamp", new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ") - .format(timestamp)); - returnMap.put("Thread Id", String.valueOf(threadId)); - returnMap.put("Thread Name", String.valueOf(threadName)); - customFormat(eventClass, eventData, returnMap); - return returnMap; + + EventResourceBuilder edb = new EventResourceBuilder(); + edb.setTimeStamp(timeMs); + edb.setThreadId(threadId); + edb.setThreadName(threadName); + edb.setModuleEventName(moduleEventName); + edb.setEventInstanceId(eventInstanceId); + edb.setAcked(acked); + customFormat(eventClass, eventData, edb); + return edb.build(); } + @SuppressWarnings("unchecked") private void customFormat(Class<?> clazz, Object eventData, - Map<String, String> retMap) { + EventResourceBuilder eventDataBuilder) { for (Field f : clazz.getDeclaredFields()) { EventColumn ec = f.getAnnotation(EventColumn.class); if (ec == null) continue; f.setAccessible(true); try { - Object obj = f.get(eventData); - - switch(ec.description()) { - case DPID: - retMap.put(ec.name(), HexString.toHexString((Long) obj)); - break; - case MAC: - retMap.put(ec.name(), HexString.toHexString((Long) obj, 6)); - break; - case IPv4: - retMap.put(ec.name(), IPv4.fromIPv4Address((Integer) obj)); - break; - case FLOW_MOD_FLAGS: - int flags = (Integer)obj; - StringBuilder builder = new StringBuilder(); - if (flags == 0) { - builder.append("None"); - } - else { - if ((flags & OFFlowMod.OFPFF_SEND_FLOW_REM) != 0) { - builder.append("SEND_FLOW_REM "); - } - if ((flags & OFFlowMod.OFPFF_CHECK_OVERLAP) != 0) { - builder.append("CHECK_OVERLAP "); - } - if ((flags & OFFlowMod.OFPFF_EMERG) != 0) { - builder.append("EMERG "); - } - } - retMap.put(ec.name(), builder.toString()); - break; - case LIST_IPV4: - @SuppressWarnings("unchecked") - List<Integer> ipv4Addresses = (List<Integer>)obj; - StringBuilder ipv4AddressesStr = new StringBuilder(); - if (ipv4Addresses.size() == 0) { - ipv4AddressesStr.append("--"); - } else { - for (Integer ipv4Addr : ipv4Addresses) { - ipv4AddressesStr.append(IPv4.fromIPv4Address(ipv4Addr.intValue())); - ipv4AddressesStr.append(" "); - } - } - retMap.put(ec.name(), ipv4AddressesStr.toString()); - break; - case LIST_ATTACHMENT_POINT: - @SuppressWarnings("unchecked") - List<SwitchPort> aps = (List<SwitchPort>)obj; - StringBuilder apsStr = new StringBuilder(); - if (aps.size() == 0) { - apsStr.append("--"); - } else { - for (SwitchPort ap : aps) { - apsStr.append(HexString.toHexString(ap.getSwitchDPID())); - apsStr.append("/"); - apsStr.append(ap.getPort()); - apsStr.append(" "); - } - } - retMap.put(ec.name(), apsStr.toString()); - break; - case LIST_OBJECT: - @SuppressWarnings("unchecked") - List<Object> obl = (List<Object>)obj; - StringBuilder sbldr = new StringBuilder(); - if (obl.size() == 0) { - sbldr.append("--"); - } else { - for (Object o : obl) { - sbldr.append(o.toString()); - sbldr.append(" "); - } - } - retMap.put(ec.name(), sbldr.toString()); - break; - case SREF_LIST_OBJECT: - @SuppressWarnings("unchecked") - SoftReference<List<Object>> srefListObj = - (SoftReference<List<Object>>)obj; - List<Object> ol = srefListObj.get(); - if (ol != null) { - StringBuilder sb = new StringBuilder(); - if (ol.size() == 0) { - sb.append("--"); - } else { - for (Object o : ol) { - sb.append(o.toString()); - sb.append(" "); - } - } - retMap.put(ec.name(), sb.toString()); - } else { - retMap.put(ec.name(), "-- reference not available --"); - } - break; - case SREF_OBJECT: - @SuppressWarnings("unchecked") - SoftReference<Object> srefObj = (SoftReference<Object>)obj; - if (srefObj == null) { - retMap.put(ec.name(), "--"); - } else { - Object o = srefObj.get(); - if (o != null) { - retMap.put(ec.name(), o.toString()); - } else { - retMap.put(ec.name(), - "-- reference not available --"); - } - } - break; - case STRING: - case OBJECT: - case PRIMITIVE: - default: - retMap.put(ec.name(), obj.toString()); + Object obj = f.get(eventData); + @SuppressWarnings("rawtypes") + CustomFormatter cf = DebugEventService.customFormatter.get(ec.description()); + + if (cf == null) { + throw new IllegalArgumentException( + "CustomFormatter for " + + ec.description() + + " does not exist."); + } else { + cf.customFormat(obj, ec.name(), eventDataBuilder); } } catch (ClassCastException e) { - retMap.put("Error", e.getMessage()); + eventDataBuilder.dataFields.add(new Metadata("Error", + e.getMessage())); } catch (IllegalArgumentException e) { - retMap.put("Error", e.getMessage()); + eventDataBuilder.dataFields.add(new Metadata("Error", + e.getMessage())); } catch (IllegalAccessException e) { - retMap.put("Error", e.getMessage()); + eventDataBuilder.dataFields.add(new Metadata("Error", + e.getMessage())); } } } diff --git a/src/main/java/net/floodlightcontroller/debugevent/EventResource.java b/src/main/java/net/floodlightcontroller/debugevent/EventResource.java new file mode 100644 index 0000000000000000000000000000000000000000..6ce52b53bf55ff15c054412cc808479a857a4a29 --- /dev/null +++ b/src/main/java/net/floodlightcontroller/debugevent/EventResource.java @@ -0,0 +1,258 @@ +package net.floodlightcontroller.debugevent; + +import java.util.ArrayList; +import java.util.List; + +import javax.annotation.concurrent.Immutable; + +import java.util.Date; + +import com.google.common.collect.ImmutableList; + +/** + * Event Instance of a particular event category. This is only for REST purpose. + * Actual {@link Event} class consists of Object of Event. Here we parse it into + * its string form using {@link getCustomFormatter} and return for REST and CLI. + */ +@Immutable +public class EventResource { + private final Date timestamp; + private final long threadId; + private final String threadName; + private final String moduleEventName; + private final ImmutableList<Metadata> dataFields; + // NOTE: this is only for CLI purpose. cascaded fields show up as + // different tables in CLI which is not very useful + private final String dataString; + long eventInstanceId; + boolean acked; + + private EventResource(EventResourceBuilder evInstanceBuilder) { + this.timestamp = evInstanceBuilder.timestamp; + this.threadId = evInstanceBuilder.threadId; + this.threadName = evInstanceBuilder.threadName; + this.moduleEventName = evInstanceBuilder.moduleEventName; + this.dataFields = ImmutableList.copyOf(evInstanceBuilder.dataFields); + this.dataString = this.dataFields.toString(); + this.eventInstanceId = evInstanceBuilder.eventInstanceId; + this.acked = evInstanceBuilder.acked; + } + + public Date getTimestamp() { + return timestamp; + } + + public String getModuleEventName() { + return moduleEventName; + } + + public List<Metadata> getDataFields() { + return dataFields; + } + + public String getDataString() { + return dataString; + } + + public long getEventInstanceId() { + return eventInstanceId; + } + + public boolean isAcked() { + return acked; + } + + public static class EventResourceBuilder { + private Date timestamp; + private long threadId; + private String threadName; + private String moduleEventName; + protected List<Metadata> dataFields; + long eventInstanceId; + boolean acked; + + public EventResourceBuilder() { + this.dataFields = new ArrayList<Metadata>(); + } + + public EventResource build() { + return new EventResource(this); + } + + public Date getTimestamp() { + return timestamp; + } + + public void setTimeStamp(long timeMs) { + this.timestamp = new Date(timeMs); + } + + public long getThreadId() { + return threadId; + } + + public void setThreadId(long threadId) { + this.threadId = threadId; + } + + public String getThreadName() { + return threadName; + } + + public void setThreadName(String threadName) { + this.threadName = threadName; + } + + public String getModuleEventName() { + return moduleEventName; + } + + public void setModuleEventName(String moduleEventName) { + this.moduleEventName = moduleEventName; + } + + public List<Metadata> getDataFields() { + return dataFields; + } + + public void setDataFields(List<Metadata> dataFields) { + this.dataFields = dataFields; + } + + public long getEventInstanceId() { + return eventInstanceId; + } + + public void setEventInstanceId(long eventInstanceId) { + this.eventInstanceId = eventInstanceId; + } + + public boolean isAcked() { + return acked; + } + + public void setAcked(boolean acked) { + this.acked = acked; + } + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + + ((dataFields == null) ? 0 : dataFields.hashCode()); + result = prime * result + + ((dataString == null) ? 0 : dataString.hashCode()); + result = prime + * result + + ((moduleEventName == null) ? 0 + : moduleEventName.hashCode()); + result = prime * result + (int) (threadId ^ (threadId >>> 32)); + result = prime * result + + ((threadName == null) ? 0 : threadName.hashCode()); + result = prime * result + + ((timestamp == null) ? 0 : timestamp.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + EventResource other = (EventResource) obj; + if (dataFields == null) { + if (other.dataFields != null) return false; + } else if (!dataFields.equals(other.dataFields)) return false; + if (dataString == null) { + if (other.dataString != null) return false; + } else if (!dataString.equals(other.dataString)) return false; + if (moduleEventName == null) { + if (other.moduleEventName != null) return false; + } else if (!moduleEventName.equals(other.moduleEventName)) + return false; + if (threadId != other.threadId) return false; + if (threadName == null) { + if (other.threadName != null) return false; + } else if (!threadName.equals(other.threadName)) return false; + if (timestamp == null) { + if (other.timestamp != null) return false; + } else if (!timestamp.equals(other.timestamp)) return false; + return true; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append(moduleEventName); + builder.append(" "); + builder.append(timestamp); + builder.append(", threadId="); + builder.append(threadId); + builder.append(", threadName=\""); + builder.append(threadName); + builder.append("\", dataFields="); + builder.append(dataFields); + builder.append(", eventInstanceId="); + builder.append(eventInstanceId); + builder.append(", acked="); + builder.append(acked); + return builder.toString(); + } + + /** + * Actual {@link Event} has Object of Event. The fields of that Object are + * converted to {@literal String} and returned as {@link Metadata} with + * {@link EventResource} + */ + @Immutable + public static class Metadata { + private final String eventClass; + private final String eventData; + + public Metadata(String eventClass, String eventData) { + this.eventClass = eventClass; + this.eventData = eventData; + } + + public String getEventClass() { + return eventClass; + } + + public String getEventData() { + return eventData; + } + + @Override + public String toString() { + return this.eventClass + ":" + this.eventData; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + + ((eventClass == null) ? 0 : eventClass.hashCode()); + result = prime * result + + ((eventData == null) ? 0 : eventData.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + Metadata other = (Metadata) obj; + if (eventClass == null) { + if (other.eventClass != null) return false; + } else if (!eventClass.equals(other.eventClass)) return false; + if (eventData == null) { + if (other.eventData != null) return false; + } else if (!eventData.equals(other.eventData)) return false; + return true; + } + } +} diff --git a/src/main/java/net/floodlightcontroller/debugevent/IDebugEventService.java b/src/main/java/net/floodlightcontroller/debugevent/IDebugEventService.java index d54e9f632af539f53ecdd9e4b2b4477cba8d79d5..c2a0fcdd95db1235614011b4d97713d31ba28162 100644 --- a/src/main/java/net/floodlightcontroller/debugevent/IDebugEventService.java +++ b/src/main/java/net/floodlightcontroller/debugevent/IDebugEventService.java @@ -5,39 +5,47 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.List; -import java.util.Map; import net.floodlightcontroller.core.module.IFloodlightService; -import net.floodlightcontroller.debugevent.DebugEvent.EventInfo; +import net.floodlightcontroller.debugevent.DebugEventResource.EventInfoResource; +import net.floodlightcontroller.debugevent.DebugEventService.EventCategory; +import net.floodlightcontroller.debugevent.DebugEventService.EventCategoryBuilder; public interface IDebugEventService extends IFloodlightService { /** - * Different event types. Events that are meant to be logged on demand - * need to be separately enabled/disabled. + * Different event types. Events that are meant to be logged on demand need + * to be separately enabled/disabled. */ public enum EventType { - ALWAYS_LOG, - LOG_ON_DEMAND + ALWAYS_LOG, LOG_ON_DEMAND + } + + /** + * Is the Event <b>ACKABLE</b> or <b>NOT_ACKABLE</b> + */ + public enum AckableEvent { + ACKABLE, NOT_ACKABLE } /** * Describes the type of field obtained from reflection */ enum EventFieldType { - DPID, IPv4, MAC, STRING, OBJECT, PRIMITIVE, LIST_IPV4, - LIST_ATTACHMENT_POINT, LIST_OBJECT, SREF_LIST_OBJECT, SREF_OBJECT, - FLOW_MOD_FLAGS + DPID, IPv4, MAC, STRING, OBJECT, PRIMITIVE, COLLECTION_IPV4, + COLLECTION_ATTACHMENT_POINT, COLLECTION_OBJECT, SREF_COLLECTION_OBJECT, + SREF_OBJECT } /** - * EventColumn is the only annotation given to the fields of the event - * when updating an event. + * EventColumn is the only annotation given to the fields of the event when + * updating an event. */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface EventColumn { String name() default "param"; + EventFieldType description() default EventFieldType.PRIMITIVE; } @@ -48,82 +56,52 @@ public interface IDebugEventService extends IFloodlightService { public static final String EV_MDATA_ERROR = "error"; /** - * A limit on the maximum number of events that can be created - */ - public static final int MAX_EVENTS = 2000; - - /** - * Public class for information returned in response to rest API calls. - */ - public class DebugEventInfo { - EventInfo eventInfo; - List<Map<String,String>> events; - - public DebugEventInfo(EventInfo eventInfo, - List<Map<String, String>> eventHistory) { - this.eventInfo = eventInfo; - this.events = eventHistory; - } - - public EventInfo getEventInfo() { - return eventInfo; - } - - public List<Map<String,String>> getEvents() { - return events; - } - } - - /** - * exception thrown when MAX_EVENTS have been registered - */ - public class MaxEventsRegistered extends Exception { - private static final long serialVersionUID = 2609587082227510262L; - } - - /** - * Register an event for debugging. + * Returns an {@link EventCategoryBuilder} that can be used to build a new + * {@link EventCategory}. Before calling the {@literal build} method, set + * the following parameters: * - * @param moduleName module registering event eg. linkdiscovery, virtualrouting. - * @param eventName name given to event. - * @param eventDescription A descriptive string describing the event. - * @param eventType EventType for this event. On-demand events have to - * be explicitly enabled using other methods in this API - * @param eventClass A user defined class that annotates the fields - * with @EventColumn. This class specifies the - * fields/columns for this event. - * @param bufferCapacity Number of events to store for this event in a circular - * buffer. Older events will be discarded once the - * buffer is full. - * @param metaData variable arguments that qualify an event - * eg. EV_MDATA_WARN, EV_MDATA_ERROR etc. See Debug Event Qualifiers - * @return IEventUpdater with update methods that can be used to - * update an event of the given eventClass + * @param moduleName + * module registering event eg. linkdiscovery, virtualrouting. + * @param eventName + * name given to event. + * @param eventDescription + * A descriptive string describing the event. + * @param eventType + * EventType for this event. On-demand events have to be + * explicitly enabled using other methods in this API + * @param eventClass + * A user defined class that annotates the fields with + * @EventColumn. This class specifies the fields/columns for this + * event. + * @param bufferCapacity + * Number of events to store for this event in a circular buffer. + * Older events will be discarded once the buffer is full. + * @param ackable + * is the event used as part of ackable-event framework boolean + * + * @return IEventCategory with <b>newEvent</b> method that can be used + * to create instances of event of the given eventClass * @throws MaxEventsRegistered */ - public <T> IEventUpdater<T> registerEvent(String moduleName, String eventName, - String eventDescription, - EventType eventType, - Class<T> eventClass, - int bufferCapacity, - String... metaData) - throws MaxEventsRegistered; + public <T> EventCategoryBuilder<T> buildEvent(Class<T> evClass); /** - * Update the global event stores with values from the thread local stores. This - * method is not typically intended for use by any module. It's typical usage is from - * floodlight core for events that happen in the packet processing pipeline. - * For other rare events, flushEvents should be called. + * Update the global event stores with values from the thread local stores. + * This method is not typically intended for use by any module. It's typical + * usage is from floodlight core for events that happen in the packet + * processing pipeline. For other rare events, flushEvents should be called. */ public void flushEvents(); /** * Determine if eventName is a registered event for a given moduleName */ - public boolean containsModuleEventName(String moduleName, String eventName); + public boolean containsModuleEventName(String moduleName, + String eventName); /** - * Determine if any events have been registered for module of name moduleName + * Determine if any events have been registered for module of name + * moduleName */ public boolean containsModuleName(String moduleName); @@ -131,29 +109,35 @@ public interface IDebugEventService extends IFloodlightService { * Get event history for all events. This call can be expensive as it * formats the event histories for all events. * - * @return a list of all event histories or an empty list if no events have - * been registered + * @return a list of all event histories or an empty list if no events have + * been registered */ - public List<DebugEventInfo> getAllEventHistory(); + public List<EventInfoResource> getAllEventHistory(); /** * Get event history for all events registered for a given moduleName * - * @return a list of all event histories for all events registered for the - * the module or an empty list if there are no events for this module + * @return a list of all event histories for all events registered for the + * the module or an empty list if there are no events for this + * module */ - public List<DebugEventInfo> getModuleEventHistory(String moduleName); + public List<EventInfoResource> getModuleEventHistory(String moduleName); /** * Get event history for a single event * - * @param moduleName registered module name - * @param eventName registered event name for moduleName - * @param last last X events + * @param moduleName + * registered module name + * @param eventName + * registered event name for moduleName + * @param numOfEvents + * last X events * @return DebugEventInfo for that event, or null if the moduleEventName * does not correspond to a registered event. */ - public DebugEventInfo getSingleEventHistory(String moduleName, String eventName, int last); + public EventInfoResource getSingleEventHistory(String moduleName, + String eventName, + int numOfEvents); /** * Wipe out all event history for all registered events @@ -161,16 +145,21 @@ public interface IDebugEventService extends IFloodlightService { public void resetAllEvents(); /** - * Wipe out all event history for all events registered for a specific module + * Wipe out all event history for all events registered for a specific + * module * - * @param moduleName registered module name + * @param moduleName + * registered module name */ public void resetAllModuleEvents(String moduleName); /** * Wipe out event history for a single event - * @param moduleName registered module name - * @param eventName registered event name for moduleName + * + * @param moduleName + * registered module name + * @param eventName + * registered event name for moduleName */ public void resetSingleEvent(String moduleName, String eventName); @@ -181,9 +170,24 @@ public interface IDebugEventService extends IFloodlightService { public List<String> getModuleList(); /** - * Returns a list of all events registered for a specific moduleName - * or a empty list + * Returns a list of all events registered for a specific moduleName or a + * empty list */ public List<String> getModuleEventList(String moduleName); + /** + * Sets the 'ack' for the individual {@link DebugEventService} instance + * pointed by the <b>eventId</b> and <b>eventInstanceId</b> given in the + * PATCH request <br/> + * Returns void + * + * @param eventId + * unique event queue identifier + * @param eventInstanceId + * unique event identifier + * @param ack + * boolean ack - true or false + */ + public void setAck(int eventId, long eventInstanceId, boolean ack); + } diff --git a/src/main/java/net/floodlightcontroller/debugevent/IEventCategory.java b/src/main/java/net/floodlightcontroller/debugevent/IEventCategory.java new file mode 100644 index 0000000000000000000000000000000000000000..ebd17e45e821cd07214bd838de824bac5d168f3a --- /dev/null +++ b/src/main/java/net/floodlightcontroller/debugevent/IEventCategory.java @@ -0,0 +1,30 @@ +package net.floodlightcontroller.debugevent; + +/** + * EventCategory is used to log events for pre-registered events. + */ +public interface IEventCategory<T> { + + /** + * Logs the instance of the event thread-locally. Flushing to the global + * circular buffer for this event is delayed resulting in better + * performance. This method should typically be used by those events that + * happen in the packet processing pipeline + * + * @param event + * an instance of the user-defined event of type T + */ + public void newEventNoFlush(T event); + + /** + * Logs the instance of the event thread-locally and immediately flushes to + * the global circular buffer for this event. This method should typically + * be used by those events that happen outside the packet processing + * pipeline + * + * @param event + * an instance of the user-defined event of type T + */ + public void newEventWithFlush(T event); + +} diff --git a/src/main/java/net/floodlightcontroller/debugevent/IEventUpdater.java b/src/main/java/net/floodlightcontroller/debugevent/IEventUpdater.java deleted file mode 100644 index 7aec38f44aa1b7637c00a180226761ecc30511a7..0000000000000000000000000000000000000000 --- a/src/main/java/net/floodlightcontroller/debugevent/IEventUpdater.java +++ /dev/null @@ -1,30 +0,0 @@ -package net.floodlightcontroller.debugevent; - -/** - * eventUPdater is used to log events for pre-registered events. - */ -public interface IEventUpdater<T> { - - /** - * Logs the instance of the event thread-locally. Flushing to the global - * circular buffer for this event is delayed resulting in better performance. - * This method should typically be used by those events that happen in the - * packet processing pipeline - * - * @param event an instance of the user-defined event of type T - */ - public void updateEventNoFlush(T event); - - /** - * Logs the instance of the event thread-locally and immediated flushes - * to the global circular buffer for this event. - * This method should typically be used by those events that happen - * outside the packet processing pipeline - * - * @param event an instance of the user-defined event of type T - */ - public void updateEventWithFlush(T event); - - - -} diff --git a/src/main/java/net/floodlightcontroller/debugevent/NullDebugEvent.java b/src/main/java/net/floodlightcontroller/debugevent/MockDebugEventService.java similarity index 58% rename from src/main/java/net/floodlightcontroller/debugevent/NullDebugEvent.java rename to src/main/java/net/floodlightcontroller/debugevent/MockDebugEventService.java index 7d479ea6df9242691e7b23e9dcfc72cab8b9a340..cf43d81469f2369c48f68cae97fa9a5d3b37339d 100644 --- a/src/main/java/net/floodlightcontroller/debugevent/NullDebugEvent.java +++ b/src/main/java/net/floodlightcontroller/debugevent/MockDebugEventService.java @@ -11,55 +11,25 @@ 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.debugevent.DebugEventResource.EventInfoResource; +import net.floodlightcontroller.debugevent.DebugEventService.EventCategoryBuilder; -public class NullDebugEvent implements IFloodlightModule, IDebugEventService { - - - @Override - public void flushEvents() { - - } - - @Override - public Collection<Class<? extends IFloodlightService>> - getModuleServices() { - Collection<Class<? extends IFloodlightService>> services = - new ArrayList<Class<? extends IFloodlightService>>(1); - services.add(IDebugEventService.class); - return services; - } - - @Override - public Map<Class<? extends IFloodlightService>, IFloodlightService> - getServiceImpls() { - Map<Class<? extends IFloodlightService>, - IFloodlightService> m = - new HashMap<Class<? extends IFloodlightService>, - IFloodlightService>(); - m.put(IDebugEventService.class, this); - return m; - } - - @Override - public Collection<Class<? extends IFloodlightService>> - getModuleDependencies() { - return null; - } +public class MockDebugEventService implements IFloodlightModule, IDebugEventService { @Override - public void init(FloodlightModuleContext context) - throws FloodlightModuleException { - + public <T> EventCategoryBuilder<T> buildEvent(Class<T> evClass) { + DebugEventService des = new DebugEventService(); + return des.buildEvent(evClass); } @Override - public void startUp(FloodlightModuleContext context) - throws FloodlightModuleException { + public void flushEvents() { } @Override - public boolean containsModuleEventName(String moduleName, String eventName) { + public boolean containsModuleEventName(String moduleName, + String eventName) { return false; } @@ -69,18 +39,19 @@ public class NullDebugEvent implements IFloodlightModule, IDebugEventService { } @Override - public List<DebugEventInfo> getAllEventHistory() { + public List<EventInfoResource> getAllEventHistory() { return Collections.emptyList(); } @Override - public List<DebugEventInfo> getModuleEventHistory(String param) { + public List<EventInfoResource> getModuleEventHistory(String moduleName) { return Collections.emptyList(); } @Override - public DebugEventInfo getSingleEventHistory(String moduleName, String eventName, - int last) { + public EventInfoResource getSingleEventHistory(String moduleName, + String eventName, + int numOfEvents) { return null; } @@ -100,36 +71,57 @@ public class NullDebugEvent implements IFloodlightModule, IDebugEventService { } @Override - public <T> IEventUpdater<T> - registerEvent(String moduleName, String eventName, - String eventDescription, EventType eventType, - Class<T> eventClass, int bufferCapacity, - String... metaData) throws MaxEventsRegistered { - return new NullEventImpl<T>(); + public List<String> getModuleList() { + return Collections.emptyList(); } - public class NullEventImpl<T> implements IEventUpdater<T> { - - @Override - public void updateEventNoFlush(Object event) { + @Override + public List<String> getModuleEventList(String moduleName) { + return Collections.emptyList(); + } - } + @Override + public void setAck(int eventId, long eventInstanceId, boolean ack) { - @Override - public void updateEventWithFlush(Object event) { + } - } + @Override + public Collection<Class<? extends IFloodlightService>> + getModuleServices() { + Collection<Class<? extends IFloodlightService>> services = + new ArrayList<Class<? extends IFloodlightService>>(1); + services.add(IDebugEventService.class); + return services; + } + @Override + public Map<Class<? extends IFloodlightService>, IFloodlightService> + getServiceImpls() { + Map<Class<? extends IFloodlightService>, IFloodlightService> m = + new HashMap<Class<? extends IFloodlightService>, IFloodlightService>(); + m.put(IDebugEventService.class, this); + return m; } @Override - public List<String> getModuleList() { - return Collections.emptyList(); + public Collection<Class<? extends IFloodlightService>> + getModuleDependencies() { + return null; } @Override - public List<String> getModuleEventList(String moduleName) { - return Collections.emptyList(); + public + void + init(FloodlightModuleContext context) + throws FloodlightModuleException { + } + @Override + public + void + startUp(FloodlightModuleContext context) + throws FloodlightModuleException { + + } } diff --git a/src/main/java/net/floodlightcontroller/debugevent/web/DebugEventResource.java b/src/main/java/net/floodlightcontroller/debugevent/web/DebugEventResource.java deleted file mode 100644 index a3f06ceaf3f7ab615ae062d9e4d35d02117eb4e9..0000000000000000000000000000000000000000 --- a/src/main/java/net/floodlightcontroller/debugevent/web/DebugEventResource.java +++ /dev/null @@ -1,322 +0,0 @@ -package net.floodlightcontroller.debugevent.web; - - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import net.floodlightcontroller.debugevent.IDebugEventService.DebugEventInfo; -import net.floodlightcontroller.debugevent.IDebugEventService.EventType; - -import org.restlet.resource.Get; -import org.restlet.resource.Post; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Web interface for Debug Events - * - * @author Saurav - */ -public class DebugEventResource extends DebugEventResourceBase { - protected static Logger logger = - LoggerFactory.getLogger(DebugEventResource.class); - - /** - * The output JSON model that contains the counter information - */ - public static class DebugEventInfoOutput { - protected class DEInfo { - private final boolean enabled; - private final int bufferCapacity; - private final EventType eventType; - private final String eventDesc; - private final String eventName; - private final String moduleName; - private final String[] metaData; - private final List<Map<String,String>> eventHistory; - - DEInfo(DebugEventInfo dei) { - this.moduleName = dei.getEventInfo().getModuleName(); - this.eventName = dei.getEventInfo().getEventName(); - this.eventDesc = dei.getEventInfo().getEventDesc(); - this.metaData = dei.getEventInfo().getMetaData(); - this.enabled = dei.getEventInfo().isEnabled(); - this.eventType = dei.getEventInfo().getEtype(); - this.bufferCapacity = dei.getEventInfo().getBufferCapacity(); - this.eventHistory = dei.getEvents(); - } - public boolean isEnabled() { - return enabled; - } - public int getBufferCapacity() { - return bufferCapacity; - } - public String getEventDesc() { - return eventDesc; - } - public String getEventName() { - return eventName; - } - public String getModuleName() { - return moduleName; - } - public String[] getMetaData() { - return metaData; - } - public EventType getEventType() { - return eventType; - } - public List<Map<String,String>> getEventHistory() { - return eventHistory; - } - - } - - public Map<String, DEInfo> eventMap = null; - public List<String> names = null; - public String error = null; - - DebugEventInfoOutput(boolean getList) { - if (!getList) { - eventMap = new HashMap<String, DEInfo>(); - } - } - public Map<String, DEInfo> getEventMap() { - return eventMap; - } - public List<String> getNames() { - return names; - } - public String getError() { - return error; - } - - } - - public enum Option { - ALL, ONE_MODULE, ONE_MODULE_EVENT, ERROR_BAD_MODULE_NAME, ERROR_BAD_PARAM, - ERROR_BAD_MODULE_EVENT_NAME - } - - public static class DebugEventPost { - public Boolean reset; - - public Boolean getReset() { - return reset; - } - public void setReset(Boolean reset) { - this.reset = reset; - } - } - - public static class ResetOutput { - String error = null; - - public String getError() { - return error; - } - public void setError(String error) { - this.error = error; - } - } - - /** - * Reset events - * - * If using curl: - * curl -X POST -d {\"reset\":true} -H "Content-Type: application/json" URL - * where URL must be in one of the following forms for resetting registered events: - * "http://{controller-hostname}:8080/wm/debugevent/ - * "http://{controller-hostname}:8080/wm/debugevent/{param1} - * "http://{controller-hostname}:8080/wm/debugevent/{param1}/{param2} - * - * Not giving {param1} will reset all events - * {param1} can be 'all' or the name of a module. The former case will reset - * all events, while the latter will reset all events for the moduleName (if - * param2 is null).{param2} must be an eventName for the given moduleName to - * reset a specific event. - */ - @Post - public ResetOutput postHandler(DebugEventPost postData) { - ResetOutput output = new ResetOutput(); - String param1 = (String)getRequestAttributes().get("param1"); - String param2 = (String)getRequestAttributes().get("param2"); - - if (postData.getReset() != null && postData.getReset()) { - Option choice = Option.ERROR_BAD_PARAM; - - if (param1 == null) { - param1 = "all"; - choice = Option.ALL; - } else if (param1.equals("all")) { - choice = Option.ALL; - } else if (param2 == null) { - boolean isRegistered = debugEvent.containsModuleName(param1); - if (isRegistered) { - choice = Option.ONE_MODULE; - } else { - choice = Option.ERROR_BAD_MODULE_NAME; - } - } else { - // differentiate between disabled and non-existing events - boolean isRegistered = debugEvent.containsModuleEventName(param1, param2); - if (isRegistered) { - choice = Option.ONE_MODULE_EVENT; - } else { - choice = Option.ERROR_BAD_MODULE_EVENT_NAME; - } - } - - switch (choice) { - case ALL: - debugEvent.resetAllEvents(); - break; - case ONE_MODULE: - debugEvent.resetAllModuleEvents(param1); - break; - case ONE_MODULE_EVENT: - debugEvent.resetSingleEvent(param1, param2); - break; - case ERROR_BAD_MODULE_NAME: - output.error = "Module name has no corresponding registered events"; - break; - case ERROR_BAD_MODULE_EVENT_NAME: - output.error = "Event not registered"; - break; - case ERROR_BAD_PARAM: - output.error = "Bad param"; - } - } - - return output; - - } - - /** - * Return the debug event data for the get rest-api call - * - * URL must be in one of the following forms for retrieving a list - * moduleNames "http://{controller-hostname}:8080/wm/debugevent/ - * counterNames "http://{controller-hostname}:8080/wm/debugevent/{moduleName} - * - * URL must be in one of the following forms for retrieving event data: - * "http://{controller-hostname}:8080/wm/debugevent/{param1} - * "http://{controller-hostname}:8080/wm/debugevent/{param1}/{param2} - * - * where {param1} must be one of (no quotes): - * null if nothing is given then by default the list - * of all moduleNames is returned for which - * events have been registered - * "all" can return value/info on all active events - * but is currently disallowed - * "{moduleName}" returns value/info on events for the specified module - * depending on the value of param2 - * and {param2} must be one of (no quotes): - * null returns all eventNames registered for the - * given moduleName (in param1) - * "{eventName}" returns value/info for specific event if it is active. - * - */ - @Get("json") - public DebugEventInfoOutput handleEventInfoQuery() { - Option choice = Option.ERROR_BAD_PARAM; - DebugEventInfoOutput output; - String laststr = getQueryValue("last"); - int last = Integer.MAX_VALUE; - try { - if (laststr != null) - last = Integer.valueOf(laststr); - if (last < 1) last = Integer.MAX_VALUE; - } catch (NumberFormatException e) { - output = new DebugEventInfoOutput(false); - output.error = "Expected an integer requesting last X events;" + - " received " + laststr; - return output; - } - String param1 = (String)getRequestAttributes().get("param1"); - String param2 = (String)getRequestAttributes().get("param2"); - - if (param1 == null) { - output = new DebugEventInfoOutput(true); - return listEvents(output); - } else if (param1.equals("all")) { - output = new DebugEventInfoOutput(false); - //populateEvents(debugEvent.getAllEventHistory(), output); - output.error = "Cannot retrieve all events - please select a specific event"; - return output; - } - - if (param2 == null) { - output = new DebugEventInfoOutput(true); - boolean isRegistered = debugEvent.containsModuleName(param1); - if (isRegistered) { - return listEvents(param1, output); - } else { - choice = Option.ERROR_BAD_MODULE_NAME; - } - } else if (param2.equals("all")) { - output = new DebugEventInfoOutput(false); - //choice = Option.ONE_MODULE; - output.error = "Cannot retrieve all events - please select a specific event"; - return output; - } else { - // differentiate between disabled and non-existing events - boolean isRegistered = debugEvent.containsModuleEventName(param1, param2); - if (isRegistered) { - choice = Option.ONE_MODULE_EVENT; - } else { - choice = Option.ERROR_BAD_MODULE_EVENT_NAME; - } - } - - output = new DebugEventInfoOutput(false); - switch (choice) { - case ONE_MODULE: - populateEvents(debugEvent.getModuleEventHistory(param1), output); - break; - case ONE_MODULE_EVENT: - populateSingleEvent(debugEvent.getSingleEventHistory(param1, param2, last), - output); - break; - case ERROR_BAD_MODULE_NAME: - output.error = "Module name has no corresponding registered events"; - break; - case ERROR_BAD_MODULE_EVENT_NAME: - output.error = "Event not registered"; - break; - case ERROR_BAD_PARAM: - default: - output.error = "Bad param"; - } - - return output; - } - - private DebugEventInfoOutput listEvents(DebugEventInfoOutput output) { - output.names = debugEvent.getModuleList(); - return output; - } - - private DebugEventInfoOutput listEvents(String moduleName, - DebugEventInfoOutput output) { - output.names = debugEvent.getModuleEventList(moduleName); - return output; - } - - private void populateSingleEvent(DebugEventInfo singleEventHistory, - DebugEventInfoOutput output) { - if (singleEventHistory != null) { - output.eventMap.put(singleEventHistory.getEventInfo().getModuleEventName(), - output.new DEInfo(singleEventHistory)); - } - } - - private void populateEvents(List<DebugEventInfo> eventHistory, - DebugEventInfoOutput output) { - if (eventHistory != null) { - for (DebugEventInfo de : eventHistory) - populateSingleEvent(de, output); - } - } -} diff --git a/src/main/java/net/floodlightcontroller/debugevent/web/DebugEventResourceBase.java b/src/main/java/net/floodlightcontroller/debugevent/web/DebugEventResourceBase.java deleted file mode 100644 index 964deeb7467bf70d1b8e6e3578743ded26415558..0000000000000000000000000000000000000000 --- a/src/main/java/net/floodlightcontroller/debugevent/web/DebugEventResourceBase.java +++ /dev/null @@ -1,18 +0,0 @@ -package net.floodlightcontroller.debugevent.web; - - -import net.floodlightcontroller.debugevent.IDebugEventService; - -import org.restlet.resource.ResourceException; -import org.restlet.resource.ServerResource; - -public class DebugEventResourceBase extends ServerResource{ - protected IDebugEventService debugEvent; - - @Override - protected void doInit() throws ResourceException { - super.doInit(); - debugEvent = (IDebugEventService)getContext().getAttributes(). - get(IDebugEventService.class.getCanonicalName()); - } -} diff --git a/src/main/java/net/floodlightcontroller/debugevent/web/DebugEventRoutable.java b/src/main/java/net/floodlightcontroller/debugevent/web/DebugEventRoutable.java deleted file mode 100644 index d4ee7c6fd1b21ccaf63ad588c16993fcb91f6ed0..0000000000000000000000000000000000000000 --- a/src/main/java/net/floodlightcontroller/debugevent/web/DebugEventRoutable.java +++ /dev/null @@ -1,27 +0,0 @@ -package net.floodlightcontroller.debugevent.web; - -import org.restlet.Context; -import org.restlet.Restlet; -import org.restlet.routing.Router; - -import net.floodlightcontroller.restserver.RestletRoutable; - -public class DebugEventRoutable implements RestletRoutable { - - @Override - public Restlet getRestlet(Context context) { - Router router = new Router(context); - router.attach("/{param1}/{param2}/", DebugEventResource.class); - router.attach("/{param1}/{param2}", DebugEventResource.class); - router.attach("/{param1}/", DebugEventResource.class); - router.attach("/{param1}", DebugEventResource.class); - router.attach("/", DebugEventResource.class); - return router; - } - - @Override - public String basePath() { - return "/wm/debugevent"; - } - -} diff --git a/src/main/java/net/floodlightcontroller/devicemanager/IDevice.java b/src/main/java/net/floodlightcontroller/devicemanager/IDevice.java index 4593b7bac9d8f4b25f9a3c1e5c4693bfd1a0fca0..4d5ba9be6d5d5ef7f31a4316cb92d2303a62502f 100644 --- a/src/main/java/net/floodlightcontroller/devicemanager/IDevice.java +++ b/src/main/java/net/floodlightcontroller/devicemanager/IDevice.java @@ -19,6 +19,10 @@ package net.floodlightcontroller.devicemanager; import java.util.Date; +import org.projectfloodlight.openflow.types.IPv4Address; +import org.projectfloodlight.openflow.types.MacAddress; +import org.projectfloodlight.openflow.types.VlanVid; + /** * Represents an independent device on the network. A device consists of a @@ -37,7 +41,7 @@ public interface IDevice { * Get the MAC address of the device as a Long value. * @return the MAC address for the device */ - public long getMACAddress(); + public MacAddress getMACAddress(); /** * Get the MAC address of the device as a String value. @@ -50,13 +54,13 @@ public interface IDevice { * entities, then the value -1 will be returned. * @return an array containing all unique VLAN IDs for the device. */ - public Short[] getVlanId(); + public VlanVid[] getVlanId(); /** * Get all unique IPv4 addresses associated with the device. * @return an array containing the unique IPv4 addresses for the device. */ - public Integer[] getIPv4Addresses(); + public IPv4Address[] getIPv4Addresses(); /** * Get all unique attachment points associated with the device. This will @@ -85,7 +89,7 @@ public interface IDevice { * @param swp the switch port to query * @return an array containing the unique VLAN IDs */ - public Short[] getSwitchPortVlanIds(SwitchPort swp); + public VlanVid[] getSwitchPortVlanIds(SwitchPort swp); /** * Get the most recent timestamp for this device diff --git a/src/main/java/net/floodlightcontroller/devicemanager/IDeviceService.java b/src/main/java/net/floodlightcontroller/devicemanager/IDeviceService.java index eb3801344f73d68ab0c35e6db556c09edfa1ba68..40d729f6894149d9a1e3142c618bc606c0753a4b 100755 --- a/src/main/java/net/floodlightcontroller/devicemanager/IDeviceService.java +++ b/src/main/java/net/floodlightcontroller/devicemanager/IDeviceService.java @@ -22,6 +22,12 @@ import java.util.EnumSet; import java.util.Iterator; import java.util.Set; +import org.projectfloodlight.openflow.types.DatapathId; +import org.projectfloodlight.openflow.types.IPv4Address; +import org.projectfloodlight.openflow.types.MacAddress; +import org.projectfloodlight.openflow.types.VlanVid; +import org.projectfloodlight.openflow.types.OFPort; + import net.floodlightcontroller.core.FloodlightContextStore; import net.floodlightcontroller.core.module.IFloodlightService; @@ -95,9 +101,9 @@ public interface IDeviceService extends IFloodlightService { * @throws IllegalArgumentException if not all key fields of the * current {@link IEntityClassifierService} are specified. */ - public IDevice findDevice(long macAddress, Short vlan, - Integer ipv4Address, Long switchDPID, - Integer switchPort) + public IDevice findDevice(MacAddress macAddress, VlanVid vlan, + IPv4Address ipv4Address, DatapathId switchDPID, + OFPort switchPort) throws IllegalArgumentException; /** @@ -122,8 +128,8 @@ public interface IDeviceService extends IFloodlightService { * source's {@link IEntityClass} are specified. */ public IDevice findClassDevice(IEntityClass entityClass, - long macAddress, Short vlan, - Integer ipv4Address) + MacAddress macAddress, VlanVid vlan, + IPv4Address ipv4Address) throws IllegalArgumentException; /** @@ -162,11 +168,11 @@ public interface IDeviceService extends IFloodlightService { * @see IDeviceService#queryClassDevices(IEntityClass, Long, * Short, Integer, Long, Integer) */ - public Iterator<? extends IDevice> queryDevices(Long macAddress, - Short vlan, - Integer ipv4Address, - Long switchDPID, - Integer switchPort); + public Iterator<? extends IDevice> queryDevices(MacAddress macAddress, + VlanVid vlan, + IPv4Address ipv4Address, + DatapathId switchDPID, + OFPort switchPort); /** * Find devices that match the provided query. Only the index for @@ -187,11 +193,11 @@ public interface IDeviceService extends IFloodlightService { * Short, Integer, Long, Integer) */ public Iterator<? extends IDevice> queryClassDevices(IEntityClass entityClass, - Long macAddress, - Short vlan, - Integer ipv4Address, - Long switchDPID, - Integer switchPort); + MacAddress macAddress, + VlanVid vlan, + IPv4Address ipv4Address, + DatapathId switchDPID, + OFPort switchPort); /** * Adds a listener to listen for IDeviceManagerServices notifications @@ -207,9 +213,9 @@ public interface IDeviceService extends IFloodlightService { * @param sw * @param port */ - public void addSuppressAPs(long swId, short port); + public void addSuppressAPs(DatapathId swId, OFPort port); - public void removeSuppressAPs(long swId, short port); + public void removeSuppressAPs(DatapathId swId, OFPort port); public Set<SwitchPort> getSuppressAPs(); diff --git a/src/main/java/net/floodlightcontroller/devicemanager/IEntityClassifierService.java b/src/main/java/net/floodlightcontroller/devicemanager/IEntityClassifierService.java index 2569a7df54ed892464810ca415b12a52d1ea76a7..f3e17ccfea12f90ff7136f79eb78a19d4bc94d1a 100644 --- a/src/main/java/net/floodlightcontroller/devicemanager/IEntityClassifierService.java +++ b/src/main/java/net/floodlightcontroller/devicemanager/IEntityClassifierService.java @@ -80,8 +80,7 @@ public interface IEntityClassifierService extends IFloodlightService { * @param entity the entity to reclassify * @return the IEntityClass resulting from the classification */ - IEntityClass reclassifyEntity(IDevice curDevice, - Entity entity); + IEntityClass reclassifyEntity(IDevice curDevice, Entity entity); /** * Once reclassification is complete for a device, this method will be @@ -95,8 +94,7 @@ public interface IEntityClassifierService extends IFloodlightService { * @param newDevices all the new devices derived from the entities of the * old device. If null, the old device was unchanged. */ - void deviceUpdate(IDevice oldDevice, - Collection<? extends IDevice> newDevices); + void deviceUpdate(IDevice oldDevice, Collection<? extends IDevice> newDevices); /** * Adds a listener to listen for IEntityClassifierServices notifications diff --git a/src/main/java/net/floodlightcontroller/devicemanager/SwitchPort.java b/src/main/java/net/floodlightcontroller/devicemanager/SwitchPort.java index 9fe62d9507c0d2e575bebaa8bdcc86b8c15676a4..d057f9c4a353f0501631f6cf7725fd00d4be5b4e 100644 --- a/src/main/java/net/floodlightcontroller/devicemanager/SwitchPort.java +++ b/src/main/java/net/floodlightcontroller/devicemanager/SwitchPort.java @@ -17,7 +17,8 @@ package net.floodlightcontroller.devicemanager; -import org.openflow.util.HexString; +import org.projectfloodlight.openflow.types.DatapathId; +import org.projectfloodlight.openflow.types.OFPort; import net.floodlightcontroller.core.web.serializers.DPIDSerializer; @@ -55,8 +56,8 @@ public class SwitchPort { } } - private final long switchDPID; - private final int port; + private final DatapathId switchDPID; + private final OFPort port; private final ErrorStatus errorStatus; /** @@ -65,7 +66,7 @@ public class SwitchPort { * @param port the port * @param errorStatus any error status for the switch port */ - public SwitchPort(long switchDPID, int port, ErrorStatus errorStatus) { + public SwitchPort(DatapathId switchDPID, OFPort port, ErrorStatus errorStatus) { super(); this.switchDPID = switchDPID; this.port = port; @@ -77,7 +78,7 @@ public class SwitchPort { * @param switchDPID the dpid * @param port the port */ - public SwitchPort(long switchDPID, int port) { + public SwitchPort(DatapathId switchDPID, OFPort port) { super(); this.switchDPID = switchDPID; this.port = port; @@ -89,11 +90,11 @@ public class SwitchPort { // *************** @JsonSerialize(using=DPIDSerializer.class) - public long getSwitchDPID() { + public DatapathId getSwitchDPID() { return switchDPID; } - public int getPort() { + public OFPort getPort() { return port; } @@ -113,8 +114,8 @@ public class SwitchPort { + ((errorStatus == null) ? 0 : errorStatus.hashCode()); - result = prime * result + port; - result = prime * result + (int) (switchDPID ^ (switchDPID >>> 32)); + result = prime * result + port.getPortNumber(); + result = prime * result + (int) (switchDPID.getLong() ^ (switchDPID.getLong() >>> 32)); return result; } @@ -132,7 +133,7 @@ public class SwitchPort { @Override public String toString() { - return "SwitchPort [switchDPID=" + HexString.toHexString(switchDPID) + + return "SwitchPort [switchDPID=" + switchDPID.toString() + ", port=" + port + ", errorStatus=" + errorStatus + "]"; } diff --git a/src/main/java/net/floodlightcontroller/devicemanager/internal/AttachmentPoint.java b/src/main/java/net/floodlightcontroller/devicemanager/internal/AttachmentPoint.java index a08a3a5d2cb723582cf422b5ca988a22ac61e3f2..da7490f1ef9a9e80983aa48fad27f39242580779 100644 --- a/src/main/java/net/floodlightcontroller/devicemanager/internal/AttachmentPoint.java +++ b/src/main/java/net/floodlightcontroller/devicemanager/internal/AttachmentPoint.java @@ -21,11 +21,16 @@ package net.floodlightcontroller.devicemanager.internal; +import java.util.Date; + +import org.projectfloodlight.openflow.types.DatapathId; +import org.projectfloodlight.openflow.types.OFPort; + public class AttachmentPoint { - long sw; - short port; - long activeSince; - long lastSeen; + DatapathId sw; + OFPort port; + Date activeSince; + Date lastSeen; // Timeout for moving attachment points from OF/broadcast // domain to another. @@ -34,15 +39,14 @@ public class AttachmentPoint { public static final long OPENFLOW_TO_EXTERNAL_TIMEOUT = 30000; // 30 seconds public static final long CONSISTENT_TIMEOUT = 30000; // 30 seconds - public AttachmentPoint(long sw, short port, long activeSince, - long lastSeen) { + public AttachmentPoint(DatapathId sw, OFPort port, Date activeSince, Date lastSeen) { this.sw = sw; this.port = port; this.activeSince = activeSince; this.lastSeen = lastSeen; } - public AttachmentPoint(long sw, short port, long lastSeen) { + public AttachmentPoint(DatapathId sw, OFPort port, Date lastSeen) { this.sw = sw; this.port = port; this.lastSeen = lastSeen; @@ -50,37 +54,37 @@ public class AttachmentPoint { } public AttachmentPoint(AttachmentPoint ap) { - this.sw = ap.sw; + this.sw = ap.getSw(); this.port = ap.port; this.activeSince = ap.activeSince; this.lastSeen = ap.lastSeen; } - public long getSw() { + public DatapathId getSw() { return sw; } - public void setSw(long sw) { + public void setSw(DatapathId sw) { this.sw = sw; } - public short getPort() { + public OFPort getPort() { return port; } - public void setPort(short port) { + public void setPort(OFPort port) { this.port = port; } - public long getActiveSince() { + public Date getActiveSince() { return activeSince; } - public void setActiveSince(long activeSince) { + public void setActiveSince(Date activeSince) { this.activeSince = activeSince; } - public long getLastSeen() { + public Date getLastSeen() { return lastSeen; } - public void setLastSeen(long lastSeen) { - if (this.lastSeen + INACTIVITY_INTERVAL < lastSeen) + public void setLastSeen(Date lastSeen) { + if (this.lastSeen.getTime() + INACTIVITY_INTERVAL < lastSeen.getTime()) this.activeSince = lastSeen; - if (this.lastSeen < lastSeen) + if (this.lastSeen.before(lastSeen)) this.lastSeen = lastSeen; } @@ -91,8 +95,8 @@ public class AttachmentPoint { public int hashCode() { final int prime = 31; int result = 1; - result = prime * result + port; - result = prime * result + (int) (sw ^ (sw >>> 32)); + result = prime * result + port.getPortNumber(); + result = prime * result + (int) (sw.getLong() ^ (sw.getLong() >>> 32)); return result; } @@ -108,9 +112,9 @@ public class AttachmentPoint { if (getClass() != obj.getClass()) return false; AttachmentPoint other = (AttachmentPoint) obj; - if (port != other.port) + if (port.getPortNumber() != other.port.getPortNumber()) return false; - if (sw != other.sw) + if (sw.getLong() != other.sw.getLong()) return false; return true; } @@ -118,7 +122,7 @@ public class AttachmentPoint { @Override public String toString() { return "AttachmentPoint [sw=" + sw + ", port=" + port - + ", activeSince=" + activeSince + ", lastSeen=" + lastSeen + + ", activeSince=" + activeSince + ", lastSeen=" + lastSeen.toString() + "]"; } } diff --git a/src/main/java/net/floodlightcontroller/devicemanager/internal/DefaultEntityClassifier.java b/src/main/java/net/floodlightcontroller/devicemanager/internal/DefaultEntityClassifier.java index faed0d4016147080ce254122b02fa6b8f5c4975f..ea55f8cac2bba0f33746375ff96b40a8ad6825f4 100644 --- a/src/main/java/net/floodlightcontroller/devicemanager/internal/DefaultEntityClassifier.java +++ b/src/main/java/net/floodlightcontroller/devicemanager/internal/DefaultEntityClassifier.java @@ -68,8 +68,7 @@ public class DefaultEntityClassifier implements static { keyFields = EnumSet.of(DeviceField.MAC, DeviceField.VLAN); } - protected static DefaultEntityClass entityClass = - new DefaultEntityClass("DefaultEntityClass"); + protected static DefaultEntityClass entityClass = new DefaultEntityClass("DefaultEntityClass"); @Override public IEntityClass classifyEntity(Entity entity) { @@ -77,14 +76,12 @@ public class DefaultEntityClassifier implements } @Override - public IEntityClass reclassifyEntity(IDevice curDevice, - Entity entity) { + public IEntityClass reclassifyEntity(IDevice curDevice, Entity entity) { return entityClass; } @Override - public void deviceUpdate(IDevice oldDevice, - Collection<? extends IDevice> newDevices) { + public void deviceUpdate(IDevice oldDevice, Collection<? extends IDevice> newDevices) { // no-op } @@ -95,8 +92,7 @@ public class DefaultEntityClassifier implements @Override public Collection<Class<? extends IFloodlightService>> getModuleServices() { - Collection<Class<? extends IFloodlightService>> l = - new ArrayList<Class<? extends IFloodlightService>>(); + Collection<Class<? extends IFloodlightService>> l = new ArrayList<Class<? extends IFloodlightService>>(); l.add(IEntityClassifierService.class); return l; } @@ -104,24 +100,20 @@ public class DefaultEntityClassifier implements @Override public Map<Class<? extends IFloodlightService>, IFloodlightService> getServiceImpls() { Map<Class<? extends IFloodlightService>, - IFloodlightService> m = - new HashMap<Class<? extends IFloodlightService>, - IFloodlightService>(); + IFloodlightService> m = new HashMap<Class<? extends IFloodlightService>, IFloodlightService>(); // We are the class that implements the service m.put(IEntityClassifierService.class, this); return m; } @Override - public Collection<Class<? extends IFloodlightService>> - getModuleDependencies() { + public Collection<Class<? extends IFloodlightService>> getModuleDependencies() { // No dependencies return null; } @Override - public void init(FloodlightModuleContext context) - throws FloodlightModuleException { + public void init(FloodlightModuleContext context) throws FloodlightModuleException { // no-op } diff --git a/src/main/java/net/floodlightcontroller/devicemanager/internal/Device.java b/src/main/java/net/floodlightcontroller/devicemanager/internal/Device.java index ef0ba346809496090c83e273be35c5576699369c..5b663e3246df6b42c74cb5fe80f6525486716969 100755 --- a/src/main/java/net/floodlightcontroller/devicemanager/internal/Device.java +++ b/src/main/java/net/floodlightcontroller/devicemanager/internal/Device.java @@ -30,7 +30,12 @@ import java.util.Map; import java.util.TreeSet; import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import org.openflow.util.HexString; + +import org.projectfloodlight.openflow.types.DatapathId; +import org.projectfloodlight.openflow.types.IPv4Address; +import org.projectfloodlight.openflow.types.MacAddress; +import org.projectfloodlight.openflow.types.OFPort; +import org.projectfloodlight.openflow.types.VlanVid; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -40,8 +45,6 @@ import net.floodlightcontroller.devicemanager.IDevice; import net.floodlightcontroller.devicemanager.IEntityClass; import net.floodlightcontroller.devicemanager.SwitchPort; import net.floodlightcontroller.devicemanager.SwitchPort.ErrorStatus; -import net.floodlightcontroller.packet.Ethernet; -import net.floodlightcontroller.packet.IPv4; import net.floodlightcontroller.topology.ITopologyService; /** @@ -50,8 +53,7 @@ import net.floodlightcontroller.topology.ITopologyService; */ @JsonSerialize(using=DeviceSerializer.class) public class Device implements IDevice { - protected static Logger log = - LoggerFactory.getLogger(Device.class); + protected static Logger log = LoggerFactory.getLogger(Device.class); private final Long deviceKey; protected final DeviceManagerImpl deviceManager; @@ -61,7 +63,7 @@ public class Device implements IDevice { protected final String macAddressString; // the vlan Ids from the entities of this device - protected final Short[] vlanIds; + protected final VlanVid[] vlanIds; protected volatile String dhcpClientName; /** @@ -85,15 +87,12 @@ public class Device implements IDevice { * @param entity the initial entity for the device * @param entityClass the entity classes associated with the entity */ - public Device(DeviceManagerImpl deviceManager, - Long deviceKey, - Entity entity, - IEntityClass entityClass) { + public Device(DeviceManagerImpl deviceManager, Long deviceKey, + Entity entity, IEntityClass entityClass) { this.deviceManager = deviceManager; this.deviceKey = deviceKey; this.entities = new Entity[] {entity}; - this.macAddressString = - HexString.toHexString(entity.getMacAddress(), 6); + this.macAddressString = entity.getMacAddress().toString(); this.entityClass = entityClass; Arrays.sort(this.entities); @@ -101,16 +100,13 @@ public class Device implements IDevice { this.oldAPs = null; this.attachmentPoints = null; - if (entity.getSwitchDPID() != null && - entity.getSwitchPort() != null){ - long sw = entity.getSwitchDPID(); - short port = entity.getSwitchPort().shortValue(); + if (entity.getSwitchDPID() != null && entity.getSwitchPort() != null) { + DatapathId sw = entity.getSwitchDPID(); + OFPort port = entity.getSwitchPort(); if (deviceManager.isValidAttachmentPoint(sw, port)) { AttachmentPoint ap; - ap = new AttachmentPoint(sw, port, - entity.getLastSeenTimestamp().getTime()); - + ap = new AttachmentPoint(sw, port, entity.getLastSeenTimestamp()); this.attachmentPoints = new ArrayList<AttachmentPoint>(); this.attachmentPoints.add(ap); } @@ -139,15 +135,12 @@ public class Device implements IDevice { this.oldAPs = null; this.attachmentPoints = null; if (oldAPs != null) { - this.oldAPs = - new ArrayList<AttachmentPoint>(oldAPs); + this.oldAPs = new ArrayList<AttachmentPoint>(oldAPs); } if (attachmentPoints != null) { - this.attachmentPoints = - new ArrayList<AttachmentPoint>(attachmentPoints); + this.attachmentPoints = new ArrayList<AttachmentPoint>(attachmentPoints); } - this.macAddressString = - HexString.toHexString(this.entities[0].getMacAddress(), 6); + this.macAddressString = this.entities[0].getMacAddress().toString(); this.entityClass = entityClass; Arrays.sort(this.entities); vlanIds = computeVlandIds(); @@ -165,31 +158,26 @@ public class Device implements IDevice { * new entity should be inserted. If negative we will compute the * correct insertion point */ - public Device(Device device, - Entity newEntity, - int insertionpoint) { + public Device(Device device, Entity newEntity, int insertionpoint) { this.deviceManager = device.deviceManager; this.deviceKey = device.deviceKey; this.dhcpClientName = device.dhcpClientName; this.entities = new Entity[device.entities.length + 1]; if (insertionpoint < 0) { - insertionpoint = -(Arrays.binarySearch(device.entities, - newEntity)+1); + insertionpoint = -(Arrays.binarySearch(device.entities, newEntity) + 1); } if (insertionpoint > 0) { // insertion point is not the beginning: // copy up to insertion point - System.arraycopy(device.entities, 0, - this.entities, 0, - insertionpoint); + System.arraycopy(device.entities, 0, this.entities, 0, insertionpoint); } if (insertionpoint < device.entities.length) { // insertion point is not the end // copy from insertion point System.arraycopy(device.entities, insertionpoint, - this.entities, insertionpoint+1, - device.entities.length-insertionpoint); + this.entities, insertionpoint + 1, + device.entities.length - insertionpoint); } this.entities[insertionpoint] = newEntity; /* @@ -200,39 +188,36 @@ public class Device implements IDevice { */ this.oldAPs = null; if (device.oldAPs != null) { - this.oldAPs = - new ArrayList<AttachmentPoint>(device.oldAPs); + this.oldAPs = new ArrayList<AttachmentPoint>(device.oldAPs); } this.attachmentPoints = null; if (device.attachmentPoints != null) { - this.attachmentPoints = - new ArrayList<AttachmentPoint>(device.attachmentPoints); + this.attachmentPoints = new ArrayList<AttachmentPoint>(device.attachmentPoints); } - this.macAddressString = - HexString.toHexString(this.entities[0].getMacAddress(), 6); + this.macAddressString = this.entities[0].getMacAddress().toString(); this.entityClass = device.entityClass; vlanIds = computeVlandIds(); } - private Short[] computeVlandIds() { + private VlanVid[] computeVlandIds() { if (entities.length == 1) { if (entities[0].getVlan() != null) { - return new Short[]{ entities[0].getVlan() }; + return new VlanVid[]{ entities[0].getVlan() }; } else { - return new Short[] { Short.valueOf((short)-1) }; + return new VlanVid[] { VlanVid.ofVlan(-1) }; } } - TreeSet<Short> vals = new TreeSet<Short>(); + TreeSet<VlanVid> vals = new TreeSet<VlanVid>(); for (Entity e : entities) { if (e.getVlan() == null) - vals.add((short)-1); + vals.add(VlanVid.ofVlan(-1)); else vals.add(e.getVlan()); } - return vals.toArray(new Short[vals.size()]); + return vals.toArray(new VlanVid[vals.size()]); } /** @@ -241,7 +226,7 @@ public class Device implements IDevice { * @param apList * @return */ - private Map<Long, AttachmentPoint> getAPMap(List<AttachmentPoint> apList) { + private Map<DatapathId, AttachmentPoint> getAPMap(List<AttachmentPoint> apList) { if (apList == null) return null; ITopologyService topology = deviceManager.topology; @@ -254,7 +239,7 @@ public class Device implements IDevice { List<AttachmentPoint>tempAP = new ArrayList<AttachmentPoint>(); for(AttachmentPoint ap: oldAP) { - if (deviceManager.isValidAttachmentPoint(ap.getSw(), ap.getPort())){ + if (deviceManager.isValidAttachmentPoint(ap.getSw(), ap.getPort())) { tempAP.add(ap); } } @@ -263,16 +248,15 @@ public class Device implements IDevice { Collections.sort(oldAP, deviceManager.apComparator); // Map of attachment point by L2 domain Id. - Map<Long, AttachmentPoint> apMap = new HashMap<Long, AttachmentPoint>(); + Map<DatapathId, AttachmentPoint> apMap = new HashMap<DatapathId, AttachmentPoint>(); for(int i=0; i<oldAP.size(); ++i) { AttachmentPoint ap = oldAP.get(i); // if this is not a valid attachment point, continue - if (!deviceManager.isValidAttachmentPoint(ap.getSw(), - ap.getPort())) + if (!deviceManager.isValidAttachmentPoint(ap.getSw(), ap.getPort())) continue; - long id = topology.getL2DomainId(ap.getSw()); + DatapathId id = topology.getL2DomainId(ap.getSw()); apMap.put(id, ap); } @@ -293,9 +277,9 @@ public class Device implements IDevice { if (apList == null) return false; for(AttachmentPoint ap: apList) { - if (ap.getLastSeen() + AttachmentPoint.INACTIVITY_INTERVAL < - System.currentTimeMillis()) + if (ap.getLastSeen().getTime() + AttachmentPoint.INACTIVITY_INTERVAL < System.currentTimeMillis()) { expiredAPs.add(ap); + } } if (expiredAPs.size() > 0) { apList.removeAll(expiredAPs); @@ -315,25 +299,23 @@ public class Device implements IDevice { * @param apMap * @return */ - List<AttachmentPoint> getDuplicateAttachmentPoints(List<AttachmentPoint>oldAPList, - Map<Long, AttachmentPoint>apMap) { + List<AttachmentPoint> getDuplicateAttachmentPoints(List<AttachmentPoint>oldAPList, Map<DatapathId, AttachmentPoint>apMap) { ITopologyService topology = deviceManager.topology; List<AttachmentPoint> dupAPs = new ArrayList<AttachmentPoint>(); - long timeThreshold = System.currentTimeMillis() - - AttachmentPoint.INACTIVITY_INTERVAL; + long timeThreshold = System.currentTimeMillis() - AttachmentPoint.INACTIVITY_INTERVAL; if (oldAPList == null || apMap == null) return dupAPs; - for(AttachmentPoint ap: oldAPList) { - long id = topology.getL2DomainId(ap.getSw()); + for(AttachmentPoint ap : oldAPList) { + DatapathId id = topology.getL2DomainId(ap.getSw()); AttachmentPoint trueAP = apMap.get(id); if (trueAP == null) continue; boolean c = (topology.isConsistent(trueAP.getSw(), trueAP.getPort(), ap.getSw(), ap.getPort())); - boolean active = (ap.getActiveSince() > trueAP.getActiveSince()); - boolean last = ap.getLastSeen() > timeThreshold; + boolean active = (ap.getActiveSince().after(trueAP.getActiveSince())); + boolean last = ap.getLastSeen().getTime() > timeThreshold; if (!c && active && last) { dupAPs.add(ap); } @@ -357,7 +339,7 @@ public class Device implements IDevice { List<AttachmentPoint> apList = new ArrayList<AttachmentPoint>(); if (attachmentPoints != null) apList.addAll(attachmentPoints); - Map<Long, AttachmentPoint> newMap = getAPMap(apList); + Map<DatapathId, AttachmentPoint> newMap = getAPMap(apList); if (newMap == null || newMap.size() != apList.size()) { moved = true; } @@ -365,8 +347,7 @@ public class Device implements IDevice { // Prepare the new attachment point list. if (moved) { log.info("updateAttachmentPoint: ap {} newmap {} ", attachmentPoints, newMap); - List<AttachmentPoint> newAPList = - new ArrayList<AttachmentPoint>(); + List<AttachmentPoint> newAPList = new ArrayList<AttachmentPoint>(); if (newMap != null) newAPList.addAll(newMap.values()); this.attachmentPoints = newAPList; } @@ -385,7 +366,7 @@ public class Device implements IDevice { * @param lastSeen * @return */ - protected boolean updateAttachmentPoint(long sw, short port, long lastSeen){ + protected boolean updateAttachmentPoint(DatapathId sw, OFPort port, Date lastSeen){ ITopologyService topology = deviceManager.topology; List<AttachmentPoint> oldAPList; List<AttachmentPoint> apList; @@ -412,7 +393,7 @@ public class Device implements IDevice { // newAP now contains the new attachment point. // Get the APMap is null or empty. - Map<Long, AttachmentPoint> apMap = getAPMap(apList); + Map<DatapathId, AttachmentPoint> apMap = getAPMap(apList); if (apMap == null || apMap.isEmpty()) { apList.add(newAP); attachmentPoints = apList; @@ -423,11 +404,10 @@ public class Device implements IDevice { return true; } - long id = topology.getL2DomainId(sw); + DatapathId id = topology.getL2DomainId(sw); AttachmentPoint oldAP = apMap.get(id); - if (oldAP == null) // No attachment on this L2 domain. - { + if (oldAP == null) { // No attachment on this L2 domain. apList = new ArrayList<AttachmentPoint>(); apList.addAll(apMap.values()); apList.add(newAP); @@ -439,7 +419,7 @@ public class Device implements IDevice { // we need to compare oldAP and newAP. if (oldAP.equals(newAP)) { // nothing to do here. just the last seen has to be changed. - if (newAP.lastSeen > oldAP.lastSeen) { + if (newAP.lastSeen.after(oldAP.lastSeen)) { oldAP.setLastSeen(newAP.lastSeen); } this.attachmentPoints = @@ -480,9 +460,9 @@ public class Device implements IDevice { * @param port * @return */ - public boolean deleteAttachmentPoint(long sw, short port) { - AttachmentPoint ap = new AttachmentPoint(sw, port, 0); - + public boolean deleteAttachmentPoint(DatapathId sw, OFPort port) { + AttachmentPoint ap = new AttachmentPoint(sw, port, new Date(0)); + if (this.oldAPs != null) { ArrayList<AttachmentPoint> apList = new ArrayList<AttachmentPoint>(); apList.addAll(this.oldAPs); @@ -506,7 +486,7 @@ public class Device implements IDevice { return false; } - public boolean deleteAttachmentPoint(long sw) { + public boolean deleteAttachmentPoint(DatapathId sw) { boolean deletedFlag; ArrayList<AttachmentPoint> apList; ArrayList<AttachmentPoint> modifiedList; @@ -519,7 +499,7 @@ public class Device implements IDevice { modifiedList = new ArrayList<AttachmentPoint>(); for(AttachmentPoint ap: apList) { - if (ap.getSw() == sw) { + if (ap.getSw().equals(sw)) { deletedFlag = true; } else { modifiedList.add(ap); @@ -538,7 +518,7 @@ public class Device implements IDevice { modifiedList = new ArrayList<AttachmentPoint>(); for(AttachmentPoint ap: apList) { - if (ap.getSw() == sw) { + if (ap.getSw().equals(sw)) { deletedFlag = true; } else { modifiedList.add(ap); @@ -617,7 +597,7 @@ public class Device implements IDevice { List<AttachmentPoint> dupList; // get AP map. - Map<Long, AttachmentPoint> apMap = getAPMap(apList); + Map<DatapathId, AttachmentPoint> apMap = getAPMap(apList); dupList = this.getDuplicateAttachmentPoints(oldAPList, apMap); if (dupList != null) { for(AttachmentPoint ap: dupList) { @@ -636,7 +616,7 @@ public class Device implements IDevice { } @Override - public long getMACAddress() { + public MacAddress getMACAddress() { // we assume only one MAC per device for now. return entities[0].getMacAddress(); } @@ -647,18 +627,18 @@ public class Device implements IDevice { } @Override - public Short[] getVlanId() { + public VlanVid[] getVlanId() { return Arrays.copyOf(vlanIds, vlanIds.length); } static final EnumSet<DeviceField> ipv4Fields = EnumSet.of(DeviceField.IPV4); @Override - public Integer[] getIPv4Addresses() { + public IPv4Address[] getIPv4Addresses() { // XXX - TODO we can cache this result. Let's find out if this // is really a performance bottleneck first though. - TreeSet<Integer> vals = new TreeSet<Integer>(); + TreeSet<IPv4Address> vals = new TreeSet<IPv4Address>(); for (Entity e : entities) { if (e.getIpv4Address() == null) continue; @@ -689,22 +669,22 @@ public class Device implements IDevice { vals.add(e.getIpv4Address()); } - return vals.toArray(new Integer[vals.size()]); + return vals.toArray(new IPv4Address[vals.size()]); } @Override - public Short[] getSwitchPortVlanIds(SwitchPort swp) { - TreeSet<Short> vals = new TreeSet<Short>(); + public VlanVid[] getSwitchPortVlanIds(SwitchPort swp) { + TreeSet<VlanVid> vals = new TreeSet<VlanVid>(); for (Entity e : entities) { if (e.switchDPID == swp.getSwitchDPID() && e.switchPort == swp.getPort()) { if (e.getVlan() == null) - vals.add(Ethernet.VLAN_UNTAGGED); + vals.add(VlanVid.ofVlan(-1)); //TODO @Ryan is this the correct way to represent an untagged vlan? else vals.add(e.getVlan()); } } - return vals.toArray(new Short[vals.size()]); + return vals.toArray(new VlanVid[vals.size()]); } @Override @@ -782,11 +762,11 @@ public class Device implements IDevice { builder.append(macAddressString); builder.append(", IPs=["); boolean isFirst = true; - for (Integer ip: getIPv4Addresses()) { + for (IPv4Address ip: getIPv4Addresses()) { if (!isFirst) builder.append(", "); isFirst = false; - builder.append(IPv4.fromIPv4Address(ip)); + builder.append(ip.toString()); } builder.append("], APs="); builder.append(Arrays.toString(getAttachmentPoints(true))); diff --git a/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceIndex.java b/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceIndex.java index 0d8ea75dd42466600e9abf24637903750479985c..0c5100498a87c1c94736a90e793d1968c282283e 100644 --- a/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceIndex.java +++ b/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceIndex.java @@ -71,8 +71,9 @@ public abstract class DeviceIndex { * update will not fail because of a concurrent update * @param device the device to update * @param deviceKey the device key for the device + * @return */ - public abstract void updateIndex(Entity entity, Long deviceKey); + public abstract boolean updateIndex(Entity entity, Long deviceKey); /** * Remove the entry for the given entity diff --git a/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceIterator.java b/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceIterator.java index 2cbea66e5577495461171661f479c4f26335f11f..7d4d0c35a9c141eb08a82ea52e7283993bdbafaa 100644 --- a/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceIterator.java +++ b/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceIterator.java @@ -19,6 +19,13 @@ package net.floodlightcontroller.devicemanager.internal; import java.util.Arrays; import java.util.Iterator; +import java.util.List; + +import org.projectfloodlight.openflow.types.DatapathId; +import org.projectfloodlight.openflow.types.IPv4Address; +import org.projectfloodlight.openflow.types.MacAddress; +import org.projectfloodlight.openflow.types.OFPort; +import org.projectfloodlight.openflow.types.VlanVid; import net.floodlightcontroller.devicemanager.IEntityClass; import net.floodlightcontroller.devicemanager.SwitchPort; @@ -30,11 +37,11 @@ import net.floodlightcontroller.util.FilterIterator; public class DeviceIterator extends FilterIterator<Device> { private IEntityClass[] entityClasses; - private Long macAddress; - private Short vlan; - private Integer ipv4Address; - private Long switchDPID; - private Integer switchPort; + private MacAddress macAddress; + private VlanVid vlan; + private IPv4Address ipv4Address; + private DatapathId switchDPID; + private OFPort switchPort; /** * Construct a new device iterator over the key fields @@ -48,11 +55,11 @@ public class DeviceIterator extends FilterIterator<Device> { */ public DeviceIterator(Iterator<Device> subIterator, IEntityClass[] entityClasses, - Long macAddress, - Short vlan, - Integer ipv4Address, - Long switchDPID, - Integer switchPort) { + MacAddress macAddress, + VlanVid vlan, + IPv4Address ipv4Address, + DatapathId switchDPID, + OFPort switchPort) { super(subIterator); this.entityClasses = entityClasses; this.subIterator = subIterator; @@ -80,18 +87,31 @@ public class DeviceIterator extends FilterIterator<Device> { if (!match) return false; } if (macAddress != null) { - if (macAddress.longValue() != value.getMACAddress()) + if (!macAddress.equals(value.getMACAddress())) return false; } if (vlan != null) { - Short[] vlans = value.getVlanId(); - if (Arrays.binarySearch(vlans, vlan) < 0) + VlanVid[] vlans = value.getVlanId(); + List<VlanVid> searchableVlanList = Arrays.asList(vlans); + if (!searchableVlanList.contains(vlan)) { + return false; + } + /*TODO @Ryan Does the above replace the below? + * Maybe should change the return of Device to be List/ArrayList instead + if (Arrays.binarySearch(vlans.vlans, vlan) < 0) return false; + */ } if (ipv4Address != null) { - Integer[] ipv4Addresses = value.getIPv4Addresses(); - if (Arrays.binarySearch(ipv4Addresses, ipv4Address) < 0) + IPv4Address[] ipv4Addresses = value.getIPv4Addresses(); + /*TODO @Ryan same here... + if (Arrays.binarySearch(ipv4Addresses, ipv4Address) < 0) return false; + */ + List<IPv4Address> searchableIPv4AddrList = Arrays.asList(ipv4Addresses); + if (!searchableIPv4AddrList.contains(ipv4Address)) { + return false; + } } if (switchDPID != null || switchPort != null) { SwitchPort[] sps = value.getAttachmentPoints(); @@ -100,11 +120,11 @@ public class DeviceIterator extends FilterIterator<Device> { match = false; for (SwitchPort sp : sps) { if (switchDPID != null) { - if (switchDPID.longValue() != sp.getSwitchDPID()) + if (switchDPID.getLong() != sp.getSwitchDPID().getLong()) return false; } if (switchPort != null) { - if (switchPort.intValue() != sp.getPort()) + if (switchPort.getPortNumber() != sp.getPort().getPortNumber()) return false; } match = true; diff --git a/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceManagerImpl.java b/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceManagerImpl.java index e4f67420bed7489558a389dd6714c14b57451766..63aad5e3ebfafda042543217f1370f1aad69867e 100755 --- a/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceManagerImpl.java +++ b/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceManagerImpl.java @@ -47,7 +47,7 @@ import net.floodlightcontroller.core.IHAListener; import net.floodlightcontroller.core.IInfoProvider; import net.floodlightcontroller.core.IOFMessageListener; import net.floodlightcontroller.core.IOFSwitch; -import net.floodlightcontroller.core.IFloodlightProviderService.Role; +import net.floodlightcontroller.core.HARole; import net.floodlightcontroller.core.module.FloodlightModuleContext; import net.floodlightcontroller.core.module.FloodlightModuleException; import net.floodlightcontroller.core.module.IFloodlightModule; @@ -56,16 +56,12 @@ import net.floodlightcontroller.core.util.ListenerDispatcher; import net.floodlightcontroller.core.util.SingletonTask; import net.floodlightcontroller.debugcounter.IDebugCounter; import net.floodlightcontroller.debugcounter.IDebugCounterService; -import net.floodlightcontroller.debugcounter.IDebugCounterService.CounterException; -import net.floodlightcontroller.debugcounter.NullDebugCounter; -import net.floodlightcontroller.debugcounter.IDebugCounterService.CounterType; +import net.floodlightcontroller.debugevent.DebugEventService.EventCategoryBuilder; import net.floodlightcontroller.debugevent.IDebugEventService; 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.debugevent.IEventUpdater; -import net.floodlightcontroller.debugevent.NullDebugEvent; +import net.floodlightcontroller.debugevent.IEventCategory; import net.floodlightcontroller.devicemanager.IDevice; import net.floodlightcontroller.devicemanager.IDeviceService; import net.floodlightcontroller.devicemanager.IEntityClass; @@ -93,14 +89,21 @@ import net.floodlightcontroller.threadpool.IThreadPoolService; import net.floodlightcontroller.topology.ITopologyListener; import net.floodlightcontroller.topology.ITopologyService; import net.floodlightcontroller.util.MultiIterator; +import net.floodlightcontroller.util.OFMatchWithSwDpid; import static net.floodlightcontroller.devicemanager.internal. DeviceManagerImpl.DeviceUpdate.Change.*; -import org.openflow.protocol.OFMatchWithSwDpid; -import org.openflow.protocol.OFMessage; -import org.openflow.protocol.OFPacketIn; -import org.openflow.protocol.OFPort; -import org.openflow.protocol.OFType; +import org.projectfloodlight.openflow.protocol.OFMessage; +import org.projectfloodlight.openflow.protocol.OFPacketIn; +import org.projectfloodlight.openflow.protocol.OFVersion; +import org.projectfloodlight.openflow.types.DatapathId; +import org.projectfloodlight.openflow.types.IPv4Address; +import org.projectfloodlight.openflow.types.MacAddress; +import org.projectfloodlight.openflow.types.OFPort; +import org.projectfloodlight.openflow.types.OFVlanVidMatch; +import org.projectfloodlight.openflow.types.VlanVid; +import org.projectfloodlight.openflow.protocol.OFType; +import org.projectfloodlight.openflow.protocol.match.MatchField; import org.sdnplatform.sync.IClosableIterator; import org.sdnplatform.sync.IStoreClient; import org.sdnplatform.sync.ISyncService; @@ -117,1329 +120,1281 @@ import org.slf4j.LoggerFactory; * within the network. * @author readams */ -public class DeviceManagerImpl implements -IDeviceService, IOFMessageListener, ITopologyListener, -IFloodlightModule, IEntityClassListener, -IFlowReconcileListener, IInfoProvider { - protected static Logger logger = - LoggerFactory.getLogger(DeviceManagerImpl.class); - - - protected IFloodlightProviderService floodlightProvider; - protected ITopologyService topology; - protected IStorageSourceService storageSource; - protected IRestApiService restApi; - protected IThreadPoolService threadPool; - protected IFlowReconcileService flowReconcileMgr; - protected IFlowReconcileEngineService flowReconcileEngine; - protected IDebugCounterService debugCounters; - private ISyncService syncService; - private IStoreClient<String,DeviceSyncRepresentation> storeClient; - private DeviceSyncManager deviceSyncManager; - - /** - * Debug Counters - */ - public static final String MODULE_NAME = "devicemanager"; - public static final String PACKAGE = DeviceManagerImpl.class.getPackage().getName(); - public IDebugCounter cntIncoming; - public IDebugCounter cntReconcileRequest; - public IDebugCounter cntReconcileNoSource; - public IDebugCounter cntReconcileNoDest; - public IDebugCounter cntInvalidSource; - public IDebugCounter cntInvalidDest; - public IDebugCounter cntNoSource; - public IDebugCounter cntNoDest; - public IDebugCounter cntDhcpClientNameSnooped; - public IDebugCounter cntDeviceOnInternalPortNotLearned; - public IDebugCounter cntPacketNotAllowed; - public IDebugCounter cntNewDevice; - public IDebugCounter cntPacketOnInternalPortForKnownDevice; - public IDebugCounter cntNewEntity; - public IDebugCounter cntDeviceChanged; - public IDebugCounter cntDeviceMoved; - public IDebugCounter cntCleanupEntitiesRuns; - public IDebugCounter cntEntityRemovedTimeout; - public IDebugCounter cntDeviceDeleted; - public IDebugCounter cntDeviceReclassifyDelete; - public IDebugCounter cntDeviceStrored; - public IDebugCounter cntDeviceStoreThrottled; - public IDebugCounter cntDeviceRemovedFromStore; - public IDebugCounter cntSyncException; - public IDebugCounter cntDevicesFromStore; - public IDebugCounter cntConsolidateStoreRuns; - public IDebugCounter cntConsolidateStoreDevicesRemoved; - public IDebugCounter cntTransitionToMaster; - - /** - * Debug Events - */ - private IDebugEventService debugEvents; - private IEventUpdater<DeviceEvent> evDevice; - - private boolean isMaster = false; - - static final String DEVICE_SYNC_STORE_NAME = - DeviceManagerImpl.class.getCanonicalName() + ".stateStore"; - - /** - * Time interval between writes of entries for the same device to - * the sync store. - */ - static final int DEFAULT_SYNC_STORE_WRITE_INTERVAL_MS = - 5*60*1000; // 5 min - private int syncStoreWriteIntervalMs = DEFAULT_SYNC_STORE_WRITE_INTERVAL_MS; - - /** - * Time after SLAVE->MASTER until we run the consolidate store - * code. - */ - static final int DEFAULT_INITIAL_SYNC_STORE_CONSOLIDATE_MS = - 15*1000; // 15 sec - private int initialSyncStoreConsolidateMs = - DEFAULT_INITIAL_SYNC_STORE_CONSOLIDATE_MS; - - /** - * Time interval between consolidate store runs. - */ - static final int DEFAULT_SYNC_STORE_CONSOLIDATE_INTERVAL_MS = - 75*60*1000; // 75 min - private final int syncStoreConsolidateIntervalMs = - DEFAULT_SYNC_STORE_CONSOLIDATE_INTERVAL_MS; - - /** - * Time in milliseconds before entities will expire - */ - protected static final int ENTITY_TIMEOUT = 60*60*1000; - - /** - * Time in seconds between cleaning up old entities/devices - */ - protected static final int ENTITY_CLEANUP_INTERVAL = 60*60; - - /** - * This is the master device map that maps device IDs to {@link Device} - * objects. - */ - protected ConcurrentHashMap<Long, Device> deviceMap; - - /** - * Counter used to generate device keys - */ - protected AtomicLong deviceKeyCounter = new AtomicLong(0); - - /** - * This is the primary entity index that contains all entities - */ - protected DeviceUniqueIndex primaryIndex; - - /** - * This stores secondary indices over the fields in the devices - */ - protected Map<EnumSet<DeviceField>, DeviceIndex> secondaryIndexMap; - - /** - * This map contains state for each of the {@ref IEntityClass} - * that exist - */ - protected ConcurrentHashMap<String, ClassState> classStateMap; - - /** - * This is the list of indices we want on a per-class basis - */ - protected Set<EnumSet<DeviceField>> perClassIndices; - - /** - * The entity classifier currently in use - */ - protected IEntityClassifierService entityClassifier; - - /** - * Used to cache state about specific entity classes - */ - protected class ClassState { - - /** - * The class index - */ - protected DeviceUniqueIndex classIndex; - - /** - * This stores secondary indices over the fields in the device for the - * class - */ - protected Map<EnumSet<DeviceField>, DeviceIndex> secondaryIndexMap; - - /** - * Allocate a new {@link ClassState} object for the class - * @param clazz the class to use for the state - */ - public ClassState(IEntityClass clazz) { - EnumSet<DeviceField> keyFields = clazz.getKeyFields(); - EnumSet<DeviceField> primaryKeyFields = - entityClassifier.getKeyFields(); - boolean keyFieldsMatchPrimary = - primaryKeyFields.equals(keyFields); - - if (!keyFieldsMatchPrimary) - classIndex = new DeviceUniqueIndex(keyFields); - - secondaryIndexMap = - new HashMap<EnumSet<DeviceField>, DeviceIndex>(); - for (EnumSet<DeviceField> fields : perClassIndices) { - secondaryIndexMap.put(fields, - new DeviceMultiIndex(fields)); - } - } - } - - /** - * Device manager event listeners - * reclassifyDeviceListeners are notified first before reconcileDeviceListeners. - * This is to make sure devices are correctly reclassified before reconciliation. - */ - protected ListenerDispatcher<String,IDeviceListener> deviceListeners; - - /** - * A device update event to be dispatched - */ - protected static class DeviceUpdate { - public enum Change { - ADD, DELETE, CHANGE; - } - - /** - * The affected device - */ - protected Device device; - - /** - * The change that was made - */ - protected Change change; - - /** - * If not added, then this is the list of fields changed - */ - protected EnumSet<DeviceField> fieldsChanged; - - public DeviceUpdate(Device device, Change change, - EnumSet<DeviceField> fieldsChanged) { - super(); - this.device = device; - this.change = change; - this.fieldsChanged = fieldsChanged; - } - - @Override - public String toString() { - String devIdStr = device.getEntityClass().getName() + "::" + - device.getMACAddressString(); - return "DeviceUpdate [device=" + devIdStr + ", change=" + change - + ", fieldsChanged=" + fieldsChanged + "]"; - } - - } - - /** - * AttachmentPointComparator - * - * Compares two attachment points and returns the latest one. - * It is assumed that the two attachment points are in the same - * L2 domain. - * - * @author srini - */ - protected class AttachmentPointComparator - implements Comparator<AttachmentPoint> { - public AttachmentPointComparator() { - super(); - } - - @Override - public int compare(AttachmentPoint oldAP, AttachmentPoint newAP) { - //First compare based on L2 domain ID; - - long oldSw = oldAP.getSw(); - short oldPort = oldAP.getPort(); - long oldDomain = topology.getL2DomainId(oldSw); - boolean oldBD = topology.isBroadcastDomainPort(oldSw, oldPort); - - long newSw = newAP.getSw(); - short newPort = newAP.getPort(); - long newDomain = topology.getL2DomainId(newSw); - boolean newBD = topology.isBroadcastDomainPort(newSw, newPort); - - if (oldDomain < newDomain) return -1; - else if (oldDomain > newDomain) return 1; - - - // Give preference to OFPP_LOCAL always - if (oldPort != OFPort.OFPP_LOCAL.getValue() && - newPort == OFPort.OFPP_LOCAL.getValue()) { - return -1; - } else if (oldPort == OFPort.OFPP_LOCAL.getValue() && - newPort != OFPort.OFPP_LOCAL.getValue()) { - return 1; - } - - // We expect that the last seen of the new AP is higher than - // old AP, if it is not, just reverse and send the negative - // of the result. - if (oldAP.getActiveSince() > newAP.getActiveSince()) - return -compare(newAP, oldAP); - - long activeOffset = 0; - if (!topology.isConsistent(oldSw, oldPort, newSw, newPort)) { - if (!newBD && oldBD) { - return -1; - } - if (newBD && oldBD) { - activeOffset = AttachmentPoint.EXTERNAL_TO_EXTERNAL_TIMEOUT; - } - else if (newBD && !oldBD){ - activeOffset = AttachmentPoint.OPENFLOW_TO_EXTERNAL_TIMEOUT; - } - - } else { - // The attachment point is consistent. - activeOffset = AttachmentPoint.CONSISTENT_TIMEOUT; - } - - - if ((newAP.getActiveSince() > oldAP.getLastSeen() + activeOffset) || - (newAP.getLastSeen() > oldAP.getLastSeen() + - AttachmentPoint.INACTIVITY_INTERVAL)) { - return -1; - } - return 1; - } - } - /** - * Comparator for sorting by cluster ID - */ - public AttachmentPointComparator apComparator; - - /** - * Switch ports where attachment points shouldn't be learned - */ - private Set<SwitchPort> suppressAPs; - - /** - * Periodic task to clean up expired entities - */ - public SingletonTask entityCleanupTask; - - - /** - * Periodic task to consolidate entries in the store. I.e., delete - * entries in the store that are not known to DeviceManager - */ - private SingletonTask storeConsolidateTask; - - /** - * Listens for HA notifications - */ - protected HAListenerDelegate haListenerDelegate; - - - // ********************* - // IDeviceManagerService - // ********************* - - @Override - public IDevice getDevice(Long deviceKey) { - return deviceMap.get(deviceKey); - } - - @Override - public IDevice findDevice(long macAddress, Short vlan, - Integer ipv4Address, Long switchDPID, - Integer switchPort) - throws IllegalArgumentException { - if (vlan != null && vlan.shortValue() <= 0) - vlan = null; - if (ipv4Address != null && ipv4Address == 0) - ipv4Address = null; - Entity e = new Entity(macAddress, vlan, ipv4Address, switchDPID, - switchPort, null); - if (!allKeyFieldsPresent(e, entityClassifier.getKeyFields())) { - throw new IllegalArgumentException("Not all key fields specified." - + " Required fields: " + entityClassifier.getKeyFields()); - } - return findDeviceByEntity(e); - } - - @Override - public IDevice findClassDevice(IEntityClass entityClass, long macAddress, - Short vlan, Integer ipv4Address) - throws IllegalArgumentException { - if (vlan != null && vlan.shortValue() <= 0) - vlan = null; - if (ipv4Address != null && ipv4Address == 0) - ipv4Address = null; - Entity e = new Entity(macAddress, vlan, ipv4Address, - null, null, null); - if (entityClass == null || - !allKeyFieldsPresent(e, entityClass.getKeyFields())) { - throw new IllegalArgumentException("Not all key fields and/or " - + " no source device specified. Required fields: " + - entityClassifier.getKeyFields()); - } - return findDestByEntity(entityClass, e); - } - - @Override - public Collection<? extends IDevice> getAllDevices() { - return Collections.unmodifiableCollection(deviceMap.values()); - } - - @Override - public void addIndex(boolean perClass, - EnumSet<DeviceField> keyFields) { - if (perClass) { - perClassIndices.add(keyFields); - } else { - secondaryIndexMap.put(keyFields, - new DeviceMultiIndex(keyFields)); - } - } - - @Override - public Iterator<? extends IDevice> queryDevices(Long macAddress, - Short vlan, - Integer ipv4Address, - Long switchDPID, - Integer switchPort) { - DeviceIndex index = null; - if (secondaryIndexMap.size() > 0) { - EnumSet<DeviceField> keys = - getEntityKeys(macAddress, vlan, ipv4Address, - switchDPID, switchPort); - index = secondaryIndexMap.get(keys); - } - - Iterator<Device> deviceIterator = null; - if (index == null) { - // Do a full table scan - deviceIterator = deviceMap.values().iterator(); - } else { - // index lookup - Entity entity = new Entity((macAddress == null ? 0 : macAddress), - vlan, - ipv4Address, - switchDPID, - switchPort, - null); - deviceIterator = - new DeviceIndexInterator(this, index.queryByEntity(entity)); - } - - DeviceIterator di = - new DeviceIterator(deviceIterator, - null, - macAddress, - vlan, - ipv4Address, - switchDPID, - switchPort); - return di; - } - - @Override - public Iterator<? extends IDevice> queryClassDevices(IEntityClass entityClass, - Long macAddress, - Short vlan, - Integer ipv4Address, - Long switchDPID, - Integer switchPort) { - ArrayList<Iterator<Device>> iterators = - new ArrayList<Iterator<Device>>(); - ClassState classState = getClassState(entityClass); - - DeviceIndex index = null; - if (classState.secondaryIndexMap.size() > 0) { - EnumSet<DeviceField> keys = - getEntityKeys(macAddress, vlan, ipv4Address, - switchDPID, switchPort); - index = classState.secondaryIndexMap.get(keys); - } - - Iterator<Device> iter; - if (index == null) { - index = classState.classIndex; - if (index == null) { - // scan all devices - return new DeviceIterator(deviceMap.values().iterator(), - new IEntityClass[] { entityClass }, - macAddress, vlan, ipv4Address, - switchDPID, switchPort); - } else { - // scan the entire class - iter = new DeviceIndexInterator(this, index.getAll()); - } - } else { - // index lookup - Entity entity = - new Entity((macAddress == null ? 0 : macAddress), - vlan, - ipv4Address, - switchDPID, - switchPort, - null); - iter = new DeviceIndexInterator(this, - index.queryByEntity(entity)); - } - iterators.add(iter); - - return new MultiIterator<Device>(iterators.iterator()); - } - - protected Iterator<Device> getDeviceIteratorForQuery(Long macAddress, - Short vlan, - Integer ipv4Address, - Long switchDPID, - Integer switchPort) { - DeviceIndex index = null; - if (secondaryIndexMap.size() > 0) { - EnumSet<DeviceField> keys = - getEntityKeys(macAddress, vlan, ipv4Address, - switchDPID, switchPort); - index = secondaryIndexMap.get(keys); - } - - Iterator<Device> deviceIterator = null; - if (index == null) { - // Do a full table scan - deviceIterator = deviceMap.values().iterator(); - } else { - // index lookup - Entity entity = new Entity((macAddress == null ? 0 : macAddress), - vlan, - ipv4Address, - switchDPID, - switchPort, - null); - deviceIterator = - new DeviceIndexInterator(this, index.queryByEntity(entity)); - } - - DeviceIterator di = - new DeviceIterator(deviceIterator, - null, - macAddress, - vlan, - ipv4Address, - switchDPID, - switchPort); - return di; - } - - @Override - public void addListener(IDeviceListener listener) { - deviceListeners.addListener("device", listener); - logListeners(); - } - - @Override - public void addSuppressAPs(long swId, short port) { - suppressAPs.add(new SwitchPort(swId, port)); - } - - @Override - public void removeSuppressAPs(long swId, short port) { - suppressAPs.remove(new SwitchPort(swId, port)); - } - - @Override - public Set<SwitchPort> getSuppressAPs() { - return Collections.unmodifiableSet(suppressAPs); - } - - private void logListeners() { - List<IDeviceListener> listeners = deviceListeners.getOrderedListeners(); - if (listeners != null) { - StringBuffer sb = new StringBuffer(); - sb.append("DeviceListeners: "); - for (IDeviceListener l : listeners) { - sb.append(l.getName()); - sb.append(","); - } - logger.debug(sb.toString()); - } - } - - // *************** - // IDeviceListener - // *************** - private class DeviceDebugEventLogger implements IDeviceListener { - @Override - public String getName() { - return "deviceDebugEventLogger"; - } - - @Override - public boolean isCallbackOrderingPrereq(String type, String name) { - return false; - } - - @Override - public boolean isCallbackOrderingPostreq(String type, String name) { - return false; - } - - @Override - public void deviceAdded(IDevice device) { - generateDeviceEvent(device, "host-added"); - } - - @Override - public void deviceRemoved(IDevice device) { - generateDeviceEvent(device, "host-removed"); - } - - @Override - public void deviceMoved(IDevice device) { - generateDeviceEvent(device, "host-moved"); - } - - @Override - public void deviceIPV4AddrChanged(IDevice device) { - generateDeviceEvent(device, "host-ipv4-addr-changed"); - } - - @Override - public void deviceVlanChanged(IDevice device) { - generateDeviceEvent(device, "host-vlan-changed"); - } - - private void generateDeviceEvent(IDevice device, String reason) { - List<Integer> ipv4Addresses = - new ArrayList<Integer>(Arrays.asList(device.getIPv4Addresses())); - List<SwitchPort> oldAps = - new ArrayList<SwitchPort>(Arrays.asList(device.getOldAP())); - List<SwitchPort> currentAps = - new ArrayList<SwitchPort>(Arrays.asList(device.getAttachmentPoints())); - List<Short> vlanIds = - new ArrayList<Short>(Arrays.asList(device.getVlanId())); - - evDevice.updateEventNoFlush( - new DeviceEvent(device.getMACAddress(), - ipv4Addresses, - oldAps, - currentAps, - vlanIds, reason)); - } - } - - // ************* - // IInfoProvider - // ************* - - @Override - public Map<String, Object> getInfo(String type) { - if (!"summary".equals(type)) - return null; - - Map<String, Object> info = new HashMap<String, Object>(); - info.put("# hosts", deviceMap.size()); - return info; - } - - // ****************** - // IOFMessageListener - // ****************** - - @Override - public String getName() { - return MODULE_NAME; - } - - @Override - public boolean isCallbackOrderingPrereq(OFType type, String name) { - return ((type == OFType.PACKET_IN || type == OFType.FLOW_MOD) - && name.equals("topology")); - } - - @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: - cntIncoming.updateCounterNoFlush(); - return this.processPacketInMessage(sw, - (OFPacketIn) msg, cntx); - default: - break; - } - return Command.CONTINUE; - } - - // *************** - // IFlowReconcileListener - // *************** - @Override - public Command reconcileFlows(ArrayList<OFMatchReconcile> ofmRcList) { - ListIterator<OFMatchReconcile> iter = ofmRcList.listIterator(); - while (iter.hasNext()) { - OFMatchReconcile ofm = iter.next(); - - // Remove the STOPPed flow. - if (Command.STOP == reconcileFlow(ofm)) { - iter.remove(); - } - } - - if (ofmRcList.size() > 0) { - return Command.CONTINUE; - } else { - return Command.STOP; - } - } - - protected Command reconcileFlow(OFMatchReconcile ofm) { - cntReconcileRequest.updateCounterNoFlush(); - // Extract source entity information - Entity srcEntity = - getEntityFromFlowMod(ofm.ofmWithSwDpid, true); - if (srcEntity == null) { - cntReconcileNoSource.updateCounterNoFlush(); - return Command.STOP; - } - - // Find the device by source entity - Device srcDevice = findDeviceByEntity(srcEntity); - if (srcDevice == null) { - cntReconcileNoSource.updateCounterNoFlush(); - return Command.STOP; - } - // Store the source device in the context - fcStore.put(ofm.cntx, CONTEXT_SRC_DEVICE, srcDevice); - - // Find the device matching the destination from the entity - // classes of the source. - Entity dstEntity = getEntityFromFlowMod(ofm.ofmWithSwDpid, false); - Device dstDevice = null; - if (dstEntity != null) { - dstDevice = findDestByEntity(srcDevice.getEntityClass(), dstEntity); - if (dstDevice != null) - fcStore.put(ofm.cntx, CONTEXT_DST_DEVICE, dstDevice); - else - cntReconcileNoDest.updateCounterNoFlush(); - } else { - cntReconcileNoDest.updateCounterNoFlush(); - } - if (logger.isTraceEnabled()) { - logger.trace("Reconciling flow: match={}, srcEntity={}, srcDev={}, " - + "dstEntity={}, dstDev={}", - new Object[] {ofm.ofmWithSwDpid.getOfMatch(), - srcEntity, srcDevice, - dstEntity, dstDevice } ); - } - return Command.CONTINUE; - } - - // ***************** - // IFloodlightModule - // ***************** - - @Override - public Collection<Class<? extends IFloodlightService>> getModuleServices() { - Collection<Class<? extends IFloodlightService>> l = - new ArrayList<Class<? extends IFloodlightService>>(); - l.add(IDeviceService.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(IDeviceService.class, this); - return m; - } - - @Override - public Collection<Class<? extends IFloodlightService>> getModuleDependencies() { - Collection<Class<? extends IFloodlightService>> l = - new ArrayList<Class<? extends IFloodlightService>>(); - l.add(IFloodlightProviderService.class); - l.add(IStorageSourceService.class); - l.add(ITopologyService.class); - l.add(IRestApiService.class); - l.add(IThreadPoolService.class); - l.add(IFlowReconcileService.class); - l.add(IEntityClassifierService.class); - l.add(ISyncService.class); - return l; - } - - @Override - public void init(FloodlightModuleContext fmc) throws FloodlightModuleException { - this.perClassIndices = - new HashSet<EnumSet<DeviceField>>(); - addIndex(true, EnumSet.of(DeviceField.IPV4)); - - this.deviceListeners = new ListenerDispatcher<String, IDeviceListener>(); - this.suppressAPs = Collections.newSetFromMap( - new ConcurrentHashMap<SwitchPort, Boolean>()); - - this.floodlightProvider = - fmc.getServiceImpl(IFloodlightProviderService.class); - this.storageSource = - fmc.getServiceImpl(IStorageSourceService.class); - this.topology = - fmc.getServiceImpl(ITopologyService.class); - this.restApi = fmc.getServiceImpl(IRestApiService.class); - this.threadPool = fmc.getServiceImpl(IThreadPoolService.class); - this.flowReconcileMgr = fmc.getServiceImpl(IFlowReconcileService.class); - this.flowReconcileEngine = fmc.getServiceImpl(IFlowReconcileEngineService.class); - this.entityClassifier = fmc.getServiceImpl(IEntityClassifierService.class); - this.debugCounters = fmc.getServiceImpl(IDebugCounterService.class); - this.debugEvents = fmc.getServiceImpl(IDebugEventService.class); - this.syncService = fmc.getServiceImpl(ISyncService.class); - this.deviceSyncManager = new DeviceSyncManager(); - this.haListenerDelegate = new HAListenerDelegate(); - registerDeviceManagerDebugCounters(); - registerDeviceManagerDebugEvents(); - this.addListener(new DeviceDebugEventLogger()); - } - - private void registerDeviceManagerDebugEvents() throws FloodlightModuleException { - if (debugEvents == null) { - debugEvents = new NullDebugEvent(); - } - try { - evDevice = - debugEvents.registerEvent(PACKAGE, "hostevent", - "Host added, removed, updated, or moved", - EventType.ALWAYS_LOG, DeviceEvent.class, 500); - } catch (MaxEventsRegistered e) { - throw new FloodlightModuleException("Max events registered", e); - } - } - - @Override - public void startUp(FloodlightModuleContext fmc) - throws FloodlightModuleException { - isMaster = (floodlightProvider.getRole() == Role.MASTER); - primaryIndex = new DeviceUniqueIndex(entityClassifier.getKeyFields()); - secondaryIndexMap = new HashMap<EnumSet<DeviceField>, DeviceIndex>(); - - deviceMap = new ConcurrentHashMap<Long, Device>(); - classStateMap = - new ConcurrentHashMap<String, ClassState>(); - apComparator = new AttachmentPointComparator(); - - floodlightProvider.addOFMessageListener(OFType.PACKET_IN, this); - floodlightProvider.addHAListener(this.haListenerDelegate); - if (topology != null) - topology.addListener(this); - flowReconcileMgr.addFlowReconcileListener(this); - entityClassifier.addListener(this); - - ScheduledExecutorService ses = threadPool.getScheduledExecutor(); - Runnable ecr = new Runnable() { - @Override - public void run() { - cleanupEntities(); - entityCleanupTask.reschedule(ENTITY_CLEANUP_INTERVAL, - TimeUnit.SECONDS); - } - }; - entityCleanupTask = new SingletonTask(ses, ecr); - entityCleanupTask.reschedule(ENTITY_CLEANUP_INTERVAL, - TimeUnit.SECONDS); - - Runnable consolidateStoreRunner = new Runnable() { - @Override - public void run() { - deviceSyncManager.consolidateStore(); - storeConsolidateTask.reschedule(syncStoreConsolidateIntervalMs, - TimeUnit.MILLISECONDS); - } - }; - storeConsolidateTask = new SingletonTask(ses, consolidateStoreRunner); - if (isMaster) - storeConsolidateTask.reschedule(syncStoreConsolidateIntervalMs, - TimeUnit.MILLISECONDS); - - - if (restApi != null) { - restApi.addRestletRoutable(new DeviceRoutable()); - } else { - logger.debug("Could not instantiate REST API"); - } - - try { - this.syncService.registerStore(DEVICE_SYNC_STORE_NAME, Scope.LOCAL); - this.storeClient = this.syncService - .getStoreClient(DEVICE_SYNC_STORE_NAME, - String.class, - DeviceSyncRepresentation.class); - } catch (SyncException e) { - throw new FloodlightModuleException("Error while setting up sync service", e); - } - floodlightProvider.addInfoProvider("summary", this); - } - - private void registerDeviceManagerDebugCounters() throws FloodlightModuleException { - if (debugCounters == null) { - logger.error("Debug Counter Service not found."); - debugCounters = new NullDebugCounter(); - } - try { - cntIncoming = debugCounters.registerCounter(PACKAGE, "incoming", - "All incoming packets seen by this module", CounterType.ALWAYS_COUNT); - cntReconcileRequest = debugCounters.registerCounter(PACKAGE, - "reconcile-request", - "Number of flows that have been received for reconciliation by " + - "this module", - CounterType.ALWAYS_COUNT); - cntReconcileNoSource = debugCounters.registerCounter(PACKAGE, - "reconcile-no-source-device", - "Number of flow reconcile events that failed because no source " + - "device could be identified", - CounterType.ALWAYS_COUNT, IDebugCounterService.CTR_MDATA_WARN); // is this really a IDebugCounterService.CTR_MDATA_WARNing - cntReconcileNoDest = debugCounters.registerCounter(PACKAGE, - "reconcile-no-dest-device", - "Number of flow reconcile events that failed because no " + - "destination device could be identified", - CounterType.ALWAYS_COUNT, IDebugCounterService.CTR_MDATA_WARN); // is this really a IDebugCounterService.CTR_MDATA_WARNing - cntInvalidSource = debugCounters.registerCounter(PACKAGE, - "invalid-source", - "Number of packetIns that were discarded because the source " + - "MAC was invalid (broadcast, multicast, or zero)", - CounterType.ALWAYS_COUNT, IDebugCounterService.CTR_MDATA_WARN); - cntNoSource = debugCounters.registerCounter(PACKAGE, "no-source-device", - "Number of packetIns that were discarded because the " + - "could not identify a source device. This can happen if a " + - "packet is not allowed, appears on an illegal port, does not " + - "have a valid address space, etc.", - CounterType.ALWAYS_COUNT, IDebugCounterService.CTR_MDATA_WARN); - cntInvalidDest = debugCounters.registerCounter(PACKAGE, - "invalid-dest", - "Number of packetIns that were discarded because the dest " + - "MAC was invalid (zero)", - CounterType.ALWAYS_COUNT, IDebugCounterService.CTR_MDATA_WARN); - cntNoDest = debugCounters.registerCounter(PACKAGE, "no-dest-device", - "Number of packetIns that did not have an associated " + - "destination device. E.g., because the destination MAC is " + - "broadcast/multicast or is not yet known to the controller.", - CounterType.ALWAYS_COUNT); - cntDhcpClientNameSnooped = debugCounters.registerCounter(PACKAGE, - "dhcp-client-name-snooped", - "Number of times a DHCP client name was snooped from a " + - "packetIn.", - CounterType.ALWAYS_COUNT); - cntDeviceOnInternalPortNotLearned = debugCounters.registerCounter( - PACKAGE, - "device-on-internal-port-not-learned", - "Number of times packetIn was received on an internal port and" + - "no source device is known for the source MAC. The packetIn is " + - "discarded.", - CounterType.ALWAYS_COUNT, IDebugCounterService.CTR_MDATA_WARN); - cntPacketNotAllowed = debugCounters.registerCounter(PACKAGE, - "packet-not-allowed", - "Number of times a packetIn was not allowed due to spoofing " + - "protection configuration.", - CounterType.ALWAYS_COUNT, IDebugCounterService.CTR_MDATA_WARN); // is this really a IDebugCounterService.CTR_MDATA_WARNing? - cntNewDevice = debugCounters.registerCounter(PACKAGE, "new-device", - "Number of times a new device was learned", - CounterType.ALWAYS_COUNT); - cntPacketOnInternalPortForKnownDevice = debugCounters.registerCounter( - PACKAGE, - "packet-on-internal-port-for-known-device", - "Number of times a packetIn was received on an internal port " + - "for a known device.", - CounterType.ALWAYS_COUNT); - cntNewEntity = debugCounters.registerCounter(PACKAGE, "new-entity", - "Number of times a new entity was learned for an existing device", - CounterType.ALWAYS_COUNT); - cntDeviceChanged = debugCounters.registerCounter(PACKAGE, "device-changed", - "Number of times device properties have changed", - CounterType.ALWAYS_COUNT); - cntDeviceMoved = debugCounters.registerCounter(PACKAGE, "device-moved", - "Number of times devices have moved", - CounterType.ALWAYS_COUNT); - cntCleanupEntitiesRuns = debugCounters.registerCounter(PACKAGE, - "cleanup-entities-runs", - "Number of times the entity cleanup task has been run", - CounterType.ALWAYS_COUNT); - cntEntityRemovedTimeout = debugCounters.registerCounter(PACKAGE, - "entity-removed-timeout", - "Number of times entities have been removed due to timeout " + - "(entity has been inactive for " + ENTITY_TIMEOUT/1000 + "s)", - CounterType.ALWAYS_COUNT); - cntDeviceDeleted = debugCounters.registerCounter(PACKAGE, "device-deleted", - "Number of devices that have been removed due to inactivity", - CounterType.ALWAYS_COUNT); - cntDeviceReclassifyDelete = debugCounters.registerCounter(PACKAGE, - "device-reclassify-delete", - "Number of devices that required reclassification and have been " + - "temporarily delete for reclassification", - CounterType.ALWAYS_COUNT); - cntDeviceStrored = debugCounters.registerCounter(PACKAGE, "device-stored", - "Number of device entries written or updated to the sync store", - CounterType.ALWAYS_COUNT); - cntDeviceStoreThrottled = debugCounters.registerCounter(PACKAGE, - "device-store-throttled", - "Number of times a device update to the sync store was " + - "requested but not performed because the same device entities " + - "have recently been updated already", - CounterType.ALWAYS_COUNT); - cntDeviceRemovedFromStore = debugCounters.registerCounter(PACKAGE, - "device-removed-from-store", - "Number of devices that were removed from the sync store " + - "because the local controller removed the device due to " + - "inactivity", - CounterType.ALWAYS_COUNT); - cntSyncException = debugCounters.registerCounter(PACKAGE, "sync-exception", - "Number of times an operation on the sync store resulted in " + - "sync exception", - CounterType.ALWAYS_COUNT, IDebugCounterService.CTR_MDATA_WARN); // it this an error? - cntDevicesFromStore = debugCounters.registerCounter(PACKAGE, - "devices-from-store", - "Number of devices that were read from the sync store after " + - "the local controller transitioned from SLAVE to MASTER", - CounterType.ALWAYS_COUNT); - cntConsolidateStoreRuns = debugCounters.registerCounter(PACKAGE, - "consolidate-store-runs", - "Number of times the task to consolidate entries in the " + - "store witch live known devices has been run", - CounterType.ALWAYS_COUNT); - cntConsolidateStoreDevicesRemoved = debugCounters.registerCounter(PACKAGE, - "consolidate-store-devices-removed", - "Number of times a device has been removed from the sync " + - "store because no corresponding live device is known. " + - "This indicates a remote controller still writing device " + - "entries despite the local controller being MASTER or an " + - "incosistent store update from the local controller.", - CounterType.ALWAYS_COUNT, IDebugCounterService.CTR_MDATA_WARN); - cntTransitionToMaster = debugCounters.registerCounter(PACKAGE, - "transition-to-master", - "Number of times this controller has transitioned from SLAVE " + - "to MASTER role. Will be 0 or 1.", - CounterType.ALWAYS_COUNT); - } catch (CounterException e) { - throw new FloodlightModuleException(e.getMessage()); - } - } - - // *************** - // IHAListener - // *************** - - protected class HAListenerDelegate implements IHAListener { - @Override - public void transitionToMaster() { - DeviceManagerImpl.this.isMaster = true; - DeviceManagerImpl.this.deviceSyncManager.goToMaster(); - } - - @Override - public void controllerNodeIPsChanged( - Map<String, String> curControllerNodeIPs, - Map<String, String> addedControllerNodeIPs, - Map<String, String> removedControllerNodeIPs) { - // no-op - } - - @Override - public String getName() { - return DeviceManagerImpl.this.getName(); - } - - @Override - public boolean isCallbackOrderingPrereq(HAListenerTypeMarker type, - String name) { - return ("topology".equals(name) || - "bvsmanager".equals(name)); - } - - @Override - public boolean isCallbackOrderingPostreq(HAListenerTypeMarker type, - String name) { - // TODO Auto-generated method stub - return false; - } - } - - - // **************** - // Internal methods - // **************** - - protected Command processPacketInMessage(IOFSwitch sw, OFPacketIn pi, - FloodlightContext cntx) { - Ethernet eth = - IFloodlightProviderService.bcStore. - get(cntx,IFloodlightProviderService.CONTEXT_PI_PAYLOAD); - - // Extract source entity information - Entity srcEntity = - getSourceEntityFromPacket(eth, sw.getId(), pi.getInPort()); - if (srcEntity == null) { - cntInvalidSource.updateCounterNoFlush(); - return Command.STOP; - } - - // Learn from ARP packet for special VRRP settings. - // In VRRP settings, the source MAC address and sender MAC - // addresses can be different. In such cases, we need to learn - // the IP to MAC mapping of the VRRP IP address. The source - // entity will not have that information. Hence, a separate call - // to learn devices in such cases. - learnDeviceFromArpResponseData(eth, sw.getId(), pi.getInPort()); - - // Learn/lookup device information - Device srcDevice = learnDeviceByEntity(srcEntity); - if (srcDevice == null) { - cntNoSource.updateCounterNoFlush(); - return Command.STOP; - } - - // Store the source device in the context - fcStore.put(cntx, CONTEXT_SRC_DEVICE, srcDevice); - - // Find the device matching the destination from the entity - // classes of the source. - if (eth.getDestinationMAC().toLong() == 0) { - cntInvalidDest.updateCounterNoFlush(); - return Command.STOP; - } - Entity dstEntity = getDestEntityFromPacket(eth); - Device dstDevice = null; - if (dstEntity != null) { - dstDevice = - findDestByEntity(srcDevice.getEntityClass(), dstEntity); - if (dstDevice != null) - fcStore.put(cntx, CONTEXT_DST_DEVICE, dstDevice); - else - cntNoDest.updateCounterNoFlush(); - } else { - cntNoDest.updateCounterNoFlush(); - } - - if (logger.isTraceEnabled()) { - logger.trace("Received PI: {} on switch {}, port {} *** eth={}" + - " *** srcDev={} *** dstDev={} *** ", - new Object[] { pi, sw.getStringId(), pi.getInPort(), eth, - srcDevice, dstDevice }); - } - - snoopDHCPClientName(eth, srcDevice); - - return Command.CONTINUE; - } - - /** - * Snoop and record client-provided host name from DHCP requests - * @param eth - * @param srcDevice - */ - private void snoopDHCPClientName(Ethernet eth, Device srcDevice) { - if (! (eth.getPayload() instanceof IPv4) ) - return; - IPv4 ipv4 = (IPv4) eth.getPayload(); - if (! (ipv4.getPayload() instanceof UDP) ) - return; - UDP udp = (UDP) ipv4.getPayload(); - if (!(udp.getPayload() instanceof DHCP)) - return; - DHCP dhcp = (DHCP) udp.getPayload(); - byte opcode = dhcp.getOpCode(); - if (opcode == DHCP.OPCODE_REQUEST) { - DHCPOption dhcpOption = dhcp.getOption( - DHCPOptionCode.OptionCode_Hostname); - if (dhcpOption != null) { - cntDhcpClientNameSnooped.updateCounterNoFlush(); - srcDevice.dhcpClientName = new String(dhcpOption.getData()); - } - } - } - - /** - * Check whether the given attachment point is valid given the current - * topology - * @param switchDPID the DPID - * @param switchPort the port - * @return true if it's a valid attachment point - */ - public boolean isValidAttachmentPoint(long switchDPID, - int switchPort) { - if (topology.isAttachmentPointPort(switchDPID, - (short)switchPort) == false) - return false; - - if (suppressAPs.contains(new SwitchPort(switchDPID, switchPort))) - return false; - - return true; - } - - /** - * Get sender IP address from packet if the packet is an ARP - * packet and if the source MAC address matches the ARP packets - * sender MAC address. - * @param eth - * @param dlAddr - * @return - */ - private int getSrcNwAddr(Ethernet eth, long dlAddr) { - if (eth.getPayload() instanceof ARP) { - ARP arp = (ARP) eth.getPayload(); - if ((arp.getProtocolType() == ARP.PROTO_TYPE_IP) && - (Ethernet.toLong(arp.getSenderHardwareAddress()) == dlAddr)) { - return IPv4.toIPv4Address(arp.getSenderProtocolAddress()); - } - } - return 0; - } - - /** - * Parse an entity from an {@link Ethernet} packet. - * @param eth the packet to parse - * @param sw the switch on which the packet arrived - * @param pi the original packetin - * @return the entity from the packet - */ - protected Entity getSourceEntityFromPacket(Ethernet eth, - long swdpid, - int port) { - byte[] dlAddrArr = eth.getSourceMACAddress(); - long dlAddr = Ethernet.toLong(dlAddrArr); - - // Ignore broadcast/multicast source - if ((dlAddrArr[0] & 0x1) != 0) - return null; - // Ignore 0 source mac - if (dlAddr == 0) - return null; - - short vlan = eth.getVlanID(); - int nwSrc = getSrcNwAddr(eth, dlAddr); - return new Entity(dlAddr, - ((vlan >= 0) ? vlan : null), - ((nwSrc != 0) ? nwSrc : null), - swdpid, - port, - new Date()); - } - - /** - * Learn device from ARP data in scenarios where the - * Ethernet source MAC is different from the sender hardware - * address in ARP data. - */ - protected void learnDeviceFromArpResponseData(Ethernet eth, - long swdpid, - int port) { - - if (!(eth.getPayload() instanceof ARP)) return; - ARP arp = (ARP) eth.getPayload(); - - byte[] dlAddrArr = eth.getSourceMACAddress(); - long dlAddr = Ethernet.toLong(dlAddrArr); - - byte[] senderHardwareAddr = arp.getSenderHardwareAddress(); - long senderAddr = Ethernet.toLong(senderHardwareAddr); - - if (dlAddr == senderAddr) return; - - // Ignore broadcast/multicast source - if ((senderHardwareAddr[0] & 0x1) != 0) - return; - // Ignore zero sender mac - if (senderAddr == 0) - return; - - short vlan = eth.getVlanID(); - int nwSrc = IPv4.toIPv4Address(arp.getSenderProtocolAddress()); - - Entity e = new Entity(senderAddr, - ((vlan >= 0) ? vlan : null), - ((nwSrc != 0) ? nwSrc : null), - swdpid, - port, - new Date()); - - learnDeviceByEntity(e); - } - - /** - * Get a (partial) entity for the destination from the packet. - * @param eth - * @return - */ - protected Entity getDestEntityFromPacket(Ethernet eth) { - byte[] dlAddrArr = eth.getDestinationMACAddress(); - long dlAddr = Ethernet.toLong(dlAddrArr); - short vlan = eth.getVlanID(); - int nwDst = 0; - - // Ignore broadcast/multicast destination - if ((dlAddrArr[0] & 0x1) != 0) - return null; - // Ignore zero dest mac - if (dlAddr == 0) - return null; - - if (eth.getPayload() instanceof IPv4) { - IPv4 ipv4 = (IPv4) eth.getPayload(); - nwDst = ipv4.getDestinationAddress(); - } - - return new Entity(dlAddr, - ((vlan >= 0) ? vlan : null), - ((nwDst != 0) ? nwDst : null), - null, - null, - null); - } - - /** - * Parse an entity from an OFMatchWithSwDpid. - * @param ofmWithSwDpid - * @return the entity from the packet - */ - private Entity getEntityFromFlowMod(OFMatchWithSwDpid ofmWithSwDpid, - boolean isSource) { - byte[] dlAddrArr = ofmWithSwDpid.getOfMatch().getDataLayerSource(); - int nwSrc = ofmWithSwDpid.getOfMatch().getNetworkSource(); - if (!isSource) { - dlAddrArr = ofmWithSwDpid.getOfMatch().getDataLayerDestination(); - nwSrc = ofmWithSwDpid.getOfMatch().getNetworkDestination(); - } - - long dlAddr = Ethernet.toLong(dlAddrArr); - - // Ignore broadcast/multicast source - if ((dlAddrArr[0] & 0x1) != 0) - return null; - - Long swDpid = null; - Short inPort = null; - - if (isSource) { - swDpid = ofmWithSwDpid.getSwitchDataPathId(); - inPort = ofmWithSwDpid.getOfMatch().getInputPort(); - } - - /**for the new flow cache design, the flow mods retrived are not always - * from the source, learn AP should be disabled --meiyang*/ - boolean learnap = false; - /** - * if (swDpid == null || +public class DeviceManagerImpl implements IDeviceService, IOFMessageListener, ITopologyListener, IFloodlightModule, IEntityClassListener, IFlowReconcileListener, IInfoProvider { + protected static Logger logger = LoggerFactory.getLogger(DeviceManagerImpl.class); + protected IFloodlightProviderService floodlightProvider; + protected ITopologyService topology; + protected IStorageSourceService storageSource; + protected IRestApiService restApi; + protected IThreadPoolService threadPool; + protected IFlowReconcileService flowReconcileMgr; + protected IFlowReconcileEngineService flowReconcileEngine; + protected IDebugCounterService debugCounters; + private ISyncService syncService; + private IStoreClient<String, DeviceSyncRepresentation> storeClient; + private DeviceSyncManager deviceSyncManager; + + /** + * Debug Counters + */ + public static final String MODULE_NAME = "devicemanager"; + public static final String PACKAGE = DeviceManagerImpl.class.getPackage().getName(); + public IDebugCounter cntIncoming; + public IDebugCounter cntReconcileRequest; + public IDebugCounter cntReconcileNoSource; + public IDebugCounter cntReconcileNoDest; + public IDebugCounter cntInvalidSource; + public IDebugCounter cntInvalidDest; + public IDebugCounter cntNoSource; + public IDebugCounter cntNoDest; + public IDebugCounter cntDhcpClientNameSnooped; + public IDebugCounter cntDeviceOnInternalPortNotLearned; + public IDebugCounter cntPacketNotAllowed; + public IDebugCounter cntNewDevice; + public IDebugCounter cntPacketOnInternalPortForKnownDevice; + public IDebugCounter cntNewEntity; + public IDebugCounter cntDeviceChanged; + public IDebugCounter cntDeviceMoved; + public IDebugCounter cntCleanupEntitiesRuns; + public IDebugCounter cntEntityRemovedTimeout; + public IDebugCounter cntDeviceDeleted; + public IDebugCounter cntDeviceReclassifyDelete; + public IDebugCounter cntDeviceStrored; + public IDebugCounter cntDeviceStoreThrottled; + public IDebugCounter cntDeviceRemovedFromStore; + public IDebugCounter cntSyncException; + public IDebugCounter cntDevicesFromStore; + public IDebugCounter cntConsolidateStoreRuns; + public IDebugCounter cntConsolidateStoreDevicesRemoved; + public IDebugCounter cntTransitionToMaster; + + /** + * Debug Events + */ + private IDebugEventService debugEventService; + private IEventCategory<DeviceEvent> debugEventCategory; + + private boolean isMaster = false; + + static final String DEVICE_SYNC_STORE_NAME = + DeviceManagerImpl.class.getCanonicalName() + ".stateStore"; + + /** + * Time interval between writes of entries for the same device to + * the sync store. + */ + static final int DEFAULT_SYNC_STORE_WRITE_INTERVAL_MS = 5*60*1000; // 5 min + private int syncStoreWriteIntervalMs = DEFAULT_SYNC_STORE_WRITE_INTERVAL_MS; + + /** + * Time after SLAVE->MASTER until we run the consolidate store + * code. + */ + static final int DEFAULT_INITIAL_SYNC_STORE_CONSOLIDATE_MS = + 15*1000; // 15 sec + private int initialSyncStoreConsolidateMs = + DEFAULT_INITIAL_SYNC_STORE_CONSOLIDATE_MS; + + /** + * Time interval between consolidate store runs. + */ + static final int DEFAULT_SYNC_STORE_CONSOLIDATE_INTERVAL_MS = + 75*60*1000; // 75 min + private final int syncStoreConsolidateIntervalMs = + DEFAULT_SYNC_STORE_CONSOLIDATE_INTERVAL_MS; + + /** + * Time in milliseconds before entities will expire + */ + protected static final int ENTITY_TIMEOUT = 60*60*1000; + + /** + * Time in seconds between cleaning up old entities/devices + */ + protected static final int ENTITY_CLEANUP_INTERVAL = 60*60; + + /** + * This is the master device map that maps device IDs to {@link Device} + * objects. + */ + protected ConcurrentHashMap<Long, Device> deviceMap; + + /** + * Counter used to generate device keys + */ + protected AtomicLong deviceKeyCounter = new AtomicLong(0); + + /** + * This is the primary entity index that contains all entities + */ + protected DeviceUniqueIndex primaryIndex; + + /** + * This stores secondary indices over the fields in the devices + */ + protected Map<EnumSet<DeviceField>, DeviceIndex> secondaryIndexMap; + + /** + * This map contains state for each of the {@ref IEntityClass} + * that exist + */ + protected ConcurrentHashMap<String, ClassState> classStateMap; + + /** + * This is the list of indices we want on a per-class basis + */ + protected Set<EnumSet<DeviceField>> perClassIndices; + + /** + * The entity classifier currently in use + */ + protected IEntityClassifierService entityClassifier; + + /** + * Used to cache state about specific entity classes + */ + protected class ClassState { + + /** + * The class index + */ + protected DeviceUniqueIndex classIndex; + + /** + * This stores secondary indices over the fields in the device for the + * class + */ + protected Map<EnumSet<DeviceField>, DeviceIndex> secondaryIndexMap; + + /** + * Allocate a new {@link ClassState} object for the class + * @param clazz the class to use for the state + */ + public ClassState(IEntityClass clazz) { + EnumSet<DeviceField> keyFields = clazz.getKeyFields(); + EnumSet<DeviceField> primaryKeyFields = + entityClassifier.getKeyFields(); + boolean keyFieldsMatchPrimary = + primaryKeyFields.equals(keyFields); + + if (!keyFieldsMatchPrimary) + classIndex = new DeviceUniqueIndex(keyFields); + + secondaryIndexMap = + new HashMap<EnumSet<DeviceField>, DeviceIndex>(); + for (EnumSet<DeviceField> fields : perClassIndices) { + secondaryIndexMap.put(fields, + new DeviceMultiIndex(fields)); + } + } + } + + /** + * Device manager event listeners + * reclassifyDeviceListeners are notified first before reconcileDeviceListeners. + * This is to make sure devices are correctly reclassified before reconciliation. + */ + protected ListenerDispatcher<String,IDeviceListener> deviceListeners; + + /** + * A device update event to be dispatched + */ + protected static class DeviceUpdate { + public enum Change { + ADD, DELETE, CHANGE; + } + + /** + * The affected device + */ + protected Device device; + + /** + * The change that was made + */ + protected Change change; + + /** + * If not added, then this is the list of fields changed + */ + protected EnumSet<DeviceField> fieldsChanged; + + public DeviceUpdate(Device device, Change change, + EnumSet<DeviceField> fieldsChanged) { + super(); + this.device = device; + this.change = change; + this.fieldsChanged = fieldsChanged; + } + + @Override + public String toString() { + String devIdStr = device.getEntityClass().getName() + "::" + + device.getMACAddressString(); + return "DeviceUpdate [device=" + devIdStr + ", change=" + change + + ", fieldsChanged=" + fieldsChanged + "]"; + } + + } + + /** + * AttachmentPointComparator + * + * Compares two attachment points and returns the latest one. + * It is assumed that the two attachment points are in the same + * L2 domain. + * + * @author srini + */ + protected class AttachmentPointComparator + implements Comparator<AttachmentPoint> { + public AttachmentPointComparator() { + super(); + } + + @Override + public int compare(AttachmentPoint oldAP, AttachmentPoint newAP) { + //First compare based on L2 domain ID; + + DatapathId oldSw = oldAP.getSw(); + OFPort oldPort = oldAP.getPort(); + DatapathId oldDomain = topology.getL2DomainId(oldSw); + boolean oldBD = topology.isBroadcastDomainPort(oldSw, oldPort); + + DatapathId newSw = newAP.getSw(); + OFPort newPort = newAP.getPort(); + DatapathId newDomain = topology.getL2DomainId(newSw); + boolean newBD = topology.isBroadcastDomainPort(newSw, newPort); + + if (oldDomain.getLong() < newDomain.getLong()) return -1; + else if (oldDomain.getLong() > newDomain.getLong()) return 1; + + + // Give preference to LOCAL always + if (oldPort != OFPort.LOCAL && + newPort == OFPort.LOCAL) { + return -1; + } else if (oldPort == OFPort.LOCAL && + newPort != OFPort.LOCAL) { + return 1; + } + + // We expect that the last seen of the new AP is higher than + // old AP, if it is not, just reverse and send the negative + // of the result. + if (oldAP.getActiveSince().after(newAP.getActiveSince())) + return -compare(newAP, oldAP); + + long activeOffset = 0; + if (!topology.isConsistent(oldSw, oldPort, newSw, newPort)) { + if (!newBD && oldBD) { + return -1; + } + if (newBD && oldBD) { + activeOffset = AttachmentPoint.EXTERNAL_TO_EXTERNAL_TIMEOUT; + } + else if (newBD && !oldBD){ + activeOffset = AttachmentPoint.OPENFLOW_TO_EXTERNAL_TIMEOUT; + } + + } else { + // The attachment point is consistent. + activeOffset = AttachmentPoint.CONSISTENT_TIMEOUT; + } + + + if ((newAP.getActiveSince().getTime() > oldAP.getLastSeen().getTime() + activeOffset) || + (newAP.getLastSeen().getTime() > oldAP.getLastSeen().getTime() + + AttachmentPoint.INACTIVITY_INTERVAL)) { + return -1; + } + return 1; + } + } + /** + * Comparator for sorting by cluster ID + */ + public AttachmentPointComparator apComparator; + + /** + * Switch ports where attachment points shouldn't be learned + */ + private Set<SwitchPort> suppressAPs; + + /** + * Periodic task to clean up expired entities + */ + public SingletonTask entityCleanupTask; + + + /** + * Periodic task to consolidate entries in the store. I.e., delete + * entries in the store that are not known to DeviceManager + */ + private SingletonTask storeConsolidateTask; + + /** + * Listens for HA notifications + */ + protected HAListenerDelegate haListenerDelegate; + + + // ********************* + // IDeviceManagerService + // ********************* + + @Override + public IDevice getDevice(Long deviceKey) { + return deviceMap.get(deviceKey); + } + + @Override + public IDevice findDevice(MacAddress macAddress, VlanVid vlan, + IPv4Address ipv4Address, DatapathId switchDPID, + OFPort switchPort) + throws IllegalArgumentException { + if (vlan != null && vlan.getVlan() <= 0) + vlan = null; + if (ipv4Address != null && ipv4Address.getInt() == 0) + ipv4Address = null; + Entity e = new Entity(macAddress, vlan, ipv4Address, switchDPID, + switchPort, null); + if (!allKeyFieldsPresent(e, entityClassifier.getKeyFields())) { + throw new IllegalArgumentException("Not all key fields specified." + + " Required fields: " + entityClassifier.getKeyFields()); + } + return findDeviceByEntity(e); + } + + @Override + public IDevice findClassDevice(IEntityClass entityClass, MacAddress macAddress, + VlanVid vlan, IPv4Address ipv4Address) + throws IllegalArgumentException { + if (vlan != null && vlan.getVlan() <= 0) + vlan = null; + if (ipv4Address != null && ipv4Address.getInt() == 0) + ipv4Address = null; + Entity e = new Entity(macAddress, vlan, ipv4Address, + null, null, null); + if (entityClass == null || + !allKeyFieldsPresent(e, entityClass.getKeyFields())) { + throw new IllegalArgumentException("Not all key fields and/or " + + " no source device specified. Required fields: " + + entityClassifier.getKeyFields()); + } + return findDestByEntity(entityClass, e); + } + + @Override + public Collection<? extends IDevice> getAllDevices() { + return Collections.unmodifiableCollection(deviceMap.values()); + } + + @Override + public void addIndex(boolean perClass, + EnumSet<DeviceField> keyFields) { + if (perClass) { + perClassIndices.add(keyFields); + } else { + secondaryIndexMap.put(keyFields, + new DeviceMultiIndex(keyFields)); + } + } + + @Override + public Iterator<? extends IDevice> queryDevices(MacAddress macAddress, + VlanVid vlan, + IPv4Address ipv4Address, + DatapathId switchDPID, + OFPort switchPort) { + DeviceIndex index = null; + if (secondaryIndexMap.size() > 0) { + EnumSet<DeviceField> keys = + getEntityKeys(macAddress, vlan, ipv4Address, + switchDPID, switchPort); + index = secondaryIndexMap.get(keys); + } + + Iterator<Device> deviceIterator = null; + if (index == null) { + // Do a full table scan + deviceIterator = deviceMap.values().iterator(); + } else { + // index lookup + Entity entity = new Entity(macAddress, + vlan, + ipv4Address, + switchDPID, + switchPort, + null); + deviceIterator = + new DeviceIndexInterator(this, index.queryByEntity(entity)); + } + + DeviceIterator di = + new DeviceIterator(deviceIterator, + null, + macAddress, + vlan, + ipv4Address, + switchDPID, + switchPort); + return di; + } + + @Override + public Iterator<? extends IDevice> queryClassDevices(IEntityClass entityClass, + MacAddress macAddress, + VlanVid vlan, + IPv4Address ipv4Address, + DatapathId switchDPID, + OFPort switchPort) { + ArrayList<Iterator<Device>> iterators = + new ArrayList<Iterator<Device>>(); + ClassState classState = getClassState(entityClass); + + DeviceIndex index = null; + if (classState.secondaryIndexMap.size() > 0) { + EnumSet<DeviceField> keys = + getEntityKeys(macAddress, vlan, ipv4Address, + switchDPID, switchPort); + index = classState.secondaryIndexMap.get(keys); + } + + Iterator<Device> iter; + if (index == null) { + index = classState.classIndex; + if (index == null) { + // scan all devices + return new DeviceIterator(deviceMap.values().iterator(), + new IEntityClass[] { entityClass }, + macAddress, vlan, ipv4Address, + switchDPID, switchPort); + } else { + // scan the entire class + iter = new DeviceIndexInterator(this, index.getAll()); + } + } else { + // index lookup + Entity entity = + new Entity(macAddress, + vlan, + ipv4Address, + switchDPID, + switchPort, + null); + iter = new DeviceIndexInterator(this, + index.queryByEntity(entity)); + } + iterators.add(iter); + + return new MultiIterator<Device>(iterators.iterator()); + } + + protected Iterator<Device> getDeviceIteratorForQuery(MacAddress macAddress, + VlanVid vlan, + IPv4Address ipv4Address, + DatapathId switchDPID, + OFPort switchPort) { + DeviceIndex index = null; + if (secondaryIndexMap.size() > 0) { + EnumSet<DeviceField> keys = + getEntityKeys(macAddress, vlan, ipv4Address, + switchDPID, switchPort); + index = secondaryIndexMap.get(keys); + } + + Iterator<Device> deviceIterator = null; + if (index == null) { + // Do a full table scan + deviceIterator = deviceMap.values().iterator(); + } else { + // index lookup + Entity entity = new Entity(macAddress, + vlan, + ipv4Address, + switchDPID, + switchPort, + null); + deviceIterator = + new DeviceIndexInterator(this, index.queryByEntity(entity)); + } + + DeviceIterator di = + new DeviceIterator(deviceIterator, + null, + macAddress, + vlan, + ipv4Address, + switchDPID, + switchPort); + return di; + } + + @Override + public void addListener(IDeviceListener listener) { + deviceListeners.addListener("device", listener); + logListeners(); + } + + @Override + public void addSuppressAPs(DatapathId swId, OFPort port) { + suppressAPs.add(new SwitchPort(swId, port)); + } + + @Override + public void removeSuppressAPs(DatapathId swId, OFPort port) { + suppressAPs.remove(new SwitchPort(swId, port)); + } + + @Override + public Set<SwitchPort> getSuppressAPs() { + return Collections.unmodifiableSet(suppressAPs); + } + + private void logListeners() { + List<IDeviceListener> listeners = deviceListeners.getOrderedListeners(); + if (listeners != null) { + StringBuffer sb = new StringBuffer(); + sb.append("DeviceListeners: "); + for (IDeviceListener l : listeners) { + sb.append(l.getName()); + sb.append(","); + } + logger.debug(sb.toString()); + } + } + + // *************** + // IDeviceListener + // *************** + private class DeviceDebugEventLogger implements IDeviceListener { + @Override + public String getName() { + return "deviceDebugEventLogger"; + } + + @Override + public boolean isCallbackOrderingPrereq(String type, String name) { + return false; + } + + @Override + public boolean isCallbackOrderingPostreq(String type, String name) { + return false; + } + + @Override + public void deviceAdded(IDevice device) { + generateDeviceEvent(device, "host-added"); + } + + @Override + public void deviceRemoved(IDevice device) { + generateDeviceEvent(device, "host-removed"); + } + + @Override + public void deviceMoved(IDevice device) { + generateDeviceEvent(device, "host-moved"); + } + + @Override + public void deviceIPV4AddrChanged(IDevice device) { + generateDeviceEvent(device, "host-ipv4-addr-changed"); + } + + @Override + public void deviceVlanChanged(IDevice device) { + generateDeviceEvent(device, "host-vlan-changed"); + } + + private void generateDeviceEvent(IDevice device, String reason) { + List<IPv4Address> ipv4Addresses = + new ArrayList<IPv4Address>(Arrays.asList(device.getIPv4Addresses())); + List<SwitchPort> oldAps = + new ArrayList<SwitchPort>(Arrays.asList(device.getOldAP())); + List<SwitchPort> currentAps = + new ArrayList<SwitchPort>(Arrays.asList(device.getAttachmentPoints())); + List<VlanVid> vlanIds = + new ArrayList<VlanVid>(Arrays.asList(device.getVlanId())); + + debugEventCategory.newEventNoFlush(new DeviceEvent(device.getMACAddress(), + ipv4Addresses, + oldAps, + currentAps, + vlanIds, reason)); + } + } + + // ************* + // IInfoProvider + // ************* + + @Override + public Map<String, Object> getInfo(String type) { + if (!"summary".equals(type)) + return null; + + Map<String, Object> info = new HashMap<String, Object>(); + info.put("# hosts", deviceMap.size()); + return info; + } + + // ****************** + // IOFMessageListener + // ****************** + + @Override + public String getName() { + return MODULE_NAME; + } + + @Override + public boolean isCallbackOrderingPrereq(OFType type, String name) { + return ((type == OFType.PACKET_IN || type == OFType.FLOW_MOD) + && name.equals("topology")); + } + + @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: + cntIncoming.increment(); + return this.processPacketInMessage(sw, (OFPacketIn) msg, cntx); + default: + break; + } + return Command.CONTINUE; + } + + // *************** + // IFlowReconcileListener + // *************** + @Override + public Command reconcileFlows(ArrayList<OFMatchReconcile> ofmRcList) { + ListIterator<OFMatchReconcile> iter = ofmRcList.listIterator(); + while (iter.hasNext()) { + OFMatchReconcile ofm = iter.next(); + + // Remove the STOPPed flow. + if (Command.STOP == reconcileFlow(ofm)) { + iter.remove(); + } + } + + if (ofmRcList.size() > 0) { + return Command.CONTINUE; + } else { + return Command.STOP; + } + } + + protected Command reconcileFlow(OFMatchReconcile ofm) { + cntReconcileRequest.increment(); + // Extract source entity information + Entity srcEntity = + getEntityFromFlowMod(ofm.ofmWithSwDpid, true); + if (srcEntity == null) { + cntReconcileNoSource.increment(); + return Command.STOP; + } + + // Find the device by source entity + Device srcDevice = findDeviceByEntity(srcEntity); + if (srcDevice == null) { + cntReconcileNoSource.increment(); + return Command.STOP; + } + // Store the source device in the context + fcStore.put(ofm.cntx, CONTEXT_SRC_DEVICE, srcDevice); + + // Find the device matching the destination from the entity + // classes of the source. + Entity dstEntity = getEntityFromFlowMod(ofm.ofmWithSwDpid, false); + Device dstDevice = null; + if (dstEntity != null) { + dstDevice = findDestByEntity(srcDevice.getEntityClass(), dstEntity); + if (dstDevice != null) + fcStore.put(ofm.cntx, CONTEXT_DST_DEVICE, dstDevice); + else + cntReconcileNoDest.increment(); + } else { + cntReconcileNoDest.increment(); + } + if (logger.isTraceEnabled()) { + logger.trace("Reconciling flow: match={}, srcEntity={}, srcDev={}, " + + "dstEntity={}, dstDev={}", + new Object[] {ofm.ofmWithSwDpid.getMatch(), + srcEntity, srcDevice, + dstEntity, dstDevice } ); + } + return Command.CONTINUE; + } + + // ***************** + // IFloodlightModule + // ***************** + + @Override + public Collection<Class<? extends IFloodlightService>> getModuleServices() { + Collection<Class<? extends IFloodlightService>> l = + new ArrayList<Class<? extends IFloodlightService>>(); + l.add(IDeviceService.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(IDeviceService.class, this); + return m; + } + + @Override + public Collection<Class<? extends IFloodlightService>> getModuleDependencies() { + Collection<Class<? extends IFloodlightService>> l = + new ArrayList<Class<? extends IFloodlightService>>(); + l.add(IFloodlightProviderService.class); + l.add(IStorageSourceService.class); + l.add(ITopologyService.class); + l.add(IRestApiService.class); + l.add(IThreadPoolService.class); + l.add(IFlowReconcileService.class); + l.add(IEntityClassifierService.class); + l.add(ISyncService.class); + return l; + } + + @Override + public void init(FloodlightModuleContext fmc) throws FloodlightModuleException { + this.perClassIndices = + new HashSet<EnumSet<DeviceField>>(); + addIndex(true, EnumSet.of(DeviceField.IPV4)); + + this.deviceListeners = new ListenerDispatcher<String, IDeviceListener>(); + this.suppressAPs = Collections.newSetFromMap( + new ConcurrentHashMap<SwitchPort, Boolean>()); + + this.floodlightProvider = + fmc.getServiceImpl(IFloodlightProviderService.class); + this.storageSource = + fmc.getServiceImpl(IStorageSourceService.class); + this.topology = + fmc.getServiceImpl(ITopologyService.class); + this.restApi = fmc.getServiceImpl(IRestApiService.class); + this.threadPool = fmc.getServiceImpl(IThreadPoolService.class); + this.flowReconcileMgr = fmc.getServiceImpl(IFlowReconcileService.class); + this.flowReconcileEngine = fmc.getServiceImpl(IFlowReconcileEngineService.class); + this.entityClassifier = fmc.getServiceImpl(IEntityClassifierService.class); + this.debugCounters = fmc.getServiceImpl(IDebugCounterService.class); + this.debugEventService = fmc.getServiceImpl(IDebugEventService.class); + this.syncService = fmc.getServiceImpl(ISyncService.class); + this.deviceSyncManager = new DeviceSyncManager(); + this.haListenerDelegate = new HAListenerDelegate(); + registerDeviceManagerDebugCounters(); + registerDeviceManagerDebugEvents(); + this.addListener(new DeviceDebugEventLogger()); + } + + private void registerDeviceManagerDebugEvents() throws FloodlightModuleException { + if (debugEventService == null) { + logger.error("debugEventService should not be null"); + } + EventCategoryBuilder<DeviceEvent> ecb = debugEventService.buildEvent(DeviceEvent.class); + debugEventCategory = ecb.setModuleName(PACKAGE) + .setEventName("hostevent") + .setEventDescription("Host added, removed, updated, or moved") + .setEventType(EventType.ALWAYS_LOG) + .setBufferCapacity(500) + .setAckable(false) + .register(); + } + + @Override + public void startUp(FloodlightModuleContext fmc) + throws FloodlightModuleException { + isMaster = (floodlightProvider.getRole() == HARole.ACTIVE); + primaryIndex = new DeviceUniqueIndex(entityClassifier.getKeyFields()); + secondaryIndexMap = new HashMap<EnumSet<DeviceField>, DeviceIndex>(); + + deviceMap = new ConcurrentHashMap<Long, Device>(); + classStateMap = + new ConcurrentHashMap<String, ClassState>(); + apComparator = new AttachmentPointComparator(); + + floodlightProvider.addOFMessageListener(OFType.PACKET_IN, this); + floodlightProvider.addHAListener(this.haListenerDelegate); + if (topology != null) + topology.addListener(this); + flowReconcileMgr.addFlowReconcileListener(this); + entityClassifier.addListener(this); + + ScheduledExecutorService ses = threadPool.getScheduledExecutor(); + Runnable ecr = new Runnable() { + @Override + public void run() { + cleanupEntities(); + entityCleanupTask.reschedule(ENTITY_CLEANUP_INTERVAL, + TimeUnit.SECONDS); + } + }; + entityCleanupTask = new SingletonTask(ses, ecr); + entityCleanupTask.reschedule(ENTITY_CLEANUP_INTERVAL, + TimeUnit.SECONDS); + + Runnable consolidateStoreRunner = new Runnable() { + @Override + public void run() { + deviceSyncManager.consolidateStore(); + storeConsolidateTask.reschedule(syncStoreConsolidateIntervalMs, + TimeUnit.MILLISECONDS); + } + }; + storeConsolidateTask = new SingletonTask(ses, consolidateStoreRunner); + if (isMaster) + storeConsolidateTask.reschedule(syncStoreConsolidateIntervalMs, + TimeUnit.MILLISECONDS); + + + if (restApi != null) { + restApi.addRestletRoutable(new DeviceRoutable()); + } else { + logger.debug("Could not instantiate REST API"); + } + + try { + this.syncService.registerStore(DEVICE_SYNC_STORE_NAME, Scope.LOCAL); + this.storeClient = this.syncService + .getStoreClient(DEVICE_SYNC_STORE_NAME, + String.class, + DeviceSyncRepresentation.class); + } catch (SyncException e) { + throw new FloodlightModuleException("Error while setting up sync service", e); + } + floodlightProvider.addInfoProvider("summary", this); + } + + private void registerDeviceManagerDebugCounters() throws FloodlightModuleException { + if (debugCounters == null) { + logger.error("Debug Counter Service not found."); + } + debugCounters.registerModule(PACKAGE); + cntIncoming = debugCounters.registerCounter(PACKAGE, "incoming", + "All incoming packets seen by this module"); + cntReconcileRequest = debugCounters.registerCounter(PACKAGE, + "reconcile-request", + "Number of flows that have been received for reconciliation by " + + "this module"); + cntReconcileNoSource = debugCounters.registerCounter(PACKAGE, + "reconcile-no-source-device", + "Number of flow reconcile events that failed because no source " + + "device could be identified", IDebugCounterService.MetaData.WARN); // is this really a IDebugCounterService.CTR_MDATA_WARNing + cntReconcileNoDest = debugCounters.registerCounter(PACKAGE, + "reconcile-no-dest-device", + "Number of flow reconcile events that failed because no " + + "destination device could be identified", IDebugCounterService.MetaData.WARN); // is this really a IDebugCounterService.CTR_MDATA_WARNing + cntInvalidSource = debugCounters.registerCounter(PACKAGE, + "invalid-source", + "Number of packetIns that were discarded because the source " + + "MAC was invalid (broadcast, multicast, or zero)", IDebugCounterService.MetaData.WARN); + cntNoSource = debugCounters.registerCounter(PACKAGE, "no-source-device", + "Number of packetIns that were discarded because the " + + "could not identify a source device. This can happen if a " + + "packet is not allowed, appears on an illegal port, does not " + + "have a valid address space, etc.", IDebugCounterService.MetaData.WARN); + cntInvalidDest = debugCounters.registerCounter(PACKAGE, + "invalid-dest", + "Number of packetIns that were discarded because the dest " + + "MAC was invalid (zero)", IDebugCounterService.MetaData.WARN); + cntNoDest = debugCounters.registerCounter(PACKAGE, "no-dest-device", + "Number of packetIns that did not have an associated " + + "destination device. E.g., because the destination MAC is " + + "broadcast/multicast or is not yet known to the controller."); + cntDhcpClientNameSnooped = debugCounters.registerCounter(PACKAGE, + "dhcp-client-name-snooped", + "Number of times a DHCP client name was snooped from a " + + "packetIn."); + cntDeviceOnInternalPortNotLearned = debugCounters.registerCounter( + PACKAGE, + "device-on-internal-port-not-learned", + "Number of times packetIn was received on an internal port and" + + "no source device is known for the source MAC. The packetIn is " + + "discarded.", IDebugCounterService.MetaData.WARN); + cntPacketNotAllowed = debugCounters.registerCounter(PACKAGE, + "packet-not-allowed", + "Number of times a packetIn was not allowed due to spoofing " + + "protection configuration.", IDebugCounterService.MetaData.WARN); // is this really a IDebugCounterService.CTR_MDATA_WARNing? + cntNewDevice = debugCounters.registerCounter(PACKAGE, "new-device", + "Number of times a new device was learned"); + cntPacketOnInternalPortForKnownDevice = debugCounters.registerCounter( + PACKAGE, + "packet-on-internal-port-for-known-device", + "Number of times a packetIn was received on an internal port " + + "for a known device."); + cntNewEntity = debugCounters.registerCounter(PACKAGE, "new-entity", + "Number of times a new entity was learned for an existing device"); + cntDeviceChanged = debugCounters.registerCounter(PACKAGE, "device-changed", + "Number of times device properties have changed"); + cntDeviceMoved = debugCounters.registerCounter(PACKAGE, "device-moved", + "Number of times devices have moved"); + cntCleanupEntitiesRuns = debugCounters.registerCounter(PACKAGE, + "cleanup-entities-runs", + "Number of times the entity cleanup task has been run"); + cntEntityRemovedTimeout = debugCounters.registerCounter(PACKAGE, + "entity-removed-timeout", + "Number of times entities have been removed due to timeout " + + "(entity has been inactive for " + ENTITY_TIMEOUT/1000 + "s)"); + cntDeviceDeleted = debugCounters.registerCounter(PACKAGE, "device-deleted", + "Number of devices that have been removed due to inactivity"); + cntDeviceReclassifyDelete = debugCounters.registerCounter(PACKAGE, + "device-reclassify-delete", + "Number of devices that required reclassification and have been " + + "temporarily delete for reclassification"); + cntDeviceStrored = debugCounters.registerCounter(PACKAGE, "device-stored", + "Number of device entries written or updated to the sync store"); + cntDeviceStoreThrottled = debugCounters.registerCounter(PACKAGE, + "device-store-throttled", + "Number of times a device update to the sync store was " + + "requested but not performed because the same device entities " + + "have recently been updated already"); + cntDeviceRemovedFromStore = debugCounters.registerCounter(PACKAGE, + "device-removed-from-store", + "Number of devices that were removed from the sync store " + + "because the local controller removed the device due to " + + "inactivity"); + cntSyncException = debugCounters.registerCounter(PACKAGE, "sync-exception", + "Number of times an operation on the sync store resulted in " + + "sync exception", IDebugCounterService.MetaData.WARN); // it this an error? + cntDevicesFromStore = debugCounters.registerCounter(PACKAGE, + "devices-from-store", + "Number of devices that were read from the sync store after " + + "the local controller transitioned from SLAVE to MASTER"); + cntConsolidateStoreRuns = debugCounters.registerCounter(PACKAGE, + "consolidate-store-runs", + "Number of times the task to consolidate entries in the " + + "store witch live known devices has been run"); + cntConsolidateStoreDevicesRemoved = debugCounters.registerCounter(PACKAGE, + "consolidate-store-devices-removed", + "Number of times a device has been removed from the sync " + + "store because no corresponding live device is known. " + + "This indicates a remote controller still writing device " + + "entries despite the local controller being MASTER or an " + + "incosistent store update from the local controller.", IDebugCounterService.MetaData.WARN); + cntTransitionToMaster = debugCounters.registerCounter(PACKAGE, + "transition-to-master", + "Number of times this controller has transitioned from SLAVE " + + "to MASTER role. Will be 0 or 1."); + } + + // *************** + // IHAListener + // *************** + + protected class HAListenerDelegate implements IHAListener { + @Override + public void transitionToActive() { + DeviceManagerImpl.this.isMaster = true; + DeviceManagerImpl.this.deviceSyncManager.goToMaster(); + } + + @Override + public void controllerNodeIPsChanged( + Map<String, String> curControllerNodeIPs, + Map<String, String> addedControllerNodeIPs, + Map<String, String> removedControllerNodeIPs) { + // no-op + } + + @Override + public String getName() { + return DeviceManagerImpl.this.getName(); + } + + @Override + public boolean isCallbackOrderingPrereq(HAListenerTypeMarker type, + String name) { + return ("topology".equals(name) || + "bvsmanager".equals(name)); + } + + @Override + public boolean isCallbackOrderingPostreq(HAListenerTypeMarker type, + String name) { + // TODO Auto-generated method stub + return false; + } + + @Override + public void transitionToStandby() { + DeviceManagerImpl.this.isMaster = false; + //TODO @Ryan is there a goToStandby()? + //DeviceManagerImpl.this.deviceSyncManager.goToSlave(); + } + } + + + // **************** + // Internal methods + // **************** + + protected Command processPacketInMessage(IOFSwitch sw, OFPacketIn pi, FloodlightContext cntx) { + Ethernet eth = IFloodlightProviderService.bcStore.get(cntx,IFloodlightProviderService.CONTEXT_PI_PAYLOAD); + + // Extract source entity information + Entity srcEntity = getSourceEntityFromPacket(eth, sw.getId(), (pi.getVersion().compareTo(OFVersion.OF_13) < 0 ? pi.getInPort() : pi.getMatch().get(MatchField.IN_PORT))); + if (srcEntity == null) { + cntInvalidSource.increment(); + return Command.STOP; + } + + // Learn from ARP packet for special VRRP settings. + // In VRRP settings, the source MAC address and sender MAC + // addresses can be different. In such cases, we need to learn + // the IP to MAC mapping of the VRRP IP address. The source + // entity will not have that information. Hence, a separate call + // to learn devices in such cases. + learnDeviceFromArpResponseData(eth, sw.getId(), (pi.getVersion().compareTo(OFVersion.OF_13) < 0 ? pi.getInPort() : pi.getMatch().get(MatchField.IN_PORT))); + + // Learn/lookup device information + Device srcDevice = learnDeviceByEntity(srcEntity); + if (srcDevice == null) { + cntNoSource.increment(); + return Command.STOP; + } + + // Store the source device in the context + fcStore.put(cntx, CONTEXT_SRC_DEVICE, srcDevice); + + // Find the device matching the destination from the entity + // classes of the source. + if (eth.getDestinationMACAddress().getLong() == 0) { + cntInvalidDest.increment(); + return Command.STOP; + } + Entity dstEntity = getDestEntityFromPacket(eth); + Device dstDevice = null; + if (dstEntity != null) { + dstDevice = findDestByEntity(srcDevice.getEntityClass(), dstEntity); + if (dstDevice != null) + fcStore.put(cntx, CONTEXT_DST_DEVICE, dstDevice); + else + cntNoDest.increment(); + } else { + cntNoDest.increment(); + } + + if (logger.isTraceEnabled()) { + logger.trace("Received PI: {} on switch {}, port {} *** eth={}" + + " *** srcDev={} *** dstDev={} *** ", + new Object[] { pi, sw.getId().toString(), (pi.getVersion().compareTo(OFVersion.OF_13) < 0 ? pi.getInPort() : pi.getMatch().get(MatchField.IN_PORT)), eth, + srcDevice, dstDevice }); + } + + snoopDHCPClientName(eth, srcDevice); + + return Command.CONTINUE; + } + + /** + * Snoop and record client-provided host name from DHCP requests + * @param eth + * @param srcDevice + */ + private void snoopDHCPClientName(Ethernet eth, Device srcDevice) { + if (! (eth.getPayload() instanceof IPv4) ) + return; + IPv4 ipv4 = (IPv4) eth.getPayload(); + if (! (ipv4.getPayload() instanceof UDP) ) + return; + UDP udp = (UDP) ipv4.getPayload(); + if (!(udp.getPayload() instanceof DHCP)) + return; + DHCP dhcp = (DHCP) udp.getPayload(); + byte opcode = dhcp.getOpCode(); + if (opcode == DHCP.OPCODE_REQUEST) { + DHCPOption dhcpOption = dhcp.getOption( + DHCPOptionCode.OptionCode_Hostname); + if (dhcpOption != null) { + cntDhcpClientNameSnooped.increment(); + srcDevice.dhcpClientName = new String(dhcpOption.getData()); + } + } + } + + /** + * Check whether the given attachment point is valid given the current + * topology + * @param switchDPID the DPID + * @param switchPort the port + * @return true if it's a valid attachment point + */ + public boolean isValidAttachmentPoint(DatapathId switchDPID, + OFPort switchPort) { + if (topology.isAttachmentPointPort(switchDPID, switchPort) == false) + return false; + + if (suppressAPs.contains(new SwitchPort(switchDPID, switchPort))) + return false; + + return true; + } + + /** + * Get sender IP address from packet if the packet is an ARP + * packet and if the source MAC address matches the ARP packets + * sender MAC address. + * @param eth + * @param dlAddr + * @return + */ + private IPv4Address getSrcNwAddr(Ethernet eth, MacAddress dlAddr) { + if (eth.getPayload() instanceof ARP) { + ARP arp = (ARP) eth.getPayload(); + if ((arp.getProtocolType() == ARP.PROTO_TYPE_IP) && (MacAddress.of(arp.getSenderHardwareAddress()).equals(dlAddr))) { + return IPv4Address.of(arp.getSenderProtocolAddress()); + } + } + return IPv4Address.NONE; + } + + /** + * Parse an entity from an {@link Ethernet} packet. + * @param eth the packet to parse + * @param sw the switch on which the packet arrived + * @param pi the original packetin + * @return the entity from the packet + */ + protected Entity getSourceEntityFromPacket(Ethernet eth, DatapathId swdpid, OFPort port) { + MacAddress dlAddr = eth.getSourceMACAddress(); + + // Ignore broadcast/multicast source + if (dlAddr.isBroadcast() || dlAddr.isMulticast()) + return null; + // Ignore 0 source mac + if (dlAddr.getLong() == 0) + return null; + + VlanVid vlan = VlanVid.ofVlan(eth.getVlanID()); + IPv4Address nwSrc = getSrcNwAddr(eth, dlAddr); + return new Entity(dlAddr, + vlan, + nwSrc, + swdpid, + port, + new Date()); + } + + /** + * Learn device from ARP data in scenarios where the + * Ethernet source MAC is different from the sender hardware + * address in ARP data. + */ + protected void learnDeviceFromArpResponseData(Ethernet eth, + DatapathId swdpid, + OFPort port) { + + if (!(eth.getPayload() instanceof ARP)) return; + ARP arp = (ARP) eth.getPayload(); + + MacAddress dlAddr = eth.getSourceMACAddress(); + + MacAddress senderAddr = MacAddress.of(arp.getSenderHardwareAddress()); + + if (dlAddr.equals(senderAddr)) return; // arp request + + // Ignore broadcast/multicast source + if (senderAddr.isBroadcast() || senderAddr.isMulticast()) + return; + // Ignore zero sender mac + if (senderAddr.getLong() == 0) + return; + + VlanVid vlan = VlanVid.ofVlan(eth.getVlanID()); + IPv4Address nwSrc = IPv4Address.of(arp.getSenderProtocolAddress()); + + Entity e = new Entity(senderAddr, + ((vlan.getVlan() >= 0) ? vlan : null), + ((nwSrc.getInt() != 0) ? nwSrc : null), + swdpid, + port, + new Date()); + + learnDeviceByEntity(e); + } + + /** + * Get a (partial) entity for the destination from the packet. + * @param eth + * @return + */ + protected Entity getDestEntityFromPacket(Ethernet eth) { + MacAddress dlAddr = eth.getDestinationMACAddress(); + VlanVid vlan = VlanVid.ofVlan(eth.getVlanID()); + IPv4Address nwDst = IPv4Address.NONE; + + // Ignore broadcast/multicast destination + if (dlAddr.isBroadcast() || dlAddr.isMulticast()) + return null; + // Ignore zero dest mac + if (dlAddr.getLong() == 0) + return null; + + if (eth.getPayload() instanceof IPv4) { + IPv4 ipv4 = (IPv4) eth.getPayload(); + nwDst = ipv4.getDestinationAddress(); + } + + return new Entity(dlAddr, + vlan, + nwDst, + null, + null, + null); + } + + /** + * Parse an entity from an OFMatchWithSwDpid. + * @param ofmWithSwDpid + * @return the entity from the packet + */ + private Entity getEntityFromFlowMod(OFMatchWithSwDpid ofmWithSwDpid, + boolean isSource) { + MacAddress dlAddr = ofmWithSwDpid.getMatch().get(MatchField.ETH_SRC); + IPv4Address nwSrc = ofmWithSwDpid.getMatch().get(MatchField.IPV4_SRC); + if (!isSource) { + dlAddr = ofmWithSwDpid.getMatch().get(MatchField.ETH_DST); + nwSrc = ofmWithSwDpid.getMatch().get(MatchField.IPV4_DST); + } + + // Ignore broadcast/multicast source + if (dlAddr.isBroadcast() || dlAddr.isMulticast()) + return null; + + DatapathId swDpid = DatapathId.NONE; + OFPort inPort = OFPort.ZERO; + + if (isSource) { + swDpid = ofmWithSwDpid.getDpid(); + inPort = ofmWithSwDpid.getMatch().get(MatchField.IN_PORT); + } + + /**for the new flow cache design, the flow mods retrived are not always + * from the source, learn AP should be disabled --meiyang*/ + boolean learnap = false; + /** + * if (swDpid == null || inPort == null || !isValidAttachmentPoint(swDpid, inPort)) { // If this is an internal port or we otherwise don't want @@ -1450,1177 +1405,1164 @@ IFlowReconcileListener, IInfoProvider { // as a key field. learnap = false; } - */ - - short vlan = ofmWithSwDpid.getOfMatch().getDataLayerVirtualLan(); - return new Entity(dlAddr, - ((vlan >= 0) ? vlan : null), - ((nwSrc != 0) ? nwSrc : null), - (learnap ? swDpid : null), - (learnap ? (int)inPort : null), - new Date()); - } - - /** - * Look up a {@link Device} based on the provided {@link Entity}. We first - * check the primary index. If we do not find an entry there we classify - * the device into its IEntityClass and query the classIndex. - * This implies that all key field of the current IEntityClassifier must - * be present in the entity for the lookup to succeed! - * @param entity the entity to search for - * @return The {@link Device} object if found - */ - protected Device findDeviceByEntity(Entity entity) { - // Look up the fully-qualified entity to see if it already - // exists in the primary entity index. - Long deviceKey = primaryIndex.findByEntity(entity); - IEntityClass entityClass = null; - - if (deviceKey == null) { - // If the entity does not exist in the primary entity index, - // use the entity classifier for find the classes for the - // entity. Look up the entity in the returned class' - // class entity index. - entityClass = entityClassifier.classifyEntity(entity); - if (entityClass == null) { - return null; - } - ClassState classState = getClassState(entityClass); - - if (classState.classIndex != null) { - deviceKey = - classState.classIndex.findByEntity(entity); - } - } - if (deviceKey == null) return null; - return deviceMap.get(deviceKey); - } - - /** - * Get a destination device using entity fields that corresponds with - * the given source device. The source device is important since - * there could be ambiguity in the destination device without the - * attachment point information. - * @param reference the source device's entity class. - * The returned destination will be - * in the same entity class as the source. - * @param dstEntity the entity to look up - * @return an {@link Device} or null if no device is found. - */ - protected Device findDestByEntity(IEntityClass reference, - Entity dstEntity) { - - // Look up the fully-qualified entity to see if it - // exists in the primary entity index - Long deviceKey = primaryIndex.findByEntity(dstEntity); - - if (deviceKey == null) { - // This could happen because: - // 1) no destination known, or a broadcast destination - // 2) if we have attachment point key fields since - // attachment point information isn't available for - // destination devices. - // For the second case, we'll need to match up the - // destination device with the class of the source - // device. - ClassState classState = getClassState(reference); - if (classState.classIndex == null) { - return null; - } - deviceKey = classState.classIndex.findByEntity(dstEntity); - } - if (deviceKey == null) return null; - return deviceMap.get(deviceKey); - } - - /** - * Look up a {@link Device} within a particular entity class based on - * the provided {@link Entity}. - * @param clazz the entity class to search for the entity - * @param entity the entity to search for - * @return The {@link Device} object if found + */ + + OFVlanVidMatch vlan = ofmWithSwDpid.getMatch().get(MatchField.VLAN_VID); + return new Entity(dlAddr, + ((vlan.getVlan() >= 0) ? vlan.getVlanVid() : null), + ((nwSrc.getInt() != 0) ? nwSrc : null), + (learnap ? swDpid : null), + (learnap ? inPort : null), + new Date()); + } + + /** + * Look up a {@link Device} based on the provided {@link Entity}. We first + * check the primary index. If we do not find an entry there we classify + * the device into its IEntityClass and query the classIndex. + * This implies that all key field of the current IEntityClassifier must + * be present in the entity for the lookup to succeed! + * @param entity the entity to search for + * @return The {@link Device} object if found + */ + protected Device findDeviceByEntity(Entity entity) { + // Look up the fully-qualified entity to see if it already + // exists in the primary entity index. + Long deviceKey = primaryIndex.findByEntity(entity); + IEntityClass entityClass = null; + + if (deviceKey == null) { + // If the entity does not exist in the primary entity index, + // use the entity classifier for find the classes for the + // entity. Look up the entity in the returned class' + // class entity index. + entityClass = entityClassifier.classifyEntity(entity); + if (entityClass == null) { + return null; + } + ClassState classState = getClassState(entityClass); + + if (classState.classIndex != null) { + deviceKey = classState.classIndex.findByEntity(entity); + } + } + if (deviceKey == null) return null; + return deviceMap.get(deviceKey); + } + + /** + * Get a destination device using entity fields that corresponds with + * the given source device. The source device is important since + * there could be ambiguity in the destination device without the + * attachment point information. + * @param reference the source device's entity class. + * The returned destination will be + * in the same entity class as the source. + * @param dstEntity the entity to look up + * @return an {@link Device} or null if no device is found. + */ + protected Device findDestByEntity(IEntityClass reference, + Entity dstEntity) { + + // Look up the fully-qualified entity to see if it + // exists in the primary entity index + Long deviceKey = primaryIndex.findByEntity(dstEntity); + + if (deviceKey == null) { + // This could happen because: + // 1) no destination known, or a broadcast destination + // 2) if we have attachment point key fields since + // attachment point information isn't available for + // destination devices. + // For the second case, we'll need to match up the + // destination device with the class of the source + // device. + ClassState classState = getClassState(reference); + if (classState.classIndex == null) { + return null; + } + deviceKey = classState.classIndex.findByEntity(dstEntity); + } + if (deviceKey == null) return null; + return deviceMap.get(deviceKey); + } + + /** + * Look up a {@link Device} within a particular entity class based on + * the provided {@link Entity}. + * @param clazz the entity class to search for the entity + * @param entity the entity to search for + * @return The {@link Device} object if found private Device findDeviceInClassByEntity(IEntityClass clazz, Entity entity) { // XXX - TODO throw new UnsupportedOperationException(); } - */ - - /** - * Look up a {@link Device} based on the provided {@link Entity}. Also - * learns based on the new entity, and will update existing devices as - * required. - * - * @param entity the {@link Entity} - * @return The {@link Device} object if found - */ - protected Device learnDeviceByEntity(Entity entity) { - ArrayList<Long> deleteQueue = null; - LinkedList<DeviceUpdate> deviceUpdates = null; - Device device = null; - - // we may need to restart the learning process if we detect - // concurrent modification. Note that we ensure that at least - // one thread should always succeed so we don't get into infinite - // starvation loops - while (true) { - deviceUpdates = null; - - // Look up the fully-qualified entity to see if it already - // exists in the primary entity index. - Long deviceKey = primaryIndex.findByEntity(entity); - IEntityClass entityClass = null; - - if (deviceKey == null) { - // If the entity does not exist in the primary entity index, - // use the entity classifier for find the classes for the - // entity. Look up the entity in the returned class' - // class entity index. - entityClass = entityClassifier.classifyEntity(entity); - if (entityClass == null) { - // could not classify entity. No device - device = null; - break; - } - ClassState classState = getClassState(entityClass); - - if (classState.classIndex != null) { - deviceKey = - classState.classIndex.findByEntity(entity); - } - } - if (deviceKey != null) { - // If the primary or secondary index contains the entity - // use resulting device key to look up the device in the - // device map, and use the referenced Device below. - device = deviceMap.get(deviceKey); - if (device == null) { - // This can happen due to concurrent modification - if (logger.isDebugEnabled()) { - logger.debug("No device for deviceKey {} while " - + "while processing entity {}", - deviceKey, entity); - } - // if so, then try again till we don't even get the device key - // and so we recreate the device - continue; - } - } else { - // If the secondary index does not contain the entity, - // create a new Device object containing the entity, and - // generate a new device ID if the the entity is on an - // attachment point port. Otherwise ignore. - if (entity.hasSwitchPort() && - !topology.isAttachmentPointPort(entity.getSwitchDPID(), - entity.getSwitchPort().shortValue())) { - cntDeviceOnInternalPortNotLearned.updateCounterNoFlush(); - if (logger.isDebugEnabled()) { - logger.debug("Not learning new device on internal" - + " link: {}", entity); - } - device = null; - break; - } - // Before we create the new device also check if - // the entity is allowed (e.g., for spoofing protection) - if (!isEntityAllowed(entity, entityClass)) { - cntPacketNotAllowed.updateCounterNoFlush(); - if (logger.isDebugEnabled()) { - logger.debug("PacketIn is not allowed {} {}", - entityClass.getName(), entity); - } - device = null; - break; - } - deviceKey = deviceKeyCounter.getAndIncrement(); - device = allocateDevice(deviceKey, entity, entityClass); - - - // Add the new device to the primary map with a simple put - deviceMap.put(deviceKey, device); - - // update indices - if (!updateIndices(device, deviceKey)) { - if (deleteQueue == null) - deleteQueue = new ArrayList<Long>(); - deleteQueue.add(deviceKey); - continue; - } - - updateSecondaryIndices(entity, entityClass, deviceKey); - - // We need to count and log here. If we log earlier we could - // hit a concurrent modification and restart the dev creation - // and potentially count the device twice. - cntNewDevice.updateCounterNoFlush(); - if (logger.isDebugEnabled()) { - logger.debug("New device created: {} deviceKey={}, entity={}", - new Object[]{device, deviceKey, entity}); - } - // generate new device update - deviceUpdates = - updateUpdates(deviceUpdates, - new DeviceUpdate(device, ADD, null)); - - break; - } - // if it gets here, we have a pre-existing Device for this Entity - if (!isEntityAllowed(entity, device.getEntityClass())) { - cntPacketNotAllowed.updateCounterNoFlush(); - if (logger.isDebugEnabled()) { - logger.info("PacketIn is not allowed {} {}", - device.getEntityClass().getName(), entity); - } - return null; - } - // If this is not an attachment point port we don't learn the new entity - // and don't update indexes. But we do allow the device to continue up - // the chain. - if (entity.hasSwitchPort() && - !topology.isAttachmentPointPort(entity.getSwitchDPID(), - entity.getSwitchPort().shortValue())) { - cntPacketOnInternalPortForKnownDevice.updateCounterNoFlush(); - break; - } - int entityindex = -1; - if ((entityindex = device.entityIndex(entity)) >= 0) { - // Entity already exists - // update timestamp on the found entity - Date lastSeen = entity.getLastSeenTimestamp(); - if (lastSeen == null) { - lastSeen = new Date(); - entity.setLastSeenTimestamp(lastSeen); - } - device.entities[entityindex].setLastSeenTimestamp(lastSeen); - // we break the loop after checking for changes to the AP - } else { - // New entity for this device - // compute the insertion point for the entity. - // see Arrays.binarySearch() - entityindex = -(entityindex + 1); - Device newDevice = allocateDevice(device, entity, entityindex); - - // generate updates - EnumSet<DeviceField> changedFields = - findChangedFields(device, entity); - - // update the device map with a replace call - boolean res = deviceMap.replace(deviceKey, device, newDevice); - // If replace returns false, restart the process from the - // beginning (this implies another thread concurrently - // modified this Device). - if (!res) - continue; - - device = newDevice; - // update indices - if (!updateIndices(device, deviceKey)) { - continue; - } - updateSecondaryIndices(entity, - device.getEntityClass(), - deviceKey); - - // We need to count here after all the possible "continue" - // statements in this branch - cntNewEntity.updateCounterNoFlush(); - if (changedFields.size() > 0) { - cntDeviceChanged.updateCounterNoFlush(); - deviceUpdates = - updateUpdates(deviceUpdates, - new DeviceUpdate(newDevice, CHANGE, - changedFields)); - } - // we break the loop after checking for changed AP - } - // Update attachment point (will only be hit if the device - // already existed and no concurrent modification) - if (entity.hasSwitchPort()) { - boolean moved = - device.updateAttachmentPoint(entity.getSwitchDPID(), - entity.getSwitchPort().shortValue(), - entity.getLastSeenTimestamp().getTime()); - // TODO: use update mechanism instead of sending the - // notification directly - if (moved) { - // we count device moved events in sendDeviceMovedNotification() - sendDeviceMovedNotification(device); - if (logger.isTraceEnabled()) { - logger.trace("Device moved: attachment points {}," + - "entities {}", device.attachmentPoints, - device.entities); - } - } else { - if (logger.isTraceEnabled()) { - logger.trace("Device attachment point updated: " + - "attachment points {}," + - "entities {}", device.attachmentPoints, - device.entities); - } - } - } - break; - } - - if (deleteQueue != null) { - for (Long l : deleteQueue) { - Device dev = deviceMap.get(l); - this.deleteDevice(dev); - } - } - - processUpdates(deviceUpdates); - deviceSyncManager.storeDeviceThrottled(device); - - return device; - } - - protected boolean isEntityAllowed(Entity entity, IEntityClass entityClass) { - return true; - } - - - - - - protected EnumSet<DeviceField> findChangedFields(Device device, - Entity newEntity) { - EnumSet<DeviceField> changedFields = - EnumSet.of(DeviceField.IPV4, - DeviceField.VLAN, - DeviceField.SWITCH); - - if (newEntity.getIpv4Address() == null) - changedFields.remove(DeviceField.IPV4); - if (newEntity.getVlan() == null) - changedFields.remove(DeviceField.VLAN); - if (newEntity.getSwitchDPID() == null || - newEntity.getSwitchPort() == null) - changedFields.remove(DeviceField.SWITCH); - - if (changedFields.size() == 0) return changedFields; - - for (Entity entity : device.getEntities()) { - if (newEntity.getIpv4Address() == null || - (entity.getIpv4Address() != null && - entity.getIpv4Address().equals(newEntity.getIpv4Address()))) - changedFields.remove(DeviceField.IPV4); - if (newEntity.getVlan() == null || - (entity.getVlan() != null && - entity.getVlan().equals(newEntity.getVlan()))) - changedFields.remove(DeviceField.VLAN); - if (newEntity.getSwitchDPID() == null || - newEntity.getSwitchPort() == null || - (entity.getSwitchDPID() != null && - entity.getSwitchPort() != null && - entity.getSwitchDPID().equals(newEntity.getSwitchDPID()) && - entity.getSwitchPort().equals(newEntity.getSwitchPort()))) - changedFields.remove(DeviceField.SWITCH); - } - - return changedFields; - } - - /** - * Send update notifications to listeners - * @param updates the updates to process. - */ - protected void processUpdates(Queue<DeviceUpdate> updates) { - if (updates == null) return; - DeviceUpdate update = null; - while (null != (update = updates.poll())) { - if (logger.isTraceEnabled()) { - logger.trace("Dispatching device update: {}", update); - } - if (update.change == DeviceUpdate.Change.DELETE) - deviceSyncManager.removeDevice(update.device); - else - deviceSyncManager.storeDevice(update.device); - List<IDeviceListener> listeners = deviceListeners.getOrderedListeners(); - notifyListeners(listeners, update); - } - } - - protected void notifyListeners(List<IDeviceListener> listeners, DeviceUpdate update) { - if (listeners == null) { - return; - } - for (IDeviceListener listener : listeners) { - switch (update.change) { - case ADD: - listener.deviceAdded(update.device); - break; - case DELETE: - listener.deviceRemoved(update.device); - break; - case CHANGE: - for (DeviceField field : update.fieldsChanged) { - switch (field) { - case IPV4: - listener.deviceIPV4AddrChanged(update.device); - break; - case SWITCH: - case PORT: - //listener.deviceMoved(update.device); - break; - case VLAN: - listener.deviceVlanChanged(update.device); - break; - default: - logger.debug("Unknown device field changed {}", - update.fieldsChanged.toString()); - break; - } - } - break; - } - } - } - - /** - * Check if the entity e has all the keyFields set. Returns false if not - * @param e entity to check - * @param keyFields the key fields to check e against - * @return - */ - protected boolean allKeyFieldsPresent(Entity e, EnumSet<DeviceField> keyFields) { - for (DeviceField f : keyFields) { - switch (f) { - case MAC: - // MAC address is always present - break; - case IPV4: - if (e.ipv4Address == null) return false; - break; - case SWITCH: - if (e.switchDPID == null) return false; - break; - case PORT: - if (e.switchPort == null) return false; - break; - case VLAN: - // FIXME: vlan==null is ambiguous: it can mean: not present - // or untagged - //if (e.vlan == null) return false; - break; - default: - // we should never get here. unless somebody extended - // DeviceFields - throw new IllegalStateException(); - } - } - return true; - } - - private LinkedList<DeviceUpdate> - updateUpdates(LinkedList<DeviceUpdate> list, DeviceUpdate update) { - if (update == null) return list; - if (list == null) - list = new LinkedList<DeviceUpdate>(); - list.add(update); - - return list; - } - - /** - * Get the secondary index for a class. Will return null if the - * secondary index was created concurrently in another thread. - * @param clazz the class for the index - * @return - */ - private ClassState getClassState(IEntityClass clazz) { - ClassState classState = classStateMap.get(clazz.getName()); - if (classState != null) return classState; - - classState = new ClassState(clazz); - ClassState r = classStateMap.putIfAbsent(clazz.getName(), classState); - if (r != null) { - // concurrent add - return r; - } - return classState; - } - - /** - * Update both the primary and class indices for the provided device. - * If the update fails because of an concurrent update, will return false. - * @param device the device to update - * @param deviceKey the device key for the device - * @return true if the update succeeded, false otherwise. - */ - private boolean updateIndices(Device device, Long deviceKey) { - if (!primaryIndex.updateIndex(device, deviceKey)) { - return false; - } - IEntityClass entityClass = device.getEntityClass(); - ClassState classState = getClassState(entityClass); - - if (classState.classIndex != null) { - if (!classState.classIndex.updateIndex(device, - deviceKey)) - return false; - } - return true; - } - - /** - * Update the secondary indices for the given entity and associated - * entity classes - * @param entity the entity to update - * @param entityClass the entity class for the entity - * @param deviceKey the device key to set up - */ - private void updateSecondaryIndices(Entity entity, - IEntityClass entityClass, - Long deviceKey) { - for (DeviceIndex index : secondaryIndexMap.values()) { - index.updateIndex(entity, deviceKey); - } - ClassState state = getClassState(entityClass); - for (DeviceIndex index : state.secondaryIndexMap.values()) { - index.updateIndex(entity, deviceKey); - } - } - - /** - * Clean up expired entities/devices - */ - protected void cleanupEntities () { - cntCleanupEntitiesRuns.updateCounterWithFlush(); - - Calendar c = Calendar.getInstance(); - c.add(Calendar.MILLISECOND, -ENTITY_TIMEOUT); - Date cutoff = c.getTime(); - - ArrayList<Entity> toRemove = new ArrayList<Entity>(); - ArrayList<Entity> toKeep = new ArrayList<Entity>(); - - Iterator<Device> diter = deviceMap.values().iterator(); - LinkedList<DeviceUpdate> deviceUpdates = - new LinkedList<DeviceUpdate>(); - - while (diter.hasNext()) { - Device d = diter.next(); - - while (true) { - deviceUpdates.clear(); - toRemove.clear(); - toKeep.clear(); - for (Entity e : d.getEntities()) { - if (e.getLastSeenTimestamp() != null && - 0 > e.getLastSeenTimestamp().compareTo(cutoff)) { - // individual entity needs to be removed - toRemove.add(e); - } else { - toKeep.add(e); - } - } - if (toRemove.size() == 0) { - break; - } - - cntEntityRemovedTimeout.updateCounterWithFlush(); - for (Entity e : toRemove) { - removeEntity(e, d.getEntityClass(), d.getDeviceKey(), toKeep); - } - - if (toKeep.size() > 0) { - Device newDevice = allocateDevice(d.getDeviceKey(), - d.getDHCPClientName(), - d.oldAPs, - d.attachmentPoints, - toKeep, - d.getEntityClass()); - - EnumSet<DeviceField> changedFields = - EnumSet.noneOf(DeviceField.class); - for (Entity e : toRemove) { - changedFields.addAll(findChangedFields(newDevice, e)); - } - DeviceUpdate update = null; - if (changedFields.size() > 0) { - update = new DeviceUpdate(d, CHANGE, changedFields); - } - - if (!deviceMap.replace(newDevice.getDeviceKey(), - d, - newDevice)) { - // concurrent modification; try again - // need to use device that is the map now for the next - // iteration - d = deviceMap.get(d.getDeviceKey()); - if (null != d) - continue; - } - if (update != null) { - // need to count after all possibly continue stmts in - // this branch - cntDeviceChanged.updateCounterWithFlush(); - deviceUpdates.add(update); - } - } else { - DeviceUpdate update = new DeviceUpdate(d, DELETE, null); - if (!deviceMap.remove(d.getDeviceKey(), d)) { - // concurrent modification; try again - // need to use device that is the map now for the next - // iteration - d = deviceMap.get(d.getDeviceKey()); - if (null != d) - continue; - cntDeviceDeleted.updateCounterWithFlush(); - } - deviceUpdates.add(update); - } - processUpdates(deviceUpdates); - break; - } - } - // Since cleanupEntities() is not called in the packet-in pipeline, - // debugEvents need to be flushed explicitly - debugEvents.flushEvents(); - } - - protected void removeEntity(Entity removed, - IEntityClass entityClass, - Long deviceKey, - Collection<Entity> others) { - // Don't count in this method. This method CAN BE called to clean-up - // after concurrent device adds/updates and thus counting here - // is misleading - for (DeviceIndex index : secondaryIndexMap.values()) { - index.removeEntityIfNeeded(removed, deviceKey, others); - } - ClassState classState = getClassState(entityClass); - for (DeviceIndex index : classState.secondaryIndexMap.values()) { - index.removeEntityIfNeeded(removed, deviceKey, others); - } - - primaryIndex.removeEntityIfNeeded(removed, deviceKey, others); - - if (classState.classIndex != null) { - classState.classIndex.removeEntityIfNeeded(removed, - deviceKey, - others); - } - } - - /** - * method to delete a given device, remove all entities first and then - * finally delete the device itself. - * @param device - */ - protected void deleteDevice(Device device) { - // Don't count in this method. This method CAN BE called to clean-up - // after concurrent device adds/updates and thus counting here - // is misleading - ArrayList<Entity> emptyToKeep = new ArrayList<Entity>(); - for (Entity entity : device.getEntities()) { - this.removeEntity(entity, device.getEntityClass(), - device.getDeviceKey(), emptyToKeep); - } - if (!deviceMap.remove(device.getDeviceKey(), device)) { - if (logger.isDebugEnabled()) - logger.debug("device map does not have this device -" + - device.toString()); - } - } - - private EnumSet<DeviceField> getEntityKeys(Long macAddress, - Short vlan, - Integer ipv4Address, - Long switchDPID, - Integer switchPort) { - // FIXME: vlan==null is a valid search. Need to handle this - // case correctly. Note that the code will still work correctly. - // But we might do a full device search instead of using an index. - EnumSet<DeviceField> keys = EnumSet.noneOf(DeviceField.class); - if (macAddress != null) keys.add(DeviceField.MAC); - if (vlan != null) keys.add(DeviceField.VLAN); - if (ipv4Address != null) keys.add(DeviceField.IPV4); - if (switchDPID != null) keys.add(DeviceField.SWITCH); - if (switchPort != null) keys.add(DeviceField.PORT); - return keys; - } - - protected Iterator<Device> queryClassByEntity(IEntityClass clazz, - EnumSet<DeviceField> keyFields, - Entity entity) { - ClassState classState = getClassState(clazz); - DeviceIndex index = classState.secondaryIndexMap.get(keyFields); - if (index == null) return Collections.<Device>emptySet().iterator(); - return new DeviceIndexInterator(this, index.queryByEntity(entity)); - } - - protected Device allocateDevice(Long deviceKey, - Entity entity, - IEntityClass entityClass) { - return new Device(this, deviceKey, entity, entityClass); - } - - // TODO: FIX THIS. - protected Device allocateDevice(Long deviceKey, - String dhcpClientName, - List<AttachmentPoint> aps, - List<AttachmentPoint> trueAPs, - Collection<Entity> entities, - IEntityClass entityClass) { - return new Device(this, deviceKey, dhcpClientName, aps, trueAPs, - entities, entityClass); - } - - protected Device allocateDevice(Device device, - Entity entity, - int insertionpoint) { - return new Device(device, entity, insertionpoint); - } - - //not used - protected Device allocateDevice(Device device, Set <Entity> entities) { - List <AttachmentPoint> newPossibleAPs = - new ArrayList<AttachmentPoint>(); - List <AttachmentPoint> newAPs = - new ArrayList<AttachmentPoint>(); - for (Entity entity : entities) { - if (entity.switchDPID != null && entity.switchPort != null) { - AttachmentPoint aP = - new AttachmentPoint(entity.switchDPID.longValue(), - entity.switchPort.shortValue(), 0); - newPossibleAPs.add(aP); - } - } - if (device.attachmentPoints != null) { - for (AttachmentPoint oldAP : device.attachmentPoints) { - if (newPossibleAPs.contains(oldAP)) { - newAPs.add(oldAP); - } - } - } - if (newAPs.isEmpty()) - newAPs = null; - Device d = new Device(this, device.getDeviceKey(), - device.getDHCPClientName(), newAPs, null, - entities, device.getEntityClass()); - d.updateAttachmentPoint(); - return d; - } - - // ********************* - // ITopologyListener - // ********************* - - /** - * Topology listener method. - */ - @Override - public void topologyChanged(List<LDUpdate> updateList) { - Iterator<Device> diter = deviceMap.values().iterator(); - if (updateList != null) { - if (logger.isTraceEnabled()) { - for(LDUpdate update: updateList) { - logger.trace("Topo update: {}", update); - } - } - } - - while (diter.hasNext()) { - Device d = diter.next(); - if (d.updateAttachmentPoint()) { - if (logger.isDebugEnabled()) { - logger.debug("Attachment point changed for device: {}", d); - } - sendDeviceMovedNotification(d); - } - } - // Since topologyChanged() does not occur in the packet-in pipeline, - // debugEvents need to be flushed explicitly - debugEvents.flushEvents(); - } - - /** - * Send update notifications to listeners - * @param updates the updates to process. - */ - protected void sendDeviceMovedNotification(Device d) { - cntDeviceMoved.updateCounterNoFlush(); - deviceSyncManager.storeDevice(d); - List<IDeviceListener> listeners = deviceListeners.getOrderedListeners(); - if (listeners != null) { - for (IDeviceListener listener : listeners) { - listener.deviceMoved(d); - } - } - } - - // ********************* - // IEntityClassListener - // ********************* - - @Override - public void entityClassChanged (Set<String> entityClassNames) { - /* iterate through the devices, reclassify the devices that belong - * to these entity class names - */ - Iterator<Device> diter = deviceMap.values().iterator(); - while (diter.hasNext()) { - Device d = diter.next(); - if (d.getEntityClass() == null || - entityClassNames.contains(d.getEntityClass().getName())) - reclassifyDevice(d); - } - } - - /** - * this method will reclassify and reconcile a device - possibilities - * are - create new device(s), remove entities from this device. If the - * device entity class did not change then it returns false else true. - * @param device - */ - protected boolean reclassifyDevice(Device device) - { - // first classify all entities of this device - if (device == null) { - logger.debug("In reclassify for null device"); - return false; - } - boolean needToReclassify = false; - for (Entity entity : device.entities) { - IEntityClass entityClass = - this.entityClassifier.classifyEntity(entity); - if (entityClass == null || device.getEntityClass() == null) { - needToReclassify = true; - break; - } - if (!entityClass.getName(). - equals(device.getEntityClass().getName())) { - needToReclassify = true; - break; - } - } - if (needToReclassify == false) { - return false; - } - - cntDeviceReclassifyDelete.updateCounterNoFlush(); - LinkedList<DeviceUpdate> deviceUpdates = - new LinkedList<DeviceUpdate>(); - // delete this device and then re-learn all the entities - this.deleteDevice(device); - deviceUpdates.add(new DeviceUpdate(device, - DeviceUpdate.Change.DELETE, null)); - if (!deviceUpdates.isEmpty()) - processUpdates(deviceUpdates); - for (Entity entity: device.entities ) { - this.learnDeviceByEntity(entity); - } - // Since reclassifyDevices() is not called in the packet-in pipeline, - // debugEvents need to be flushed explicitly - debugEvents.flushEvents(); - return true; - } - - /** - * For testing: sets the interval between writes of the same device - * to the device store. - * @param intervalMs - */ - void setSyncStoreWriteInterval(int intervalMs) { - this.syncStoreWriteIntervalMs = intervalMs; - } - - /** - * For testing: sets the time between transition to MASTER and - * consolidate store - * @param intervalMs - */ - void setInitialSyncStoreConsolidateMs(int intervalMs) { - this.initialSyncStoreConsolidateMs = intervalMs; - } - - /** - * For testing: consolidate the store NOW - */ - void scheduleConsolidateStoreNow() { - this.storeConsolidateTask.reschedule(0, TimeUnit.MILLISECONDS); - } - - private class DeviceSyncManager { - // maps (opaque) deviceKey to the time in System.nanoTime() when we - // last wrote the device to the sync store - private final ConcurrentMap<Long, Long> lastWriteTimes = - new ConcurrentHashMap<Long, Long>(); - - /** - * Write the given device to storage if we are MASTER. - * Use this method if the device has significantly changed (e.g., - * new AP, new IP, entities removed). - * @param d the device to store - */ - public void storeDevice(Device d) { - if (!isMaster) - return; - if (d == null) - return; - long now = System.nanoTime(); - writeUpdatedDeviceToStorage(d); - lastWriteTimes.put(d.getDeviceKey(), now); - } - - /** - * Write the given device to storage if we are MASTER and if the - * last write for the device was more than this.syncStoreIntervalNs - * time ago. - * Use this method to updated last active times in the store. - * @param d the device to store - */ - public void storeDeviceThrottled(Device d) { - long intervalNs = syncStoreWriteIntervalMs*1000L*1000L; - if (!isMaster) - return; - if (d == null) - return; - long now = System.nanoTime(); - Long last = lastWriteTimes.get(d.getDeviceKey()); - if (last == null || - now - last > intervalNs) { - writeUpdatedDeviceToStorage(d); - lastWriteTimes.put(d.getDeviceKey(), now); - } else { - cntDeviceStoreThrottled.updateCounterWithFlush(); - } - } - - /** - * Remove the given device from the store. If only some entities have - * been removed the updated device should be written using - * {@link #storeDevice(Device)} - * @param d - */ - public void removeDevice(Device d) { - if (!isMaster) - return; - // FIXME: could we have a problem with concurrent put to the - // hashMap? I.e., we write a stale entry to the map after the - // delete and now are left with an entry we'll never clean up - lastWriteTimes.remove(d.getDeviceKey()); - try { - // TODO: should probably do versioned delete. OTOH, even - // if we accidentally delete, we'll write it again after - // the next entity .... - cntDeviceRemovedFromStore.updateCounterWithFlush(); - storeClient.delete(DeviceSyncRepresentation.computeKey(d)); - } catch(ObsoleteVersionException e) { - // FIXME - } catch (SyncException e) { - cntSyncException.updateCounterWithFlush(); - logger.error("Could not remove device " + d + " from store", e); - } - } - - /** - * Remove the given Versioned device from the store. If the device - * was locally modified ignore the delete request. - * @param syncedDeviceKey - */ - private void removeDevice(Versioned<DeviceSyncRepresentation> dev) { - try { - cntDeviceRemovedFromStore.updateCounterWithFlush(); - storeClient.delete(dev.getValue().getKey(), - dev.getVersion()); - } catch(ObsoleteVersionException e) { - // Key was locally modified by another thread. - // Do not delete and ignore. - } catch(SyncException e) { - cntSyncException.updateCounterWithFlush(); - logger.error("Failed to remove device entry for " + - dev.toString() + " from store.", e); - } - } - - /** - * Synchronously transition from SLAVE to MASTER. By iterating through - * the store and learning all devices from the store - */ - private void goToMaster() { - if (logger.isDebugEnabled()) { - logger.debug("Transitioning to MASTER role"); - } - cntTransitionToMaster.updateCounterWithFlush(); - IClosableIterator<Map.Entry<String,Versioned<DeviceSyncRepresentation>>> - iter = null; - try { - iter = storeClient.entries(); - } catch (SyncException e) { - cntSyncException.updateCounterWithFlush(); - logger.error("Failed to read devices from sync store", e); - return; - } - try { - while(iter.hasNext()) { - Versioned<DeviceSyncRepresentation> versionedDevice = - iter.next().getValue(); - DeviceSyncRepresentation storedDevice = - versionedDevice.getValue(); - if (storedDevice == null) - continue; - cntDevicesFromStore.updateCounterWithFlush(); - for(SyncEntity se: storedDevice.getEntities()) { - learnDeviceByEntity(se.asEntity()); - } - } - } finally { - if (iter != null) - iter.close(); - } - storeConsolidateTask.reschedule(initialSyncStoreConsolidateMs, - TimeUnit.MILLISECONDS); - } - - /** - * Actually perform the write of the device to the store - * FIXME: concurrent modification behavior - * @param device The device to write - */ - private void writeUpdatedDeviceToStorage(Device device) { - try { - cntDeviceStrored.updateCounterWithFlush(); - // FIXME: use a versioned put - DeviceSyncRepresentation storeDevice = - new DeviceSyncRepresentation(device); - storeClient.put(storeDevice.getKey(), storeDevice); - } catch (ObsoleteVersionException e) { - // FIXME: what's the right behavior here. Can the store client - // even throw this error? - } catch (SyncException e) { - cntSyncException.updateCounterWithFlush(); - logger.error("Could not write device " + device + - " to sync store:", e); - } - } - - /** - * Iterate through all entries in the sync store. For each device - * in the store check if any stored entity matches a live device. If - * no entities match a live device we remove the entry from the store. - * - * Note: we do not check if all devices known to device manager are - * in the store. We rely on regular packetIns for that. - * Note: it's possible that multiple entries in the store map to the - * same device. We don't check or handle this case. - * - * We need to perform this check after a SLAVE->MASTER transition to - * get rid of all entries the old master might have written to the - * store after we took over. We also run it regularly in MASTER - * state to ensure we don't have stale entries in the store - */ - private void consolidateStore() { - if (!isMaster) - return; - cntConsolidateStoreRuns.updateCounterWithFlush(); - if (logger.isDebugEnabled()) { - logger.debug("Running consolidateStore."); - } - IClosableIterator<Map.Entry<String,Versioned<DeviceSyncRepresentation>>> - iter = null; - try { - iter = storeClient.entries(); - } catch (SyncException e) { - cntSyncException.updateCounterWithFlush(); - logger.error("Failed to read devices from sync store", e); - return; - } - try { - while(iter.hasNext()) { - boolean found = false; - Versioned<DeviceSyncRepresentation> versionedDevice = - iter.next().getValue(); - DeviceSyncRepresentation storedDevice = - versionedDevice.getValue(); - if (storedDevice == null) - continue; - for(SyncEntity se: storedDevice.getEntities()) { - try { - // Do we have a device for this entity?? - IDevice d = findDevice(se.macAddress, se.vlan, - se.ipv4Address, - se.switchDPID, - se.switchPort); - if (d != null) { - found = true; - break; - } - } catch (IllegalArgumentException e) { - // not all key fields provided. Skip entity - } - } - if (!found) { - // We currently DO NOT have a live device that - // matches the current device from the store. - // Delete device from store. - if (logger.isDebugEnabled()) { - logger.debug("Removing device {} from store. No " - + "corresponding live device", - storedDevice.getKey()); - } - cntConsolidateStoreDevicesRemoved.updateCounterWithFlush(); - removeDevice(versionedDevice); - } - } - } finally { - if (iter != null) - iter.close(); - } - } - } - - - /** - * For testing. Sets the syncService. Only call after init but before - * startUp. Used by MockDeviceManager - * @param syncService - */ - protected void setSyncServiceIfNotSet(ISyncService syncService) { - if (this.syncService == null) - this.syncService = syncService; - } - - /** - * For testing. - * @return - */ - IHAListener getHAListener() { - return this.haListenerDelegate; - } - - /** - * Device Event Class used to log Device related events - */ - private class DeviceEvent { - @EventColumn(name = "MAC", description = EventFieldType.MAC) - private final long macAddress; - @EventColumn(name = "IPs", description = EventFieldType.LIST_IPV4) - private final List<Integer> ipv4Addresses; - @EventColumn(name = "Old Attachment Points", - description = EventFieldType.LIST_ATTACHMENT_POINT) - private final List<SwitchPort> oldAttachmentPoints; - @EventColumn(name = "Current Attachment Points", - description = EventFieldType.LIST_ATTACHMENT_POINT) - private final List<SwitchPort> currentAttachmentPoints; - @EventColumn(name = "VLAN IDs", description = EventFieldType.LIST_OBJECT) - private final List<Short> vlanIds; - @EventColumn(name = "Reason", description = EventFieldType.STRING) - private final String reason; - - public DeviceEvent(long macAddress, List<Integer> ipv4Addresses, - List<SwitchPort> oldAttachmentPoints, - List<SwitchPort> currentAttachmentPoints, - List<Short> vlanIds, String reason) { - super(); - this.macAddress = macAddress; - this.ipv4Addresses = ipv4Addresses; - this.oldAttachmentPoints = oldAttachmentPoints; - this.currentAttachmentPoints = currentAttachmentPoints; - this.vlanIds = vlanIds; - this.reason = reason; - } - } + */ + + /** + * Look up a {@link Device} based on the provided {@link Entity}. Also + * learns based on the new entity, and will update existing devices as + * required. + * + * @param entity the {@link Entity} + * @return The {@link Device} object if found + */ + protected Device learnDeviceByEntity(Entity entity) { + ArrayList<Long> deleteQueue = null; + LinkedList<DeviceUpdate> deviceUpdates = null; + Device device = null; + + // we may need to restart the learning process if we detect + // concurrent modification. Note that we ensure that at least + // one thread should always succeed so we don't get into infinite + // starvation loops + while (true) { + deviceUpdates = null; + + // Look up the fully-qualified entity to see if it already + // exists in the primary entity index. + Long deviceKey = primaryIndex.findByEntity(entity); + IEntityClass entityClass = null; + + if (deviceKey == null) { + // If the entity does not exist in the primary entity index, + // use the entity classifier for find the classes for the + // entity. Look up the entity in the returned class' + // class entity index. + entityClass = entityClassifier.classifyEntity(entity); + if (entityClass == null) { + // could not classify entity. No device + device = null; + break; + } + ClassState classState = getClassState(entityClass); + + if (classState.classIndex != null) { + deviceKey = classState.classIndex.findByEntity(entity); + } + } + if (deviceKey != null) { + // If the primary or secondary index contains the entity + // use resulting device key to look up the device in the + // device map, and use the referenced Device below. + device = deviceMap.get(deviceKey); + if (device == null) { + // This can happen due to concurrent modification + if (logger.isDebugEnabled()) { + logger.debug("No device for deviceKey {} while " + + "while processing entity {}", + deviceKey, entity); + } + // if so, then try again till we don't even get the device key + // and so we recreate the device + continue; + } + } else { + // If the secondary index does not contain the entity, + // create a new Device object containing the entity, and + // generate a new device ID if the the entity is on an + // attachment point port. Otherwise ignore. + if (entity.hasSwitchPort() && !topology.isAttachmentPointPort(entity.getSwitchDPID(), entity.getSwitchPort())) { + cntDeviceOnInternalPortNotLearned.increment(); + if (logger.isDebugEnabled()) { + logger.debug("Not learning new device on internal" + + " link: {}", entity); + } + device = null; + break; + } + // Before we create the new device also check if + // the entity is allowed (e.g., for spoofing protection) + if (!isEntityAllowed(entity, entityClass)) { + cntPacketNotAllowed.increment(); + if (logger.isDebugEnabled()) { + logger.debug("PacketIn is not allowed {} {}", + entityClass.getName(), entity); + } + device = null; + break; + } + deviceKey = deviceKeyCounter.getAndIncrement(); + device = allocateDevice(deviceKey, entity, entityClass); + + + // Add the new device to the primary map with a simple put + deviceMap.put(deviceKey, device); + // update indices + if (!updateIndices(device, deviceKey)) { + if (deleteQueue == null) + deleteQueue = new ArrayList<Long>(); + deleteQueue.add(deviceKey); + continue; + } + + updateSecondaryIndices(entity, entityClass, deviceKey); + + // We need to count and log here. If we log earlier we could + // hit a concurrent modification and restart the dev creation + // and potentially count the device twice. + cntNewDevice.increment(); + if (logger.isDebugEnabled()) { + logger.debug("New device created: {} deviceKey={}, entity={}", + new Object[]{device, deviceKey, entity}); + } + // generate new device update + deviceUpdates = updateUpdates(deviceUpdates, new DeviceUpdate(device, ADD, null)); + + break; + } + // if it gets here, we have a pre-existing Device for this Entity + if (!isEntityAllowed(entity, device.getEntityClass())) { + cntPacketNotAllowed.increment(); + if (logger.isDebugEnabled()) { + logger.info("PacketIn is not allowed {} {}", + device.getEntityClass().getName(), entity); + } + return null; + } + // If this is not an attachment point port we don't learn the new entity + // and don't update indexes. But we do allow the device to continue up + // the chain. + if (entity.hasSwitchPort() && !topology.isAttachmentPointPort(entity.getSwitchDPID(), entity.getSwitchPort())) { + cntPacketOnInternalPortForKnownDevice.increment(); + break; + } + int entityindex = -1; + if ((entityindex = device.entityIndex(entity)) >= 0) { + // Entity already exists + // update timestamp on the found entity + Date lastSeen = entity.getLastSeenTimestamp(); + if (lastSeen == null) { + lastSeen = new Date(); + entity.setLastSeenTimestamp(lastSeen); + } + device.entities[entityindex].setLastSeenTimestamp(lastSeen); + // we break the loop after checking for changes to the AP + } else { + // New entity for this device + // compute the insertion point for the entity. + // see Arrays.binarySearch() + entityindex = -(entityindex + 1); + Device newDevice = allocateDevice(device, entity, entityindex); + + // generate updates + EnumSet<DeviceField> changedFields = findChangedFields(device, entity); + + // update the device map with a replace call + boolean res = deviceMap.replace(deviceKey, device, newDevice); + // If replace returns false, restart the process from the + // beginning (this implies another thread concurrently + // modified this Device). + if (!res) + continue; + + device = newDevice; + // update indices + if (!updateIndices(device, deviceKey)) { + continue; + } + updateSecondaryIndices(entity, + device.getEntityClass(), + deviceKey); + + // We need to count here after all the possible "continue" + // statements in this branch + cntNewEntity.increment(); + if (changedFields.size() > 0) { + cntDeviceChanged.increment(); + deviceUpdates = + updateUpdates(deviceUpdates, + new DeviceUpdate(newDevice, CHANGE, + changedFields)); + } + // we break the loop after checking for changed AP + } + // Update attachment point (will only be hit if the device + // already existed and no concurrent modification) + if (entity.hasSwitchPort()) { + boolean moved = device.updateAttachmentPoint(entity.getSwitchDPID(), + entity.getSwitchPort(), + entity.getLastSeenTimestamp()); + // TODO: use update mechanism instead of sending the + // notification directly + if (moved) { + // we count device moved events in sendDeviceMovedNotification() + sendDeviceMovedNotification(device); + if (logger.isTraceEnabled()) { + logger.trace("Device moved: attachment points {}," + + "entities {}", device.attachmentPoints, + device.entities); + } + } else { + if (logger.isTraceEnabled()) { + logger.trace("Device attachment point updated: " + + "attachment points {}," + + "entities {}", device.attachmentPoints, + device.entities); + } + } + } + break; + } + + if (deleteQueue != null) { + for (Long l : deleteQueue) { + Device dev = deviceMap.get(l); + this.deleteDevice(dev); + } + } + processUpdates(deviceUpdates); + deviceSyncManager.storeDeviceThrottled(device); + + return device; + } + + protected boolean isEntityAllowed(Entity entity, IEntityClass entityClass) { + return true; + } + + + + + + protected EnumSet<DeviceField> findChangedFields(Device device, + Entity newEntity) { + EnumSet<DeviceField> changedFields = + EnumSet.of(DeviceField.IPV4, + DeviceField.VLAN, + DeviceField.SWITCH); + + if (newEntity.getIpv4Address() == null) + changedFields.remove(DeviceField.IPV4); + if (newEntity.getVlan() == null) + changedFields.remove(DeviceField.VLAN); + if (newEntity.getSwitchDPID() == null || + newEntity.getSwitchPort() == null) + changedFields.remove(DeviceField.SWITCH); + + if (changedFields.size() == 0) return changedFields; + + for (Entity entity : device.getEntities()) { + if (newEntity.getIpv4Address() == null || + (entity.getIpv4Address() != null && + entity.getIpv4Address().equals(newEntity.getIpv4Address()))) + changedFields.remove(DeviceField.IPV4); + if (newEntity.getVlan() == null || + (entity.getVlan() != null && + entity.getVlan().equals(newEntity.getVlan()))) + changedFields.remove(DeviceField.VLAN); + if (newEntity.getSwitchDPID() == null || + newEntity.getSwitchPort() == null || + (entity.getSwitchDPID() != null && + entity.getSwitchPort() != null && + entity.getSwitchDPID().equals(newEntity.getSwitchDPID()) && + entity.getSwitchPort().equals(newEntity.getSwitchPort()))) + changedFields.remove(DeviceField.SWITCH); + } + + return changedFields; + } + + /** + * Send update notifications to listeners + * @param updates the updates to process. + */ + protected void processUpdates(Queue<DeviceUpdate> updates) { + if (updates == null) return; + DeviceUpdate update = null; + while (null != (update = updates.poll())) { + if (logger.isTraceEnabled()) { + logger.trace("Dispatching device update: {}", update); + } + if (update.change == DeviceUpdate.Change.DELETE) { + deviceSyncManager.removeDevice(update.device); + } else { + deviceSyncManager.storeDevice(update.device); + } + List<IDeviceListener> listeners = deviceListeners.getOrderedListeners(); + notifyListeners(listeners, update); + } + } + + protected void notifyListeners(List<IDeviceListener> listeners, DeviceUpdate update) { + if (listeners == null) { + return; + } + for (IDeviceListener listener : listeners) { + switch (update.change) { + case ADD: + listener.deviceAdded(update.device); + break; + case DELETE: + listener.deviceRemoved(update.device); + break; + case CHANGE: + for (DeviceField field : update.fieldsChanged) { + switch (field) { + case IPV4: + listener.deviceIPV4AddrChanged(update.device); + break; + case SWITCH: + case PORT: + //listener.deviceMoved(update.device); + break; + case VLAN: + listener.deviceVlanChanged(update.device); + break; + default: + logger.debug("Unknown device field changed {}", + update.fieldsChanged.toString()); + break; + } + } + break; + } + } + } + + /** + * Check if the entity e has all the keyFields set. Returns false if not + * @param e entity to check + * @param keyFields the key fields to check e against + * @return + */ + protected boolean allKeyFieldsPresent(Entity e, EnumSet<DeviceField> keyFields) { + for (DeviceField f : keyFields) { + switch (f) { + case MAC: + // MAC address is always present + break; + case IPV4: + if (e.ipv4Address == null) return false; + break; + case SWITCH: + if (e.switchDPID == null) return false; + break; + case PORT: + if (e.switchPort == null) return false; + break; + case VLAN: + // FIXME: vlan==null is ambiguous: it can mean: not present + // or untagged + //if (e.vlan == null) return false; + break; + default: + // we should never get here. unless somebody extended + // DeviceFields + throw new IllegalStateException(); + } + } + return true; + } + + private LinkedList<DeviceUpdate> updateUpdates(LinkedList<DeviceUpdate> list, DeviceUpdate update) { + if (update == null) return list; + if (list == null) + list = new LinkedList<DeviceUpdate>(); + list.add(update); + + return list; + } + + /** + * Get the secondary index for a class. Will return null if the + * secondary index was created concurrently in another thread. + * @param clazz the class for the index + * @return + */ + private ClassState getClassState(IEntityClass clazz) { + ClassState classState = classStateMap.get(clazz.getName()); + if (classState != null) return classState; + + classState = new ClassState(clazz); + ClassState r = classStateMap.putIfAbsent(clazz.getName(), classState); + if (r != null) { + // concurrent add + return r; + } + return classState; + } + + /** + * Update both the primary and class indices for the provided device. + * If the update fails because of an concurrent update, will return false. + * @param device the device to update + * @param deviceKey the device key for the device + * @return true if the update succeeded, false otherwise. + */ + private boolean updateIndices(Device device, Long deviceKey) { + if (!primaryIndex.updateIndex(device, deviceKey)) { + return false; + } + IEntityClass entityClass = device.getEntityClass(); + ClassState classState = getClassState(entityClass); + + if (classState.classIndex != null) { + if (!classState.classIndex.updateIndex(device, + deviceKey)) + return false; + } + return true; + } + + /** + * Update the secondary indices for the given entity and associated + * entity classes + * @param entity the entity to update + * @param entityClass the entity class for the entity + * @param deviceKey the device key to set up + */ + private void updateSecondaryIndices(Entity entity, + IEntityClass entityClass, + Long deviceKey) { + for (DeviceIndex index : secondaryIndexMap.values()) { + index.updateIndex(entity, deviceKey); + } + ClassState state = getClassState(entityClass); + for (DeviceIndex index : state.secondaryIndexMap.values()) { + index.updateIndex(entity, deviceKey); + } + } + + /** + * Clean up expired entities/devices + */ + protected void cleanupEntities () { + cntCleanupEntitiesRuns.increment(); + + Calendar c = Calendar.getInstance(); + c.add(Calendar.MILLISECOND, -ENTITY_TIMEOUT); + Date cutoff = c.getTime(); + + ArrayList<Entity> toRemove = new ArrayList<Entity>(); + ArrayList<Entity> toKeep = new ArrayList<Entity>(); + + Iterator<Device> diter = deviceMap.values().iterator(); + LinkedList<DeviceUpdate> deviceUpdates = + new LinkedList<DeviceUpdate>(); + + while (diter.hasNext()) { + Device d = diter.next(); + + while (true) { + deviceUpdates.clear(); + toRemove.clear(); + toKeep.clear(); + for (Entity e : d.getEntities()) { + if (e.getLastSeenTimestamp() != null && + 0 > e.getLastSeenTimestamp().compareTo(cutoff)) { + // individual entity needs to be removed + toRemove.add(e); + } else { + toKeep.add(e); + } + } + if (toRemove.size() == 0) { + break; + } + + cntEntityRemovedTimeout.increment(); + for (Entity e : toRemove) { + removeEntity(e, d.getEntityClass(), d.getDeviceKey(), toKeep); + } + + if (toKeep.size() > 0) { + Device newDevice = allocateDevice(d.getDeviceKey(), + d.getDHCPClientName(), + d.oldAPs, + d.attachmentPoints, + toKeep, + d.getEntityClass()); + + EnumSet<DeviceField> changedFields = + EnumSet.noneOf(DeviceField.class); + for (Entity e : toRemove) { + changedFields.addAll(findChangedFields(newDevice, e)); + } + DeviceUpdate update = null; + if (changedFields.size() > 0) { + update = new DeviceUpdate(d, CHANGE, changedFields); + } + + if (!deviceMap.replace(newDevice.getDeviceKey(), + d, + newDevice)) { + // concurrent modification; try again + // need to use device that is the map now for the next + // iteration + d = deviceMap.get(d.getDeviceKey()); + if (null != d) + continue; + } + if (update != null) { + // need to count after all possibly continue stmts in + // this branch + cntDeviceChanged.increment(); + deviceUpdates.add(update); + } + } else { + DeviceUpdate update = new DeviceUpdate(d, DELETE, null); + if (!deviceMap.remove(d.getDeviceKey(), d)) { + // concurrent modification; try again + // need to use device that is the map now for the next + // iteration + d = deviceMap.get(d.getDeviceKey()); + if (null != d) + continue; + cntDeviceDeleted.increment(); + } + deviceUpdates.add(update); + } + processUpdates(deviceUpdates); + break; + } + } + // Since cleanupEntities() is not called in the packet-in pipeline, + // debugEvents need to be flushed explicitly + debugEventService.flushEvents(); + } + + protected void removeEntity(Entity removed, + IEntityClass entityClass, + Long deviceKey, + Collection<Entity> others) { + // Don't count in this method. This method CAN BE called to clean-up + // after concurrent device adds/updates and thus counting here + // is misleading + for (DeviceIndex index : secondaryIndexMap.values()) { + index.removeEntityIfNeeded(removed, deviceKey, others); + } + ClassState classState = getClassState(entityClass); + for (DeviceIndex index : classState.secondaryIndexMap.values()) { + index.removeEntityIfNeeded(removed, deviceKey, others); + } + + primaryIndex.removeEntityIfNeeded(removed, deviceKey, others); + + if (classState.classIndex != null) { + classState.classIndex.removeEntityIfNeeded(removed, + deviceKey, + others); + } + } + + /** + * method to delete a given device, remove all entities first and then + * finally delete the device itself. + * @param device + */ + protected void deleteDevice(Device device) { + // Don't count in this method. This method CAN BE called to clean-up + // after concurrent device adds/updates and thus counting here + // is misleading + ArrayList<Entity> emptyToKeep = new ArrayList<Entity>(); + for (Entity entity : device.getEntities()) { + this.removeEntity(entity, device.getEntityClass(), + device.getDeviceKey(), emptyToKeep); + } + if (!deviceMap.remove(device.getDeviceKey(), device)) { + if (logger.isDebugEnabled()) + logger.debug("device map does not have this device -" + + device.toString()); + } + } + + private EnumSet<DeviceField> getEntityKeys(MacAddress macAddress, + VlanVid vlan, + IPv4Address ipv4Address, + DatapathId switchDPID, + OFPort switchPort) { + // FIXME: vlan==null is a valid search. Need to handle this + // case correctly. Note that the code will still work correctly. + // But we might do a full device search instead of using an index. + EnumSet<DeviceField> keys = EnumSet.noneOf(DeviceField.class); + if (macAddress != null) keys.add(DeviceField.MAC); + if (vlan != null) keys.add(DeviceField.VLAN); + if (ipv4Address != null) keys.add(DeviceField.IPV4); + if (switchDPID != null) keys.add(DeviceField.SWITCH); + if (switchPort != null) keys.add(DeviceField.PORT); + return keys; + } + + protected Iterator<Device> queryClassByEntity(IEntityClass clazz, + EnumSet<DeviceField> keyFields, + Entity entity) { + ClassState classState = getClassState(clazz); + DeviceIndex index = classState.secondaryIndexMap.get(keyFields); + if (index == null) return Collections.<Device>emptySet().iterator(); + return new DeviceIndexInterator(this, index.queryByEntity(entity)); + } + + protected Device allocateDevice(Long deviceKey, + Entity entity, + IEntityClass entityClass) { + return new Device(this, deviceKey, entity, entityClass); + } + + // TODO: FIX THIS. + protected Device allocateDevice(Long deviceKey, + String dhcpClientName, + List<AttachmentPoint> aps, + List<AttachmentPoint> trueAPs, + Collection<Entity> entities, + IEntityClass entityClass) { + return new Device(this, deviceKey, dhcpClientName, aps, trueAPs, + entities, entityClass); + } + + protected Device allocateDevice(Device device, + Entity entity, + int insertionpoint) { + return new Device(device, entity, insertionpoint); + } + + //not used + protected Device allocateDevice(Device device, Set <Entity> entities) { + List <AttachmentPoint> newPossibleAPs = + new ArrayList<AttachmentPoint>(); + List <AttachmentPoint> newAPs = + new ArrayList<AttachmentPoint>(); + for (Entity entity : entities) { + if (entity.switchDPID != null && entity.switchPort != null) { + AttachmentPoint aP = + new AttachmentPoint(entity.switchDPID, + entity.switchPort, new Date(0)); + newPossibleAPs.add(aP); + } + } + if (device.attachmentPoints != null) { + for (AttachmentPoint oldAP : device.attachmentPoints) { + if (newPossibleAPs.contains(oldAP)) { + newAPs.add(oldAP); + } + } + } + if (newAPs.isEmpty()) + newAPs = null; + Device d = new Device(this, device.getDeviceKey(), + device.getDHCPClientName(), newAPs, null, + entities, device.getEntityClass()); + d.updateAttachmentPoint(); + return d; + } + + // ********************* + // ITopologyListener + // ********************* + + /** + * Topology listener method. + */ + @Override + public void topologyChanged(List<LDUpdate> updateList) { + Iterator<Device> diter = deviceMap.values().iterator(); + if (updateList != null) { + if (logger.isTraceEnabled()) { + for(LDUpdate update: updateList) { + logger.trace("Topo update: {}", update); + } + } + } + + while (diter.hasNext()) { + Device d = diter.next(); + if (d.updateAttachmentPoint()) { + if (logger.isDebugEnabled()) { + logger.debug("Attachment point changed for device: {}", d); + } + sendDeviceMovedNotification(d); + } + } + // Since topologyChanged() does not occur in the packet-in pipeline, + // debugEvents need to be flushed explicitly + debugEventService.flushEvents(); + } + + /** + * Send update notifications to listeners + * @param updates the updates to process. + */ + protected void sendDeviceMovedNotification(Device d) { + cntDeviceMoved.increment(); + deviceSyncManager.storeDevice(d); + List<IDeviceListener> listeners = deviceListeners.getOrderedListeners(); + if (listeners != null) { + for (IDeviceListener listener : listeners) { + listener.deviceMoved(d); + } + } + } + + // ********************* + // IEntityClassListener + // ********************* + + @Override + public void entityClassChanged (Set<String> entityClassNames) { + /* iterate through the devices, reclassify the devices that belong + * to these entity class names + */ + Iterator<Device> diter = deviceMap.values().iterator(); + while (diter.hasNext()) { + Device d = diter.next(); + if (d.getEntityClass() == null || + entityClassNames.contains(d.getEntityClass().getName())) + reclassifyDevice(d); + } + } + + /** + * this method will reclassify and reconcile a device - possibilities + * are - create new device(s), remove entities from this device. If the + * device entity class did not change then it returns false else true. + * @param device + */ + protected boolean reclassifyDevice(Device device) + { + // first classify all entities of this device + if (device == null) { + logger.debug("In reclassify for null device"); + return false; + } + boolean needToReclassify = false; + for (Entity entity : device.entities) { + IEntityClass entityClass = + this.entityClassifier.classifyEntity(entity); + if (entityClass == null || device.getEntityClass() == null) { + needToReclassify = true; + break; + } + if (!entityClass.getName(). + equals(device.getEntityClass().getName())) { + needToReclassify = true; + break; + } + } + if (needToReclassify == false) { + return false; + } + + cntDeviceReclassifyDelete.increment(); + LinkedList<DeviceUpdate> deviceUpdates = + new LinkedList<DeviceUpdate>(); + // delete this device and then re-learn all the entities + this.deleteDevice(device); + deviceUpdates.add(new DeviceUpdate(device, + DeviceUpdate.Change.DELETE, null)); + if (!deviceUpdates.isEmpty()) + processUpdates(deviceUpdates); + for (Entity entity: device.entities ) { + this.learnDeviceByEntity(entity); + } + // Since reclassifyDevices() is not called in the packet-in pipeline, + // debugEvents need to be flushed explicitly + debugEventService.flushEvents(); + return true; + } + + /** + * For testing: sets the interval between writes of the same device + * to the device store. + * @param intervalMs + */ + void setSyncStoreWriteInterval(int intervalMs) { + this.syncStoreWriteIntervalMs = intervalMs; + } + + /** + * For testing: sets the time between transition to MASTER and + * consolidate store + * @param intervalMs + */ + void setInitialSyncStoreConsolidateMs(int intervalMs) { + this.initialSyncStoreConsolidateMs = intervalMs; + } + + /** + * For testing: consolidate the store NOW + */ + void scheduleConsolidateStoreNow() { + this.storeConsolidateTask.reschedule(0, TimeUnit.MILLISECONDS); + } + + private class DeviceSyncManager { + // maps (opaque) deviceKey to the time in System.nanoTime() when we + // last wrote the device to the sync store + private final ConcurrentMap<Long, Long> lastWriteTimes = new ConcurrentHashMap<Long, Long>(); + + /** + * Write the given device to storage if we are MASTER. + * Use this method if the device has significantly changed (e.g., + * new AP, new IP, entities removed). + * @param d the device to store + */ + public void storeDevice(Device d) { + if (!isMaster) + return; + if (d == null) + return; + long now = System.nanoTime(); + writeUpdatedDeviceToStorage(d); + lastWriteTimes.put(d.getDeviceKey(), now); + } + + /** + * Write the given device to storage if we are MASTER and if the + * last write for the device was more than this.syncStoreIntervalNs + * time ago. + * Use this method to updated last active times in the store. + * @param d the device to store + */ + public void storeDeviceThrottled(Device d) { + long intervalNs = syncStoreWriteIntervalMs*1000L*1000L; + if (!isMaster) + return; + if (d == null) + return; + long now = System.nanoTime(); + Long last = lastWriteTimes.get(d.getDeviceKey()); + if (last == null || (now - last) > intervalNs) { + writeUpdatedDeviceToStorage(d); + lastWriteTimes.put(d.getDeviceKey(), now); + } else { + cntDeviceStoreThrottled.increment(); + } + } + + /** + * Remove the given device from the store. If only some entities have + * been removed the updated device should be written using + * {@link #storeDevice(Device)} + * @param d + */ + public void removeDevice(Device d) { + if (!isMaster) + return; + // FIXME: could we have a problem with concurrent put to the + // hashMap? I.e., we write a stale entry to the map after the + // delete and now are left with an entry we'll never clean up + lastWriteTimes.remove(d.getDeviceKey()); + try { + // TODO: should probably do versioned delete. OTOH, even + // if we accidentally delete, we'll write it again after + // the next entity .... + cntDeviceRemovedFromStore.increment(); + storeClient.delete(DeviceSyncRepresentation.computeKey(d)); + } catch(ObsoleteVersionException e) { + // FIXME + } catch (SyncException e) { + cntSyncException.increment(); + logger.error("Could not remove device " + d + " from store", e); + } + } + + /** + * Remove the given Versioned device from the store. If the device + * was locally modified ignore the delete request. + * @param syncedDeviceKey + */ + private void removeDevice(Versioned<DeviceSyncRepresentation> dev) { + try { + cntDeviceRemovedFromStore.increment(); + storeClient.delete(dev.getValue().getKey(), + dev.getVersion()); + } catch(ObsoleteVersionException e) { + // Key was locally modified by another thread. + // Do not delete and ignore. + } catch(SyncException e) { + cntSyncException.increment(); + logger.error("Failed to remove device entry for " + + dev.toString() + " from store.", e); + } + } + + /** + * Synchronously transition from SLAVE to MASTER. By iterating through + * the store and learning all devices from the store + */ + private void goToMaster() { + if (logger.isDebugEnabled()) { + logger.debug("Transitioning to MASTER role"); + } + cntTransitionToMaster.increment(); + IClosableIterator<Map.Entry<String,Versioned<DeviceSyncRepresentation>>> + iter = null; + try { + iter = storeClient.entries(); + } catch (SyncException e) { + cntSyncException.increment(); + logger.error("Failed to read devices from sync store", e); + return; + } + try { + while(iter.hasNext()) { + Versioned<DeviceSyncRepresentation> versionedDevice = + iter.next().getValue(); + DeviceSyncRepresentation storedDevice = + versionedDevice.getValue(); + if (storedDevice == null) + continue; + cntDevicesFromStore.increment(); + for(SyncEntity se: storedDevice.getEntities()) { + learnDeviceByEntity(se.asEntity()); + } + } + } finally { + if (iter != null) + iter.close(); + } + storeConsolidateTask.reschedule(initialSyncStoreConsolidateMs, + TimeUnit.MILLISECONDS); + } + + /** + * Actually perform the write of the device to the store + * FIXME: concurrent modification behavior + * @param device The device to write + */ + private void writeUpdatedDeviceToStorage(Device device) { + try { + cntDeviceStrored.increment(); + // FIXME: use a versioned put + DeviceSyncRepresentation storeDevice = new DeviceSyncRepresentation(device); + storeClient.put(storeDevice.getKey(), storeDevice); + } catch (ObsoleteVersionException e) { + // FIXME: what's the right behavior here. Can the store client + // even throw this error? + } catch (SyncException e) { + cntSyncException.increment(); + logger.error("Could not write device " + device + + " to sync store:", e); + } catch (Exception e) { + logger.error("Count not write device to sync storage " + e.getMessage()); + } + } + + /** + * Iterate through all entries in the sync store. For each device + * in the store check if any stored entity matches a live device. If + * no entities match a live device we remove the entry from the store. + * + * Note: we do not check if all devices known to device manager are + * in the store. We rely on regular packetIns for that. + * Note: it's possible that multiple entries in the store map to the + * same device. We don't check or handle this case. + * + * We need to perform this check after a SLAVE->MASTER transition to + * get rid of all entries the old master might have written to the + * store after we took over. We also run it regularly in MASTER + * state to ensure we don't have stale entries in the store + */ + private void consolidateStore() { + if (!isMaster) + return; + cntConsolidateStoreRuns.increment(); + if (logger.isDebugEnabled()) { + logger.debug("Running consolidateStore."); + } + IClosableIterator<Map.Entry<String,Versioned<DeviceSyncRepresentation>>> + iter = null; + try { + iter = storeClient.entries(); + } catch (SyncException e) { + cntSyncException.increment(); + logger.error("Failed to read devices from sync store", e); + return; + } + try { + while(iter.hasNext()) { + boolean found = false; + Versioned<DeviceSyncRepresentation> versionedDevice = + iter.next().getValue(); + DeviceSyncRepresentation storedDevice = + versionedDevice.getValue(); + if (storedDevice == null) + continue; + for(SyncEntity se: storedDevice.getEntities()) { + try { + // Do we have a device for this entity?? + IDevice d = findDevice(se.macAddress, se.vlan, + se.ipv4Address, + se.switchDPID, + se.switchPort); + if (d != null) { + found = true; + break; + } + } catch (IllegalArgumentException e) { + // not all key fields provided. Skip entity + } + } + if (!found) { + // We currently DO NOT have a live device that + // matches the current device from the store. + // Delete device from store. + if (logger.isDebugEnabled()) { + logger.debug("Removing device {} from store. No " + + "corresponding live device", + storedDevice.getKey()); + } + cntConsolidateStoreDevicesRemoved.increment(); + removeDevice(versionedDevice); + } + } + } finally { + if (iter != null) + iter.close(); + } + } + } + + + /** + * For testing. Sets the syncService. Only call after init but before + * startUp. Used by MockDeviceManager + * @param syncService + */ + protected void setSyncServiceIfNotSet(ISyncService syncService) { + if (this.syncService == null) + this.syncService = syncService; + } + + /** + * For testing. + * @return + */ + IHAListener getHAListener() { + return this.haListenerDelegate; + } + + /** + * Device Event Class used to log Device related events + */ + private class DeviceEvent { + @EventColumn(name = "MAC", description = EventFieldType.MAC) + private final MacAddress macAddress; + @EventColumn(name = "IPs", description = EventFieldType.IPv4) + private final List<IPv4Address> ipv4Addresses; + @EventColumn(name = "Old Attachment Points", + description = EventFieldType.COLLECTION_ATTACHMENT_POINT) + private final List<SwitchPort> oldAttachmentPoints; + @EventColumn(name = "Current Attachment Points", + description = EventFieldType.COLLECTION_ATTACHMENT_POINT) + private final List<SwitchPort> currentAttachmentPoints; + @EventColumn(name = "VLAN IDs", description = EventFieldType.COLLECTION_OBJECT) + private final List<VlanVid> vlanIds; + @EventColumn(name = "Reason", description = EventFieldType.STRING) + private final String reason; + + public DeviceEvent(MacAddress macAddress, List<IPv4Address> ipv4Addresses, + List<SwitchPort> oldAttachmentPoints, + List<SwitchPort> currentAttachmentPoints, + List<VlanVid> vlanIds, String reason) { + super(); + this.macAddress = macAddress; + this.ipv4Addresses = ipv4Addresses; + this.oldAttachmentPoints = oldAttachmentPoints; + this.currentAttachmentPoints = currentAttachmentPoints; + this.vlanIds = vlanIds; + this.reason = reason; + } + } } diff --git a/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceMultiIndex.java b/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceMultiIndex.java index c6aa9808e78a598c350ad388a7f5c06711ee8436..df47d32b2558df200cf8f02154cbde6444411ff4 100644 --- a/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceMultiIndex.java +++ b/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceMultiIndex.java @@ -74,11 +74,11 @@ public class DeviceMultiIndex extends DeviceIndex { } @Override - public void updateIndex(Entity entity, Long deviceKey) { + public boolean updateIndex(Entity entity, Long deviceKey) { Collection<Long> devices = null; IndexedEntity ie = new IndexedEntity(keyFields, entity); - if (!ie.hasNonNullKeys()) return; + if (!ie.hasNonNullKeys()) return false; devices = index.get(ie); if (devices == null) { @@ -90,6 +90,7 @@ public class DeviceMultiIndex extends DeviceIndex { } devices.add(deviceKey); + return true; } @Override diff --git a/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceSyncRepresentation.java b/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceSyncRepresentation.java index 42bfb01321cfc704d9578672630893bff31d4e0e..b1c395d4c9664553550685125f686bdf87508283 100644 --- a/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceSyncRepresentation.java +++ b/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceSyncRepresentation.java @@ -6,9 +6,14 @@ import java.util.Collections; import java.util.Date; import java.util.EnumSet; import java.util.List; -import net.floodlightcontroller.devicemanager.IDeviceService.DeviceField; +import org.projectfloodlight.openflow.types.DatapathId; +import org.projectfloodlight.openflow.types.IPv4Address; +import org.projectfloodlight.openflow.types.MacAddress; +import org.projectfloodlight.openflow.types.OFPort; +import org.projectfloodlight.openflow.types.VlanVid; +import net.floodlightcontroller.devicemanager.IDeviceService.DeviceField; import net.floodlightcontroller.devicemanager.SwitchPort; import com.fasterxml.jackson.annotation.JsonProperty; @@ -16,15 +21,15 @@ import com.fasterxml.jackson.annotation.JsonProperty; public class DeviceSyncRepresentation { public static class SyncEntity implements Comparable<SyncEntity> { @JsonProperty - public long macAddress; + public MacAddress macAddress; @JsonProperty - public Integer ipv4Address; + public IPv4Address ipv4Address; @JsonProperty - public Short vlan; + public VlanVid vlan; @JsonProperty - public Long switchDPID; + public DatapathId switchDPID; @JsonProperty - public Integer switchPort; + public OFPort switchPort; @JsonProperty public Date lastSeenTimestamp; @JsonProperty diff --git a/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceUniqueIndex.java b/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceUniqueIndex.java index 4811013e77258654c9577b0271a51f72c31bfd8d..e373018bff2bc02a02a41384ae58daadaf45cb0b 100644 --- a/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceUniqueIndex.java +++ b/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceUniqueIndex.java @@ -78,10 +78,11 @@ public class DeviceUniqueIndex extends DeviceIndex { } @Override - public void updateIndex(Entity entity, Long deviceKey) { + public boolean updateIndex(Entity entity, Long deviceKey) { IndexedEntity ie = new IndexedEntity(keyFields, entity); - if (!ie.hasNonNullKeys()) return; + if (!ie.hasNonNullKeys()) return false; index.put(ie, deviceKey); + return true; } @Override diff --git a/src/main/java/net/floodlightcontroller/devicemanager/internal/Entity.java b/src/main/java/net/floodlightcontroller/devicemanager/internal/Entity.java index db8d8e9fb3ff92256e0e9db55751e9345e6765fa..eda51cc1d45e5d1f713bb9a301d770a962ad2fdc 100644 --- a/src/main/java/net/floodlightcontroller/devicemanager/internal/Entity.java +++ b/src/main/java/net/floodlightcontroller/devicemanager/internal/Entity.java @@ -22,11 +22,15 @@ import java.util.Date; import net.floodlightcontroller.core.web.serializers.IPv4Serializer; import net.floodlightcontroller.core.web.serializers.MACSerializer; import net.floodlightcontroller.core.web.serializers.DPIDSerializer; -import net.floodlightcontroller.packet.IPv4; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import org.openflow.util.HexString; + +import org.projectfloodlight.openflow.types.DatapathId; +import org.projectfloodlight.openflow.types.IPv4Address; +import org.projectfloodlight.openflow.types.MacAddress; +import org.projectfloodlight.openflow.types.OFPort; +import org.projectfloodlight.openflow.types.VlanVid; /** * An entity on the network is a visible trace of a device that corresponds @@ -52,30 +56,30 @@ public class Entity implements Comparable<Entity> { /** * The MAC address associated with this entity */ - protected long macAddress; + protected MacAddress macAddress; /** * The IP address associated with this entity, or null if no IP learned * from the network observation associated with this entity */ - protected Integer ipv4Address; + protected IPv4Address ipv4Address; /** * The VLAN tag on this entity, or null if untagged */ - protected Short vlan; + protected VlanVid vlan; /** * The DPID of the switch for the ingress point for this entity, * or null if not present */ - protected Long switchDPID; + protected DatapathId switchDPID; /** * The port number of the switch for the ingress point for this entity, * or null if not present */ - protected Integer switchPort; + protected OFPort switchPort; /** * The last time we observed this entity on the network @@ -108,8 +112,8 @@ public class Entity implements Comparable<Entity> { * @param switchPort * @param lastSeenTimestamp */ - public Entity(long macAddress, Short vlan, - Integer ipv4Address, Long switchDPID, Integer switchPort, + public Entity(MacAddress macAddress, VlanVid vlan, + IPv4Address ipv4Address, DatapathId switchDPID, OFPort switchPort, Date lastSeenTimestamp) { this.macAddress = macAddress; this.ipv4Address = ipv4Address; @@ -125,25 +129,25 @@ public class Entity implements Comparable<Entity> { // *************** @JsonSerialize(using=MACSerializer.class) - public long getMacAddress() { + public MacAddress getMacAddress() { return macAddress; } @JsonSerialize(using=IPv4Serializer.class) - public Integer getIpv4Address() { + public IPv4Address getIpv4Address() { return ipv4Address; } - public Short getVlan() { + public VlanVid getVlan() { return vlan; } @JsonSerialize(using=DPIDSerializer.class) - public Long getSwitchDPID() { + public DatapathId getSwitchDPID() { return switchDPID; } - public Integer getSwitchPort() { + public OFPort getSwitchPort() { return switchPort; } @@ -163,9 +167,7 @@ public class Entity implements Comparable<Entity> { * @see {@link Entity#activeSince} */ public void setLastSeenTimestamp(Date lastSeenTimestamp) { - if (activeSince == null || - (activeSince.getTime() + ACTIVITY_TIMEOUT) < - lastSeenTimestamp.getTime()) + if (activeSince == null || (activeSince.getTime() + ACTIVITY_TIMEOUT) < lastSeenTimestamp.getTime()) this.activeSince = lastSeenTimestamp; this.lastSeenTimestamp = lastSeenTimestamp; } @@ -185,7 +187,7 @@ public class Entity implements Comparable<Entity> { hashCode = 1; hashCode = prime * hashCode + ((ipv4Address == null) ? 0 : ipv4Address.hashCode()); - hashCode = prime * hashCode + (int) (macAddress ^ (macAddress >>> 32)); + hashCode = prime * hashCode + (int) (macAddress.getLong() ^ (macAddress.getLong() >>> 32)); hashCode = prime * hashCode + ((switchDPID == null) ? 0 : switchDPID.hashCode()); hashCode = prime * hashCode @@ -204,7 +206,7 @@ public class Entity implements Comparable<Entity> { if (ipv4Address == null) { if (other.ipv4Address != null) return false; } else if (!ipv4Address.equals(other.ipv4Address)) return false; - if (macAddress != other.macAddress) return false; + if (!macAddress.equals(other.macAddress)) return false; if (switchDPID == null) { if (other.switchDPID != null) return false; } else if (!switchDPID.equals(other.switchDPID)) return false; @@ -223,28 +225,55 @@ public class Entity implements Comparable<Entity> { public String toString() { StringBuilder builder = new StringBuilder(); builder.append("Entity [macAddress="); - builder.append(HexString.toHexString(macAddress, 6)); + if (macAddress != null) { + builder.append(macAddress.toString()); + } else { + builder.append("null"); + } builder.append(", ipv4Address="); - builder.append(IPv4.fromIPv4Address(ipv4Address==null ? - 0 : ipv4Address.intValue())); + if (ipv4Address != null) { + builder.append(ipv4Address.toString()); + } else { + builder.append("null"); + } builder.append(", vlan="); - builder.append(vlan); + if (vlan != null) { + builder.append(vlan.getVlan()); + } else { + builder.append("null"); + } builder.append(", switchDPID="); - builder.append(switchDPID); + if (switchDPID != null) { + builder.append(switchDPID.toString()); + } else { + builder.append("null"); + } builder.append(", switchPort="); - builder.append(switchPort); + if (switchPort != null) { + builder.append(switchPort.getPortNumber()); + } else { + builder.append("null"); + } builder.append(", lastSeenTimestamp="); - builder.append(lastSeenTimestamp == null? "null" : lastSeenTimestamp.getTime()); + if (lastSeenTimestamp != null) { + builder.append(lastSeenTimestamp == null? "null" : lastSeenTimestamp.getTime()); + } else { + builder.append("null"); + } builder.append(", activeSince="); - builder.append(activeSince == null? "null" : activeSince.getTime()); + if (activeSince != null) { + builder.append(activeSince == null? "null" : activeSince.getTime()); + } else { + builder.append("null"); + } builder.append("]"); return builder.toString(); } @Override public int compareTo(Entity o) { - if (macAddress < o.macAddress) return -1; - if (macAddress > o.macAddress) return 1; + if (macAddress.getLong() < o.macAddress.getLong()) return -1; + if (macAddress.getLong() > o.macAddress.getLong()) return 1; int r; if (switchDPID == null) diff --git a/src/main/java/net/floodlightcontroller/devicemanager/internal/IndexedEntity.java b/src/main/java/net/floodlightcontroller/devicemanager/internal/IndexedEntity.java index 13a78a49accfa001c51fb3260325179abc362a46..6fb1dda1305e4e35e86cf95131a2edf17f843895 100644 --- a/src/main/java/net/floodlightcontroller/devicemanager/internal/IndexedEntity.java +++ b/src/main/java/net/floodlightcontroller/devicemanager/internal/IndexedEntity.java @@ -89,8 +89,8 @@ public class IndexedEntity { switch (f) { case MAC: hashCode = prime * hashCode - + (int) (entity.macAddress ^ - (entity.macAddress >>> 32)); + + (int) (entity.macAddress.getLong() ^ + (entity.macAddress.getLong() >>> 32)); break; case IPV4: hashCode = prime * hashCode @@ -134,32 +134,28 @@ public class IndexedEntity { for (IDeviceService.DeviceField f : keyFields) { switch (f) { case MAC: - if (entity.macAddress != other.entity.macAddress) + if (!entity.macAddress.equals(other.entity.macAddress)) return false; break; case IPV4: if (entity.ipv4Address == null) { if (other.entity.ipv4Address != null) return false; - } else if (!entity.ipv4Address. - equals(other.entity.ipv4Address)) return false; + } else if (!entity.ipv4Address.equals(other.entity.ipv4Address)) return false; break; case SWITCH: if (entity.switchDPID == null) { if (other.entity.switchDPID != null) return false; - } else if (!entity.switchDPID. - equals(other.entity.switchDPID)) return false; + } else if (!entity.switchDPID.equals(other.entity.switchDPID)) return false; break; case PORT: if (entity.switchPort == null) { if (other.entity.switchPort != null) return false; - } else if (!entity.switchPort. - equals(other.entity.switchPort)) return false; + } else if (!entity.switchPort.equals(other.entity.switchPort)) return false; break; case VLAN: if (entity.vlan == null) { if (other.entity.vlan != null) return false; - } else if (!entity.vlan. - equals(other.entity.vlan)) return false; + } else if (!entity.vlan.equals(other.entity.vlan)) return false; break; } } diff --git a/src/main/java/net/floodlightcontroller/devicemanager/web/AbstractDeviceResource.java b/src/main/java/net/floodlightcontroller/devicemanager/web/AbstractDeviceResource.java index 7866b7914a57f0e7e91f6e2b2283a12c3df64d46..512e4912babe760a3dbf44049465fd4424f0e96e 100644 --- a/src/main/java/net/floodlightcontroller/devicemanager/web/AbstractDeviceResource.java +++ b/src/main/java/net/floodlightcontroller/devicemanager/web/AbstractDeviceResource.java @@ -23,10 +23,13 @@ import net.floodlightcontroller.devicemanager.IDevice; import net.floodlightcontroller.devicemanager.IDeviceService; import net.floodlightcontroller.devicemanager.SwitchPort; import net.floodlightcontroller.devicemanager.internal.Device; -import net.floodlightcontroller.packet.IPv4; import net.floodlightcontroller.util.FilterIterator; -import org.openflow.util.HexString; +import org.projectfloodlight.openflow.types.DatapathId; +import org.projectfloodlight.openflow.types.IPv4Address; +import org.projectfloodlight.openflow.types.MacAddress; +import org.projectfloodlight.openflow.types.OFPort; +import org.projectfloodlight.openflow.types.VlanVid; import org.restlet.data.Form; import org.restlet.data.Status; import org.restlet.resource.ServerResource; @@ -54,11 +57,11 @@ public abstract class AbstractDeviceResource extends ServerResource { (IDeviceService)getContext().getAttributes(). get(IDeviceService.class.getCanonicalName()); - Long macAddress = null; - Short vlan = null; - Integer ipv4Address = null; - Long switchDPID = null; - Integer switchPort = null; + MacAddress macAddress = null; + VlanVid vlan = null; + IPv4Address ipv4Address = null; + DatapathId switchDPID = null; + OFPort switchPort = null; Form form = getQuery(); String macAddrStr = form.getFirstValue("mac", true); @@ -69,7 +72,7 @@ public abstract class AbstractDeviceResource extends ServerResource { if (macAddrStr != null) { try { - macAddress = HexString.toLong(macAddrStr); + macAddress = MacAddress.of(macAddrStr); } catch (Exception e) { setStatus(Status.CLIENT_ERROR_BAD_REQUEST, MAC_ERROR); return null; @@ -77,8 +80,8 @@ public abstract class AbstractDeviceResource extends ServerResource { } if (vlanStr != null) { try { - vlan = Short.parseShort(vlanStr); - if (vlan > 4095 || vlan < 0) { + vlan = VlanVid.ofVlan(Integer.parseInt(vlanStr)); + if (vlan.getVlan() > 4095 || vlan.getVlan() < 0) { setStatus(Status.CLIENT_ERROR_BAD_REQUEST, VLAN_ERROR); return null; } @@ -89,7 +92,7 @@ public abstract class AbstractDeviceResource extends ServerResource { } if (ipv4Str != null) { try { - ipv4Address = IPv4.toIPv4Address(ipv4Str); + ipv4Address = IPv4Address.of(ipv4Str); } catch (Exception e) { setStatus(Status.CLIENT_ERROR_BAD_REQUEST, IPV4_ERROR); return null; @@ -97,7 +100,7 @@ public abstract class AbstractDeviceResource extends ServerResource { } if (dpid != null) { try { - switchDPID = HexString.toLong(dpid); + switchDPID = DatapathId.of(dpid); } catch (Exception e) { setStatus(Status.CLIENT_ERROR_BAD_REQUEST, DPID_ERROR); return null; @@ -105,8 +108,8 @@ public abstract class AbstractDeviceResource extends ServerResource { } if (port != null) { try { - switchPort = Integer.parseInt(port); - if (switchPort < 0) { + switchPort = OFPort.of(Integer.parseInt(port)); + if (switchPort.getPortNumber() < 0) { setStatus(Status.CLIENT_ERROR_BAD_REQUEST, PORT_ERROR); return null; } @@ -144,7 +147,7 @@ public abstract class AbstractDeviceResource extends ServerResource { } if (vlanStartsWith != null) { boolean match = false; - for (Short v : value.getVlanId()) { + for (VlanVid v : value.getVlanId()) { if (v != null && v.toString().startsWith(vlanStartsWith)) { match = true; @@ -155,10 +158,10 @@ public abstract class AbstractDeviceResource extends ServerResource { } if (ipv4StartsWith != null) { boolean match = false; - for (Integer v : value.getIPv4Addresses()) { + for (IPv4Address v : value.getIPv4Addresses()) { String str; if (v != null && - (str = IPv4.fromIPv4Address(v)) != null && + (str = v.toString()) != null && str.startsWith(ipv4StartsWith)) { match = true; break; @@ -171,8 +174,7 @@ public abstract class AbstractDeviceResource extends ServerResource { for (SwitchPort v : value.getAttachmentPoints(true)) { String str; if (v != null && - (str = HexString.toHexString(v.getSwitchDPID(), - 8)) != null && + (str = v.getSwitchDPID().toString()) != null && str.startsWith(dpidStartsWith)) { match = true; break; @@ -185,7 +187,7 @@ public abstract class AbstractDeviceResource extends ServerResource { for (SwitchPort v : value.getAttachmentPoints(true)) { String str; if (v != null && - (str = Integer.toString(v.getPort())) != null && + (str = v.getPort().toString()) != null && str.startsWith(portStartsWith)) { match = true; break; diff --git a/src/main/java/net/floodlightcontroller/devicemanager/web/DeviceSerializer.java b/src/main/java/net/floodlightcontroller/devicemanager/web/DeviceSerializer.java index 26b2027aaa34ece55d1541a3f60bb058d27e74e3..0cd43e245ac822fcb46a88cbf5d8cf59c3a29d0f 100644 --- a/src/main/java/net/floodlightcontroller/devicemanager/web/DeviceSerializer.java +++ b/src/main/java/net/floodlightcontroller/devicemanager/web/DeviceSerializer.java @@ -19,15 +19,16 @@ package net.floodlightcontroller.devicemanager.web; import java.io.IOException; +import org.projectfloodlight.openflow.types.IPv4Address; +import org.projectfloodlight.openflow.types.VlanVid; + import net.floodlightcontroller.devicemanager.SwitchPort; import net.floodlightcontroller.devicemanager.internal.Device; -import net.floodlightcontroller.packet.IPv4; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; -import org.openflow.util.HexString; /** * Serialize a device object @@ -43,18 +44,18 @@ public class DeviceSerializer extends JsonSerializer<Device> { jGen.writeStringField("entityClass", device.getEntityClass().getName()); jGen.writeArrayFieldStart("mac"); - jGen.writeString(HexString.toHexString(device.getMACAddress(), 6)); + jGen.writeString(device.getMACAddress().toString()); jGen.writeEndArray(); jGen.writeArrayFieldStart("ipv4"); - for (Integer ip : device.getIPv4Addresses()) - jGen.writeString(IPv4.fromIPv4Address(ip)); + for (IPv4Address ip : device.getIPv4Addresses()) + jGen.writeString(ip.toString()); jGen.writeEndArray(); jGen.writeArrayFieldStart("vlan"); - for (Short vlan : device.getVlanId()) - if (vlan >= 0) - jGen.writeNumber(vlan); + for (VlanVid vlan : device.getVlanId()) + if (vlan.getVlan() >= 0) + jGen.writeNumber(vlan.toString()); jGen.writeEndArray(); jGen.writeArrayFieldStart("attachmentPoint"); for (SwitchPort ap : device.getAttachmentPoints(true)) { diff --git a/src/main/java/net/floodlightcontroller/firewall/WildcardsPair.java b/src/main/java/net/floodlightcontroller/firewall/AllowDropPair.java similarity index 75% rename from src/main/java/net/floodlightcontroller/firewall/WildcardsPair.java rename to src/main/java/net/floodlightcontroller/firewall/AllowDropPair.java index db4cf7441a11de5931a7b9b2db4205a9d66c54e6..719e067b9633eb4145dafb23ee1d725c424bd9bc 100644 --- a/src/main/java/net/floodlightcontroller/firewall/WildcardsPair.java +++ b/src/main/java/net/floodlightcontroller/firewall/AllowDropPair.java @@ -17,9 +17,12 @@ package net.floodlightcontroller.firewall; -import org.openflow.protocol.OFMatch; +import org.projectfloodlight.openflow.protocol.match.Match; -public class WildcardsPair { - public int allow = OFMatch.OFPFW_ALL; - public int drop = OFMatch.OFPFW_ALL; + +public class AllowDropPair { + //public int allow = OFMatch.OFPFW_ALL; + //public int drop = OFMatch.OFPFW_ALL; + public Match.Builder allow; + public Match.Builder drop; } diff --git a/src/main/java/net/floodlightcontroller/firewall/Firewall.java b/src/main/java/net/floodlightcontroller/firewall/Firewall.java index 21dc8aed6ca937f3123e9a00971ee8b0fc13fd80..e95a7d4e47b10fd662b414f62ae2c2d61d0cb843 100644 --- a/src/main/java/net/floodlightcontroller/firewall/Firewall.java +++ b/src/main/java/net/floodlightcontroller/firewall/Firewall.java @@ -24,9 +24,16 @@ import java.util.Iterator; import java.util.List; import java.util.Map; -import org.openflow.protocol.OFMessage; -import org.openflow.protocol.OFPacketIn; -import org.openflow.protocol.OFType; +import org.projectfloodlight.openflow.protocol.OFMessage; +import org.projectfloodlight.openflow.protocol.OFPacketIn; +import org.projectfloodlight.openflow.protocol.OFType; +import org.projectfloodlight.openflow.types.DatapathId; +import org.projectfloodlight.openflow.types.EthType; +import org.projectfloodlight.openflow.types.IPv4AddressWithMask; +import org.projectfloodlight.openflow.types.IpProtocol; +import org.projectfloodlight.openflow.types.MacAddress; +import org.projectfloodlight.openflow.types.OFPort; +import org.projectfloodlight.openflow.types.TransportPort; import net.floodlightcontroller.core.FloodlightContext; import net.floodlightcontroller.core.IOFMessageListener; @@ -35,11 +42,11 @@ 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.IFloodlightProviderService; import net.floodlightcontroller.devicemanager.IDeviceService; import java.util.ArrayList; + import net.floodlightcontroller.packet.Ethernet; import net.floodlightcontroller.packet.IPv4; import net.floodlightcontroller.restserver.IRestApiService; @@ -88,15 +95,15 @@ public class Firewall implements IFirewallService, IOFMessageListener, public static final String COLUMN_TP_SRC = "tp_src"; public static final String COLUMN_TP_DST = "tp_dst"; public static final String COLUMN_WILDCARD_DPID = "wildcard_dpid"; - public static final String COLUMN_WILDCARD_IN_PORT = "wildcard_in_port"; - public static final String COLUMN_WILDCARD_DL_SRC = "wildcard_dl_src"; - public static final String COLUMN_WILDCARD_DL_DST = "wildcard_dl_dst"; - public static final String COLUMN_WILDCARD_DL_TYPE = "wildcard_dl_type"; - public static final String COLUMN_WILDCARD_NW_SRC = "wildcard_nw_src"; - public static final String COLUMN_WILDCARD_NW_DST = "wildcard_nw_dst"; - public static final String COLUMN_WILDCARD_NW_PROTO = "wildcard_nw_proto"; - public static final String COLUMN_WILDCARD_TP_SRC = "wildcard_tp_src"; - public static final String COLUMN_WILDCARD_TP_DST = "wildcard_tp_dst"; + public static final String COLUMN_WILDCARD_IN_PORT = "any_in_port"; + public static final String COLUMN_WILDCARD_DL_SRC = "any_dl_src"; + public static final String COLUMN_WILDCARD_DL_DST = "any_dl_dst"; + public static final String COLUMN_WILDCARD_DL_TYPE = "any_dl_type"; + public static final String COLUMN_WILDCARD_NW_SRC = "any_nw_src"; + public static final String COLUMN_WILDCARD_NW_DST = "any_nw_dst"; + public static final String COLUMN_WILDCARD_NW_PROTO = "any_nw_proto"; + public static final String COLUMN_WILDCARD_TP_SRC = "any_tp_src"; + public static final String COLUMN_WILDCARD_TP_DST = "any_tp_dst"; public static final String COLUMN_PRIORITY = "priority"; public static final String COLUMN_ACTION = "action"; public static String ColumnNames[] = { COLUMN_RULEID, COLUMN_DPID, @@ -166,161 +173,86 @@ public class Firewall implements IFirewallService, IOFMessageListener, Map<String, Object> row; // (..., null, null) for no predicate, no ordering - IResultSet resultSet = storageSource.executeQuery(TABLE_NAME, - ColumnNames, null, null); + IResultSet resultSet = storageSource.executeQuery(TABLE_NAME, ColumnNames, null, null); // put retrieved rows into FirewallRules for (Iterator<IResultSet> it = resultSet.iterator(); it.hasNext();) { row = it.next().getRow(); // now, parse row FirewallRule r = new FirewallRule(); - if (!row.containsKey(COLUMN_RULEID) - || !row.containsKey(COLUMN_DPID)) { - logger.error( - "skipping entry with missing required 'ruleid' or 'switchid' entry: {}", - row); + if (!row.containsKey(COLUMN_RULEID) || !row.containsKey(COLUMN_DPID)) { + logger.error( "skipping entry with missing required 'ruleid' or 'switchid' entry: {}", row); return l; } try { r.ruleid = Integer .parseInt((String) row.get(COLUMN_RULEID)); - r.dpid = Long.parseLong((String) row.get(COLUMN_DPID)); + r.dpid = DatapathId.of((String) row.get(COLUMN_DPID)); for (String key : row.keySet()) { - if (row.get(key) == null) + if (row.get(key) == null) { continue; - if (key.equals(COLUMN_RULEID) - || key.equals(COLUMN_DPID) - || key.equals("id")) { + } + if (key.equals(COLUMN_RULEID) || key.equals(COLUMN_DPID) || key.equals("id")) { continue; // already handled - } - - else if (key.equals(COLUMN_IN_PORT)) { - r.in_port = Short.parseShort((String) row - .get(COLUMN_IN_PORT)); - } - - else if (key.equals(COLUMN_DL_SRC)) { - r.dl_src = Long.parseLong((String) row - .get(COLUMN_DL_SRC)); - } - - else if (key.equals(COLUMN_DL_DST)) { - r.dl_dst = Long.parseLong((String) row - .get(COLUMN_DL_DST)); - } - - else if (key.equals(COLUMN_DL_TYPE)) { - r.dl_type = Short.parseShort((String) row - .get(COLUMN_DL_TYPE)); - } - - else if (key.equals(COLUMN_NW_SRC_PREFIX)) { - r.nw_src_prefix = Integer.parseInt((String) row - .get(COLUMN_NW_SRC_PREFIX)); - } - - else if (key.equals(COLUMN_NW_SRC_MASKBITS)) { - r.nw_src_maskbits = Integer.parseInt((String) row - .get(COLUMN_NW_SRC_MASKBITS)); - } - - else if (key.equals(COLUMN_NW_DST_PREFIX)) { - r.nw_dst_prefix = Integer.parseInt((String) row - .get(COLUMN_NW_DST_PREFIX)); - } - - else if (key.equals(COLUMN_NW_DST_MASKBITS)) { - r.nw_dst_maskbits = Integer.parseInt((String) row - .get(COLUMN_NW_DST_MASKBITS)); - } - - else if (key.equals(COLUMN_NW_PROTO)) { - r.nw_proto = Short.parseShort((String) row - .get(COLUMN_NW_PROTO)); - } - - else if (key.equals(COLUMN_TP_SRC)) { - r.tp_src = Short.parseShort((String) row - .get(COLUMN_TP_SRC)); - } - - else if (key.equals(COLUMN_TP_DST)) { - r.tp_dst = Short.parseShort((String) row - .get(COLUMN_TP_DST)); - } - - else if (key.equals(COLUMN_WILDCARD_DPID)) { - r.wildcard_dpid = Boolean.parseBoolean((String) row - .get(COLUMN_WILDCARD_DPID)); - } - - else if (key.equals(COLUMN_WILDCARD_IN_PORT)) { - r.wildcard_in_port = Boolean - .parseBoolean((String) row - .get(COLUMN_WILDCARD_IN_PORT)); - } - - else if (key.equals(COLUMN_WILDCARD_DL_SRC)) { - r.wildcard_dl_src = Boolean - .parseBoolean((String) row - .get(COLUMN_WILDCARD_DL_SRC)); - } - - else if (key.equals(COLUMN_WILDCARD_DL_DST)) { - r.wildcard_dl_dst = Boolean - .parseBoolean((String) row - .get(COLUMN_WILDCARD_DL_DST)); - } - - else if (key.equals(COLUMN_WILDCARD_DL_TYPE)) { - r.wildcard_dl_type = Boolean - .parseBoolean((String) row - .get(COLUMN_WILDCARD_DL_TYPE)); - } - - else if (key.equals(COLUMN_WILDCARD_NW_SRC)) { - r.wildcard_nw_src = Boolean - .parseBoolean((String) row - .get(COLUMN_WILDCARD_NW_SRC)); - } - - else if (key.equals(COLUMN_WILDCARD_NW_DST)) { - r.wildcard_nw_dst = Boolean - .parseBoolean((String) row - .get(COLUMN_WILDCARD_NW_DST)); - } - - else if (key.equals(COLUMN_WILDCARD_NW_PROTO)) { - r.wildcard_nw_proto = Boolean - .parseBoolean((String) row - .get(COLUMN_WILDCARD_NW_PROTO)); - } - - else if (key.equals(COLUMN_PRIORITY)) { - r.priority = Integer.parseInt((String) row - .get(COLUMN_PRIORITY)); - } - - else if (key.equals(COLUMN_ACTION)) { + } else if (key.equals(COLUMN_IN_PORT)) { + r.in_port = OFPort.of(Integer.parseInt((String) row.get(COLUMN_IN_PORT))); + } else if (key.equals(COLUMN_DL_SRC)) { + r.dl_src = MacAddress.of(Long.parseLong((String) row.get(COLUMN_DL_SRC))); + } else if (key.equals(COLUMN_DL_DST)) { + r.dl_dst = MacAddress.of(Long.parseLong((String) row.get(COLUMN_DL_DST))); + } else if (key.equals(COLUMN_DL_TYPE)) { + r.dl_type = EthType.of(Integer.parseInt((String) row.get(COLUMN_DL_TYPE))); + } else if (key.equals(COLUMN_NW_SRC_PREFIX)) { + r.nw_src_prefix_and_mask = IPv4AddressWithMask.of(Integer.parseInt((String) row.get(COLUMN_NW_SRC_PREFIX)), r.nw_src_prefix_and_mask.getMask().getInt()); + } else if (key.equals(COLUMN_NW_SRC_MASKBITS)) { + r.nw_src_prefix_and_mask = IPv4AddressWithMask.of(r.nw_src_prefix_and_mask.getValue().getInt(), Integer.parseInt((String) row.get(COLUMN_NW_SRC_MASKBITS))); + } else if (key.equals(COLUMN_NW_DST_PREFIX)) { + r.nw_dst_prefix_and_mask = IPv4AddressWithMask.of(Integer.parseInt((String) row.get(COLUMN_NW_DST_PREFIX)), r.nw_dst_prefix_and_mask.getMask().getInt()); + } else if (key.equals(COLUMN_NW_DST_MASKBITS)) { + r.nw_dst_prefix_and_mask = IPv4AddressWithMask.of(r.nw_dst_prefix_and_mask.getValue().getInt(), Integer.parseInt((String) row.get(COLUMN_NW_DST_MASKBITS))); + } else if (key.equals(COLUMN_NW_PROTO)) { + r.nw_proto = IpProtocol.of(Short.parseShort((String) row.get(COLUMN_NW_PROTO))); + } else if (key.equals(COLUMN_TP_SRC)) { + r.tp_src = TransportPort.of(Integer.parseInt((String) row.get(COLUMN_TP_SRC))); + } else if (key.equals(COLUMN_TP_DST)) { + r.tp_dst = TransportPort.of(Integer.parseInt((String) row.get(COLUMN_TP_DST))); + } else if (key.equals(COLUMN_WILDCARD_DPID)) { + r.any_dpid = Boolean.parseBoolean((String) row.get(COLUMN_WILDCARD_DPID)); + } else if (key.equals(COLUMN_WILDCARD_IN_PORT)) { + r.any_in_port = Boolean.parseBoolean((String) row.get(COLUMN_WILDCARD_IN_PORT)); + } else if (key.equals(COLUMN_WILDCARD_DL_SRC)) { + r.any_dl_src = Boolean.parseBoolean((String) row.get(COLUMN_WILDCARD_DL_SRC)); + } else if (key.equals(COLUMN_WILDCARD_DL_DST)) { + r.any_dl_dst = Boolean.parseBoolean((String) row.get(COLUMN_WILDCARD_DL_DST)); + } else if (key.equals(COLUMN_WILDCARD_DL_TYPE)) { + r.any_dl_type = Boolean.parseBoolean((String) row.get(COLUMN_WILDCARD_DL_TYPE)); + } else if (key.equals(COLUMN_WILDCARD_NW_SRC)) { + r.any_nw_src = Boolean.parseBoolean((String) row.get(COLUMN_WILDCARD_NW_SRC)); + } else if (key.equals(COLUMN_WILDCARD_NW_DST)) { + r.any_nw_dst = Boolean.parseBoolean((String) row.get(COLUMN_WILDCARD_NW_DST)); + } else if (key.equals(COLUMN_WILDCARD_NW_PROTO)) { + r.any_nw_proto = Boolean.parseBoolean((String) row.get(COLUMN_WILDCARD_NW_PROTO)); + } else if (key.equals(COLUMN_PRIORITY)) { + r.priority = Integer.parseInt((String) row.get(COLUMN_PRIORITY)); + } else if (key.equals(COLUMN_ACTION)) { int tmp = Integer.parseInt((String) row.get(COLUMN_ACTION)); - if (tmp == FirewallRule.FirewallAction.DENY.ordinal()) - r.action = FirewallRule.FirewallAction.DENY; - else if (tmp == FirewallRule.FirewallAction.ALLOW.ordinal()) + if (tmp == FirewallRule.FirewallAction.DROP.ordinal()) { + r.action = FirewallRule.FirewallAction.DROP; + } else if (tmp == FirewallRule.FirewallAction.ALLOW.ordinal()) { r.action = FirewallRule.FirewallAction.ALLOW; - else { + } else { r.action = null; logger.error("action not recognized"); } } } } catch (ClassCastException e) { - logger.error( - "skipping rule {} with bad data : " - + e.getMessage(), r.ruleid); + logger.error("skipping rule {} with bad data : " + e.getMessage(), r.ruleid); } - if (r.action != null) + if (r.action != null) { l.add(r); + } } } catch (StorageException e) { logger.error("failed to access storage: {}", e.getMessage()); @@ -335,10 +267,8 @@ public class Firewall implements IFirewallService, IOFMessageListener, } @Override - public void init(FloodlightModuleContext context) - throws FloodlightModuleException { - floodlightProvider = context - .getServiceImpl(IFloodlightProviderService.class); + public void init(FloodlightModuleContext context) throws FloodlightModuleException { + floodlightProvider = context.getServiceImpl(IFloodlightProviderService.class); storageSource = context.getServiceImpl(IStorageSourceService.class); restApi = context.getServiceImpl(IRestApiService.class); rules = new ArrayList<FirewallRule>(); @@ -364,18 +294,16 @@ public class Firewall implements IFirewallService, IOFMessageListener, @Override public Command receive(IOFSwitch sw, OFMessage msg, FloodlightContext cntx) { - if (!this.enabled) + if (!this.enabled) { return Command.CONTINUE; + } switch (msg.getType()) { case PACKET_IN: IRoutingDecision decision = null; if (cntx != null) { - decision = IRoutingDecision.rtStore.get(cntx, - IRoutingDecision.CONTEXT_DECISION); - - return this.processPacketInMessage(sw, (OFPacketIn) msg, - decision, cntx); + decision = IRoutingDecision.rtStore.get(cntx, IRoutingDecision.CONTEXT_DECISION); + return this.processPacketInMessage(sw, (OFPacketIn) msg, decision, cntx); } break; default: @@ -404,8 +332,7 @@ public class Firewall implements IFirewallService, IOFMessageListener, ArrayList<Map<String, Object>> l = new ArrayList<Map<String, Object>>(); try { // null1=no predicate, null2=no ordering - IResultSet resultSet = storageSource.executeQuery(TABLE_NAME, - ColumnNames, null, null); + IResultSet resultSet = storageSource.executeQuery(TABLE_NAME, ColumnNames, null, null); for (Iterator<IResultSet> it = resultSet.iterator(); it.hasNext();) { l.add(it.next().getRow()); } @@ -452,38 +379,28 @@ public class Firewall implements IFirewallService, IOFMessageListener, // add rule to database Map<String, Object> entry = new HashMap<String, Object>(); entry.put(COLUMN_RULEID, Integer.toString(rule.ruleid)); - entry.put(COLUMN_DPID, Long.toString(rule.dpid)); - entry.put(COLUMN_IN_PORT, Short.toString(rule.in_port)); - entry.put(COLUMN_DL_SRC, Long.toString(rule.dl_src)); - entry.put(COLUMN_DL_DST, Long.toString(rule.dl_dst)); - entry.put(COLUMN_DL_TYPE, Short.toString(rule.dl_type)); - entry.put(COLUMN_NW_SRC_PREFIX, Integer.toString(rule.nw_src_prefix)); - entry.put(COLUMN_NW_SRC_MASKBITS, Integer.toString(rule.nw_src_maskbits)); - entry.put(COLUMN_NW_DST_PREFIX, Integer.toString(rule.nw_dst_prefix)); - entry.put(COLUMN_NW_DST_MASKBITS, Integer.toString(rule.nw_dst_maskbits)); - entry.put(COLUMN_NW_PROTO, Short.toString(rule.nw_proto)); - entry.put(COLUMN_TP_SRC, Integer.toString(rule.tp_src)); - entry.put(COLUMN_TP_DST, Integer.toString(rule.tp_dst)); - entry.put(COLUMN_WILDCARD_DPID, - Boolean.toString(rule.wildcard_dpid)); - entry.put(COLUMN_WILDCARD_IN_PORT, - Boolean.toString(rule.wildcard_in_port)); - entry.put(COLUMN_WILDCARD_DL_SRC, - Boolean.toString(rule.wildcard_dl_src)); - entry.put(COLUMN_WILDCARD_DL_DST, - Boolean.toString(rule.wildcard_dl_dst)); - entry.put(COLUMN_WILDCARD_DL_TYPE, - Boolean.toString(rule.wildcard_dl_type)); - entry.put(COLUMN_WILDCARD_NW_SRC, - Boolean.toString(rule.wildcard_nw_src)); - entry.put(COLUMN_WILDCARD_NW_DST, - Boolean.toString(rule.wildcard_nw_dst)); - entry.put(COLUMN_WILDCARD_NW_PROTO, - Boolean.toString(rule.wildcard_nw_proto)); - entry.put(COLUMN_WILDCARD_TP_SRC, - Boolean.toString(rule.wildcard_tp_src)); - entry.put(COLUMN_WILDCARD_TP_DST, - Boolean.toString(rule.wildcard_tp_dst)); + entry.put(COLUMN_DPID, Long.toString(rule.dpid.getLong())); + entry.put(COLUMN_IN_PORT, Integer.toString(rule.in_port.getPortNumber())); + entry.put(COLUMN_DL_SRC, Long.toString(rule.dl_src.getLong())); + entry.put(COLUMN_DL_DST, Long.toString(rule.dl_dst.getLong())); + entry.put(COLUMN_DL_TYPE, Integer.toString(rule.dl_type.getValue())); + entry.put(COLUMN_NW_SRC_PREFIX, Integer.toString(rule.nw_src_prefix_and_mask.getValue().getInt())); + entry.put(COLUMN_NW_SRC_MASKBITS, Integer.toString(rule.nw_src_prefix_and_mask.getMask().getInt())); + entry.put(COLUMN_NW_DST_PREFIX, Integer.toString(rule.nw_dst_prefix_and_mask.getValue().getInt())); + entry.put(COLUMN_NW_DST_MASKBITS, Integer.toString(rule.nw_dst_prefix_and_mask.getMask().getInt())); + entry.put(COLUMN_NW_PROTO, Short.toString(rule.nw_proto.getIpProtocolNumber())); + entry.put(COLUMN_TP_SRC, Integer.toString(rule.tp_src.getPort())); + entry.put(COLUMN_TP_DST, Integer.toString(rule.tp_dst.getPort())); + entry.put(COLUMN_WILDCARD_DPID, Boolean.toString(rule.any_dpid)); + entry.put(COLUMN_WILDCARD_IN_PORT, Boolean.toString(rule.any_in_port)); + entry.put(COLUMN_WILDCARD_DL_SRC, Boolean.toString(rule.any_dl_src)); + entry.put(COLUMN_WILDCARD_DL_DST, Boolean.toString(rule.any_dl_dst)); + entry.put(COLUMN_WILDCARD_DL_TYPE, Boolean.toString(rule.any_dl_type)); + entry.put(COLUMN_WILDCARD_NW_SRC, Boolean.toString(rule.any_nw_src)); + entry.put(COLUMN_WILDCARD_NW_DST, Boolean.toString(rule.any_nw_dst)); + entry.put(COLUMN_WILDCARD_NW_PROTO, Boolean.toString(rule.any_nw_proto)); + entry.put(COLUMN_WILDCARD_TP_SRC, Boolean.toString(rule.any_tp_src)); + entry.put(COLUMN_WILDCARD_TP_DST, Boolean.toString(rule.any_tp_dst)); entry.put(COLUMN_PRIORITY, Integer.toString(rule.priority)); entry.put(COLUMN_ACTION, Integer.toString(rule.action.ordinal())); storageSource.insertRow(TABLE_NAME, entry); @@ -535,12 +452,10 @@ public class Firewall implements IFirewallService, IOFMessageListener, * @return an instance of RuleWildcardsPair that specify rule that matches * and the wildcards for the firewall decision */ - protected RuleWildcardsPair matchWithRule(IOFSwitch sw, OFPacketIn pi, - FloodlightContext cntx) { + protected RuleMatchPair matchWithRule(IOFSwitch sw, OFPacketIn pi, FloodlightContext cntx) { FirewallRule matched_rule = null; - Ethernet eth = IFloodlightProviderService.bcStore.get(cntx, - IFloodlightProviderService.CONTEXT_PI_PAYLOAD); - WildcardsPair wildcards = new WildcardsPair(); + Ethernet eth = IFloodlightProviderService.bcStore.get(cntx, IFloodlightProviderService.CONTEXT_PI_PAYLOAD); + AllowDropPair adp = new AllowDropPair(); synchronized (rules) { Iterator<FirewallRule> iter = this.rules.iterator(); @@ -551,7 +466,9 @@ public class Firewall implements IFirewallService, IOFMessageListener, rule = iter.next(); // check if rule matches - if (rule.matchesFlow(sw.getId(), pi.getInPort(), eth, wildcards) == true) { + // AllowDropPair adp's allow and drop matches will modified with what matches + // TODO @Ryan might need to re-init adp each time (look into later) + if (rule.matchesThisPacket(sw.getId(), pi.getInPort(), eth, adp) == true) { matched_rule = rule; break; } @@ -559,14 +476,14 @@ public class Firewall implements IFirewallService, IOFMessageListener, } // make a pair of rule and wildcards, then return it - RuleWildcardsPair ret = new RuleWildcardsPair(); - ret.rule = matched_rule; - if (matched_rule == null || matched_rule.action == FirewallRule.FirewallAction.DENY) { - ret.wildcards = wildcards.drop; + RuleMatchPair rmp = new RuleMatchPair(); + rmp.rule = matched_rule; + if (matched_rule == null || matched_rule.action == FirewallRule.FirewallAction.DROP) { + rmp.match = adp.drop.build(); } else { - ret.wildcards = wildcards.allow; + rmp.match = adp.allow.build(); } - return ret; + return rmp; } /** @@ -583,42 +500,34 @@ public class Firewall implements IFirewallService, IOFMessageListener, return ((IPAddress & inv_subnet_mask) == inv_subnet_mask); } - public Command processPacketInMessage(IOFSwitch sw, OFPacketIn pi, - IRoutingDecision decision, FloodlightContext cntx) { - Ethernet eth = IFloodlightProviderService.bcStore.get(cntx, - IFloodlightProviderService.CONTEXT_PI_PAYLOAD); + public Command processPacketInMessage(IOFSwitch sw, OFPacketIn pi, IRoutingDecision decision, FloodlightContext cntx) { + Ethernet eth = IFloodlightProviderService.bcStore.get(cntx, IFloodlightProviderService.CONTEXT_PI_PAYLOAD); // Allowing L2 broadcast + ARP broadcast request (also deny malformed // broadcasts -> L2 broadcast + L3 unicast) if (eth.isBroadcast() == true) { boolean allowBroadcast = true; // the case to determine if we have L2 broadcast + L3 unicast - // don't allow this broadcast packet if such is the case (malformed - // packet) - if ((eth.getPayload() instanceof IPv4) - && this.IPIsBroadcast(((IPv4) eth.getPayload()) - .getDestinationAddress()) == false) { + // don't allow this broadcast packet if such is the case (malformed packet) + if ((eth.getPayload() instanceof IPv4) && (((IPv4) eth.getPayload()).getDestinationAddress().isBroadcast() == false)) { allowBroadcast = false; } if (allowBroadcast == true) { - if (logger.isTraceEnabled()) - logger.trace("Allowing broadcast traffic for PacketIn={}", - pi); + if (logger.isTraceEnabled()) { + logger.trace("Allowing broadcast traffic for PacketIn={}", pi); + } - decision = new RoutingDecision(sw.getId(), pi.getInPort() - , IDeviceService.fcStore. - get(cntx, IDeviceService.CONTEXT_SRC_DEVICE), + decision = new RoutingDecision(sw.getId(), pi.getInPort(), + IDeviceService.fcStore.get(cntx, IDeviceService.CONTEXT_SRC_DEVICE), IRoutingDecision.RoutingAction.MULTICAST); decision.addToContext(cntx); } else { - if (logger.isTraceEnabled()) - logger.trace( - "Blocking malformed broadcast traffic for PacketIn={}", - pi); - - decision = new RoutingDecision(sw.getId(), pi.getInPort() - , IDeviceService.fcStore. - get(cntx, IDeviceService.CONTEXT_SRC_DEVICE), + if (logger.isTraceEnabled()) { + logger.trace("Blocking malformed broadcast traffic for PacketIn={}", pi); + } + + decision = new RoutingDecision(sw.getId(), pi.getInPort(), + IDeviceService.fcStore.get(cntx, IDeviceService.CONTEXT_SRC_DEVICE), IRoutingDecision.RoutingAction.DROP); decision.addToContext(cntx); } @@ -635,39 +544,36 @@ public class Firewall implements IFirewallService, IOFMessageListener, * decision.addToContext(cntx); return Command.CONTINUE; } */ - // check if we have a matching rule for this packet/flow - // and no decision is taken yet + // check if we have a matching rule for this packet/flow and no decision has been made yet if (decision == null) { - RuleWildcardsPair match_ret = this.matchWithRule(sw, pi, cntx); - FirewallRule rule = match_ret.rule; - - if (rule == null || rule.action == FirewallRule.FirewallAction.DENY) { - decision = new RoutingDecision(sw.getId(), pi.getInPort() - , IDeviceService.fcStore. - get(cntx, IDeviceService.CONTEXT_SRC_DEVICE), - IRoutingDecision.RoutingAction.DROP); - decision.setWildcards(match_ret.wildcards); + // check if the packet we received matches an existing rule + RuleMatchPair rmp = this.matchWithRule(sw, pi, cntx); + FirewallRule rule = rmp.rule; + + // Drop the packet if we don't have a rule allowing or dropping it or if we explicitly drop it + if (rule == null || rule.action == FirewallRule.FirewallAction.DROP) { + decision = new RoutingDecision(sw.getId(), pi.getInPort(), + IDeviceService.fcStore.get(cntx, IDeviceService.CONTEXT_SRC_DEVICE), + IRoutingDecision.RoutingAction.DROP); + decision.setMatch(rmp.match); decision.addToContext(cntx); if (logger.isTraceEnabled()) { - if (rule == null) - logger.trace( - "No firewall rule found for PacketIn={}, blocking flow", - pi); - else if (rule.action == FirewallRule.FirewallAction.DENY) { - logger.trace("Deny rule={} match for PacketIn={}", - rule, pi); + if (rule == null) { + logger.trace("No firewall rule found for PacketIn={}, blocking flow", pi); + } else if (rule.action == FirewallRule.FirewallAction.DROP) { + logger.trace("Deny rule={} match for PacketIn={}", rule, pi); } } + // Found a rule and the rule is not a drop, so allow the packet } else { - decision = new RoutingDecision(sw.getId(), pi.getInPort() - , IDeviceService.fcStore. - get(cntx, IDeviceService.CONTEXT_SRC_DEVICE), + decision = new RoutingDecision(sw.getId(), pi.getInPort(), + IDeviceService.fcStore.get(cntx, IDeviceService.CONTEXT_SRC_DEVICE), IRoutingDecision.RoutingAction.FORWARD_OR_FLOOD); - decision.setWildcards(match_ret.wildcards); + decision.setMatch(rmp.match); decision.addToContext(cntx); - if (logger.isTraceEnabled()) - logger.trace("Allow rule={} match for PacketIn={}", rule, - pi); + if (logger.isTraceEnabled()) { + logger.trace("Allow rule={} match for PacketIn={}", rule, pi); + } } } diff --git a/src/main/java/net/floodlightcontroller/firewall/FirewallRule.java b/src/main/java/net/floodlightcontroller/firewall/FirewallRule.java index f457ce8d9e85510f91fdefe2bb2f3ac84276eec4..b32d9c10ace35606a59447094f74585d58443f41 100644 --- a/src/main/java/net/floodlightcontroller/firewall/FirewallRule.java +++ b/src/main/java/net/floodlightcontroller/firewall/FirewallRule.java @@ -18,7 +18,16 @@ package net.floodlightcontroller.firewall; import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import org.openflow.protocol.OFMatch; + +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; +import org.projectfloodlight.openflow.types.IPv4AddressWithMask; +import org.projectfloodlight.openflow.types.IpProtocol; +import org.projectfloodlight.openflow.types.MacAddress; +import org.projectfloodlight.openflow.types.OFPort; +import org.projectfloodlight.openflow.types.TransportPort; import net.floodlightcontroller.packet.Ethernet; import net.floodlightcontroller.packet.IPacket; @@ -30,29 +39,31 @@ import net.floodlightcontroller.packet.UDP; public class FirewallRule implements Comparable<FirewallRule> { public int ruleid; - public long dpid; - public short in_port; - public long dl_src; - public long dl_dst; - public short dl_type; - public int nw_src_prefix; - public int nw_src_maskbits; - public int nw_dst_prefix; - public int nw_dst_maskbits; - public short nw_proto; - public short tp_src; - public short tp_dst; - - public boolean wildcard_dpid; - public boolean wildcard_in_port; - public boolean wildcard_dl_src; - public boolean wildcard_dl_dst; - public boolean wildcard_dl_type; - public boolean wildcard_nw_src; - public boolean wildcard_nw_dst; - public boolean wildcard_nw_proto; - public boolean wildcard_tp_src; - public boolean wildcard_tp_dst; + public DatapathId dpid; + public OFPort in_port; + public MacAddress dl_src; + public MacAddress dl_dst; + public EthType dl_type; + public IPv4AddressWithMask nw_src_prefix_and_mask; + public IPv4AddressWithMask nw_dst_prefix_and_mask; + public IpProtocol nw_proto; + public TransportPort tp_src; + public TransportPort tp_dst; + + /* Specify whether or not a match field is relevant. + * true = anything goes; don't care what it is + * false = field must match (w or w/o mask depending on field) + */ + public boolean any_dpid; + public boolean any_in_port; + public boolean any_dl_src; + public boolean any_dl_dst; + public boolean any_dl_type; + public boolean any_nw_src; + public boolean any_nw_dst; + public boolean any_nw_proto; + public boolean any_tp_src; + public boolean any_tp_dst; public int priority = 0; @@ -60,35 +71,38 @@ public class FirewallRule implements Comparable<FirewallRule> { public enum FirewallAction { /* - * DENY: Deny rule + * DROP: Drop rule * ALLOW: Allow rule */ - DENY, ALLOW + DROP, ALLOW } + /** + * The default rule is to match on anything. + */ public FirewallRule() { - this.in_port = 0; - this.dl_src = 0; - this.nw_src_prefix = 0; - this.nw_src_maskbits = 0; - this.dl_dst = 0; - this.nw_proto = 0; - this.tp_src = 0; - this.tp_dst = 0; - this.dl_dst = 0; - this.nw_dst_prefix = 0; - this.nw_dst_maskbits = 0; - this.dpid = -1; - this.wildcard_dpid = true; - this.wildcard_in_port = true; - this.wildcard_dl_src = true; - this.wildcard_dl_dst = true; - this.wildcard_dl_type = true; - this.wildcard_nw_src = true; - this.wildcard_nw_dst = true; - this.wildcard_nw_proto = true; - this.wildcard_tp_src = true; - this.wildcard_tp_dst = true; + this.in_port = OFPort.ZERO; + this.dl_src = MacAddress.NONE; + this.nw_src_prefix_and_mask = IPv4AddressWithMask.NONE; + //this.nw_src_maskbits = 0; + this.dl_dst = MacAddress.NONE; + this.nw_proto = IpProtocol.NONE; + this.tp_src = TransportPort.NONE; + this.tp_dst = TransportPort.NONE; + this.dl_dst = MacAddress.NONE; + this.nw_dst_prefix_and_mask = IPv4AddressWithMask.NONE; + //this.nw_dst_maskbits = 0; + this.dpid = DatapathId.NONE; + this.any_dpid = true; + this.any_in_port = true; + this.any_dl_src = true; + this.any_dl_dst = true; + this.any_dl_type = true; + this.any_nw_src = true; + this.any_nw_dst = true; + this.any_nw_proto = true; + this.any_tp_src = true; + this.any_tp_dst = true; this.priority = 0; this.action = FirewallAction.ALLOW; this.ruleid = 0; @@ -131,33 +145,33 @@ public class FirewallRule implements Comparable<FirewallRule> { **/ public boolean isSameAs(FirewallRule r) { if (this.action != r.action - || this.wildcard_dl_type != r.wildcard_dl_type - || (this.wildcard_dl_type == false && this.dl_type != r.dl_type) - || this.wildcard_tp_src != r.wildcard_tp_src - || (this.wildcard_tp_src == false && this.tp_src != r.tp_src) - || this.wildcard_tp_dst != r.wildcard_tp_dst - || (this.wildcard_tp_dst == false &&this.tp_dst != r.tp_dst) - || this.wildcard_dpid != r.wildcard_dpid - || (this.wildcard_dpid == false && this.dpid != r.dpid) - || this.wildcard_in_port != r.wildcard_in_port - || (this.wildcard_in_port == false && this.in_port != r.in_port) - || this.wildcard_nw_src != r.wildcard_nw_src - || (this.wildcard_nw_src == false && (this.nw_src_prefix != r.nw_src_prefix || this.nw_src_maskbits != r.nw_src_maskbits)) - || this.wildcard_dl_src != r.wildcard_dl_src - || (this.wildcard_dl_src == false && this.dl_src != r.dl_src) - || this.wildcard_nw_proto != r.wildcard_nw_proto - || (this.wildcard_nw_proto == false && this.nw_proto != r.nw_proto) - || this.wildcard_nw_dst != r.wildcard_nw_dst - || (this.wildcard_nw_dst == false && (this.nw_dst_prefix != r.nw_dst_prefix || this.nw_dst_maskbits != r.nw_dst_maskbits)) - || this.wildcard_dl_dst != r.wildcard_dl_dst - || (this.wildcard_dl_dst == false && this.dl_dst != r.dl_dst)) { + || this.any_dl_type != r.any_dl_type + || (this.any_dl_type == false && !this.dl_type.equals(r.dl_type)) + || this.any_tp_src != r.any_tp_src + || (this.any_tp_src == false && !this.tp_src.equals(r.tp_src)) + || this.any_tp_dst != r.any_tp_dst + || (this.any_tp_dst == false && !this.tp_dst.equals(r.tp_dst)) + || this.any_dpid != r.any_dpid + || (this.any_dpid == false && !this.dpid.equals(r.dpid)) + || this.any_in_port != r.any_in_port + || (this.any_in_port == false && !this.in_port.equals(r.in_port)) + || this.any_nw_src != r.any_nw_src + || (this.any_nw_src == false && !this.nw_src_prefix_and_mask.equals(r.nw_src_prefix_and_mask)) + || this.any_dl_src != r.any_dl_src + || (this.any_dl_src == false && !this.dl_src.equals(r.dl_src)) + || this.any_nw_proto != r.any_nw_proto + || (this.any_nw_proto == false && !this.nw_proto.equals(r.nw_proto)) + || this.any_nw_dst != r.any_nw_dst + || (this.any_nw_dst == false && !this.nw_dst_prefix_and_mask.equals(r.nw_dst_prefix_and_mask)) + || this.any_dl_dst != r.any_dl_dst + || (this.any_dl_dst == false && this.dl_dst != r.dl_dst)) { return false; } return true; } /** - * Matches this rule to a given flow - incoming packet + * Checks if this rule is a match for the incoming packet's MatchFields * * @param switchDpid * the Id of the connected switch @@ -165,14 +179,13 @@ public class FirewallRule implements Comparable<FirewallRule> { * the switch port where the packet originated from * @param packet * the Ethernet packet that arrives at the switch - * @param wildcards - * the pair of wildcards (allow and deny) given by Firewall + * @param allow-drop-pair + * the pair of matches (allow and drop) given by the Firewall * module that is used by the Firewall module's matchWithRule - * method to derive wildcards for the decision to be taken + * method to derive the match object for the decision to be taken * @return true if the rule matches the given packet-in, false otherwise */ - public boolean matchesFlow(long switchDpid, short inPort, Ethernet packet, - WildcardsPair wildcards) { + public boolean matchesThisPacket(DatapathId switchDpid, OFPort inPort, Ethernet packet, AllowDropPair adp) { IPacket pkt = packet.getPayload(); // dl_type type @@ -183,143 +196,174 @@ public class FirewallRule implements Comparable<FirewallRule> { UDP pkt_udp = null; // tp_src and tp_dst (tp port numbers) - short pkt_tp_src = 0; - short pkt_tp_dst = 0; + TransportPort pkt_tp_src = TransportPort.NONE; + TransportPort pkt_tp_dst = TransportPort.NONE; // switchID matches? - if (wildcard_dpid == false && dpid != switchDpid) + if (any_dpid == false && !dpid.equals(switchDpid)) return false; // in_port matches? - if (wildcard_in_port == false && in_port != inPort) + if (any_in_port == false && !in_port.equals(inPort)) return false; - if (action == FirewallRule.FirewallAction.DENY) { - wildcards.drop &= ~OFMatch.OFPFW_IN_PORT; + if (action == FirewallRule.FirewallAction.DROP) { + //wildcards.drop &= ~OFMatch.OFPFW_IN_PORT; + adp.drop.setExact(MatchField.IN_PORT, this.in_port); } else { - wildcards.allow &= ~OFMatch.OFPFW_IN_PORT; + //wildcards.allow &= ~OFMatch.OFPFW_IN_PORT; + adp.allow.setExact(MatchField.IN_PORT, this.in_port); } // mac address (src and dst) match? - if (wildcard_dl_src == false - && dl_src != packet.getSourceMAC().toLong()) + if (any_dl_src == false && !dl_src.equals(packet.getSourceMACAddress())) return false; - if (action == FirewallRule.FirewallAction.DENY) { - wildcards.drop &= ~OFMatch.OFPFW_DL_SRC; + if (action == FirewallRule.FirewallAction.DROP) { + //wildcards.drop &= ~OFMatch.OFPFW_DL_SRC; + adp.drop.setExact(MatchField.ETH_SRC, this.dl_src); } else { - wildcards.allow &= ~OFMatch.OFPFW_DL_SRC; + //wildcards.allow &= ~OFMatch.OFPFW_DL_SRC; + adp.allow.setExact(MatchField.ETH_SRC, this.dl_src); } - if (wildcard_dl_dst == false - && dl_dst != packet.getDestinationMAC().toLong()) + if (any_dl_dst == false && !dl_dst.equals(packet.getDestinationMACAddress())) return false; - if (action == FirewallRule.FirewallAction.DENY) { - wildcards.drop &= ~OFMatch.OFPFW_DL_DST; + if (action == FirewallRule.FirewallAction.DROP) { + //wildcards.drop &= ~OFMatch.OFPFW_DL_DST; + adp.drop.setExact(MatchField.ETH_DST, this.dl_dst); } else { - wildcards.allow &= ~OFMatch.OFPFW_DL_DST; + //wildcards.allow &= ~OFMatch.OFPFW_DL_DST; + adp.allow.setExact(MatchField.ETH_DST, this.dl_dst); } // dl_type check: ARP, IP // if this is not an ARP rule but the pkt is ARP, // return false match - no need to continue protocol specific check - if (wildcard_dl_type == false) { - if (dl_type == Ethernet.TYPE_ARP) { - if (packet.getEtherType() != Ethernet.TYPE_ARP) + if (any_dl_type == false) { + if (dl_type.equals(EthType.ARP)) { + if (packet.getEtherType() != EthType.ARP.getValue()) return false; else { - if (action == FirewallRule.FirewallAction.DENY) { - wildcards.drop &= ~OFMatch.OFPFW_DL_TYPE; + if (action == FirewallRule.FirewallAction.DROP) { + //wildcards.drop &= ~OFMatch.OFPFW_DL_TYPE; + adp.drop.setExact(MatchField.ETH_TYPE, this.dl_type); } else { - wildcards.allow &= ~OFMatch.OFPFW_DL_TYPE; + //wildcards.allow &= ~OFMatch.OFPFW_DL_TYPE; + adp.allow.setExact(MatchField.ETH_TYPE, this.dl_type); } } - } else if (dl_type == Ethernet.TYPE_IPv4) { - if (packet.getEtherType() != Ethernet.TYPE_IPv4) + } else if (dl_type.equals(EthType.IPv4)) { + if (packet.getEtherType() != EthType.IPv4.getValue()) return false; else { - if (action == FirewallRule.FirewallAction.DENY) { - wildcards.drop &= ~OFMatch.OFPFW_NW_PROTO; + if (action == FirewallRule.FirewallAction.DROP) { + //wildcards.drop &= ~OFMatch.OFPFW_NW_PROTO; + adp.drop.setExact(MatchField.IP_PROTO, this.nw_proto); } else { - wildcards.allow &= ~OFMatch.OFPFW_NW_PROTO; + //wildcards.allow &= ~OFMatch.OFPFW_NW_PROTO; + adp.allow.setExact(MatchField.IP_PROTO, this.nw_proto); } // IP packets, proceed with ip address check pkt_ip = (IPv4) pkt; // IP addresses (src and dst) match? - if (wildcard_nw_src == false - && this.matchIPAddress(nw_src_prefix, - nw_src_maskbits, pkt_ip.getSourceAddress()) == false) + if (any_nw_src == false && this.matchIPAddress(nw_src_prefix_and_mask.getValue().getInt(), nw_src_prefix_and_mask.getMask().getInt(), pkt_ip.getSourceAddress()) == false) return false; - if (action == FirewallRule.FirewallAction.DENY) { - wildcards.drop &= ~OFMatch.OFPFW_NW_SRC_ALL; - wildcards.drop |= (nw_src_maskbits << OFMatch.OFPFW_NW_SRC_SHIFT); + if (action == FirewallRule.FirewallAction.DROP) { + //wildcards.drop &= ~OFMatch.OFPFW_NW_SRC_ALL; + //wildcards.drop |= (nw_src_maskbits << OFMatch.OFPFW_NW_SRC_SHIFT); + adp.drop.setMasked(MatchField.IPV4_SRC, nw_src_prefix_and_mask); } else { - wildcards.allow &= ~OFMatch.OFPFW_NW_SRC_ALL; - wildcards.allow |= (nw_src_maskbits << OFMatch.OFPFW_NW_SRC_SHIFT); + //wildcards.allow &= ~OFMatch.OFPFW_NW_SRC_ALL; + //wildcards.allow |= (nw_src_maskbits << OFMatch.OFPFW_NW_SRC_SHIFT); + adp.allow.setMasked(MatchField.IPV4_SRC, nw_src_prefix_and_mask); } - if (wildcard_nw_dst == false - && this.matchIPAddress(nw_dst_prefix, - nw_dst_maskbits, - pkt_ip.getDestinationAddress()) == false) + if (any_nw_dst == false && this.matchIPAddress(nw_dst_prefix_and_mask.getValue().getInt(), nw_dst_prefix_and_mask.getMask().getInt(), pkt_ip.getDestinationAddress()) == false) return false; - if (action == FirewallRule.FirewallAction.DENY) { - wildcards.drop &= ~OFMatch.OFPFW_NW_DST_ALL; - wildcards.drop |= (nw_dst_maskbits << OFMatch.OFPFW_NW_DST_SHIFT); + if (action == FirewallRule.FirewallAction.DROP) { + //wildcards.drop &= ~OFMatch.OFPFW_NW_DST_ALL; + //wildcards.drop |= (nw_dst_maskbits << OFMatch.OFPFW_NW_DST_SHIFT); + adp.drop.setMasked(MatchField.IPV4_DST, nw_dst_prefix_and_mask); } else { - wildcards.allow &= ~OFMatch.OFPFW_NW_DST_ALL; - wildcards.allow |= (nw_dst_maskbits << OFMatch.OFPFW_NW_DST_SHIFT); + //wildcards.allow &= ~OFMatch.OFPFW_NW_DST_ALL; + //wildcards.allow |= (nw_dst_maskbits << OFMatch.OFPFW_NW_DST_SHIFT); + adp.allow.setMasked(MatchField.IPV4_DST, nw_dst_prefix_and_mask); } // nw_proto check - if (wildcard_nw_proto == false) { - if (nw_proto == IPv4.PROTOCOL_TCP) { - if (pkt_ip.getProtocol() != IPv4.PROTOCOL_TCP) + if (any_nw_proto == false) { + if (nw_proto.equals(IpProtocol.TCP)) { + if (!pkt_ip.getProtocol().equals(IpProtocol.TCP)) { return false; - else { + } else { pkt_tcp = (TCP) pkt_ip.getPayload(); pkt_tp_src = pkt_tcp.getSourcePort(); pkt_tp_dst = pkt_tcp.getDestinationPort(); } - } else if (nw_proto == IPv4.PROTOCOL_UDP) { - if (pkt_ip.getProtocol() != IPv4.PROTOCOL_UDP) + } else if (nw_proto.equals(IpProtocol.UDP)) { + if (!pkt_ip.getProtocol().equals(IpProtocol.UDP)) { return false; - else { + } else { pkt_udp = (UDP) pkt_ip.getPayload(); pkt_tp_src = pkt_udp.getSourcePort(); pkt_tp_dst = pkt_udp.getDestinationPort(); } - } else if (nw_proto == IPv4.PROTOCOL_ICMP) { - if (pkt_ip.getProtocol() != IPv4.PROTOCOL_ICMP) + } else if (nw_proto.equals(IpProtocol.ICMP)) { + if (!pkt_ip.getProtocol().equals(IpProtocol.ICMP)) { return false; - else { + } else { // nothing more needed for ICMP } } - if (action == FirewallRule.FirewallAction.DENY) { - wildcards.drop &= ~OFMatch.OFPFW_NW_PROTO; + if (action == FirewallRule.FirewallAction.DROP) { + //wildcards.drop &= ~OFMatch.OFPFW_NW_PROTO; + adp.drop.setExact(MatchField.IP_PROTO, this.nw_proto); } else { - wildcards.allow &= ~OFMatch.OFPFW_NW_PROTO; + //wildcards.allow &= ~OFMatch.OFPFW_NW_PROTO; + adp.allow.setExact(MatchField.IP_PROTO, this.nw_proto); } // TCP/UDP source and destination ports match? if (pkt_tcp != null || pkt_udp != null) { // does the source port match? - if (tp_src != 0 && tp_src != pkt_tp_src) + if (tp_src.getPort() != 0 && tp_src.getPort() != pkt_tp_src.getPort()) { return false; - if (action == FirewallRule.FirewallAction.DENY) { - wildcards.drop &= ~OFMatch.OFPFW_TP_SRC; + } + if (action == FirewallRule.FirewallAction.DROP) { + //wildcards.drop &= ~OFMatch.OFPFW_TP_SRC; + if (pkt_tcp != null) { + adp.drop.setExact(MatchField.TCP_SRC, this.tp_src); + } else { + adp.drop.setExact(MatchField.UDP_SRC, this.tp_src); + } } else { - wildcards.allow &= ~OFMatch.OFPFW_TP_SRC; + //wildcards.allow &= ~OFMatch.OFPFW_TP_SRC; + if (pkt_tcp != null) { + adp.allow.setExact(MatchField.TCP_SRC, this.tp_src); + } else { + adp.allow.setExact(MatchField.UDP_SRC, this.tp_src); + } } // does the destination port match? - if (tp_dst != 0 && tp_dst != pkt_tp_dst) + if (tp_dst.getPort() != 0 && tp_dst.getPort() != pkt_tp_dst.getPort()) { return false; - if (action == FirewallRule.FirewallAction.DENY) { - wildcards.drop &= ~OFMatch.OFPFW_TP_DST; + } + if (action == FirewallRule.FirewallAction.DROP) { + //wildcards.drop &= ~OFMatch.OFPFW_TP_DST; + if (pkt_tcp != null) { + adp.drop.setExact(MatchField.TCP_DST, this.tp_dst); + } else { + adp.drop.setExact(MatchField.UDP_DST, this.tp_dst); + } } else { - wildcards.allow &= ~OFMatch.OFPFW_TP_DST; + //wildcards.allow &= ~OFMatch.OFPFW_TP_DST; + if (pkt_tcp != null) { + adp.allow.setExact(MatchField.TCP_DST, this.tp_dst); + } else { + adp.allow.setExact(MatchField.UDP_DST, this.tp_dst); + } } } } @@ -330,10 +374,12 @@ public class FirewallRule implements Comparable<FirewallRule> { return false; } } - if (action == FirewallRule.FirewallAction.DENY) { - wildcards.drop &= ~OFMatch.OFPFW_DL_TYPE; + if (action == FirewallRule.FirewallAction.DROP) { + //wildcards.drop &= ~OFMatch.OFPFW_DL_TYPE; + adp.drop.setExact(MatchField.ETH_TYPE, this.dl_type); } else { - wildcards.allow &= ~OFMatch.OFPFW_DL_TYPE; + //wildcards.allow &= ~OFMatch.OFPFW_DL_TYPE; + adp.allow.setExact(MatchField.ETH_TYPE, this.dl_type); } // all applicable checks passed @@ -352,13 +398,12 @@ public class FirewallRule implements Comparable<FirewallRule> { * @return true if CIDR address matches the packet's IP address, false * otherwise */ - protected boolean matchIPAddress(int rulePrefix, int ruleBits, - int packetAddress) { + protected boolean matchIPAddress(int rulePrefix, int ruleBits, IPv4Address packetAddress) { boolean matched = true; int rule_iprng = 32 - ruleBits; int rule_ipint = rulePrefix; - int pkt_ipint = packetAddress; + int pkt_ipint = packetAddress.getInt(); // if there's a subnet range (bits to be wildcarded > 0) if (rule_iprng > 0) { // right shift bits to remove rule_iprng of LSB that are to be @@ -382,30 +427,30 @@ public class FirewallRule implements Comparable<FirewallRule> { public int hashCode() { final int prime = 2521; int result = super.hashCode(); - result = prime * result + (int) dpid; - result = prime * result + in_port; - result = prime * result + (int) dl_src; - result = prime * result + (int) dl_dst; - result = prime * result + dl_type; - result = prime * result + nw_src_prefix; - result = prime * result + nw_src_maskbits; - result = prime * result + nw_dst_prefix; - result = prime * result + nw_dst_maskbits; - result = prime * result + nw_proto; - result = prime * result + tp_src; - result = prime * result + tp_dst; + result = prime * result + (int) dpid.getLong(); + result = prime * result + in_port.getPortNumber(); + result = prime * result + (int) dl_src.getLong(); + result = prime * result + (int) dl_dst.getLong(); + result = prime * result + dl_type.getValue(); + result = prime * result + nw_src_prefix_and_mask.getValue().getInt(); + result = prime * result + nw_src_prefix_and_mask.getMask().getInt(); + result = prime * result + nw_dst_prefix_and_mask.getValue().getInt(); + result = prime * result + nw_dst_prefix_and_mask.getMask().getInt(); + result = prime * result + nw_proto.getIpProtocolNumber(); + result = prime * result + tp_src.getPort(); + result = prime * result + tp_dst.getPort(); result = prime * result + action.ordinal(); result = prime * result + priority; - result = prime * result + (new Boolean(wildcard_dpid)).hashCode(); - result = prime * result + (new Boolean(wildcard_in_port)).hashCode(); - result = prime * result + (new Boolean(wildcard_dl_src)).hashCode(); - result = prime * result + (new Boolean(wildcard_dl_dst)).hashCode(); - result = prime * result + (new Boolean(wildcard_dl_type)).hashCode(); - result = prime * result + (new Boolean(wildcard_nw_src)).hashCode(); - result = prime * result + (new Boolean(wildcard_nw_dst)).hashCode(); - result = prime * result + (new Boolean(wildcard_nw_proto)).hashCode(); - result = prime * result + (new Boolean(wildcard_tp_src)).hashCode(); - result = prime * result + (new Boolean(wildcard_tp_dst)).hashCode(); + result = prime * result + (new Boolean(any_dpid)).hashCode(); + result = prime * result + (new Boolean(any_in_port)).hashCode(); + result = prime * result + (new Boolean(any_dl_src)).hashCode(); + result = prime * result + (new Boolean(any_dl_dst)).hashCode(); + result = prime * result + (new Boolean(any_dl_type)).hashCode(); + result = prime * result + (new Boolean(any_nw_src)).hashCode(); + result = prime * result + (new Boolean(any_nw_dst)).hashCode(); + result = prime * result + (new Boolean(any_nw_proto)).hashCode(); + result = prime * result + (new Boolean(any_tp_src)).hashCode(); + result = prime * result + (new Boolean(any_tp_dst)).hashCode(); return result; } } diff --git a/src/main/java/net/floodlightcontroller/firewall/FirewallRuleSerializer.java b/src/main/java/net/floodlightcontroller/firewall/FirewallRuleSerializer.java index 3180968d1dd1786ef183933bc8584a5b896b123d..334d02236d4188969a30b644b486598d908aa8ff 100644 --- a/src/main/java/net/floodlightcontroller/firewall/FirewallRuleSerializer.java +++ b/src/main/java/net/floodlightcontroller/firewall/FirewallRuleSerializer.java @@ -19,14 +19,10 @@ package net.floodlightcontroller.firewall; import java.io.IOException; -import net.floodlightcontroller.packet.IPv4; -import net.floodlightcontroller.util.MACAddress; - import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; -import org.openflow.util.HexString; /** * Serialize a FirewallRule object @@ -42,28 +38,28 @@ public class FirewallRuleSerializer extends JsonSerializer<FirewallRule> { jGen.writeStartObject(); jGen.writeNumberField("ruleid", rule.ruleid); - jGen.writeStringField("dpid", HexString.toHexString(rule.dpid)); - jGen.writeNumberField("in_port", rule.in_port); - jGen.writeStringField("dl_src",String.valueOf(MACAddress.valueOf(rule.dl_src))); - jGen.writeStringField("dl_dst", String.valueOf(MACAddress.valueOf(rule.dl_dst))); - jGen.writeNumberField("dl_type", rule.dl_type); - jGen.writeStringField("nw_src_prefix", IPv4.fromIPv4Address(rule.nw_src_prefix)); - jGen.writeNumberField("nw_src_maskbits", rule.nw_src_maskbits); - jGen.writeStringField("nw_dst_prefix", IPv4.fromIPv4Address(rule.nw_dst_prefix)); - jGen.writeNumberField("nw_dst_maskbits", rule.nw_dst_maskbits); - jGen.writeNumberField("nw_proto", rule.nw_proto); - jGen.writeNumberField("tp_src", rule.tp_src); - jGen.writeNumberField("tp_dst", rule.tp_dst); - jGen.writeBooleanField("wildcard_dpid", rule.wildcard_dpid); - jGen.writeBooleanField("wildcard_in_port", rule.wildcard_in_port); - jGen.writeBooleanField("wildcard_dl_src", rule.wildcard_dl_src); - jGen.writeBooleanField("wildcard_dl_dst", rule.wildcard_dl_dst); - jGen.writeBooleanField("wildcard_dl_type", rule.wildcard_dl_type); - jGen.writeBooleanField("wildcard_nw_src", rule.wildcard_nw_src); - jGen.writeBooleanField("wildcard_nw_dst", rule.wildcard_nw_dst); - jGen.writeBooleanField("wildcard_nw_proto", rule.wildcard_nw_proto); - jGen.writeBooleanField("wildcard_tp_src", rule.wildcard_tp_src); - jGen.writeBooleanField("wildcard_tp_dst", rule.wildcard_tp_dst); + jGen.writeStringField("dpid", rule.dpid.toString()); + jGen.writeNumberField("in_port", rule.in_port.getPortNumber()); + jGen.writeStringField("dl_src", rule.dl_src.toString()); + jGen.writeStringField("dl_dst", rule.dl_dst.toString()); + jGen.writeNumberField("dl_type", rule.dl_type.getValue()); + jGen.writeStringField("nw_src_prefix", rule.nw_src_prefix_and_mask.getValue().toString()); + jGen.writeNumberField("nw_src_maskbits", rule.nw_src_prefix_and_mask.getMask().asCidrMaskLength()); + jGen.writeStringField("nw_dst_prefix", rule.nw_dst_prefix_and_mask.getValue().toString()); + jGen.writeNumberField("nw_dst_maskbits", rule.nw_dst_prefix_and_mask.getMask().asCidrMaskLength()); + jGen.writeNumberField("nw_proto", rule.nw_proto.getIpProtocolNumber()); + jGen.writeNumberField("tp_src", rule.tp_src.getPort()); + jGen.writeNumberField("tp_dst", rule.tp_dst.getPort()); + jGen.writeBooleanField("any_dpid", rule.any_dpid); + jGen.writeBooleanField("any_in_port", rule.any_in_port); + jGen.writeBooleanField("any_dl_src", rule.any_dl_src); + jGen.writeBooleanField("any_dl_dst", rule.any_dl_dst); + jGen.writeBooleanField("any_dl_type", rule.any_dl_type); + jGen.writeBooleanField("any_nw_src", rule.any_nw_src); + jGen.writeBooleanField("any_nw_dst", rule.any_nw_dst); + jGen.writeBooleanField("any_nw_proto", rule.any_nw_proto); + jGen.writeBooleanField("any_tp_src", rule.any_tp_src); + jGen.writeBooleanField("any_tp_dst", rule.any_tp_dst); jGen.writeNumberField("priority", rule.priority); jGen.writeStringField("action", String.valueOf(rule.action)); diff --git a/src/main/java/net/floodlightcontroller/firewall/FirewallRulesResource.java b/src/main/java/net/floodlightcontroller/firewall/FirewallRulesResource.java index 7af219cc63f70879eda9cdd5ab7971b1a3111cea..90c3d13a2d24f9aa128c8480852dd3a15d4baf77 100644 --- a/src/main/java/net/floodlightcontroller/firewall/FirewallRulesResource.java +++ b/src/main/java/net/floodlightcontroller/firewall/FirewallRulesResource.java @@ -25,7 +25,14 @@ import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.databind.MappingJsonFactory; -import org.openflow.util.HexString; + +import org.projectfloodlight.openflow.types.DatapathId; +import org.projectfloodlight.openflow.types.EthType; +import org.projectfloodlight.openflow.types.IPv4AddressWithMask; +import org.projectfloodlight.openflow.types.IpProtocol; +import org.projectfloodlight.openflow.types.MacAddress; +import org.projectfloodlight.openflow.types.OFPort; +import org.projectfloodlight.openflow.types.TransportPort; import org.restlet.resource.Delete; import org.restlet.resource.Post; import org.restlet.resource.Get; @@ -33,7 +40,6 @@ import org.restlet.resource.ServerResource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import net.floodlightcontroller.packet.Ethernet; import net.floodlightcontroller.packet.IPv4; public class FirewallRulesResource extends ServerResource { @@ -166,96 +172,92 @@ public class FirewallRulesResource extends ServerResource { tmp = jp.getText(); if (tmp.equalsIgnoreCase("-1") == false) { // user inputs hex format dpid - rule.dpid = HexString.toLong(tmp); - rule.wildcard_dpid = false; + rule.dpid = DatapathId.of(tmp); + rule.any_dpid = false; } } else if (n == "src-inport") { - rule.in_port = Short.parseShort(jp.getText()); - rule.wildcard_in_port = false; + rule.in_port = OFPort.of(Integer.parseInt(jp.getText())); + rule.any_in_port = false; } else if (n == "src-mac") { tmp = jp.getText(); if (tmp.equalsIgnoreCase("ANY") == false) { - rule.wildcard_dl_src = false; - rule.dl_src = Ethernet.toLong(Ethernet.toMACAddress(tmp)); + rule.any_dl_src = false; + rule.dl_src = MacAddress.of(tmp); } } else if (n == "dst-mac") { tmp = jp.getText(); if (tmp.equalsIgnoreCase("ANY") == false) { - rule.wildcard_dl_dst = false; - rule.dl_dst = Ethernet.toLong(Ethernet.toMACAddress(tmp)); + rule.any_dl_dst = false; + rule.dl_dst = MacAddress.of(tmp); } } else if (n == "dl-type") { tmp = jp.getText(); if (tmp.equalsIgnoreCase("ARP")) { - rule.wildcard_dl_type = false; - rule.dl_type = Ethernet.TYPE_ARP; + rule.any_dl_type = false; + rule.dl_type = EthType.ARP; } if (tmp.equalsIgnoreCase("IPv4")) { - rule.wildcard_dl_type = false; - rule.dl_type = Ethernet.TYPE_IPv4; + rule.any_dl_type = false; + rule.dl_type = EthType.IPv4; } } else if (n == "src-ip") { tmp = jp.getText(); if (tmp.equalsIgnoreCase("ANY") == false) { - rule.wildcard_nw_src = false; - rule.wildcard_dl_type = false; - rule.dl_type = Ethernet.TYPE_IPv4; - int[] cidr = IPCIDRToPrefixBits(tmp); - rule.nw_src_prefix = cidr[0]; - rule.nw_src_maskbits = cidr[1]; + rule.any_nw_src = false; + rule.any_dl_type = false; + rule.dl_type = EthType.IPv4; + rule.nw_src_prefix_and_mask = IPv4AddressWithMask.of(tmp); } } else if (n == "dst-ip") { tmp = jp.getText(); if (tmp.equalsIgnoreCase("ANY") == false) { - rule.wildcard_nw_dst = false; - rule.wildcard_dl_type = false; - rule.dl_type = Ethernet.TYPE_IPv4; - int[] cidr = IPCIDRToPrefixBits(tmp); - rule.nw_dst_prefix = cidr[0]; - rule.nw_dst_maskbits = cidr[1]; + rule.any_nw_dst = false; + rule.any_dl_type = false; + rule.dl_type = EthType.IPv4; + rule.nw_dst_prefix_and_mask = IPv4AddressWithMask.of(tmp); } } else if (n == "nw-proto") { tmp = jp.getText(); if (tmp.equalsIgnoreCase("TCP")) { - rule.wildcard_nw_proto = false; - rule.nw_proto = IPv4.PROTOCOL_TCP; - rule.wildcard_dl_type = false; - rule.dl_type = Ethernet.TYPE_IPv4; + rule.any_nw_proto = false; + rule.nw_proto = IpProtocol.TCP; + rule.any_dl_type = false; + rule.dl_type = EthType.IPv4; } else if (tmp.equalsIgnoreCase("UDP")) { - rule.wildcard_nw_proto = false; - rule.nw_proto = IPv4.PROTOCOL_UDP; - rule.wildcard_dl_type = false; - rule.dl_type = Ethernet.TYPE_IPv4; + rule.any_nw_proto = false; + rule.nw_proto = IpProtocol.UDP; + rule.any_dl_type = false; + rule.dl_type = EthType.IPv4; } else if (tmp.equalsIgnoreCase("ICMP")) { - rule.wildcard_nw_proto = false; - rule.nw_proto = IPv4.PROTOCOL_ICMP; - rule.wildcard_dl_type = false; - rule.dl_type = Ethernet.TYPE_IPv4; + rule.any_nw_proto = false; + rule.nw_proto = IpProtocol.ICMP; + rule.any_dl_type = false; + rule.dl_type = EthType.IPv4; } } else if (n == "tp-src") { - rule.wildcard_tp_src = false; - rule.tp_src = Short.parseShort(jp.getText()); + rule.any_tp_src = false; + rule.tp_src = TransportPort.of(Integer.parseInt(jp.getText())); } else if (n == "tp-dst") { - rule.wildcard_tp_dst = false; - rule.tp_dst = Short.parseShort(jp.getText()); + rule.any_tp_dst = false; + rule.tp_dst = TransportPort.of(Integer.parseInt(jp.getText())); } else if (n == "priority") { @@ -266,7 +268,7 @@ public class FirewallRulesResource extends ServerResource { if (jp.getText().equalsIgnoreCase("allow") == true) { rule.action = FirewallRule.FirewallAction.ALLOW; } else if (jp.getText().equalsIgnoreCase("deny") == true) { - rule.action = FirewallRule.FirewallAction.DENY; + rule.action = FirewallRule.FirewallAction.DROP; } } } diff --git a/src/main/java/net/floodlightcontroller/firewall/RuleWildcardsPair.java b/src/main/java/net/floodlightcontroller/firewall/RuleMatchPair.java similarity index 86% rename from src/main/java/net/floodlightcontroller/firewall/RuleWildcardsPair.java rename to src/main/java/net/floodlightcontroller/firewall/RuleMatchPair.java index d2aa2e5ff458b4fdef9c00bb4544e9851df3fe5c..ed9fb45024c308b2a88dc944b20796fb27887f59 100644 --- a/src/main/java/net/floodlightcontroller/firewall/RuleWildcardsPair.java +++ b/src/main/java/net/floodlightcontroller/firewall/RuleMatchPair.java @@ -17,9 +17,9 @@ package net.floodlightcontroller.firewall; -import org.openflow.protocol.OFMatch; +import org.projectfloodlight.openflow.protocol.match.Match; -public class RuleWildcardsPair { +public class RuleMatchPair { public FirewallRule rule; - public int wildcards = OFMatch.OFPFW_ALL; + public Match match; } diff --git a/src/main/java/net/floodlightcontroller/flowcache/FRQueryBvsMatchMac.java b/src/main/java/net/floodlightcontroller/flowcache/FRQueryBvsMatchMac.java index 12fac87914b6292112fedd8e8d88e5c40d394f22..60fab0d760c64ca419938534f71284bf3e5473ad 100644 --- a/src/main/java/net/floodlightcontroller/flowcache/FRQueryBvsMatchMac.java +++ b/src/main/java/net/floodlightcontroller/flowcache/FRQueryBvsMatchMac.java @@ -16,7 +16,7 @@ package net.floodlightcontroller.flowcache; -import net.floodlightcontroller.util.MACAddress; +import org.projectfloodlight.openflow.types.MacAddress; /** * The Class for FlowReconcileQuery for link down event. @@ -63,7 +63,7 @@ public class FRQueryBvsMatchMac extends FlowReconcileQuery { StringBuilder builder = new StringBuilder(); builder.append("["); builder.append("MAC: "); - builder.append(MACAddress.valueOf(mac).toString()); + builder.append(MacAddress.of(mac).toString()); builder.append("]"); return builder.toString(); } diff --git a/src/main/java/net/floodlightcontroller/flowcache/FRQueryBvsMatchSwitchPort.java b/src/main/java/net/floodlightcontroller/flowcache/FRQueryBvsMatchSwitchPort.java index 27ecc8dfbda9628a9cec5a121e8edb959385cffb..933b9324a8c35d527932af29c4690d6f1b7280f9 100644 --- a/src/main/java/net/floodlightcontroller/flowcache/FRQueryBvsMatchSwitchPort.java +++ b/src/main/java/net/floodlightcontroller/flowcache/FRQueryBvsMatchSwitchPort.java @@ -18,13 +18,13 @@ package net.floodlightcontroller.flowcache; import java.util.List; -import org.openflow.util.HexString; +import org.projectfloodlight.openflow.types.DatapathId; /** * The Class for FlowReconcileQuery for BVS config interface match switch port. */ public class FRQueryBvsMatchSwitchPort extends FlowReconcileQuery { /*switch DPID*/ - public long swId; + public DatapathId swId; /*List of ports:*/ public List<String> matchPortList; @@ -32,7 +32,7 @@ public class FRQueryBvsMatchSwitchPort extends FlowReconcileQuery { super(ReconcileQueryEvType.BVS_INTERFACE_RULE_CHANGED_MATCH_SWITCH_PORT); } - public FRQueryBvsMatchSwitchPort(Long swId, List<String> portList) { + public FRQueryBvsMatchSwitchPort(DatapathId swId, List<String> portList) { this(); this.swId = swId; this.matchPortList = portList; @@ -42,7 +42,7 @@ public class FRQueryBvsMatchSwitchPort extends FlowReconcileQuery { public int hashCode() { final int prime = 347; int result = super.hashCode(); - result = prime * result + (int)swId; + result = (int) (prime * result + swId.getLong()); return result; } @@ -58,8 +58,8 @@ public class FRQueryBvsMatchSwitchPort extends FlowReconcileQuery { return false; } FRQueryBvsMatchSwitchPort other = (FRQueryBvsMatchSwitchPort) obj; - if (swId != other.swId) return false; - if (! matchPortList.equals(other.matchPortList)) return false; + if (swId.equals(other.swId)) return false; + if (!matchPortList.equals(other.matchPortList)) return false; return true; } @@ -68,7 +68,7 @@ public class FRQueryBvsMatchSwitchPort extends FlowReconcileQuery { StringBuilder builder = new StringBuilder(); builder.append("["); builder.append("Switch: "); - builder.append(HexString.toHexString(swId)); + builder.append(swId.toString()); builder.append(", Match Port List:"); builder.append(matchPortList); builder.append("]"); diff --git a/src/main/java/net/floodlightcontroller/flowcache/FRQueryDevicePropertyChanged.java b/src/main/java/net/floodlightcontroller/flowcache/FRQueryDevicePropertyChanged.java index 48a2eb304040f0e4b45a7b5213b91a4cf0c21e48..64b9d2aaeaeda72169f48b675b80d7e01e58393d 100644 --- a/src/main/java/net/floodlightcontroller/flowcache/FRQueryDevicePropertyChanged.java +++ b/src/main/java/net/floodlightcontroller/flowcache/FRQueryDevicePropertyChanged.java @@ -17,7 +17,6 @@ package net.floodlightcontroller.flowcache; import net.floodlightcontroller.devicemanager.IDevice; -import net.floodlightcontroller.util.MACAddress; /** * The Class for FlowReconcileQuery for device property changed event. @@ -62,7 +61,7 @@ public class FRQueryDevicePropertyChanged extends FlowReconcileQuery { StringBuilder builder = new StringBuilder(); builder.append("["); builder.append("Device: "); - builder.append(MACAddress.valueOf(device.getMACAddress())); + builder.append(device.getMACAddress().toString()); builder.append("]"); return builder.toString(); } diff --git a/src/main/java/net/floodlightcontroller/flowcache/FlowReconcileManager.java b/src/main/java/net/floodlightcontroller/flowcache/FlowReconcileManager.java index 4ae2caed69063207f88d4fbd8422ddce5a4fd2f5..578a15e60d15cc41294155e052082b107b9562b2 100644 --- a/src/main/java/net/floodlightcontroller/flowcache/FlowReconcileManager.java +++ b/src/main/java/net/floodlightcontroller/flowcache/FlowReconcileManager.java @@ -26,50 +26,44 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; +import net.floodlightcontroller.core.IFloodlightProviderService; 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.ListenerDispatcher; import net.floodlightcontroller.core.util.SingletonTask; -import net.floodlightcontroller.counter.CounterStore; -import net.floodlightcontroller.counter.ICounter; -import net.floodlightcontroller.counter.ICounterStoreService; -import net.floodlightcontroller.counter.SimpleCounter; +import net.floodlightcontroller.debugcounter.DebugCounterResource; import net.floodlightcontroller.debugcounter.IDebugCounter; import net.floodlightcontroller.debugcounter.IDebugCounterService; -import net.floodlightcontroller.debugcounter.NullDebugCounter; -import net.floodlightcontroller.debugcounter.IDebugCounterService.CounterException; -import net.floodlightcontroller.debugcounter.IDebugCounterService.CounterType; import net.floodlightcontroller.flowcache.FlowReconcileQuery.FlowReconcileQueryDebugEvent; import net.floodlightcontroller.flowcache.IFlowReconcileListener; import net.floodlightcontroller.flowcache.OFMatchReconcile; import net.floodlightcontroller.flowcache.PriorityPendingQueue.EventPriority; import net.floodlightcontroller.threadpool.IThreadPoolService; -import org.openflow.protocol.OFType; +import org.projectfloodlight.openflow.protocol.OFType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class FlowReconcileManager - implements IFloodlightModule, IFlowReconcileService { +import com.sun.j3d.utils.scenegraph.io.retained.Controller; +public class FlowReconcileManager implements IFloodlightModule, IFlowReconcileService { /** The logger. */ - private static Logger logger = - LoggerFactory.getLogger(FlowReconcileManager.class); + private static Logger logger = LoggerFactory.getLogger(FlowReconcileManager.class); /** Reference to dependent modules */ - protected IThreadPoolService threadPool; - protected ICounterStoreService counterStore; - protected IDebugCounterService debugCounters; + protected IThreadPoolService threadPoolService; + protected IDebugCounterService debugCounterService; + protected IFloodlightProviderService floodlightProviderService; + /** * The list of flow reconcile listeners that have registered to get * flow reconcile callbacks. Such callbacks are invoked, for example, when * a switch with existing flow-mods joins this controller and those flows * need to be reconciled with the current configuration of the controller. */ - protected ListenerDispatcher<OFType, IFlowReconcileListener> - flowReconcileListeners; + protected ListenerDispatcher<OFType, IFlowReconcileListener> flowReconcileListeners; /** A FIFO queue to keep all outstanding flows for reconciliation */ PriorityPendingQueue <OFMatchReconcile> flowQueue; @@ -77,8 +71,8 @@ public class FlowReconcileManager /** Asynchronous task to feed the flowReconcile pipeline */ protected SingletonTask flowReconcileTask; - String controllerPktInCounterName; - protected SimpleCounter lastPacketInCounter; + protected DebugCounterResource ctrControllerPktIn; + protected IDebugCounter lastPacketInCounter; protected final static int MAX_SYSTEM_LOAD_PER_SECOND = 10000; /** a minimum flow reconcile rate so that it won't stave */ @@ -99,6 +93,7 @@ public class FlowReconcileManager private IDebugCounter ctrFlowReconcileRequest; private IDebugCounter ctrReconciledFlows; protected boolean flowReconcileEnabled; + private DebugCounterResource ctrPacketInRsrc = null; public AtomicInteger flowReconcileThreadRunCount; @@ -143,7 +138,7 @@ public class FlowReconcileManager OFMatchReconcile myOfmRc = new OFMatchReconcile(ofmRcIn); flowQueue.offer(myOfmRc, priority); - ctrFlowReconcileRequest.updateCounterWithFlush(); + ctrFlowReconcileRequest.increment(); Date currTime = new Date(); long delay = 0; @@ -192,19 +187,18 @@ public class FlowReconcileManager Collection<Class<? extends IFloodlightService>> l = new ArrayList<Class<? extends IFloodlightService>>(); l.add(IThreadPoolService.class); - l.add(ICounterStoreService.class); + l.add(IDebugCounterService.class); return null; } @Override public void init(FloodlightModuleContext context) throws FloodlightModuleException { - threadPool = context.getServiceImpl(IThreadPoolService.class); - counterStore = context.getServiceImpl(ICounterStoreService.class); - debugCounters = context.getServiceImpl(IDebugCounterService.class); + floodlightProviderService = context.getServiceImpl(IFloodlightProviderService.class); + threadPoolService = context.getServiceImpl(IThreadPoolService.class); + debugCounterService = context.getServiceImpl(IDebugCounterService.class); flowQueue = new PriorityPendingQueue<OFMatchReconcile>(); - flowReconcileListeners = - new ListenerDispatcher<OFType, IFlowReconcileListener>(); + flowReconcileListeners = new ListenerDispatcher<OFType, IFlowReconcileListener>(); Map<String, String> configParam = context.getConfigParams(this); String enableValue = configParam.get(EnableConfigKey); @@ -221,18 +215,16 @@ public class FlowReconcileManager } private void registerFlowReconcileManagerDebugCounters() throws FloodlightModuleException { - if (debugCounters == null) { + if (debugCounterService == null) { logger.error("Debug Counter Service not found."); - debugCounters = new NullDebugCounter(); } try { - ctrFlowReconcileRequest = debugCounters.registerCounter(PACKAGE, "flow-reconcile-request", - "All flow reconcile request received by this module", - CounterType.ALWAYS_COUNT); - ctrReconciledFlows = debugCounters.registerCounter(PACKAGE, "reconciled-flows", - "All flows reconciled successfully by this module", - CounterType.ALWAYS_COUNT); - } catch (CounterException e) { + debugCounterService.registerModule(PACKAGE); + ctrFlowReconcileRequest = debugCounterService.registerCounter(PACKAGE, "flow-reconcile-request", + "All flow reconcile request received by this module"); + ctrReconciledFlows = debugCounterService.registerCounter(PACKAGE, "reconciled-flows", + "All flows reconciled successfully by this module"); + } catch (Exception e) { throw new FloodlightModuleException(e.getMessage()); } } @@ -241,7 +233,7 @@ public class FlowReconcileManager @Override public void startUp(FloodlightModuleContext context) { // thread to do flow reconcile - ScheduledExecutorService ses = threadPool.getScheduledExecutor(); + ScheduledExecutorService ses = threadPoolService.getScheduledExecutor(); flowReconcileTask = new SingletonTask(ses, new Runnable() { @Override public void run() { @@ -257,14 +249,8 @@ public class FlowReconcileManager } }); - String packetInName = OFType.PACKET_IN.toClass().getName(); + String packetInName = OFType.PACKET_IN.getClass().getName(); packetInName = packetInName.substring(packetInName.lastIndexOf('.')+1); - - // Construct controller counter for the packet_in - controllerPktInCounterName = - CounterStore.createCounterName(ICounterStoreService.CONTROLLER_NAME, - -1, - packetInName); } protected void updateFlush() { @@ -296,7 +282,7 @@ public class FlowReconcileManager reconcileCapacity--; if (ofmRc != null) { ofmRcList.add(ofmRc); - ctrReconciledFlows.updateCounterWithFlush(); + ctrReconciledFlows.increment(); if (logger.isTraceEnabled()) { logger.trace("Add flow {} to be the reconcileList", ofmRc.cookie); } @@ -332,7 +318,7 @@ public class FlowReconcileManager for (OFMatchReconcile ofmRc : ofmRcList) { if (ofmRc.origReconcileQueryEvent != null) { ofmRc.origReconcileQueryEvent.evType.getDebugEvent() - .updateEventWithFlush(new FlowReconcileQueryDebugEvent( + .newEventWithFlush(new FlowReconcileQueryDebugEvent( ofmRc.origReconcileQueryEvent, "Flow Reconciliation Complete", ofmRc)); @@ -376,34 +362,32 @@ public class FlowReconcileManager * @return */ protected int getCurrentCapacity() { - ICounter pktInCounter = - counterStore.getCounter(controllerPktInCounterName); - int minFlows = MIN_FLOW_RECONCILE_PER_SECOND * - FLOW_RECONCILE_DELAY_MILLISEC / 1000; - + /*int minFlows = MIN_FLOW_RECONCILE_PER_SECOND * FLOW_RECONCILE_DELAY_MILLISEC / 1000; + + List<DebugCounterResource> contCtrRsrcList = debugCounterService.getModuleCounterValues(Controller.class.getName()); + for (DebugCounterResource dcr : contCtrRsrcList) { + if (dcr.getCounterHierarchy().equals("packet-in")) { + ctrPacketInRsrc = dcr; + break; + } + } + // If no packetInCounter, then there shouldn't be any flow. - if (pktInCounter == null || - pktInCounter.getCounterDate() == null || - pktInCounter.getCounterValue() == null) { - logger.debug("counter {} doesn't exist", - controllerPktInCounterName); + if (ctrPacketInRsrc == null || ctrPacketInRsrc.getCounterValue() == null || ctrPacketInRsrc.getCounterValue() == 0) { + logger.debug("counter {} doesn't exist", ctrPacketInRsrc); return minFlows; } - // Haven't get any counter yet. - if (lastPacketInCounter == null) { - logger.debug("First time get the count for {}", - controllerPktInCounterName); - lastPacketInCounter = (SimpleCounter) - SimpleCounter.createCounter(pktInCounter); + // We're the first packet_in + if (lastPacketInCounter.getCounterValue() == 0) { + logger.debug("First time get the count for {}", lastPacketInCounter); return minFlows; } - int pktInRate = getPktInRate(pktInCounter, new Date()); + int pktInRate = getPktInRate(ctrPacketInRsrc, new Date()); // Update the last packetInCounter - lastPacketInCounter = (SimpleCounter) - SimpleCounter.createCounter(pktInCounter); + lastPacketInCounter = new DebugCounterResource(counter); int capacity = minFlows; if ((pktInRate + MIN_FLOW_RECONCILE_PER_SECOND) <= MAX_SYSTEM_LOAD_PER_SECOND) { @@ -414,11 +398,11 @@ public class FlowReconcileManager if (logger.isTraceEnabled()) { logger.trace("Capacity is {}", capacity); } - return capacity; + return capacity; */ return 0; } - protected int getPktInRate(ICounter newCnt, Date currentTime) { - if (newCnt == null || + protected int getPktInRate(DebugCounterResource newCnt, Date currentTime) { + /*if (newCnt == null || newCnt.getCounterDate() == null || newCnt.getCounterValue() == null) { return 0; @@ -466,7 +450,7 @@ public class FlowReconcileManager break; } - return (int)(diff/elapsedTimeInSecond); + return (int)(diff/elapsedTimeInSecond); */ return 0; } } diff --git a/src/main/java/net/floodlightcontroller/flowcache/FlowReconcileQuery.java b/src/main/java/net/floodlightcontroller/flowcache/FlowReconcileQuery.java index ad6ce1289f6a3976e00008b82310b09b09cdd2fe..538e01d873eb874248aae07d1688995a5563958e 100644 --- a/src/main/java/net/floodlightcontroller/flowcache/FlowReconcileQuery.java +++ b/src/main/java/net/floodlightcontroller/flowcache/FlowReconcileQuery.java @@ -21,9 +21,9 @@ import java.lang.ref.SoftReference; 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.debugevent.IEventCategory; import net.floodlightcontroller.debugevent.IDebugEventService; -import net.floodlightcontroller.debugevent.IEventUpdater; +import net.floodlightcontroller.debugevent.MockDebugEventService; import net.floodlightcontroller.flowcache.PriorityPendingQueue.EventPriority; /** @@ -93,38 +93,37 @@ public class FlowReconcileQuery { private String description; private EventPriority priority; - private IEventUpdater<FlowReconcileQueryDebugEvent> - evReconcileQueryDebugEvent; + private IEventCategory<FlowReconcileQueryDebugEvent> eventCategory; + private IDebugEventService debugEventService; - private ReconcileQueryEvType(EventPriority priority, - String description) { + private ReconcileQueryEvType(EventPriority priority, String description) { this.priority = priority; this.description = description; } + public EventPriority getPriority() { return this.priority; } + public String getDescription() { return description; } - public void registerDebugEvent(String packageName, - IDebugEventService debugEvents) - throws MaxEventsRegistered { - try { - evReconcileQueryDebugEvent = - debugEvents.registerEvent( - packageName, - this.toString().toLowerCase().replace("_", "-"), - this.getDescription(), - EventType.ALWAYS_LOG, - FlowReconcileQueryDebugEvent.class, - 500); - } catch (MaxEventsRegistered e) { - throw e; - } + + public void registerDebugEvent(String packageName, IDebugEventService debugEvents) { + if (debugEventService == null) { + debugEventService = new MockDebugEventService(); + } + eventCategory = debugEventService.buildEvent(FlowReconcileQueryDebugEvent.class) + .setModuleName(packageName) + .setEventName(this.toString().toLowerCase().replace("_", "-")) + .setEventDescription(this.getDescription()) + .setEventType(EventType.ALWAYS_LOG) + .setBufferCapacity(500) + .register(); } - public IEventUpdater<FlowReconcileQueryDebugEvent> getDebugEvent() { - return evReconcileQueryDebugEvent; + + public IEventCategory<FlowReconcileQueryDebugEvent> getDebugEvent() { + return eventCategory; } } public FlowReconcileQuery(ReconcileQueryEvType evType) { diff --git a/src/main/java/net/floodlightcontroller/flowcache/FlowReconcileQueryDeviceMove.java b/src/main/java/net/floodlightcontroller/flowcache/FlowReconcileQueryDeviceMove.java index 245ebdf3993233397e808ec0d8c42a442daca5ef..27134c6844a8a02e1a8a56728bd5c329403bb0cb 100644 --- a/src/main/java/net/floodlightcontroller/flowcache/FlowReconcileQueryDeviceMove.java +++ b/src/main/java/net/floodlightcontroller/flowcache/FlowReconcileQueryDeviceMove.java @@ -20,7 +20,6 @@ import java.util.Arrays; import net.floodlightcontroller.devicemanager.IDevice; import net.floodlightcontroller.devicemanager.SwitchPort; -import net.floodlightcontroller.util.MACAddress; /** * The Class for FlowReconcileQuery for device move event. @@ -74,7 +73,7 @@ public class FlowReconcileQueryDeviceMove extends FlowReconcileQuery { StringBuilder builder = new StringBuilder(); builder.append("["); builder.append("Device: "); - builder.append(MACAddress.valueOf(deviceMoved.getMACAddress()).toString()); + builder.append(deviceMoved.getMACAddress().toString()); builder.append(", Old Attachment Points:"); builder.append(Arrays.toString(oldAp)); builder.append("]"); diff --git a/src/main/java/net/floodlightcontroller/flowcache/FlowReconcileQueryPortDown.java b/src/main/java/net/floodlightcontroller/flowcache/FlowReconcileQueryPortDown.java index 23c03352383ae9f070df48d596e35860c3159d6f..65361e6e99f5627f0b78fd483f8211cf284eba75 100644 --- a/src/main/java/net/floodlightcontroller/flowcache/FlowReconcileQueryPortDown.java +++ b/src/main/java/net/floodlightcontroller/flowcache/FlowReconcileQueryPortDown.java @@ -16,22 +16,23 @@ package net.floodlightcontroller.flowcache; -import org.openflow.util.HexString; +import org.projectfloodlight.openflow.types.DatapathId; +import org.projectfloodlight.openflow.types.OFPort; /** * The Class for FlowReconcileQuery for link down event. */ public class FlowReconcileQueryPortDown extends FlowReconcileQuery { /*down port switch DPID*/ - public long swId; + public DatapathId swId; /*down port ID */ - public short port; + public OFPort port; public FlowReconcileQueryPortDown() { super(ReconcileQueryEvType.LINK_DOWN); } - public FlowReconcileQueryPortDown(long swId, short portDown) { + public FlowReconcileQueryPortDown(DatapathId swId, OFPort portDown) { this(); this.swId = swId; this.port = portDown; @@ -41,8 +42,8 @@ public class FlowReconcileQueryPortDown extends FlowReconcileQuery { public int hashCode() { final int prime = 347; int result = super.hashCode(); - result = prime * result + (int)swId; - result = prime * result + port; + result = prime * result + (int) swId.getLong(); + result = prime * result + port.getPortNumber(); return result; } @@ -68,9 +69,9 @@ public class FlowReconcileQueryPortDown extends FlowReconcileQuery { StringBuilder builder = new StringBuilder(); builder.append("["); builder.append("Switch: "); - builder.append(HexString.toHexString(swId)); + builder.append(swId.toString()); builder.append(", Port: "); - builder.append(port); + builder.append(port.getPortNumber()); builder.append("]"); return builder.toString(); } diff --git a/src/main/java/net/floodlightcontroller/flowcache/IFlowReconcileListener.java b/src/main/java/net/floodlightcontroller/flowcache/IFlowReconcileListener.java index f01bd0b54fa0b5063f7e4d29536817fd1c82e7e9..3fee84743a7061343649c16049f2e77819fefc77 100644 --- a/src/main/java/net/floodlightcontroller/flowcache/IFlowReconcileListener.java +++ b/src/main/java/net/floodlightcontroller/flowcache/IFlowReconcileListener.java @@ -19,7 +19,7 @@ package net.floodlightcontroller.flowcache; import java.util.ArrayList; import net.floodlightcontroller.core.IListener; -import org.openflow.protocol.OFType; +import org.projectfloodlight.openflow.protocol.OFType; /** * The Interface IFlowReconciler. diff --git a/src/main/java/net/floodlightcontroller/flowcache/OFMatchReconcile.java b/src/main/java/net/floodlightcontroller/flowcache/OFMatchReconcile.java index 70b4d42a38efe6a06c6cc823ad58f0e660e89f25..2f81dc6ad71bd2e98401610147e00d538d1f3503 100644 --- a/src/main/java/net/floodlightcontroller/flowcache/OFMatchReconcile.java +++ b/src/main/java/net/floodlightcontroller/flowcache/OFMatchReconcile.java @@ -17,7 +17,9 @@ package net.floodlightcontroller.flowcache; import net.floodlightcontroller.core.FloodlightContext; -import org.openflow.protocol.OFMatchWithSwDpid; +import net.floodlightcontroller.util.OFMatchWithSwDpid; + +import org.projectfloodlight.openflow.types.OFPort; /** * OFMatchReconcile class to indicate result of a flow-reconciliation. @@ -66,7 +68,7 @@ public class OFMatchReconcile { /** The reconcile action. */ public ReconcileAction rcAction; /** Outport in the event of UPDATE_PATH action**/ - public short outPort; + public OFPort outPort; // The context for the reconcile action public FloodlightContext cntx; @@ -79,15 +81,15 @@ public class OFMatchReconcile { * Instantiates a new oF match reconcile object. */ public OFMatchReconcile() { - ofmWithSwDpid = new OFMatchWithSwDpid(); + ofmWithSwDpid = new OFMatchWithSwDpid(); rcAction = ReconcileAction.NO_CHANGE; cntx = new FloodlightContext(); } public OFMatchReconcile(OFMatchReconcile copy) { ofmWithSwDpid = - new OFMatchWithSwDpid(copy.ofmWithSwDpid.getOfMatch(), - copy.ofmWithSwDpid.getSwitchDataPathId()); + new OFMatchWithSwDpid(copy.ofmWithSwDpid.getMatch(), + copy.ofmWithSwDpid.getDpid()); priority = copy.priority; action = copy.action; cookie = copy.cookie; @@ -119,7 +121,7 @@ public class OFMatchReconcile { + ((newAppInstName == null) ? 0 : newAppInstName.hashCode()); result = prime * result + ((ofmWithSwDpid == null) ? 0 : ofmWithSwDpid.hashCode()); - result = prime * result + outPort; + result = prime * result + outPort.getPortNumber(); result = prime * result + priority; result = prime * result + ((rcAction == null) ? 0 : rcAction.hashCode()); diff --git a/src/main/java/net/floodlightcontroller/flowcache/PortDownReconciliation.java b/src/main/java/net/floodlightcontroller/flowcache/PortDownReconciliation.java index a2f57bb9cc166e073e2ebacf4b950fdc292b6a12..55e408d88e01cb9bfbf6545cb9b5a23a0d01c6c5 100644 --- a/src/main/java/net/floodlightcontroller/flowcache/PortDownReconciliation.java +++ b/src/main/java/net/floodlightcontroller/flowcache/PortDownReconciliation.java @@ -17,7 +17,6 @@ package net.floodlightcontroller.flowcache; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -25,24 +24,22 @@ import java.util.Map.Entry; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; -import org.openflow.protocol.OFFlowMod; -import org.openflow.protocol.OFMatch; -import org.openflow.protocol.OFMatchWithSwDpid; -import org.openflow.protocol.OFMessage; -import org.openflow.protocol.OFStatisticsRequest; -import org.openflow.protocol.OFType; -import org.openflow.protocol.statistics.OFFlowStatisticsReply; -import org.openflow.protocol.statistics.OFFlowStatisticsRequest; -import org.openflow.protocol.statistics.OFStatistics; -import org.openflow.protocol.statistics.OFStatisticsType; -import org.openflow.util.HexString; -import org.openflow.util.U16; +import org.projectfloodlight.openflow.protocol.match.Match; +import org.projectfloodlight.openflow.protocol.match.MatchField; +import org.projectfloodlight.openflow.protocol.OFFlowDelete; +import org.projectfloodlight.openflow.protocol.OFFlowStatsEntry; +import org.projectfloodlight.openflow.protocol.OFType; +import org.projectfloodlight.openflow.protocol.OFFlowStatsReply; +import org.projectfloodlight.openflow.protocol.OFFlowStatsRequest; +import org.projectfloodlight.openflow.types.OFPort; +import org.projectfloodlight.openflow.types.TableId; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import net.floodlightcontroller.core.FloodlightContext; import net.floodlightcontroller.core.IFloodlightProviderService; import net.floodlightcontroller.core.IOFSwitch; +import net.floodlightcontroller.core.internal.IOFSwitchService; import net.floodlightcontroller.core.module.FloodlightModuleContext; import net.floodlightcontroller.core.module.FloodlightModuleException; import net.floodlightcontroller.core.module.IFloodlightModule; @@ -58,6 +55,7 @@ import net.floodlightcontroller.linkdiscovery.ILinkDiscoveryService; import net.floodlightcontroller.routing.Link; import net.floodlightcontroller.topology.ITopologyListener; import net.floodlightcontroller.topology.ITopologyService; +import net.floodlightcontroller.util.OFMatchWithSwDpid; /** * Flow reconciliation module that is triggered by PORT_DOWN events. This module @@ -74,14 +72,14 @@ public class PortDownReconciliation implements IFloodlightModule, protected static Logger log = LoggerFactory.getLogger(PortDownReconciliation.class); protected ITopologyService topology; - protected IFloodlightProviderService floodlightProvider; + protected IOFSwitchService switchService; protected IFlowReconcileService frm; protected ILinkDiscoveryService lds; protected Map<Link, LinkInfo> links; protected FloodlightContext cntx; protected static boolean waiting = false; protected int statsQueryXId; - protected static List<OFFlowStatisticsReply> statsReply; + protected static List<OFFlowStatsReply> statsReply; // ITopologyListener @Override @@ -91,18 +89,16 @@ public class PortDownReconciliation implements IFloodlightModule, .equals(ILinkDiscovery.UpdateOperation.PORT_DOWN)) { // Get the switch ID for the OFMatchWithSwDpid object - long affectedSwitch = floodlightProvider.getSwitch(ldu.getSrc()) - .getId(); + IOFSwitch affectedSwitch = switchService.getSwitch(ldu.getSrc()); // Create an OFMatchReconcile object OFMatchReconcile ofmr = new OFMatchReconcile(); // Generate an OFMatch objects for the OFMatchWithSwDpid object - OFMatch match = new OFMatch().setWildcards(OFMatch.OFPFW_ALL); + Match match = affectedSwitch.getOFFactory().buildMatch().build(); // nothing specific set, so all wildcarded // Generate the OFMatchWithSwDpid - OFMatchWithSwDpid ofmatchsw = new OFMatchWithSwDpid(match, - affectedSwitch); + OFMatchWithSwDpid ofmatchsw = new OFMatchWithSwDpid(match, affectedSwitch.getId()); // Set the action to update the path to remove flows routing // towards the downed port @@ -150,7 +146,7 @@ public class PortDownReconciliation implements IFloodlightModule, void init(FloodlightModuleContext context) throws FloodlightModuleException { - floodlightProvider = context.getServiceImpl(IFloodlightProviderService.class); + switchService = context.getServiceImpl(IOFSwitchService.class); topology = context.getServiceImpl(ITopologyService.class); frm = context.getServiceImpl(IFlowReconcileService.class); lds = context.getServiceImpl(ILinkDiscoveryService.class); @@ -185,8 +181,7 @@ public class PortDownReconciliation implements IFloodlightModule, * @return the Command whether to STOP or Continue */ @Override - public net.floodlightcontroller.core.IListener.Command - reconcileFlows(ArrayList<OFMatchReconcile> ofmRcList) { + public net.floodlightcontroller.core.IListener.Command reconcileFlows(ArrayList<OFMatchReconcile> ofmRcList) { if (lds != null) { links = new HashMap<Link, LinkInfo>(); // Get all the switch links from the topology @@ -197,38 +192,37 @@ public class PortDownReconciliation implements IFloodlightModule, // update the path to a switch if (ofmr.rcAction.equals(OFMatchReconcile.ReconcileAction.UPDATE_PATH)) { // Get the switch object from the OFMatchReconcile - IOFSwitch sw = floodlightProvider - .getSwitch(ofmr.ofmWithSwDpid.getSwitchDataPathId()); + IOFSwitch sw = switchService.getSwitch(ofmr.ofmWithSwDpid.getDpid()); // Map data structure that holds the invalid matches and the // ingress ports of those matches - Map<Short, List<OFMatch>> invalidBaseIngressAndMatches = new HashMap<Short, List<OFMatch>>(); + Map<OFPort, List<Match>> invalidBaseIngressAndMatches = new HashMap<OFPort, List<Match>>(); // Get the invalid flows - List<OFFlowStatisticsReply> flows = getFlows(sw, - ofmr.outPort); + List<OFFlowStatsReply> flows = getFlows(sw, ofmr.outPort); // Analyze all the flows with outPorts equaling the downed // port and extract OFMatch's to trace back to neighbors - for (OFFlowStatisticsReply flow : flows) { - // Create a reference to the match for ease - OFMatch match = flow.getMatch(); - - // Here we utilize an index of input ports which point - // to multiple invalid matches - if (invalidBaseIngressAndMatches.containsKey(match.getInputPort())) - // If the input port is already in the index, add - // the match to it's list - invalidBaseIngressAndMatches.get(match.getInputPort()) - .add(match); - else { - // Otherwise create a new list and add it to the - // index - List<OFMatch> matches = new ArrayList<OFMatch>(); - matches.add(match); - invalidBaseIngressAndMatches.put(match.getInputPort(), - matches); - } + for (OFFlowStatsReply flow : flows) { + // Create a reference to the match for ease + for (OFFlowStatsEntry entry : flow.getEntries()) { + Match match = entry.getMatch(); + + // Here we utilize an index of input ports which point + // to multiple invalid matches + if (invalidBaseIngressAndMatches.containsKey(match.get(MatchField.IN_PORT))) + // If the input port is already in the index, add + // the match to it's list + invalidBaseIngressAndMatches.get(match.get(MatchField.IN_PORT)) + .add(match); + else { + // Otherwise create a new list and add it to the + // index + List<Match> matches = new ArrayList<Match>(); + matches.add(match); + invalidBaseIngressAndMatches.put(match.get(MatchField.IN_PORT), matches); + } + } } // Remove invalid flows from the base switch, if they exist @@ -240,7 +234,7 @@ public class PortDownReconciliation implements IFloodlightModule, // Create a list of neighboring switches we need to remove // invalid flows from - Map<IOFSwitch, Map<Short, List<OFMatch>>> neighborSwitches = new HashMap<IOFSwitch, Map<Short, List<OFMatch>>>(); + Map<IOFSwitch, Map<OFPort, List<Match>>> neighborSwitches = new HashMap<IOFSwitch, Map<OFPort, List<Match>>>(); // Loop through all the links for (Link link : links.keySet()) { @@ -248,20 +242,19 @@ public class PortDownReconciliation implements IFloodlightModule, if (link.getDst() == sw.getId()) { // Loop through the links to neighboring switches // which have invalid flows - for (Entry<Short, List<OFMatch>> invalidBaseIngressAndMatch : invalidBaseIngressAndMatches.entrySet()) { + for (Entry<OFPort, List<Match>> invalidBaseIngressAndMatch : invalidBaseIngressAndMatches.entrySet()) { // Find links on the network which link to the // ingress ports that have invalidly routed // flows if (link.getDstPort() == invalidBaseIngressAndMatch.getKey()) { - Map<Short, List<OFMatch>> invalidNeighborOutportAndMatch = new HashMap<Short, List<OFMatch>>(); + Map<OFPort, List<Match>> invalidNeighborOutportAndMatch = new HashMap<OFPort, List<Match>>(); // Insert the neighbor's outPort to the base // switch and the invalid match invalidNeighborOutportAndMatch.put(link.getSrcPort(), invalidBaseIngressAndMatch.getValue()); // Link a neighbor switch's invalid match // and outport to their Switch object - neighborSwitches.put(floodlightProvider.getSwitch(link.getSrc()), - invalidNeighborOutportAndMatch); + neighborSwitches.put(switchService.getSwitch(link.getSrc()), invalidNeighborOutportAndMatch); } } } @@ -271,11 +264,9 @@ public class PortDownReconciliation implements IFloodlightModule, // Loop through all the switches we found to have potential // issues for (IOFSwitch neighborSwitch : neighborSwitches.keySet()) { - log.debug("NeighborSwitch ID : " - + neighborSwitch.getId()); + log.debug("NeighborSwitch ID : " + neighborSwitch.getId()); if (neighborSwitches.get(neighborSwitch) != null) - deleteInvalidFlows(neighborSwitch, - neighborSwitches.get(neighborSwitch)); + deleteInvalidFlows(neighborSwitch, neighborSwitches.get(neighborSwitch)); } } return Command.CONTINUE; @@ -293,31 +284,26 @@ public class PortDownReconciliation implements IFloodlightModule, * the output action port we wish to find flows with * @return a list of OFFlowStatisticsReply objects or essentially flows */ - public List<OFFlowStatisticsReply> getFlows(IOFSwitch sw, Short outPort) { + public List<OFFlowStatsReply> getFlows(IOFSwitch sw, OFPort outPort) { - statsReply = new ArrayList<OFFlowStatisticsReply>(); - List<OFStatistics> values = null; - Future<List<OFStatistics>> future; + statsReply = new ArrayList<OFFlowStatsReply>(); + List<OFFlowStatsReply> values = null; + Future<List<OFFlowStatsReply>> future; // Statistics request object for getting flows - OFStatisticsRequest req = new OFStatisticsRequest(); - req.setStatisticType(OFStatisticsType.FLOW); - int requestLength = req.getLengthU(); - OFFlowStatisticsRequest specificReq = new OFFlowStatisticsRequest(); - specificReq.setMatch(new OFMatch().setWildcards(0xffffffff)); - specificReq.setOutPort(outPort); - specificReq.setTableId((byte) 0xff); - req.setStatistics(Collections.singletonList((OFStatistics) specificReq)); - requestLength += specificReq.getLength(); - req.setLengthU(requestLength); + OFFlowStatsRequest req = sw.getOFFactory().buildFlowStatsRequest() + .setMatch(sw.getOFFactory().buildMatch().build()) + .setOutPort(outPort) + .setTableId(TableId.ALL) //TODO @Ryan I suppose 0xFF is all tables, maybe not though... + .build(); try { // System.out.println(sw.getStatistics(req)); - future = sw.queryStatistics(req); + future = sw.writeStatsRequest(req); values = future.get(10, TimeUnit.SECONDS); if (values != null) { - for (OFStatistics stat : values) { - statsReply.add((OFFlowStatisticsReply) stat); + for (OFFlowStatsReply stat : values) { + statsReply.add(stat); } } } catch (Exception e) { @@ -334,22 +320,19 @@ public class PortDownReconciliation implements IFloodlightModule, * The specific Output Action OutPort of specific flows we wish * to delete */ - public void clearFlowMods(IOFSwitch sw, Short outPort) { - // Delete all pre-existing flows with the same output action port or - // outPort - OFMatch match = new OFMatch().setWildcards(OFMatch.OFPFW_ALL); - OFMessage fm = ((OFFlowMod) floodlightProvider.getOFMessageFactory() - .getMessage(OFType.FLOW_MOD)).setMatch(match) - .setCommand(OFFlowMod.OFPFC_DELETE) - .setOutPort(outPort) - .setLength(U16.t(OFFlowMod.MINIMUM_LENGTH)); - try { - List<OFMessage> msglist = new ArrayList<OFMessage>(1); - msglist.add(fm); - sw.write(msglist, cntx); - } catch (Exception e) { - log.error("Failed to clear flows on switch {} - {}", this, e); - } + public void clearFlowMods(IOFSwitch sw, OFPort outPort) { + // Delete all pre-existing flows with the same output action port or + // outPort + Match match = sw.getOFFactory().buildMatch().build(); + OFFlowDelete fm = sw.getOFFactory().buildFlowDelete() + .setMatch(match) + .setOutPort(outPort) + .build(); + try { + sw.write(fm); + } catch (Exception e) { + log.error("Failed to clear flows on switch {} - {}", this, e); + } } /** @@ -362,19 +345,15 @@ public class PortDownReconciliation implements IFloodlightModule, * The specific Output Action OutPort of specific flows we wish * to delete */ - public void clearFlowMods(IOFSwitch sw, OFMatch match, Short outPort) { + public void clearFlowMods(IOFSwitch sw, Match match, OFPort outPort) { // Delete pre-existing flows with the same match, and output action port // or outPort - match.setWildcards(OFMatch.OFPFW_ALL); - OFMessage fm = ((OFFlowMod) floodlightProvider.getOFMessageFactory() - .getMessage(OFType.FLOW_MOD)).setMatch(match) - .setCommand(OFFlowMod.OFPFC_DELETE) - .setOutPort(outPort) - .setLength(U16.t(OFFlowMod.MINIMUM_LENGTH)); + OFFlowDelete fm = sw.getOFFactory().buildFlowDelete() + .setMatch(match) + .setOutPort(outPort) + .build(); try { - List<OFMessage> msglist = new ArrayList<OFMessage>(1); - msglist.add(fm); - sw.write(msglist, cntx); + sw.write(fm); } catch (Exception e) { log.error("Failed to clear flows on switch {} - {}", this, e); } @@ -393,91 +372,82 @@ public class PortDownReconciliation implements IFloodlightModule, * the output action port wanted from the flows, which follows * the route to the base switch */ - public - void - deleteInvalidFlows(IOFSwitch sw, - Map<Short, List<OFMatch>> invalidOutportAndMatch) { + public void deleteInvalidFlows(IOFSwitch sw, Map<OFPort, List<Match>> invalidOutportAndMatch) { log.debug("Deleting invalid flows on switch : " + sw.getId()); // A map that holds the input ports and invalid matches on a switch - Map<Short, List<OFMatch>> invalidNeighborIngressAndMatches = new HashMap<Short, List<OFMatch>>(); + Map<OFPort, List<Match>> invalidNeighborIngressAndMatches = new HashMap<OFPort, List<Match>>(); - for (Short outPort : invalidOutportAndMatch.keySet()) { + for (OFPort outPort : invalidOutportAndMatch.keySet()) { // Get the flows on the switch - List<OFFlowStatisticsReply> flows = getFlows(sw, outPort); + List<OFFlowStatsReply> flows = getFlows(sw, outPort); // Analyze all the flows with outPorts pointing to problematic route - for (OFFlowStatisticsReply flow : flows) { - // Loop through all the problematic matches - for (OFMatch match : invalidOutportAndMatch.get(outPort)) { - // Compare the problematic matches with the match of the - // flow on the switch - if (HexString.toHexString(flow.getMatch() - .getDataLayerDestination()) - .equals(HexString.toHexString(match.getDataLayerDestination())) - && HexString.toHexString(flow.getMatch() - .getDataLayerSource()) - .equals(HexString.toHexString(match.getDataLayerSource())) - && flow.getMatch().getDataLayerType() == match.getDataLayerType() - && flow.getMatch().getDataLayerVirtualLan() == match.getDataLayerVirtualLan() - && flow.getMatch().getNetworkDestination() == match.getNetworkDestination() - && flow.getMatch().getNetworkDestinationMaskLen() == match.getNetworkDestinationMaskLen() - && flow.getMatch().getNetworkProtocol() == match.getNetworkProtocol() - && flow.getMatch().getNetworkSource() == match.getNetworkSource() - && flow.getMatch().getNetworkSourceMaskLen() == match.getNetworkSourceMaskLen() - && flow.getMatch().getNetworkTypeOfService() == match.getNetworkTypeOfService()) { - - // Here we utilize an index of input ports which point - // to multiple invalid matches - if (invalidNeighborIngressAndMatches.containsKey(match.getInputPort())) - // If the input port is already in the index, add - // the match to it's list - invalidNeighborIngressAndMatches.get(match.getInputPort()) - .add(match); - else { - // Otherwise create a new list and add it to the - // index - List<OFMatch> matches = new ArrayList<OFMatch>(); - matches.add(match); - invalidNeighborIngressAndMatches.put(match.getInputPort(), - matches); - } - // Remove flows from the switch with the invalid match - // and outPort - clearFlowMods(sw, flow.getMatch(), outPort); - } - } + for (OFFlowStatsReply flow : flows) { + for (OFFlowStatsEntry entry : flow.getEntries()) { + // Loop through all the problematic matches + for (Match match : invalidOutportAndMatch.get(outPort)) { + // Compare the problematic matches with the match of the + // flow on the switch + if (entry.getMatch().get(MatchField.ETH_DST).equals(match.get(MatchField.ETH_DST)) + && entry.getMatch().get(MatchField.ETH_SRC).equals(match.get(MatchField.ETH_SRC)) + && entry.getMatch().get(MatchField.ETH_TYPE).equals(match.get(MatchField.ETH_TYPE)) + && entry.getMatch().get(MatchField.VLAN_VID).equals(match.get(MatchField.VLAN_VID)) + && entry.getMatch().get(MatchField.IPV4_DST).equals(match.get(MatchField.IPV4_DST)) //TODO @Ryan mask lengths built into the MatchField for IPV4, I think + && entry.getMatch().get(MatchField.IP_PROTO).equals(match.get(MatchField.IP_PROTO)) + && entry.getMatch().get(MatchField.IPV4_SRC).equals(match.get(MatchField.IPV4_SRC)) // same here I think + && entry.getMatch().get(MatchField.IP_DSCP).equals(match.get(MatchField.IP_DSCP)) // dscp and ecn replace tos + && entry.getMatch().get(MatchField.IP_ECN).equals(match.get(MatchField.IP_ECN))) { + + // Here we utilize an index of input ports which point + // to multiple invalid matches + if (invalidNeighborIngressAndMatches.containsKey(match.get(MatchField.IN_PORT))) + // If the input port is already in the index, add + // the match to it's list + invalidNeighborIngressAndMatches.get(match.get(MatchField.IN_PORT)) + .add(match); + else { + // Otherwise create a new list and add it to the + // index + List<Match> matches = new ArrayList<Match>(); + matches.add(match); + invalidNeighborIngressAndMatches.put(match.get(MatchField.IN_PORT), matches); + } + // Remove flows from the switch with the invalid match + // and outPort + clearFlowMods(sw, entry.getMatch(), outPort); + } + } + } } // Create a list of neighboring switches we need to check for // invalid flows - Map<IOFSwitch, Map<Short, List<OFMatch>>> neighborSwitches = new HashMap<IOFSwitch, Map<Short, List<OFMatch>>>(); + Map<IOFSwitch, Map<OFPort, List<Match>>> neighborSwitches = new HashMap<IOFSwitch, Map<OFPort, List<Match>>>(); // Loop through all the links for (Link link : links.keySet()) { // Filter out links we care about - if (link.getDst() == sw.getId()) { + if (link.getDst().equals(sw.getId())) { // Loop through the ingressPorts that are involved in // invalid flows on neighboring switches - for (Entry<Short, List<OFMatch>> ingressPort : invalidNeighborIngressAndMatches.entrySet()) { + for (Entry<OFPort, List<Match>> ingressPort : invalidNeighborIngressAndMatches.entrySet()) { // Filter out invalid links by matching the link // destination port to our invalid flows ingress port - if (link.getDstPort() == ingressPort.getKey()) { + if (link.getDstPort().equals(ingressPort.getKey())) { // Generate a match and outPort map since I don't // want to create an object - Map<Short, List<OFMatch>> invalidNeighborOutportAndMatch = new HashMap<Short, List<OFMatch>>(); + Map<OFPort, List<Match>> invalidNeighborOutportAndMatch = new HashMap<OFPort, List<Match>>(); invalidNeighborOutportAndMatch.put(link.getSrcPort(), ingressPort.getValue()); // Link a neighbor switch's invalid match and // outport to their Switch object - neighborSwitches.put(floodlightProvider.getSwitch(link.getSrc()), - invalidNeighborOutportAndMatch); + neighborSwitches.put(switchService.getSwitch(link.getSrc()), invalidNeighborOutportAndMatch); } } } } - log.debug("We have " + neighborSwitches.size() - + " neighbors to deal with!"); + log.debug("We have " + neighborSwitches.size() + " neighbors to deal with!"); // Loop through all the neighbor switches we found to have // invalid matches @@ -485,8 +455,7 @@ public class PortDownReconciliation implements IFloodlightModule, log.debug("NeighborSwitch ID : " + neighborSwitch.getId()); // Recursively seek out and delete invalid flows on the // neighbor switch - deleteInvalidFlows(neighborSwitch, - neighborSwitches.get(neighborSwitch)); + deleteInvalidFlows(neighborSwitch, neighborSwitches.get(neighborSwitch)); } } } diff --git a/src/main/java/net/floodlightcontroller/forwarding/Forwarding.java b/src/main/java/net/floodlightcontroller/forwarding/Forwarding.java index 588e8bc3123336bf3fa7bab8a421e5ce17ff456c..abf899c9ca8e8fa160e7cb9f0197ea2e6bef4ff5 100644 --- a/src/main/java/net/floodlightcontroller/forwarding/Forwarding.java +++ b/src/main/java/net/floodlightcontroller/forwarding/Forwarding.java @@ -1,19 +1,19 @@ /** -* Copyright 2011, Big Switch Networks, Inc. -* Originally created by David Erickson, Stanford University -* -* Licensed under the Apache License, Version 2.0 (the "License"); you may -* not use this file except in compliance with the License. You may obtain -* a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -* License for the specific language governing permissions and limitations -* under the License. -**/ + * Copyright 2011, Big Switch Networks, Inc. + * Originally created by David Erickson, Stanford University + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + **/ package net.floodlightcontroller.forwarding; @@ -33,393 +33,422 @@ import net.floodlightcontroller.devicemanager.SwitchPort; import net.floodlightcontroller.core.annotations.LogMessageCategory; import net.floodlightcontroller.core.annotations.LogMessageDoc; import net.floodlightcontroller.core.annotations.LogMessageDocs; +import net.floodlightcontroller.core.internal.IOFSwitchService; 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.AppCookie; -import net.floodlightcontroller.counter.ICounterStoreService; +import net.floodlightcontroller.debugcounter.IDebugCounterService; import net.floodlightcontroller.packet.Ethernet; +import net.floodlightcontroller.packet.IPv4; import net.floodlightcontroller.routing.ForwardingBase; import net.floodlightcontroller.routing.IRoutingDecision; import net.floodlightcontroller.routing.IRoutingService; import net.floodlightcontroller.routing.Route; import net.floodlightcontroller.topology.ITopologyService; -import org.openflow.protocol.OFFlowMod; -import org.openflow.protocol.OFMatch; -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.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.types.DatapathId; +import org.projectfloodlight.openflow.types.EthType; +import org.projectfloodlight.openflow.types.IPv4Address; +import org.projectfloodlight.openflow.types.MacAddress; +import org.projectfloodlight.openflow.types.OFBufferId; +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; @LogMessageCategory("Flow Programming") public class Forwarding extends ForwardingBase implements IFloodlightModule { - protected static Logger log = LoggerFactory.getLogger(Forwarding.class); - - @Override - @LogMessageDoc(level="ERROR", - message="Unexpected decision made for this packet-in={}", - explanation="An unsupported PacketIn decision has been " + - "passed to the flow programming component", - recommendation=LogMessageDoc.REPORT_CONTROLLER_BUG) - public Command processPacketInMessage(IOFSwitch sw, OFPacketIn pi, IRoutingDecision decision, - FloodlightContext cntx) { - Ethernet eth = IFloodlightProviderService.bcStore.get(cntx, - IFloodlightProviderService.CONTEXT_PI_PAYLOAD); - - // If a decision has been made we obey it - // otherwise we just forward - if (decision != null) { - if (log.isTraceEnabled()) { - log.trace("Forwaring decision={} was made for PacketIn={}", - decision.getRoutingAction().toString(), - pi); - } - - switch(decision.getRoutingAction()) { - case NONE: - // don't do anything - return Command.CONTINUE; - case FORWARD_OR_FLOOD: - case FORWARD: - doForwardFlow(sw, pi, cntx, false); - return Command.CONTINUE; - case MULTICAST: - // treat as broadcast - doFlood(sw, pi, cntx); - return Command.CONTINUE; - case DROP: - doDropFlow(sw, pi, decision, cntx); - return Command.CONTINUE; - default: - log.error("Unexpected decision made for this packet-in={}", - pi, decision.getRoutingAction()); - return Command.CONTINUE; - } - } else { - if (log.isTraceEnabled()) { - log.trace("No decision was made for PacketIn={}, forwarding", - pi); - } - - if (eth.isBroadcast() || eth.isMulticast()) { - // For now we treat multicast as broadcast - doFlood(sw, pi, cntx); - } else { - doForwardFlow(sw, pi, cntx, false); - } - } - - return Command.CONTINUE; - } - - @LogMessageDoc(level="ERROR", - message="Failure writing drop flow mod", - explanation="An I/O error occured while trying to write a " + - "drop flow mod to a switch", - recommendation=LogMessageDoc.CHECK_SWITCH) - protected void doDropFlow(IOFSwitch sw, OFPacketIn pi, IRoutingDecision decision, FloodlightContext cntx) { - // initialize match structure and populate it using the packet - OFMatch match = new OFMatch(); - match.loadFromPacket(pi.getPacketData(), pi.getInPort()); - if (decision.getWildcards() != null) { - match.setWildcards(decision.getWildcards()); - } - - // Create flow-mod based on packet-in and src-switch - OFFlowMod fm = - (OFFlowMod) floodlightProvider.getOFMessageFactory() - .getMessage(OFType.FLOW_MOD); - List<OFAction> actions = new ArrayList<OFAction>(); // Set no action to - // drop - long cookie = AppCookie.makeCookie(FORWARDING_APP_ID, 0); - - fm.setCookie(cookie) - .setHardTimeout((short) 0) - .setIdleTimeout((short) 5) - .setBufferId(OFPacketOut.BUFFER_ID_NONE) - .setMatch(match) - .setActions(actions) - .setLengthU(OFFlowMod.MINIMUM_LENGTH); // +OFActionOutput.MINIMUM_LENGTH); - - try { - if (log.isDebugEnabled()) { - log.debug("write drop flow-mod sw={} match={} flow-mod={}", - new Object[] { sw, match, fm }); - } - messageDamper.write(sw, fm, cntx); - } catch (IOException e) { - log.error("Failure writing drop flow mod", e); - } - } - - protected void doForwardFlow(IOFSwitch sw, OFPacketIn pi, - FloodlightContext cntx, - boolean requestFlowRemovedNotifn) { - OFMatch match = new OFMatch(); - match.loadFromPacket(pi.getPacketData(), pi.getInPort()); - - // Check if we have the location of the destination - IDevice dstDevice = - IDeviceService.fcStore. - get(cntx, IDeviceService.CONTEXT_DST_DEVICE); - - if (dstDevice != null) { - IDevice srcDevice = - IDeviceService.fcStore. - get(cntx, IDeviceService.CONTEXT_SRC_DEVICE); - Long srcIsland = topology.getL2DomainId(sw.getId()); - - if (srcDevice == null) { - log.debug("No device entry found for source device"); - return; - } - if (srcIsland == null) { - log.debug("No openflow island found for source {}/{}", - sw.getStringId(), pi.getInPort()); - 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()) { - long dstSwDpid = dstDap.getSwitchDPID(); - Long dstIsland = topology.getL2DomainId(dstSwDpid); - if ((dstIsland != null) && dstIsland.equals(srcIsland)) { - on_same_island = true; - if ((sw.getId() == dstSwDpid) && - (pi.getInPort() == dstDap.getPort())) { - on_same_if = true; - } - break; - } - } - - 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; - } - - if (on_same_if) { - if (log.isTraceEnabled()) { - log.trace("Both source and destination are on the same " + - "switch/port {}/{}, Action = NOP", - sw.toString(), pi.getInPort()); - } - 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. - Long srcCluster = - topology.getL2DomainId(srcDap.getSwitchDPID()); - Long dstCluster = - topology.getL2DomainId(dstDap.getSwitchDPID()); - - int srcVsDest = srcCluster.compareTo(dstCluster); - if (srcVsDest == 0) { - if (!srcDap.equals(dstDap)) { - Route route = - routingEngine.getRoute(srcDap.getSwitchDPID(), - (short)srcDap.getPort(), - dstDap.getSwitchDPID(), - (short)dstDap.getPort(), 0); //cookie = 0, i.e., default route - if (route != null) { - if (log.isTraceEnabled()) { - log.trace("pushRoute match={} route={} " + - "destination={}:{}", - new Object[] {match, route, - dstDap.getSwitchDPID(), - dstDap.getPort()}); - } - long cookie = - AppCookie.makeCookie(FORWARDING_APP_ID, 0); - - // if there is prior routing decision use wildcard - Integer wildcard_hints = null; - IRoutingDecision decision = null; - if (cntx != null) { - decision = IRoutingDecision.rtStore - .get(cntx, - IRoutingDecision.CONTEXT_DECISION); - } - if (decision != null) { - wildcard_hints = decision.getWildcards(); - } else { - // L2 only wildcard if there is no prior route decision - wildcard_hints = ((Integer) sw - .getAttribute(IOFSwitch.PROP_FASTWILDCARDS)) - .intValue() - & ~OFMatch.OFPFW_IN_PORT - & ~OFMatch.OFPFW_DL_VLAN - & ~OFMatch.OFPFW_DL_SRC - & ~OFMatch.OFPFW_DL_DST - & ~OFMatch.OFPFW_NW_SRC_MASK - & ~OFMatch.OFPFW_NW_DST_MASK; - } - - pushRoute(route, match, wildcard_hints, pi, sw.getId(), cookie, - cntx, requestFlowRemovedNotifn, false, - OFFlowMod.OFPFC_ADD); - } - } - iSrcDaps++; - iDstDaps++; - } else if (srcVsDest < 0) { - iSrcDaps++; - } else { - iDstDaps++; - } - } - } else { - // Flood since we don't know the dst device - doFlood(sw, pi, cntx); - } - } - - /** - * Creates a OFPacketOut with the OFPacketIn data that is flooded on all ports unless - * the port is blocked, in which case the packet will be dropped. - * @param sw The switch that receives the OFPacketIn - * @param pi The OFPacketIn that came to the switch - * @param cntx The FloodlightContext associated with this OFPacketIn - */ - @LogMessageDoc(level="ERROR", - message="Failure writing PacketOut " + - "switch={switch} packet-in={packet-in} " + - "packet-out={packet-out}", - explanation="An I/O error occured while writing a packet " + - "out message to the switch", - recommendation=LogMessageDoc.CHECK_SWITCH) - protected void doFlood(IOFSwitch sw, OFPacketIn pi, FloodlightContext cntx) { - if (topology.isIncomingBroadcastAllowed(sw.getId(), - pi.getInPort()) == false) { - if (log.isTraceEnabled()) { - log.trace("doFlood, drop broadcast packet, pi={}, " + - "from a blocked port, srcSwitch=[{},{}], linkInfo={}", - new Object[] {pi, sw.getId(),pi.getInPort()}); - } - return; - } - - // Set Action to flood - OFPacketOut po = - (OFPacketOut) floodlightProvider.getOFMessageFactory().getMessage(OFType.PACKET_OUT); - List<OFAction> actions = new ArrayList<OFAction>(); - if (sw.hasAttribute(IOFSwitch.PROP_SUPPORTS_OFPP_FLOOD)) { - actions.add(new OFActionOutput(OFPort.OFPP_FLOOD.getValue(), - (short)0xFFFF)); - } else { - actions.add(new OFActionOutput(OFPort.OFPP_ALL.getValue(), - (short)0xFFFF)); - } - po.setActions(actions); - po.setActionsLength((short) OFActionOutput.MINIMUM_LENGTH); - - // set buffer-id, in-port and packet-data based on packet-in - short poLength = (short)(po.getActionsLength() + OFPacketOut.MINIMUM_LENGTH); - po.setBufferId(OFPacketOut.BUFFER_ID_NONE); - po.setInPort(pi.getInPort()); - byte[] packetData = pi.getPacketData(); - poLength += packetData.length; - po.setPacketData(packetData); - po.setLength(poLength); - - try { - if (log.isTraceEnabled()) { - log.trace("Writing flood PacketOut switch={} packet-in={} packet-out={}", - new Object[] {sw, pi, po}); - } - messageDamper.write(sw, po, cntx); - } catch (IOException e) { - log.error("Failure writing PacketOut switch={} packet-in={} packet-out={}", - new Object[] {sw, pi, po}, e); - } - - return; - } - - // IFloodlightModule methods - - @Override - public Collection<Class<? extends IFloodlightService>> getModuleServices() { - // We don't export any services - return null; - } - - @Override - public Map<Class<? extends IFloodlightService>, IFloodlightService> - getServiceImpls() { - // We don't have any services - return null; - } - - @Override - public Collection<Class<? extends IFloodlightService>> getModuleDependencies() { - Collection<Class<? extends IFloodlightService>> l = - new ArrayList<Class<? extends IFloodlightService>>(); - l.add(IFloodlightProviderService.class); - l.add(IDeviceService.class); - l.add(IRoutingService.class); - l.add(ITopologyService.class); - l.add(ICounterStoreService.class); - return l; - } - - @Override - @LogMessageDocs({ - @LogMessageDoc(level="WARN", - message="Error parsing flow idle timeout, " + - "using default of {number} seconds", - explanation="The properties file contains an invalid " + - "flow idle timeout", - recommendation="Correct the idle timeout in the " + - "properties file."), - @LogMessageDoc(level="WARN", - message="Error parsing flow hard timeout, " + - "using default of {number} seconds", - explanation="The properties file contains an invalid " + - "flow hard timeout", - recommendation="Correct the hard timeout in the " + - "properties file.") - }) - public void init(FloodlightModuleContext context) throws FloodlightModuleException { - super.init(); - this.floodlightProvider = context.getServiceImpl(IFloodlightProviderService.class); - this.deviceManager = context.getServiceImpl(IDeviceService.class); - this.routingEngine = context.getServiceImpl(IRoutingService.class); - this.topology = context.getServiceImpl(ITopologyService.class); - this.counterStore = context.getServiceImpl(ICounterStoreService.class); - } - - @Override - public void startUp(FloodlightModuleContext context) { - super.startUp(); - } -} + protected static Logger log = LoggerFactory.getLogger(Forwarding.class); + + protected static int DEFAULT_HARD_TIMEOUT = 0; // not final b/c could be configured from config file + protected static int DEFAULT_IDLE_TIMEOUT = 5; + + @Override + @LogMessageDoc(level="ERROR", + message="Unexpected decision made for this packet-in={}", + explanation="An unsupported PacketIn decision has been " + + "passed to the flow programming component", + recommendation=LogMessageDoc.REPORT_CONTROLLER_BUG) + public Command processPacketInMessage(IOFSwitch sw, OFPacketIn pi, IRoutingDecision decision, FloodlightContext cntx) { + Ethernet eth = IFloodlightProviderService.bcStore.get(cntx, IFloodlightProviderService.CONTEXT_PI_PAYLOAD); + + // We found a routing decision (i.e. Firewall is enabled... it's the only thing that makes RoutingDecisions) + if (decision != null) { + if (log.isTraceEnabled()) { + log.trace("Forwaring decision={} was made for PacketIn={}", decision.getRoutingAction().toString(), pi); + } + + switch(decision.getRoutingAction()) { + case NONE: + // don't do anything + return Command.CONTINUE; + case FORWARD_OR_FLOOD: + case FORWARD: + doForwardFlow(sw, pi, cntx, false); + return Command.CONTINUE; + case MULTICAST: + // treat as broadcast + doFlood(sw, pi, cntx); + return Command.CONTINUE; + case DROP: + doDropFlow(sw, pi, decision, cntx); + return Command.CONTINUE; + default: + log.error("Unexpected decision made for this packet-in={}", pi, decision.getRoutingAction()); + return Command.CONTINUE; + } + } else { // No routing decision was found. Forward to destination or flood if bcast or mcast. + if (log.isTraceEnabled()) { + log.trace("No decision was made for PacketIn={}, forwarding", pi); + } + + if (eth.isBroadcast() || eth.isMulticast()) { + doFlood(sw, pi, cntx); + } else { + doForwardFlow(sw, pi, cntx, false); + } + } + + return Command.CONTINUE; + } + + @LogMessageDoc(level="ERROR", + message="Failure writing drop flow mod", + explanation="An I/O error occured while trying to write a " + + "drop flow mod to a switch", + recommendation=LogMessageDoc.CHECK_SWITCH) + protected void doDropFlow(IOFSwitch sw, OFPacketIn pi, IRoutingDecision decision, FloodlightContext cntx) { + // initialize match structure and populate it based on the packet in's match + Match.Builder mb = null; + if (decision.getMatch() != null) { + /* TODO @Ryan This routing decision should be a match object with all appropriate fields set, + * not just masked. If it's a decision that matches the packet we received, then simply setting + * the masks to the new match will create the same match in the end. We can just use the routing + * match object instead (right?). + * + * The Firewall is currently the only module/service that sets routing decisions in the context + * store (or instantiates any for that matter). It's disabled by default, so as-is a decision's + * match should always be null, meaning this will never be true. + */ + mb = decision.getMatch().createBuilder(); + } else { + mb = pi.getMatch().createBuilder(); // otherwise no route is known so go based on packet's match object + } + + 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); + + fmb.setCookie(cookie) + .setHardTimeout(DEFAULT_HARD_TIMEOUT) + .setIdleTimeout(DEFAULT_IDLE_TIMEOUT) + .setBufferId(OFBufferId.NO_BUFFER) + .setMatch(mb.build()) + .setActions(actions); // empty list + + try { + if (log.isDebugEnabled()) { + log.debug("write drop flow-mod sw={} match={} flow-mod={}", + new Object[] { sw, mb.build(), fmb.build() }); + } + boolean dampened = messageDamper.write(sw, fmb.build(), cntx); + log.debug("OFMessage dampened: {}", dampened); + } catch (IOException e) { + log.error("Failure writing drop flow mod", e); + } + } + + protected void doForwardFlow(IOFSwitch sw, OFPacketIn pi, FloodlightContext cntx, boolean requestFlowRemovedNotifn) { + OFPort inPort = (pi.getVersion().compareTo(OFVersion.OF_13) < 0 ? pi.getInPort() : pi.getMatch().get(MatchField.IN_PORT)); + // Check if we have the location of the destination + IDevice dstDevice = IDeviceService.fcStore.get(cntx, IDeviceService.CONTEXT_DST_DEVICE); + + if (dstDevice != null) { + IDevice srcDevice = IDeviceService.fcStore.get(cntx, IDeviceService.CONTEXT_SRC_DEVICE); + DatapathId srcIsland = topologyService.getL2DomainId(sw.getId()); + + if (srcDevice == null) { + log.debug("No device entry found for source device"); + return; + } + if (srcIsland == null) { + log.debug("No openflow island found for source {}/{}", + sw.getId().toString(), (pi.getVersion().compareTo(OFVersion.OF_13) < 0 ? pi.getInPort() : pi.getMatch().get(MatchField.IN_PORT))); + 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); + if ((dstIsland != null) && dstIsland.equals(srcIsland)) { + on_same_island = true; + if ((sw.getId().equals(dstSwDpid)) && ((pi.getVersion().compareTo(OFVersion.OF_13) < 0 ? pi.getInPort() : pi.getMatch().get(MatchField.IN_PORT)).equals(dstDap.getPort()))) { + on_same_if = true; + } + break; + } + } + + 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; + } + + if (on_same_if) { + if (log.isTraceEnabled()) { + log.trace("Both source and destination are on the same " + + "switch/port {}/{}, Action = NOP", + sw.toString(), (pi.getVersion().compareTo(OFVersion.OF_13) < 0 ? pi.getInPort() : pi.getMatch().get(MatchField.IN_PORT))); + } + 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); + + // if there is prior routing decision use route's match + Match routeMatch = null; + IRoutingDecision decision = null; + if (cntx != null) { + decision = IRoutingDecision.rtStore.get(cntx, IRoutingDecision.CONTEXT_DECISION); + } + if (decision != null) { + routeMatch = decision.getMatch(); + } else { + // The packet in match will only contain the port number. + // We need to add in specifics for the hosts we're routing between. + Ethernet eth = IFloodlightProviderService.bcStore.get(cntx, IFloodlightProviderService.CONTEXT_PI_PAYLOAD); + VlanVid vlan = VlanVid.ofVlan(eth.getVlanID()); + MacAddress srcMac = eth.getSourceMACAddress(); + MacAddress dstMac = eth.getDestinationMACAddress(); + + // A retentive builder will remember all MatchFields of the parent the builder was generated from + // With a normal builder, all parent MatchFields will be lost if any MatchFields are added, mod, del + Match.Builder mb = sw.getOFFactory().buildMatch(); + mb.setExact(MatchField.IN_PORT, inPort) + .setExact(MatchField.ETH_SRC, srcMac) + .setExact(MatchField.ETH_DST, dstMac); + + if (!vlan.equals(VlanVid.ZERO)) { + mb.setExact(MatchField.VLAN_VID, OFVlanVidMatch.ofVlanVid(vlan)); + } + + if (eth.getEtherType() == Ethernet.TYPE_IPv4) { + IPv4 ip = (IPv4) eth.getPayload(); + IPv4Address srcIp = ip.getSourceAddress(); + IPv4Address dstIp = ip.getDestinationAddress(); + mb.setExact(MatchField.IPV4_SRC, srcIp) + .setExact(MatchField.IPV4_DST, dstIp) + .setExact(MatchField.ETH_TYPE, EthType.IPv4); + } else if (eth.getEtherType() == Ethernet.TYPE_ARP) { + mb.setExact(MatchField.ETH_TYPE, EthType.ARP); + } //TODO @Ryan should probably include other ethertypes + + routeMatch = mb.build(); + } + + pushRoute(route, routeMatch, pi, sw.getId(), cookie, + cntx, requestFlowRemovedNotifn, false, + OFFlowModCommand.ADD); + } + } + iSrcDaps++; + iDstDaps++; + } else if (srcVsDest < 0) { + iSrcDaps++; + } else { + iDstDaps++; + } + } + } else { + // Flood since we don't know the dst device + doFlood(sw, pi, cntx); + } + } + + /** + * Creates a OFPacketOut with the OFPacketIn data that is flooded on all ports unless + * the port is blocked, in which case the packet will be dropped. + * @param sw The switch that receives the OFPacketIn + * @param pi The OFPacketIn that came to the switch + * @param cntx The FloodlightContext associated with this OFPacketIn + */ + @LogMessageDoc(level="ERROR", + message="Failure writing PacketOut " + + "switch={switch} packet-in={packet-in} " + + "packet-out={packet-out}", + explanation="An I/O error occured while writing a packet " + + "out message to the switch", + recommendation=LogMessageDoc.CHECK_SWITCH) + protected void doFlood(IOFSwitch sw, OFPacketIn pi, FloodlightContext cntx) { + if (topologyService.isIncomingBroadcastAllowed(sw.getId(), (pi.getVersion().compareTo(OFVersion.OF_13) < 0 ? pi.getInPort() : pi.getMatch().get(MatchField.IN_PORT))) == false) { + if (log.isTraceEnabled()) { + log.trace("doFlood, drop broadcast packet, pi={}, " + + "from a blocked port, srcSwitch=[{},{}], linkInfo={}", + new Object[] {pi, sw.getId(), (pi.getVersion().compareTo(OFVersion.OF_13) < 0 ? pi.getInPort() : pi.getMatch().get(MatchField.IN_PORT))}); + } + 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)); + } + pob.setActions(actions); + + // set buffer-id, in-port and packet-data based on packet-in + pob.setBufferId(OFBufferId.NO_BUFFER); + pob.setInPort((pi.getVersion().compareTo(OFVersion.OF_13) < 0 ? pi.getInPort() : pi.getMatch().get(MatchField.IN_PORT))); + pob.setData(pi.getData()); + + try { + if (log.isTraceEnabled()) { + log.trace("Writing flood PacketOut switch={} packet-in={} packet-out={}", + new Object[] {sw, pi, pob.build()}); + } + messageDamper.write(sw, pob.build(), cntx); + } catch (IOException e) { + log.error("Failure writing PacketOut switch={} packet-in={} packet-out={}", + new Object[] {sw, pi, pob.build()}, e); + } + + return; + } + + // IFloodlightModule methods + + @Override + public Collection<Class<? extends IFloodlightService>> getModuleServices() { + // We don't export any services + return null; + } + + @Override + public Map<Class<? extends IFloodlightService>, IFloodlightService> + getServiceImpls() { + // We don't have any services + return null; + } + + @Override + public Collection<Class<? extends IFloodlightService>> getModuleDependencies() { + Collection<Class<? extends IFloodlightService>> l = + new ArrayList<Class<? extends IFloodlightService>>(); + l.add(IFloodlightProviderService.class); + l.add(IDeviceService.class); + l.add(IRoutingService.class); + l.add(ITopologyService.class); + l.add(IDebugCounterService.class); + return l; + } + + @Override + @LogMessageDocs({ + @LogMessageDoc(level="WARN", + message="Error parsing flow idle timeout, " + + "using default of {number} seconds", + explanation="The properties file contains an invalid " + + "flow idle timeout", + recommendation="Correct the idle timeout in the " + + "properties file."), + @LogMessageDoc(level="WARN", + message="Error parsing flow hard timeout, " + + "using default of {number} seconds", + explanation="The properties file contains an invalid " + + "flow hard timeout", + recommendation="Correct the hard timeout in the " + + "properties file.") + }) + public void init(FloodlightModuleContext context) throws FloodlightModuleException { + super.init(); + this.floodlightProviderService = context.getServiceImpl(IFloodlightProviderService.class); + this.deviceManagerService = context.getServiceImpl(IDeviceService.class); + this.routingEngineService = context.getServiceImpl(IRoutingService.class); + this.topologyService = context.getServiceImpl(ITopologyService.class); + this.debugCounterService = context.getServiceImpl(IDebugCounterService.class); + this.switchService = context.getServiceImpl(IOFSwitchService.class); + + Map<String, String> configParameters = context.getConfigParams(this); + String tmp = configParameters.get("hard-timeout"); + if (tmp != null) { + DEFAULT_HARD_TIMEOUT = Integer.parseInt(tmp); + log.info("Default hard timeout set to {}.", DEFAULT_HARD_TIMEOUT); + } else { + log.info("Default hard timeout not configured. Using {}.", DEFAULT_HARD_TIMEOUT); + } + tmp = configParameters.get("idle-timeout"); + if (tmp != null) { + DEFAULT_IDLE_TIMEOUT = Integer.parseInt(tmp); + log.info("Default idle timeout set to {}.", DEFAULT_IDLE_TIMEOUT); + } else { + log.info("Default idle timeout not configured. Using {}.", DEFAULT_IDLE_TIMEOUT); + } + + + + } + + @Override + public void startUp(FloodlightModuleContext context) { + super.startUp(); + } +} \ No newline at end of file diff --git a/src/main/java/net/floodlightcontroller/hub/Hub.java b/src/main/java/net/floodlightcontroller/hub/Hub.java index 4a3549ba47224cc0b8a7821798f589d61fbabb52..ed5efe236d83ce6d617f63e6425d556458caa175 100644 --- a/src/main/java/net/floodlightcontroller/hub/Hub.java +++ b/src/main/java/net/floodlightcontroller/hub/Hub.java @@ -17,7 +17,6 @@ package net.floodlightcontroller.hub; -import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -32,25 +31,26 @@ import net.floodlightcontroller.core.module.FloodlightModuleException; import net.floodlightcontroller.core.module.IFloodlightModule; import net.floodlightcontroller.core.module.IFloodlightService; -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.openflow.util.U16; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import org.projectfloodlight.openflow.protocol.OFFlowAdd; +import org.projectfloodlight.openflow.protocol.OFMessage; +import org.projectfloodlight.openflow.protocol.OFPacketIn; +import org.projectfloodlight.openflow.protocol.OFPacketOut; +import org.projectfloodlight.openflow.protocol.OFType; +import org.projectfloodlight.openflow.protocol.OFVersion; +import org.projectfloodlight.openflow.protocol.action.OFAction; +import org.projectfloodlight.openflow.protocol.action.OFActionOutput; +import org.projectfloodlight.openflow.protocol.match.MatchField; +import org.projectfloodlight.openflow.types.OFBufferId; +import org.projectfloodlight.openflow.types.OFPort; /** * * @author David Erickson (daviderickson@cs.stanford.edu) - 04/04/10 */ public class Hub implements IFloodlightModule, IOFMessageListener { - protected static Logger log = LoggerFactory.getLogger(Hub.class); + private enum HubType {USE_PACKET_OUT, USE_FLOW_MOD}; - protected IFloodlightProviderService floodlightProvider; + private IFloodlightProviderService floodlightProvider; /** * @param floodlightProvider the floodlightProvider to set @@ -65,35 +65,53 @@ public class Hub implements IFloodlightModule, IOFMessageListener { } public Command receive(IOFSwitch sw, OFMessage msg, FloodlightContext cntx) { - OFPacketIn pi = (OFPacketIn) msg; - OFPacketOut po = (OFPacketOut) floodlightProvider.getOFMessageFactory() - .getMessage(OFType.PACKET_OUT); - po.setBufferId(pi.getBufferId()) - .setInPort(pi.getInPort()); + OFMessage outMessage; + HubType ht = HubType.USE_FLOW_MOD; + switch (ht) { + case USE_FLOW_MOD: + outMessage = createHubFlowMod(sw, msg); + break; + default: + case USE_PACKET_OUT: + outMessage = createHubPacketOut(sw, msg); + break; + } + sw.write(outMessage); + + return Command.CONTINUE; + } + + private OFMessage createHubFlowMod(IOFSwitch sw, OFMessage msg) { + OFPacketIn pi = (OFPacketIn) msg; + OFFlowAdd.Builder fmb = sw.getOFFactory().buildFlowAdd(); + + fmb.setBufferId(pi.getBufferId()) + .setXid(pi.getXid()); // set actions - OFActionOutput action = new OFActionOutput() - .setPort(OFPort.OFPP_FLOOD.getValue()); - po.setActions(Collections.singletonList((OFAction)action)); - po.setActionsLength((short) OFActionOutput.MINIMUM_LENGTH); - - // set data if is is included in the packetin - if (pi.getBufferId() == OFPacketOut.BUFFER_ID_NONE) { - byte[] packetData = pi.getPacketData(); - po.setLength(U16.t(OFPacketOut.MINIMUM_LENGTH - + po.getActionsLength() + packetData.length)); - po.setPacketData(packetData); - } else { - po.setLength(U16.t(OFPacketOut.MINIMUM_LENGTH - + po.getActionsLength())); - } - try { - sw.write(po, cntx); - } catch (IOException e) { - log.error("Failure writing PacketOut", e); - } + OFActionOutput.Builder actionBuilder = sw.getOFFactory().actions().buildOutput(); + actionBuilder.setPort(OFPort.FLOOD); + fmb.setActions(Collections.singletonList((OFAction) actionBuilder.build())); - return Command.CONTINUE; + return fmb.build(); + } + + private OFMessage createHubPacketOut(IOFSwitch sw, OFMessage msg) { + OFPacketIn pi = (OFPacketIn) msg; + OFPacketOut.Builder pob = sw.getOFFactory().buildPacketOut(); + pob.setBufferId(pi.getBufferId()).setXid(pi.getXid()).setInPort((pi.getVersion().compareTo(OFVersion.OF_13) < 0 ? pi.getInPort() : pi.getMatch().get(MatchField.IN_PORT))); + + // set actions + OFActionOutput.Builder actionBuilder = sw.getOFFactory().actions().buildOutput(); + actionBuilder.setPort(OFPort.FLOOD); + pob.setActions(Collections.singletonList((OFAction) actionBuilder.build())); + + // set data if it is included in the packetin + if (pi.getBufferId() == OFBufferId.NO_BUFFER) { + byte[] packetData = pi.getData(); + pob.setData(packetData); + } + return pob.build(); } @Override @@ -133,8 +151,7 @@ public class Hub implements IFloodlightModule, IOFMessageListener { @Override public void init(FloodlightModuleContext context) throws FloodlightModuleException { - floodlightProvider = - context.getServiceImpl(IFloodlightProviderService.class); + floodlightProvider = context.getServiceImpl(IFloodlightProviderService.class); } @Override diff --git a/src/main/java/net/floodlightcontroller/learningswitch/ILearningSwitchService.java b/src/main/java/net/floodlightcontroller/learningswitch/ILearningSwitchService.java index 92352e93d60a5cfbe063e09c98af66902a9e190d..88e0c4b63ce632858e9c63b954c5fb144995bfce 100644 --- a/src/main/java/net/floodlightcontroller/learningswitch/ILearningSwitchService.java +++ b/src/main/java/net/floodlightcontroller/learningswitch/ILearningSwitchService.java @@ -18,6 +18,8 @@ package net.floodlightcontroller.learningswitch; import java.util.Map; +import org.projectfloodlight.openflow.types.OFPort; + import net.floodlightcontroller.core.IOFSwitch; import net.floodlightcontroller.core.module.IFloodlightService; import net.floodlightcontroller.core.types.MacVlanPair; @@ -27,5 +29,5 @@ public interface ILearningSwitchService extends IFloodlightService { * Returns the LearningSwitch's learned host table * @return The learned host table */ - public Map<IOFSwitch, Map<MacVlanPair,Short>> getTable(); + public Map<IOFSwitch, Map<MacVlanPair, OFPort>> getTable(); } diff --git a/src/main/java/net/floodlightcontroller/learningswitch/LearningSwitch.java b/src/main/java/net/floodlightcontroller/learningswitch/LearningSwitch.java index 2e8ce6f1baf678771543273e80e0f3dad0277054..7e347294c30dd4822e83e3c3980ea35ac49489f0 100644 --- a/src/main/java/net/floodlightcontroller/learningswitch/LearningSwitch.java +++ b/src/main/java/net/floodlightcontroller/learningswitch/LearningSwitch.java @@ -29,14 +29,14 @@ package net.floodlightcontroller.learningswitch; -import java.io.IOException; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import net.floodlightcontroller.core.FloodlightContext; @@ -48,22 +48,28 @@ import net.floodlightcontroller.core.module.FloodlightModuleException; import net.floodlightcontroller.core.module.IFloodlightModule; import net.floodlightcontroller.core.module.IFloodlightService; import net.floodlightcontroller.core.types.MacVlanPair; -import net.floodlightcontroller.counter.ICounterStoreService; -import net.floodlightcontroller.packet.Ethernet; +import net.floodlightcontroller.debugcounter.IDebugCounterService; import net.floodlightcontroller.restserver.IRestApiService; -import org.openflow.protocol.OFFlowMod; -import org.openflow.protocol.OFFlowRemoved; -import org.openflow.protocol.OFMatch; -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.openflow.util.HexString; -import org.openflow.util.LRULinkedHashMap; +import org.projectfloodlight.openflow.protocol.OFFlowMod; +import org.projectfloodlight.openflow.protocol.OFFlowRemoved; +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.OFFlowModFlags; +import org.projectfloodlight.openflow.protocol.OFMessage; +import org.projectfloodlight.openflow.protocol.OFPacketIn; +import org.projectfloodlight.openflow.protocol.OFPacketOut; +import org.projectfloodlight.openflow.protocol.OFType; +import org.projectfloodlight.openflow.protocol.action.OFAction; +import org.projectfloodlight.openflow.types.IpProtocol; +import org.projectfloodlight.openflow.types.MacAddress; +import org.projectfloodlight.openflow.types.OFBufferId; +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.util.LRULinkedHashMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -72,12 +78,12 @@ public class LearningSwitch protected static Logger log = LoggerFactory.getLogger(LearningSwitch.class); // Module dependencies - protected IFloodlightProviderService floodlightProvider; - protected ICounterStoreService counterStore; - protected IRestApiService restApi; + protected IFloodlightProviderService floodlightProviderService; + protected IDebugCounterService debugCounterService; + protected IRestApiService restApiService; // Stores the learned state for each switch - protected Map<IOFSwitch, Map<MacVlanPair,Short>> macVlanToSwitchPortMap; + protected Map<IOFSwitch, Map<MacVlanPair, OFPort>> macVlanToSwitchPortMap; // flow-mod - for use in the cookie public static final int LEARNING_SWITCH_APP_ID = 1; @@ -101,8 +107,8 @@ public class LearningSwitch /** * @param floodlightProvider the floodlightProvider to set */ - public void setFloodlightProvider(IFloodlightProviderService floodlightProvider) { - this.floodlightProvider = floodlightProvider; + public void setFloodlightProvider(IFloodlightProviderService floodlightProviderService) { + this.floodlightProviderService = floodlightProviderService; } @Override @@ -117,18 +123,18 @@ public class LearningSwitch * @param vlan The VLAN that the host is on * @param portVal The switchport that the host is on */ - protected void addToPortMap(IOFSwitch sw, long mac, short vlan, short portVal) { - Map<MacVlanPair,Short> swMap = macVlanToSwitchPortMap.get(sw); + protected void addToPortMap(IOFSwitch sw, MacAddress mac, VlanVid vlan, OFPort portVal) { + Map<MacVlanPair, OFPort> swMap = macVlanToSwitchPortMap.get(sw); - if (vlan == (short) 0xffff) { + if (vlan == VlanVid.FULL_MASK) { // OFMatch.loadFromPacket sets VLAN ID to 0xffff if the packet contains no VLAN tag; // for our purposes that is equivalent to the default VLAN ID 0 - vlan = 0; + vlan = VlanVid.ofVlan(0); } if (swMap == null) { // May be accessed by REST API so we need to make it thread safe - swMap = Collections.synchronizedMap(new LRULinkedHashMap<MacVlanPair,Short>(MAX_MACS_PER_SWITCH)); + swMap = Collections.synchronizedMap(new LRULinkedHashMap<MacVlanPair, OFPort>(MAX_MACS_PER_SWITCH)); macVlanToSwitchPortMap.put(sw, swMap); } swMap.put(new MacVlanPair(mac, vlan), portVal); @@ -140,13 +146,15 @@ public class LearningSwitch * @param mac The MAC address of the host to remove * @param vlan The VLAN that the host is on */ - protected void removeFromPortMap(IOFSwitch sw, long mac, short vlan) { - if (vlan == (short) 0xffff) { - vlan = 0; + protected void removeFromPortMap(IOFSwitch sw, MacAddress mac, VlanVid vlan) { + if (vlan == VlanVid.FULL_MASK) { + vlan = VlanVid.ofVlan(0); } - Map<MacVlanPair,Short> swMap = macVlanToSwitchPortMap.get(sw); - if (swMap != null) + + Map<MacVlanPair, OFPort> swMap = macVlanToSwitchPortMap.get(sw); + if (swMap != null) { swMap.remove(new MacVlanPair(mac, vlan)); + } } /** @@ -156,13 +164,14 @@ public class LearningSwitch * @param vlan The VLAN number to get * @return The port the host is on */ - public Short getFromPortMap(IOFSwitch sw, long mac, short vlan) { - if (vlan == (short) 0xffff) { - vlan = 0; + public OFPort getFromPortMap(IOFSwitch sw, MacAddress mac, VlanVid vlan) { + if (vlan == VlanVid.FULL_MASK) { + vlan = VlanVid.FULL_MASK; } - Map<MacVlanPair,Short> swMap = macVlanToSwitchPortMap.get(sw); - if (swMap != null) + Map<MacVlanPair, OFPort> swMap = macVlanToSwitchPortMap.get(sw); + if (swMap != null) { return swMap.get(new MacVlanPair(mac, vlan)); + } // if none found return null; @@ -180,13 +189,14 @@ public class LearningSwitch * @param sw The switch to clear the mapping for */ public void clearLearnedTable(IOFSwitch sw) { - Map<MacVlanPair, Short> swMap = macVlanToSwitchPortMap.get(sw); - if (swMap != null) + Map<MacVlanPair, OFPort> swMap = macVlanToSwitchPortMap.get(sw); + if (swMap != null) { swMap.clear(); + } } @Override - public synchronized Map<IOFSwitch, Map<MacVlanPair,Short>> getTable() { + public synchronized Map<IOFSwitch, Map<MacVlanPair, OFPort>> getTable() { return macVlanToSwitchPortMap; } @@ -198,8 +208,8 @@ public class LearningSwitch * @param match The OFMatch structure to write. * @param outPort The switch port to output it to. */ - private void writeFlowMod(IOFSwitch sw, short command, int bufferId, - OFMatch match, short outPort) { + private void writeFlowMod(IOFSwitch sw, OFFlowModCommand command, OFBufferId bufferId, + Match match, OFPort outPort) { // from openflow 1.0 spec - need to set these on a struct ofp_flow_mod: // struct ofp_flow_mod { // struct ofp_header header; @@ -223,16 +233,25 @@ public class LearningSwitch // header. */ // }; - OFFlowMod flowMod = (OFFlowMod) floodlightProvider.getOFMessageFactory().getMessage(OFType.FLOW_MOD); - flowMod.setMatch(match); - flowMod.setCookie(LearningSwitch.LEARNING_SWITCH_COOKIE); - flowMod.setCommand(command); - flowMod.setIdleTimeout(LearningSwitch.FLOWMOD_DEFAULT_IDLE_TIMEOUT); - flowMod.setHardTimeout(LearningSwitch.FLOWMOD_DEFAULT_HARD_TIMEOUT); - flowMod.setPriority(LearningSwitch.FLOWMOD_PRIORITY); - flowMod.setBufferId(bufferId); - flowMod.setOutPort((command == OFFlowMod.OFPFC_DELETE) ? outPort : OFPort.OFPP_NONE.getValue()); - flowMod.setFlags((command == OFFlowMod.OFPFC_DELETE) ? 0 : (short) (1 << 0)); // OFPFF_SEND_FLOW_REM + OFFlowMod.Builder fmb; + if (command == OFFlowModCommand.DELETE) { + fmb = sw.getOFFactory().buildFlowDelete(); + } else { + fmb = sw.getOFFactory().buildFlowModify(); + } + fmb.setMatch(match); + fmb.setCookie((U64.of(LearningSwitch.LEARNING_SWITCH_COOKIE))); + fmb.setIdleTimeout(LearningSwitch.FLOWMOD_DEFAULT_IDLE_TIMEOUT); + fmb.setHardTimeout(LearningSwitch.FLOWMOD_DEFAULT_HARD_TIMEOUT); + fmb.setPriority(LearningSwitch.FLOWMOD_PRIORITY); + fmb.setBufferId(bufferId); + fmb.setOutPort((command == OFFlowModCommand.DELETE) ? outPort : OFPort.ANY); + Set<OFFlowModFlags> sfmf = new HashSet<OFFlowModFlags>(); + if (command != OFFlowModCommand.DELETE) { + sfmf.add(OFFlowModFlags.SEND_FLOW_REM); + } + fmb.setFlags(sfmf); + // set the ofp_action_header/out actions: // from the openflow 1.0 spec: need to set these on a struct ofp_action_output: @@ -242,22 +261,19 @@ public class LearningSwitch // uint16_t max_len; /* Max length to send to controller. */ // type/len are set because it is OFActionOutput, // and port, max_len are arguments to this constructor - flowMod.setActions(Arrays.asList((OFAction) new OFActionOutput(outPort, (short) 0xffff))); - flowMod.setLength((short) (OFFlowMod.MINIMUM_LENGTH + OFActionOutput.MINIMUM_LENGTH)); + List<OFAction> al = new ArrayList<OFAction>(); + al.add(sw.getOFFactory().actions().buildOutput().setPort(outPort).build()); + fmb.setActions(al); if (log.isTraceEnabled()) { log.trace("{} {} flow mod {}", - new Object[]{ sw, (command == OFFlowMod.OFPFC_DELETE) ? "deleting" : "adding", flowMod }); + new Object[]{ sw, (command == OFFlowModCommand.DELETE) ? "deleting" : "adding", fmb.build() }); } - counterStore.updatePktOutFMCounterStoreLocal(sw, flowMod); + //TODO @Ryan counterStore.updatePktOutFMCounterStoreLocal(sw, fmb.build()); // and write it out - try { - sw.write(flowMod, null); - } catch (IOException e) { - log.error("Failed to write {} to switch {}", new Object[]{ flowMod, sw }, e); - } + sw.write(fmb.build()); } /** @@ -270,7 +286,7 @@ public class LearningSwitch * @param pi packet-in * @param outport output port */ - private void pushPacket(IOFSwitch sw, OFMatch match, OFPacketIn pi, short outport) { + private void pushPacket(IOFSwitch sw, Match match, OFPacketIn pi, OFPort outport) { if (pi == null) { return; } @@ -278,7 +294,7 @@ public class LearningSwitch // The assumption here is (sw) is the switch that generated the // packet-in. If the input port is the same as output port, then // the packet-out should be ignored. - if (pi.getInPort() == outport) { + if (pi.getInPort().equals(outport)) { if (log.isDebugEnabled()) { log.debug("Attempting to do packet-out to the same " + "interface as packet-in. Dropping packet. " + @@ -293,47 +309,35 @@ public class LearningSwitch new Object[] {sw, match, pi}); } - OFPacketOut po = - (OFPacketOut) floodlightProvider.getOFMessageFactory() - .getMessage(OFType.PACKET_OUT); + OFPacketOut.Builder pob = sw.getOFFactory().buildPacketOut(); // set actions List<OFAction> actions = new ArrayList<OFAction>(); - actions.add(new OFActionOutput(outport, (short) 0xffff)); - - po.setActions(actions) - .setActionsLength((short) OFActionOutput.MINIMUM_LENGTH); - short poLength = - (short) (po.getActionsLength() + OFPacketOut.MINIMUM_LENGTH); + actions.add(sw.getOFFactory().actions().buildOutput().setPort(outport).build()); + pob.setActions(actions); + // If the switch doens't support buffering set the buffer id to be none // otherwise it'll be the the buffer id of the PacketIn if (sw.getBuffers() == 0) { // We set the PI buffer id here so we don't have to check again below - pi.setBufferId(OFPacketOut.BUFFER_ID_NONE); - po.setBufferId(OFPacketOut.BUFFER_ID_NONE); + pi = pi.createBuilder().setBufferId(OFBufferId.NO_BUFFER).build(); + pob.setBufferId(OFBufferId.NO_BUFFER); } else { - po.setBufferId(pi.getBufferId()); + pob.setBufferId(pi.getBufferId()); } - po.setInPort(pi.getInPort()); + pob.setInPort(pi.getInPort()); // If the buffer id is none or the switch doesn's support buffering // we send the data with the packet out - if (pi.getBufferId() == OFPacketOut.BUFFER_ID_NONE) { - byte[] packetData = pi.getPacketData(); - poLength += packetData.length; - po.setPacketData(packetData); + if (pi.getBufferId() == OFBufferId.NO_BUFFER) { + byte[] packetData = pi.getData(); + pob.setData(packetData); } - po.setLength(poLength); - - try { - counterStore.updatePktOutFMCounterStoreLocal(sw, po); - sw.write(po, null); - } catch (IOException e) { - log.error("Failure writing packet out", e); - } + //TODO @Ryan counterStore.updatePktOutFMCounterStoreLocal(sw, pob.build()); + sw.write(pob.build()); } /** @@ -342,9 +346,7 @@ public class LearningSwitch * @param packetInMessage The corresponding PacketIn. * @param egressPort The switchport to output the PacketOut. */ - private void writePacketOutForPacketIn(IOFSwitch sw, - OFPacketIn packetInMessage, - short egressPort) { + private void writePacketOutForPacketIn(IOFSwitch sw, OFPacketIn packetInMessage, OFPort egressPort) { // from openflow 1.0 spec - need to set these on a struct ofp_packet_out: // uint32_t buffer_id; /* ID assigned by datapath (-1 if none). */ // uint16_t in_port; /* Packet's input port (OFPP_NONE if none). */ @@ -354,37 +356,27 @@ public class LearningSwitch from the length field in the header. (Only meaningful if buffer_id == -1.) */ - OFPacketOut packetOutMessage = (OFPacketOut) floodlightProvider.getOFMessageFactory().getMessage(OFType.PACKET_OUT); - short packetOutLength = (short)OFPacketOut.MINIMUM_LENGTH; // starting length + OFPacketOut.Builder pob = sw.getOFFactory().buildPacketOut(); // Set buffer_id, in_port, actions_len - packetOutMessage.setBufferId(packetInMessage.getBufferId()); - packetOutMessage.setInPort(packetInMessage.getInPort()); - packetOutMessage.setActionsLength((short)OFActionOutput.MINIMUM_LENGTH); - packetOutLength += OFActionOutput.MINIMUM_LENGTH; + pob.setBufferId(packetInMessage.getBufferId()); + pob.setInPort(packetInMessage.getInPort()); // set actions List<OFAction> actions = new ArrayList<OFAction>(1); - actions.add(new OFActionOutput(egressPort, (short) 0)); - packetOutMessage.setActions(actions); + actions.add(sw.getOFFactory().actions().buildOutput().setPort(egressPort).build()); + pob.setActions(actions); // set data - only if buffer_id == -1 - if (packetInMessage.getBufferId() == OFPacketOut.BUFFER_ID_NONE) { - byte[] packetData = packetInMessage.getPacketData(); - packetOutMessage.setPacketData(packetData); - packetOutLength += (short)packetData.length; + if (packetInMessage.getBufferId() == OFBufferId.NO_BUFFER) { + byte[] packetData = packetInMessage.getData(); + pob.setData(packetData); } - // finally, set the total length - packetOutMessage.setLength(packetOutLength); - // and write it out - try { - counterStore.updatePktOutFMCounterStoreLocal(sw, packetOutMessage); - sw.write(packetOutMessage, null); - } catch (IOException e) { - log.error("Failed to write {} to switch {}: {}", new Object[]{ packetOutMessage, sw, e }); - } + //TODO @Ryan counterStore.updatePktOutFMCounterStoreLocal(sw, pob.build()); + sw.write(pob.build()); + } /** @@ -398,36 +390,35 @@ public class LearningSwitch */ private Command processPacketInMessage(IOFSwitch sw, OFPacketIn pi, FloodlightContext cntx) { // Read in packet data headers by using OFMatch - OFMatch match = new OFMatch(); - match.loadFromPacket(pi.getPacketData(), pi.getInPort()); - Long sourceMac = Ethernet.toLong(match.getDataLayerSource()); - Long destMac = Ethernet.toLong(match.getDataLayerDestination()); - Short vlan = match.getDataLayerVirtualLan(); - if ((destMac & 0xfffffffffff0L) == 0x0180c2000000L) { + Match.Builder mb = pi.getMatch().createBuilder(); + MacAddress sourceMac = mb.get(MatchField.ETH_SRC); + MacAddress destMac = mb.get(MatchField.ETH_DST); + OFVlanVidMatch vlan = mb.get(MatchField.VLAN_VID); + if ((destMac.getLong() & 0xfffffffffff0L) == 0x0180c2000000L) { if (log.isTraceEnabled()) { log.trace("ignoring packet addressed to 802.1D/Q reserved addr: switch {} vlan {} dest MAC {}", - new Object[]{ sw, vlan, HexString.toHexString(destMac) }); + new Object[]{ sw, vlan, destMac.toString() }); } return Command.STOP; } - if ((sourceMac & 0x010000000000L) == 0) { + if ((sourceMac.getLong() & 0x010000000000L) == 0) { // If source MAC is a unicast address, learn the port for this MAC/VLAN - this.addToPortMap(sw, sourceMac, vlan, pi.getInPort()); + this.addToPortMap(sw, sourceMac, vlan.getVlanVid(), pi.getInPort()); } // Now output flow-mod and/or packet - Short outPort = getFromPortMap(sw, destMac, vlan); + OFPort outPort = getFromPortMap(sw, destMac, vlan.getVlanVid()); if (outPort == null) { // If we haven't learned the port for the dest MAC/VLAN, flood it // Don't flood broadcast packets if the broadcast is disabled. // XXX For LearningSwitch this doesn't do much. The sourceMac is removed // from port map whenever a flow expires, so you would still see // a lot of floods. - this.writePacketOutForPacketIn(sw, pi, OFPort.OFPP_FLOOD.getValue()); - } else if (outPort == match.getInputPort()) { + this.writePacketOutForPacketIn(sw, pi, OFPort.FLOOD); + } else if (outPort.equals(mb.get(MatchField.IN_PORT))) { log.trace("ignoring packet that arrived on same port as learned destination:" + " switch {} vlan {} dest MAC {} port {}", - new Object[]{ sw, vlan, HexString.toHexString(destMac), outPort }); + new Object[]{ sw, vlan, destMac.toString(), outPort.getPortNumber() }); } else { // Add flow table entry matching source MAC, dest MAC, VLAN and input port // that sends to the port we previously learned for the dest MAC/VLAN. Also @@ -438,23 +429,30 @@ public class LearningSwitch // its former location does not keep the stale entry alive forever. // FIXME: current HP switches ignore DL_SRC and DL_DST fields, so we have to match on // NW_SRC and NW_DST as well - match.setWildcards(((Integer)sw.getAttribute(IOFSwitch.PROP_FASTWILDCARDS)).intValue() - & ~OFMatch.OFPFW_IN_PORT - & ~OFMatch.OFPFW_DL_VLAN & ~OFMatch.OFPFW_DL_SRC & ~OFMatch.OFPFW_DL_DST - & ~OFMatch.OFPFW_NW_SRC_MASK & ~OFMatch.OFPFW_NW_DST_MASK); // We write FlowMods with Buffer ID none then explicitly PacketOut the buffered packet - this.pushPacket(sw, match, pi, outPort); - this.writeFlowMod(sw, OFFlowMod.OFPFC_ADD, OFPacketOut.BUFFER_ID_NONE, match, outPort); + this.pushPacket(sw, mb.build(), pi, outPort); + this.writeFlowMod(sw, OFFlowModCommand.ADD, OFBufferId.NO_BUFFER, mb.build(), outPort); if (LEARNING_SWITCH_REVERSE_FLOW) { - this.writeFlowMod(sw, OFFlowMod.OFPFC_ADD, -1, match.clone() - .setDataLayerSource(match.getDataLayerDestination()) - .setDataLayerDestination(match.getDataLayerSource()) - .setNetworkSource(match.getNetworkDestination()) - .setNetworkDestination(match.getNetworkSource()) - .setTransportSource(match.getTransportDestination()) - .setTransportDestination(match.getTransportSource()) - .setInputPort(outPort), - match.getInputPort()); + Match.Builder mb2 = mb.build().createBuilder(); + mb2.setExact(MatchField.ETH_SRC, mb.get(MatchField.ETH_DST)) + .setExact(MatchField.ETH_DST, mb.get(MatchField.ETH_SRC)) + .setExact(MatchField.IPV4_SRC, mb.get(MatchField.IPV4_DST)) + .setExact(MatchField.IPV4_DST, mb.get(MatchField.IPV4_SRC)); + if (mb.get(MatchField.IP_PROTO).equals(IpProtocol.TCP)) { + mb2.setExact(MatchField.TCP_SRC, mb.get(MatchField.TCP_DST)) + .setExact(MatchField.TCP_DST, mb.get(MatchField.TCP_SRC)); + } else if (mb.get(MatchField.IP_PROTO).equals(IpProtocol.UDP)) { + mb2.setExact(MatchField.UDP_SRC, mb.get(MatchField.UDP_DST)) + .setExact(MatchField.UDP_DST, mb.get(MatchField.UDP_SRC)); + } else if (mb.get(MatchField.IP_PROTO).equals(IpProtocol.SCTP)) { + mb2.setExact(MatchField.SCTP_SRC, mb.get(MatchField.SCTP_DST)) + .setExact(MatchField.SCTP_DST, mb.get(MatchField.SCTP_SRC)); + } else { + log.debug("In writing reverse LS flow, could not determine L4 proto (was int " + mb.get(MatchField.IP_PROTO).getIpProtocolNumber() + ")"); + } + mb2.setExact(MatchField.IN_PORT, outPort); + + this.writeFlowMod(sw, OFFlowModCommand.ADD, OFBufferId.NO_BUFFER, mb2.build(), mb.get(MatchField.IN_PORT)); } } return Command.CONTINUE; @@ -468,36 +466,42 @@ public class LearningSwitch * @return Whether to continue processing this message or stop. */ private Command processFlowRemovedMessage(IOFSwitch sw, OFFlowRemoved flowRemovedMessage) { - if (flowRemovedMessage.getCookie() != LearningSwitch.LEARNING_SWITCH_COOKIE) { + if (!flowRemovedMessage.getCookie().equals(U64.of(LearningSwitch.LEARNING_SWITCH_COOKIE))) { return Command.CONTINUE; } if (log.isTraceEnabled()) { log.trace("{} flow entry removed {}", sw, flowRemovedMessage); } - OFMatch match = flowRemovedMessage.getMatch(); + Match match = flowRemovedMessage.getMatch(); // When a flow entry expires, it means the device with the matching source // MAC address and VLAN either stopped sending packets or moved to a different // port. If the device moved, we can't know where it went until it sends // another packet, allowing us to re-learn its port. Meanwhile we remove // it from the macVlanToPortMap to revert to flooding packets to this device. - this.removeFromPortMap(sw, Ethernet.toLong(match.getDataLayerSource()), - match.getDataLayerVirtualLan()); + this.removeFromPortMap(sw, match.get(MatchField.ETH_SRC), match.get(MatchField.VLAN_VID).getVlanVid()); // Also, if packets keep coming from another device (e.g. from ping), the // corresponding reverse flow entry will never expire on its own and will // send the packets to the wrong port (the matching input port of the // expired flow entry), so we must delete the reverse entry explicitly. - this.writeFlowMod(sw, OFFlowMod.OFPFC_DELETE, -1, match.clone() - .setWildcards(((Integer)sw.getAttribute(IOFSwitch.PROP_FASTWILDCARDS)).intValue() - & ~OFMatch.OFPFW_DL_VLAN & ~OFMatch.OFPFW_DL_SRC & ~OFMatch.OFPFW_DL_DST - & ~OFMatch.OFPFW_NW_SRC_MASK & ~OFMatch.OFPFW_NW_DST_MASK) - .setDataLayerSource(match.getDataLayerDestination()) - .setDataLayerDestination(match.getDataLayerSource()) - .setNetworkSource(match.getNetworkDestination()) - .setNetworkDestination(match.getNetworkSource()) - .setTransportSource(match.getTransportDestination()) - .setTransportDestination(match.getTransportSource()), - match.getInputPort()); + Match.Builder mb = match.createBuilder(); + mb.setExact(MatchField.ETH_SRC, match.get(MatchField.ETH_DST)) + .setExact(MatchField.ETH_DST, match.get(MatchField.ETH_SRC)) + .setExact(MatchField.IPV4_SRC, match.get(MatchField.IPV4_DST)) + .setExact(MatchField.IPV4_DST, match.get(MatchField.IPV4_SRC)); + if (match.get(MatchField.IP_PROTO).equals(IpProtocol.TCP)) { + mb.setExact(MatchField.TCP_SRC, match.get(MatchField.TCP_DST)) + .setExact(MatchField.TCP_DST, match.get(MatchField.TCP_SRC)); + } else if (match.get(MatchField.IP_PROTO).equals(IpProtocol.UDP)) { + mb.setExact(MatchField.UDP_SRC, match.get(MatchField.UDP_DST)) + .setExact(MatchField.UDP_DST, match.get(MatchField.UDP_SRC)); + } else if (match.get(MatchField.IP_PROTO).equals(IpProtocol.SCTP)) { + mb.setExact(MatchField.SCTP_SRC, match.get(MatchField.SCTP_DST)) + .setExact(MatchField.SCTP_DST, match.get(MatchField.SCTP_SRC)); + } else { + log.debug("In writing reverse LS flow, could not determine L4 proto (was int " + mb.get(MatchField.IP_PROTO).getIpProtocolNumber() + ")"); + } + this.writeFlowMod(sw, OFFlowModCommand.DELETE, OFBufferId.NO_BUFFER, mb.build(), match.get(MatchField.IN_PORT)); return Command.CONTINUE; } @@ -541,46 +545,37 @@ public class LearningSwitch } @Override - public Map<Class<? extends IFloodlightService>, IFloodlightService> - getServiceImpls() { - Map<Class<? extends IFloodlightService>, - IFloodlightService> m = - new HashMap<Class<? extends IFloodlightService>, - IFloodlightService>(); + public Map<Class<? extends IFloodlightService>, IFloodlightService> getServiceImpls() { + Map<Class<? extends IFloodlightService>, IFloodlightService> m = + new HashMap<Class<? extends IFloodlightService>, IFloodlightService>(); m.put(ILearningSwitchService.class, this); return m; } @Override - public Collection<Class<? extends IFloodlightService>> - getModuleDependencies() { + public Collection<Class<? extends IFloodlightService>> getModuleDependencies() { Collection<Class<? extends IFloodlightService>> l = new ArrayList<Class<? extends IFloodlightService>>(); l.add(IFloodlightProviderService.class); - l.add(ICounterStoreService.class); + l.add(IDebugCounterService.class); l.add(IRestApiService.class); return l; } @Override - public void init(FloodlightModuleContext context) - throws FloodlightModuleException { - macVlanToSwitchPortMap = - new ConcurrentHashMap<IOFSwitch, Map<MacVlanPair,Short>>(); - floodlightProvider = - context.getServiceImpl(IFloodlightProviderService.class); - counterStore = - context.getServiceImpl(ICounterStoreService.class); - restApi = - context.getServiceImpl(IRestApiService.class); + public void init(FloodlightModuleContext context) throws FloodlightModuleException { + macVlanToSwitchPortMap = new ConcurrentHashMap<IOFSwitch, Map<MacVlanPair, OFPort>>(); + floodlightProviderService = context.getServiceImpl(IFloodlightProviderService.class); + debugCounterService = context.getServiceImpl(IDebugCounterService.class); + restApiService = context.getServiceImpl(IRestApiService.class); } @Override public void startUp(FloodlightModuleContext context) { - floodlightProvider.addOFMessageListener(OFType.PACKET_IN, this); - floodlightProvider.addOFMessageListener(OFType.FLOW_REMOVED, this); - floodlightProvider.addOFMessageListener(OFType.ERROR, this); - restApi.addRestletRoutable(new LearningSwitchWebRoutable()); + floodlightProviderService.addOFMessageListener(OFType.PACKET_IN, this); + floodlightProviderService.addOFMessageListener(OFType.FLOW_REMOVED, this); + floodlightProviderService.addOFMessageListener(OFType.ERROR, this); + restApiService.addRestletRoutable(new LearningSwitchWebRoutable()); // read our config options Map<String, String> configOptions = context.getConfigParams(this); @@ -591,8 +586,7 @@ public class LearningSwitch } } catch (NumberFormatException e) { log.warn("Error parsing flow idle timeout, " + - "using default of {} seconds", - FLOWMOD_DEFAULT_IDLE_TIMEOUT); + "using default of {} seconds", FLOWMOD_DEFAULT_IDLE_TIMEOUT); } try { String hardTimeout = configOptions.get("hardtimeout"); @@ -601,8 +595,7 @@ public class LearningSwitch } } catch (NumberFormatException e) { log.warn("Error parsing flow hard timeout, " + - "using default of {} seconds", - FLOWMOD_DEFAULT_HARD_TIMEOUT); + "using default of {} seconds", FLOWMOD_DEFAULT_HARD_TIMEOUT); } try { String priority = configOptions.get("priority"); @@ -614,11 +607,8 @@ public class LearningSwitch "using default of {}", FLOWMOD_PRIORITY); } - log.debug("FlowMod idle timeout set to {} seconds", - FLOWMOD_DEFAULT_IDLE_TIMEOUT); - log.debug("FlowMod hard timeout set to {} seconds", - FLOWMOD_DEFAULT_HARD_TIMEOUT); - log.debug("FlowMod priority set to {}", - FLOWMOD_PRIORITY); + log.debug("FlowMod idle timeout set to {} seconds", FLOWMOD_DEFAULT_IDLE_TIMEOUT); + log.debug("FlowMod hard timeout set to {} seconds", FLOWMOD_DEFAULT_HARD_TIMEOUT); + log.debug("FlowMod priority set to {}", FLOWMOD_PRIORITY); } } diff --git a/src/main/java/net/floodlightcontroller/learningswitch/LearningSwitchTable.java b/src/main/java/net/floodlightcontroller/learningswitch/LearningSwitchTable.java index 2eb1b914f1bc501a449d954733317d229463114e..bb222a41632360e1f30c428e497d139ca23e8ff8 100644 --- a/src/main/java/net/floodlightcontroller/learningswitch/LearningSwitchTable.java +++ b/src/main/java/net/floodlightcontroller/learningswitch/LearningSwitchTable.java @@ -22,11 +22,12 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; -import net.floodlightcontroller.core.IFloodlightProviderService; import net.floodlightcontroller.core.IOFSwitch; +import net.floodlightcontroller.core.internal.IOFSwitchService; import net.floodlightcontroller.core.types.MacVlanPair; -import org.openflow.util.HexString; +import org.projectfloodlight.openflow.types.DatapathId; +import org.projectfloodlight.openflow.types.OFPort; import org.restlet.data.Status; import org.restlet.resource.Get; import org.restlet.resource.ServerResource; @@ -36,17 +37,17 @@ import org.slf4j.LoggerFactory; public class LearningSwitchTable extends ServerResource { protected static Logger log = LoggerFactory.getLogger(LearningSwitchTable.class); - protected Map<String, Object> formatTableEntry(MacVlanPair key, short port) { + protected Map<String, Object> formatTableEntry(MacVlanPair key, OFPort port) { Map<String, Object> entry = new HashMap<String, Object>(); - entry.put("mac", HexString.toHexString(key.mac)); - entry.put("vlan", key.vlan); - entry.put("port", port); + entry.put("mac", key.mac.toString()); + entry.put("vlan", key.vlan.getVlan()); + entry.put("port", port.getPortNumber()); return entry; } - protected List<Map<String, Object>> getOneSwitchTable(Map<MacVlanPair, Short> switchMap) { + protected List<Map<String, Object>> getOneSwitchTable(Map<MacVlanPair, OFPort> switchMap) { List<Map<String, Object>> switchTable = new ArrayList<Map<String, Object>>(); - for (Entry<MacVlanPair, Short> entry : switchMap.entrySet()) { + for (Entry<MacVlanPair, OFPort> entry : switchMap.entrySet()) { switchTable.add(formatTableEntry(entry.getKey(), entry.getValue())); } return switchTable; @@ -58,22 +59,21 @@ public class LearningSwitchTable extends ServerResource { (ILearningSwitchService)getContext().getAttributes(). get(ILearningSwitchService.class.getCanonicalName()); - Map<IOFSwitch, Map<MacVlanPair,Short>> table = lsp.getTable(); + Map<IOFSwitch, Map<MacVlanPair, OFPort>> table = lsp.getTable(); Map<String, List<Map<String, Object>>> allSwitchTableJson = new HashMap<String, List<Map<String, Object>>>(); String switchId = (String) getRequestAttributes().get("switch"); if (switchId.toLowerCase().equals("all")) { for (IOFSwitch sw : table.keySet()) { - allSwitchTableJson.put(HexString.toHexString(sw.getId()), getOneSwitchTable(table.get(sw))); + allSwitchTableJson.put(sw.getId().toString(), getOneSwitchTable(table.get(sw))); } } else { try { - IFloodlightProviderService floodlightProvider = - (IFloodlightProviderService)getContext().getAttributes(). - get(IFloodlightProviderService.class.getCanonicalName()); - long dpid = HexString.toLong(switchId); - IOFSwitch sw = floodlightProvider.getSwitch(dpid); - allSwitchTableJson.put(HexString.toHexString(sw.getId()), getOneSwitchTable(table.get(sw))); + IOFSwitchService switchService = + (IOFSwitchService) getContext().getAttributes(). + get(IOFSwitchService.class.getCanonicalName()); + IOFSwitch sw = switchService.getSwitch(DatapathId.of(switchId)); + allSwitchTableJson.put(sw.getId().toString(), getOneSwitchTable(table.get(sw))); } catch (NumberFormatException e) { log.error("Could not decode switch ID = " + switchId); setStatus(Status.CLIENT_ERROR_BAD_REQUEST); diff --git a/src/main/java/net/floodlightcontroller/linkdiscovery/ILinkDiscovery.java b/src/main/java/net/floodlightcontroller/linkdiscovery/ILinkDiscovery.java index e4e109ca46fea5d8605109aea8f497c6300edcb6..d1dee02a9347d5d5fd185dd9bc17356a57240365 100644 --- a/src/main/java/net/floodlightcontroller/linkdiscovery/ILinkDiscovery.java +++ b/src/main/java/net/floodlightcontroller/linkdiscovery/ILinkDiscovery.java @@ -18,7 +18,9 @@ package net.floodlightcontroller.linkdiscovery; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; -import org.openflow.util.HexString; + +import org.projectfloodlight.openflow.types.DatapathId; +import org.projectfloodlight.openflow.types.OFPort; public interface ILinkDiscovery { @@ -45,16 +47,16 @@ public interface ILinkDiscovery { } public class LDUpdate { - protected long src; - protected short srcPort; - protected long dst; - protected short dstPort; + protected DatapathId src; + protected OFPort srcPort; + protected DatapathId dst; + protected OFPort dstPort; protected SwitchType srcType; protected LinkType type; protected UpdateOperation operation; - public LDUpdate(long src, short srcPort, - long dst, short dstPort, + public LDUpdate(DatapathId src, OFPort srcPort, + DatapathId dst, OFPort dstPort, ILinkDiscovery.LinkType type, UpdateOperation operation) { this.src = src; @@ -76,32 +78,32 @@ public interface ILinkDiscovery { } // For updtedSwitch(sw) - public LDUpdate(long switchId, SwitchType stype, UpdateOperation oper ){ + public LDUpdate(DatapathId switchId, SwitchType stype, UpdateOperation oper){ this.operation = oper; this.src = switchId; this.srcType = stype; } // For port up or port down; and tunnel port added and removed. - public LDUpdate(long sw, short port, UpdateOperation operation) { + public LDUpdate(DatapathId sw, OFPort port, UpdateOperation operation) { this.src = sw; this.srcPort = port; this.operation = operation; } - public long getSrc() { + public DatapathId getSrc() { return src; } - public short getSrcPort() { + public OFPort getSrcPort() { return srcPort; } - public long getDst() { + public DatapathId getDst() { return dst; } - public short getDstPort() { + public OFPort getDstPort() { return dstPort; } @@ -127,20 +129,20 @@ public interface ILinkDiscovery { case LINK_REMOVED: case LINK_UPDATED: return "LDUpdate [operation=" + operation + - ", src=" + HexString.toHexString(src) - + ", srcPort=" + srcPort - + ", dst=" + HexString.toHexString(dst) - + ", dstPort=" + dstPort + ", src=" + src.toString() + + ", srcPort=" + srcPort.toString() + + ", dst=" + dst.toString() + + ", dstPort=" + dstPort.toString() + ", type=" + type + "]"; case PORT_DOWN: case PORT_UP: return "LDUpdate [operation=" + operation + - ", src=" + HexString.toHexString(src) - + ", srcPort=" + srcPort + "]"; + ", src=" + src.toString() + + ", srcPort=" + srcPort.toString() + "]"; case SWITCH_REMOVED: case SWITCH_UPDATED: return "LDUpdate [operation=" + operation + - ", src=" + HexString.toHexString(src) + "]"; + ", src=" + src.toString() + "]"; default: return "LDUpdate: Unknown update."; } diff --git a/src/main/java/net/floodlightcontroller/linkdiscovery/ILinkDiscoveryService.java b/src/main/java/net/floodlightcontroller/linkdiscovery/ILinkDiscoveryService.java index 3773efd713b992755e093bd50dfdaf85317114a0..2bb53727917ffba3b4138bf3d06a9e56b79db1f2 100644 --- a/src/main/java/net/floodlightcontroller/linkdiscovery/ILinkDiscoveryService.java +++ b/src/main/java/net/floodlightcontroller/linkdiscovery/ILinkDiscoveryService.java @@ -20,7 +20,10 @@ package net.floodlightcontroller.linkdiscovery; import java.util.Map; import java.util.Set; -import org.openflow.protocol.OFPacketOut; +import org.projectfloodlight.openflow.protocol.OFPacketOut; +import org.projectfloodlight.openflow.types.DatapathId; +import org.projectfloodlight.openflow.types.MacAddress; +import org.projectfloodlight.openflow.types.OFPort; import net.floodlightcontroller.core.module.IFloodlightService; import net.floodlightcontroller.routing.Link; @@ -32,7 +35,7 @@ public interface ILinkDiscoveryService extends IFloodlightService { /** * Returns if a given switchport is a tunnel endpoint or not */ - public boolean isTunnelPort(long sw, short port); + public boolean isTunnelPort(DatapathId sw, OFPort port); /** * Retrieves a map of all known link connections between OpenFlow switches @@ -59,7 +62,7 @@ public interface ILinkDiscoveryService extends IFloodlightService { * to switchport (sw, port). PacketOut does not contain actions. * PacketOut length includes the minimum length and data length. */ - public OFPacketOut generateLLDPMessage(long sw, short port, + public OFPacketOut generateLLDPMessage(DatapathId sw, OFPort port, boolean isStandard, boolean isReverse); @@ -67,7 +70,7 @@ public interface ILinkDiscoveryService extends IFloodlightService { * Returns an unmodifiable map from switch id to a set of all links with it * as an endpoint. */ - public Map<Long, Set<Link>> getSwitchLinks(); + public Map<DatapathId, Set<Link>> getSwitchLinks(); /** * Adds a listener to listen for ILinkDiscoveryService messages @@ -84,17 +87,17 @@ public interface ILinkDiscoveryService extends IFloodlightService { * Adds a switch port to suppress lldp set. LLDPs and BDDPs will not be sent * out, and if any are received on this port then they will be dropped. */ - public void AddToSuppressLLDPs(long sw, short port); + public void AddToSuppressLLDPs(DatapathId sw, OFPort port); /** * Removes a switch port from suppress lldp set */ - public void RemoveFromSuppressLLDPs(long sw, short port); + public void RemoveFromSuppressLLDPs(DatapathId sw, OFPort port); /** * Get the set of quarantined ports on a switch */ - public Set<Short> getQuarantinedPorts(long sw); + public Set<OFPort> getQuarantinedPorts(DatapathId sw); /** * Get the status of auto port fast feature. @@ -126,5 +129,5 @@ public interface ILinkDiscoveryService extends IFloodlightService { * ALL MAC addresses to the ignore list. This will cause a drop of * ALL packet ins. */ - public void addMACToIgnoreList(long mac, int ignoreBits); + public void addMACToIgnoreList(MacAddress mac, int ignoreBits); } diff --git a/src/main/java/net/floodlightcontroller/linkdiscovery/LinkInfo.java b/src/main/java/net/floodlightcontroller/linkdiscovery/LinkInfo.java index 0230b3bbd31f8cb47581ff83f6b79f4632386560..6c2d7334e436cabe3f44eca1547d4f1425c3d440 100644 --- a/src/main/java/net/floodlightcontroller/linkdiscovery/LinkInfo.java +++ b/src/main/java/net/floodlightcontroller/linkdiscovery/LinkInfo.java @@ -15,15 +15,17 @@ package net.floodlightcontroller.linkdiscovery; +import java.util.Date; + import net.floodlightcontroller.linkdiscovery.ILinkDiscovery.LinkType; import com.fasterxml.jackson.annotation.JsonIgnore; public class LinkInfo { - public LinkInfo(Long firstSeenTime, - Long lastLldpReceivedTime, - Long lastBddpReceivedTime) { + public LinkInfo(Date firstSeenTime, + Date lastLldpReceivedTime, + Date lastBddpReceivedTime) { super(); this.firstSeenTime = firstSeenTime; this.lastLldpReceivedTime = lastLldpReceivedTime; @@ -46,9 +48,9 @@ public class LinkInfo { this.lastBddpReceivedTime = fromLinkInfo.getMulticastValidTime(); } - protected Long firstSeenTime; - protected Long lastLldpReceivedTime; /* Standard LLLDP received time */ - protected Long lastBddpReceivedTime; /* Modified LLDP received time */ + protected Date firstSeenTime; + protected Date lastLldpReceivedTime; /* Standard LLLDP received time */ + protected Date lastBddpReceivedTime; /* Modified LLDP received time */ /** The port states stored here are topology's last knowledge of * the state of the port. This mostly mirrors the state @@ -60,27 +62,27 @@ public class LinkInfo { * requires the new state to be written to storage. */ - public Long getFirstSeenTime() { + public Date getFirstSeenTime() { return firstSeenTime; } - public void setFirstSeenTime(Long firstSeenTime) { + public void setFirstSeenTime(Date firstSeenTime) { this.firstSeenTime = firstSeenTime; } - public Long getUnicastValidTime() { + public Date getUnicastValidTime() { return lastLldpReceivedTime; } - public void setUnicastValidTime(Long unicastValidTime) { + public void setUnicastValidTime(Date unicastValidTime) { this.lastLldpReceivedTime = unicastValidTime; } - public Long getMulticastValidTime() { + public Date getMulticastValidTime() { return lastBddpReceivedTime; } - public void setMulticastValidTime(Long multicastValidTime) { + public void setMulticastValidTime(Date multicastValidTime) { this.lastBddpReceivedTime = multicastValidTime; } @@ -147,8 +149,8 @@ public class LinkInfo { */ @Override public String toString() { - return "LinkInfo [unicastValidTime=" + ((lastLldpReceivedTime == null) ? "null" : lastLldpReceivedTime) - + ", multicastValidTime=" + ((lastBddpReceivedTime == null) ? "null" : lastBddpReceivedTime) + return "LinkInfo [unicastValidTime=" + ((lastLldpReceivedTime == null) ? "null" : lastLldpReceivedTime.getTime()) + + ", multicastValidTime=" + ((lastBddpReceivedTime == null) ? "null" : lastBddpReceivedTime.getTime()) + "]"; } } diff --git a/src/main/java/net/floodlightcontroller/linkdiscovery/internal/LinkDiscoveryManager.java b/src/main/java/net/floodlightcontroller/linkdiscovery/internal/LinkDiscoveryManager.java index 2dbba0a30de9a7d558e26e79d6c216b388a4c455..e2b53a77707e34434536ee481c4831bc813d4103 100644 --- a/src/main/java/net/floodlightcontroller/linkdiscovery/internal/LinkDiscoveryManager.java +++ b/src/main/java/net/floodlightcontroller/linkdiscovery/internal/LinkDiscoveryManager.java @@ -17,7 +17,6 @@ package net.floodlightcontroller.linkdiscovery.internal; -import java.io.IOException; import java.net.NetworkInterface; import java.net.SocketException; import java.nio.ByteBuffer; @@ -25,6 +24,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.Date; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; @@ -42,17 +42,19 @@ import java.util.concurrent.locks.ReentrantReadWriteLock; import net.floodlightcontroller.core.FloodlightContext; import net.floodlightcontroller.core.HAListenerTypeMarker; +import net.floodlightcontroller.core.HARole; import net.floodlightcontroller.core.IFloodlightProviderService; -import net.floodlightcontroller.core.IFloodlightProviderService.Role; +import net.floodlightcontroller.core.IShutdownService; +import net.floodlightcontroller.core.PortChangeType; import net.floodlightcontroller.core.IHAListener; import net.floodlightcontroller.core.IInfoProvider; import net.floodlightcontroller.core.IOFMessageListener; import net.floodlightcontroller.core.IOFSwitch; import net.floodlightcontroller.core.IOFSwitchListener; -import net.floodlightcontroller.core.ImmutablePort; import net.floodlightcontroller.core.annotations.LogMessageCategory; import net.floodlightcontroller.core.annotations.LogMessageDoc; import net.floodlightcontroller.core.annotations.LogMessageDocs; +import net.floodlightcontroller.core.internal.IOFSwitchService; import net.floodlightcontroller.core.module.FloodlightModuleContext; import net.floodlightcontroller.core.module.FloodlightModuleException; import net.floodlightcontroller.core.module.IFloodlightModule; @@ -60,15 +62,10 @@ import net.floodlightcontroller.core.module.IFloodlightService; import net.floodlightcontroller.core.util.SingletonTask; 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.IDebugEventService.EventColumn; import net.floodlightcontroller.debugevent.IDebugEventService.EventFieldType; -import net.floodlightcontroller.debugevent.IDebugEventService.MaxEventsRegistered; -import net.floodlightcontroller.debugevent.IEventUpdater; -import net.floodlightcontroller.debugevent.NullDebugEvent; +import net.floodlightcontroller.debugevent.IEventCategory; import net.floodlightcontroller.debugevent.IDebugEventService.EventType; import net.floodlightcontroller.linkdiscovery.ILinkDiscovery; import net.floodlightcontroller.linkdiscovery.ILinkDiscovery.LDUpdate; @@ -95,16 +92,20 @@ import net.floodlightcontroller.storage.StorageException; import net.floodlightcontroller.threadpool.IThreadPoolService; import net.floodlightcontroller.topology.NodePortTuple; -import org.openflow.protocol.OFMessage; -import org.openflow.protocol.OFPacketIn; -import org.openflow.protocol.OFPacketOut; -import org.openflow.protocol.OFPhysicalPort; -import org.openflow.protocol.OFPhysicalPort.OFPortState; -import org.openflow.protocol.OFPort; -import org.openflow.protocol.OFType; -import org.openflow.protocol.action.OFAction; -import org.openflow.protocol.action.OFActionOutput; -import org.openflow.util.HexString; +import org.projectfloodlight.openflow.protocol.OFMessage; +import org.projectfloodlight.openflow.protocol.OFPacketIn; +import org.projectfloodlight.openflow.protocol.OFPacketOut; +import org.projectfloodlight.openflow.protocol.OFPortDesc; +import org.projectfloodlight.openflow.protocol.OFPortState; +import org.projectfloodlight.openflow.protocol.OFVersion; +import org.projectfloodlight.openflow.types.DatapathId; +import org.projectfloodlight.openflow.types.MacAddress; +import org.projectfloodlight.openflow.types.OFBufferId; +import org.projectfloodlight.openflow.types.OFPort; +import org.projectfloodlight.openflow.util.HexString; +import org.projectfloodlight.openflow.protocol.OFType; +import org.projectfloodlight.openflow.protocol.action.OFAction; +import org.projectfloodlight.openflow.protocol.match.MatchField; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -125,1540 +126,1507 @@ import org.slf4j.LoggerFactory; */ @LogMessageCategory("Network Topology") public class LinkDiscoveryManager implements IOFMessageListener, - IOFSwitchListener, IStorageSourceListener, ILinkDiscoveryService, - IFloodlightModule, IInfoProvider { - protected static final Logger log = LoggerFactory.getLogger(LinkDiscoveryManager.class); - protected static final INotificationManager notifier = - NotificationManagerFactory.getNotificationManager(LinkDiscoveryManager.class); - - public static final String MODULE_NAME = "linkdiscovery"; - - // Names of table/fields for links in the storage API - private static final String TOPOLOGY_TABLE_NAME = "controller_topologyconfig"; - private static final String TOPOLOGY_ID = "id"; - private static final String TOPOLOGY_AUTOPORTFAST = "autoportfast"; - - private static final String LINK_TABLE_NAME = "controller_link"; - private static final String LINK_ID = "id"; - private static final String LINK_SRC_SWITCH = "src_switch_id"; - private static final String LINK_SRC_PORT = "src_port"; - private static final String LINK_DST_SWITCH = "dst_switch_id"; - private static final String LINK_DST_PORT = "dst_port"; - private static final String LINK_VALID_TIME = "valid_time"; - private static final String LINK_TYPE = "link_type"; - private static final String SWITCH_CONFIG_TABLE_NAME = "controller_switchconfig"; - private static final String SWITCH_CONFIG_CORE_SWITCH = "core_switch"; - - // Event updaters for debug events - protected IEventUpdater<DirectLinkEvent> evDirectLink; - - protected IFloodlightProviderService floodlightProvider; - protected IStorageSourceService storageSource; - protected IThreadPoolService threadPool; - protected IRestApiService restApi; - protected IDebugCounterService debugCounters; - protected IDebugEventService debugEvents; - - // Role - protected Role role; - - // LLDP and BDDP fields - private static final byte[] LLDP_STANDARD_DST_MAC_STRING = - HexString.fromHexString("01:80:c2:00:00:0e"); - private static final long LINK_LOCAL_MASK = 0xfffffffffff0L; - private static final long LINK_LOCAL_VALUE = 0x0180c2000000L; - protected static int EVENT_HISTORY_SIZE = 1024; // in seconds - - // BigSwitch OUI is 5C:16:C7, so 5D:16:C7 is the multicast version - // private static final String LLDP_BSN_DST_MAC_STRING = - // "5d:16:c7:00:00:01"; - private static final String LLDP_BSN_DST_MAC_STRING = "ff:ff:ff:ff:ff:ff"; - - // Direction TLVs are used to indicate if the LLDPs were sent - // periodically or in response to a recieved LLDP - private static final byte TLV_DIRECTION_TYPE = 0x73; - private static final short TLV_DIRECTION_LENGTH = 1; // 1 byte - private static final byte TLV_DIRECTION_VALUE_FORWARD[] = { 0x01 }; - private static final byte TLV_DIRECTION_VALUE_REVERSE[] = { 0x02 }; - private static final LLDPTLV forwardTLV = new LLDPTLV().setType(TLV_DIRECTION_TYPE) - .setLength(TLV_DIRECTION_LENGTH) - .setValue(TLV_DIRECTION_VALUE_FORWARD); - - private static final LLDPTLV reverseTLV = new LLDPTLV().setType(TLV_DIRECTION_TYPE) - .setLength(TLV_DIRECTION_LENGTH) - .setValue(TLV_DIRECTION_VALUE_REVERSE); - - // Link discovery task details. - protected SingletonTask discoveryTask; - protected final int DISCOVERY_TASK_INTERVAL = 1; - protected final int LINK_TIMEOUT = 35; // timeout as part of LLDP process. - protected final int LLDP_TO_ALL_INTERVAL = 15; // 15 seconds. - protected long lldpClock = 0; - // This value is intentionally kept higher than LLDP_TO_ALL_INTERVAL. - // If we want to identify link failures faster, we could decrease this - // value to a small number, say 1 or 2 sec. - protected final int LLDP_TO_KNOWN_INTERVAL = 20; // LLDP frequency for known - // links - - protected LLDPTLV controllerTLV; - protected ReentrantReadWriteLock lock; - int lldpTimeCount = 0; - - /** - * Flag to indicate if automatic port fast is enabled or not. Default is set - * to false -- Initialized in the init method as well. - */ - protected boolean AUTOPORTFAST_DEFAULT = false; - protected boolean autoPortFastFeature = AUTOPORTFAST_DEFAULT; - - /** - * Map from link to the most recent time it was verified functioning - */ - protected Map<Link, LinkInfo> links; - - /** - * Map from switch id to a set of all links with it as an endpoint - */ - protected Map<Long, Set<Link>> switchLinks; - - /** - * Map from a id:port to the set of links containing it as an endpoint - */ - protected Map<NodePortTuple, Set<Link>> portLinks; - - protected volatile boolean shuttingDown = false; - - /* - * topology aware components are called in the order they were added to the - * the array - */ - protected ArrayList<ILinkDiscoveryListener> linkDiscoveryAware; - protected BlockingQueue<LDUpdate> updates; - protected Thread updatesThread; - - /** - * List of ports through which LLDP/BDDPs are not sent. - */ - protected Set<NodePortTuple> suppressLinkDiscovery; - - /** - * A list of ports that are quarantined for discovering links through them. - * Data traffic from these ports are not allowed until the ports are - * released from quarantine. - */ - protected LinkedBlockingQueue<NodePortTuple> quarantineQueue; - protected LinkedBlockingQueue<NodePortTuple> maintenanceQueue; - /** - * Quarantine task - */ - protected SingletonTask bddpTask; - protected final int BDDP_TASK_INTERVAL = 100; // 100 ms. - protected final int BDDP_TASK_SIZE = 10; // # of ports per iteration - - private class MACRange { - long baseMAC; - int ignoreBits; - } - protected Set<MACRange> ignoreMACSet; - - private IHAListener haListener; - - /** - * Debug Counters - */ - private IDebugCounter ctrQuarantineDrops; - private IDebugCounter ctrIgnoreSrcMacDrops; - private IDebugCounter ctrIncoming; - private IDebugCounter ctrLinkLocalDrops; - private IDebugCounter ctrLldpEol; - - private final String PACKAGE = LinkDiscoveryManager.class.getPackage().getName(); - - - //********************* - // ILinkDiscoveryService - //********************* - - @Override - public OFPacketOut generateLLDPMessage(long sw, short port, - boolean isStandard, boolean isReverse) { - - IOFSwitch iofSwitch = floodlightProvider.getSwitch(sw); - ImmutablePort ofpPort = iofSwitch.getPort(port); - - if (log.isTraceEnabled()) { - log.trace("Sending LLDP packet out of swich: {}, port: {}", - HexString.toHexString(sw), port); - } - - // using "nearest customer bridge" MAC address for broadest possible - // propagation - // through provider and TPMR bridges (see IEEE 802.1AB-2009 and - // 802.1Q-2011), - // in particular the Linux bridge which behaves mostly like a provider - // bridge - byte[] chassisId = new byte[] { 4, 0, 0, 0, 0, 0, 0 }; // filled in - // later - byte[] portId = new byte[] { 2, 0, 0 }; // filled in later - byte[] ttlValue = new byte[] { 0, 0x78 }; - // OpenFlow OUI - 00-26-E1 - byte[] dpidTLVValue = new byte[] { 0x0, 0x26, (byte) 0xe1, 0, 0, 0, - 0, 0, 0, 0, 0, 0 }; - LLDPTLV dpidTLV = new LLDPTLV().setType((byte) 127) - .setLength((short) dpidTLVValue.length) - .setValue(dpidTLVValue); - - byte[] dpidArray = new byte[8]; - ByteBuffer dpidBB = ByteBuffer.wrap(dpidArray); - ByteBuffer portBB = ByteBuffer.wrap(portId, 1, 2); - - Long dpid = sw; - dpidBB.putLong(dpid); - // set the chassis id's value to last 6 bytes of dpid - System.arraycopy(dpidArray, 2, chassisId, 1, 6); - // set the optional tlv to the full dpid - System.arraycopy(dpidArray, 0, dpidTLVValue, 4, 8); - - // TODO: Consider remove this block of code. - // It's evil to overwrite port object. The the old code always - // overwrote mac address, we now only overwrite zero macs and - // log a warning, mostly for paranoia. - byte[] srcMac = ofpPort.getHardwareAddress(); - byte[] zeroMac = { 0, 0, 0, 0, 0, 0 }; - if (Arrays.equals(srcMac, zeroMac)) { - log.warn("Port {}/{} has zero hareware address" - + "overwrite with lower 6 bytes of dpid", - HexString.toHexString(dpid), ofpPort.getPortNumber()); - System.arraycopy(dpidArray, 2, srcMac, 0, 6); - } - - // set the portId to the outgoing port - portBB.putShort(port); - if (log.isTraceEnabled()) { - log.trace("Sending LLDP out of interface: {}/{}", - HexString.toHexString(sw), port); - } - - LLDP lldp = new LLDP(); - lldp.setChassisId(new LLDPTLV().setType((byte) 1) - .setLength((short) chassisId.length) - .setValue(chassisId)); - lldp.setPortId(new LLDPTLV().setType((byte) 2) - .setLength((short) portId.length) - .setValue(portId)); - lldp.setTtl(new LLDPTLV().setType((byte) 3) - .setLength((short) ttlValue.length) - .setValue(ttlValue)); - lldp.getOptionalTLVList().add(dpidTLV); - - // Add the controller identifier to the TLV value. - lldp.getOptionalTLVList().add(controllerTLV); - if (isReverse) { - lldp.getOptionalTLVList().add(reverseTLV); - } else { - lldp.getOptionalTLVList().add(forwardTLV); - } - - Ethernet ethernet; - if (isStandard) { - ethernet = new Ethernet().setSourceMACAddress(ofpPort.getHardwareAddress()) - .setDestinationMACAddress(LLDP_STANDARD_DST_MAC_STRING) - .setEtherType(Ethernet.TYPE_LLDP); - ethernet.setPayload(lldp); - } else { - BSN bsn = new BSN(BSN.BSN_TYPE_BDDP); - bsn.setPayload(lldp); - - ethernet = new Ethernet().setSourceMACAddress(ofpPort.getHardwareAddress()) - .setDestinationMACAddress(LLDP_BSN_DST_MAC_STRING) - .setEtherType(Ethernet.TYPE_BSN); - ethernet.setPayload(bsn); - } - - // serialize and wrap in a packet out - byte[] data = ethernet.serialize(); - OFPacketOut po = (OFPacketOut) floodlightProvider.getOFMessageFactory() - .getMessage(OFType.PACKET_OUT); - po.setBufferId(OFPacketOut.BUFFER_ID_NONE); - po.setInPort(OFPort.OFPP_NONE); - - // set data and data length - po.setLengthU(OFPacketOut.MINIMUM_LENGTH + data.length); - po.setPacketData(data); - - return po; - } - - /** - * Get the LLDP sending period in seconds. - * - * @return LLDP sending period in seconds. - */ - public int getLldpFrequency() { - return LLDP_TO_KNOWN_INTERVAL; - } - - /** - * Get the LLDP timeout value in seconds - * - * @return LLDP timeout value in seconds - */ - public int getLldpTimeout() { - return LINK_TIMEOUT; - } - - @Override - public Map<NodePortTuple, Set<Link>> getPortLinks() { - return portLinks; - } - - @Override - public Set<NodePortTuple> getSuppressLLDPsInfo() { - return suppressLinkDiscovery; - } - - /** - * Add a switch port to the suppressed LLDP list. Remove any known links on - * the switch port. - */ - @Override - public void AddToSuppressLLDPs(long sw, short port) { - NodePortTuple npt = new NodePortTuple(sw, port); - this.suppressLinkDiscovery.add(npt); - deleteLinksOnPort(npt, "LLDP suppressed."); - } - - /** - * Remove a switch port from the suppressed LLDP list. Discover links on - * that switchport. - */ - @Override - public void RemoveFromSuppressLLDPs(long sw, short port) { - NodePortTuple npt = new NodePortTuple(sw, port); - this.suppressLinkDiscovery.remove(npt); - discover(npt); - } - - public boolean isShuttingDown() { - return shuttingDown; - } - - @Override - public boolean isTunnelPort(long sw, short port) { - return false; - } - - @Override - public ILinkDiscovery.LinkType getLinkType(Link lt, LinkInfo info) { - if (info.getUnicastValidTime() != null) { - return ILinkDiscovery.LinkType.DIRECT_LINK; - } else if (info.getMulticastValidTime() != null) { - return ILinkDiscovery.LinkType.MULTIHOP_LINK; - } - return ILinkDiscovery.LinkType.INVALID_LINK; - } - - @Override - public Set<Short> getQuarantinedPorts(long sw) { - Set<Short> qPorts = new HashSet<Short>(); - - Iterator<NodePortTuple> iter = quarantineQueue.iterator(); - while (iter.hasNext()) { - NodePortTuple npt = iter.next(); - if (npt.getNodeId() == sw) { - qPorts.add(npt.getPortId()); - } - } - return qPorts; - } - - @Override - public Map<Long, Set<Link>> getSwitchLinks() { - return this.switchLinks; - } - - @Override - public void addMACToIgnoreList(long mac, int ignoreBits) { - MACRange range = new MACRange(); - range.baseMAC = mac; - range.ignoreBits = ignoreBits; - ignoreMACSet.add(range); - } - - @Override - public boolean isAutoPortFastFeature() { - return autoPortFastFeature; - } - - @Override - public void setAutoPortFastFeature(boolean autoPortFastFeature) { - this.autoPortFastFeature = autoPortFastFeature; - } - - @Override - public void addListener(ILinkDiscoveryListener listener) { - linkDiscoveryAware.add(listener); - } - - @Override - public Map<Link, LinkInfo> getLinks() { - lock.readLock().lock(); - Map<Link, LinkInfo> result; - try { - result = new HashMap<Link, LinkInfo>(links); - } finally { - lock.readLock().unlock(); - } - return result; - } - - @Override - public LinkInfo getLinkInfo(Link link) { - lock.readLock().lock(); - LinkInfo linkInfo = links.get(link); - LinkInfo retLinkInfo = null; - if (linkInfo != null) { - retLinkInfo = new LinkInfo(linkInfo); - } - lock.readLock().unlock(); - return retLinkInfo; - } - - @Override - public String getName() { - return MODULE_NAME; - } - - //********************* - // OFMessage Listener - //********************* - - @Override - public Command receive(IOFSwitch sw, OFMessage msg, - FloodlightContext cntx) { - switch (msg.getType()) { - case PACKET_IN: - ctrIncoming.updateCounterNoFlush(); - return this.handlePacketIn(sw.getId(), (OFPacketIn) msg, - cntx); - default: - break; - } - return Command.CONTINUE; - } - - @Override - public boolean isCallbackOrderingPrereq(OFType type, String name) { - return false; - } - - @Override - public boolean isCallbackOrderingPostreq(OFType type, String name) { - return false; - } - - //*********************************** - // Internal Methods - Packet-in Processing Related - //*********************************** - - protected Command handlePacketIn(long sw, OFPacketIn pi, - FloodlightContext cntx) { - 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; - return handleLldp((LLDP) bsn.getPayload(), sw, pi.getInPort(), false, cntx); - } else if (eth.getPayload() instanceof LLDP) { - return handleLldp((LLDP) eth.getPayload(), sw, pi.getInPort(), true, cntx); - } else if (eth.getEtherType() < 1500) { - long destMac = eth.getDestinationMAC().toLong(); - if ((destMac & LINK_LOCAL_MASK) == LINK_LOCAL_VALUE) { - ctrLinkLocalDrops.updateCounterNoFlush(); - if (log.isTraceEnabled()) { - log.trace("Ignoring packet addressed to 802.1D/Q " - + "reserved address."); - } - return Command.STOP; - } - } - - if (ignorePacketInFromSource(eth.getSourceMAC().toLong())) { - ctrIgnoreSrcMacDrops.updateCounterNoFlush(); - return Command.STOP; - } - - // If packet-in is from a quarantine port, stop processing. - NodePortTuple npt = new NodePortTuple(sw, pi.getInPort()); - if (quarantineQueue.contains(npt)) { - ctrQuarantineDrops.updateCounterNoFlush(); - return Command.STOP; - } - - return Command.CONTINUE; - } - - private boolean ignorePacketInFromSource(long srcMAC) { - Iterator<MACRange> it = ignoreMACSet.iterator(); - while (it.hasNext()) { - MACRange range = it.next(); - long mask = ~0; - if (range.ignoreBits >= 0 && range.ignoreBits <= 48) { - mask = mask << range.ignoreBits; - if ((range.baseMAC & mask) == (srcMAC & mask)) { - return true; - } - } - } - return false; - } - - private Command handleLldp(LLDP lldp, long sw, short inPort, - boolean isStandard, FloodlightContext cntx) { - // If LLDP is suppressed on this port, ignore received packet as well - IOFSwitch iofSwitch = floodlightProvider.getSwitch(sw); - - if (!isIncomingDiscoveryAllowed(sw, inPort, isStandard)) - return Command.STOP; - - // If this is a malformed LLDP exit - if (lldp.getPortId() == null || lldp.getPortId().getLength() != 3) { - return Command.STOP; - } - - long myId = ByteBuffer.wrap(controllerTLV.getValue()).getLong(); - long otherId = 0; - boolean myLLDP = false; - Boolean isReverse = null; - - ByteBuffer portBB = ByteBuffer.wrap(lldp.getPortId().getValue()); - portBB.position(1); - - Short remotePort = portBB.getShort(); - IOFSwitch remoteSwitch = null; - - // Verify this LLDP packet matches what we're looking for - for (LLDPTLV lldptlv : lldp.getOptionalTLVList()) { - if (lldptlv.getType() == 127 && lldptlv.getLength() == 12 - && lldptlv.getValue()[0] == 0x0 - && lldptlv.getValue()[1] == 0x26 - && lldptlv.getValue()[2] == (byte) 0xe1 - && lldptlv.getValue()[3] == 0x0) { - ByteBuffer dpidBB = ByteBuffer.wrap(lldptlv.getValue()); - remoteSwitch = floodlightProvider.getSwitch(dpidBB.getLong(4)); - } else if (lldptlv.getType() == 12 && lldptlv.getLength() == 8) { - otherId = ByteBuffer.wrap(lldptlv.getValue()).getLong(); - if (myId == otherId) myLLDP = true; - } else if (lldptlv.getType() == TLV_DIRECTION_TYPE - && lldptlv.getLength() == TLV_DIRECTION_LENGTH) { - if (lldptlv.getValue()[0] == TLV_DIRECTION_VALUE_FORWARD[0]) - isReverse = false; - else if (lldptlv.getValue()[0] == TLV_DIRECTION_VALUE_REVERSE[0]) - isReverse = true; - } - } - - if (myLLDP == false) { - // This is not the LLDP sent by this controller. - // If the LLDP message has multicast bit set, then we need to - // broadcast the packet as a regular packet (after checking IDs) - if (isStandard) { - if (log.isTraceEnabled()) { - log.trace("Got a standard LLDP=[{}] that was not sent by" + - " this controller. Not fowarding it.", lldp.toString()); - } - return Command.STOP; - } else if (myId < otherId) { - if (log.isTraceEnabled()) { - log.trace("Getting BDDP packets from a different controller" - + "and letting it go through normal processing chain."); - } - return Command.CONTINUE; - } - return Command.STOP; - } - - if (remoteSwitch == null) { - // Ignore LLDPs not generated by Floodlight, or from a switch that - // has recently - // disconnected, or from a switch connected to another Floodlight - // instance - if (log.isTraceEnabled()) { - log.trace("Received LLDP from remote switch not connected to the controller"); - } - return Command.STOP; - } - - if (!remoteSwitch.portEnabled(remotePort)) { - if (log.isTraceEnabled()) { - log.trace("Ignoring link with disabled source port: switch {} port {} {}", - new Object[] { remoteSwitch.getStringId(), - remotePort, - remoteSwitch.getPort(remotePort)}); - } - return Command.STOP; - } - if (suppressLinkDiscovery.contains(new NodePortTuple( - remoteSwitch.getId(), - remotePort))) { - if (log.isTraceEnabled()) { - log.trace("Ignoring link with suppressed src port: switch {} port {} {}", - new Object[] { remoteSwitch.getStringId(), - remotePort, - remoteSwitch.getPort(remotePort)}); - } - return Command.STOP; - } - if (!iofSwitch.portEnabled(inPort)) { - if (log.isTraceEnabled()) { - log.trace("Ignoring link with disabled dest port: switch {} port {} {}", - new Object[] { HexString.toHexString(sw), - inPort, - iofSwitch.getPort(inPort)}); - } - return Command.STOP; - } - - // Store the time of update to this link, and push it out to - // routingEngine - Link lt = new Link(remoteSwitch.getId(), remotePort, - iofSwitch.getId(), inPort); - - if (!isLinkAllowed(lt.getSrc(), lt.getSrcPort(), - lt.getDst(), lt.getDstPort())) - return Command.STOP; - - // Continue only if link is allowed. - Long lastLldpTime = null; - Long lastBddpTime = null; - - Long firstSeenTime = System.currentTimeMillis(); - - if (isStandard) - lastLldpTime = System.currentTimeMillis(); - else - lastBddpTime = System.currentTimeMillis(); - - LinkInfo newLinkInfo = new LinkInfo(firstSeenTime, lastLldpTime, - lastBddpTime); - - addOrUpdateLink(lt, newLinkInfo); - - // Check if reverse link exists. - // If it doesn't exist and if the forward link was seen - // first seen within a small interval, send probe on the - // reverse link. - newLinkInfo = links.get(lt); - if (newLinkInfo != null && isStandard && isReverse == false) { - Link reverseLink = new Link(lt.getDst(), lt.getDstPort(), - lt.getSrc(), lt.getSrcPort()); - LinkInfo reverseInfo = links.get(reverseLink); - if (reverseInfo == null) { - // the reverse link does not exist. - if (newLinkInfo.getFirstSeenTime() > System.currentTimeMillis() - - LINK_TIMEOUT) { - this.sendDiscoveryMessage(lt.getDst(), lt.getDstPort(), - isStandard, true); - } - } - } - - // If the received packet is a BDDP packet, then create a reverse BDDP - // link as well. - if (!isStandard) { - Link reverseLink = new Link(lt.getDst(), lt.getDstPort(), - lt.getSrc(), lt.getSrcPort()); - - // srcPortState and dstPort state are reversed. - LinkInfo reverseInfo = new LinkInfo(firstSeenTime, lastLldpTime, - lastBddpTime); - - addOrUpdateLink(reverseLink, reverseInfo); - } - - // Remove the node ports from the quarantine and maintenance queues. - NodePortTuple nptSrc = new NodePortTuple(lt.getSrc(), - lt.getSrcPort()); - NodePortTuple nptDst = new NodePortTuple(lt.getDst(), - lt.getDstPort()); - removeFromQuarantineQueue(nptSrc); - removeFromMaintenanceQueue(nptSrc); - removeFromQuarantineQueue(nptDst); - removeFromMaintenanceQueue(nptDst); - - // Consume this message - ctrLldpEol.updateCounterNoFlush(); - return Command.STOP; - } - - //*********************************** - // Internal Methods - Port Status/ New Port Processing Related - //*********************************** - /** - * Process a new port. If link discovery is disabled on the port, then do - * nothing. If autoportfast feature is enabled and the port is a fast port, - * then do nothing. Otherwise, send LLDP message. Add the port to - * quarantine. - * - * @param sw - * @param p - */ - private void processNewPort(long sw, short p) { - if (isLinkDiscoverySuppressed(sw, p)) { - // Do nothing as link discovery is suppressed. - return; - } - - IOFSwitch iofSwitch = floodlightProvider.getSwitch(sw); - if (iofSwitch == null) return; - - if (autoPortFastFeature && iofSwitch.isFastPort(p)) { - // Do nothing as the port is a fast port. - return; - } - NodePortTuple npt = new NodePortTuple(sw, p); - discover(sw, p); - // if it is not a fast port, add it to quarantine. - if (!iofSwitch.isFastPort(p)) { - addToQuarantineQueue(npt); - } else { - // Add to maintenance queue to ensure that BDDP packets - // are sent out. - addToMaintenanceQueue(npt); - } - } - - //*********************************** - // Internal Methods - Discovery Related - //*********************************** - - @LogMessageDoc(level = "ERROR", - message = "Error in link discovery updates loop", - explanation = "An unknown error occured while dispatching " - + "link update notifications", - recommendation = LogMessageDoc.GENERIC_ACTION) - private void doUpdatesThread() throws InterruptedException { - do { - LDUpdate update = updates.take(); - List<LDUpdate> updateList = new ArrayList<LDUpdate>(); - updateList.add(update); - - // Add all the pending updates to the list. - while (updates.peek() != null) { - updateList.add(updates.remove()); - } - - if (linkDiscoveryAware != null) { - if (log.isTraceEnabled()) { - log.trace("Dispatching link discovery update {} {} {} {} {} for {}", - new Object[] { - update.getOperation(), - HexString.toHexString(update.getSrc()), - update.getSrcPort(), - HexString.toHexString(update.getDst()), - update.getDstPort(), - linkDiscoveryAware }); - } - try { - for (ILinkDiscoveryListener lda : linkDiscoveryAware) { // order - // maintained - lda.linkDiscoveryUpdate(updateList); - } - } catch (Exception e) { - log.error("Error in link discovery updates loop", e); - } - } - } while (updates.peek() != null); - } - - protected boolean isLinkDiscoverySuppressed(long sw, short portNumber) { - return this.suppressLinkDiscovery.contains(new NodePortTuple(sw, - portNumber)); - } - - protected void discoverLinks() { - - // timeout known links. - timeoutLinks(); - - // increment LLDP clock - lldpClock = (lldpClock + 1) % LLDP_TO_ALL_INTERVAL; - - if (lldpClock == 0) { - if (log.isTraceEnabled()) - log.trace("Sending LLDP out on all ports."); - discoverOnAllPorts(); - } - } - - /** - * Quarantine Ports. - */ - protected class QuarantineWorker implements Runnable { - @Override - public void run() { - try { - processBDDPLists(); - } catch (Exception e) { - log.error("Error in quarantine worker thread", e); - } finally { - bddpTask.reschedule(BDDP_TASK_INTERVAL, - TimeUnit.MILLISECONDS); - } - } - } - - /** - * Add a switch port to the quarantine queue. Schedule the quarantine task - * if the quarantine queue was empty before adding this switch port. - * - * @param npt - */ - protected void addToQuarantineQueue(NodePortTuple npt) { - if (quarantineQueue.contains(npt) == false) - quarantineQueue.add(npt); - } - - /** - * Remove a switch port from the quarantine queue. - */ - protected void removeFromQuarantineQueue(NodePortTuple npt) { - // Remove all occurrences of the node port tuple from the list. - while (quarantineQueue.remove(npt)) - ; - } - - /** - * Add a switch port to maintenance queue. - * - * @param npt - */ - protected void addToMaintenanceQueue(NodePortTuple npt) { - // TODO We are not checking if the switch port tuple is already - // in the maintenance list or not. This will be an issue for - // really large number of switch ports in the network. - if (maintenanceQueue.contains(npt) == false) - maintenanceQueue.add(npt); - } - - /** - * Remove a switch port from maintenance queue. - * - * @param npt - */ - protected void removeFromMaintenanceQueue(NodePortTuple npt) { - // Remove all occurrences of the node port tuple from the queue. - while (maintenanceQueue.remove(npt)) - ; - } - - /** - * This method processes the quarantine list in bursts. The task is at most - * once per BDDP_TASK_INTERVAL. One each call, BDDP_TASK_SIZE number of - * switch ports are processed. Once the BDDP packets are sent out through - * the switch ports, the ports are removed from the quarantine list. - */ - protected void processBDDPLists() { - int count = 0; - Set<NodePortTuple> nptList = new HashSet<NodePortTuple>(); - - while (count < BDDP_TASK_SIZE && quarantineQueue.peek() != null) { - NodePortTuple npt; - npt = quarantineQueue.remove(); - sendDiscoveryMessage(npt.getNodeId(), npt.getPortId(), false, - false); - nptList.add(npt); - count++; - } - - count = 0; - while (count < BDDP_TASK_SIZE && maintenanceQueue.peek() != null) { - NodePortTuple npt; - npt = maintenanceQueue.remove(); - sendDiscoveryMessage(npt.getNodeId(), npt.getPortId(), false, - false); - count++; - } - - for (NodePortTuple npt : nptList) { - generateSwitchPortStatusUpdate(npt.getNodeId(), npt.getPortId()); - } - } - - private void generateSwitchPortStatusUpdate(long sw, short port) { - UpdateOperation operation; - - IOFSwitch iofSwitch = floodlightProvider.getSwitch(sw); - if (iofSwitch == null) return; - - OFPhysicalPort ofp = iofSwitch.getPort(port).toOFPhysicalPort(); - if (ofp == null) return; - - int srcPortState = ofp.getState(); - boolean portUp = ((srcPortState & OFPortState.OFPPS_STP_MASK.getValue()) - != OFPortState.OFPPS_STP_BLOCK.getValue()); - - if (portUp) - operation = UpdateOperation.PORT_UP; - else - operation = UpdateOperation.PORT_DOWN; - - updates.add(new LDUpdate(sw, port, operation)); - } - - protected void discover(NodePortTuple npt) { - discover(npt.getNodeId(), npt.getPortId()); - } - - protected void discover(long sw, short port) { - sendDiscoveryMessage(sw, port, true, false); - } - - /** - * Check if incoming discovery messages are enabled or not. - * @param sw - * @param port - * @param isStandard - * @return - */ - protected boolean isIncomingDiscoveryAllowed(long sw, short port, - boolean isStandard) { - - if (isLinkDiscoverySuppressed(sw, port)) { - /* Do not process LLDPs from this port as suppressLLDP is set */ - return false; - } - - IOFSwitch iofSwitch = floodlightProvider.getSwitch(sw); - if (iofSwitch == null) { - return false; - } - - if (port == OFPort.OFPP_LOCAL.getValue()) return false; - - ImmutablePort ofpPort = iofSwitch.getPort(port); - if (ofpPort == null) { - if (log.isTraceEnabled()) { - log.trace("Null physical port. sw={}, port={}", - HexString.toHexString(sw), port); - } - return false; - } - - return true; - } - - /** - * Check if outgoing discovery messages are enabled or not. - * @param sw - * @param port - * @param isStandard - * @param isReverse - * @return - */ - protected boolean isOutgoingDiscoveryAllowed(long sw, short port, - boolean isStandard, - boolean isReverse) { - - if (isLinkDiscoverySuppressed(sw, port)) { - /* Dont send LLDPs out of this port as suppressLLDP is set */ - return false; - } - - IOFSwitch iofSwitch = floodlightProvider.getSwitch(sw); - if (iofSwitch == null) { - return false; - } - - if (port == OFPort.OFPP_LOCAL.getValue()) return false; - - ImmutablePort ofpPort = iofSwitch.getPort(port); - if (ofpPort == null) { - if (log.isTraceEnabled()) { - log.trace("Null physical port. sw={}, port={}", - HexString.toHexString(sw), port); - } - return false; - } - - // For fast ports, do not send forward LLDPs or BDDPs. - if (!isReverse && autoPortFastFeature && iofSwitch.isFastPort(port)) - return false; - return true; - } - - /** - * Get the actions for packet-out corresponding to a specific port. - * This is a placeholder for adding actions if any port-specific - * actions are desired. The default action is simply to output to - * the given port. - * @param port - * @return - */ - protected List<OFAction> getDiscoveryActions (IOFSwitch sw, OFPhysicalPort port){ - // set actions - List<OFAction> actions = new ArrayList<OFAction>(); - actions.add(new OFActionOutput(port.getPortNumber(), (short) 0)); - return actions; - } - - /** - * Send link discovery message out of a given switch port. The discovery - * message may be a standard LLDP or a modified LLDP, where the dst mac - * address is set to :ff. TODO: The modified LLDP will updated in the future - * and may use a different eth-type. - * - * @param sw - * @param port - * @param isStandard - * indicates standard or modified LLDP - * @param isReverse - * indicates whether the LLDP was sent as a response - */ - @LogMessageDoc(level = "ERROR", - message = "Failure sending LLDP out port {port} on switch {switch}", - explanation = "An I/O error occured while sending LLDP message " - + "to the switch.", - recommendation = LogMessageDoc.CHECK_SWITCH) - protected void sendDiscoveryMessage(long sw, short port, - boolean isStandard, boolean isReverse) { - - // Takes care of all checks including null pointer checks. - if (!isOutgoingDiscoveryAllowed(sw, port, isStandard, isReverse)) - return; - - IOFSwitch iofSwitch = floodlightProvider.getSwitch(sw); - OFPhysicalPort ofpPort = iofSwitch.getPort(port).toOFPhysicalPort(); - - if (log.isTraceEnabled()) { - log.trace("Sending LLDP packet out of swich: {}, port: {}", - HexString.toHexString(sw), port); - } - OFPacketOut po = generateLLDPMessage(sw, port, isStandard, isReverse); - - // Add actions - List<OFAction> actions = getDiscoveryActions(iofSwitch, ofpPort); - po.setActions(actions); - short actionLength = 0; - Iterator <OFAction> actionIter = actions.iterator(); - while (actionIter.hasNext()) { - actionLength += actionIter.next().getLength(); - } - po.setActionsLength(actionLength); - - // po already has the minimum length + data length set - // simply add the actions length to this. - po.setLengthU(po.getLengthU() + po.getActionsLength()); - - // send - try { - iofSwitch.write(po, null); - iofSwitch.flush(); - } catch (IOException e) { - log.error("Failure sending LLDP out port {} on switch {}", - new Object[] { port, iofSwitch.getStringId() }, e); - } - } - - /** - * Send LLDPs to all switch-ports - */ - protected void discoverOnAllPorts() { - if (log.isTraceEnabled()) { - log.trace("Sending LLDP packets out of all the enabled ports"); - } - // Send standard LLDPs - for (long sw : floodlightProvider.getAllSwitchDpids()) { - IOFSwitch iofSwitch = floodlightProvider.getSwitch(sw); - if (iofSwitch == null) continue; - if (iofSwitch.getEnabledPorts() != null) { - for (ImmutablePort ofp : iofSwitch.getEnabledPorts()) { - if (isLinkDiscoverySuppressed(sw, ofp.getPortNumber())) - continue; - if (autoPortFastFeature - && iofSwitch.isFastPort(ofp.getPortNumber())) - continue; - - // sends forward LLDP only non-fastports. - sendDiscoveryMessage(sw, ofp.getPortNumber(), true, - false); - - // If the switch port is not already in the maintenance - // queue, add it. - NodePortTuple npt = new NodePortTuple( - sw, - ofp.getPortNumber()); - addToMaintenanceQueue(npt); - } - } - } - } - - protected UpdateOperation getUpdateOperation(int srcPortState, - int dstPortState) { - boolean added = (((srcPortState & OFPortState.OFPPS_STP_MASK.getValue()) - != OFPortState.OFPPS_STP_BLOCK.getValue()) && - ((dstPortState & OFPortState.OFPPS_STP_MASK.getValue()) - != OFPortState.OFPPS_STP_BLOCK.getValue())); - - if (added) return UpdateOperation.LINK_UPDATED; - return UpdateOperation.LINK_REMOVED; - } - - protected UpdateOperation getUpdateOperation(int srcPortState) { - boolean portUp = ((srcPortState & OFPortState.OFPPS_STP_MASK.getValue()) != OFPortState.OFPPS_STP_BLOCK.getValue()); - - if (portUp) - return UpdateOperation.PORT_UP; - else - return UpdateOperation.PORT_DOWN; - } - - //************************************ - // Internal Methods - Link Operations Related - //************************************ - - /** - * This method is used to specifically ignore/consider specific links. - */ - protected boolean isLinkAllowed(long src, short srcPort, - long dst, short dstPort) { - return true; - } - - private boolean addLink(Link lt, LinkInfo newInfo) { - NodePortTuple srcNpt, dstNpt; - - srcNpt = new NodePortTuple(lt.getSrc(), lt.getSrcPort()); - dstNpt = new NodePortTuple(lt.getDst(), lt.getDstPort()); - - // index it by switch source - if (!switchLinks.containsKey(lt.getSrc())) - switchLinks.put(lt.getSrc(), - new HashSet<Link>()); - switchLinks.get(lt.getSrc()).add(lt); - - // index it by switch dest - if (!switchLinks.containsKey(lt.getDst())) - switchLinks.put(lt.getDst(), - new HashSet<Link>()); - switchLinks.get(lt.getDst()).add(lt); - - // index both ends by switch:port - if (!portLinks.containsKey(srcNpt)) - portLinks.put(srcNpt, - new HashSet<Link>()); - portLinks.get(srcNpt).add(lt); - - if (!portLinks.containsKey(dstNpt)) - portLinks.put(dstNpt, - new HashSet<Link>()); - portLinks.get(dstNpt).add(lt); - - return true; - } - - protected boolean updateLink(Link lt, LinkInfo oldInfo, LinkInfo newInfo) { - boolean linkChanged = false; - // Since the link info is already there, we need to - // update the right fields. - if (newInfo.getUnicastValidTime() == null) { - // This is due to a multicast LLDP, so copy the old unicast - // value. - if (oldInfo.getUnicastValidTime() != null) { - newInfo.setUnicastValidTime(oldInfo.getUnicastValidTime()); - } - } else if (newInfo.getMulticastValidTime() == null) { - // This is due to a unicast LLDP, so copy the old multicast - // value. - if (oldInfo.getMulticastValidTime() != null) { - newInfo.setMulticastValidTime(oldInfo.getMulticastValidTime()); - } - } - - Long oldTime = oldInfo.getUnicastValidTime(); - Long newTime = newInfo.getUnicastValidTime(); - // the link has changed its state between openflow and - // non-openflow - // if the unicastValidTimes are null or not null - if (oldTime != null & newTime == null) { - linkChanged = true; - } else if (oldTime == null & newTime != null) { - linkChanged = true; - } - - return linkChanged; - } - - @LogMessageDocs({ - @LogMessageDoc(message="Inter-switch link detected:", - explanation="Detected a new link between two openflow switches," + - "use show link to find current status"), - @LogMessageDoc(message="Inter-switch link updated:", - explanation="Detected a link change between two openflow switches, " + - "use show link to find current status") - }) - protected boolean addOrUpdateLink(Link lt, LinkInfo newInfo) { - - boolean linkChanged = false; - - lock.writeLock().lock(); - 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() < newInfo.getFirstSeenTime()) - newInfo.setFirstSeenTime(oldInfo.getFirstSeenTime()); - - if (log.isTraceEnabled()) { - log.trace("addOrUpdateLink: {} {}", - lt, - (newInfo.getMulticastValidTime() != null) ? "multicast" - : "unicast"); - } - - UpdateOperation updateOperation = null; - linkChanged = false; - - if (oldInfo == null) { - addLink(lt, newInfo); - updateOperation = UpdateOperation.LINK_UPDATED; - linkChanged = true; - - // Log direct links only. Multi-hop links may be numerous - // Add all to event history - LinkType linkType = getLinkType(lt, newInfo); - if (linkType == ILinkDiscovery.LinkType.DIRECT_LINK) { - log.info("Inter-switch link detected: {}", lt); - evDirectLink.updateEventNoFlush(new DirectLinkEvent(lt.getSrc(), - lt.getSrcPort(), lt.getDst(), lt.getDstPort(), "direct-link-added::rcvd LLDP")); - } - notifier.postNotification("Link added: " + lt.toString()); - } else { - linkChanged = updateLink(lt, oldInfo, newInfo); - if (linkChanged) { - updateOperation = UpdateOperation.LINK_UPDATED; - LinkType linkType = getLinkType(lt, newInfo); - if (linkType == ILinkDiscovery.LinkType.DIRECT_LINK) { - log.info("Inter-switch link updated: {}", lt); - evDirectLink.updateEventNoFlush(new DirectLinkEvent(lt.getSrc(), - lt.getSrcPort(), lt.getDst(), lt.getDstPort(), - "link-port-state-updated::rcvd LLDP")); - } - notifier.postNotification("Link updated: " + lt.toString()); - } - } - - // Write changes to storage. This will always write the updated - // valid time, plus the port states if they've changed (i.e. if - // they weren't set to null in the previous block of code. - writeLinkToStorage(lt, newInfo); - - if (linkChanged) { - // find out if the link was added or removed here. - updates.add(new LDUpdate(lt.getSrc(), lt.getSrcPort(), - lt.getDst(), lt.getDstPort(), - getLinkType(lt, newInfo), - updateOperation)); - } - } finally { - lock.writeLock().unlock(); - } - - return linkChanged; - } - - /** - * Delete a link - * - * @param link - * - link to be deleted. - * @param reason - * - reason why the link is deleted. - */ - protected void deleteLink(Link link, String reason) { - if (link == null) - return; - List<Link> linkList = new ArrayList<Link>(); - linkList.add(link); - deleteLinks(linkList, reason); - } - /** - * Removes links from memory and storage. - * - * @param links - * The List of @LinkTuple to delete. - */ - protected void deleteLinks(List<Link> links, String reason) { - deleteLinks(links, reason, null); - } - - /** - * Removes links from memory and storage. - * - * @param links - * The List of @LinkTuple to delete. - */ - @LogMessageDoc(message="Inter-switch link removed:", - explanation="A previously detected link between two openflow switches no longer exists, " + - "use show link to find current status") - protected void deleteLinks(List<Link> links, String reason, - List<LDUpdate> updateList) { - - NodePortTuple srcNpt, dstNpt; - List<LDUpdate> linkUpdateList = new ArrayList<LDUpdate>(); - lock.writeLock().lock(); - try { - for (Link lt : links) { - srcNpt = new NodePortTuple(lt.getSrc(), lt.getSrcPort()); - dstNpt = new NodePortTuple(lt.getDst(), lt.getDstPort()); - - if (switchLinks.containsKey(lt.getSrc())) { - switchLinks.get(lt.getSrc()).remove(lt); - if (switchLinks.get(lt.getSrc()).isEmpty()) - this.switchLinks.remove(lt.getSrc()); - } - if (this.switchLinks.containsKey(lt.getDst())) { - switchLinks.get(lt.getDst()).remove(lt); - if (this.switchLinks.get(lt.getDst()).isEmpty()) - this.switchLinks.remove(lt.getDst()); - } - - if (this.portLinks.get(srcNpt) != null) { - this.portLinks.get(srcNpt).remove(lt); - if (this.portLinks.get(srcNpt).isEmpty()) - this.portLinks.remove(srcNpt); - } - if (this.portLinks.get(dstNpt) != null) { - this.portLinks.get(dstNpt).remove(lt); - if (this.portLinks.get(dstNpt).isEmpty()) - this.portLinks.remove(dstNpt); - } - - LinkInfo info = this.links.remove(lt); - LinkType linkType = getLinkType(lt, info); - linkUpdateList.add(new LDUpdate(lt.getSrc(), - lt.getSrcPort(), - lt.getDst(), - lt.getDstPort(), - linkType, - UpdateOperation.LINK_REMOVED)); - - // FIXME: link type shows up as invalid now -- thus not checking if - // link type is a direct link - evDirectLink.updateEventWithFlush(new DirectLinkEvent(lt.getSrc(), - lt.getSrcPort(), lt.getDst(), lt.getDstPort(), - "link-deleted::" + reason)); - // remove link from storage. - removeLinkFromStorage(lt); - - // TODO Whenever link is removed, it has to checked if - // the switchports must be added to quarantine. - - if (linkType == ILinkDiscovery.LinkType.DIRECT_LINK) { - log.info("Inter-switch link removed: {}", lt); - notifier.postNotification("Inter-switch link removed: " + - lt.toString()); - } else if (log.isTraceEnabled()) { - log.trace("Deleted link {}", lt); - } - } - } finally { - if (updateList != null) linkUpdateList.addAll(updateList); - updates.addAll(linkUpdateList); - lock.writeLock().unlock(); - } - } - - /** - * Delete links incident on a given switch port. - * - * @param npt - * @param reason - */ - protected void deleteLinksOnPort(NodePortTuple npt, String reason) { - List<Link> eraseList = new ArrayList<Link>(); - if (this.portLinks.containsKey(npt)) { - if (log.isTraceEnabled()) { - log.trace("handlePortStatus: Switch {} port #{} " - + "removing links {}", - new Object[] { - HexString.toHexString(npt.getNodeId()), - npt.getPortId(), - this.portLinks.get(npt) }); - } - eraseList.addAll(this.portLinks.get(npt)); - deleteLinks(eraseList, reason); - } - } - - /** - * Iterates through the list of links and deletes if the last discovery - * message reception time exceeds timeout values. - */ - protected void timeoutLinks() { - List<Link> eraseList = new ArrayList<Link>(); - Long curTime = System.currentTimeMillis(); - boolean linkChanged = false; - - // reentrant required here because deleteLink also write locks - lock.writeLock().lock(); - try { - Iterator<Entry<Link, LinkInfo>> it = this.links.entrySet() - .iterator(); - while (it.hasNext()) { - Entry<Link, LinkInfo> entry = it.next(); - Link lt = entry.getKey(); - LinkInfo info = entry.getValue(); - - // Timeout the unicast and multicast LLDP valid times - // independently. - if ((info.getUnicastValidTime() != null) - && (info.getUnicastValidTime() - + (this.LINK_TIMEOUT * 1000) < curTime)) { - info.setUnicastValidTime(null); - linkChanged = true; - } - if ((info.getMulticastValidTime() != null) - && (info.getMulticastValidTime() - + (this.LINK_TIMEOUT * 1000) < curTime)) { - info.setMulticastValidTime(null); - linkChanged = true; - } - // Add to the erase list only if the unicast - // time is null. - if (info.getUnicastValidTime() == null - && info.getMulticastValidTime() == null) { - eraseList.add(entry.getKey()); - } else if (linkChanged) { - updates.add(new LDUpdate(lt.getSrc(), lt.getSrcPort(), - lt.getDst(), lt.getDstPort(), - getLinkType(lt, info), - UpdateOperation.LINK_UPDATED)); - } - } - - // if any link was deleted or any link was changed. - if ((eraseList.size() > 0) || linkChanged) { - deleteLinks(eraseList, "LLDP timeout"); - } - } finally { - lock.writeLock().unlock(); - } - } - - //****************** - // Internal Helper Methods - //****************** - @LogMessageDoc(level="WARN", - message="Could not get list of interfaces of local machine to " + - "encode in TLV: {detail-msg}", - explanation="Outgoing LLDP packets encode a unique hash to " + - "identify the local machine. The list of network " + - "interfaces is used as input and the controller failed " + - "to query this list", - recommendation=LogMessageDoc.REPORT_CONTROLLER_BUG) - protected void setControllerTLV() { - // Setting the controllerTLVValue based on current nano time, - // controller's IP address, and the network interface object hash - // the corresponding IP address. - - final int prime = 7867; - - byte[] controllerTLVValue = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }; // 8 - // byte - // value. - ByteBuffer bb = ByteBuffer.allocate(10); - - long result = System.nanoTime(); - try{ - // Use some data specific to the machine this controller is - // running on. In this case: the list of network interfaces - Enumeration<NetworkInterface> ifaces = - NetworkInterface.getNetworkInterfaces(); - if (ifaces != null) { - result = result * prime + ifaces.hashCode(); - } - } catch (SocketException e) { - log.warn("Could not get list of interfaces of local machine to " + - "encode in TLV: {}", e.toString()); - } - // set the first 4 bits to 0. - result = result & (0x0fffffffffffffffL); - - bb.putLong(result); - - bb.rewind(); - bb.get(controllerTLVValue, 0, 8); - - this.controllerTLV = new LLDPTLV().setType((byte) 0x0c) - .setLength((short) controllerTLVValue.length) - .setValue(controllerTLVValue); - } - - //****************** - // IOFSwitchListener - //****************** - private void handlePortDown(long switchId, short portNumber) { - NodePortTuple npt = new NodePortTuple(switchId, portNumber); - deleteLinksOnPort(npt, "Port Status Changed"); - LDUpdate update = new LDUpdate(switchId, portNumber, - UpdateOperation.PORT_DOWN); - updates.add(update); - } - /** - * We don't react the port changed notifications here. we listen for - * OFPortStatus messages directly. Might consider using this notifier - * instead - */ - @Override - public void switchPortChanged(long switchId, - ImmutablePort port, - IOFSwitch.PortChangeType type) { - - switch (type) { - case UP: - processNewPort(switchId, port.getPortNumber()); - break; - case DELETE: case DOWN: - handlePortDown(switchId, port.getPortNumber()); - break; - case OTHER_UPDATE: case ADD: - // This is something other than port add or delete. - // Topology does not worry about this. - // If for some reason the port features change, which - // we may have to react. - break; - } - } - - @Override - public void switchAdded(long switchId) { - // no-op - // We don't do anything at switch added, but we do only when the - // switch is activated. - } - - @Override - public void switchRemoved(long sw) { +IOFSwitchListener, IStorageSourceListener, ILinkDiscoveryService, +IFloodlightModule, IInfoProvider { + protected static final Logger log = LoggerFactory.getLogger(LinkDiscoveryManager.class); + protected static final INotificationManager notifier = + NotificationManagerFactory.getNotificationManager(LinkDiscoveryManager.class); + + public static final String MODULE_NAME = "linkdiscovery"; + + // Names of table/fields for links in the storage API + private static final String TOPOLOGY_TABLE_NAME = "controller_topologyconfig"; + private static final String TOPOLOGY_ID = "id"; + private static final String TOPOLOGY_AUTOPORTFAST = "autoportfast"; + + private static final String LINK_TABLE_NAME = "controller_link"; + private static final String LINK_ID = "id"; + private static final String LINK_SRC_SWITCH = "src_switch_id"; + private static final String LINK_SRC_PORT = "src_port"; + private static final String LINK_DST_SWITCH = "dst_switch_id"; + private static final String LINK_DST_PORT = "dst_port"; + private static final String LINK_VALID_TIME = "valid_time"; + private static final String LINK_TYPE = "link_type"; + private static final String SWITCH_CONFIG_TABLE_NAME = "controller_switchconfig"; + + // Event updaters for debug events + protected IEventCategory<DirectLinkEvent> eventCategory; + + protected IFloodlightProviderService floodlightProviderService; + protected IOFSwitchService switchService; + protected IStorageSourceService storageSourceService; + protected IThreadPoolService threadPoolService; + protected IRestApiService restApiService; + protected IDebugCounterService debugCounterService; + protected IDebugEventService debugEventService; + protected IShutdownService shutdownService; + + // Role + protected HARole role; + + // LLDP and BDDP fields + private static final byte[] LLDP_STANDARD_DST_MAC_STRING = + HexString.fromHexString("01:80:c2:00:00:0e"); + private static final long LINK_LOCAL_MASK = 0xfffffffffff0L; + private static final long LINK_LOCAL_VALUE = 0x0180c2000000L; + protected static int EVENT_HISTORY_SIZE = 1024; // in seconds + + // BigSwitch OUI is 5C:16:C7, so 5D:16:C7 is the multicast version + // private static final String LLDP_BSN_DST_MAC_STRING = + // "5d:16:c7:00:00:01"; + private static final String LLDP_BSN_DST_MAC_STRING = "ff:ff:ff:ff:ff:ff"; + + // Direction TLVs are used to indicate if the LLDPs were sent + // periodically or in response to a recieved LLDP + private static final byte TLV_DIRECTION_TYPE = 0x73; + private static final short TLV_DIRECTION_LENGTH = 1; // 1 byte + private static final byte TLV_DIRECTION_VALUE_FORWARD[] = { 0x01 }; + private static final byte TLV_DIRECTION_VALUE_REVERSE[] = { 0x02 }; + private static final LLDPTLV forwardTLV = new LLDPTLV().setType(TLV_DIRECTION_TYPE) + .setLength(TLV_DIRECTION_LENGTH) + .setValue(TLV_DIRECTION_VALUE_FORWARD); + + private static final LLDPTLV reverseTLV = new LLDPTLV().setType(TLV_DIRECTION_TYPE) + .setLength(TLV_DIRECTION_LENGTH) + .setValue(TLV_DIRECTION_VALUE_REVERSE); + + // Link discovery task details. + protected SingletonTask discoveryTask; + protected final int DISCOVERY_TASK_INTERVAL = 1; + protected final int LINK_TIMEOUT = 35; // timeout as part of LLDP process. + protected final int LLDP_TO_ALL_INTERVAL = 15; // 15 seconds. + protected long lldpClock = 0; + // This value is intentionally kept higher than LLDP_TO_ALL_INTERVAL. + // If we want to identify link failures faster, we could decrease this + // value to a small number, say 1 or 2 sec. + protected final int LLDP_TO_KNOWN_INTERVAL = 20; // LLDP frequency for known + // links + + protected LLDPTLV controllerTLV; + protected ReentrantReadWriteLock lock; + int lldpTimeCount = 0; + + /** + * Flag to indicate if automatic port fast is enabled or not. Default is set + * to false -- Initialized in the init method as well. + */ + protected boolean AUTOPORTFAST_DEFAULT = false; + protected boolean autoPortFastFeature = AUTOPORTFAST_DEFAULT; + + /** + * Map from link to the most recent time it was verified functioning + */ + protected Map<Link, LinkInfo> links; + + /** + * Map from switch id to a set of all links with it as an endpoint + */ + protected Map<DatapathId, Set<Link>> switchLinks; + + /** + * Map from a id:port to the set of links containing it as an endpoint + */ + protected Map<NodePortTuple, Set<Link>> portLinks; + + protected volatile boolean shuttingDown = false; + + /* + * topology aware components are called in the order they were added to the + * the array + */ + protected ArrayList<ILinkDiscoveryListener> linkDiscoveryAware; + protected BlockingQueue<LDUpdate> updates; + protected Thread updatesThread; + + /** + * List of ports through which LLDP/BDDPs are not sent. + */ + protected Set<NodePortTuple> suppressLinkDiscovery; + + /** + * A list of ports that are quarantined for discovering links through them. + * Data traffic from these ports are not allowed until the ports are + * released from quarantine. + */ + protected LinkedBlockingQueue<NodePortTuple> quarantineQueue; + protected LinkedBlockingQueue<NodePortTuple> maintenanceQueue; + /** + * Quarantine task + */ + protected SingletonTask bddpTask; + protected final int BDDP_TASK_INTERVAL = 100; // 100 ms. + protected final int BDDP_TASK_SIZE = 10; // # of ports per iteration + + private class MACRange { + MacAddress baseMAC; + int ignoreBits; + } + protected Set<MACRange> ignoreMACSet; + + private IHAListener haListener; + + /** + * Debug Counters + */ + private IDebugCounter ctrQuarantineDrops; + private IDebugCounter ctrIgnoreSrcMacDrops; + private IDebugCounter ctrIncoming; + private IDebugCounter ctrLinkLocalDrops; + private IDebugCounter ctrLldpEol; + + private final String PACKAGE = LinkDiscoveryManager.class.getPackage().getName(); + + + //********************* + // ILinkDiscoveryService + //********************* + + @Override + public OFPacketOut generateLLDPMessage(DatapathId sw, OFPort port, + boolean isStandard, boolean isReverse) { + + IOFSwitch iofSwitch = switchService.getSwitch(sw); + OFPortDesc ofpPort = iofSwitch.getPort(port); + + if (log.isTraceEnabled()) { + log.trace("Sending LLDP packet out of swich: {}, port: {}", + sw.toString(), port); + } + + // using "nearest customer bridge" MAC address for broadest possible + // propagation + // through provider and TPMR bridges (see IEEE 802.1AB-2009 and + // 802.1Q-2011), + // in particular the Linux bridge which behaves mostly like a provider + // bridge + byte[] chassisId = new byte[] { 4, 0, 0, 0, 0, 0, 0 }; // filled in + // later + byte[] portId = new byte[] { 2, 0, 0 }; // filled in later + byte[] ttlValue = new byte[] { 0, 0x78 }; + // OpenFlow OUI - 00-26-E1 + byte[] dpidTLVValue = new byte[] { 0x0, 0x26, (byte) 0xe1, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }; + LLDPTLV dpidTLV = new LLDPTLV().setType((byte) 127) + .setLength((short) dpidTLVValue.length) + .setValue(dpidTLVValue); + + byte[] dpidArray = new byte[8]; + ByteBuffer dpidBB = ByteBuffer.wrap(dpidArray); + ByteBuffer portBB = ByteBuffer.wrap(portId, 1, 2); + + DatapathId dpid = sw; + dpidBB.putLong(dpid.getLong()); + // set the chassis id's value to last 6 bytes of dpid + System.arraycopy(dpidArray, 2, chassisId, 1, 6); + // set the optional tlv to the full dpid + System.arraycopy(dpidArray, 0, dpidTLVValue, 4, 8); + + // TODO: Consider remove this block of code. + // It's evil to overwrite port object. The the old code always + // overwrote mac address, we now only overwrite zero macs and + // log a warning, mostly for paranoia. + byte[] srcMac = ofpPort.getHwAddr().getBytes(); + byte[] zeroMac = { 0, 0, 0, 0, 0, 0 }; + if (Arrays.equals(srcMac, zeroMac)) { + log.warn("Port {}/{} has zero hareware address" + + "overwrite with lower 6 bytes of dpid", + dpid.toString(), ofpPort.getPortNo().getPortNumber()); + System.arraycopy(dpidArray, 2, srcMac, 0, 6); + } + + // set the portId to the outgoing port + portBB.putShort(port.getShortPortNumber()); + if (log.isTraceEnabled()) { + log.trace("Sending LLDP out of interface: {}/{}", + sw.toString(), port); + } + + LLDP lldp = new LLDP(); + lldp.setChassisId(new LLDPTLV().setType((byte) 1) + .setLength((short) chassisId.length) + .setValue(chassisId)); + lldp.setPortId(new LLDPTLV().setType((byte) 2) + .setLength((short) portId.length) + .setValue(portId)); + lldp.setTtl(new LLDPTLV().setType((byte) 3) + .setLength((short) ttlValue.length) + .setValue(ttlValue)); + lldp.getOptionalTLVList().add(dpidTLV); + + // Add the controller identifier to the TLV value. + lldp.getOptionalTLVList().add(controllerTLV); + if (isReverse) { + lldp.getOptionalTLVList().add(reverseTLV); + } else { + lldp.getOptionalTLVList().add(forwardTLV); + } + + Ethernet ethernet; + if (isStandard) { + ethernet = new Ethernet().setSourceMACAddress(ofpPort.getHwAddr()) + .setDestinationMACAddress(LLDP_STANDARD_DST_MAC_STRING) + .setEtherType(Ethernet.TYPE_LLDP); + ethernet.setPayload(lldp); + } else { + BSN bsn = new BSN(BSN.BSN_TYPE_BDDP); + bsn.setPayload(lldp); + + ethernet = new Ethernet().setSourceMACAddress(ofpPort.getHwAddr()) + .setDestinationMACAddress(LLDP_BSN_DST_MAC_STRING) + .setEtherType(Ethernet.TYPE_BSN); + ethernet.setPayload(bsn); + } + + // serialize and wrap in a packet out + byte[] data = ethernet.serialize(); + OFPacketOut.Builder pob = switchService.getSwitch(sw).getOFFactory().buildPacketOut(); + pob.setBufferId(OFBufferId.NO_BUFFER); + pob.setInPort(OFPort.ANY); + + // set data and data length + pob.setData(data); + + return pob.build(); + } + + /** + * Get the LLDP sending period in seconds. + * + * @return LLDP sending period in seconds. + */ + public int getLldpFrequency() { + return LLDP_TO_KNOWN_INTERVAL; + } + + /** + * Get the LLDP timeout value in seconds + * + * @return LLDP timeout value in seconds + */ + public int getLldpTimeout() { + return LINK_TIMEOUT; + } + + @Override + public Map<NodePortTuple, Set<Link>> getPortLinks() { + return portLinks; + } + + @Override + public Set<NodePortTuple> getSuppressLLDPsInfo() { + return suppressLinkDiscovery; + } + + /** + * Add a switch port to the suppressed LLDP list. Remove any known links on + * the switch port. + */ + @Override + public void AddToSuppressLLDPs(DatapathId sw, OFPort port) { + NodePortTuple npt = new NodePortTuple(sw, port); + this.suppressLinkDiscovery.add(npt); + deleteLinksOnPort(npt, "LLDP suppressed."); + } + + /** + * Remove a switch port from the suppressed LLDP list. Discover links on + * that switchport. + */ + @Override + public void RemoveFromSuppressLLDPs(DatapathId sw, OFPort port) { + NodePortTuple npt = new NodePortTuple(sw, port); + this.suppressLinkDiscovery.remove(npt); + discover(npt); + } + + public boolean isShuttingDown() { + return shuttingDown; + } + + @Override + public boolean isTunnelPort(DatapathId sw, OFPort port) { + return false; + } + + @Override + public ILinkDiscovery.LinkType getLinkType(Link lt, LinkInfo info) { + if (info.getUnicastValidTime() != null) { + return ILinkDiscovery.LinkType.DIRECT_LINK; + } else if (info.getMulticastValidTime() != null) { + return ILinkDiscovery.LinkType.MULTIHOP_LINK; + } + return ILinkDiscovery.LinkType.INVALID_LINK; + } + + @Override + public Set<OFPort> getQuarantinedPorts(DatapathId sw) { + Set<OFPort> qPorts = new HashSet<OFPort>(); + + Iterator<NodePortTuple> iter = quarantineQueue.iterator(); + while (iter.hasNext()) { + NodePortTuple npt = iter.next(); + if (npt.getNodeId().equals(sw)) { + qPorts.add(npt.getPortId()); + } + } + return qPorts; + } + + @Override + public Map<DatapathId, Set<Link>> getSwitchLinks() { + return this.switchLinks; + } + + @Override + public void addMACToIgnoreList(MacAddress mac, int ignoreBits) { + MACRange range = new MACRange(); + range.baseMAC = mac; + range.ignoreBits = ignoreBits; + ignoreMACSet.add(range); + } + + @Override + public boolean isAutoPortFastFeature() { + return autoPortFastFeature; + } + + @Override + public void setAutoPortFastFeature(boolean autoPortFastFeature) { + this.autoPortFastFeature = autoPortFastFeature; + } + + @Override + public void addListener(ILinkDiscoveryListener listener) { + linkDiscoveryAware.add(listener); + } + + @Override + public Map<Link, LinkInfo> getLinks() { + lock.readLock().lock(); + Map<Link, LinkInfo> result; + try { + result = new HashMap<Link, LinkInfo>(links); + } finally { + lock.readLock().unlock(); + } + return result; + } + + @Override + public LinkInfo getLinkInfo(Link link) { + lock.readLock().lock(); + LinkInfo linkInfo = links.get(link); + LinkInfo retLinkInfo = null; + if (linkInfo != null) { + retLinkInfo = new LinkInfo(linkInfo); + } + lock.readLock().unlock(); + return retLinkInfo; + } + + @Override + public String getName() { + return MODULE_NAME; + } + + //********************* + // OFMessage Listener + //********************* + + @Override + public Command receive(IOFSwitch sw, OFMessage msg, + FloodlightContext cntx) { + switch (msg.getType()) { + case PACKET_IN: + ctrIncoming.increment(); + return this.handlePacketIn(sw.getId(), (OFPacketIn) msg, + cntx); + default: + break; + } + return Command.CONTINUE; + } + + @Override + public boolean isCallbackOrderingPrereq(OFType type, String name) { + return false; + } + + @Override + public boolean isCallbackOrderingPostreq(OFType type, String name) { + return false; + } + + //*********************************** + // Internal Methods - Packet-in Processing Related + //*********************************** + + protected Command handlePacketIn(DatapathId sw, OFPacketIn pi, + FloodlightContext cntx) { + 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; + return handleLldp((LLDP) bsn.getPayload(), sw, pi.getInPort(), false, cntx); + } else if (eth.getPayload() instanceof LLDP) { + return handleLldp((LLDP) eth.getPayload(), sw, pi.getInPort(), true, cntx); + } else if (eth.getEtherType() < 1500) { + long destMac = eth.getDestinationMACAddress().getLong(); + if ((destMac & LINK_LOCAL_MASK) == LINK_LOCAL_VALUE) { + ctrLinkLocalDrops.increment(); + if (log.isTraceEnabled()) { + log.trace("Ignoring packet addressed to 802.1D/Q " + + "reserved address."); + } + return Command.STOP; + } + } + + if (ignorePacketInFromSource(eth.getSourceMACAddress())) { + ctrIgnoreSrcMacDrops.increment(); + return Command.STOP; + } + + // If packet-in is from a quarantine port, stop processing. + NodePortTuple npt = new NodePortTuple(sw, (pi.getVersion().compareTo(OFVersion.OF_13) < 0 ? pi.getInPort() : pi.getMatch().get(MatchField.IN_PORT))); + if (quarantineQueue.contains(npt)) { + ctrQuarantineDrops.increment(); + return Command.STOP; + } + + return Command.CONTINUE; + } + + private boolean ignorePacketInFromSource(MacAddress srcMAC) { + Iterator<MACRange> it = ignoreMACSet.iterator(); + while (it.hasNext()) { + MACRange range = it.next(); + long mask = ~0; + if (range.ignoreBits >= 0 && range.ignoreBits <= 48) { + mask = mask << range.ignoreBits; + if ((range.baseMAC.getLong() & mask) == (srcMAC.getLong() & mask)) { + return true; + } + } + } + return false; + } + + private Command handleLldp(LLDP lldp, DatapathId sw, OFPort inPort, + boolean isStandard, FloodlightContext cntx) { + // If LLDP is suppressed on this port, ignore received packet as well + IOFSwitch iofSwitch = switchService.getSwitch(sw); + + if (!isIncomingDiscoveryAllowed(sw, inPort, isStandard)) + return Command.STOP; + + // If this is a malformed LLDP exit + if (lldp.getPortId() == null || lldp.getPortId().getLength() != 3) { + return Command.STOP; + } + + long myId = ByteBuffer.wrap(controllerTLV.getValue()).getLong(); + long otherId = 0; + boolean myLLDP = false; + Boolean isReverse = null; + + ByteBuffer portBB = ByteBuffer.wrap(lldp.getPortId().getValue()); + portBB.position(1); + + OFPort remotePort = OFPort.of(portBB.getShort()); + IOFSwitch remoteSwitch = null; + + // Verify this LLDP packet matches what we're looking for + for (LLDPTLV lldptlv : lldp.getOptionalTLVList()) { + if (lldptlv.getType() == 127 && lldptlv.getLength() == 12 + && lldptlv.getValue()[0] == 0x0 + && lldptlv.getValue()[1] == 0x26 + && lldptlv.getValue()[2] == (byte) 0xe1 + && lldptlv.getValue()[3] == 0x0) { + ByteBuffer dpidBB = ByteBuffer.wrap(lldptlv.getValue()); + remoteSwitch = switchService.getSwitch(DatapathId.of(dpidBB.getLong(4))); + } else if (lldptlv.getType() == 12 && lldptlv.getLength() == 8) { + otherId = ByteBuffer.wrap(lldptlv.getValue()).getLong(); + if (myId == otherId) myLLDP = true; + } else if (lldptlv.getType() == TLV_DIRECTION_TYPE + && lldptlv.getLength() == TLV_DIRECTION_LENGTH) { + if (lldptlv.getValue()[0] == TLV_DIRECTION_VALUE_FORWARD[0]) + isReverse = false; + else if (lldptlv.getValue()[0] == TLV_DIRECTION_VALUE_REVERSE[0]) + isReverse = true; + } + } + + if (myLLDP == false) { + // This is not the LLDP sent by this controller. + // If the LLDP message has multicast bit set, then we need to + // broadcast the packet as a regular packet (after checking IDs) + if (isStandard) { + if (log.isTraceEnabled()) { + log.trace("Got a standard LLDP=[{}] that was not sent by" + + " this controller. Not fowarding it.", lldp.toString()); + } + return Command.STOP; + } else if (myId < otherId) { + if (log.isTraceEnabled()) { + log.trace("Getting BDDP packets from a different controller" + + "and letting it go through normal processing chain."); + } + return Command.CONTINUE; + } + return Command.STOP; + } + + if (remoteSwitch == null) { + // Ignore LLDPs not generated by Floodlight, or from a switch that + // has recently + // disconnected, or from a switch connected to another Floodlight + // instance + if (log.isTraceEnabled()) { + log.trace("Received LLDP from remote switch not connected to the controller"); + } + return Command.STOP; + } + + if (!remoteSwitch.portEnabled(remotePort)) { + if (log.isTraceEnabled()) { + log.trace("Ignoring link with disabled source port: switch {} port {} {}", + new Object[] { remoteSwitch.getId().toString(), + remotePort, + remoteSwitch.getPort(remotePort)}); + } + return Command.STOP; + } + if (suppressLinkDiscovery.contains(new NodePortTuple( + remoteSwitch.getId(), + remotePort))) { + if (log.isTraceEnabled()) { + log.trace("Ignoring link with suppressed src port: switch {} port {} {}", + new Object[] { remoteSwitch.getId().toString(), + remotePort, + remoteSwitch.getPort(remotePort)}); + } + return Command.STOP; + } + if (!iofSwitch.portEnabled(inPort)) { + if (log.isTraceEnabled()) { + log.trace("Ignoring link with disabled dest port: switch {} port {} {}", + new Object[] { sw.toString(), + inPort.getPortNumber(), + iofSwitch.getPort(inPort).getPortNo().getPortNumber()}); + } + return Command.STOP; + } + + // Store the time of update to this link, and push it out to + // routingEngine + Link lt = new Link(remoteSwitch.getId(), remotePort, + iofSwitch.getId(), inPort); + + if (!isLinkAllowed(lt.getSrc(), lt.getSrcPort(), + lt.getDst(), lt.getDstPort())) + return Command.STOP; + + // Continue only if link is allowed. + Date lastLldpTime = null; + Date lastBddpTime = null; + + Date firstSeenTime = new Date(System.currentTimeMillis()); + + if (isStandard) + lastLldpTime = new Date(System.currentTimeMillis()); + else + lastBddpTime = new Date(System.currentTimeMillis()); + + LinkInfo newLinkInfo = new LinkInfo(firstSeenTime, lastLldpTime, + lastBddpTime); + + addOrUpdateLink(lt, newLinkInfo); + + // Check if reverse link exists. + // If it doesn't exist and if the forward link was seen + // first seen within a small interval, send probe on the + // reverse link. + newLinkInfo = links.get(lt); + if (newLinkInfo != null && isStandard && isReverse == false) { + Link reverseLink = new Link(lt.getDst(), lt.getDstPort(), + lt.getSrc(), lt.getSrcPort()); + LinkInfo reverseInfo = links.get(reverseLink); + if (reverseInfo == null) { + // the reverse link does not exist. + if (newLinkInfo.getFirstSeenTime().getTime() > System.currentTimeMillis() + - LINK_TIMEOUT) { + this.sendDiscoveryMessage(lt.getDst(), lt.getDstPort(), + isStandard, true); + } + } + } + + // If the received packet is a BDDP packet, then create a reverse BDDP + // link as well. + if (!isStandard) { + Link reverseLink = new Link(lt.getDst(), lt.getDstPort(), + lt.getSrc(), lt.getSrcPort()); + + // srcPortState and dstPort state are reversed. + LinkInfo reverseInfo = new LinkInfo(firstSeenTime, lastLldpTime, + lastBddpTime); + + addOrUpdateLink(reverseLink, reverseInfo); + } + + // Remove the node ports from the quarantine and maintenance queues. + NodePortTuple nptSrc = new NodePortTuple(lt.getSrc(), + lt.getSrcPort()); + NodePortTuple nptDst = new NodePortTuple(lt.getDst(), + lt.getDstPort()); + removeFromQuarantineQueue(nptSrc); + removeFromMaintenanceQueue(nptSrc); + removeFromQuarantineQueue(nptDst); + removeFromMaintenanceQueue(nptDst); + + // Consume this message + ctrLldpEol.increment(); + return Command.STOP; + } + + //*********************************** + // Internal Methods - Port Status/ New Port Processing Related + //*********************************** + /** + * Process a new port. If link discovery is disabled on the port, then do + * nothing. If autoportfast feature is enabled and the port is a fast port, + * then do nothing. Otherwise, send LLDP message. Add the port to + * quarantine. + * + * @param sw + * @param p + */ + private void processNewPort(DatapathId sw, OFPort p) { + if (isLinkDiscoverySuppressed(sw, p)) { + // Do nothing as link discovery is suppressed. + return; + } + + IOFSwitch iofSwitch = switchService.getSwitch(sw); + if (iofSwitch == null) { + return; + } + + NodePortTuple npt = new NodePortTuple(sw, p); + discover(sw, p); + addToQuarantineQueue(npt); + } + + //*********************************** + // Internal Methods - Discovery Related + //*********************************** + + @LogMessageDoc(level = "ERROR", + message = "Error in link discovery updates loop", + explanation = "An unknown error occured while dispatching " + + "link update notifications", + recommendation = LogMessageDoc.GENERIC_ACTION) + private void doUpdatesThread() throws InterruptedException { + do { + LDUpdate update = updates.take(); + List<LDUpdate> updateList = new ArrayList<LDUpdate>(); + updateList.add(update); + + // Add all the pending updates to the list. + while (updates.peek() != null) { + updateList.add(updates.remove()); + } + + if (linkDiscoveryAware != null) { + if (log.isTraceEnabled()) { + log.trace("Dispatching link discovery update {} {} {} {} {} for {}", + new Object[] { + update.getOperation(), + update.getSrc().toString(), + update.getSrcPort(), + update.getDst().toString(), + update.getDstPort(), + linkDiscoveryAware }); + } + try { + for (ILinkDiscoveryListener lda : linkDiscoveryAware) { // order + // maintained + lda.linkDiscoveryUpdate(updateList); + } + } catch (Exception e) { + log.error("Error in link discovery updates loop", e); + } + } + } while (updates.peek() != null); + } + + protected boolean isLinkDiscoverySuppressed(DatapathId sw, OFPort portNumber) { + return this.suppressLinkDiscovery.contains(new NodePortTuple(sw, + portNumber)); + } + + protected void discoverLinks() { + + // timeout known links. + timeoutLinks(); + + // increment LLDP clock + lldpClock = (lldpClock + 1) % LLDP_TO_ALL_INTERVAL; + + if (lldpClock == 0) { + if (log.isTraceEnabled()) + log.trace("Sending LLDP out on all ports."); + discoverOnAllPorts(); + } + } + + /** + * Quarantine Ports. + */ + protected class QuarantineWorker implements Runnable { + @Override + public void run() { + try { + processBDDPLists(); + } catch (Exception e) { + log.error("Error in quarantine worker thread", e); + } finally { + bddpTask.reschedule(BDDP_TASK_INTERVAL, + TimeUnit.MILLISECONDS); + } + } + } + + /** + * Add a switch port to the quarantine queue. Schedule the quarantine task + * if the quarantine queue was empty before adding this switch port. + * + * @param npt + */ + protected void addToQuarantineQueue(NodePortTuple npt) { + if (quarantineQueue.contains(npt) == false) + quarantineQueue.add(npt); + } + + /** + * Remove a switch port from the quarantine queue. + */ + protected void removeFromQuarantineQueue(NodePortTuple npt) { + // Remove all occurrences of the node port tuple from the list. + while (quarantineQueue.remove(npt)); + } + + /** + * Add a switch port to maintenance queue. + * + * @param npt + */ + protected void addToMaintenanceQueue(NodePortTuple npt) { + if (maintenanceQueue.contains(npt) == false) { + maintenanceQueue.add(npt); + } + } + + /** + * Remove a switch port from maintenance queue. + * + * @param npt + */ + protected void removeFromMaintenanceQueue(NodePortTuple npt) { + // Remove all occurrences of the node port tuple from the queue. + while (maintenanceQueue.remove(npt)); + } + + /** + * This method processes the quarantine list in bursts. The task is at most + * once per BDDP_TASK_INTERVAL. One each call, BDDP_TASK_SIZE number of + * switch ports are processed. Once the BDDP packets are sent out through + * the switch ports, the ports are removed from the quarantine list. + */ + protected void processBDDPLists() { + int count = 0; + Set<NodePortTuple> nptList = new HashSet<NodePortTuple>(); + + while (count < BDDP_TASK_SIZE && quarantineQueue.peek() != null) { + NodePortTuple npt; + npt = quarantineQueue.remove(); + sendDiscoveryMessage(npt.getNodeId(), npt.getPortId(), false, false); + nptList.add(npt); + count++; + } + + count = 0; + while (count < BDDP_TASK_SIZE && maintenanceQueue.peek() != null) { + NodePortTuple npt; + npt = maintenanceQueue.remove(); + sendDiscoveryMessage(npt.getNodeId(), npt.getPortId(), false, false); + count++; + } + + for (NodePortTuple npt : nptList) { + generateSwitchPortStatusUpdate(npt.getNodeId(), npt.getPortId()); + } + } + + private void generateSwitchPortStatusUpdate(DatapathId sw, OFPort port) { + UpdateOperation operation; + + IOFSwitch iofSwitch = switchService.getSwitch(sw); + if (iofSwitch == null) return; + + OFPortDesc ofp = iofSwitch.getPort(port); + if (ofp == null) return; + + Set<OFPortState> srcPortState = ofp.getState(); + //TODO @Ryan verify this is equivalent boolean portUp = ((srcPortState & OFPortState.OFPPS_STP_MASK.getValue()) != OFPortState.OFPPS_STP_BLOCK.getValue()); + boolean portUp = !srcPortState.contains(OFPortState.STP_BLOCK); + + if (portUp) { + operation = UpdateOperation.PORT_UP; + } else { + operation = UpdateOperation.PORT_DOWN; + } + + updates.add(new LDUpdate(sw, port, operation)); + } + + protected void discover(NodePortTuple npt) { + discover(npt.getNodeId(), npt.getPortId()); + } + + protected void discover(DatapathId sw, OFPort port) { + sendDiscoveryMessage(sw, port, true, false); + } + + /** + * Check if incoming discovery messages are enabled or not. + * @param sw + * @param port + * @param isStandard + * @return + */ + protected boolean isIncomingDiscoveryAllowed(DatapathId sw, OFPort port, + boolean isStandard) { + + if (isLinkDiscoverySuppressed(sw, port)) { + /* Do not process LLDPs from this port as suppressLLDP is set */ + return false; + } + + IOFSwitch iofSwitch = switchService.getSwitch(sw); + if (iofSwitch == null) { + return false; + } + + if (port == OFPort.LOCAL) return false; + + OFPortDesc ofpPort = iofSwitch.getPort(port); + if (ofpPort == null) { + if (log.isTraceEnabled()) { + log.trace("Null physical port. sw={}, port={}", + sw.toString(), port.getPortNumber()); + } + return false; + } + + return true; + } + + /** + * Check if outgoing discovery messages are enabled or not. + * @param sw + * @param port + * @param isStandard + * @param isReverse + * @return + */ + protected boolean isOutgoingDiscoveryAllowed(DatapathId sw, OFPort port, + boolean isStandard, + boolean isReverse) { + + if (isLinkDiscoverySuppressed(sw, port)) { + /* Dont send LLDPs out of this port as suppressLLDP is set */ + return false; + } + + IOFSwitch iofSwitch = switchService.getSwitch(sw); + if (iofSwitch == null) { + return false; + } + + if (port == OFPort.LOCAL) return false; + + OFPortDesc ofpPort = iofSwitch.getPort(port); + if (ofpPort == null) { + if (log.isTraceEnabled()) { + log.trace("Null physical port. sw={}, port={}", + sw.toString(), port.getPortNumber()); + } + return false; + } else { + return true; + } + } + + /** + * Get the actions for packet-out corresponding to a specific port. + * This is a placeholder for adding actions if any port-specific + * actions are desired. The default action is simply to output to + * the given port. + * @param port + * @return + */ + protected List<OFAction> getDiscoveryActions (IOFSwitch sw, OFPort port){ + // set actions + List<OFAction> actions = new ArrayList<OFAction>(); + actions.add(sw.getOFFactory().actions().buildOutput().setPort(port).build()); + return actions; + } + + /** + * Send link discovery message out of a given switch port. The discovery + * message may be a standard LLDP or a modified LLDP, where the dst mac + * address is set to :ff. TODO: The modified LLDP will updated in the future + * and may use a different eth-type. + * + * @param sw + * @param port + * @param isStandard + * indicates standard or modified LLDP + * @param isReverse + * indicates whether the LLDP was sent as a response + */ + @LogMessageDoc(level = "ERROR", + message = "Failure sending LLDP out port {port} on switch {switch}", + explanation = "An I/O error occured while sending LLDP message " + + "to the switch.", + recommendation = LogMessageDoc.CHECK_SWITCH) + protected void sendDiscoveryMessage(DatapathId sw, OFPort port, + boolean isStandard, boolean isReverse) { + + // Takes care of all checks including null pointer checks. + if (!isOutgoingDiscoveryAllowed(sw, port, isStandard, isReverse)) + return; + + IOFSwitch iofSwitch = switchService.getSwitch(sw); + OFPortDesc ofpPort = iofSwitch.getPort(port); + + if (log.isTraceEnabled()) { + log.trace("Sending LLDP packet out of swich: {}, port: {}", + sw.toString(), port.getPortNumber()); + } + OFPacketOut po = generateLLDPMessage(sw, port, isStandard, isReverse); + OFPacketOut.Builder pob = po.createBuilder(); + + // Add actions + List<OFAction> actions = getDiscoveryActions(iofSwitch, ofpPort.getPortNo()); + pob.setActions(actions); + + // no need to set length anymore + + // send + // no more try-catch. switch will silently fail + iofSwitch.write(po); + iofSwitch.flush(); + } + + /** + * Send LLDPs to all switch-ports + */ + protected void discoverOnAllPorts() { + if (log.isTraceEnabled()) { + log.trace("Sending LLDP packets out of all the enabled ports"); + } + // Send standard LLDPs + for (DatapathId sw : switchService.getAllSwitchDpids()) { + IOFSwitch iofSwitch = switchService.getSwitch(sw); + if (iofSwitch == null) continue; + if (iofSwitch.getEnabledPorts() != null) { + for (OFPortDesc ofp : iofSwitch.getEnabledPorts()) { + if (isLinkDiscoverySuppressed(sw, ofp.getPortNo())) { + continue; + } + + sendDiscoveryMessage(sw, ofp.getPortNo(), true, false); + + // If the switch port is not already in the maintenance + // queue, add it. + NodePortTuple npt = new NodePortTuple(sw, ofp.getPortNo()); + addToMaintenanceQueue(npt); + } + } + } + } + + protected UpdateOperation getUpdateOperation(OFPortState srcPortState, OFPortState dstPortState) { + //TODO @Ryan verify this is equivalent + //boolean added = (((srcPortState & OFPortState.STP_MASK) != OFPortState.STP_BLOCK) && ((dstPortState & OFPortState.STP_MASK) != OFPortState.STP_BLOCK)); + boolean added = ((srcPortState != OFPortState.STP_BLOCK) && (dstPortState != OFPortState.STP_BLOCK)); + + if (added) { + return UpdateOperation.LINK_UPDATED; + } else { + return UpdateOperation.LINK_REMOVED; + } + } + + protected UpdateOperation getUpdateOperation(OFPortState srcPortState) { + //TODO @Ryan verify this too + //boolean portUp = ((srcPortState & OFPortState.STP_MASK) != OFPortState.STP_BLOCK); + boolean portUp = (srcPortState != OFPortState.STP_BLOCK); + + if (portUp) { + return UpdateOperation.PORT_UP; + } else { + return UpdateOperation.PORT_DOWN; + } + } + + //************************************ + // Internal Methods - Link Operations Related + //************************************ + + /** + * This method is used to specifically ignore/consider specific links. + */ + protected boolean isLinkAllowed(DatapathId src, OFPort srcPort, + DatapathId dst, OFPort dstPort) { + return true; + } + + private boolean addLink(Link lt, LinkInfo newInfo) { + NodePortTuple srcNpt, dstNpt; + + srcNpt = new NodePortTuple(lt.getSrc(), lt.getSrcPort()); + dstNpt = new NodePortTuple(lt.getDst(), lt.getDstPort()); + + // index it by switch source + if (!switchLinks.containsKey(lt.getSrc())) + switchLinks.put(lt.getSrc(), + new HashSet<Link>()); + switchLinks.get(lt.getSrc()).add(lt); + + // index it by switch dest + if (!switchLinks.containsKey(lt.getDst())) + switchLinks.put(lt.getDst(), + new HashSet<Link>()); + switchLinks.get(lt.getDst()).add(lt); + + // index both ends by switch:port + if (!portLinks.containsKey(srcNpt)) + portLinks.put(srcNpt, + new HashSet<Link>()); + portLinks.get(srcNpt).add(lt); + + if (!portLinks.containsKey(dstNpt)) + portLinks.put(dstNpt, + new HashSet<Link>()); + portLinks.get(dstNpt).add(lt); + + return true; + } + + protected boolean updateLink(Link lt, LinkInfo oldInfo, LinkInfo newInfo) { + boolean linkChanged = false; + // Since the link info is already there, we need to + // update the right fields. + if (newInfo.getUnicastValidTime() == null) { + // This is due to a multicast LLDP, so copy the old unicast + // value. + if (oldInfo.getUnicastValidTime() != null) { + newInfo.setUnicastValidTime(oldInfo.getUnicastValidTime()); + } + } else if (newInfo.getMulticastValidTime() == null) { + // This is due to a unicast LLDP, so copy the old multicast + // value. + if (oldInfo.getMulticastValidTime() != null) { + newInfo.setMulticastValidTime(oldInfo.getMulticastValidTime()); + } + } + + Date oldTime = oldInfo.getUnicastValidTime(); + Date newTime = newInfo.getUnicastValidTime(); + // the link has changed its state between openflow and + // non-openflow + // if the unicastValidTimes are null or not null + if (oldTime != null & newTime == null) { + linkChanged = true; + } else if (oldTime == null & newTime != null) { + linkChanged = true; + } + + return linkChanged; + } + + @LogMessageDocs({ + @LogMessageDoc(message="Inter-switch link detected:", + explanation="Detected a new link between two openflow switches," + + "use show link to find current status"), + @LogMessageDoc(message="Inter-switch link updated:", + explanation="Detected a link change between two openflow switches, " + + "use show link to find current status") + }) + protected boolean addOrUpdateLink(Link lt, LinkInfo newInfo) { + + boolean linkChanged = false; + + lock.writeLock().lock(); + 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()) + newInfo.setFirstSeenTime(oldInfo.getFirstSeenTime()); + + if (log.isTraceEnabled()) { + log.trace("addOrUpdateLink: {} {}", + lt, + (newInfo.getMulticastValidTime() != null) ? "multicast" + : "unicast"); + } + + UpdateOperation updateOperation = null; + linkChanged = false; + + if (oldInfo == null) { + addLink(lt, newInfo); + updateOperation = UpdateOperation.LINK_UPDATED; + linkChanged = true; + + // Log direct links only. Multi-hop links may be numerous + // Add all to event history + LinkType linkType = getLinkType(lt, newInfo); + if (linkType == ILinkDiscovery.LinkType.DIRECT_LINK) { + log.info("Inter-switch link detected: {}", lt); + eventCategory.newEventNoFlush(new DirectLinkEvent(lt.getSrc(), + lt.getSrcPort(), lt.getDst(), lt.getDstPort(), "direct-link-added::rcvd LLDP")); + } + notifier.postNotification("Link added: " + lt.toString()); + } else { + linkChanged = updateLink(lt, oldInfo, newInfo); + if (linkChanged) { + updateOperation = UpdateOperation.LINK_UPDATED; + LinkType linkType = getLinkType(lt, newInfo); + if (linkType == ILinkDiscovery.LinkType.DIRECT_LINK) { + log.info("Inter-switch link updated: {}", lt); + eventCategory.newEventNoFlush(new DirectLinkEvent(lt.getSrc(), + lt.getSrcPort(), lt.getDst(), lt.getDstPort(), + "link-port-state-updated::rcvd LLDP")); + } + notifier.postNotification("Link updated: " + lt.toString()); + } + } + + // Write changes to storage. This will always write the updated + // valid time, plus the port states if they've changed (i.e. if + // they weren't set to null in the previous block of code. + writeLinkToStorage(lt, newInfo); + + if (linkChanged) { + // find out if the link was added or removed here. + updates.add(new LDUpdate(lt.getSrc(), lt.getSrcPort(), + lt.getDst(), lt.getDstPort(), + getLinkType(lt, newInfo), + updateOperation)); + } + } finally { + lock.writeLock().unlock(); + } + + return linkChanged; + } + + /** + * Delete a link + * + * @param link + * - link to be deleted. + * @param reason + * - reason why the link is deleted. + */ + protected void deleteLink(Link link, String reason) { + if (link == null) + return; + List<Link> linkList = new ArrayList<Link>(); + linkList.add(link); + deleteLinks(linkList, reason); + } + /** + * Removes links from memory and storage. + * + * @param links + * The List of @LinkTuple to delete. + */ + protected void deleteLinks(List<Link> links, String reason) { + deleteLinks(links, reason, null); + } + + /** + * Removes links from memory and storage. + * + * @param links + * The List of @LinkTuple to delete. + */ + @LogMessageDoc(message="Inter-switch link removed:", + explanation="A previously detected link between two openflow switches no longer exists, " + + "use show link to find current status") + protected void deleteLinks(List<Link> links, String reason, + List<LDUpdate> updateList) { + + NodePortTuple srcNpt, dstNpt; + List<LDUpdate> linkUpdateList = new ArrayList<LDUpdate>(); + lock.writeLock().lock(); + try { + for (Link lt : links) { + srcNpt = new NodePortTuple(lt.getSrc(), lt.getSrcPort()); + dstNpt = new NodePortTuple(lt.getDst(), lt.getDstPort()); + + if (switchLinks.containsKey(lt.getSrc())) { + switchLinks.get(lt.getSrc()).remove(lt); + if (switchLinks.get(lt.getSrc()).isEmpty()) + this.switchLinks.remove(lt.getSrc()); + } + if (this.switchLinks.containsKey(lt.getDst())) { + switchLinks.get(lt.getDst()).remove(lt); + if (this.switchLinks.get(lt.getDst()).isEmpty()) + this.switchLinks.remove(lt.getDst()); + } + + if (this.portLinks.get(srcNpt) != null) { + this.portLinks.get(srcNpt).remove(lt); + if (this.portLinks.get(srcNpt).isEmpty()) + this.portLinks.remove(srcNpt); + } + if (this.portLinks.get(dstNpt) != null) { + this.portLinks.get(dstNpt).remove(lt); + if (this.portLinks.get(dstNpt).isEmpty()) + this.portLinks.remove(dstNpt); + } + + LinkInfo info = this.links.remove(lt); + LinkType linkType = getLinkType(lt, info); + linkUpdateList.add(new LDUpdate(lt.getSrc(), + lt.getSrcPort(), + lt.getDst(), + lt.getDstPort(), + linkType, + UpdateOperation.LINK_REMOVED)); + + // FIXME: link type shows up as invalid now -- thus not checking if + // link type is a direct link + eventCategory.newEventWithFlush(new DirectLinkEvent(lt.getSrc(), + lt.getSrcPort(), lt.getDst(), lt.getDstPort(), + "link-deleted::" + reason)); + // remove link from storage. + removeLinkFromStorage(lt); + + // TODO Whenever link is removed, it has to checked if + // the switchports must be added to quarantine. + + if (linkType == ILinkDiscovery.LinkType.DIRECT_LINK) { + log.info("Inter-switch link removed: {}", lt); + notifier.postNotification("Inter-switch link removed: " + + lt.toString()); + } else if (log.isTraceEnabled()) { + log.trace("Deleted link {}", lt); + } + } + } finally { + if (updateList != null) linkUpdateList.addAll(updateList); + updates.addAll(linkUpdateList); + lock.writeLock().unlock(); + } + } + + /** + * Delete links incident on a given switch port. + * + * @param npt + * @param reason + */ + protected void deleteLinksOnPort(NodePortTuple npt, String reason) { + List<Link> eraseList = new ArrayList<Link>(); + if (this.portLinks.containsKey(npt)) { + if (log.isTraceEnabled()) { + log.trace("handlePortStatus: Switch {} port #{} " + + "removing links {}", + new Object[] { + npt.getNodeId().toString(), + npt.getPortId(), + this.portLinks.get(npt) }); + } + eraseList.addAll(this.portLinks.get(npt)); + deleteLinks(eraseList, reason); + } + } + + /** + * Iterates through the list of links and deletes if the last discovery + * message reception time exceeds timeout values. + */ + protected void timeoutLinks() { + List<Link> eraseList = new ArrayList<Link>(); + Long curTime = System.currentTimeMillis(); + boolean linkChanged = false; + + // reentrant required here because deleteLink also write locks + lock.writeLock().lock(); + try { + Iterator<Entry<Link, LinkInfo>> it = this.links.entrySet() + .iterator(); + while (it.hasNext()) { + Entry<Link, LinkInfo> entry = it.next(); + Link lt = entry.getKey(); + LinkInfo info = entry.getValue(); + + // Timeout the unicast and multicast LLDP valid times + // independently. + if ((info.getUnicastValidTime() != null) + && (info.getUnicastValidTime().getTime() + + (this.LINK_TIMEOUT * 1000) < curTime)) { + info.setUnicastValidTime(null); + linkChanged = true; + } + if ((info.getMulticastValidTime() != null) + && (info.getMulticastValidTime().getTime() + + (this.LINK_TIMEOUT * 1000) < curTime)) { + info.setMulticastValidTime(null); + linkChanged = true; + } + // Add to the erase list only if the unicast + // time is null. + if (info.getUnicastValidTime() == null + && info.getMulticastValidTime() == null) { + eraseList.add(entry.getKey()); + } else if (linkChanged) { + updates.add(new LDUpdate(lt.getSrc(), lt.getSrcPort(), + lt.getDst(), lt.getDstPort(), + getLinkType(lt, info), + UpdateOperation.LINK_UPDATED)); + } + } + + // if any link was deleted or any link was changed. + if ((eraseList.size() > 0) || linkChanged) { + deleteLinks(eraseList, "LLDP timeout"); + } + } finally { + lock.writeLock().unlock(); + } + } + + //****************** + // Internal Helper Methods + //****************** + @LogMessageDoc(level="WARN", + message="Could not get list of interfaces of local machine to " + + "encode in TLV: {detail-msg}", + explanation="Outgoing LLDP packets encode a unique hash to " + + "identify the local machine. The list of network " + + "interfaces is used as input and the controller failed " + + "to query this list", + recommendation=LogMessageDoc.REPORT_CONTROLLER_BUG) + protected void setControllerTLV() { + // Setting the controllerTLVValue based on current nano time, + // controller's IP address, and the network interface object hash + // the corresponding IP address. + + final int prime = 7867; + + byte[] controllerTLVValue = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }; // 8 + // byte + // value. + ByteBuffer bb = ByteBuffer.allocate(10); + + long result = System.nanoTime(); + try{ + // Use some data specific to the machine this controller is + // running on. In this case: the list of network interfaces + Enumeration<NetworkInterface> ifaces = + NetworkInterface.getNetworkInterfaces(); + if (ifaces != null) { + result = result * prime + ifaces.hashCode(); + } + } catch (SocketException e) { + log.warn("Could not get list of interfaces of local machine to " + + "encode in TLV: {}", e.toString()); + } + // set the first 4 bits to 0. + result = result & (0x0fffffffffffffffL); + + bb.putLong(result); + + bb.rewind(); + bb.get(controllerTLVValue, 0, 8); + + this.controllerTLV = new LLDPTLV().setType((byte) 0x0c) + .setLength((short) controllerTLVValue.length) + .setValue(controllerTLVValue); + } + + //****************** + // IOFSwitchListener + //****************** + private void handlePortDown(DatapathId switchId, OFPort portNumber) { + NodePortTuple npt = new NodePortTuple(switchId, portNumber); + deleteLinksOnPort(npt, "Port Status Changed"); + LDUpdate update = new LDUpdate(switchId, portNumber, + UpdateOperation.PORT_DOWN); + updates.add(update); + } + /** + * We don't react the port changed notifications here. we listen for + * OFPortStatus messages directly. Might consider using this notifier + * instead + */ + @Override + public void switchPortChanged(DatapathId switchId, + OFPortDesc port, + PortChangeType type) { + + switch (type) { + case UP: + processNewPort(switchId, port.getPortNo()); + break; + case DELETE: case DOWN: + handlePortDown(switchId, port.getPortNo()); + break; + case OTHER_UPDATE: case ADD: + // This is something other than port add or delete. + // Topology does not worry about this. + // If for some reason the port features change, which + // we may have to react. + break; + } + } + + @Override + public void switchAdded(DatapathId switchId) { + // no-op + // We don't do anything at switch added, but we do only when the + // switch is activated. + } + + @Override + public void switchRemoved(DatapathId sw) { List<Link> eraseList = new ArrayList<Link>(); lock.writeLock().lock(); try { if (switchLinks.containsKey(sw)) { if (log.isTraceEnabled()) { - log.trace("Handle switchRemoved. Switch {}; removing links {}", - HexString.toHexString(sw), switchLinks.get(sw)); + log.trace("Handle switchRemoved. Switch {}; removing links {}", sw.toString(), switchLinks.get(sw)); } List<LDUpdate> updateList = new ArrayList<LDUpdate>(); - updateList.add(new LDUpdate(sw, null, - UpdateOperation.SWITCH_REMOVED)); + updateList.add(new LDUpdate(sw, SwitchType.BASIC_SWITCH, UpdateOperation.SWITCH_REMOVED)); // add all tuples with an endpoint on this switch to erase list eraseList.addAll(switchLinks.get(sw)); @@ -1669,8 +1637,7 @@ public class LinkDiscoveryManager implements IOFMessageListener, deleteLinks(eraseList, "Switch Removed", updateList); } else { // Switch does not have any links. - updates.add(new LDUpdate(sw, null, - UpdateOperation.SWITCH_REMOVED)); + updates.add(new LDUpdate(sw, SwitchType.BASIC_SWITCH, UpdateOperation.SWITCH_REMOVED)); } } finally { lock.writeLock().unlock(); @@ -1678,66 +1645,67 @@ public class LinkDiscoveryManager implements IOFMessageListener, } - @Override - public void switchActivated(long switchId) { - IOFSwitch sw = floodlightProvider.getSwitch(switchId); - if (sw.getEnabledPortNumbers() != null) { - for (Short p : sw.getEnabledPortNumbers()) { - processNewPort(sw.getId(), p); - } - } - LDUpdate update = new LDUpdate(sw.getId(), null, - UpdateOperation.SWITCH_UPDATED); - updates.add(update); - } - - @Override - public void switchChanged(long switchId) { - // no-op - } - - //********************* - // Storage Listener - //********************* - /** - * Sets the IStorageSource to use for Topology - * - * @param storageSource - * the storage source to use - */ - public void setStorageSource(IStorageSourceService storageSource) { - this.storageSource = storageSource; - } - - /** - * Gets the storage source for this ITopology - * - * @return The IStorageSource ITopology is writing to - */ - public IStorageSourceService getStorageSource() { - return storageSource; - } - - @Override - public void rowsModified(String tableName, Set<Object> rowKeys) { + @Override + public void switchActivated(DatapathId switchId) { + IOFSwitch sw = switchService.getSwitch(switchId); + if (sw.getEnabledPortNumbers() != null) { + for (OFPort p : sw.getEnabledPortNumbers()) { + processNewPort(sw.getId(), p); + } + } + LDUpdate update = new LDUpdate(sw.getId(), SwitchType.BASIC_SWITCH, UpdateOperation.SWITCH_UPDATED); + updates.add(update); + } + + @Override + public void switchChanged(DatapathId switchId) { + // no-op + } + + + //********************* + // Storage Listener + //********************* + /** + * Sets the IStorageSource to use for Topology + * + * @param storageSource + * the storage source to use + */ + public void setStorageSource(IStorageSourceService storageSourceService) { + this.storageSourceService = storageSourceService; + } + + /** + * Gets the storage source for this ITopology + * + * @return The IStorageSource ITopology is writing to + */ + public IStorageSourceService getStorageSource() { + return storageSourceService; + } + + @Override + public void rowsModified(String tableName, Set<Object> rowKeys) { if (tableName.equals(TOPOLOGY_TABLE_NAME)) { readTopologyConfigFromStorage(); return; } + /* TODO @Ryan can we nuke all this? core switch isn't used all that often (I don't think) and not at all anymore ArrayList<IOFSwitch> updated_switches = new ArrayList<IOFSwitch>(); for (Object key : rowKeys) { - Long swId = new Long(HexString.toLong((String) key)); - IOFSwitch sw = floodlightProvider.getSwitch(swId); + DatapathId swId = DatapathId.of((String) key); + IOFSwitch sw = switchService.getSwitch(swId); if (sw != null) { - boolean curr_status = sw.hasAttribute(IOFSwitch.SWITCH_IS_CORE_SWITCH); + boolean curr_status = sw.hasAttribute(IOFSwitch.SWITCH_IS_CORE_SWITCH); boolean new_status = false; IResultSet resultSet = null; try { - resultSet = storageSource.getRow(tableName, key); + resultSet = storageSourceService.getRow(tableName, key); for (Iterator<IResultSet> it = resultSet.iterator(); it.hasNext();) { // In case of multiple rows, use the status in last row? Map<String, Object> row = it.next().getRow(); @@ -1746,7 +1714,9 @@ public class LinkDiscoveryManager implements IOFMessageListener, } } } finally { - if (resultSet != null) resultSet.close(); + if (resultSet != null) { + resultSet.close(); + } } if (curr_status != new_status) { @@ -1754,9 +1724,7 @@ public class LinkDiscoveryManager implements IOFMessageListener, } } else { if (log.isTraceEnabled()) { - log.trace("Update for switch which has no entry in switch " - + "list (dpid={}), a delete action.", - key); + log.trace("Update for switch which has no entry in switch " + "list (dpid={}), a delete action.", key); } } } @@ -1766,489 +1734,482 @@ public class LinkDiscoveryManager implements IOFMessageListener, if (sw.hasAttribute(IOFSwitch.SWITCH_IS_CORE_SWITCH)) { sw.removeAttribute(IOFSwitch.SWITCH_IS_CORE_SWITCH); if (log.isTraceEnabled()) { - log.trace("SWITCH_IS_CORE_SWITCH set to False for {}", - sw); - } + log.trace("SWITCH_IS_CORE_SWITCH set to False for {}", sw); + } updates.add(new LDUpdate(sw.getId(), SwitchType.BASIC_SWITCH, UpdateOperation.SWITCH_UPDATED)); } else { - sw.setAttribute(IOFSwitch.SWITCH_IS_CORE_SWITCH, - new Boolean(true)); + sw.setAttribute(IOFSwitch.SWITCH_IS_CORE_SWITCH, new Boolean(true)); if (log.isTraceEnabled()) { log.trace("SWITCH_IS_CORE_SWITCH set to True for {}", sw); } - updates.add(new LDUpdate(sw.getId(), SwitchType.CORE_SWITCH, - UpdateOperation.SWITCH_UPDATED)); - } - } - } - - @Override - public void rowsDeleted(String tableName, Set<Object> rowKeys) { - // Ignore delete events, the switch delete will do the - // right thing on it's own. - readTopologyConfigFromStorage(); - } - - - //****************************** - // Internal methods - Config Related - //****************************** - - protected void readTopologyConfigFromStorage() { - IResultSet topologyResult = storageSource.executeQuery(TOPOLOGY_TABLE_NAME, - null, null, - null); - - if (topologyResult.next()) { - boolean apf = topologyResult.getBoolean(TOPOLOGY_AUTOPORTFAST); - autoPortFastFeature = apf; - } else { - this.autoPortFastFeature = AUTOPORTFAST_DEFAULT; - } - - if (autoPortFastFeature) - log.info("Setting autoportfast feature to ON"); - else - log.info("Setting autoportfast feature to OFF"); - } - - /** - * Deletes all links from storage - */ - void clearAllLinks() { - storageSource.deleteRowsAsync(LINK_TABLE_NAME, null); - } - - /** - * Writes a LinkTuple and corresponding LinkInfo to storage - * - * @param lt - * The LinkTuple to write - * @param linkInfo - * The LinkInfo to write - */ - protected void writeLinkToStorage(Link lt, LinkInfo linkInfo) { - LinkType type = getLinkType(lt, linkInfo); - - // Write only direct links. Do not write links to external - // L2 network. - // if (type != LinkType.DIRECT_LINK && type != LinkType.TUNNEL) { - // return; - // } - - Map<String, Object> rowValues = new HashMap<String, Object>(); - String id = getLinkId(lt); - rowValues.put(LINK_ID, id); - rowValues.put(LINK_VALID_TIME, linkInfo.getUnicastValidTime()); - String srcDpid = HexString.toHexString(lt.getSrc()); - rowValues.put(LINK_SRC_SWITCH, srcDpid); - rowValues.put(LINK_SRC_PORT, lt.getSrcPort()); - - if (type == LinkType.DIRECT_LINK) - rowValues.put(LINK_TYPE, "internal"); - else if (type == LinkType.MULTIHOP_LINK) - rowValues.put(LINK_TYPE, "external"); - else if (type == LinkType.TUNNEL) - rowValues.put(LINK_TYPE, "tunnel"); - else - rowValues.put(LINK_TYPE, "invalid"); - - String dstDpid = HexString.toHexString(lt.getDst()); - rowValues.put(LINK_DST_SWITCH, dstDpid); - rowValues.put(LINK_DST_PORT, lt.getDstPort()); - - storageSource.updateRowAsync(LINK_TABLE_NAME, rowValues); - } - - /** - * Removes a link from storage using an asynchronous call. - * - * @param lt - * The LinkTuple to delete. - */ - protected void removeLinkFromStorage(Link lt) { - String id = getLinkId(lt); - storageSource.deleteRowAsync(LINK_TABLE_NAME, id); - } - - public Long readLinkValidTime(Link lt) { - // FIXME: We're not currently using this right now, but if we start - // to use this again, we probably shouldn't use it in its current - // form, because it's doing synchronous storage calls. Depending - // on the context this may still be OK, but if it's being called - // on the packet in processing thread it should be reworked to - // use asynchronous storage calls. - Long validTime = null; - IResultSet resultSet = null; - try { - String[] columns = { LINK_VALID_TIME }; - String id = getLinkId(lt); - resultSet = storageSource.executeQuery(LINK_TABLE_NAME, - columns, - new OperatorPredicate( - LINK_ID, - OperatorPredicate.Operator.EQ, - id), - null); - if (resultSet.next()) - validTime = resultSet.getLong(LINK_VALID_TIME); - } finally { - if (resultSet != null) resultSet.close(); - } - return validTime; - } - - /** - * Gets the storage key for a LinkTuple - * - * @param lt - * The LinkTuple to get - * @return The storage key as a String - */ - private String getLinkId(Link lt) { - return HexString.toHexString(lt.getSrc()) + "-" + lt.getSrcPort() - + "-" + HexString.toHexString(lt.getDst()) + "-" - + lt.getDstPort(); - } - - //*************** - // IFloodlightModule - //*************** - - @Override - public Collection<Class<? extends IFloodlightService>> getModuleServices() { - Collection<Class<? extends IFloodlightService>> l = - new ArrayList<Class<? extends IFloodlightService>>(); - l.add(ILinkDiscoveryService.class); - // l.add(ITopologyService.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(ILinkDiscoveryService.class, this); - return m; - } - - @Override - public Collection<Class<? extends IFloodlightService>> getModuleDependencies() { - Collection<Class<? extends IFloodlightService>> l = - new ArrayList<Class<? extends IFloodlightService>>(); - l.add(IFloodlightProviderService.class); - l.add(IStorageSourceService.class); - l.add(IThreadPoolService.class); - l.add(IRestApiService.class); - return l; - } - - @Override - public void init(FloodlightModuleContext context) - throws FloodlightModuleException { - floodlightProvider = context.getServiceImpl(IFloodlightProviderService.class); - storageSource = context.getServiceImpl(IStorageSourceService.class); - threadPool = context.getServiceImpl(IThreadPoolService.class); - restApi = context.getServiceImpl(IRestApiService.class); - debugCounters = context.getServiceImpl(IDebugCounterService.class); - debugEvents = context.getServiceImpl(IDebugEventService.class); - - // read our config options - Map<String, String> configOptions = context.getConfigParams(this); - try { - String histSize = configOptions.get("eventhistorysize"); - if (histSize != null) { - EVENT_HISTORY_SIZE = Short.parseShort(histSize); - } - } catch (NumberFormatException e) { - log.warn("Error event history size, using default of {} seconds", EVENT_HISTORY_SIZE); - } - log.debug("Event history size set to {}", EVENT_HISTORY_SIZE); - - // Set the autoportfast feature to false. - this.autoPortFastFeature = AUTOPORTFAST_DEFAULT; - - // We create this here because there is no ordering guarantee - this.linkDiscoveryAware = new ArrayList<ILinkDiscoveryListener>(); - this.lock = new ReentrantReadWriteLock(); - this.updates = new LinkedBlockingQueue<LDUpdate>(); - this.links = new HashMap<Link, LinkInfo>(); - this.portLinks = new HashMap<NodePortTuple, Set<Link>>(); - this.suppressLinkDiscovery = Collections.synchronizedSet(new HashSet<NodePortTuple>()); - this.switchLinks = new HashMap<Long, Set<Link>>(); - this.quarantineQueue = new LinkedBlockingQueue<NodePortTuple>(); - this.maintenanceQueue = new LinkedBlockingQueue<NodePortTuple>(); - - this.ignoreMACSet = Collections.newSetFromMap( - new ConcurrentHashMap<MACRange,Boolean>()); - this.haListener = new HAListenerDelegate(); - registerLinkDiscoveryDebugCounters(); - registerLinkDiscoveryDebugEvents(); - } - - @Override - @LogMessageDocs({ - @LogMessageDoc(level = "ERROR", - message = "No storage source found.", - explanation = "Storage source was not initialized; cannot initialize " - + "link discovery.", - recommendation = LogMessageDoc.REPORT_CONTROLLER_BUG), - @LogMessageDoc(level = "ERROR", - message = "Error in installing listener for " - + "switch config table {table}", - explanation = "Failed to install storage notification for the " - + "switch config table", - recommendation = LogMessageDoc.REPORT_CONTROLLER_BUG), - @LogMessageDoc(level = "ERROR", - message = "No storage source found.", - explanation = "Storage source was not initialized; cannot initialize " - + "link discovery.", - recommendation = LogMessageDoc.REPORT_CONTROLLER_BUG), - @LogMessageDoc(level = "ERROR", - message = "Exception in LLDP send timer.", - explanation = "An unknown error occured while sending LLDP " - + "messages to switches.", - recommendation = LogMessageDoc.CHECK_SWITCH) }) - public void startUp(FloodlightModuleContext context) throws FloodlightModuleException { - - // Initialize role to floodlight provider role. - this.role = floodlightProvider.getRole(); - - // Create our storage tables - if (storageSource == null) { - log.error("No storage source found."); - return; - } - - storageSource.createTable(TOPOLOGY_TABLE_NAME, null); - storageSource.setTablePrimaryKeyName(TOPOLOGY_TABLE_NAME, - TOPOLOGY_ID); - readTopologyConfigFromStorage(); - - storageSource.createTable(LINK_TABLE_NAME, null); - storageSource.setTablePrimaryKeyName(LINK_TABLE_NAME, LINK_ID); - storageSource.deleteMatchingRows(LINK_TABLE_NAME, null); - // Register for storage updates for the switch table - try { - storageSource.addListener(SWITCH_CONFIG_TABLE_NAME, this); - storageSource.addListener(TOPOLOGY_TABLE_NAME, this); - } catch (StorageException ex) { - log.error("Error in installing listener for " - + "switch table {}", SWITCH_CONFIG_TABLE_NAME); - } - - ScheduledExecutorService ses = threadPool.getScheduledExecutor(); - - // To be started by the first switch connection - discoveryTask = new SingletonTask(ses, new Runnable() { - @Override - public void run() { - try { - discoverLinks(); - } catch (StorageException e) { - log.error("Storage exception in LLDP send timer; " - + "terminating process", e); - floodlightProvider.terminate(); - } catch (Exception e) { - log.error("Exception in LLDP send timer.", e); - } finally { - if (!shuttingDown) { - // null role implies HA mode is not enabled. - if (role == null || role == Role.MASTER) { - log.trace("Rescheduling discovery task as role = {}", - role); - discoveryTask.reschedule(DISCOVERY_TASK_INTERVAL, - TimeUnit.SECONDS); - } else { - log.trace("Stopped LLDP rescheduling due to role = {}.", - role); - } - } - } - } - }); - - // null role implies HA mode is not enabled. - if (role == null || role == Role.MASTER) { - log.trace("Setup: Rescheduling discovery task. role = {}", role); - discoveryTask.reschedule(DISCOVERY_TASK_INTERVAL, - TimeUnit.SECONDS); - } else { - log.trace("Setup: Not scheduling LLDP as role = {}.", role); - } - - // Setup the BDDP task. It is invoked whenever switch port tuples - // are added to the quarantine list. - bddpTask = new SingletonTask(ses, new QuarantineWorker()); - bddpTask.reschedule(BDDP_TASK_INTERVAL, TimeUnit.MILLISECONDS); - - updatesThread = new Thread(new Runnable() { - @Override - public void run() { - while (true) { - try { - doUpdatesThread(); - } catch (InterruptedException e) { - return; - } - } - } - }, "Topology Updates"); - updatesThread.start(); - - // Register for the OpenFlow messages we want to receive - floodlightProvider.addOFMessageListener(OFType.PACKET_IN, this); - floodlightProvider.addOFMessageListener(OFType.PORT_STATUS, this); - // Register for switch updates - floodlightProvider.addOFSwitchListener(this); - floodlightProvider.addHAListener(this.haListener); - floodlightProvider.addInfoProvider("summary", this); - if (restApi != null) - restApi.addRestletRoutable(new LinkDiscoveryWebRoutable()); - setControllerTLV(); - } - - // **************************************************** - // Link Discovery DebugCounters and DebugEvents - // **************************************************** - - private void registerLinkDiscoveryDebugCounters() 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); - ctrLldpEol = debugCounters.registerCounter(PACKAGE, "lldp-eol", - "End of Life for LLDP packets", CounterType.COUNT_ON_DEMAND); - ctrLinkLocalDrops = debugCounters.registerCounter(PACKAGE, "linklocal-drops", - "All link local packets dropped by this module", - CounterType.ALWAYS_COUNT); - ctrIgnoreSrcMacDrops = debugCounters.registerCounter(PACKAGE, "ignore-srcmac-drops", - "All packets whose srcmac is configured to be dropped by this module", - CounterType.ALWAYS_COUNT); - ctrQuarantineDrops = debugCounters.registerCounter(PACKAGE, "quarantine-drops", - "All packets arriving on quarantined ports dropped by this module", - CounterType.ALWAYS_COUNT, IDebugCounterService.CTR_MDATA_WARN); - } catch (CounterException e) { - throw new FloodlightModuleException(e.getMessage()); - } - } - - private void registerLinkDiscoveryDebugEvents() throws FloodlightModuleException { - if (debugEvents == null) { - log.error("Debug Event Service not found."); - debugEvents = new NullDebugEvent(); - } - - try { - evDirectLink = debugEvents.registerEvent( - PACKAGE, "linkevent", - "Direct OpenFlow links discovered or timed-out", - EventType.ALWAYS_LOG, DirectLinkEvent.class, 100); - } catch (MaxEventsRegistered e) { - throw new FloodlightModuleException("max events registered", e); - } - - } - - public class DirectLinkEvent { - @EventColumn(name = "srcSw", description = EventFieldType.DPID) - long srcDpid; - - @EventColumn(name = "srcPort", description = EventFieldType.PRIMITIVE) - short srcPort; - - @EventColumn(name = "dstSw", description = EventFieldType.DPID) - long dstDpid; - - @EventColumn(name = "dstPort", description = EventFieldType.PRIMITIVE) - short dstPort; - - @EventColumn(name = "reason", description = EventFieldType.STRING) - String reason; - - public DirectLinkEvent(long srcDpid, short srcPort, long dstDpid, - short dstPort, String reason) { - this.srcDpid = srcDpid; - this.srcPort = srcPort; - this.dstDpid = dstDpid; - this.dstPort = dstPort; - this.reason = reason; - } - } - - - //********************* - // IInfoProvider - //********************* - - @Override - public Map<String, Object> getInfo(String type) { - if (!"summary".equals(type)) return null; - - Map<String, Object> info = new HashMap<String, Object>(); - - int numDirectLinks = 0; - for (Set<Link> links : switchLinks.values()) { - for (Link link : links) { - LinkInfo linkInfo = this.getLinkInfo(link); - if (linkInfo != null && - linkInfo.getLinkType() == LinkType.DIRECT_LINK) { - numDirectLinks++; - } - } - } - info.put("# inter-switch links", numDirectLinks / 2); - info.put("# quarantine ports", quarantineQueue.size()); - return info; - } - - //*************** - // IHAListener - //*************** - - private class HAListenerDelegate implements IHAListener { - @Override - public void transitionToMaster() { - if (log.isTraceEnabled()) { - log.trace("Sending LLDPs " - + "to HA change from SLAVE->MASTER"); - } - LinkDiscoveryManager.this.role = Role.MASTER; - clearAllLinks(); - readTopologyConfigFromStorage(); - log.debug("Role Change to Master: Rescheduling discovery task."); - discoveryTask.reschedule(1, TimeUnit.MICROSECONDS); - } - - @Override - public void controllerNodeIPsChanged(Map<String, String> curControllerNodeIPs, - Map<String, String> addedControllerNodeIPs, - Map<String, String> removedControllerNodeIPs) { - // ignore - } - - @Override - public String getName() { - return LinkDiscoveryManager.this.getName(); - } - - @Override - public boolean isCallbackOrderingPrereq(HAListenerTypeMarker type, - String name) { - return ("topology".equals(name)); - } - - @Override - public boolean isCallbackOrderingPostreq(HAListenerTypeMarker type, - String name) { - return "tunnelmanager".equals(name); - } - } + updates.add(new LDUpdate(sw.getId(), SwitchType.CORE_SWITCH, UpdateOperation.SWITCH_UPDATED)); + } + }*/ + } + + @Override + public void rowsDeleted(String tableName, Set<Object> rowKeys) { + // Ignore delete events, the switch delete will do the + // right thing on it's own. + readTopologyConfigFromStorage(); + } + + + //****************************** + // Internal methods - Config Related + //****************************** + + protected void readTopologyConfigFromStorage() { + IResultSet topologyResult = storageSourceService.executeQuery(TOPOLOGY_TABLE_NAME, + null, null, + null); + + if (topologyResult.next()) { + boolean apf = topologyResult.getBoolean(TOPOLOGY_AUTOPORTFAST); + autoPortFastFeature = apf; + } else { + this.autoPortFastFeature = AUTOPORTFAST_DEFAULT; + } + + if (autoPortFastFeature) + log.info("Setting autoportfast feature to ON"); + else + log.info("Setting autoportfast feature to OFF"); + } + + /** + * Deletes all links from storage + */ + void clearAllLinks() { + storageSourceService.deleteRowsAsync(LINK_TABLE_NAME, null); + } + + /** + * Writes a LinkTuple and corresponding LinkInfo to storage + * + * @param lt + * The LinkTuple to write + * @param linkInfo + * The LinkInfo to write + */ + protected void writeLinkToStorage(Link lt, LinkInfo linkInfo) { + LinkType type = getLinkType(lt, linkInfo); + + // Write only direct links. Do not write links to external + // L2 network. + // if (type != LinkType.DIRECT_LINK && type != LinkType.TUNNEL) { + // return; + // } + + Map<String, Object> rowValues = new HashMap<String, Object>(); + String id = getLinkId(lt); + rowValues.put(LINK_ID, id); + rowValues.put(LINK_VALID_TIME, linkInfo.getUnicastValidTime()); + String srcDpid = lt.getSrc().toString(); + rowValues.put(LINK_SRC_SWITCH, srcDpid); + rowValues.put(LINK_SRC_PORT, lt.getSrcPort()); + + if (type == LinkType.DIRECT_LINK) + rowValues.put(LINK_TYPE, "internal"); + else if (type == LinkType.MULTIHOP_LINK) + rowValues.put(LINK_TYPE, "external"); + else if (type == LinkType.TUNNEL) + rowValues.put(LINK_TYPE, "tunnel"); + else + rowValues.put(LINK_TYPE, "invalid"); + + String dstDpid = lt.getDst().toString(); + rowValues.put(LINK_DST_SWITCH, dstDpid); + rowValues.put(LINK_DST_PORT, lt.getDstPort()); + + storageSourceService.updateRowAsync(LINK_TABLE_NAME, rowValues); + } + + /** + * Removes a link from storage using an asynchronous call. + * + * @param lt + * The LinkTuple to delete. + */ + protected void removeLinkFromStorage(Link lt) { + String id = getLinkId(lt); + storageSourceService.deleteRowAsync(LINK_TABLE_NAME, id); + } + + public Long readLinkValidTime(Link lt) { + // FIXME: We're not currently using this right now, but if we start + // to use this again, we probably shouldn't use it in its current + // form, because it's doing synchronous storage calls. Depending + // on the context this may still be OK, but if it's being called + // on the packet in processing thread it should be reworked to + // use asynchronous storage calls. + Long validTime = null; + IResultSet resultSet = null; + try { + String[] columns = { LINK_VALID_TIME }; + String id = getLinkId(lt); + resultSet = storageSourceService.executeQuery(LINK_TABLE_NAME, + columns, + new OperatorPredicate( + LINK_ID, + OperatorPredicate.Operator.EQ, + id), + null); + if (resultSet.next()) + validTime = resultSet.getLong(LINK_VALID_TIME); + } finally { + if (resultSet != null) resultSet.close(); + } + return validTime; + } + + /** + * Gets the storage key for a LinkTuple + * + * @param lt + * The LinkTuple to get + * @return The storage key as a String + */ + private String getLinkId(Link lt) { + return lt.getSrc().toString() + "-" + lt.getSrcPort() + + "-" + lt.getDst().toString() + "-" + + lt.getDstPort(); + } + + //*************** + // IFloodlightModule + //*************** + + @Override + public Collection<Class<? extends IFloodlightService>> getModuleServices() { + Collection<Class<? extends IFloodlightService>> l = + new ArrayList<Class<? extends IFloodlightService>>(); + l.add(ILinkDiscoveryService.class); + // l.add(ITopologyService.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(ILinkDiscoveryService.class, this); + return m; + } + + @Override + public Collection<Class<? extends IFloodlightService>> getModuleDependencies() { + Collection<Class<? extends IFloodlightService>> l = + new ArrayList<Class<? extends IFloodlightService>>(); + l.add(IFloodlightProviderService.class); + l.add(IStorageSourceService.class); + l.add(IThreadPoolService.class); + l.add(IRestApiService.class); + l.add(IShutdownService.class); + return l; + } + + @Override + public void init(FloodlightModuleContext context) + throws FloodlightModuleException { + floodlightProviderService = context.getServiceImpl(IFloodlightProviderService.class); + switchService = context.getServiceImpl(IOFSwitchService.class); + storageSourceService = context.getServiceImpl(IStorageSourceService.class); + threadPoolService = context.getServiceImpl(IThreadPoolService.class); + restApiService = context.getServiceImpl(IRestApiService.class); + debugCounterService = context.getServiceImpl(IDebugCounterService.class); + debugEventService = context.getServiceImpl(IDebugEventService.class); + shutdownService = context.getServiceImpl(IShutdownService.class); + + // read our config options + Map<String, String> configOptions = context.getConfigParams(this); + try { + String histSize = configOptions.get("eventhistorysize"); + if (histSize != null) { + EVENT_HISTORY_SIZE = Short.parseShort(histSize); + } + } catch (NumberFormatException e) { + log.warn("Error event history size, using default of {} seconds", EVENT_HISTORY_SIZE); + } + log.debug("Event history size set to {}", EVENT_HISTORY_SIZE); + + // Set the autoportfast feature to false. + this.autoPortFastFeature = AUTOPORTFAST_DEFAULT; + + // We create this here because there is no ordering guarantee + this.linkDiscoveryAware = new ArrayList<ILinkDiscoveryListener>(); + this.lock = new ReentrantReadWriteLock(); + this.updates = new LinkedBlockingQueue<LDUpdate>(); + this.links = new HashMap<Link, LinkInfo>(); + this.portLinks = new HashMap<NodePortTuple, Set<Link>>(); + this.suppressLinkDiscovery = Collections.synchronizedSet(new HashSet<NodePortTuple>()); + this.switchLinks = new HashMap<DatapathId, Set<Link>>(); + this.quarantineQueue = new LinkedBlockingQueue<NodePortTuple>(); + this.maintenanceQueue = new LinkedBlockingQueue<NodePortTuple>(); + + this.ignoreMACSet = Collections.newSetFromMap( + new ConcurrentHashMap<MACRange,Boolean>()); + this.haListener = new HAListenerDelegate(); + registerLinkDiscoveryDebugCounters(); + registerLinkDiscoveryDebugEvents(); + } + + @Override + @LogMessageDocs({ + @LogMessageDoc(level = "ERROR", + message = "No storage source found.", + explanation = "Storage source was not initialized; cannot initialize " + + "link discovery.", + recommendation = LogMessageDoc.REPORT_CONTROLLER_BUG), + @LogMessageDoc(level = "ERROR", + message = "Error in installing listener for " + + "switch config table {table}", + explanation = "Failed to install storage notification for the " + + "switch config table", + recommendation = LogMessageDoc.REPORT_CONTROLLER_BUG), + @LogMessageDoc(level = "ERROR", + message = "No storage source found.", + explanation = "Storage source was not initialized; cannot initialize " + + "link discovery.", + recommendation = LogMessageDoc.REPORT_CONTROLLER_BUG), + @LogMessageDoc(level = "ERROR", + message = "Exception in LLDP send timer.", + explanation = "An unknown error occured while sending LLDP " + + "messages to switches.", + recommendation = LogMessageDoc.CHECK_SWITCH) }) + public void startUp(FloodlightModuleContext context) throws FloodlightModuleException { + + // Initialize role to floodlight provider role. + this.role = floodlightProviderService.getRole(); + + // Create our storage tables + if (storageSourceService == null) { + log.error("No storage source found."); + return; + } + + storageSourceService.createTable(TOPOLOGY_TABLE_NAME, null); + storageSourceService.setTablePrimaryKeyName(TOPOLOGY_TABLE_NAME, + TOPOLOGY_ID); + readTopologyConfigFromStorage(); + + storageSourceService.createTable(LINK_TABLE_NAME, null); + storageSourceService.setTablePrimaryKeyName(LINK_TABLE_NAME, LINK_ID); + storageSourceService.deleteMatchingRows(LINK_TABLE_NAME, null); + // Register for storage updates for the switch table + try { + storageSourceService.addListener(SWITCH_CONFIG_TABLE_NAME, this); + storageSourceService.addListener(TOPOLOGY_TABLE_NAME, this); + } catch (StorageException ex) { + log.error("Error in installing listener for " + + "switch table {}", SWITCH_CONFIG_TABLE_NAME); + } + + ScheduledExecutorService ses = threadPoolService.getScheduledExecutor(); + + // To be started by the first switch connection + discoveryTask = new SingletonTask(ses, new Runnable() { + @Override + public void run() { + try { + discoverLinks(); + } catch (StorageException e) { + shutdownService.terminate("Storage exception in LLDP send timer. Terminating process " + e, 0); // TODO @Ryan as there "standard" shutdown codes Floodlight uses? + } catch (Exception e) { + log.error("Exception in LLDP send timer.", e); + } finally { + if (!shuttingDown) { + // null role implies HA mode is not enabled. + if (role == null || role == HARole.ACTIVE) { + log.trace("Rescheduling discovery task as role = {}", + role); + discoveryTask.reschedule(DISCOVERY_TASK_INTERVAL, + TimeUnit.SECONDS); + } else { + log.trace("Stopped LLDP rescheduling due to role = {}.", + role); + } + } + } + } + }); + + // null role implies HA mode is not enabled. + if (role == null || role == HARole.ACTIVE) { + log.trace("Setup: Rescheduling discovery task. role = {}", role); + discoveryTask.reschedule(DISCOVERY_TASK_INTERVAL, + TimeUnit.SECONDS); + } else { + log.trace("Setup: Not scheduling LLDP as role = {}.", role); + } + + // Setup the BDDP task. It is invoked whenever switch port tuples + // are added to the quarantine list. + bddpTask = new SingletonTask(ses, new QuarantineWorker()); + bddpTask.reschedule(BDDP_TASK_INTERVAL, TimeUnit.MILLISECONDS); + + updatesThread = new Thread(new Runnable() { + @Override + public void run() { + while (true) { + try { + doUpdatesThread(); + } catch (InterruptedException e) { + return; + } + } + } + }, "Topology Updates"); + updatesThread.start(); + + // Register for the OpenFlow messages we want to receive + floodlightProviderService.addOFMessageListener(OFType.PACKET_IN, this); + floodlightProviderService.addOFMessageListener(OFType.PORT_STATUS, this); + // Register for switch updates + switchService.addOFSwitchListener(this); + floodlightProviderService.addHAListener(this.haListener); + floodlightProviderService.addInfoProvider("summary", this); + if (restApiService != null) + restApiService.addRestletRoutable(new LinkDiscoveryWebRoutable()); + setControllerTLV(); + } + + // **************************************************** + // Link Discovery DebugCounters and DebugEvents + // **************************************************** + + private void registerLinkDiscoveryDebugCounters() throws FloodlightModuleException { + if (debugCounterService == null) { + log.error("Debug Counter Service not found."); + } + debugCounterService.registerModule(PACKAGE); + ctrIncoming = debugCounterService.registerCounter(PACKAGE, "incoming", + "All incoming packets seen by this module"); + ctrLldpEol = debugCounterService.registerCounter(PACKAGE, "lldp-eol", + "End of Life for LLDP packets"); + ctrLinkLocalDrops = debugCounterService.registerCounter(PACKAGE, "linklocal-drops", + "All link local packets dropped by this module"); + ctrIgnoreSrcMacDrops = debugCounterService.registerCounter(PACKAGE, "ignore-srcmac-drops", + "All packets whose srcmac is configured to be dropped by this module"); + ctrQuarantineDrops = debugCounterService.registerCounter(PACKAGE, "quarantine-drops", + "All packets arriving on quarantined ports dropped by this module", IDebugCounterService.MetaData.WARN); + } + + private void registerLinkDiscoveryDebugEvents() throws FloodlightModuleException { + if (debugEventService == null) { + log.error("Debug Event Service not found."); + } + + eventCategory = debugEventService.buildEvent(DirectLinkEvent.class) + .setModuleName(PACKAGE) + .setEventName("linkevent") + .setEventDescription("Direct OpenFlow links discovered or timed-out") + .setEventType(EventType.ALWAYS_LOG) + .setBufferCapacity(100) + .register(); + } + + public class DirectLinkEvent { + @EventColumn(name = "srcSw", description = EventFieldType.DPID) + DatapathId srcDpid; + + @EventColumn(name = "srcPort", description = EventFieldType.PRIMITIVE) + OFPort srcPort; + + @EventColumn(name = "dstSw", description = EventFieldType.DPID) + DatapathId dstDpid; + + @EventColumn(name = "dstPort", description = EventFieldType.PRIMITIVE) + OFPort dstPort; + + @EventColumn(name = "reason", description = EventFieldType.STRING) + String reason; + + public DirectLinkEvent(DatapathId srcDpid, OFPort srcPort, DatapathId dstDpid, + OFPort dstPort, String reason) { + this.srcDpid = srcDpid; + this.srcPort = srcPort; + this.dstDpid = dstDpid; + this.dstPort = dstPort; + this.reason = reason; + } + } + + + //********************* + // IInfoProvider + //********************* + + @Override + public Map<String, Object> getInfo(String type) { + if (!"summary".equals(type)) return null; + + Map<String, Object> info = new HashMap<String, Object>(); + + int numDirectLinks = 0; + for (Set<Link> links : switchLinks.values()) { + for (Link link : links) { + LinkInfo linkInfo = this.getLinkInfo(link); + if (linkInfo != null && + linkInfo.getLinkType() == LinkType.DIRECT_LINK) { + numDirectLinks++; + } + } + } + info.put("# inter-switch links", numDirectLinks / 2); + info.put("# quarantine ports", quarantineQueue.size()); + return info; + } + + //*************** + // IHAListener + //*************** + + private class HAListenerDelegate implements IHAListener { + @Override + public void transitionToActive() { + if (log.isTraceEnabled()) { + log.trace("Sending LLDPs " + + "to HA change from STANDBY->MASTER"); + } + LinkDiscoveryManager.this.role = HARole.ACTIVE; + clearAllLinks(); + readTopologyConfigFromStorage(); + log.debug("Role Change to Master: Rescheduling discovery task."); + discoveryTask.reschedule(1, TimeUnit.MICROSECONDS); + } + + @Override + public void controllerNodeIPsChanged(Map<String, String> curControllerNodeIPs, + Map<String, String> addedControllerNodeIPs, + Map<String, String> removedControllerNodeIPs) { + // ignore + } + + @Override + public String getName() { + return LinkDiscoveryManager.this.getName(); + } + + @Override + public boolean isCallbackOrderingPrereq(HAListenerTypeMarker type, + String name) { + return ("topology".equals(name)); + } + + @Override + public boolean isCallbackOrderingPostreq(HAListenerTypeMarker type, + String name) { + return "tunnelmanager".equals(name); + } + + @Override + public void transitionToStandby() { + //no-op + } + } } diff --git a/src/main/java/net/floodlightcontroller/linkdiscovery/web/ExternalLinksResource.java b/src/main/java/net/floodlightcontroller/linkdiscovery/web/ExternalLinksResource.java index c2aa9d4e47dbf8a7d52020380466c50c62bd9660..c3dea4918245d2f30002315a229288202a388373 100644 --- a/src/main/java/net/floodlightcontroller/linkdiscovery/web/ExternalLinksResource.java +++ b/src/main/java/net/floodlightcontroller/linkdiscovery/web/ExternalLinksResource.java @@ -27,6 +27,8 @@ import net.floodlightcontroller.linkdiscovery.ILinkDiscoveryService; import net.floodlightcontroller.linkdiscovery.LinkInfo; import net.floodlightcontroller.routing.Link; +import org.projectfloodlight.openflow.types.DatapathId; +import org.projectfloodlight.openflow.types.OFPort; import org.restlet.resource.Get; import org.restlet.resource.ServerResource; @@ -47,10 +49,10 @@ public class ExternalLinksResource extends ServerResource { if (type == LinkType.MULTIHOP_LINK) { LinkWithType lwt; - long src = link.getSrc(); - long dst = link.getDst(); - short srcPort = link.getSrcPort(); - short dstPort = link.getDstPort(); + DatapathId src = link.getSrc(); + DatapathId dst = link.getDst(); + OFPort srcPort = link.getSrcPort(); + OFPort dstPort = link.getDstPort(); Link otherLink = new Link(dst, dstPort, src, srcPort); LinkInfo otherInfo = links.get(otherLink); LinkType otherType = null; @@ -59,7 +61,8 @@ public class ExternalLinksResource extends ServerResource { if (otherType == LinkType.MULTIHOP_LINK) { // This is a bi-direcitonal link. // It is sufficient to add only one side of it. - if ((src < dst) || (src == dst && srcPort < dstPort)) { + if ((src.getLong() < dst.getLong()) || (src.getLong() == dst.getLong() + && srcPort.getPortNumber() < dstPort.getPortNumber())) { lwt = new LinkWithType(link, type, LinkDirection.BIDIRECTIONAL); diff --git a/src/main/java/net/floodlightcontroller/linkdiscovery/web/LinkWithType.java b/src/main/java/net/floodlightcontroller/linkdiscovery/web/LinkWithType.java index d95235705d016ab44bc22bbacea79302198876a5..81b5094ddb2f5debff234a779bb4e2596781fc1a 100644 --- a/src/main/java/net/floodlightcontroller/linkdiscovery/web/LinkWithType.java +++ b/src/main/java/net/floodlightcontroller/linkdiscovery/web/LinkWithType.java @@ -18,12 +18,14 @@ package net.floodlightcontroller.linkdiscovery.web; import java.io.IOException; +import org.projectfloodlight.openflow.types.DatapathId; +import org.projectfloodlight.openflow.types.OFPort; + import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import org.openflow.util.HexString; import net.floodlightcontroller.linkdiscovery.ILinkDiscovery.LinkDirection; import net.floodlightcontroller.linkdiscovery.ILinkDiscovery.LinkType; @@ -36,10 +38,10 @@ import net.floodlightcontroller.routing.Link; */ @JsonSerialize(using=LinkWithType.class) public class LinkWithType extends JsonSerializer<LinkWithType> { - public long srcSwDpid; - public short srcPort; - public long dstSwDpid; - public short dstPort; + public DatapathId srcSwDpid; + public OFPort srcPort; + public DatapathId dstSwDpid; + public OFPort dstPort; public LinkType type; public LinkDirection direction; @@ -62,10 +64,10 @@ public class LinkWithType extends JsonSerializer<LinkWithType> { throws IOException, JsonProcessingException { // You ****MUST*** use lwt for the fields as it's actually a different object. jgen.writeStartObject(); - jgen.writeStringField("src-switch", HexString.toHexString(lwt.srcSwDpid)); - jgen.writeNumberField("src-port", lwt.srcPort); - jgen.writeStringField("dst-switch", HexString.toHexString(lwt.dstSwDpid)); - jgen.writeNumberField("dst-port", lwt.dstPort); + jgen.writeStringField("src-switch", lwt.srcSwDpid.toString()); + jgen.writeNumberField("src-port", lwt.srcPort.getPortNumber()); + jgen.writeStringField("dst-switch", lwt.dstSwDpid.toString()); + jgen.writeNumberField("dst-port", lwt.dstPort.getPortNumber()); jgen.writeStringField("type", lwt.type.toString()); jgen.writeStringField("direction", lwt.direction.toString()); jgen.writeEndObject(); diff --git a/src/main/java/net/floodlightcontroller/linkdiscovery/web/LinksResource.java b/src/main/java/net/floodlightcontroller/linkdiscovery/web/LinksResource.java index 0e488ddf2e13e97c5834f6380515f7008a240916..c766fb179a228adb4050caf8d878435e0260ef8f 100644 --- a/src/main/java/net/floodlightcontroller/linkdiscovery/web/LinksResource.java +++ b/src/main/java/net/floodlightcontroller/linkdiscovery/web/LinksResource.java @@ -27,6 +27,8 @@ import net.floodlightcontroller.linkdiscovery.ILinkDiscoveryService; import net.floodlightcontroller.linkdiscovery.LinkInfo; import net.floodlightcontroller.routing.Link; +import org.projectfloodlight.openflow.types.DatapathId; +import org.projectfloodlight.openflow.types.OFPort; import org.restlet.resource.Get; import org.restlet.resource.ServerResource; @@ -47,10 +49,10 @@ public class LinksResource extends ServerResource { if (type == LinkType.DIRECT_LINK || type == LinkType.TUNNEL) { LinkWithType lwt; - long src = link.getSrc(); - long dst = link.getDst(); - short srcPort = link.getSrcPort(); - short dstPort = link.getDstPort(); + DatapathId src = link.getSrc(); + DatapathId dst = link.getDst(); + OFPort srcPort = link.getSrcPort(); + OFPort dstPort = link.getDstPort(); Link otherLink = new Link(dst, dstPort, src, srcPort); LinkInfo otherInfo = links.get(otherLink); LinkType otherType = null; @@ -60,7 +62,8 @@ public class LinksResource extends ServerResource { otherType == LinkType.TUNNEL) { // This is a bi-direcitonal link. // It is sufficient to add only one side of it. - if ((src < dst) || (src == dst && srcPort < dstPort)) { + if ((src.getLong() < dst.getLong()) || (src.getLong() == dst.getLong() + && srcPort.getPortNumber() < dstPort.getPortNumber())) { lwt = new LinkWithType(link, type, LinkDirection.BIDIRECTIONAL); diff --git a/src/main/java/net/floodlightcontroller/loadbalancer/LBVip.java b/src/main/java/net/floodlightcontroller/loadbalancer/LBVip.java index 1d0a9bdadd16cca75dccb00cb3251cd4b160e59b..00f2141685a7adbec7da241dc6160b70d91421c3 100644 --- a/src/main/java/net/floodlightcontroller/loadbalancer/LBVip.java +++ b/src/main/java/net/floodlightcontroller/loadbalancer/LBVip.java @@ -18,10 +18,11 @@ package net.floodlightcontroller.loadbalancer; import java.util.ArrayList; +import org.projectfloodlight.openflow.types.MacAddress; + import com.fasterxml.jackson.databind.annotation.JsonSerialize; import net.floodlightcontroller.loadbalancer.LoadBalancer.IPClient; -import net.floodlightcontroller.util.MACAddress; /** * Data structure for Load Balancer based on @@ -46,7 +47,7 @@ public class LBVip { protected short adminState; protected short status; - protected MACAddress proxyMac; + protected MacAddress proxyMac; public static String LB_PROXY_MAC= "12:34:56:78:90:12"; @@ -65,7 +66,7 @@ public class LBVip { this.address = 0; this.status = 0; - this.proxyMac = MACAddress.valueOf(LB_PROXY_MAC); + this.proxyMac = MacAddress.of(LB_PROXY_MAC); } public String pickPool(IPClient client) { diff --git a/src/main/java/net/floodlightcontroller/loadbalancer/LoadBalancer.java b/src/main/java/net/floodlightcontroller/loadbalancer/LoadBalancer.java index dabcef1e556231fdbb3c19d3496d5236e9714494..f14134c5f17b651c9294b031667fdb13f109b9fd 100644 --- a/src/main/java/net/floodlightcontroller/loadbalancer/LoadBalancer.java +++ b/src/main/java/net/floodlightcontroller/loadbalancer/LoadBalancer.java @@ -24,34 +24,25 @@ import java.util.Comparator; import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; -import java.util.LinkedList; import java.util.List; import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.openflow.protocol.OFFlowMod; -import org.openflow.protocol.OFMatch; -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.OFActionDataLayerDestination; -import org.openflow.protocol.action.OFActionDataLayerSource; -import org.openflow.protocol.action.OFActionEnqueue; -import org.openflow.protocol.action.OFActionNetworkLayerDestination; -import org.openflow.protocol.action.OFActionNetworkLayerSource; -import org.openflow.protocol.action.OFActionNetworkTypeOfService; -import org.openflow.protocol.action.OFActionOutput; -import org.openflow.protocol.action.OFActionStripVirtualLan; -import org.openflow.protocol.action.OFActionTransportLayerDestination; -import org.openflow.protocol.action.OFActionTransportLayerSource; -import org.openflow.protocol.action.OFActionVirtualLanIdentifier; -import org.openflow.protocol.action.OFActionVirtualLanPriorityCodePoint; -import org.openflow.util.HexString; -import org.openflow.util.U16; + +import org.projectfloodlight.openflow.protocol.OFFlowMod; +import org.projectfloodlight.openflow.protocol.match.Match; +import org.projectfloodlight.openflow.protocol.OFMessage; +import org.projectfloodlight.openflow.protocol.OFPacketIn; +import org.projectfloodlight.openflow.protocol.OFPacketOut; +import org.projectfloodlight.openflow.protocol.OFType; +import org.projectfloodlight.openflow.protocol.action.OFAction; +import org.projectfloodlight.openflow.types.DatapathId; +import org.projectfloodlight.openflow.types.IPv4Address; +import org.projectfloodlight.openflow.types.IpProtocol; +import org.projectfloodlight.openflow.types.MacAddress; +import org.projectfloodlight.openflow.types.OFBufferId; +import org.projectfloodlight.openflow.types.OFPort; +import org.projectfloodlight.openflow.types.TransportPort; +import org.projectfloodlight.openflow.types.U16; +import org.projectfloodlight.openflow.types.U64; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -59,11 +50,12 @@ import net.floodlightcontroller.core.FloodlightContext; import net.floodlightcontroller.core.IFloodlightProviderService; import net.floodlightcontroller.core.IOFMessageListener; import net.floodlightcontroller.core.IOFSwitch; +import net.floodlightcontroller.core.internal.IOFSwitchService; 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.counter.ICounterStoreService; +import net.floodlightcontroller.debugcounter.IDebugCounterService; import net.floodlightcontroller.devicemanager.IDevice; import net.floodlightcontroller.devicemanager.IDeviceService; import net.floodlightcontroller.devicemanager.SwitchPort; @@ -80,7 +72,9 @@ import net.floodlightcontroller.routing.Route; import net.floodlightcontroller.staticflowentry.IStaticFlowEntryPusherService; import net.floodlightcontroller.topology.ITopologyService; import net.floodlightcontroller.topology.NodePortTuple; -import net.floodlightcontroller.util.MACAddress; +import net.floodlightcontroller.util.ActionUtils; +import net.floodlightcontroller.util.FlowModUtils; +import net.floodlightcontroller.util.MatchUtils; import net.floodlightcontroller.util.OFMessageDamper; /** @@ -102,21 +96,22 @@ public class LoadBalancer implements IFloodlightModule, protected static Logger log = LoggerFactory.getLogger(LoadBalancer.class); // Our dependencies - protected IFloodlightProviderService floodlightProvider; - protected IRestApiService restApi; + protected IFloodlightProviderService floodlightProviderService; + protected IRestApiService restApiService; - protected ICounterStoreService counterStore; + protected IDebugCounterService debugCounterService; protected OFMessageDamper messageDamper; - protected IDeviceService deviceManager; - protected IRoutingService routingEngine; - protected ITopologyService topology; - protected IStaticFlowEntryPusherService sfp; + protected IDeviceService deviceManagerService; + protected IRoutingService routingEngineService; + protected ITopologyService topologyService; + protected IStaticFlowEntryPusherService sfpService; + protected IOFSwitchService switchService; protected HashMap<String, LBVip> vips; protected HashMap<String, LBPool> pools; protected HashMap<String, LBMember> members; protected HashMap<Integer, String> vipIpToId; - protected HashMap<Integer, MACAddress> vipIpToMac; + protected HashMap<Integer, MacAddress> vipIpToMac; protected HashMap<Integer, String> memberIpToId; protected HashMap<IPClient, LBMember> clientToMember; @@ -131,26 +126,24 @@ public class LoadBalancer implements IFloodlightModule, new Comparator<SwitchPort>() { @Override public int compare(SwitchPort d1, SwitchPort d2) { - Long d1ClusterId = - topology.getL2DomainId(d1.getSwitchDPID()); - Long d2ClusterId = - topology.getL2DomainId(d2.getSwitchDPID()); + DatapathId d1ClusterId = topologyService.getL2DomainId(d1.getSwitchDPID()); + DatapathId d2ClusterId = topologyService.getL2DomainId(d2.getSwitchDPID()); return d1ClusterId.compareTo(d2ClusterId); } }; // data structure for storing connected public class IPClient { - int ipAddress; - byte nw_proto; - short srcPort; // tcp/udp src port. icmp type (OFMatch convention) - short targetPort; // tcp/udp dst port, icmp code (OFMatch convention) + IPv4Address ipAddress; + IpProtocol nw_proto; + TransportPort srcPort; // tcp/udp src port. icmp type (OFMatch convention) + TransportPort targetPort; // tcp/udp dst port, icmp code (OFMatch convention) public IPClient() { - ipAddress = 0; - nw_proto = 0; - srcPort = -1; - targetPort = -1; + ipAddress = IPv4Address.NONE; + nw_proto = IpProtocol.NONE; + srcPort = TransportPort.NONE; + targetPort = TransportPort.NONE; } } @@ -185,12 +178,9 @@ public class LoadBalancer implements IFloodlightModule, return Command.CONTINUE; } - private net.floodlightcontroller.core.IListener.Command - processPacketIn(IOFSwitch sw, OFPacketIn pi, - FloodlightContext cntx) { + private net.floodlightcontroller.core.IListener.Command processPacketIn(IOFSwitch sw, OFPacketIn pi, FloodlightContext cntx) { - Ethernet eth = IFloodlightProviderService.bcStore.get(cntx, - IFloodlightProviderService.CONTEXT_PI_PAYLOAD); + Ethernet eth = IFloodlightProviderService.bcStore.get(cntx, IFloodlightProviderService.CONTEXT_PI_PAYLOAD); IPacket pkt = eth.getPayload(); if (eth.isBroadcast() || eth.isMulticast()) { @@ -214,7 +204,7 @@ public class LoadBalancer implements IFloodlightModule, IPv4 ip_pkt = (IPv4) pkt; // If match Vip and port, check pool and choose member - int destIpAddress = ip_pkt.getDestinationAddress(); + int destIpAddress = ip_pkt.getDestinationAddress().getInt(); if (vipIpToId.containsKey(destIpAddress)){ IPClient client = new IPClient(); @@ -231,8 +221,8 @@ public class LoadBalancer implements IFloodlightModule, client.targetPort = udp_pkt.getDestinationPort(); } if (ip_pkt.getPayload() instanceof ICMP) { - client.srcPort = 8; - client.targetPort = 0; + client.srcPort = TransportPort.of(8); + client.targetPort = TransportPort.of(0); } LBVip vip = vips.get(vipIpToId.get(destIpAddress)); @@ -243,7 +233,7 @@ public class LoadBalancer implements IFloodlightModule, pushBidirectionalVipRoutes(sw, pi, cntx, client, member); // packet out based on table rule - pushPacket(pkt, sw, pi.getBufferId(), pi.getInPort(), OFPort.OFPP_TABLE.getValue(), + pushPacket(pkt, sw, pi.getBufferId(), pi.getInPort(), OFPort.TABLE, cntx, true); return Command.STOP; @@ -274,7 +264,7 @@ public class LoadBalancer implements IFloodlightModule, ARP arpRequest = (ARP) eth.getPayload(); // have to do proxy arp reply since at this point we cannot determine the requesting application type - byte[] vipProxyMacBytes = vips.get(vipId).proxyMac.toBytes(); + byte[] vipProxyMacBytes = vips.get(vipId).proxyMac.getBytes(); // generate proxy ARP reply IPacket arpReply = new Ethernet() @@ -294,13 +284,12 @@ public class LoadBalancer implements IFloodlightModule, .setSenderProtocolAddress( arpRequest.getTargetProtocolAddress()) .setTargetHardwareAddress( - eth.getSourceMACAddress()) + eth.getSourceMACAddress().getBytes()) .setTargetProtocolAddress( arpRequest.getSenderProtocolAddress())); // push ARP reply out - pushPacket(arpReply, sw, OFPacketOut.BUFFER_ID_NONE, OFPort.OFPP_NONE.getValue(), - pi.getInPort(), cntx, true); + pushPacket(arpReply, sw, OFBufferId.NO_BUFFER, OFPort.ZERO, pi.getInPort(), cntx, true); log.debug("proxy ARP reply pushed as {}", IPv4.fromIPv4Address(vips.get(vipId).address)); return; @@ -319,9 +308,9 @@ public class LoadBalancer implements IFloodlightModule, */ public void pushPacket(IPacket packet, IOFSwitch sw, - int bufferId, - short inPort, - short outPort, + OFBufferId bufferId, + OFPort inPort, + OFPort outPort, FloodlightContext cntx, boolean flush) { if (log.isTraceEnabled()) { @@ -329,25 +318,20 @@ public class LoadBalancer implements IFloodlightModule, new Object[] {sw, inPort, outPort}); } - OFPacketOut po = - (OFPacketOut) floodlightProvider.getOFMessageFactory() - .getMessage(OFType.PACKET_OUT); + OFPacketOut.Builder pob = sw.getOFFactory().buildPacketOut(); // set actions List<OFAction> actions = new ArrayList<OFAction>(); - actions.add(new OFActionOutput(outPort, (short) 0xffff)); - - po.setActions(actions) - .setActionsLength((short) OFActionOutput.MINIMUM_LENGTH); - short poLength = - (short) (po.getActionsLength() + OFPacketOut.MINIMUM_LENGTH); + actions.add(sw.getOFFactory().actions().buildOutput().setPort(outPort).build()); + pob.setActions(actions); + // set buffer_id, in_port - po.setBufferId(bufferId); - po.setInPort(inPort); + pob.setBufferId(bufferId); + pob.setInPort(inPort); // set data - only if buffer_id == -1 - if (po.getBufferId() == OFPacketOut.BUFFER_ID_NONE) { + if (pob.getBufferId() == OFBufferId.NO_BUFFER) { if (packet == null) { log.error("BufferId is not set and packet data is null. " + "Cannot send packetOut. " + @@ -356,15 +340,12 @@ public class LoadBalancer implements IFloodlightModule, return; } byte[] packetData = packet.serialize(); - poLength += packetData.length; - po.setPacketData(packetData); + pob.setData(packetData); } - po.setLength(poLength); - try { - counterStore.updatePktOutFMCounterStoreLocal(sw, po); - messageDamper.write(sw, po, cntx, flush); + //TODO @Ryan debugCounterService.updatePktOutFMCounterStoreLocal(sw, pob.build()); + messageDamper.write(sw, pob.build(), cntx, flush); } catch (IOException e) { log.error("Failure writing packet out", e); } @@ -386,14 +367,13 @@ public class LoadBalancer implements IFloodlightModule, IDevice dstDevice = null; // retrieve all known devices - Collection<? extends IDevice> allDevices = deviceManager - .getAllDevices(); + Collection<? extends IDevice> allDevices = deviceManagerService.getAllDevices(); for (IDevice d : allDevices) { for (int j = 0; j < d.getIPv4Addresses().length; j++) { - if (srcDevice == null && client.ipAddress == d.getIPv4Addresses()[j]) + if (srcDevice == null && client.ipAddress.equals(d.getIPv4Addresses()[j])) srcDevice = d; - if (dstDevice == null && member.address == d.getIPv4Addresses()[j]) { + if (dstDevice == null && member.address == d.getIPv4Addresses()[j].getInt()) { dstDevice = d; member.macString = dstDevice.getMACAddressString(); } @@ -405,11 +385,11 @@ public class LoadBalancer implements IFloodlightModule, // srcDevice and/or dstDevice is null, no route can be pushed if (srcDevice == null || dstDevice == null) return; - Long srcIsland = topology.getL2DomainId(sw.getId()); + DatapathId srcIsland = topologyService.getL2DomainId(sw.getId()); if (srcIsland == null) { log.debug("No openflow island found for source {}/{}", - sw.getStringId(), pi.getInPort()); + sw.getId().toString(), pi.getInPort()); return; } @@ -418,12 +398,12 @@ public class LoadBalancer implements IFloodlightModule, boolean on_same_island = false; boolean on_same_if = false; for (SwitchPort dstDap : dstDevice.getAttachmentPoints()) { - long dstSwDpid = dstDap.getSwitchDPID(); - Long dstIsland = topology.getL2DomainId(dstSwDpid); + DatapathId dstSwDpid = dstDap.getSwitchDPID(); + DatapathId dstIsland = topologyService.getL2DomainId(dstSwDpid); if ((dstIsland != null) && dstIsland.equals(srcIsland)) { on_same_island = true; - if ((sw.getId() == dstSwDpid) && - (pi.getInPort() == dstDap.getPort())) { + if ((sw.getId().equals(dstSwDpid)) && + (pi.getInPort().equals(dstDap.getPort()))) { on_same_if = true; } break; @@ -463,10 +443,10 @@ public class LoadBalancer implements IFloodlightModule, while ((iSrcDaps < srcDaps.length) && (iDstDaps < dstDaps.length)) { SwitchPort srcDap = srcDaps[iSrcDaps]; SwitchPort dstDap = dstDaps[iDstDaps]; - Long srcCluster = - topology.getL2DomainId(srcDap.getSwitchDPID()); - Long dstCluster = - topology.getL2DomainId(dstDap.getSwitchDPID()); + DatapathId srcCluster = + topologyService.getL2DomainId(srcDap.getSwitchDPID()); + DatapathId dstCluster = + topologyService.getL2DomainId(dstDap.getSwitchDPID()); int srcVsDest = srcCluster.compareTo(dstCluster); if (srcVsDest == 0) { @@ -474,15 +454,15 @@ public class LoadBalancer implements IFloodlightModule, (srcCluster != null) && (dstCluster != null)) { Route routeIn = - routingEngine.getRoute(srcDap.getSwitchDPID(), - (short)srcDap.getPort(), + routingEngineService.getRoute(srcDap.getSwitchDPID(), + srcDap.getPort(), dstDap.getSwitchDPID(), - (short)dstDap.getPort(), 0); + dstDap.getPort(), U64.of(0)); Route routeOut = - routingEngine.getRoute(dstDap.getSwitchDPID(), - (short)dstDap.getPort(), + routingEngineService.getRoute(dstDap.getSwitchDPID(), + dstDap.getPort(), srcDap.getSwitchDPID(), - (short)srcDap.getPort(), 0); + srcDap.getPort(), U64.of(0)); // use static flow entry pusher to push flow mod along in and out path // in: match src client (ip, port), rewrite dest from vip ip/port to member ip/port, forward @@ -516,37 +496,34 @@ public class LoadBalancer implements IFloodlightModule, * @param LBMember member * @param long pinSwitch */ - public void pushStaticVipRoute(boolean inBound, Route route, IPClient client, LBMember member, long pinSwitch) { + public void pushStaticVipRoute(boolean inBound, Route route, IPClient client, LBMember member, DatapathId pinSwitch) { List<NodePortTuple> path = route.getPath(); - if (path.size()>0) { + if (path.size() > 0) { for (int i = 0; i < path.size(); i+=2) { - long sw = path.get(i).getNodeId(); - String swString = HexString.toHexString(path.get(i).getNodeId()); + DatapathId sw = path.get(i).getNodeId(); + String swString = path.get(i).getNodeId().toString(); String entryName; String matchString = null; String actionString = null; - OFFlowMod fm = (OFFlowMod) floodlightProvider.getOFMessageFactory() - .getMessage(OFType.FLOW_MOD); - - fm.setIdleTimeout((short) 0); // infinite - fm.setHardTimeout((short) 0); // infinite - fm.setBufferId(OFPacketOut.BUFFER_ID_NONE); - fm.setCommand((short) 0); - fm.setFlags((short) 0); - fm.setOutPort(OFPort.OFPP_NONE.getValue()); - fm.setCookie((long) 0); - fm.setPriority(Short.MAX_VALUE); + OFFlowMod.Builder fmb = switchService.getSwitch(pinSwitch).getOFFactory().buildFlowModify(); + + fmb.setIdleTimeout(FlowModUtils.INFINITE_TIMEOUT); + fmb.setHardTimeout(FlowModUtils.INFINITE_TIMEOUT); + fmb.setBufferId(OFBufferId.NO_BUFFER); + fmb.setOutPort(OFPort.ZERO); + fmb.setCookie(U64.of(0)); + fmb.setPriority(Short.MAX_VALUE); if (inBound) { entryName = "inbound-vip-"+ member.vipId+"-client-"+client.ipAddress+"-port-"+client.targetPort +"-srcswitch-"+path.get(0).getNodeId()+"-sw-"+sw; - matchString = "nw_src="+IPv4.fromIPv4Address(client.ipAddress)+"," - + "nw_proto="+String.valueOf(client.nw_proto)+"," - + "tp_src="+String.valueOf(client.srcPort & 0xffff)+"," - + "dl_type="+LB_ETHER_TYPE+"," - + "in_port="+String.valueOf(path.get(i).getPortId()); + matchString = MatchUtils.STR_NW_SRC + "="+client.ipAddress.toString()+"," + + MatchUtils.STR_NW_PROTO + "="+String.valueOf(client.nw_proto)+"," + + MatchUtils.STR_TP_SRC + "="+client.srcPort.toString()+"," + + MatchUtils.STR_DL_TYPE + "="+LB_ETHER_TYPE+"," + + MatchUtils.STR_IN_PORT + "="+path.get(i).getPortId().toString(); if (sw == pinSwitch) { actionString = "set-dst-ip="+IPv4.fromIPv4Address(member.address)+"," @@ -559,36 +536,35 @@ public class LoadBalancer implements IFloodlightModule, } else { entryName = "outbound-vip-"+ member.vipId+"-client-"+client.ipAddress+"-port-"+client.targetPort +"-srcswitch-"+path.get(0).getNodeId()+"-sw-"+sw; - matchString = "nw_dst="+IPv4.fromIPv4Address(client.ipAddress)+"," - + "nw_proto="+String.valueOf(client.nw_proto)+"," - + "tp_dst="+String.valueOf(client.srcPort & 0xffff)+"," - + "dl_type="+LB_ETHER_TYPE+"," - + "in_port="+String.valueOf(path.get(i).getPortId()); + matchString = MatchUtils.STR_NW_DST + "="+client.ipAddress.toString()+"," + + MatchUtils.STR_NW_PROTO + "="+client.nw_proto.toString()+"," + + MatchUtils.STR_TP_DST + "="+client.srcPort.toString()+"," + + MatchUtils.STR_DL_TYPE + "="+LB_ETHER_TYPE+"," + + MatchUtils.STR_IN_PORT + "="+path.get(i).getPortId().toString(); if (sw == pinSwitch) { - actionString = "set-src-ip="+IPv4.fromIPv4Address(vips.get(member.vipId).address)+"," - + "set-src-mac="+vips.get(member.vipId).proxyMac.toString()+"," - + "output="+path.get(i+1).getPortId(); + actionString = ActionUtils.STR_NW_SRC_SET + "="+IPv4.fromIPv4Address(vips.get(member.vipId).address)+"," + + ActionUtils.STR_DL_SRC_SET + "="+vips.get(member.vipId).proxyMac.toString()+"," + + ActionUtils.STR_OUTPUT + "="+path.get(i+1).getPortId(); } else { - actionString = "output="+path.get(i+1).getPortId(); + actionString = ActionUtils.STR_OUTPUT + "="+path.get(i+1).getPortId(); } } - parseActionString(fm, actionString, log); + ActionUtils.fromString(fmb, actionString, log); - fm.setPriority(U16.t(LB_PRIORITY)); + fmb.setPriority(U16.t(LB_PRIORITY)); - OFMatch ofMatch = new OFMatch(); + Match match = null; try { - ofMatch.fromString(matchString); + match = MatchUtils.fromString(matchString, switchService.getSwitch(sw).getOFFactory().getVersion()); } catch (IllegalArgumentException e) { - log.debug("ignoring flow entry {} on switch {} with illegal OFMatch() key: " - + matchString, entryName, swString); + log.debug("ignoring flow entry {} on switch {} with illegal OFMatch() key: " + matchString, entryName, swString); } - fm.setMatch(ofMatch); - sfp.addFlow(entryName, fm, swString); + fmb.setMatch(match); + sfpService.addFlow(entryName, fmb.build(), sw); } } @@ -650,7 +626,7 @@ public class LoadBalancer implements IFloodlightModule, @Override public LBPool createPool(LBPool pool) { - if (pool==null) + if (pool == null) pool = new LBPool(); pools.put(pool.id, pool); @@ -673,7 +649,7 @@ public class LoadBalancer implements IFloodlightModule, @Override public int removePool(String poolId) { LBPool pool; - if(pools!=null){ + if (pools != null) { pool = pools.get(poolId); if (pool.vipId != null) vips.get(pool.vipId).pools.remove(poolId); @@ -702,7 +678,7 @@ public class LoadBalancer implements IFloodlightModule, if(pools.containsKey(poolId)) { ArrayList<String> memberIds = pools.get(poolId).members; - for (int i=0; i<memberIds.size(); i++) + for (int i = 0; i<memberIds.size(); i++) result.add(members.get(memberIds.get(i))); } return result; @@ -749,31 +725,26 @@ public class LoadBalancer implements IFloodlightModule, @Override public Collection<LBMonitor> listMonitors() { - // TODO Auto-generated method stub return null; } @Override public Collection<LBMonitor> listMonitor(String monitorId) { - // TODO Auto-generated method stub return null; } @Override public LBMonitor createMonitor(LBMonitor monitor) { - // TODO Auto-generated method stub return null; } @Override public LBMonitor updateMonitor(LBMonitor monitor) { - // TODO Auto-generated method stub return null; } @Override public int removeMonitor(String monitorId) { - // TODO Auto-generated method stub return 0; } @@ -803,8 +774,9 @@ public class LoadBalancer implements IFloodlightModule, new ArrayList<Class<? extends IFloodlightService>>(); l.add(IFloodlightProviderService.class); l.add(IRestApiService.class); - l.add(ICounterStoreService.class); + l.add(IOFSwitchService.class); l.add(IDeviceService.class); + l.add(IDebugCounterService.class); l.add(ITopologyService.class); l.add(IRoutingService.class); l.add(IStaticFlowEntryPusherService.class); @@ -815,13 +787,14 @@ public class LoadBalancer implements IFloodlightModule, @Override public void init(FloodlightModuleContext context) throws FloodlightModuleException { - floodlightProvider = context.getServiceImpl(IFloodlightProviderService.class); - restApi = context.getServiceImpl(IRestApiService.class); - counterStore = context.getServiceImpl(ICounterStoreService.class); - deviceManager = context.getServiceImpl(IDeviceService.class); - routingEngine = context.getServiceImpl(IRoutingService.class); - topology = context.getServiceImpl(ITopologyService.class); - sfp = context.getServiceImpl(IStaticFlowEntryPusherService.class); + floodlightProviderService = context.getServiceImpl(IFloodlightProviderService.class); + restApiService = context.getServiceImpl(IRestApiService.class); + debugCounterService = context.getServiceImpl(IDebugCounterService.class); + deviceManagerService = context.getServiceImpl(IDeviceService.class); + routingEngineService = context.getServiceImpl(IRoutingService.class); + topologyService = context.getServiceImpl(ITopologyService.class); + sfpService = context.getServiceImpl(IStaticFlowEntryPusherService.class); + switchService = context.getServiceImpl(IOFSwitchService.class); messageDamper = new OFMessageDamper(OFMESSAGE_DAMPER_CAPACITY, EnumSet.of(OFType.FLOW_MOD), @@ -831,501 +804,13 @@ public class LoadBalancer implements IFloodlightModule, pools = new HashMap<String, LBPool>(); members = new HashMap<String, LBMember>(); vipIpToId = new HashMap<Integer, String>(); - vipIpToMac = new HashMap<Integer, MACAddress>(); + vipIpToMac = new HashMap<Integer, MacAddress>(); memberIpToId = new HashMap<Integer, String>(); } @Override public void startUp(FloodlightModuleContext context) { - floodlightProvider.addOFMessageListener(OFType.PACKET_IN, this); - restApi.addRestletRoutable(new LoadBalancerWebRoutable()); + floodlightProviderService.addOFMessageListener(OFType.PACKET_IN, this); + restApiService.addRestletRoutable(new LoadBalancerWebRoutable()); } - - // Utilities borrowed from StaticFlowEntries - - private static class SubActionStruct { - OFAction action; - int len; - } - - /** - * Parses OFFlowMod actions from strings. - * @param flowMod The OFFlowMod to set the actions for - * @param actionstr The string containing all the actions - * @param log A logger to log for errors. - */ - public static void parseActionString(OFFlowMod flowMod, String actionstr, Logger log) { - List<OFAction> actions = new LinkedList<OFAction>(); - int actionsLength = 0; - if (actionstr != null) { - actionstr = actionstr.toLowerCase(); - for (String subaction : actionstr.split(",")) { - String action = subaction.split("[=:]")[0]; - SubActionStruct subaction_struct = null; - - if (action.equals("output")) { - subaction_struct = decode_output(subaction, log); - } - else if (action.equals("enqueue")) { - subaction_struct = decode_enqueue(subaction, log); - } - else if (action.equals("strip-vlan")) { - subaction_struct = decode_strip_vlan(subaction, log); - } - else if (action.equals("set-vlan-id")) { - subaction_struct = decode_set_vlan_id(subaction, log); - } - else if (action.equals("set-vlan-priority")) { - subaction_struct = decode_set_vlan_priority(subaction, log); - } - else if (action.equals("set-src-mac")) { - subaction_struct = decode_set_src_mac(subaction, log); - } - else if (action.equals("set-dst-mac")) { - subaction_struct = decode_set_dst_mac(subaction, log); - } - else if (action.equals("set-tos-bits")) { - subaction_struct = decode_set_tos_bits(subaction, log); - } - else if (action.equals("set-src-ip")) { - subaction_struct = decode_set_src_ip(subaction, log); - } - else if (action.equals("set-dst-ip")) { - subaction_struct = decode_set_dst_ip(subaction, log); - } - else if (action.equals("set-src-port")) { - subaction_struct = decode_set_src_port(subaction, log); - } - else if (action.equals("set-dst-port")) { - subaction_struct = decode_set_dst_port(subaction, log); - } - else { - log.error("Unexpected action '{}', '{}'", action, subaction); - } - - if (subaction_struct != null) { - actions.add(subaction_struct.action); - actionsLength += subaction_struct.len; - } - } - } - log.debug("action {}", actions); - - flowMod.setActions(actions); - flowMod.setLengthU(OFFlowMod.MINIMUM_LENGTH + actionsLength); - } - - private static SubActionStruct decode_output(String subaction, Logger log) { - SubActionStruct sa = null; - Matcher n; - - n = Pattern.compile("output=(?:((?:0x)?\\d+)|(all)|(controller)|(local)|(ingress-port)|(normal)|(flood))").matcher(subaction); - if (n.matches()) { - OFActionOutput action = new OFActionOutput(); - action.setMaxLength(Short.MAX_VALUE); - short port = OFPort.OFPP_NONE.getValue(); - if (n.group(1) != null) { - try { - port = get_short(n.group(1)); - } - catch (NumberFormatException e) { - log.debug("Invalid port in: '{}' (error ignored)", subaction); - return null; - } - } - else if (n.group(2) != null) - port = OFPort.OFPP_ALL.getValue(); - else if (n.group(3) != null) - port = OFPort.OFPP_CONTROLLER.getValue(); - else if (n.group(4) != null) - port = OFPort.OFPP_LOCAL.getValue(); - else if (n.group(5) != null) - port = OFPort.OFPP_IN_PORT.getValue(); - else if (n.group(6) != null) - port = OFPort.OFPP_NORMAL.getValue(); - else if (n.group(7) != null) - port = OFPort.OFPP_FLOOD.getValue(); - action.setPort(port); - log.debug("action {}", action); - - sa = new SubActionStruct(); - sa.action = action; - sa.len = OFActionOutput.MINIMUM_LENGTH; - } - else { - log.error("Invalid subaction: '{}'", subaction); - return null; - } - - return sa; - } - - private static SubActionStruct decode_enqueue(String subaction, Logger log) { - SubActionStruct sa = null; - Matcher n; - - n = Pattern.compile("enqueue=(?:((?:0x)?\\d+)\\:((?:0x)?\\d+))").matcher(subaction); - if (n.matches()) { - short portnum = 0; - if (n.group(1) != null) { - try { - portnum = get_short(n.group(1)); - } - catch (NumberFormatException e) { - log.debug("Invalid port-num in: '{}' (error ignored)", subaction); - return null; - } - } - - int queueid = 0; - if (n.group(2) != null) { - try { - queueid = get_int(n.group(2)); - } - catch (NumberFormatException e) { - log.debug("Invalid queue-id in: '{}' (error ignored)", subaction); - return null; - } - } - - OFActionEnqueue action = new OFActionEnqueue(); - action.setPort(portnum); - action.setQueueId(queueid); - log.debug("action {}", action); - - sa = new SubActionStruct(); - sa.action = action; - sa.len = OFActionEnqueue.MINIMUM_LENGTH; - } - else { - log.debug("Invalid action: '{}'", subaction); - return null; - } - - return sa; - } - - private static SubActionStruct decode_strip_vlan(String subaction, Logger log) { - SubActionStruct sa = null; - Matcher n = Pattern.compile("strip-vlan").matcher(subaction); - - if (n.matches()) { - OFActionStripVirtualLan action = new OFActionStripVirtualLan(); - log.debug("action {}", action); - - sa = new SubActionStruct(); - sa.action = action; - sa.len = OFActionStripVirtualLan.MINIMUM_LENGTH; - } - else { - log.debug("Invalid action: '{}'", subaction); - return null; - } - - return sa; - } - - private static SubActionStruct decode_set_vlan_id(String subaction, Logger log) { - SubActionStruct sa = null; - Matcher n = Pattern.compile("set-vlan-id=((?:0x)?\\d+)").matcher(subaction); - - if (n.matches()) { - if (n.group(1) != null) { - try { - short vlanid = get_short(n.group(1)); - OFActionVirtualLanIdentifier action = new OFActionVirtualLanIdentifier(); - action.setVirtualLanIdentifier(vlanid); - log.debug(" action {}", action); - - sa = new SubActionStruct(); - sa.action = action; - sa.len = OFActionVirtualLanIdentifier.MINIMUM_LENGTH; - } - catch (NumberFormatException e) { - log.debug("Invalid VLAN in: {} (error ignored)", subaction); - return null; - } - } - } - else { - log.debug("Invalid action: '{}'", subaction); - return null; - } - - return sa; - } - - private static SubActionStruct decode_set_vlan_priority(String subaction, Logger log) { - SubActionStruct sa = null; - Matcher n = Pattern.compile("set-vlan-priority=((?:0x)?\\d+)").matcher(subaction); - - if (n.matches()) { - if (n.group(1) != null) { - try { - byte prior = get_byte(n.group(1)); - OFActionVirtualLanPriorityCodePoint action = new OFActionVirtualLanPriorityCodePoint(); - action.setVirtualLanPriorityCodePoint(prior); - log.debug(" action {}", action); - - sa = new SubActionStruct(); - sa.action = action; - sa.len = OFActionVirtualLanPriorityCodePoint.MINIMUM_LENGTH; - } - catch (NumberFormatException e) { - log.debug("Invalid VLAN priority in: {} (error ignored)", subaction); - return null; - } - } - } - else { - log.debug("Invalid action: '{}'", subaction); - return null; - } - - return sa; - } - - private static SubActionStruct decode_set_src_mac(String subaction, Logger log) { - SubActionStruct sa = null; - Matcher n = Pattern.compile("set-src-mac=(?:(\\p{XDigit}+)\\:(\\p{XDigit}+)\\:(\\p{XDigit}+)\\:(\\p{XDigit}+)\\:(\\p{XDigit}+)\\:(\\p{XDigit}+))").matcher(subaction); - - if (n.matches()) { - byte[] macaddr = get_mac_addr(n, subaction, log); - if (macaddr != null) { - OFActionDataLayerSource action = new OFActionDataLayerSource(); - action.setDataLayerAddress(macaddr); - log.debug("action {}", action); - - sa = new SubActionStruct(); - sa.action = action; - sa.len = OFActionDataLayerSource.MINIMUM_LENGTH; - } - } - else { - log.debug("Invalid action: '{}'", subaction); - return null; - } - - return sa; - } - - private static SubActionStruct decode_set_dst_mac(String subaction, Logger log) { - SubActionStruct sa = null; - Matcher n = Pattern.compile("set-dst-mac=(?:(\\p{XDigit}+)\\:(\\p{XDigit}+)\\:(\\p{XDigit}+)\\:(\\p{XDigit}+)\\:(\\p{XDigit}+)\\:(\\p{XDigit}+))").matcher(subaction); - - if (n.matches()) { - byte[] macaddr = get_mac_addr(n, subaction, log); - if (macaddr != null) { - OFActionDataLayerDestination action = new OFActionDataLayerDestination(); - action.setDataLayerAddress(macaddr); - log.debug(" action {}", action); - - sa = new SubActionStruct(); - sa.action = action; - sa.len = OFActionDataLayerDestination.MINIMUM_LENGTH; - } - } - else { - log.debug("Invalid action: '{}'", subaction); - return null; - } - - return sa; - } - - private static SubActionStruct decode_set_tos_bits(String subaction, Logger log) { - SubActionStruct sa = null; - Matcher n = Pattern.compile("set-tos-bits=((?:0x)?\\d+)").matcher(subaction); - - if (n.matches()) { - if (n.group(1) != null) { - try { - byte tosbits = get_byte(n.group(1)); - OFActionNetworkTypeOfService action = new OFActionNetworkTypeOfService(); - action.setNetworkTypeOfService(tosbits); - log.debug(" action {}", action); - - sa = new SubActionStruct(); - sa.action = action; - sa.len = OFActionNetworkTypeOfService.MINIMUM_LENGTH; - } - catch (NumberFormatException e) { - log.debug("Invalid dst-port in: {} (error ignored)", subaction); - return null; - } - } - } - else { - log.debug("Invalid action: '{}'", subaction); - return null; - } - - return sa; - } - - private static SubActionStruct decode_set_src_ip(String subaction, Logger log) { - SubActionStruct sa = null; - Matcher n = Pattern.compile("set-src-ip=(?:(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+))").matcher(subaction); - - if (n.matches()) { - int ipaddr = get_ip_addr(n, subaction, log); - OFActionNetworkLayerSource action = new OFActionNetworkLayerSource(); - action.setNetworkAddress(ipaddr); - log.debug(" action {}", action); - - sa = new SubActionStruct(); - sa.action = action; - sa.len = OFActionNetworkLayerSource.MINIMUM_LENGTH; - } - else { - log.debug("Invalid action: '{}'", subaction); - return null; - } - - return sa; - } - - private static SubActionStruct decode_set_dst_ip(String subaction, Logger log) { - SubActionStruct sa = null; - Matcher n = Pattern.compile("set-dst-ip=(?:(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+))").matcher(subaction); - - if (n.matches()) { - int ipaddr = get_ip_addr(n, subaction, log); - OFActionNetworkLayerDestination action = new OFActionNetworkLayerDestination(); - action.setNetworkAddress(ipaddr); - log.debug("action {}", action); - - sa = new SubActionStruct(); - sa.action = action; - sa.len = OFActionNetworkLayerDestination.MINIMUM_LENGTH; - } - else { - log.debug("Invalid action: '{}'", subaction); - return null; - } - - return sa; - } - - private static SubActionStruct decode_set_src_port(String subaction, Logger log) { - SubActionStruct sa = null; - Matcher n = Pattern.compile("set-src-port=((?:0x)?\\d+)").matcher(subaction); - - if (n.matches()) { - if (n.group(1) != null) { - try { - short portnum = get_short(n.group(1)); - OFActionTransportLayerSource action = new OFActionTransportLayerSource(); - action.setTransportPort(portnum); - log.debug("action {}", action); - - sa = new SubActionStruct(); - sa.action = action; - sa.len = OFActionTransportLayerSource.MINIMUM_LENGTH;; - } - catch (NumberFormatException e) { - log.debug("Invalid src-port in: {} (error ignored)", subaction); - return null; - } - } - } - else { - log.debug("Invalid action: '{}'", subaction); - return null; - } - - return sa; - } - - private static SubActionStruct decode_set_dst_port(String subaction, Logger log) { - SubActionStruct sa = null; - Matcher n = Pattern.compile("set-dst-port=((?:0x)?\\d+)").matcher(subaction); - - if (n.matches()) { - if (n.group(1) != null) { - try { - short portnum = get_short(n.group(1)); - OFActionTransportLayerDestination action = new OFActionTransportLayerDestination(); - action.setTransportPort(portnum); - log.debug("action {}", action); - - sa = new SubActionStruct(); - sa.action = action; - sa.len = OFActionTransportLayerDestination.MINIMUM_LENGTH;; - } - catch (NumberFormatException e) { - log.debug("Invalid dst-port in: {} (error ignored)", subaction); - return null; - } - } - } - else { - log.debug("Invalid action: '{}'", subaction); - return null; - } - - return sa; - } - - private static byte[] get_mac_addr(Matcher n, String subaction, Logger log) { - byte[] macaddr = new byte[6]; - - for (int i=0; i<6; i++) { - if (n.group(i+1) != null) { - try { - macaddr[i] = get_byte("0x" + n.group(i+1)); - } - catch (NumberFormatException e) { - log.debug("Invalid src-mac in: '{}' (error ignored)", subaction); - return null; - } - } - else { - log.debug("Invalid src-mac in: '{}' (null, error ignored)", subaction); - return null; - } - } - - return macaddr; - } - - private static int get_ip_addr(Matcher n, String subaction, Logger log) { - int ipaddr = 0; - - for (int i=0; i<4; i++) { - if (n.group(i+1) != null) { - try { - ipaddr = ipaddr<<8; - ipaddr = ipaddr | get_int(n.group(i+1)); - } - catch (NumberFormatException e) { - log.debug("Invalid src-ip in: '{}' (error ignored)", subaction); - return 0; - } - } - else { - log.debug("Invalid src-ip in: '{}' (null, error ignored)", subaction); - return 0; - } - } - - return ipaddr; - } - - // Parse int as decimal, hex (start with 0x or #) or octal (starts with 0) - private static int get_int(String str) { - return Integer.decode(str); - } - - // Parse short as decimal, hex (start with 0x or #) or octal (starts with 0) - private static short get_short(String str) { - return (short)(int)Integer.decode(str); - } - - // Parse byte as decimal, hex (start with 0x or #) or octal (starts with 0) - private static byte get_byte(String str) { - return Integer.decode(str).byteValue(); - } - - } diff --git a/src/main/java/net/floodlightcontroller/packet/BSNPROBE.java b/src/main/java/net/floodlightcontroller/packet/BSNPROBE.java index c9115eacb5c22c459cbaded572a64063125118c6..63f710e58748881ca2aabe1360e559448253fec8 100644 --- a/src/main/java/net/floodlightcontroller/packet/BSNPROBE.java +++ b/src/main/java/net/floodlightcontroller/packet/BSNPROBE.java @@ -21,7 +21,7 @@ package net.floodlightcontroller.packet; import java.nio.ByteBuffer; import java.util.Arrays; -import org.openflow.util.HexString; +import org.projectfloodlight.openflow.util.HexString; /** * @author Shudong Zhou (shudong.zhou@bigswitch.com) diff --git a/src/main/java/net/floodlightcontroller/packet/Ethernet.java b/src/main/java/net/floodlightcontroller/packet/Ethernet.java index 60887b3158e27a3d714927aab4f57b7085242164..2b9d0ea3a88fe2933f63ac2a898471022109635b 100644 --- a/src/main/java/net/floodlightcontroller/packet/Ethernet.java +++ b/src/main/java/net/floodlightcontroller/packet/Ethernet.java @@ -22,8 +22,8 @@ import java.util.Arrays; import java.util.HashMap; import java.util.Map; -import net.floodlightcontroller.util.MACAddress; -import org.openflow.util.HexString; +import org.projectfloodlight.openflow.types.MacAddress; +import org.projectfloodlight.openflow.types.VlanVid; /** * @@ -36,7 +36,7 @@ public class Ethernet extends BasePacket { public static final short TYPE_IPv4 = 0x0800; public static final short TYPE_LLDP = (short) 0x88cc; public static final short TYPE_BSN = (short) 0x8942; - public static final short VLAN_UNTAGGED = (short)0xffff; + public static final short VLAN_UNTAGGED = VlanVid.ZERO.getVlan(); // untagged vlan must be 0x0000 for loxi. We can use the convenient ZERO field public static final short DATALAYER_ADDRESS_LENGTH = 6; // bytes public static Map<Short, Class<? extends IPacket>> etherTypeClassMap; @@ -49,8 +49,8 @@ public class Ethernet extends BasePacket { etherTypeClassMap.put(TYPE_BSN, BSN.class); } - protected MACAddress destinationMACAddress; - protected MACAddress sourceMACAddress; + protected MacAddress destinationMACAddress; + protected MacAddress sourceMACAddress; protected byte priorityCode; protected short vlanID; protected short etherType; @@ -64,17 +64,10 @@ public class Ethernet extends BasePacket { this.vlanID = VLAN_UNTAGGED; } - /** - * @return the destination MAC as a byte array - */ - public byte[] getDestinationMACAddress() { - return destinationMACAddress.toBytes(); - } - /** * @return the destination MAC */ - public MACAddress getDestinationMAC() { + public MacAddress getDestinationMACAddress() { return destinationMACAddress; } @@ -82,29 +75,30 @@ public class Ethernet extends BasePacket { * @param destinationMACAddress the destination MAC to set */ public Ethernet setDestinationMACAddress(byte[] destinationMACAddress) { - this.destinationMACAddress = MACAddress.valueOf(destinationMACAddress); + this.destinationMACAddress = MacAddress.of(destinationMACAddress); return this; } - + /** * @param destinationMACAddress the destination MAC to set */ - public Ethernet setDestinationMACAddress(String destinationMACAddress) { - this.destinationMACAddress = MACAddress.valueOf(destinationMACAddress); + public Ethernet setDestinationMACAddress(MacAddress destinationMACAddress) { + this.destinationMACAddress = destinationMACAddress; return this; } /** - * @return the source MACAddress as a byte array + * @param destinationMACAddress the destination MAC to set */ - public byte[] getSourceMACAddress() { - return sourceMACAddress.toBytes(); + public Ethernet setDestinationMACAddress(String destinationMACAddress) { + this.destinationMACAddress = MacAddress.of(destinationMACAddress); + return this; } - + /** * @return the source MACAddress */ - public MACAddress getSourceMAC() { + public MacAddress getSourceMACAddress() { return sourceMACAddress; } @@ -112,7 +106,15 @@ public class Ethernet extends BasePacket { * @param sourceMACAddress the source MAC to set */ public Ethernet setSourceMACAddress(byte[] sourceMACAddress) { - this.sourceMACAddress = MACAddress.valueOf(sourceMACAddress); + this.sourceMACAddress = MacAddress.of(sourceMACAddress); + return this; + } + + /** + * @param sourceMACAddress the source MAC to set + */ + public Ethernet setSourceMACAddress(MacAddress sourceMACAddress) { + this.sourceMACAddress = sourceMACAddress; return this; } @@ -120,7 +122,7 @@ public class Ethernet extends BasePacket { * @param sourceMACAddress the source MAC to set */ public Ethernet setSourceMACAddress(String sourceMACAddress) { - this.sourceMACAddress = MACAddress.valueOf(sourceMACAddress); + this.sourceMACAddress = MacAddress.of(sourceMACAddress); return this; } @@ -173,7 +175,7 @@ public class Ethernet extends BasePacket { * @return True if the Ethernet frame is broadcast, false otherwise */ public boolean isBroadcast() { - assert(destinationMACAddress.length() == 6); + assert(destinationMACAddress.getLength() == 6); return destinationMACAddress.isBroadcast(); } @@ -213,8 +215,8 @@ public class Ethernet extends BasePacket { } byte[] data = new byte[length]; ByteBuffer bb = ByteBuffer.wrap(data); - bb.put(destinationMACAddress.toBytes()); - bb.put(sourceMACAddress.toBytes()); + bb.put(destinationMACAddress.getBytes()); + bb.put(sourceMACAddress.getBytes()); if (vlanID != VLAN_UNTAGGED) { bb.putShort((short) 0x8100); bb.putShort((short) ((priorityCode << 13) | (vlanID & 0x0fff))); @@ -234,16 +236,16 @@ public class Ethernet extends BasePacket { return null; ByteBuffer bb = ByteBuffer.wrap(data, offset, length); if (this.destinationMACAddress == null) - this.destinationMACAddress = MACAddress.valueOf(new byte[6]); - byte[] dstAddr = new byte[MACAddress.MAC_ADDRESS_LENGTH]; + this.destinationMACAddress = MacAddress.of(new byte[6]); + byte[] dstAddr = new byte[MacAddress.NONE.getLength()]; bb.get(dstAddr); - this.destinationMACAddress = MACAddress.valueOf(dstAddr); + this.destinationMACAddress = MacAddress.of(dstAddr); if (this.sourceMACAddress == null) - this.sourceMACAddress = MACAddress.valueOf(new byte[6]); - byte[] srcAddr = new byte[MACAddress.MAC_ADDRESS_LENGTH]; + this.sourceMACAddress = MacAddress.of(new byte[6]); + byte[] srcAddr = new byte[MacAddress.NONE.getLength()]; bb.get(srcAddr); - this.sourceMACAddress = MACAddress.valueOf(srcAddr); + this.sourceMACAddress = MacAddress.of(srcAddr); short etherType = bb.getShort(); if (etherType == (short) 0x8100) { @@ -322,7 +324,7 @@ public class Ethernet extends BasePacket { * @return The macAddress as a byte array */ public static byte[] toMACAddress(String macAddress) { - return MACAddress.valueOf(macAddress).toBytes(); + return MacAddress.of(macAddress).getBytes(); } @@ -333,7 +335,7 @@ public class Ethernet extends BasePacket { * @return a long containing the mac address bytes */ public static long toLong(byte[] macAddress) { - return MACAddress.valueOf(macAddress).toLong(); + return MacAddress.of(macAddress).getLong(); } /** @@ -342,7 +344,7 @@ public class Ethernet extends BasePacket { * @return the bytes of the mac address */ public static byte[] toByteArray(long macAddress) { - return MACAddress.valueOf(macAddress).toBytes(); + return MacAddress.of(macAddress).getBytes(); } /* (non-Javadoc) @@ -418,9 +420,9 @@ public class Ethernet extends BasePacket { sb.append("\ndl_vlan_pcp: "); sb.append(this.getPriorityCode()); sb.append("\ndl_src: "); - sb.append(HexString.toHexString(this.getSourceMACAddress())); + sb.append(this.getSourceMACAddress().toString()); sb.append("\ndl_dst: "); - sb.append(HexString.toHexString(this.getDestinationMACAddress())); + sb.append(this.getDestinationMACAddress().toString()); if (pkt instanceof ARP) { @@ -443,9 +445,9 @@ public class Ethernet extends BasePacket { else if (pkt instanceof IPv4) { IPv4 p = (IPv4) pkt; sb.append("\nnw_src: "); - sb.append(IPv4.fromIPv4Address(p.getSourceAddress())); + sb.append(p.getSourceAddress().toString()); sb.append("\nnw_dst: "); - sb.append(IPv4.fromIPv4Address(p.getDestinationAddress())); + sb.append(p.getDestinationAddress().toString()); sb.append("\nnw_tos: "); sb.append(p.getDiffServ()); sb.append("\nnw_proto: "); diff --git a/src/main/java/net/floodlightcontroller/packet/ICMP.java b/src/main/java/net/floodlightcontroller/packet/ICMP.java index 2988106484530d437df2b5fe62aac2c0a0bea480..6f0d95006aa0170b88a1b386842dd3c7e3d7f800 100644 --- a/src/main/java/net/floodlightcontroller/packet/ICMP.java +++ b/src/main/java/net/floodlightcontroller/packet/ICMP.java @@ -21,6 +21,8 @@ import java.nio.ByteBuffer; import java.util.HashMap; import java.util.Map; +import org.projectfloodlight.openflow.types.IpProtocol; + /** * Implements ICMP packet format * @author shudong.zhou@bigswitch.com @@ -126,7 +128,7 @@ public class ICMP extends BasePacket { bb.put(payloadData); if (this.parent != null && this.parent instanceof IPv4) - ((IPv4)this.parent).setProtocol(IPv4.PROTOCOL_ICMP); + ((IPv4)this.parent).setProtocol(IpProtocol.ICMP); // compute checksum if needed if (this.checksum == 0) { diff --git a/src/main/java/net/floodlightcontroller/packet/IPv4.java b/src/main/java/net/floodlightcontroller/packet/IPv4.java index 61d9730ba2040dd330e951f574b7c8471dcc051b..4f8efb07a31061a597646d7bc4bf46cbd5f47c60 100644 --- a/src/main/java/net/floodlightcontroller/packet/IPv4.java +++ b/src/main/java/net/floodlightcontroller/packet/IPv4.java @@ -26,6 +26,10 @@ import java.util.Collection; import java.util.HashMap; import java.util.Map; +import org.projectfloodlight.openflow.types.IPv4Address; +import org.projectfloodlight.openflow.types.IpProtocol; +import org.projectfloodlight.openflow.types.U8; + /** * @author David Erickson (daviderickson@cs.stanford.edu) * @@ -57,10 +61,10 @@ public class IPv4 extends BasePacket { protected byte flags; protected short fragmentOffset; protected byte ttl; - protected byte protocol; + protected IpProtocol protocol; protected short checksum; - protected int sourceAddress; - protected int destinationAddress; + protected IPv4Address sourceAddress; + protected IPv4Address destinationAddress; protected byte[] options; protected boolean isTruncated; @@ -199,14 +203,14 @@ public class IPv4 extends BasePacket { /** * @return the protocol */ - public byte getProtocol() { + public IpProtocol getProtocol() { return protocol; } /** * @param protocol the protocol to set */ - public IPv4 setProtocol(byte protocol) { + public IPv4 setProtocol(IpProtocol protocol) { this.protocol = protocol; return this; } @@ -234,46 +238,62 @@ public class IPv4 extends BasePacket { /** * @return the sourceAddress */ - public int getSourceAddress() { + public IPv4Address getSourceAddress() { return sourceAddress; } /** * @param sourceAddress the sourceAddress to set */ - public IPv4 setSourceAddress(int sourceAddress) { + public IPv4 setSourceAddress(IPv4Address sourceAddress) { this.sourceAddress = sourceAddress; return this; } + + /** + * @param sourceAddress the sourceAddress to set + */ + public IPv4 setSourceAddress(int sourceAddress) { + this.sourceAddress = IPv4Address.of(sourceAddress); + return this; + } /** * @param sourceAddress the sourceAddress to set */ public IPv4 setSourceAddress(String sourceAddress) { - this.sourceAddress = IPv4.toIPv4Address(sourceAddress); + this.sourceAddress = IPv4Address.of(sourceAddress); return this; } /** * @return the destinationAddress */ - public int getDestinationAddress() { + public IPv4Address getDestinationAddress() { return destinationAddress; } /** * @param destinationAddress the destinationAddress to set */ - public IPv4 setDestinationAddress(int destinationAddress) { + public IPv4 setDestinationAddress(IPv4Address destinationAddress) { this.destinationAddress = destinationAddress; return this; } + + /** + * @param destinationAddress the destinationAddress to set + */ + public IPv4 setDestinationAddress(int destinationAddress) { + this.destinationAddress = IPv4Address.of(destinationAddress); + return this; + } /** * @param destinationAddress the destinationAddress to set */ public IPv4 setDestinationAddress(String destinationAddress) { - this.destinationAddress = IPv4.toIPv4Address(destinationAddress); + this.destinationAddress = IPv4Address.of(destinationAddress); return this; } @@ -328,10 +348,10 @@ public class IPv4 extends BasePacket { bb.putShort((short)(((this.flags & IPV4_FLAGS_MASK) << IPV4_FLAGS_SHIFT) | (this.fragmentOffset & IPV4_OFFSET_MASK))); bb.put(this.ttl); - bb.put(this.protocol); + bb.putShort(this.protocol.getIpProtocolNumber()); bb.putShort(this.checksum); - bb.putInt(this.sourceAddress); - bb.putInt(this.destinationAddress); + bb.putInt(this.sourceAddress.getInt()); + bb.putInt(this.destinationAddress.getInt()); if (this.options != null) bb.put(this.options); if (payloadData != null) @@ -373,10 +393,10 @@ public class IPv4 extends BasePacket { this.flags = (byte) ((sscratch >> IPV4_FLAGS_SHIFT) & IPV4_FLAGS_MASK); this.fragmentOffset = (short) (sscratch & IPV4_OFFSET_MASK); this.ttl = bb.get(); - this.protocol = bb.get(); + this.protocol = IpProtocol.of(U8.f(bb.get())); this.checksum = bb.getShort(); - this.sourceAddress = bb.getInt(); - this.destinationAddress = bb.getInt(); + this.sourceAddress = IPv4Address.of(bb.getInt()); + this.destinationAddress = IPv4Address.of(bb.getInt()); if (this.headerLength > 5) { int optionsLength = (this.headerLength - 5) * 4; @@ -398,8 +418,8 @@ public class IPv4 extends BasePacket { } else { if (log.isTraceEnabled() && isFragment) { log.trace("IPv4 fragment detected {}->{}, forward using IP header only", - fromIPv4Address(this.sourceAddress), - fromIPv4Address(this.destinationAddress)); + this.sourceAddress.toString(), + this.destinationAddress.toString()); } payload = new Data(); } @@ -540,15 +560,15 @@ public class IPv4 extends BasePacket { final int prime = 2521; int result = super.hashCode(); result = prime * result + checksum; - result = prime * result + destinationAddress; + result = prime * result + destinationAddress.getInt(); result = prime * result + diffServ; result = prime * result + flags; result = prime * result + fragmentOffset; result = prime * result + headerLength; result = prime * result + identification; result = prime * result + Arrays.hashCode(options); - result = prime * result + protocol; - result = prime * result + sourceAddress; + result = prime * result + protocol.getIpProtocolNumber(); + result = prime * result + sourceAddress.getInt(); result = prime * result + totalLength; result = prime * result + ttl; result = prime * result + version; @@ -569,7 +589,7 @@ public class IPv4 extends BasePacket { IPv4 other = (IPv4) obj; if (checksum != other.checksum) return false; - if (destinationAddress != other.destinationAddress) + if (!destinationAddress.equals(other.destinationAddress)) return false; if (diffServ != other.diffServ) return false; @@ -583,9 +603,9 @@ public class IPv4 extends BasePacket { return false; if (!Arrays.equals(options, other.options)) return false; - if (protocol != other.protocol) + if (!protocol.equals(other.protocol)) return false; - if (sourceAddress != other.sourceAddress) + if (!sourceAddress.equals(other.sourceAddress)) return false; if (totalLength != other.totalLength) return false; diff --git a/src/main/java/net/floodlightcontroller/packet/TCP.java b/src/main/java/net/floodlightcontroller/packet/TCP.java index 6af5932a55c15d9e4b5729ff6eac8998c9dc16eb..f4b9a50260c94137a3d0ab7eefe9debdf14d8ba7 100644 --- a/src/main/java/net/floodlightcontroller/packet/TCP.java +++ b/src/main/java/net/floodlightcontroller/packet/TCP.java @@ -20,13 +20,16 @@ package net.floodlightcontroller.packet; import java.nio.ByteBuffer; import java.util.Arrays; +import org.projectfloodlight.openflow.types.IpProtocol; +import org.projectfloodlight.openflow.types.TransportPort; + /** * * @author shudong.zhou@bigswitch.com */ public class TCP extends BasePacket { - protected short sourcePort; - protected short destinationPort; + protected TransportPort sourcePort; + protected TransportPort destinationPort; protected int sequence; protected int acknowledge; protected byte dataOffset; @@ -39,32 +42,48 @@ public class TCP extends BasePacket { /** * @return the sourcePort */ - public short getSourcePort() { + public TransportPort getSourcePort() { return sourcePort; } /** * @param sourcePort the sourcePort to set */ - public TCP setSourcePort(short sourcePort) { + public TCP setSourcePort(TransportPort sourcePort) { this.sourcePort = sourcePort; return this; } + + /** + * @param sourcePort the sourcePort to set + */ + public TCP setSourcePort(int sourcePort) { + this.sourcePort = TransportPort.of(sourcePort); + return this; + } /** * @return the destinationPort */ - public short getDestinationPort() { + public TransportPort getDestinationPort() { return destinationPort; } /** * @param destinationPort the destinationPort to set */ - public TCP setDestinationPort(short destinationPort) { + public TCP setDestinationPort(TransportPort destinationPort) { this.destinationPort = destinationPort; return this; } + + /** + * @param destinationPort the destinationPort to set + */ + public TCP setDestinationPort(short destinationPort) { + this.destinationPort = TransportPort.of(destinationPort); + return this; + } /** * @return the checksum @@ -166,8 +185,8 @@ public class TCP extends BasePacket { byte[] data = new byte[length]; ByteBuffer bb = ByteBuffer.wrap(data); - bb.putShort(this.sourcePort); - bb.putShort(this.destinationPort); + bb.putShort((short)this.sourcePort.getPort()); //TCP ports are defined to be 16 bits + bb.putShort((short)this.destinationPort.getPort()); bb.putInt(this.sequence); bb.putInt(this.acknowledge); bb.putShort((short) (this.flags | (dataOffset << 12))); @@ -185,7 +204,7 @@ public class TCP extends BasePacket { bb.put(payloadData); if (this.parent != null && this.parent instanceof IPv4) - ((IPv4)this.parent).setProtocol(IPv4.PROTOCOL_TCP); + ((IPv4)this.parent).setProtocol(IpProtocol.TCP); // compute checksum if needed if (this.checksum == 0) { @@ -195,11 +214,11 @@ public class TCP extends BasePacket { // compute pseudo header mac if (this.parent != null && this.parent instanceof IPv4) { IPv4 ipv4 = (IPv4) this.parent; - accumulation += ((ipv4.getSourceAddress() >> 16) & 0xffff) - + (ipv4.getSourceAddress() & 0xffff); - accumulation += ((ipv4.getDestinationAddress() >> 16) & 0xffff) - + (ipv4.getDestinationAddress() & 0xffff); - accumulation += ipv4.getProtocol() & 0xff; + accumulation += ((ipv4.getSourceAddress().getInt() >> 16) & 0xffff) + + (ipv4.getSourceAddress().getInt() & 0xffff); + accumulation += ((ipv4.getDestinationAddress().getInt() >> 16) & 0xffff) + + (ipv4.getDestinationAddress().getInt() & 0xffff); + accumulation += ipv4.getProtocol().getIpProtocolNumber() & 0xff; accumulation += length & 0xffff; } @@ -227,8 +246,8 @@ public class TCP extends BasePacket { final int prime = 5807; int result = super.hashCode(); result = prime * result + checksum; - result = prime * result + destinationPort; - result = prime * result + sourcePort; + result = prime * result + destinationPort.getPort(); + result = prime * result + sourcePort.getPort(); return result; } @@ -261,8 +280,8 @@ public class TCP extends BasePacket { public IPacket deserialize(byte[] data, int offset, int length) throws PacketParsingException { ByteBuffer bb = ByteBuffer.wrap(data, offset, length); - this.sourcePort = bb.getShort(); - this.destinationPort = bb.getShort(); + this.sourcePort = TransportPort.of(bb.getShort()); + this.destinationPort = TransportPort.of(bb.getShort()); this.sequence = bb.getInt(); this.acknowledge = bb.getInt(); this.flags = bb.getShort(); diff --git a/src/main/java/net/floodlightcontroller/packet/UDP.java b/src/main/java/net/floodlightcontroller/packet/UDP.java index 710762c097137bede7a84da1fa7cd6de2a586057..fad7a128f85ccbb074665a11dc250189e540a884 100644 --- a/src/main/java/net/floodlightcontroller/packet/UDP.java +++ b/src/main/java/net/floodlightcontroller/packet/UDP.java @@ -21,6 +21,9 @@ import java.nio.ByteBuffer; import java.util.HashMap; import java.util.Map; +import org.projectfloodlight.openflow.types.IpProtocol; +import org.projectfloodlight.openflow.types.TransportPort; + /** * * @author David Erickson (daviderickson@cs.stanford.edu) @@ -40,40 +43,56 @@ public class UDP extends BasePacket { } - protected short sourcePort; - protected short destinationPort; + protected TransportPort sourcePort; + protected TransportPort destinationPort; protected short length; protected short checksum; /** * @return the sourcePort */ - public short getSourcePort() { + public TransportPort getSourcePort() { return sourcePort; } /** * @param sourcePort the sourcePort to set */ - public UDP setSourcePort(short sourcePort) { + public UDP setSourcePort(TransportPort sourcePort) { this.sourcePort = sourcePort; return this; } + + /** + * @param sourcePort the sourcePort to set + */ + public UDP setSourcePort(short sourcePort) { + this.sourcePort = TransportPort.of(sourcePort); + return this; + } /** * @return the destinationPort */ - public short getDestinationPort() { + public TransportPort getDestinationPort() { return destinationPort; } /** * @param destinationPort the destinationPort to set */ - public UDP setDestinationPort(short destinationPort) { + public UDP setDestinationPort(TransportPort destinationPort) { this.destinationPort = destinationPort; return this; } + + /** + * @param destinationPort the destinationPort to set + */ + public UDP setDestinationPort(short destinationPort) { + this.destinationPort = TransportPort.of(destinationPort); + return this; + } /** * @return the length @@ -122,15 +141,15 @@ public class UDP extends BasePacket { byte[] data = new byte[this.length]; ByteBuffer bb = ByteBuffer.wrap(data); - bb.putShort(this.sourcePort); - bb.putShort(this.destinationPort); + bb.putShort((short)this.sourcePort.getPort()); // UDP packet port numbers are 16 bit + bb.putShort((short)this.destinationPort.getPort()); bb.putShort(this.length); bb.putShort(this.checksum); if (payloadData != null) bb.put(payloadData); if (this.parent != null && this.parent instanceof IPv4) - ((IPv4)this.parent).setProtocol(IPv4.PROTOCOL_UDP); + ((IPv4)this.parent).setProtocol(IpProtocol.UDP); // compute checksum if needed if (this.checksum == 0) { @@ -140,11 +159,11 @@ public class UDP extends BasePacket { // compute pseudo header mac if (this.parent != null && this.parent instanceof IPv4) { IPv4 ipv4 = (IPv4) this.parent; - accumulation += ((ipv4.getSourceAddress() >> 16) & 0xffff) - + (ipv4.getSourceAddress() & 0xffff); - accumulation += ((ipv4.getDestinationAddress() >> 16) & 0xffff) - + (ipv4.getDestinationAddress() & 0xffff); - accumulation += ipv4.getProtocol() & 0xff; + accumulation += ((ipv4.getSourceAddress().getInt() >> 16) & 0xffff) + + (ipv4.getSourceAddress().getInt() & 0xffff); + accumulation += ((ipv4.getDestinationAddress().getInt() >> 16) & 0xffff) + + (ipv4.getDestinationAddress().getInt() & 0xffff); + accumulation += ipv4.getProtocol().getIpProtocolNumber() & 0xff; accumulation += this.length & 0xffff; } @@ -172,9 +191,9 @@ public class UDP extends BasePacket { final int prime = 5807; int result = super.hashCode(); result = prime * result + checksum; - result = prime * result + destinationPort; + result = prime * result + destinationPort.getPort(); result = prime * result + length; - result = prime * result + sourcePort; + result = prime * result + sourcePort.getPort(); return result; } @@ -205,8 +224,8 @@ public class UDP extends BasePacket { public IPacket deserialize(byte[] data, int offset, int length) throws PacketParsingException { ByteBuffer bb = ByteBuffer.wrap(data, offset, length); - this.sourcePort = bb.getShort(); - this.destinationPort = bb.getShort(); + this.sourcePort = TransportPort.of(bb.getShort()); + this.destinationPort = TransportPort.of(bb.getShort()); this.length = bb.getShort(); this.checksum = bb.getShort(); diff --git a/src/main/java/net/floodlightcontroller/perfmon/IPktInProcessingTimeService.java b/src/main/java/net/floodlightcontroller/perfmon/IPktInProcessingTimeService.java index cc70487eb8497e4330b59f679196450a57e5559d..0198d16a45863b9c89ce9b51aec59ddc4a787532 100644 --- a/src/main/java/net/floodlightcontroller/perfmon/IPktInProcessingTimeService.java +++ b/src/main/java/net/floodlightcontroller/perfmon/IPktInProcessingTimeService.java @@ -18,7 +18,7 @@ package net.floodlightcontroller.perfmon; import java.util.List; -import org.openflow.protocol.OFMessage; +import org.projectfloodlight.openflow.protocol.OFMessage; import net.floodlightcontroller.core.FloodlightContext; import net.floodlightcontroller.core.IOFMessageListener; diff --git a/src/main/java/net/floodlightcontroller/perfmon/NullPktInProcessingTime.java b/src/main/java/net/floodlightcontroller/perfmon/NullPktInProcessingTime.java index 51641ccc7651f80c7aec121763a3b04a7273b7b8..c10bc93a67a665d270e520fcee7d0b0965335b11 100644 --- a/src/main/java/net/floodlightcontroller/perfmon/NullPktInProcessingTime.java +++ b/src/main/java/net/floodlightcontroller/perfmon/NullPktInProcessingTime.java @@ -22,7 +22,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import org.openflow.protocol.OFMessage; +import org.projectfloodlight.openflow.protocol.OFMessage; import net.floodlightcontroller.core.FloodlightContext; import net.floodlightcontroller.core.IOFMessageListener; diff --git a/src/main/java/net/floodlightcontroller/perfmon/PktInProcessingTime.java b/src/main/java/net/floodlightcontroller/perfmon/PktInProcessingTime.java index 366721120e332de7eb700ed383b521c7afb1a116..9e754469d917481db367d2afb67e014866b28c1e 100644 --- a/src/main/java/net/floodlightcontroller/perfmon/PktInProcessingTime.java +++ b/src/main/java/net/floodlightcontroller/perfmon/PktInProcessingTime.java @@ -37,8 +37,8 @@ import net.floodlightcontroller.core.module.IFloodlightModule; import net.floodlightcontroller.core.module.IFloodlightService; import net.floodlightcontroller.restserver.IRestApiService; -import org.openflow.protocol.OFMessage; -import org.openflow.protocol.OFType; +import org.projectfloodlight.openflow.protocol.OFMessage; +import org.projectfloodlight.openflow.protocol.OFType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/src/main/java/net/floodlightcontroller/routing/BroadcastTree.java b/src/main/java/net/floodlightcontroller/routing/BroadcastTree.java index 0c3703c97f9b3f8f07002203a0a7946c776cf0e7..28ca79f843a5f4984ab25eb012e95141c062c2bb 100644 --- a/src/main/java/net/floodlightcontroller/routing/BroadcastTree.java +++ b/src/main/java/net/floodlightcontroller/routing/BroadcastTree.java @@ -20,48 +20,48 @@ import java.util.HashMap; import net.floodlightcontroller.routing.Link; -import org.openflow.util.HexString; +import org.projectfloodlight.openflow.types.DatapathId; public class BroadcastTree { - protected HashMap<Long, Link> links; - protected HashMap<Long, Integer> costs; + protected HashMap<DatapathId, Link> links; + protected HashMap<DatapathId, Integer> costs; public BroadcastTree() { - links = new HashMap<Long, Link>(); - costs = new HashMap<Long, Integer>(); + links = new HashMap<DatapathId, Link>(); + costs = new HashMap<DatapathId, Integer>(); } - public BroadcastTree(HashMap<Long, Link> links, HashMap<Long, Integer> costs) { + public BroadcastTree(HashMap<DatapathId, Link> links, HashMap<DatapathId, Integer> costs) { this.links = links; this.costs = costs; } - public Link getTreeLink(long node) { + public Link getTreeLink(DatapathId node) { return links.get(node); } - public int getCost(long node) { + public int getCost(DatapathId node) { if (costs.get(node) == null) return -1; return (costs.get(node)); } - public HashMap<Long, Link> getLinks() { + public HashMap<DatapathId, Link> getLinks() { return links; } - public void addTreeLink(long myNode, Link link) { + public void addTreeLink(DatapathId myNode, Link link) { links.put(myNode, link); } public String toString() { StringBuffer sb = new StringBuffer(); - for(long n: links.keySet()) { - sb.append("[" + HexString.toHexString(n) + ": cost=" + costs.get(n) + ", " + links.get(n) + "]"); + for(DatapathId n: links.keySet()) { + sb.append("[" + n.toString() + ": cost=" + costs.get(n) + ", " + links.get(n) + "]"); } return sb.toString(); } - public HashMap<Long, Integer> getCosts() { + public HashMap<DatapathId, Integer> getCosts() { return costs; } } diff --git a/src/main/java/net/floodlightcontroller/routing/ForwardingBase.java b/src/main/java/net/floodlightcontroller/routing/ForwardingBase.java index 2ef42197bd8a3a6a42c1b76df0682e37db5bf10e..878db1c99116ab3e4249d6c4a75a473a165a2077 100644 --- a/src/main/java/net/floodlightcontroller/routing/ForwardingBase.java +++ b/src/main/java/net/floodlightcontroller/routing/ForwardingBase.java @@ -32,11 +32,11 @@ import net.floodlightcontroller.core.IOFSwitch; import net.floodlightcontroller.core.annotations.LogMessageCategory; import net.floodlightcontroller.core.annotations.LogMessageDoc; import net.floodlightcontroller.core.annotations.LogMessageDocs; +import net.floodlightcontroller.core.internal.IOFSwitchService; import net.floodlightcontroller.core.util.AppCookie; -import net.floodlightcontroller.counter.ICounterStoreService; +import net.floodlightcontroller.debugcounter.IDebugCounterService; import net.floodlightcontroller.devicemanager.IDeviceService; import net.floodlightcontroller.devicemanager.SwitchPort; -import net.floodlightcontroller.packet.Ethernet; import net.floodlightcontroller.packet.IPacket; import net.floodlightcontroller.routing.IRoutingService; import net.floodlightcontroller.routing.IRoutingDecision; @@ -46,14 +46,22 @@ import net.floodlightcontroller.topology.NodePortTuple; import net.floodlightcontroller.util.OFMessageDamper; import net.floodlightcontroller.util.TimedCache; -import org.openflow.protocol.OFFlowMod; -import org.openflow.protocol.OFMatch; -import org.openflow.protocol.OFMessage; -import org.openflow.protocol.OFPacketIn; -import org.openflow.protocol.OFPacketOut; -import org.openflow.protocol.OFType; -import org.openflow.protocol.action.OFAction; -import org.openflow.protocol.action.OFActionOutput; +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.OFMessage; +import org.projectfloodlight.openflow.protocol.OFPacketIn; +import org.projectfloodlight.openflow.protocol.OFPacketOut; +import org.projectfloodlight.openflow.protocol.OFType; +import org.projectfloodlight.openflow.protocol.OFVersion; +import org.projectfloodlight.openflow.protocol.action.OFAction; +import org.projectfloodlight.openflow.protocol.action.OFActionOutput; +import org.projectfloodlight.openflow.types.DatapathId; +import org.projectfloodlight.openflow.types.MacAddress; +import org.projectfloodlight.openflow.types.OFBufferId; +import org.projectfloodlight.openflow.types.OFPort; +import org.projectfloodlight.openflow.types.U64; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -63,553 +71,445 @@ import org.slf4j.LoggerFactory; * decision. */ @LogMessageCategory("Flow Programming") -public abstract class ForwardingBase - implements IOFMessageListener { - - protected static Logger log = - LoggerFactory.getLogger(ForwardingBase.class); - - protected static int OFMESSAGE_DAMPER_CAPACITY = 10000; // TODO: find sweet spot - protected static int OFMESSAGE_DAMPER_TIMEOUT = 250; // ms - - public static short FLOWMOD_DEFAULT_IDLE_TIMEOUT = 5; // in seconds - public static short FLOWMOD_DEFAULT_HARD_TIMEOUT = 0; // infinite - - public static final short FLOWMOD_DEFAULT_IDLE_TIMEOUT_CONSTANT = 5; - public static final short FLOWMOD_DEFAULT_HARD_TIMEOUT_CONSTANT = 0; - - protected IFloodlightProviderService floodlightProvider; - protected IDeviceService deviceManager; - protected IRoutingService routingEngine; - protected ITopologyService topology; - protected ICounterStoreService counterStore; - - protected OFMessageDamper messageDamper; - - // for broadcast loop suppression - protected boolean broadcastCacheFeature = true; - public final int prime1 = 2633; // for hash calculation - public final static int prime2 = 4357; // for hash calculation - public TimedCache<Long> broadcastCache = - new TimedCache<Long>(100, 5*1000); // 5 seconds interval; - - // flow-mod - for use in the cookie - public static final int FORWARDING_APP_ID = 2; // TODO: This must be managed - // by a global APP_ID class - static { - AppCookie.registerApp(FORWARDING_APP_ID, "Forwarding"); - } - public static final long appCookie = AppCookie.makeCookie(FORWARDING_APP_ID, 0); - - // Comparator for sorting by SwitchCluster - public Comparator<SwitchPort> clusterIdComparator = - new Comparator<SwitchPort>() { - @Override - public int compare(SwitchPort d1, SwitchPort d2) { - Long d1ClusterId = - topology.getL2DomainId(d1.getSwitchDPID()); - Long d2ClusterId = - topology.getL2DomainId(d2.getSwitchDPID()); - return d1ClusterId.compareTo(d2ClusterId); - } - }; - - /** - * init data structures - * - */ - protected void init() { - messageDamper = new OFMessageDamper(OFMESSAGE_DAMPER_CAPACITY, - EnumSet.of(OFType.FLOW_MOD), - OFMESSAGE_DAMPER_TIMEOUT); - } - - /** - * Adds a listener for devicemanager and registers for PacketIns. - */ - protected void startUp() { - floodlightProvider.addOFMessageListener(OFType.PACKET_IN, this); - } - - /** - * Returns the application name "forwarding". - */ - @Override - public String getName() { - return "forwarding"; - } - - /** - * All subclasses must define this function if they want any specific - * forwarding action - * - * @param sw - * Switch that the packet came in from - * @param pi - * The packet that came in - * @param decision - * Any decision made by a policy engine - */ - public abstract Command - processPacketInMessage(IOFSwitch sw, OFPacketIn pi, - IRoutingDecision decision, - FloodlightContext cntx); - - @Override - public Command receive(IOFSwitch sw, OFMessage msg, - FloodlightContext cntx) { - switch (msg.getType()) { - case PACKET_IN: - IRoutingDecision decision = null; - if (cntx != null) - decision = - IRoutingDecision.rtStore.get(cntx, - IRoutingDecision.CONTEXT_DECISION); - - return this.processPacketInMessage(sw, - (OFPacketIn) msg, - decision, - cntx); - default: - break; - } - return Command.CONTINUE; - } - - /** - * Push routes from back to front - * @param route Route to push - * @param match OpenFlow fields to match on - * @param srcSwPort Source switch port for the first hop - * @param dstSwPort Destination switch port for final hop - * @param cookie The cookie to set in each flow_mod - * @param cntx The floodlight context - * @param reqeustFlowRemovedNotifn if set to true then the switch would - * send a flow mod removal notification when the flow mod expires - * @param doFlush if set to true then the flow mod would be immediately - * written to the switch - * @param flowModCommand flow mod. command to use, e.g. OFFlowMod.OFPFC_ADD, - * OFFlowMod.OFPFC_MODIFY etc. - * @return srcSwitchIincluded True if the source switch is included in this route - */ - @LogMessageDocs({ - @LogMessageDoc(level="WARN", - message="Unable to push route, switch at DPID {dpid} not available", - explanation="A switch along the calculated path for the " + - "flow has disconnected.", - recommendation=LogMessageDoc.CHECK_SWITCH), - @LogMessageDoc(level="ERROR", - message="Failure writing flow mod", - explanation="An I/O error occurred while writing a " + - "flow modification to a switch", - recommendation=LogMessageDoc.CHECK_SWITCH) - }) - public boolean pushRoute(Route route, OFMatch match, - Integer wildcard_hints, - OFPacketIn pi, - long pinSwitch, - long cookie, - FloodlightContext cntx, - boolean reqeustFlowRemovedNotifn, - boolean doFlush, - short flowModCommand) { - - boolean srcSwitchIncluded = false; - OFFlowMod fm = - (OFFlowMod) floodlightProvider.getOFMessageFactory() - .getMessage(OFType.FLOW_MOD); - OFActionOutput action = new OFActionOutput(); - action.setMaxLength((short)0xffff); - List<OFAction> actions = new ArrayList<OFAction>(); - actions.add(action); - - fm.setIdleTimeout(FLOWMOD_DEFAULT_IDLE_TIMEOUT) - .setHardTimeout(FLOWMOD_DEFAULT_HARD_TIMEOUT) - .setBufferId(OFPacketOut.BUFFER_ID_NONE) - .setCookie(cookie) - .setCommand(flowModCommand) - .setMatch(match) - .setActions(actions) - .setLengthU(OFFlowMod.MINIMUM_LENGTH+OFActionOutput.MINIMUM_LENGTH); - - List<NodePortTuple> switchPortList = route.getPath(); - - for (int indx = switchPortList.size()-1; indx > 0; indx -= 2) { - // indx and indx-1 will always have the same switch DPID. - long switchDPID = switchPortList.get(indx).getNodeId(); - IOFSwitch sw = floodlightProvider.getSwitch(switchDPID); - if (sw == null) { - if (log.isWarnEnabled()) { - log.warn("Unable to push route, switch at DPID {} " + - "not available", switchDPID); - } - return srcSwitchIncluded; - } - - // set the match. - fm.setMatch(wildcard(match, sw, wildcard_hints)); - - // set buffer id if it is the source switch - if (1 == indx) { - // Set the flag to request flow-mod removal notifications only for the - // source switch. The removal message is used to maintain the flow - // cache. Don't set the flag for ARP messages - TODO generalize check - if ((reqeustFlowRemovedNotifn) - && (match.getDataLayerType() != Ethernet.TYPE_ARP)) { - /**with new flow cache design, we don't need the flow removal message from switch anymore - fm.setFlags(OFFlowMod.OFPFF_SEND_FLOW_REM); - */ - match.setWildcards(fm.getMatch().getWildcards()); - } - } - - short outPort = switchPortList.get(indx).getPortId(); - short inPort = switchPortList.get(indx-1).getPortId(); - // set input and output ports on the switch - fm.getMatch().setInputPort(inPort); - ((OFActionOutput)fm.getActions().get(0)).setPort(outPort); - - try { - counterStore.updatePktOutFMCounterStoreLocal(sw, fm); - if (log.isTraceEnabled()) { - log.trace("Pushing Route flowmod routeIndx={} " + - "sw={} inPort={} outPort={}", - new Object[] {indx, - sw, - fm.getMatch().getInputPort(), - outPort }); - } - messageDamper.write(sw, fm, cntx); - if (doFlush) { - sw.flush(); - counterStore.updateFlush(); - } - - // Push the packet out the source switch - if (sw.getId() == pinSwitch) { - // TODO: Instead of doing a packetOut here we could also - // send a flowMod with bufferId set.... - pushPacket(sw, pi, false, outPort, cntx); - srcSwitchIncluded = true; - } - } catch (IOException e) { - log.error("Failure writing flow mod", e); - } - - try { - fm = fm.clone(); - } catch (CloneNotSupportedException e) { - log.error("Failure cloning flow mod", e); - } - } - - return srcSwitchIncluded; - } - - protected OFMatch wildcard(OFMatch match, IOFSwitch sw, - Integer wildcard_hints) { - if (wildcard_hints != null) { - return match.clone().setWildcards(wildcard_hints.intValue()); - } - return match.clone(); - } - - /** - * Pushes a packet-out to a switch. If bufferId != BUFFER_ID_NONE we - * assume that the packetOut switch is the same as the packetIn switch - * and we will use the bufferId. In this case the packet can be null - * Caller needs to make sure that inPort and outPort differs - * @param packet packet data to send. - * @param sw switch from which packet-out is sent - * @param bufferId bufferId - * @param inPort input port - * @param outPort output port - * @param cntx context of the packet - * @param flush force to flush the packet. - */ - @LogMessageDocs({ - @LogMessageDoc(level="ERROR", - message="BufferId is not and packet data is null. " + - "Cannot send packetOut. " + - "srcSwitch={dpid} inPort={port} outPort={port}", - explanation="The switch send a malformed packet-in." + - "The packet will be dropped", - recommendation=LogMessageDoc.REPORT_SWITCH_BUG), - @LogMessageDoc(level="ERROR", - message="Failure writing packet out", - explanation="An I/O error occurred while writing a " + - "packet out to a switch", - recommendation=LogMessageDoc.CHECK_SWITCH) - }) - - /** - * Pushes a packet-out to a switch. The assumption here is that - * the packet-in was also generated from the same switch. Thus, if the input - * port of the packet-in and the outport are the same, the function will not - * push the packet-out. - * @param sw switch that generated the packet-in, and from which packet-out is sent - * @param pi packet-in - * @param useBufferId if true, use the bufferId from the packet in and - * do not add the packetIn's payload. If false set bufferId to - * BUFFER_ID_NONE and use the packetIn's payload - * @param outport output port - * @param cntx context of the packet - */ - protected void pushPacket(IOFSwitch sw, OFPacketIn pi, - boolean useBufferId, - short outport, FloodlightContext cntx) { - - if (pi == null) { - return; - } - - // The assumption here is (sw) is the switch that generated the - // packet-in. If the input port is the same as output port, then - // the packet-out should be ignored. - if (pi.getInPort() == outport) { - if (log.isDebugEnabled()) { - log.debug("Attempting to do packet-out to the same " + - "interface as packet-in. Dropping packet. " + - " SrcSwitch={}, pi={}", - new Object[]{sw, pi}); - return; - } - } - - if (log.isTraceEnabled()) { - log.trace("PacketOut srcSwitch={} pi={}", - new Object[] {sw, pi}); - } - - OFPacketOut po = - (OFPacketOut) floodlightProvider.getOFMessageFactory() - .getMessage(OFType.PACKET_OUT); - - // set actions - List<OFAction> actions = new ArrayList<OFAction>(); - actions.add(new OFActionOutput(outport, (short) 0xffff)); - - po.setActions(actions) - .setActionsLength((short) OFActionOutput.MINIMUM_LENGTH); - short poLength = - (short) (po.getActionsLength() + OFPacketOut.MINIMUM_LENGTH); - - if (useBufferId) { - po.setBufferId(pi.getBufferId()); - } else { - po.setBufferId(OFPacketOut.BUFFER_ID_NONE); - } - - if (po.getBufferId() == OFPacketOut.BUFFER_ID_NONE) { - byte[] packetData = pi.getPacketData(); - poLength += packetData.length; - po.setPacketData(packetData); - } - - po.setInPort(pi.getInPort()); - po.setLength(poLength); - - try { - counterStore.updatePktOutFMCounterStoreLocal(sw, po); - messageDamper.write(sw, po, cntx); - } catch (IOException e) { - log.error("Failure writing packet out", e); - } - } - - - /** - * Write packetout message to sw with output actions to one or more - * output ports with inPort/outPorts passed in. - * @param packetData - * @param sw - * @param inPort - * @param ports - * @param cntx - */ - public void packetOutMultiPort(byte[] packetData, - IOFSwitch sw, - short inPort, - Set<Integer> outPorts, - FloodlightContext cntx) { - //setting actions - List<OFAction> actions = new ArrayList<OFAction>(); - - Iterator<Integer> j = outPorts.iterator(); - - while (j.hasNext()) - { - actions.add(new OFActionOutput(j.next().shortValue(), - (short) 0)); - } - - OFPacketOut po = - (OFPacketOut) floodlightProvider.getOFMessageFactory(). - getMessage(OFType.PACKET_OUT); - po.setActions(actions); - po.setActionsLength((short) (OFActionOutput.MINIMUM_LENGTH * - outPorts.size())); - - // set buffer-id to BUFFER_ID_NONE, and set in-port to OFPP_NONE - po.setBufferId(OFPacketOut.BUFFER_ID_NONE); - po.setInPort(inPort); - - // data (note buffer_id is always BUFFER_ID_NONE) and length - short poLength = (short)(po.getActionsLength() + - OFPacketOut.MINIMUM_LENGTH); - poLength += packetData.length; - po.setPacketData(packetData); - po.setLength(poLength); - - try { - counterStore.updatePktOutFMCounterStoreLocal(sw, po); - if (log.isTraceEnabled()) { - log.trace("write broadcast packet on switch-id={} " + - "interfaces={} packet-out={}", - new Object[] {sw.getId(), outPorts, po}); - } - messageDamper.write(sw, po, cntx); - - } catch (IOException e) { - log.error("Failure writing packet out", e); - } - } - - /** - * @see packetOutMultiPort - * Accepts a PacketIn instead of raw packet data. Note that the inPort - * and switch can be different than the packet in switch/port - */ - public void packetOutMultiPort(OFPacketIn pi, - IOFSwitch sw, - short inPort, - Set<Integer> outPorts, - FloodlightContext cntx) { - packetOutMultiPort(pi.getPacketData(), sw, inPort, outPorts, cntx); - } - - /** - * @see packetOutMultiPort - * Accepts an IPacket instead of raw packet data. Note that the inPort - * and switch can be different than the packet in switch/port - */ - public void packetOutMultiPort(IPacket packet, - IOFSwitch sw, - short inPort, - Set<Integer> outPorts, - FloodlightContext cntx) { - packetOutMultiPort(packet.serialize(), sw, inPort, outPorts, cntx); - } - - protected boolean isInBroadcastCache(IOFSwitch sw, OFPacketIn pi, - FloodlightContext cntx) { - // Get the cluster id of the switch. - // Get the hash of the Ethernet packet. - if (sw == null) return true; - - // If the feature is disabled, always return false; - if (!broadcastCacheFeature) return false; - - Ethernet eth = - IFloodlightProviderService.bcStore.get(cntx, - IFloodlightProviderService.CONTEXT_PI_PAYLOAD); - - Long broadcastHash; - broadcastHash = topology.getL2DomainId(sw.getId()) * prime1 + - pi.getInPort() * prime2 + eth.hashCode(); - if (broadcastCache.update(broadcastHash)) { - sw.updateBroadcastCache(broadcastHash, pi.getInPort()); - return true; - } else { - return false; - } - } - - protected boolean isInSwitchBroadcastCache(IOFSwitch sw, OFPacketIn pi, FloodlightContext cntx) { - if (sw == null) return true; - - // If the feature is disabled, always return false; - if (!broadcastCacheFeature) return false; - - // Get the hash of the Ethernet packet. - Ethernet eth = - IFloodlightProviderService.bcStore.get(cntx, IFloodlightProviderService.CONTEXT_PI_PAYLOAD); - - long hash = pi.getInPort() * prime2 + eth.hashCode(); - - // some FORWARD_OR_FLOOD packets are unicast with unknown destination mac - return sw.updateBroadcastCache(hash, pi.getInPort()); - } - - @LogMessageDocs({ - @LogMessageDoc(level="ERROR", - message="Failure writing deny flow mod", - explanation="An I/O error occurred while writing a " + - "deny flow mod to a switch", - recommendation=LogMessageDoc.CHECK_SWITCH) - }) - public static boolean - blockHost(IFloodlightProviderService floodlightProvider, - SwitchPort sw_tup, long host_mac, - short hardTimeout, long cookie) { - - if (sw_tup == null) { - return false; - } - - IOFSwitch sw = - floodlightProvider.getSwitch(sw_tup.getSwitchDPID()); - if (sw == null) return false; - int inputPort = sw_tup.getPort(); - log.debug("blockHost sw={} port={} mac={}", - new Object[] { sw, sw_tup.getPort(), Long.valueOf(host_mac) }); - - // Create flow-mod based on packet-in and src-switch - OFFlowMod fm = - (OFFlowMod) floodlightProvider.getOFMessageFactory() - .getMessage(OFType.FLOW_MOD); - OFMatch match = new OFMatch(); - List<OFAction> actions = new ArrayList<OFAction>(); // Set no action to - // drop - match.setInputPort((short)inputPort); - if (host_mac != -1L) { - match.setDataLayerSource(Ethernet.toByteArray(host_mac)) - .setWildcards(OFMatch.OFPFW_ALL & ~OFMatch.OFPFW_DL_SRC - & ~OFMatch.OFPFW_IN_PORT); - } else { - match.setWildcards(OFMatch.OFPFW_ALL & ~OFMatch.OFPFW_IN_PORT); - } - fm.setCookie(cookie) - .setHardTimeout(hardTimeout) - .setIdleTimeout(FLOWMOD_DEFAULT_IDLE_TIMEOUT) - .setBufferId(OFPacketOut.BUFFER_ID_NONE) - .setMatch(match) - .setActions(actions) - .setLengthU(OFFlowMod.MINIMUM_LENGTH); // +OFActionOutput.MINIMUM_LENGTH); - - try { - log.debug("write drop flow-mod sw={} match={} flow-mod={}", - new Object[] { sw, match, fm }); - // TODO: can't use the message damper sine this method is static - sw.write(fm, null); - } catch (IOException e) { - log.error("Failure writing deny flow mod", e); - return false; - } - return true; - - } - - @Override - public boolean isCallbackOrderingPrereq(OFType type, String name) { - return (type.equals(OFType.PACKET_IN) && - (name.equals("topology") || - name.equals("devicemanager"))); - } - - @Override - public boolean isCallbackOrderingPostreq(OFType type, String name) { - return false; - } +public abstract class ForwardingBase implements IOFMessageListener { + + protected static Logger log = + LoggerFactory.getLogger(ForwardingBase.class); + + protected static int OFMESSAGE_DAMPER_CAPACITY = 10000; // TODO: find sweet spot + protected static int OFMESSAGE_DAMPER_TIMEOUT = 250; // ms + + public static short FLOWMOD_DEFAULT_IDLE_TIMEOUT = 5; // in seconds + public static short FLOWMOD_DEFAULT_HARD_TIMEOUT = 0; // infinite + + public static final short FLOWMOD_DEFAULT_IDLE_TIMEOUT_CONSTANT = 5; + public static final short FLOWMOD_DEFAULT_HARD_TIMEOUT_CONSTANT = 0; + + protected IFloodlightProviderService floodlightProviderService; + protected IOFSwitchService switchService; + protected IDeviceService deviceManagerService; + protected IRoutingService routingEngineService; + protected ITopologyService topologyService; + protected IDebugCounterService debugCounterService; + + protected OFMessageDamper messageDamper; + + // for broadcast loop suppression + protected boolean broadcastCacheFeature = true; + public final int prime1 = 2633; // for hash calculation + public final static int prime2 = 4357; // for hash calculation + public TimedCache<Long> broadcastCache = new TimedCache<Long>(100, 5*1000); // 5 seconds interval; + + // flow-mod - for use in the cookie + public static final int FORWARDING_APP_ID = 2; // TODO: This must be managed + // by a global APP_ID class + static { + AppCookie.registerApp(FORWARDING_APP_ID, "Forwarding"); + } + public static final U64 appCookie = AppCookie.makeCookie(FORWARDING_APP_ID, 0); + + // Comparator for sorting by SwitchCluster + public Comparator<SwitchPort> clusterIdComparator = + new Comparator<SwitchPort>() { + @Override + public int compare(SwitchPort d1, SwitchPort d2) { + DatapathId d1ClusterId = topologyService.getL2DomainId(d1.getSwitchDPID()); + DatapathId d2ClusterId = topologyService.getL2DomainId(d2.getSwitchDPID()); + return d1ClusterId.compareTo(d2ClusterId); + } + }; + + /** + * init data structures + * + */ + protected void init() { + messageDamper = new OFMessageDamper(OFMESSAGE_DAMPER_CAPACITY, + EnumSet.of(OFType.FLOW_MOD), + OFMESSAGE_DAMPER_TIMEOUT); + + } + + /** + * Adds a listener for devicemanager and registers for PacketIns. + */ + protected void startUp() { + floodlightProviderService.addOFMessageListener(OFType.PACKET_IN, this); + } + + /** + * Returns the application name "forwarding". + */ + @Override + public String getName() { + return "forwarding"; + } + + /** + * All subclasses must define this function if they want any specific + * forwarding action + * + * @param sw + * Switch that the packet came in from + * @param pi + * The packet that came in + * @param decision + * Any decision made by a policy engine + */ + public abstract Command processPacketInMessage(IOFSwitch sw, OFPacketIn pi, + IRoutingDecision decision, FloodlightContext cntx); + + @Override + public Command receive(IOFSwitch sw, OFMessage msg, FloodlightContext cntx) { + switch (msg.getType()) { + case PACKET_IN: + IRoutingDecision decision = null; + if (cntx != null) { + decision = RoutingDecision.rtStore.get(cntx, IRoutingDecision.CONTEXT_DECISION); + } + + return this.processPacketInMessage(sw, (OFPacketIn) msg, decision, cntx); + default: + break; + } + return Command.CONTINUE; + } + + /** + * Push routes from back to front + * @param route Route to push + * @param match OpenFlow fields to match on + * @param srcSwPort Source switch port for the first hop + * @param dstSwPort Destination switch port for final hop + * @param cookie The cookie to set in each flow_mod + * @param cntx The floodlight context + * @param reqeustFlowRemovedNotifn if set to true then the switch would + * send a flow mod removal notification when the flow mod expires + * @param doFlush if set to true then the flow mod would be immediately + * written to the switch + * @param flowModCommand flow mod. command to use, e.g. OFFlowMod.OFPFC_ADD, + * OFFlowMod.OFPFC_MODIFY etc. + * @return srcSwitchIincluded True if the source switch is included in this route + */ + @LogMessageDocs({ + @LogMessageDoc(level="WARN", + message="Unable to push route, switch at DPID {dpid} not available", + explanation="A switch along the calculated path for the " + + "flow has disconnected.", + recommendation=LogMessageDoc.CHECK_SWITCH), + @LogMessageDoc(level="ERROR", + message="Failure writing flow mod", + explanation="An I/O error occurred while writing a " + + "flow modification to a switch", + recommendation=LogMessageDoc.CHECK_SWITCH) + }) + public boolean pushRoute(Route route, Match match, OFPacketIn pi, + DatapathId pinSwitch, U64 cookie, FloodlightContext cntx, + boolean reqeustFlowRemovedNotifn, boolean doFlush, + OFFlowModCommand flowModCommand) { + + boolean srcSwitchIncluded = false; + + List<NodePortTuple> switchPortList = route.getPath(); + + for (int indx = switchPortList.size() - 1; indx > 0; indx -= 2) { + // indx and indx-1 will always have the same switch DPID. + DatapathId switchDPID = switchPortList.get(indx).getNodeId(); + IOFSwitch sw = switchService.getSwitch(switchDPID); + + if (sw == null) { + if (log.isWarnEnabled()) { + log.warn("Unable to push route, switch at DPID {} " + "not available", switchDPID); + } + return srcSwitchIncluded; + } + + // need to build flow mod based on what type it is. Cannot set command later + OFFlowMod.Builder fmb; + switch (flowModCommand) { + case ADD: + fmb = sw.getOFFactory().buildFlowAdd(); + break; + case DELETE: + fmb = sw.getOFFactory().buildFlowDelete(); + break; + case DELETE_STRICT: + fmb = sw.getOFFactory().buildFlowDeleteStrict(); + break; + case MODIFY: + fmb = sw.getOFFactory().buildFlowModify(); + break; + default: + log.error("Could not decode OFFlowModCommand. Using MODIFY_STRICT. (Should another be used as the default?)"); + case MODIFY_STRICT: + fmb = sw.getOFFactory().buildFlowModifyStrict(); + break; + } + + OFActionOutput.Builder aob = sw.getOFFactory().actions().buildOutput(); + List<OFAction> actions = new ArrayList<OFAction>(); + //Match.Builder mb = match.createBuilder(); + + // set input and output ports on the switch + OFPort outPort = switchPortList.get(indx).getPortId(); + //OFPort inPort = switchPortList.get(indx - 1).getPortId(); + //mb.setExact(MatchField.IN_PORT, inPort); + aob.setPort(outPort); + aob.setMaxLen(Integer.MAX_VALUE); + actions.add(aob.build()); + + // compile + fmb.setMatch(match) //mb.build() + .setActions(actions) + .setIdleTimeout(FLOWMOD_DEFAULT_IDLE_TIMEOUT) + .setHardTimeout(FLOWMOD_DEFAULT_HARD_TIMEOUT) + .setBufferId(OFBufferId.NO_BUFFER) + .setCookie(cookie) + .setOutPort(outPort); // TODO @Ryan why does this need to be set in addition to the action??? + + try { + //TODO @Ryan counterStore.updatePktOutFMCounterStoreLocal(sw, fm); + if (log.isTraceEnabled()) { + log.trace("Pushing Route flowmod routeIndx={} " + + "sw={} inPort={} outPort={}", + new Object[] {indx, + sw, + fmb.getMatch().get(MatchField.IN_PORT), + outPort }); + } + messageDamper.write(sw, fmb.build(), cntx); + if (doFlush) { + sw.flush(); + //TODO @Ryan counterStore.updateFlush(); + } + + // Push the packet out the source switch + if (sw.getId().equals(pinSwitch)) { + // TODO: Instead of doing a packetOut here we could also + // send a flowMod with bufferId set.... + pushPacket(sw, pi, false, outPort, cntx); + srcSwitchIncluded = true; + } + } catch (IOException e) { + log.error("Failure writing flow mod", e); + } + } + + return srcSwitchIncluded; + } + + /** + * Pushes a packet-out to a switch. If bufferId != BUFFER_ID_NONE we + * assume that the packetOut switch is the same as the packetIn switch + * and we will use the bufferId. In this case the packet can be null + * Caller needs to make sure that inPort and outPort differs + * @param packet packet data to send. + * @param sw switch from which packet-out is sent + * @param bufferId bufferId + * @param inPort input port + * @param outPort output port + * @param cntx context of the packet + * @param flush force to flush the packet. + */ + @LogMessageDocs({ + @LogMessageDoc(level="ERROR", + message="BufferId is not and packet data is null. " + + "Cannot send packetOut. " + + "srcSwitch={dpid} inPort={port} outPort={port}", + explanation="The switch send a malformed packet-in." + + "The packet will be dropped", + recommendation=LogMessageDoc.REPORT_SWITCH_BUG), + @LogMessageDoc(level="ERROR", + message="Failure writing packet out", + explanation="An I/O error occurred while writing a " + + "packet out to a switch", + recommendation=LogMessageDoc.CHECK_SWITCH) + }) + + /** + * Pushes a packet-out to a switch. The assumption here is that + * the packet-in was also generated from the same switch. Thus, if the input + * port of the packet-in and the outport are the same, the function will not + * push the packet-out. + * @param sw switch that generated the packet-in, and from which packet-out is sent + * @param pi packet-in + * @param useBufferId if true, use the bufferId from the packet in and + * do not add the packetIn's payload. If false set bufferId to + * BUFFER_ID_NONE and use the packetIn's payload + * @param outport output port + * @param cntx context of the packet + */ + protected void pushPacket(IOFSwitch sw, OFPacketIn pi, boolean useBufferId, + OFPort outport, FloodlightContext cntx) { + + if (pi == null) { + return; + } + + // The assumption here is (sw) is the switch that generated the + // packet-in. If the input port is the same as output port, then + // the packet-out should be ignored. + if ((pi.getVersion().compareTo(OFVersion.OF_13) < 0 ? pi.getInPort() : pi.getMatch().get(MatchField.IN_PORT)).equals(outport)) { + if (log.isDebugEnabled()) { + log.debug("Attempting to do packet-out to the same " + + "interface as packet-in. Dropping packet. " + + " SrcSwitch={}, pi={}", + new Object[]{sw, pi}); + return; + } + } + + if (log.isTraceEnabled()) { + log.trace("PacketOut srcSwitch={} pi={}", + new Object[] {sw, pi}); + } + + OFPacketOut.Builder pob = sw.getOFFactory().buildPacketOut(); + // set actions + List<OFAction> actions = new ArrayList<OFAction>(); + actions.add(sw.getOFFactory().actions().output(outport, Integer.MAX_VALUE)); + pob.setActions(actions); + + if (useBufferId) { + pob.setBufferId(pi.getBufferId()); + } else { + pob.setBufferId(OFBufferId.NO_BUFFER); + } + + if (pob.getBufferId() == OFBufferId.NO_BUFFER) { + byte[] packetData = pi.getData(); + pob.setData(packetData); + } + + pob.setInPort((pi.getVersion().compareTo(OFVersion.OF_13) < 0 ? pi.getInPort() : pi.getMatch().get(MatchField.IN_PORT))); + + try { + //TODO @Ryan counterStore.updatePktOutFMCounterStoreLocal(sw, po); + messageDamper.write(sw, pob.build(), cntx); + } catch (IOException e) { + log.error("Failure writing packet out", e); + } + } + + + /** + * Write packetout message to sw with output actions to one or more + * output ports with inPort/outPorts passed in. + * @param packetData + * @param sw + * @param inPort + * @param ports + * @param cntx + */ + public void packetOutMultiPort(byte[] packetData, IOFSwitch sw, + OFPort inPort, Set<OFPort> outPorts, FloodlightContext cntx) { + //setting actions + List<OFAction> actions = new ArrayList<OFAction>(); + + Iterator<OFPort> j = outPorts.iterator(); + + while (j.hasNext()) { + actions.add(sw.getOFFactory().actions().output(j.next(), 0)); + } + + OFPacketOut.Builder pob = sw.getOFFactory().buildPacketOut(); + pob.setActions(actions); + + pob.setBufferId(OFBufferId.NO_BUFFER); + pob.setInPort(inPort); + + pob.setData(packetData); + + try { + //TODO @Ryan counterStore.updatePktOutFMCounterStoreLocal(sw, po); + if (log.isTraceEnabled()) { + log.trace("write broadcast packet on switch-id={} " + + "interfaces={} packet-out={}", + new Object[] {sw.getId(), outPorts, pob.build()}); + } + messageDamper.write(sw, pob.build(), cntx); + + } catch (IOException e) { + log.error("Failure writing packet out", e); + } + } + + /** + * @see packetOutMultiPort + * Accepts a PacketIn instead of raw packet data. Note that the inPort + * and switch can be different than the packet in switch/port + */ + public void packetOutMultiPort(OFPacketIn pi, IOFSwitch sw, + OFPort inPort, Set<OFPort> outPorts, FloodlightContext cntx) { + packetOutMultiPort(pi.getData(), sw, inPort, outPorts, cntx); + } + + /** + * @see packetOutMultiPort + * Accepts an IPacket instead of raw packet data. Note that the inPort + * and switch can be different than the packet in switch/port + */ + public void packetOutMultiPort(IPacket packet, IOFSwitch sw, + OFPort inPort, Set<OFPort> outPorts, FloodlightContext cntx) { + packetOutMultiPort(packet.serialize(), sw, inPort, outPorts, cntx); + } + + @LogMessageDocs({ + @LogMessageDoc(level="ERROR", + message="Failure writing deny flow mod", + explanation="An I/O error occurred while writing a " + + "deny flow mod to a switch", + recommendation=LogMessageDoc.CHECK_SWITCH) + }) + public static boolean blockHost(IOFSwitchService switchService, + SwitchPort sw_tup, MacAddress host_mac, short hardTimeout, U64 cookie) { + + if (sw_tup == null) { + return false; + } + + IOFSwitch sw = switchService.getSwitch(sw_tup.getSwitchDPID()); + if (sw == null) { + return false; + } + + OFPort inputPort = sw_tup.getPort(); + log.debug("blockHost sw={} port={} mac={}", + new Object[] { sw, sw_tup.getPort(), host_mac.getLong() }); + + // Create flow-mod based on packet-in and src-switch + OFFlowMod.Builder fmb = sw.getOFFactory().buildFlowAdd(); + + Match.Builder mb = sw.getOFFactory().buildMatch(); + List<OFAction> actions = new ArrayList<OFAction>(); // Set no action to drop + mb.setExact(MatchField.IN_PORT, inputPort); + if (host_mac.getLong() != -1L) { + mb.setExact(MatchField.ETH_SRC, host_mac); + } + + fmb.setCookie(cookie) + .setHardTimeout(hardTimeout) + .setIdleTimeout(FLOWMOD_DEFAULT_IDLE_TIMEOUT) + .setBufferId(OFBufferId.NO_BUFFER) + .setMatch(mb.build()) + .setActions(actions); + + log.debug("write drop flow-mod sw={} match={} flow-mod={}", + new Object[] { sw, mb.build(), fmb.build() }); + // TODO: can't use the message damper sine this method is static + sw.write(fmb.build()); + + return true; + } + + @Override + public boolean isCallbackOrderingPrereq(OFType type, String name) { + return (type.equals(OFType.PACKET_IN) && (name.equals("topology") || name.equals("devicemanager"))); + } + + @Override + public boolean isCallbackOrderingPostreq(OFType type, String name) { + return false; + } } diff --git a/src/main/java/net/floodlightcontroller/routing/IRoutingDecision.java b/src/main/java/net/floodlightcontroller/routing/IRoutingDecision.java index ab09375486b9f61b2cfa98b83a5e9eeb483bd20b..bc9df880fd08a4a72f98d9104d09f7245edabfc0 100644 --- a/src/main/java/net/floodlightcontroller/routing/IRoutingDecision.java +++ b/src/main/java/net/floodlightcontroller/routing/IRoutingDecision.java @@ -19,6 +19,8 @@ package net.floodlightcontroller.routing; import java.util.List; +import org.projectfloodlight.openflow.protocol.match.Match; + import net.floodlightcontroller.core.FloodlightContext; import net.floodlightcontroller.core.FloodlightContextStore; import net.floodlightcontroller.devicemanager.IDevice; @@ -57,8 +59,8 @@ public interface IRoutingDecision { public void addDestinationDevice(IDevice d); public List<SwitchPort> getMulticastInterfaces(); public void setMulticastInterfaces(List<SwitchPort> lspt); - public Integer getWildcards(); - public void setWildcards(Integer wildcards); + public Match getMatch(); + public void setMatch(Match match); public short getHardTimeout(); public void setHardTimeout(short hardTimeout); } diff --git a/src/main/java/net/floodlightcontroller/routing/IRoutingService.java b/src/main/java/net/floodlightcontroller/routing/IRoutingService.java index a3d77a83ba6309c02c6f99c19d2523eab2ecfa3b..fde607b89cef1ca00a78e8867748c0c2f4c2c795 100644 --- a/src/main/java/net/floodlightcontroller/routing/IRoutingService.java +++ b/src/main/java/net/floodlightcontroller/routing/IRoutingService.java @@ -19,6 +19,10 @@ package net.floodlightcontroller.routing; import java.util.ArrayList; +import org.projectfloodlight.openflow.types.DatapathId; +import org.projectfloodlight.openflow.types.OFPort; +import org.projectfloodlight.openflow.types.U64; + import net.floodlightcontroller.core.module.IFloodlightService; import net.floodlightcontroller.routing.Route; @@ -33,7 +37,7 @@ public interface IRoutingService extends IFloodlightService { * @param dst Destination switch DPID. * @param cookie cookie (usage determined by implementation; ignored by topology instance now). */ - public Route getRoute(long src, long dst, long cookie); + public Route getRoute(DatapathId src, DatapathId dst, U64 cookie); /** * Provides a route between src and dst, with option to allow or @@ -43,7 +47,7 @@ public interface IRoutingService extends IFloodlightService { * @param cookie cookie (usage determined by implementation; ignored by topology instance now). * @param tunnelEnabled boolean option. */ - public Route getRoute(long src, long dst, long cookie, boolean tunnelEnabled); + public Route getRoute(DatapathId src, DatapathId dst, U64 cookie, boolean tunnelEnabled); /** * Provides a route between srcPort on src and dstPort on dst. @@ -53,8 +57,7 @@ public interface IRoutingService extends IFloodlightService { * @param dstPort dstPort on Destination switch. * @param cookie cookie (usage determined by implementation; ignored by topology instance now). */ - public Route getRoute(long srcId, short srcPort, - long dstId, short dstPort, long cookie); + public Route getRoute(DatapathId srcId, OFPort srcPort, DatapathId dstId, OFPort dstPort, U64 cookie); /** * Provides a route between srcPort on src and dstPort on dst. @@ -65,21 +68,19 @@ public interface IRoutingService extends IFloodlightService { * @param cookie cookie (usage determined by implementation; ignored by topology instance now). * @param tunnelEnabled boolean option. */ - public Route getRoute(long srcId, short srcPort, - long dstId, short dstPort, long cookie, - boolean tunnelEnabled); + public Route getRoute(DatapathId srcId, OFPort srcPort, DatapathId dstId, OFPort dstPort, U64 cookie, boolean tunnelEnabled); /** return all routes, if available */ - public ArrayList<Route> getRoutes(long longSrcDpid, long longDstDpid, boolean tunnelEnabled); + public ArrayList<Route> getRoutes(DatapathId longSrcDpid, DatapathId longDstDpid, boolean tunnelEnabled); /** Check if a route exists between src and dst, including tunnel links * in the path. */ - public boolean routeExists(long src, long dst); + public boolean routeExists(DatapathId src, DatapathId dst); /** Check if a route exists between src and dst, with option to have * or not have tunnels as part of the path. */ - public boolean routeExists(long src, long dst, boolean tunnelEnabled); + public boolean routeExists(DatapathId src, DatapathId dst, boolean tunnelEnabled); } diff --git a/src/main/java/net/floodlightcontroller/routing/Link.java b/src/main/java/net/floodlightcontroller/routing/Link.java index 561ae04c09ba4b4d3849763da46796dff4c03f95..a2f125f5a5b2cd73c9ce6bfaa65e4b56e87d6ff6 100755 --- a/src/main/java/net/floodlightcontroller/routing/Link.java +++ b/src/main/java/net/floodlightcontroller/routing/Link.java @@ -18,34 +18,28 @@ package net.floodlightcontroller.routing; import com.fasterxml.jackson.annotation.JsonProperty; -import org.openflow.util.HexString; + +import org.projectfloodlight.openflow.types.DatapathId; +import org.projectfloodlight.openflow.types.OFPort; public class Link implements Comparable<Link> { @JsonProperty("src-switch") - private long src; + private DatapathId src; @JsonProperty("src-port") - private short srcPort; + private OFPort srcPort; @JsonProperty("dst-switch") - private long dst; + private DatapathId dst; @JsonProperty("dst-port") - private short dstPort; + private OFPort dstPort; - public Link(long srcId, short srcPort, long dstId, short dstPort) { + public Link(DatapathId srcId, OFPort srcPort, DatapathId dstId, OFPort dstPort) { this.src = srcId; this.srcPort = srcPort; this.dst = dstId; this.dstPort = dstPort; } - // Convenience method - public Link(long srcId, int srcPort, long dstId, int dstPort) { - this.src = srcId; - this.srcPort = (short) srcPort; - this.dst = dstId; - this.dstPort = (short) dstPort; - } - /* * Do not use this constructor. Used primarily for JSON * Serialization/Deserialization @@ -54,35 +48,35 @@ public class Link implements Comparable<Link> { super(); } - public long getSrc() { + public DatapathId getSrc() { return src; } - public short getSrcPort() { + public OFPort getSrcPort() { return srcPort; } - public long getDst() { + public DatapathId getDst() { return dst; } - public short getDstPort() { + public OFPort getDstPort() { return dstPort; } - public void setSrc(long src) { + public void setSrc(DatapathId src) { this.src = src; } - public void setSrcPort(short srcPort) { + public void setSrcPort(OFPort srcPort) { this.srcPort = srcPort; } - public void setDst(long dst) { + public void setDst(DatapathId dst) { this.dst = dst; } - public void setDstPort(short dstPort) { + public void setDstPort(OFPort dstPort) { this.dstPort = dstPort; } @@ -90,10 +84,10 @@ public class Link implements Comparable<Link> { public int hashCode() { final int prime = 31; int result = 1; - result = prime * result + (int) (dst ^ (dst >>> 32)); - result = prime * result + dstPort; - result = prime * result + (int) (src ^ (src >>> 32)); - result = prime * result + srcPort; + result = prime * result + (int) (dst.getLong() ^ (dst.getLong() >>> 32)); + result = prime * result + dstPort.getPortNumber(); + result = prime * result + (int) (src.getLong() ^ (src.getLong() >>> 32)); + result = prime * result + srcPort.getPortNumber(); return result; } @@ -120,35 +114,36 @@ public class Link implements Comparable<Link> { @Override public String toString() { - return "Link [src=" + HexString.toHexString(this.src) + return "Link [src=" + this.src.toString() + " outPort=" - + (srcPort & 0xffff) - + ", dst=" + HexString.toHexString(this.dst) + + srcPort.toString() + + ", dst=" + this.dst.toString() + ", inPort=" - + (dstPort & 0xffff) + + dstPort.toString() + "]"; } + //TODO @Ryan there was some short 0xFFFF bitmasking here when ports were shorts. I don't get what that did other than just allow all bits of the short (16), so I just stringified the whole thing public String toKeyString() { - return (HexString.toHexString(this.src) + "|" + - (this.srcPort & 0xffff) + "|" + - HexString.toHexString(this.dst) + "|" + - (this.dstPort & 0xffff) ); + return (this.src.toString() + "|" + + this.srcPort.toString() + "|" + + this.dst.toString() + "|" + + this.dstPort.toString()); } @Override public int compareTo(Link a) { // compare link based on natural ordering - src id, src port, dst id, dst port if (this.getSrc() != a.getSrc()) - return (int) (this.getSrc() - a.getSrc()); + return (int) (this.getSrc().getLong() - a.getSrc().getLong()); if (this.getSrcPort() != a.getSrcPort()) - return (int) (this.getSrc() - a.getSrc()); + return (int) (this.getSrc().getLong() - a.getSrc().getLong()); if (this.getDst() != a.getDst()) - return (int) (this.getDst() - a.getDst()); + return (int) (this.getDst().getLong() - a.getDst().getLong()); - return this.getDstPort() - a.getDstPort(); + return this.getDstPort().getPortNumber() - a.getDstPort().getPortNumber(); } } diff --git a/src/main/java/net/floodlightcontroller/routing/Route.java b/src/main/java/net/floodlightcontroller/routing/Route.java index da00d50b7b82a3d3498b1523543a8558abbd579a..f41a9aaf1e159249e4eb831da30cf80abe993ad5 100755 --- a/src/main/java/net/floodlightcontroller/routing/Route.java +++ b/src/main/java/net/floodlightcontroller/routing/Route.java @@ -20,6 +20,8 @@ package net.floodlightcontroller.routing; import java.util.ArrayList; import java.util.List; +import org.projectfloodlight.openflow.types.DatapathId; + import net.floodlightcontroller.topology.NodePortTuple; /** @@ -39,7 +41,7 @@ public class Route implements Comparable<Route> { this.routeCount = 0; // useful if multipath routing available } - public Route(Long src, Long dst) { + public Route(DatapathId src, DatapathId dst) { super(); this.id = new RouteId(src, dst); this.switchPorts = new ArrayList<NodePortTuple>(); diff --git a/src/main/java/net/floodlightcontroller/routing/RouteId.java b/src/main/java/net/floodlightcontroller/routing/RouteId.java index 511db735969aa849d619783cdd88229d1922b27a..dce125111fc4baaf20342c07d5a991a9a8539b57 100755 --- a/src/main/java/net/floodlightcontroller/routing/RouteId.java +++ b/src/main/java/net/floodlightcontroller/routing/RouteId.java @@ -17,7 +17,8 @@ package net.floodlightcontroller.routing; -import org.openflow.util.HexString; +import org.projectfloodlight.openflow.types.DatapathId; +import org.projectfloodlight.openflow.types.U64; /** * Stores the endpoints of a route, in this case datapath ids @@ -25,46 +26,46 @@ import org.openflow.util.HexString; * @author David Erickson (daviderickson@cs.stanford.edu) */ public class RouteId implements Cloneable, Comparable<RouteId> { - protected Long src; - protected Long dst; - protected long cookie; + protected DatapathId src; + protected DatapathId dst; + protected U64 cookie; - public RouteId(Long src, Long dst) { + public RouteId(DatapathId src, DatapathId dst) { super(); this.src = src; this.dst = dst; - this.cookie = 0; + this.cookie = U64.of(0); } - public RouteId(Long src, Long dst, long cookie) { + public RouteId(DatapathId src, DatapathId dst, U64 cookie) { super(); this.src = src; this.dst = dst; this.cookie = cookie; } - public Long getSrc() { + public DatapathId getSrc() { return src; } - public void setSrc(Long src) { + public void setSrc(DatapathId src) { this.src = src; } - public Long getDst() { + public DatapathId getDst() { return dst; } - public void setDst(Long dst) { + public void setDst(DatapathId dst) { this.dst = dst; } - public long getCookie() { + public U64 getCookie() { return cookie; } public void setCookie(int cookie) { - this.cookie = cookie; + this.cookie = U64.of(cookie); } @Override @@ -73,7 +74,7 @@ public class RouteId implements Cloneable, Comparable<RouteId> { Long result = new Long(1); result = prime * result + ((dst == null) ? 0 : dst.hashCode()); result = prime * result + ((src == null) ? 0 : src.hashCode()); - result = prime * result + cookie; + result = prime * result + cookie.getValue(); // To cope with long cookie, use Long to compute hash then use Long's // built-in hash to produce int hash code return result.hashCode(); @@ -103,8 +104,8 @@ public class RouteId implements Cloneable, Comparable<RouteId> { @Override public String toString() { - return "RouteId [src=" + HexString.toHexString(this.src) + " dst=" - + HexString.toHexString(this.dst) + "]"; + return "RouteId [src=" + this.src.toString() + " dst=" + + this.dst.toString() + "]"; } @Override diff --git a/src/main/java/net/floodlightcontroller/routing/RoutingDecision.java b/src/main/java/net/floodlightcontroller/routing/RoutingDecision.java index aa4b3dde823ab75a38671e53767294d1964012f3..0e1935cab3c31499821845635219598d79b5e2c5 100644 --- a/src/main/java/net/floodlightcontroller/routing/RoutingDecision.java +++ b/src/main/java/net/floodlightcontroller/routing/RoutingDecision.java @@ -20,6 +20,10 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import org.projectfloodlight.openflow.protocol.match.Match; +import org.projectfloodlight.openflow.types.DatapathId; +import org.projectfloodlight.openflow.types.OFPort; + import net.floodlightcontroller.core.FloodlightContext; import net.floodlightcontroller.devicemanager.IDevice; import net.floodlightcontroller.devicemanager.SwitchPort; @@ -28,25 +32,23 @@ import net.floodlightcontroller.devicemanager.SwitchPort; public class RoutingDecision implements IRoutingDecision { protected RoutingAction action; - protected Integer wildcards; + protected Match match; protected short hardTimeout; protected SwitchPort srcPort; protected IDevice srcDevice; protected List<IDevice> destDevices; protected List<SwitchPort> broadcastIntertfaces; - public RoutingDecision(long swDipd, - short inPort, + public RoutingDecision(DatapathId swDipd, + OFPort inPort, IDevice srcDevice, RoutingAction action) { this.srcPort = new SwitchPort(swDipd, inPort); this.srcDevice = srcDevice; - this.destDevices = - Collections.synchronizedList(new ArrayList<IDevice>()); - this.broadcastIntertfaces = - Collections.synchronizedList(new ArrayList<SwitchPort>()); + this.destDevices = Collections.synchronizedList(new ArrayList<IDevice>()); + this.broadcastIntertfaces = Collections.synchronizedList(new ArrayList<SwitchPort>()); this.action = action; - this.wildcards = null; + this.match = null; this.hardTimeout = ForwardingBase.FLOWMOD_DEFAULT_HARD_TIMEOUT; } @@ -93,13 +95,13 @@ public class RoutingDecision implements IRoutingDecision { } @Override - public Integer getWildcards() { - return this.wildcards; + public Match getMatch() { + return this.match; } @Override - public void setWildcards(Integer wildcards) { - this.wildcards = wildcards; + public void setMatch(Match match) { + this.match = match; } @Override @@ -120,6 +122,6 @@ public class RoutingDecision implements IRoutingDecision { public String toString() { return "action " + action + " wildcard " + - ((wildcards == null) ? null : "0x"+Integer.toHexString(wildcards.intValue())); + ((match == null) ? null : match.toString()); } } diff --git a/src/main/java/net/floodlightcontroller/staticflowentry/IStaticFlowEntryPusherService.java b/src/main/java/net/floodlightcontroller/staticflowentry/IStaticFlowEntryPusherService.java index a900b5b34863bfa0a83ad2d9f61251f00123cdd1..22b60197be03a9dbd2063552c78b121de6297620 100644 --- a/src/main/java/net/floodlightcontroller/staticflowentry/IStaticFlowEntryPusherService.java +++ b/src/main/java/net/floodlightcontroller/staticflowentry/IStaticFlowEntryPusherService.java @@ -18,7 +18,8 @@ package net.floodlightcontroller.staticflowentry; import java.util.Map; -import org.openflow.protocol.OFFlowMod; +import org.projectfloodlight.openflow.protocol.OFFlowMod; +import org.projectfloodlight.openflow.types.DatapathId; import net.floodlightcontroller.core.module.IFloodlightService; @@ -29,7 +30,7 @@ public interface IStaticFlowEntryPusherService extends IFloodlightService { * @param fm The flow to push. * @param swDpid The switch DPID to push it to, in 00:00:00:00:00:00:00:01 notation. */ - public void addFlow(String name, OFFlowMod fm, String swDpid); + public void addFlow(String name, OFFlowMod fm, DatapathId swDpid); /** * Deletes a static flow @@ -41,7 +42,7 @@ public interface IStaticFlowEntryPusherService extends IFloodlightService { * Deletes all static flows for a practicular switch * @param dpid The DPID of the switch to delete flows for. */ - public void deleteFlowsForSwitch(long dpid); + public void deleteFlowsForSwitch(DatapathId dpid); /** * Deletes all flows. @@ -56,5 +57,6 @@ public interface IStaticFlowEntryPusherService extends IFloodlightService { /** * Gets a list of flows by switch */ - public Map<String, OFFlowMod> getFlows(String dpid); + public Map<String, OFFlowMod> getFlows(DatapathId dpid); + } diff --git a/src/main/java/net/floodlightcontroller/staticflowentry/StaticFlowEntries.java b/src/main/java/net/floodlightcontroller/staticflowentry/StaticFlowEntries.java index 42329e5b4490da20f86c6af7182e93321f49470c..72d52bd49d05bd64f29d382519ad0f867cad698f 100644 --- a/src/main/java/net/floodlightcontroller/staticflowentry/StaticFlowEntries.java +++ b/src/main/java/net/floodlightcontroller/staticflowentry/StaticFlowEntries.java @@ -17,45 +17,28 @@ package net.floodlightcontroller.staticflowentry; import java.io.IOException; -import java.util.Arrays; import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; +import java.util.Iterator; import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import net.floodlightcontroller.core.annotations.LogMessageCategory; -import net.floodlightcontroller.core.annotations.LogMessageDoc; import net.floodlightcontroller.core.util.AppCookie; -import net.floodlightcontroller.packet.IPv4; +import net.floodlightcontroller.util.ActionUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.databind.MappingJsonFactory; -import org.openflow.protocol.OFFlowMod; -import org.openflow.protocol.OFMatch; -import org.openflow.protocol.OFPacketOut; -import org.openflow.protocol.OFPort; -import org.openflow.protocol.action.OFAction; -import org.openflow.protocol.action.OFActionDataLayerDestination; -import org.openflow.protocol.action.OFActionDataLayerSource; -import org.openflow.protocol.action.OFActionEnqueue; -import org.openflow.protocol.action.OFActionNetworkLayerDestination; -import org.openflow.protocol.action.OFActionNetworkLayerSource; -import org.openflow.protocol.action.OFActionNetworkTypeOfService; -import org.openflow.protocol.action.OFActionOutput; -import org.openflow.protocol.action.OFActionStripVirtualLan; -import org.openflow.protocol.action.OFActionTransportLayerDestination; -import org.openflow.protocol.action.OFActionTransportLayerSource; -import org.openflow.protocol.action.OFActionVirtualLanIdentifier; -import org.openflow.protocol.action.OFActionVirtualLanPriorityCodePoint; -import org.openflow.util.HexString; + +import org.projectfloodlight.openflow.protocol.OFFlowMod; +import org.projectfloodlight.openflow.protocol.match.Match; +import org.projectfloodlight.openflow.protocol.match.MatchField; +import org.projectfloodlight.openflow.types.OFBufferId; +import org.projectfloodlight.openflow.types.OFPort; +import org.projectfloodlight.openflow.types.U64; /** * Represents static flow entries to be maintained by the controller on the @@ -63,785 +46,364 @@ import org.openflow.util.HexString; */ @LogMessageCategory("Static Flow Pusher") public class StaticFlowEntries { - protected static Logger log = LoggerFactory.getLogger(StaticFlowEntries.class); - - private static class SubActionStruct { - OFAction action; - int len; - } - - private static byte[] zeroMac = new byte[] {0x0, 0x0, 0x0, 0x0, 0x0, 0x0}; - - /** - * This function generates a random hash for the bottom half of the cookie - * - * @param fm - * @param userCookie - * @param name - * @return A cookie that encodes the application ID and a hash - */ - public static long computeEntryCookie(OFFlowMod fm, int userCookie, String name) { - // flow-specific hash is next 20 bits LOOK! who knows if this - int prime = 211; - int flowHash = 2311; - for (int i=0; i < name.length(); i++) - flowHash = flowHash * prime + (int)name.charAt(i); - - return AppCookie.makeCookie(StaticFlowEntryPusher.STATIC_FLOW_APP_ID, flowHash); - } - - /** - * Sets defaults for an OFFlowMod - * @param fm The OFFlowMod to set defaults for - * @param entryName The name of the entry. Used to compute the cookie. - */ - public static void initDefaultFlowMod(OFFlowMod fm, String entryName) { - fm.setIdleTimeout((short) 0); // infinite - fm.setHardTimeout((short) 0); // infinite - fm.setBufferId(OFPacketOut.BUFFER_ID_NONE); - fm.setCommand((short) 0); - fm.setFlags((short) 0); - fm.setOutPort(OFPort.OFPP_NONE.getValue()); - fm.setCookie(computeEntryCookie(fm, 0, entryName)); - fm.setPriority(Short.MAX_VALUE); - } - - /** - * Gets the entry name of a flow mod - * @param fmJson The OFFlowMod in a JSON representation - * @return The name of the OFFlowMod, null if not found - * @throws IOException If there was an error parsing the JSON - */ - public static String getEntryNameFromJson(String fmJson) throws IOException{ - MappingJsonFactory f = new MappingJsonFactory(); - JsonParser jp; - - try { - jp = f.createJsonParser(fmJson); - } catch (JsonParseException e) { - throw new IOException(e); - } - - jp.nextToken(); - if (jp.getCurrentToken() != JsonToken.START_OBJECT) { - throw new IOException("Expected START_OBJECT"); - } - - while (jp.nextToken() != JsonToken.END_OBJECT) { - if (jp.getCurrentToken() != JsonToken.FIELD_NAME) { - throw new IOException("Expected FIELD_NAME"); - } - - String n = jp.getCurrentName(); - jp.nextToken(); - if (jp.getText().equals("")) - continue; - - if (n == "name") - return jp.getText(); - } - - return null; - } - - /** - * Parses an OFFlowMod (and it's inner OFMatch) to the storage entry format. - * @param fm The FlowMod to parse - * @param sw The switch the FlowMod is going to be installed on - * @param name The name of this static flow entry - * @return A Map representation of the storage entry - */ - public static Map<String, Object> flowModToStorageEntry(OFFlowMod fm, String sw, String name) { - Map<String, Object> entry = new HashMap<String, Object>(); - OFMatch match = fm.getMatch(); - entry.put(StaticFlowEntryPusher.COLUMN_NAME, name); - entry.put(StaticFlowEntryPusher.COLUMN_SWITCH, sw); - entry.put(StaticFlowEntryPusher.COLUMN_ACTIVE, Boolean.toString(true)); - entry.put(StaticFlowEntryPusher.COLUMN_PRIORITY, Short.toString(fm.getPriority())); - entry.put(StaticFlowEntryPusher.COLUMN_WILDCARD, Integer.toString(match.getWildcards())); - - if ((fm.getActions() != null) && (fm.getActions().size() > 0)) - entry.put(StaticFlowEntryPusher.COLUMN_ACTIONS, StaticFlowEntries.flowModActionsToString(fm.getActions())); - - if (match.getInputPort() != 0) - entry.put(StaticFlowEntryPusher.COLUMN_IN_PORT, Short.toString(match.getInputPort())); - - if (!Arrays.equals(match.getDataLayerSource(), zeroMac)) - entry.put(StaticFlowEntryPusher.COLUMN_DL_SRC, HexString.toHexString(match.getDataLayerSource())); - - if (!Arrays.equals(match.getDataLayerDestination(), zeroMac)) - entry.put(StaticFlowEntryPusher.COLUMN_DL_DST, HexString.toHexString(match.getDataLayerDestination())); - - if (match.getDataLayerVirtualLan() != -1) - entry.put(StaticFlowEntryPusher.COLUMN_DL_VLAN, Short.toString(match.getDataLayerVirtualLan())); - - if (match.getDataLayerVirtualLanPriorityCodePoint() != 0) - entry.put(StaticFlowEntryPusher.COLUMN_DL_VLAN_PCP, Short.toString(match.getDataLayerVirtualLanPriorityCodePoint())); - - if (match.getDataLayerType() != 0) - entry.put(StaticFlowEntryPusher.COLUMN_DL_TYPE, Short.toString(match.getDataLayerType())); - - if (match.getNetworkTypeOfService() != 0) - entry.put(StaticFlowEntryPusher.COLUMN_NW_TOS, Short.toString(match.getNetworkTypeOfService())); - - if (match.getNetworkProtocol() != 0) - entry.put(StaticFlowEntryPusher.COLUMN_NW_PROTO, Short.toString(match.getNetworkProtocol())); - - if (match.getNetworkSource() != 0) - entry.put(StaticFlowEntryPusher.COLUMN_NW_SRC, IPv4.fromIPv4Address(match.getNetworkSource())); - - if (match.getNetworkDestination() != 0) - entry.put(StaticFlowEntryPusher.COLUMN_NW_DST, IPv4.fromIPv4Address(match.getNetworkDestination())); - - if (match.getTransportSource() != 0) - entry.put(StaticFlowEntryPusher.COLUMN_TP_SRC, Short.toString(match.getTransportSource())); - - if (match.getTransportDestination() != 0) - entry.put(StaticFlowEntryPusher.COLUMN_TP_DST, Short.toString(match.getTransportDestination())); - - return entry; - } - - /** - * Returns a String representation of all the openflow actions. - * @param fmActions A list of OFActions to encode into one string - * @return A string of the actions encoded for our database - */ - @LogMessageDoc(level="ERROR", - message="Could not decode action {action}", - explanation="A static flow entry contained an invalid action", - recommendation=LogMessageDoc.REPORT_CONTROLLER_BUG) - private static String flowModActionsToString(List<OFAction> fmActions) { - StringBuilder sb = new StringBuilder(); - for (OFAction a : fmActions) { - if (sb.length() > 0) { - sb.append(','); - } - switch(a.getType()) { - case OUTPUT: - sb.append("output=" + Short.toString(((OFActionOutput)a).getPort())); - break; - case OPAQUE_ENQUEUE: - int queue = ((OFActionEnqueue)a).getQueueId(); - short port = ((OFActionEnqueue)a).getPort(); - sb.append("enqueue=" + Short.toString(port) + ":0x" + String.format("%02x", queue)); - break; - case STRIP_VLAN: - sb.append("strip-vlan"); - break; - case SET_VLAN_ID: - sb.append("set-vlan-id=" + - Short.toString(((OFActionVirtualLanIdentifier)a).getVirtualLanIdentifier())); - break; - case SET_VLAN_PCP: - sb.append("set-vlan-priority=" + - Byte.toString(((OFActionVirtualLanPriorityCodePoint)a).getVirtualLanPriorityCodePoint())); - break; - case SET_DL_SRC: - sb.append("set-src-mac=" + - HexString.toHexString(((OFActionDataLayerSource)a).getDataLayerAddress())); - break; - case SET_DL_DST: - sb.append("set-dst-mac=" + - HexString.toHexString(((OFActionDataLayerDestination)a).getDataLayerAddress())); - break; - case SET_NW_TOS: - sb.append("set-tos-bits=" + - Byte.toString(((OFActionNetworkTypeOfService)a).getNetworkTypeOfService())); - break; - case SET_NW_SRC: - sb.append("set-src-ip=" + - IPv4.fromIPv4Address(((OFActionNetworkLayerSource)a).getNetworkAddress())); - break; - case SET_NW_DST: - sb.append("set-dst-ip=" + - IPv4.fromIPv4Address(((OFActionNetworkLayerDestination)a).getNetworkAddress())); - break; - case SET_TP_SRC: - sb.append("set-src-port=" + - Short.toString(((OFActionTransportLayerSource)a).getTransportPort())); - break; - case SET_TP_DST: - sb.append("set-dst-port=" + - Short.toString(((OFActionTransportLayerDestination)a).getTransportPort())); - break; - default: - log.error("Could not decode action: {}", a); - break; - } - - } - return sb.toString(); - } - - /** - * Turns a JSON formatted Static Flow Pusher string into a storage entry - * Expects a string in JSON along the lines of: - * { - * "switch": "AA:BB:CC:DD:EE:FF:00:11", - * "name": "flow-mod-1", - * "cookie": "0", - * "priority": "32768", - * "ingress-port": "1", - * "actions": "output=2", - * } - * @param fmJson The JSON formatted static flow pusher entry - * @return The map of the storage entry - * @throws IOException If there was an error parsing the JSON - */ - public static Map<String, Object> jsonToStorageEntry(String fmJson) throws IOException { - Map<String, Object> entry = new HashMap<String, Object>(); - MappingJsonFactory f = new MappingJsonFactory(); - JsonParser jp; - - try { - jp = f.createJsonParser(fmJson); - } catch (JsonParseException e) { - throw new IOException(e); - } - - jp.nextToken(); - if (jp.getCurrentToken() != JsonToken.START_OBJECT) { - throw new IOException("Expected START_OBJECT"); - } - - while (jp.nextToken() != JsonToken.END_OBJECT) { - if (jp.getCurrentToken() != JsonToken.FIELD_NAME) { - throw new IOException("Expected FIELD_NAME"); - } - - String n = jp.getCurrentName(); - jp.nextToken(); - if (jp.getText().equals("")) - continue; - - if (n == "name") - entry.put(StaticFlowEntryPusher.COLUMN_NAME, jp.getText()); - else if (n == "switch") - entry.put(StaticFlowEntryPusher.COLUMN_SWITCH, jp.getText()); - else if (n == "actions") - entry.put(StaticFlowEntryPusher.COLUMN_ACTIONS, jp.getText()); - else if (n == "priority") - entry.put(StaticFlowEntryPusher.COLUMN_PRIORITY, jp.getText()); - else if (n == "active") - entry.put(StaticFlowEntryPusher.COLUMN_ACTIVE, jp.getText()); - else if (n == "wildcards") - entry.put(StaticFlowEntryPusher.COLUMN_WILDCARD, jp.getText()); - else if (n == "ingress-port") - entry.put(StaticFlowEntryPusher.COLUMN_IN_PORT, jp.getText()); - else if (n == "src-mac") - entry.put(StaticFlowEntryPusher.COLUMN_DL_SRC, jp.getText()); - else if (n == "dst-mac") - entry.put(StaticFlowEntryPusher.COLUMN_DL_DST, jp.getText()); - else if (n == "vlan-id") - entry.put(StaticFlowEntryPusher.COLUMN_DL_VLAN, jp.getText()); - else if (n == "vlan-priority") - entry.put(StaticFlowEntryPusher.COLUMN_DL_VLAN_PCP, jp.getText()); - else if (n == "ether-type") - entry.put(StaticFlowEntryPusher.COLUMN_DL_TYPE, jp.getText()); - else if (n == "tos-bits") - entry.put(StaticFlowEntryPusher.COLUMN_NW_TOS, jp.getText()); - else if (n == "protocol") - entry.put(StaticFlowEntryPusher.COLUMN_NW_PROTO, jp.getText()); - else if (n == "src-ip") - entry.put(StaticFlowEntryPusher.COLUMN_NW_SRC, jp.getText()); - else if (n == "dst-ip") - entry.put(StaticFlowEntryPusher.COLUMN_NW_DST, jp.getText()); - else if (n == "src-port") - entry.put(StaticFlowEntryPusher.COLUMN_TP_SRC, jp.getText()); - else if (n == "dst-port") - entry.put(StaticFlowEntryPusher.COLUMN_TP_DST, jp.getText()); - } - - return entry; - } - - /** - * Parses OFFlowMod actions from strings. - * @param flowMod The OFFlowMod to set the actions for - * @param actionstr The string containing all the actions - * @param log A logger to log for errors. - */ - @LogMessageDoc(level="ERROR", - message="Unexpected action '{action}', '{subaction}'", - explanation="A static flow entry contained an invalid action", - recommendation=LogMessageDoc.REPORT_CONTROLLER_BUG) - public static void parseActionString(OFFlowMod flowMod, String actionstr, Logger log) { - List<OFAction> actions = new LinkedList<OFAction>(); - int actionsLength = 0; - if (actionstr != null) { - actionstr = actionstr.toLowerCase(); - for (String subaction : actionstr.split(",")) { - String action = subaction.split("[=:]")[0]; - SubActionStruct subaction_struct = null; - - if (action.equals("output")) { - subaction_struct = StaticFlowEntries.decode_output(subaction, log); - } - else if (action.equals("enqueue")) { - subaction_struct = decode_enqueue(subaction, log); - } - else if (action.equals("strip-vlan")) { - subaction_struct = decode_strip_vlan(subaction, log); - } - else if (action.equals("set-vlan-id")) { - subaction_struct = decode_set_vlan_id(subaction, log); - } - else if (action.equals("set-vlan-priority")) { - subaction_struct = decode_set_vlan_priority(subaction, log); - } - else if (action.equals("set-src-mac")) { - subaction_struct = decode_set_src_mac(subaction, log); - } - else if (action.equals("set-dst-mac")) { - subaction_struct = decode_set_dst_mac(subaction, log); - } - else if (action.equals("set-tos-bits")) { - subaction_struct = decode_set_tos_bits(subaction, log); - } - else if (action.equals("set-src-ip")) { - subaction_struct = decode_set_src_ip(subaction, log); - } - else if (action.equals("set-dst-ip")) { - subaction_struct = decode_set_dst_ip(subaction, log); - } - else if (action.equals("set-src-port")) { - subaction_struct = decode_set_src_port(subaction, log); - } - else if (action.equals("set-dst-port")) { - subaction_struct = decode_set_dst_port(subaction, log); - } - else { - log.error("Unexpected action '{}', '{}'", action, subaction); - } - - if (subaction_struct != null) { - actions.add(subaction_struct.action); - actionsLength += subaction_struct.len; - } - } - } - log.debug("action {}", actions); - - flowMod.setActions(actions); - flowMod.setLengthU(OFFlowMod.MINIMUM_LENGTH + actionsLength); - } - - @LogMessageDoc(level="ERROR", - message="Invalid subaction: '{subaction}'", - explanation="A static flow entry contained an invalid subaction", - recommendation=LogMessageDoc.REPORT_CONTROLLER_BUG) - private static SubActionStruct decode_output(String subaction, Logger log) { - SubActionStruct sa = null; - Matcher n; - - n = Pattern.compile("output=(?:((?:0x)?\\d+)|(all)|(controller)|(local)|(ingress-port)|(normal)|(flood))").matcher(subaction); - if (n.matches()) { - OFActionOutput action = new OFActionOutput(); - action.setMaxLength(Short.MAX_VALUE); - short port = OFPort.OFPP_NONE.getValue(); - if (n.group(1) != null) { - try { - port = get_short(n.group(1)); - } - catch (NumberFormatException e) { - log.debug("Invalid port in: '{}' (error ignored)", subaction); - return null; - } - } - else if (n.group(2) != null) - port = OFPort.OFPP_ALL.getValue(); - else if (n.group(3) != null) - port = OFPort.OFPP_CONTROLLER.getValue(); - else if (n.group(4) != null) - port = OFPort.OFPP_LOCAL.getValue(); - else if (n.group(5) != null) - port = OFPort.OFPP_IN_PORT.getValue(); - else if (n.group(6) != null) - port = OFPort.OFPP_NORMAL.getValue(); - else if (n.group(7) != null) - port = OFPort.OFPP_FLOOD.getValue(); - action.setPort(port); - log.debug("action {}", action); - - sa = new SubActionStruct(); - sa.action = action; - sa.len = OFActionOutput.MINIMUM_LENGTH; - } - else { - log.error("Invalid subaction: '{}'", subaction); - return null; - } - - return sa; - } - - private static SubActionStruct decode_enqueue(String subaction, Logger log) { - SubActionStruct sa = null; - Matcher n; - - n = Pattern.compile("enqueue=(?:((?:0x)?\\d+)\\:((?:0x)?\\d+))").matcher(subaction); - if (n.matches()) { - short portnum = 0; - if (n.group(1) != null) { - try { - portnum = get_short(n.group(1)); - } - catch (NumberFormatException e) { - log.debug("Invalid port-num in: '{}' (error ignored)", subaction); - return null; - } - } - - int queueid = 0; - if (n.group(2) != null) { - try { - queueid = get_int(n.group(2)); - } - catch (NumberFormatException e) { - log.debug("Invalid queue-id in: '{}' (error ignored)", subaction); - return null; - } - } - - OFActionEnqueue action = new OFActionEnqueue(); - action.setPort(portnum); - action.setQueueId(queueid); - log.debug("action {}", action); - - sa = new SubActionStruct(); - sa.action = action; - sa.len = OFActionEnqueue.MINIMUM_LENGTH; - } - else { - log.debug("Invalid action: '{}'", subaction); - return null; - } - - return sa; - } - - private static SubActionStruct decode_strip_vlan(String subaction, Logger log) { - SubActionStruct sa = null; - Matcher n = Pattern.compile("strip-vlan").matcher(subaction); - - if (n.matches()) { - OFActionStripVirtualLan action = new OFActionStripVirtualLan(); - log.debug("action {}", action); - - sa = new SubActionStruct(); - sa.action = action; - sa.len = OFActionStripVirtualLan.MINIMUM_LENGTH; - } - else { - log.debug("Invalid action: '{}'", subaction); - return null; - } - - return sa; - } - - private static SubActionStruct decode_set_vlan_id(String subaction, Logger log) { - SubActionStruct sa = null; - Matcher n = Pattern.compile("set-vlan-id=((?:0x)?\\d+)").matcher(subaction); - - if (n.matches()) { - if (n.group(1) != null) { - try { - short vlanid = get_short(n.group(1)); - OFActionVirtualLanIdentifier action = new OFActionVirtualLanIdentifier(); - action.setVirtualLanIdentifier(vlanid); - log.debug(" action {}", action); - - sa = new SubActionStruct(); - sa.action = action; - sa.len = OFActionVirtualLanIdentifier.MINIMUM_LENGTH; - } - catch (NumberFormatException e) { - log.debug("Invalid VLAN in: {} (error ignored)", subaction); - return null; - } - } - } - else { - log.debug("Invalid action: '{}'", subaction); - return null; - } - - return sa; - } - - private static SubActionStruct decode_set_vlan_priority(String subaction, Logger log) { - SubActionStruct sa = null; - Matcher n = Pattern.compile("set-vlan-priority=((?:0x)?\\d+)").matcher(subaction); - - if (n.matches()) { - if (n.group(1) != null) { - try { - byte prior = get_byte(n.group(1)); - OFActionVirtualLanPriorityCodePoint action = new OFActionVirtualLanPriorityCodePoint(); - action.setVirtualLanPriorityCodePoint(prior); - log.debug(" action {}", action); - - sa = new SubActionStruct(); - sa.action = action; - sa.len = OFActionVirtualLanPriorityCodePoint.MINIMUM_LENGTH; - } - catch (NumberFormatException e) { - log.debug("Invalid VLAN priority in: {} (error ignored)", subaction); - return null; - } - } - } - else { - log.debug("Invalid action: '{}'", subaction); - return null; - } - - return sa; - } - - private static SubActionStruct decode_set_src_mac(String subaction, Logger log) { - SubActionStruct sa = null; - Matcher n = Pattern.compile("set-src-mac=(?:(\\p{XDigit}+)\\:(\\p{XDigit}+)\\:(\\p{XDigit}+)\\:(\\p{XDigit}+)\\:(\\p{XDigit}+)\\:(\\p{XDigit}+))").matcher(subaction); - - if (n.matches()) { - byte[] macaddr = get_mac_addr(n, subaction, log); - if (macaddr != null) { - OFActionDataLayerSource action = new OFActionDataLayerSource(); - action.setDataLayerAddress(macaddr); - log.debug("action {}", action); - - sa = new SubActionStruct(); - sa.action = action; - sa.len = OFActionDataLayerSource.MINIMUM_LENGTH; - } - } - else { - log.debug("Invalid action: '{}'", subaction); - return null; - } - - return sa; - } - - private static SubActionStruct decode_set_dst_mac(String subaction, Logger log) { - SubActionStruct sa = null; - Matcher n = Pattern.compile("set-dst-mac=(?:(\\p{XDigit}+)\\:(\\p{XDigit}+)\\:(\\p{XDigit}+)\\:(\\p{XDigit}+)\\:(\\p{XDigit}+)\\:(\\p{XDigit}+))").matcher(subaction); - - if (n.matches()) { - byte[] macaddr = get_mac_addr(n, subaction, log); - if (macaddr != null) { - OFActionDataLayerDestination action = new OFActionDataLayerDestination(); - action.setDataLayerAddress(macaddr); - log.debug(" action {}", action); - - sa = new SubActionStruct(); - sa.action = action; - sa.len = OFActionDataLayerDestination.MINIMUM_LENGTH; - } - } - else { - log.debug("Invalid action: '{}'", subaction); - return null; - } - - return sa; - } - - private static SubActionStruct decode_set_tos_bits(String subaction, Logger log) { - SubActionStruct sa = null; - Matcher n = Pattern.compile("set-tos-bits=((?:0x)?\\d+)").matcher(subaction); - - if (n.matches()) { - if (n.group(1) != null) { - try { - byte tosbits = get_byte(n.group(1)); - OFActionNetworkTypeOfService action = new OFActionNetworkTypeOfService(); - action.setNetworkTypeOfService(tosbits); - log.debug(" action {}", action); - - sa = new SubActionStruct(); - sa.action = action; - sa.len = OFActionNetworkTypeOfService.MINIMUM_LENGTH; - } - catch (NumberFormatException e) { - log.debug("Invalid dst-port in: {} (error ignored)", subaction); - return null; - } - } - } - else { - log.debug("Invalid action: '{}'", subaction); - return null; - } - - return sa; - } - - private static SubActionStruct decode_set_src_ip(String subaction, Logger log) { - SubActionStruct sa = null; - Matcher n = Pattern.compile("set-src-ip=(?:(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+))").matcher(subaction); - - if (n.matches()) { - int ipaddr = get_ip_addr(n, subaction, log); - OFActionNetworkLayerSource action = new OFActionNetworkLayerSource(); - action.setNetworkAddress(ipaddr); - log.debug(" action {}", action); - - sa = new SubActionStruct(); - sa.action = action; - sa.len = OFActionNetworkLayerSource.MINIMUM_LENGTH; - } - else { - log.debug("Invalid action: '{}'", subaction); - return null; - } - - return sa; - } - - private static SubActionStruct decode_set_dst_ip(String subaction, Logger log) { - SubActionStruct sa = null; - Matcher n = Pattern.compile("set-dst-ip=(?:(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+))").matcher(subaction); - - if (n.matches()) { - int ipaddr = get_ip_addr(n, subaction, log); - OFActionNetworkLayerDestination action = new OFActionNetworkLayerDestination(); - action.setNetworkAddress(ipaddr); - log.debug("action {}", action); - - sa = new SubActionStruct(); - sa.action = action; - sa.len = OFActionNetworkLayerDestination.MINIMUM_LENGTH; - } - else { - log.debug("Invalid action: '{}'", subaction); - return null; - } - - return sa; - } - - private static SubActionStruct decode_set_src_port(String subaction, Logger log) { - SubActionStruct sa = null; - Matcher n = Pattern.compile("set-src-port=((?:0x)?\\d+)").matcher(subaction); - - if (n.matches()) { - if (n.group(1) != null) { - try { - short portnum = get_short(n.group(1)); - OFActionTransportLayerSource action = new OFActionTransportLayerSource(); - action.setTransportPort(portnum); - log.debug("action {}", action); - - sa = new SubActionStruct(); - sa.action = action; - sa.len = OFActionTransportLayerSource.MINIMUM_LENGTH;; - } - catch (NumberFormatException e) { - log.debug("Invalid src-port in: {} (error ignored)", subaction); - return null; - } - } - } - else { - log.debug("Invalid action: '{}'", subaction); - return null; - } - - return sa; - } - - private static SubActionStruct decode_set_dst_port(String subaction, Logger log) { - SubActionStruct sa = null; - Matcher n = Pattern.compile("set-dst-port=((?:0x)?\\d+)").matcher(subaction); - - if (n.matches()) { - if (n.group(1) != null) { - try { - short portnum = get_short(n.group(1)); - OFActionTransportLayerDestination action = new OFActionTransportLayerDestination(); - action.setTransportPort(portnum); - log.debug("action {}", action); - - sa = new SubActionStruct(); - sa.action = action; - sa.len = OFActionTransportLayerDestination.MINIMUM_LENGTH;; - } - catch (NumberFormatException e) { - log.debug("Invalid dst-port in: {} (error ignored)", subaction); - return null; - } - } - } - else { - log.debug("Invalid action: '{}'", subaction); - return null; - } - - return sa; - } - - private static byte[] get_mac_addr(Matcher n, String subaction, Logger log) { - byte[] macaddr = new byte[6]; - - for (int i=0; i<6; i++) { - if (n.group(i+1) != null) { - try { - macaddr[i] = get_byte("0x" + n.group(i+1)); - } - catch (NumberFormatException e) { - log.debug("Invalid src-mac in: '{}' (error ignored)", subaction); - return null; - } - } - else { - log.debug("Invalid src-mac in: '{}' (null, error ignored)", subaction); - return null; - } - } - - return macaddr; - } - - private static int get_ip_addr(Matcher n, String subaction, Logger log) { - int ipaddr = 0; - - for (int i=0; i<4; i++) { - if (n.group(i+1) != null) { - try { - ipaddr = ipaddr<<8; - ipaddr = ipaddr | get_int(n.group(i+1)); - } - catch (NumberFormatException e) { - log.debug("Invalid src-ip in: '{}' (error ignored)", subaction); - return 0; - } - } - else { - log.debug("Invalid src-ip in: '{}' (null, error ignored)", subaction); - return 0; - } - } - - return ipaddr; - } - - // Parse int as decimal, hex (start with 0x or #) or octal (starts with 0) - private static int get_int(String str) { - return Integer.decode(str); - } - - // Parse short as decimal, hex (start with 0x or #) or octal (starts with 0) - private static short get_short(String str) { - return (short)(int)Integer.decode(str); - } - - // Parse byte as decimal, hex (start with 0x or #) or octal (starts with 0) - private static byte get_byte(String str) { - return Integer.decode(str).byteValue(); - } - + protected static Logger log = LoggerFactory.getLogger(StaticFlowEntries.class); + private static final int INFINITE_TIMEOUT = 0; + + /** + * This function generates a random hash for the bottom half of the cookie + * + * @param fm + * @param userCookie + * @param name + * @return A cookie that encodes the application ID and a hash + */ + public static U64 computeEntryCookie(int userCookie, String name) { + // flow-specific hash is next 20 bits LOOK! who knows if this + int prime = 211; + int flowHash = 2311; + for (int i=0; i < name.length(); i++) { + flowHash = flowHash * prime + (int)name.charAt(i); + } + + return AppCookie.makeCookie(StaticFlowEntryPusher.STATIC_FLOW_APP_ID, flowHash); + } + + /** + * Sets defaults for an OFFlowMod used in the StaticFlowEntryPusher + * @param fm The OFFlowMod to set defaults for + * @param entryName The name of the entry. Used to compute the cookie. + */ + public static void initDefaultFlowMod(OFFlowMod.Builder fmb, String entryName) { + fmb.setIdleTimeout(INFINITE_TIMEOUT) // not setting these would also work + .setHardTimeout(INFINITE_TIMEOUT) + .setBufferId(OFBufferId.NO_BUFFER) + .setOutPort(OFPort.ANY) + .setCookie(computeEntryCookie(0, entryName)) + .setPriority(Integer.MAX_VALUE); + return; + } + + /** + * Gets the entry name of a flow mod + * @param fmJson The OFFlowMod in a JSON representation + * @return The name of the OFFlowMod, null if not found + * @throws IOException If there was an error parsing the JSON + */ + public static String getEntryNameFromJson(String fmJson) throws IOException{ + MappingJsonFactory f = new MappingJsonFactory(); + JsonParser jp; + + try { + jp = f.createJsonParser(fmJson); + } catch (JsonParseException e) { + throw new IOException(e); + } + + jp.nextToken(); + if (jp.getCurrentToken() != JsonToken.START_OBJECT) { + throw new IOException("Expected START_OBJECT"); + } + + while (jp.nextToken() != JsonToken.END_OBJECT) { + if (jp.getCurrentToken() != JsonToken.FIELD_NAME) { + throw new IOException("Expected FIELD_NAME"); + } + + String n = jp.getCurrentName(); + jp.nextToken(); + if (jp.getText().equals("")) + continue; + + if (n == StaticFlowEntryPusher.COLUMN_NAME) + return jp.getText(); + } + return null; + } + + /** + * Parses an OFFlowMod (and it's inner Match) to the storage entry format. + * @param fm The FlowMod to parse + * @param sw The switch the FlowMod is going to be installed on + * @param name The name of this static flow entry + * @return A Map representation of the storage entry + */ + public static Map<String, Object> flowModToStorageEntry(OFFlowMod fm, String sw, String name) { + Map<String, Object> entry = new HashMap<String, Object>(); + entry.put(StaticFlowEntryPusher.COLUMN_NAME, name); + entry.put(StaticFlowEntryPusher.COLUMN_SWITCH, sw); + entry.put(StaticFlowEntryPusher.COLUMN_ACTIVE, Boolean.toString(true)); + entry.put(StaticFlowEntryPusher.COLUMN_PRIORITY, Integer.toString(fm.getPriority())); + + if ((fm.getActions() != null) && (fm.getActions().size() > 0)) { + entry.put(StaticFlowEntryPusher.COLUMN_ACTIONS, ActionUtils.actionsToString(fm.getActions(), log)); + } + + Match match = fm.getMatch(); + boolean setTOS = false; + Iterator<MatchField<?>> itr = match.getMatchFields().iterator(); // only get exact or masked fields (not fully wildcarded) + while(itr.hasNext()) { + @SuppressWarnings("rawtypes") // this is okay here + MatchField mf = itr.next(); + switch (mf.id) { + case IN_PORT: // iterates over only exact/masked fields. No need to check for null entries. + entry.put(StaticFlowEntryPusher.COLUMN_IN_PORT, Integer.toString((match.get(MatchField.IN_PORT)).getPortNumber())); + break; + case ETH_SRC: + entry.put(StaticFlowEntryPusher.COLUMN_DL_SRC, match.get(MatchField.ETH_SRC).toString()); + break; + case ETH_DST: + entry.put(StaticFlowEntryPusher.COLUMN_DL_DST, match.get(MatchField.ETH_DST).toString()); + break; + case VLAN_VID: + entry.put(StaticFlowEntryPusher.COLUMN_DL_VLAN, match.get(MatchField.VLAN_VID).getVlan()); + break; + case VLAN_PCP: + entry.put(StaticFlowEntryPusher.COLUMN_DL_VLAN_PCP, Byte.toString(match.get(MatchField.VLAN_PCP).getValue())); + break; + case ETH_TYPE: + entry.put(StaticFlowEntryPusher.COLUMN_DL_TYPE, match.get(MatchField.ETH_TYPE).getValue()); + break; + case IP_ECN: // TOS = [DSCP bits 0-5] + [ECN bits 6-7] --> bitwise OR to get TOS byte + if (setTOS) { //TODO @Ryan need to break TOS into ECN and DSCP columns + entry.put(StaticFlowEntryPusher.COLUMN_NW_TOS, + Byte.toString((byte) (match.get(MatchField.IP_ECN).getEcnValue() + | (Byte.parseByte(entry.get(StaticFlowEntryPusher.COLUMN_NW_TOS).toString()))))); + } else { + entry.put(StaticFlowEntryPusher.COLUMN_NW_TOS, Byte.toString((byte) (match.get(MatchField.IP_ECN).getEcnValue()))); + } + setTOS = true; + break; + case IP_DSCP: + if (setTOS) { + entry.put(StaticFlowEntryPusher.COLUMN_NW_TOS, + Byte.toString((byte) (match.get(MatchField.IP_DSCP).getDscpValue() + | (Byte.parseByte(entry.get(StaticFlowEntryPusher.COLUMN_NW_TOS).toString()))))); + } else { + entry.put(StaticFlowEntryPusher.COLUMN_NW_TOS, Byte.toString((byte) (match.get(MatchField.IP_ECN).getEcnValue()))); + } + setTOS = true; + break; + case IP_PROTO: + entry.put(StaticFlowEntryPusher.COLUMN_NW_PROTO, Short.toString(match.get(MatchField.IP_PROTO).getIpProtocolNumber())); + break; + case IPV4_SRC: + entry.put(StaticFlowEntryPusher.COLUMN_NW_SRC, match.get(MatchField.IPV4_SRC).toString()); + break; + case IPV4_DST: + entry.put(StaticFlowEntryPusher.COLUMN_NW_DST, match.get(MatchField.IPV4_DST).toString()); + break; + case TCP_SRC: + entry.put(StaticFlowEntryPusher.COLUMN_TP_SRC, match.get(MatchField.TCP_SRC).getPort()); + break; + case UDP_SRC: + entry.put(StaticFlowEntryPusher.COLUMN_TP_SRC, match.get(MatchField.UDP_SRC).getPort()); + break; + case SCTP_SRC: + entry.put(StaticFlowEntryPusher.COLUMN_TP_SRC, match.get(MatchField.SCTP_SRC).getPort()); + break; + case TCP_DST: + entry.put(StaticFlowEntryPusher.COLUMN_TP_DST, match.get(MatchField.TCP_DST).getPort()); + break; + case UDP_DST: + entry.put(StaticFlowEntryPusher.COLUMN_TP_DST, match.get(MatchField.UDP_DST).getPort()); + break; + case SCTP_DST: + entry.put(StaticFlowEntryPusher.COLUMN_TP_DST, match.get(MatchField.SCTP_DST).getPort()); + break; + case ICMPV4_TYPE: + entry.put(StaticFlowEntryPusher.COLUMN_ICMP_TYPE, match.get(MatchField.ICMPV4_TYPE).getType()); + break; + case ICMPV4_CODE: + entry.put(StaticFlowEntryPusher.COLUMN_ICMP_CODE, match.get(MatchField.ICMPV4_CODE).getCode()); + break; + case ARP_OP: + entry.put(StaticFlowEntryPusher.COLUMN_ARP_OPCODE, match.get(MatchField.ARP_OP).getOpcode()); + break; + case ARP_SHA: + entry.put(StaticFlowEntryPusher.COLUMN_ARP_SHA, match.get(MatchField.ARP_SHA).toString()); + break; + case ARP_THA: + entry.put(StaticFlowEntryPusher.COLUMN_ARP_DHA, match.get(MatchField.ARP_THA).toString()); + break; + case ARP_SPA: + entry.put(StaticFlowEntryPusher.COLUMN_ARP_SPA, match.get(MatchField.ARP_SPA).toString()); + break; + case ARP_TPA: + entry.put(StaticFlowEntryPusher.COLUMN_ARP_DPA, match.get(MatchField.ARP_TPA).toString()); + break; + case MPLS_LABEL: + entry.put(StaticFlowEntryPusher.COLUMN_MPLS_LABEL, match.get(MatchField.MPLS_LABEL).getValue()); + break; + case MPLS_TC: + entry.put(StaticFlowEntryPusher.COLUMN_MPLS_TC, match.get(MatchField.MPLS_TC).getValue()); + break; + // case MPLS_BOS not implemented in loxi + case METADATA: + entry.put(StaticFlowEntryPusher.COLUMN_METADATA, match.get(MatchField.METADATA).getValue().getValue()); + break; + // case TUNNEL_ID not implemented in loxi + // case PBB_ISID not implemented in loxi + default: + log.error("Unhandled Match when parsing OFFlowMod: {}, {}", mf, mf.id); + break; + } // end switch-case + } // end while + return entry; + } + + /** + * Turns a JSON formatted Static Flow Pusher string into a storage entry + * Expects a string in JSON along the lines of: + * { + * "switch": "AA:BB:CC:DD:EE:FF:00:11", + * "name": "flow-mod-1", + * "cookie": "0", + * "priority": "32768", + * "ingress-port": "1", + * "actions": "output=2", + * } + * @param fmJson The JSON formatted static flow pusher entry + * @return The map of the storage entry + * @throws IOException If there was an error parsing the JSON + */ + public static Map<String, Object> jsonToStorageEntry(String fmJson) throws IOException { + Map<String, Object> entry = new HashMap<String, Object>(); + MappingJsonFactory f = new MappingJsonFactory(); + JsonParser jp; + + try { + jp = f.createJsonParser(fmJson); + } catch (JsonParseException e) { + throw new IOException(e); + } + + jp.nextToken(); + if (jp.getCurrentToken() != JsonToken.START_OBJECT) { + throw new IOException("Expected START_OBJECT"); + } + + while (jp.nextToken() != JsonToken.END_OBJECT) { + if (jp.getCurrentToken() != JsonToken.FIELD_NAME) { + throw new IOException("Expected FIELD_NAME"); + } + + String n = jp.getCurrentName(); + jp.nextToken(); + if (jp.getText().equals("")) { + continue; + } + + // Java 7 switch-case on strings automatically checks for (deep) string equality. + // IMHO, this makes things easier on the eyes than if, else if, else's + + // A simplification is to make the column names the same strings as those used to + // compose the JSON flow entry; keeps all names/keys centralized and reduces liklihood + // for future string errors. + switch (n) { + case StaticFlowEntryPusher.COLUMN_NAME: + entry.put(StaticFlowEntryPusher.COLUMN_NAME, jp.getText()); + break; + case StaticFlowEntryPusher.COLUMN_SWITCH: + entry.put(StaticFlowEntryPusher.COLUMN_SWITCH, jp.getText()); + break; + case StaticFlowEntryPusher.COLUMN_ACTIVE: + entry.put(StaticFlowEntryPusher.COLUMN_ACTIVE, jp.getText()); + break; + case StaticFlowEntryPusher.COLUMN_IDLE_TIMEOUT: // store TO's, but conditionally push them + entry.put(StaticFlowEntryPusher.COLUMN_IDLE_TIMEOUT, jp.getText()); + break; + case StaticFlowEntryPusher.COLUMN_HARD_TIMEOUT: + entry.put(StaticFlowEntryPusher.COLUMN_HARD_TIMEOUT, jp.getText()); + break; + case StaticFlowEntryPusher.COLUMN_PRIORITY: + entry.put(StaticFlowEntryPusher.COLUMN_PRIORITY, jp.getText()); + break; + case StaticFlowEntryPusher.COLUMN_COOKIE: // set manually, or computed from name + entry.put(StaticFlowEntryPusher.COLUMN_COOKIE, jp.getText()); + break; + case StaticFlowEntryPusher.COLUMN_IN_PORT: + entry.put(StaticFlowEntryPusher.COLUMN_IN_PORT, jp.getText()); + break; + case StaticFlowEntryPusher.COLUMN_DL_SRC: + entry.put(StaticFlowEntryPusher.COLUMN_DL_SRC, jp.getText()); + break; + case StaticFlowEntryPusher.COLUMN_DL_DST: + entry.put(StaticFlowEntryPusher.COLUMN_DL_DST, jp.getText()); + break; + case StaticFlowEntryPusher.COLUMN_DL_VLAN: + entry.put(StaticFlowEntryPusher.COLUMN_DL_VLAN, jp.getText()); + break; + case StaticFlowEntryPusher.COLUMN_DL_VLAN_PCP: + entry.put(StaticFlowEntryPusher.COLUMN_DL_VLAN_PCP, jp.getText()); + break; + case StaticFlowEntryPusher.COLUMN_DL_TYPE: + entry.put(StaticFlowEntryPusher.COLUMN_DL_TYPE, jp.getText()); + break; + case StaticFlowEntryPusher.COLUMN_NW_TOS: + entry.put(StaticFlowEntryPusher.COLUMN_NW_TOS, jp.getText()); + break; + case StaticFlowEntryPusher.COLUMN_NW_PROTO: + entry.put(StaticFlowEntryPusher.COLUMN_NW_PROTO, jp.getText()); + break; + case StaticFlowEntryPusher.COLUMN_NW_SRC: + entry.put(StaticFlowEntryPusher.COLUMN_NW_SRC, jp.getText()); + break; + case StaticFlowEntryPusher.COLUMN_NW_DST: + entry.put(StaticFlowEntryPusher.COLUMN_NW_DST, jp.getText()); + break; + case StaticFlowEntryPusher.COLUMN_TP_SRC: + entry.put(StaticFlowEntryPusher.COLUMN_TP_SRC, jp.getText()); + break; + case StaticFlowEntryPusher.COLUMN_TP_DST: + entry.put(StaticFlowEntryPusher.COLUMN_TP_DST, jp.getText()); + break; + case StaticFlowEntryPusher.COLUMN_ICMP_TYPE: + entry.put(StaticFlowEntryPusher.COLUMN_ICMP_TYPE, jp.getText()); + break; + case StaticFlowEntryPusher.COLUMN_ICMP_CODE: + entry.put(StaticFlowEntryPusher.COLUMN_ICMP_CODE, jp.getText()); + break; + case StaticFlowEntryPusher.COLUMN_ARP_OPCODE: + entry.put(StaticFlowEntryPusher.COLUMN_ARP_OPCODE, jp.getText()); + break; + case StaticFlowEntryPusher.COLUMN_ARP_SHA: + entry.put(StaticFlowEntryPusher.COLUMN_ARP_SHA, jp.getText()); + break; + case StaticFlowEntryPusher.COLUMN_ARP_DHA: + entry.put(StaticFlowEntryPusher.COLUMN_ARP_DHA, jp.getText()); + break; + case StaticFlowEntryPusher.COLUMN_ARP_SPA: + entry.put(StaticFlowEntryPusher.COLUMN_ARP_SPA, jp.getText()); + break; + case StaticFlowEntryPusher.COLUMN_ARP_DPA: + entry.put(StaticFlowEntryPusher.COLUMN_ARP_DPA, jp.getText()); + break; + case StaticFlowEntryPusher.COLUMN_MPLS_LABEL: + entry.put(StaticFlowEntryPusher.COLUMN_MPLS_LABEL, jp.getText()); + break; + case StaticFlowEntryPusher.COLUMN_MPLS_TC: + entry.put(StaticFlowEntryPusher.COLUMN_MPLS_TC, jp.getText()); + break; + case StaticFlowEntryPusher.COLUMN_MPLS_BOS: // not supported as match in loxi right now + entry.put(StaticFlowEntryPusher.COLUMN_MPLS_BOS, jp.getText()); + break; + case StaticFlowEntryPusher.COLUMN_METADATA: + entry.put(StaticFlowEntryPusher.COLUMN_METADATA, jp.getText()); + break; + case StaticFlowEntryPusher.COLUMN_TUNNEL_ID: // not supported as match in loxi right now + entry.put(StaticFlowEntryPusher.COLUMN_TUNNEL_ID, jp.getText()); + break; + case StaticFlowEntryPusher.COLUMN_PBB_ISID: // not supported as match in loxi right now + entry.put(StaticFlowEntryPusher.COLUMN_PBB_ISID, jp.getText()); + break; + case StaticFlowEntryPusher.COLUMN_ACTIONS: + entry.put(StaticFlowEntryPusher.COLUMN_ACTIONS, jp.getText()); + break; + default: + log.error("Could not decode field from JSON string: {}", n); + } + } + return entry; + } } diff --git a/src/main/java/net/floodlightcontroller/staticflowentry/StaticFlowEntryPusher.java b/src/main/java/net/floodlightcontroller/staticflowentry/StaticFlowEntryPusher.java index 029d4a00da8eee4f0ac68ef69f1f3a57f1d4643a..66300be1ed663116829131f8be8e81cfedb45f01 100644 --- a/src/main/java/net/floodlightcontroller/staticflowentry/StaticFlowEntryPusher.java +++ b/src/main/java/net/floodlightcontroller/staticflowentry/StaticFlowEntryPusher.java @@ -16,7 +16,6 @@ package net.floodlightcontroller.staticflowentry; -import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -36,9 +35,10 @@ import net.floodlightcontroller.core.IHAListener; import net.floodlightcontroller.core.IOFMessageListener; import net.floodlightcontroller.core.IOFSwitch; import net.floodlightcontroller.core.IOFSwitchListener; -import net.floodlightcontroller.core.ImmutablePort; +import net.floodlightcontroller.core.PortChangeType; import net.floodlightcontroller.core.annotations.LogMessageCategory; import net.floodlightcontroller.core.annotations.LogMessageDoc; +import net.floodlightcontroller.core.internal.IOFSwitchService; import net.floodlightcontroller.core.module.FloodlightModuleContext; import net.floodlightcontroller.core.module.FloodlightModuleException; import net.floodlightcontroller.core.module.IFloodlightModule; @@ -50,14 +50,22 @@ import net.floodlightcontroller.storage.IResultSet; import net.floodlightcontroller.storage.IStorageSourceListener; import net.floodlightcontroller.storage.IStorageSourceService; import net.floodlightcontroller.storage.StorageException; - -import org.openflow.protocol.OFFlowMod; -import org.openflow.protocol.OFFlowRemoved; -import org.openflow.protocol.OFMatch; -import org.openflow.protocol.OFMessage; -import org.openflow.protocol.OFType; -import org.openflow.util.HexString; -import org.openflow.util.U16; +import net.floodlightcontroller.util.ActionUtils; +import net.floodlightcontroller.util.FlowModUtils; +import net.floodlightcontroller.util.MatchUtils; + +import org.projectfloodlight.openflow.protocol.OFFactories; +import org.projectfloodlight.openflow.protocol.OFFlowAdd; +import org.projectfloodlight.openflow.protocol.OFFlowDeleteStrict; +import org.projectfloodlight.openflow.protocol.OFFlowMod; +import org.projectfloodlight.openflow.protocol.OFFlowRemoved; +import org.projectfloodlight.openflow.protocol.OFFlowRemovedReason; +import org.projectfloodlight.openflow.protocol.OFPortDesc; +import org.projectfloodlight.openflow.protocol.OFMessage; +import org.projectfloodlight.openflow.protocol.OFType; +import org.projectfloodlight.openflow.types.DatapathId; +import org.projectfloodlight.openflow.types.U16; +import org.projectfloodlight.openflow.types.U64; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -68,637 +76,642 @@ import org.slf4j.LoggerFactory; * is responsible for ensuring they make sense for the network. */ public class StaticFlowEntryPusher - implements IOFSwitchListener, IFloodlightModule, IStaticFlowEntryPusherService, - IStorageSourceListener, IOFMessageListener { - protected static Logger log = LoggerFactory.getLogger(StaticFlowEntryPusher.class); - public static final String StaticFlowName = "staticflowentry"; - - public static final int STATIC_FLOW_APP_ID = 10; - static { - AppCookie.registerApp(STATIC_FLOW_APP_ID, StaticFlowName); - } - - public static final String TABLE_NAME = "controller_staticflowtableentry"; - public static final String COLUMN_NAME = "name"; - public static final String COLUMN_SWITCH = "switch_id"; - public static final String COLUMN_ACTIVE = "active"; - public static final String COLUMN_IDLE_TIMEOUT = "idle_timeout"; - public static final String COLUMN_HARD_TIMEOUT = "hard_timeout"; - public static final String COLUMN_PRIORITY = "priority"; - public static final String COLUMN_COOKIE = "cookie"; - public static final String COLUMN_WILDCARD = "wildcards"; - public static final String COLUMN_IN_PORT = "in_port"; - public static final String COLUMN_DL_SRC = "dl_src"; - public static final String COLUMN_DL_DST = "dl_dst"; - public static final String COLUMN_DL_VLAN = "dl_vlan"; - public static final String COLUMN_DL_VLAN_PCP = "dl_vlan_pcp"; - public static final String COLUMN_DL_TYPE = "dl_type"; - public static final String COLUMN_NW_TOS = "nw_tos"; - public static final String COLUMN_NW_PROTO = "nw_proto"; - public static final String COLUMN_NW_SRC = "nw_src"; // includes CIDR-style - // netmask, e.g. - // "128.8.128.0/24" - public static final String COLUMN_NW_DST = "nw_dst"; - public static final String COLUMN_TP_DST = "tp_dst"; - public static final String COLUMN_TP_SRC = "tp_src"; - public static final String COLUMN_ACTIONS = "actions"; - public static String ColumnNames[] = { COLUMN_NAME, COLUMN_SWITCH, - COLUMN_ACTIVE, COLUMN_IDLE_TIMEOUT, COLUMN_HARD_TIMEOUT, - COLUMN_PRIORITY, COLUMN_COOKIE, COLUMN_WILDCARD, COLUMN_IN_PORT, - COLUMN_DL_SRC, COLUMN_DL_DST, COLUMN_DL_VLAN, COLUMN_DL_VLAN_PCP, - COLUMN_DL_TYPE, COLUMN_NW_TOS, COLUMN_NW_PROTO, COLUMN_NW_SRC, - COLUMN_NW_DST, COLUMN_TP_DST, COLUMN_TP_SRC, COLUMN_ACTIONS }; - - - protected IFloodlightProviderService floodlightProvider; - protected IStorageSourceService storageSource; - protected IRestApiService restApi; - - private IHAListener haListener; - - // Map<DPID, Map<Name, FlowMod>>; FlowMod can be null to indicate non-active - protected Map<String, Map<String, OFFlowMod>> entriesFromStorage; - // Entry Name -> DPID of Switch it's on - protected Map<String, String> entry2dpid; - - // Class to sort FlowMod's by priority, from lowest to highest - class FlowModSorter implements Comparator<String> { - private String dpid; - public FlowModSorter(String dpid) { - this.dpid = dpid; - } - @Override - public int compare(String o1, String o2) { - OFFlowMod f1 = entriesFromStorage.get(dpid).get(o1); - OFFlowMod f2 = entriesFromStorage.get(dpid).get(o2); - if (f1 == null || f2 == null) // sort active=false flows by key - return o1.compareTo(o2); - return U16.f(f1.getPriority()) - U16.f(f2.getPriority()); - } - }; - - /** - * used for debugging and unittests - * @return the number of static flow entries as cached from storage - */ - public int countEntries() { - int size = 0; - if (entriesFromStorage == null) - return 0; - for (String ofswitch : entriesFromStorage.keySet()) - size += entriesFromStorage.get(ofswitch).size(); - return size; - } - - public IFloodlightProviderService getFloodlightProvider() { - return floodlightProvider; - } - - public void setFloodlightProvider(IFloodlightProviderService floodlightProvider) { - this.floodlightProvider = floodlightProvider; - } - - public void setStorageSource(IStorageSourceService storageSource) { - this.storageSource = storageSource; - } - - /** - * Reads from our entriesFromStorage for the specified switch and - * sends the FlowMods down to the controller in <b>sorted</b> order. - * - * Sorted is important to maintain correctness of the switch: - * if a packet would match both a lower and a higher priority - * rule, then we want it to match the higher priority or nothing, - * but never just the lower priority one. Inserting from high to - * low priority fixes this. - * - * TODO consider adding a "block all" flow mod and then removing it - * while starting up. - * - * @param sw The switch to send entries to - */ - protected void sendEntriesToSwitch(long switchId) { - IOFSwitch sw = floodlightProvider.getSwitch(switchId); - if (sw == null) - return; - String stringId = sw.getStringId(); - - if ((entriesFromStorage != null) && (entriesFromStorage.containsKey(stringId))) { - Map<String, OFFlowMod> entries = entriesFromStorage.get(stringId); - List<String> sortedList = new ArrayList<String>(entries.keySet()); - // weird that Collections.sort() returns void - Collections.sort( sortedList, new FlowModSorter(stringId)); - for (String entryName : sortedList) { - OFFlowMod flowMod = entries.get(entryName); - if (flowMod != null) { - if (log.isDebugEnabled()) { - log.debug("Pushing static entry {} for {}", stringId, entryName); - } - writeFlowModToSwitch(sw, flowMod); - } - } - } - } - - /** - * Used only for bundle-local indexing - * - * @param map - * @return - */ - - protected Map<String, String> computeEntry2DpidMap( - Map<String, Map<String, OFFlowMod>> map) { - Map<String, String> ret = new ConcurrentHashMap<String, String>(); - for(String dpid : map.keySet()) { - for( String entry: map.get(dpid).keySet()) - ret.put(entry, dpid); - } - return ret; - } - - /** - * Read entries from storageSource, and store them in a hash - * - * @return - */ - @LogMessageDoc(level="ERROR", - message="failed to access storage: {reason}", - explanation="Could not retrieve static flows from the system " + - "database", - recommendation=LogMessageDoc.CHECK_CONTROLLER) - private Map<String, Map<String, OFFlowMod>> readEntriesFromStorage() { - Map<String, Map<String, OFFlowMod>> entries = new ConcurrentHashMap<String, Map<String, OFFlowMod>>(); - try { - Map<String, Object> row; - // null1=no predicate, null2=no ordering - IResultSet resultSet = storageSource.executeQuery(TABLE_NAME, - ColumnNames, null, null); - for (Iterator<IResultSet> it = resultSet.iterator(); it.hasNext();) { - row = it.next().getRow(); - parseRow(row, entries); - } - } catch (StorageException e) { - log.error("failed to access storage: {}", e.getMessage()); - // if the table doesn't exist, then wait to populate later via - // setStorageSource() - } - return entries; - } - - /** - * Take a single row, turn it into a flowMod, and add it to the - * entries{$dpid}.{$entryName}=FlowMod - * - * IF an entry is in active, mark it with FlowMod = null - * - * @param row - * @param entries - */ - void parseRow(Map<String, Object> row, Map<String, Map<String, OFFlowMod>> entries) { - String switchName = null; - String entryName = null; - - StringBuffer matchString = new StringBuffer(); - - OFFlowMod flowMod = (OFFlowMod) floodlightProvider.getOFMessageFactory() - .getMessage(OFType.FLOW_MOD); - - if (!row.containsKey(COLUMN_SWITCH) || !row.containsKey(COLUMN_NAME)) { - log.debug( - "skipping entry with missing required 'switch' or 'name' entry: {}", - row); - return; - } - // most error checking done with ClassCastException - try { - // first, snag the required entries, for debugging info - switchName = (String) row.get(COLUMN_SWITCH); - entryName = (String) row.get(COLUMN_NAME); - if (!entries.containsKey(switchName)) - entries.put(switchName, new HashMap<String, OFFlowMod>()); - StaticFlowEntries.initDefaultFlowMod(flowMod, entryName); - - for (String key : row.keySet()) { - if (row.get(key) == null) - continue; - if (key.equals(COLUMN_SWITCH) || key.equals(COLUMN_NAME) - || key.equals("id")) - continue; // already handled - // explicitly ignore timeouts and wildcards - if (key.equals(COLUMN_HARD_TIMEOUT) || key.equals(COLUMN_IDLE_TIMEOUT) || - key.equals(COLUMN_WILDCARD)) - continue; - if (key.equals(COLUMN_ACTIVE)) { - if (!Boolean.valueOf((String) row.get(COLUMN_ACTIVE))) { - log.debug("skipping inactive entry {} for switch {}", - entryName, switchName); - entries.get(switchName).put(entryName, null); // mark this an inactive - return; - } - } else if (key.equals(COLUMN_ACTIONS)){ - StaticFlowEntries.parseActionString(flowMod, (String) row.get(COLUMN_ACTIONS), log); - } else if (key.equals(COLUMN_COOKIE)) { - flowMod.setCookie( - StaticFlowEntries.computeEntryCookie(flowMod, - Integer.valueOf((String) row.get(COLUMN_COOKIE)), - entryName)); - } else if (key.equals(COLUMN_PRIORITY)) { - flowMod.setPriority(U16.t(Integer.valueOf((String) row.get(COLUMN_PRIORITY)))); - } else { // the rest of the keys are for OFMatch().fromString() - if (matchString.length() > 0) - matchString.append(","); - matchString.append(key + "=" + row.get(key).toString()); - } - } - } catch (ClassCastException e) { - if (entryName != null && switchName != null) { - log.warn( - "Skipping entry {} on switch {} with bad data : " - + e.getMessage(), entryName, switchName); - } else { - log.warn("Skipping entry with bad data: {} :: {} ", - e.getMessage(), e.getStackTrace()); - } - } - - OFMatch ofMatch = new OFMatch(); - String match = matchString.toString(); - try { - ofMatch.fromString(match); - } catch (IllegalArgumentException e) { - log.debug( - "ignoring flow entry {} on switch {} with illegal OFMatch() key: " - + match, entryName, switchName); - return; - } - flowMod.setMatch(ofMatch); - - entries.get(switchName).put(entryName, flowMod); - } - - @Override - public void switchAdded(long switchId) { - log.debug("Switch {} connected; processing its static entries", - HexString.toHexString(switchId)); - sendEntriesToSwitch(switchId); - } - - @Override - public void switchRemoved(long switchId) { - // do NOT delete from our internal state; we're tracking the rules, - // not the switches - } - - @Override - public void switchActivated(long switchId) { - // no-op - } - - @Override - public void switchChanged(long switchId) { - // no-op - } - - @Override - public void switchPortChanged(long switchId, - ImmutablePort port, - IOFSwitch.PortChangeType type) { - // no-op - } - - - @Override - public void rowsModified(String tableName, Set<Object> rowKeys) { - // This handles both rowInsert() and rowUpdate() - log.debug("Modifying Table {}", tableName); - HashMap<String, Map<String, OFFlowMod>> entriesToAdd = - new HashMap<String, Map<String, OFFlowMod>>(); - // build up list of what was added - for (Object key: rowKeys) { - IResultSet resultSet = storageSource.getRow(tableName, key); - Iterator<IResultSet> it = resultSet.iterator(); - while (it.hasNext()) { - Map<String, Object> row = it.next().getRow(); - parseRow(row, entriesToAdd); - } - } - // batch updates by switch and blast them out - for (String dpid : entriesToAdd.keySet()) { - if (!entriesFromStorage.containsKey(dpid)) - entriesFromStorage.put(dpid, new HashMap<String, OFFlowMod>()); - - List<OFMessage> outQueue = new ArrayList<OFMessage>(); - for(String entry : entriesToAdd.get(dpid).keySet()) { - OFFlowMod newFlowMod = entriesToAdd.get(dpid).get(entry); - //OFFlowMod oldFlowMod = entriesFromStorage.get(dpid).get(entry); - OFFlowMod oldFlowMod = null; - String dpidOldFlowMod = entry2dpid.get(entry); - if (dpidOldFlowMod != null) { - oldFlowMod = entriesFromStorage.get(dpidOldFlowMod).remove(entry); - } - if (oldFlowMod != null && newFlowMod != null) { - // set the new flow mod to modify a pre-existing rule if these fields match - if(oldFlowMod.getMatch().equals(newFlowMod.getMatch()) - && oldFlowMod.getCookie() == newFlowMod.getCookie() - && oldFlowMod.getPriority() == newFlowMod.getPriority()){ - newFlowMod.setCommand(OFFlowMod.OFPFC_MODIFY_STRICT); - // if they don't match delete the old flow - } else{ - oldFlowMod.setCommand(OFFlowMod.OFPFC_DELETE_STRICT); - if (dpidOldFlowMod.equals(dpid)) { - outQueue.add(oldFlowMod); - } else { - writeOFMessageToSwitch(HexString.toLong(dpidOldFlowMod), oldFlowMod); - } - } - } - // write the new flow - if (newFlowMod != null) { - entriesFromStorage.get(dpid).put(entry, newFlowMod); - outQueue.add(newFlowMod); - entry2dpid.put(entry, dpid); - } else { - entriesFromStorage.get(dpid).remove(entry); - entry2dpid.remove(entry); - } - } - writeOFMessagesToSwitch(HexString.toLong(dpid), outQueue); - } - } - - @Override - public void rowsDeleted(String tableName, Set<Object> rowKeys) { - if (log.isDebugEnabled()) { - log.debug("Deleting from table {}", tableName); - } - - for(Object obj : rowKeys) { - if (!(obj instanceof String)) { - log.debug("Tried to delete non-string key {}; ignoring", obj); - continue; - } - deleteStaticFlowEntry((String) obj); - } - } - - @LogMessageDoc(level="ERROR", - message="inconsistent internal state: no switch has rule {rule}", - explanation="Inconsistent internat state discovered while " + - "deleting a static flow rule", - recommendation=LogMessageDoc.REPORT_CONTROLLER_BUG) - private void deleteStaticFlowEntry(String entryName) { - String dpid = entry2dpid.remove(entryName); - - if (dpid == null) { - // assume state has been cleared by deleteFlowsForSwitch() or - // deleteAllFlows() - return; - } - - if (log.isDebugEnabled()) { - log.debug("Sending delete flow mod for flow {} for switch {}", entryName, dpid); - } - - // send flow_mod delete - OFFlowMod flowMod = entriesFromStorage.get(dpid).get(entryName); - flowMod.setCommand(OFFlowMod.OFPFC_DELETE_STRICT); - - if (entriesFromStorage.containsKey(dpid) && - entriesFromStorage.get(dpid).containsKey(entryName)) { - entriesFromStorage.get(dpid).remove(entryName); - } else { - log.debug("Tried to delete non-existent entry {} for switch {}", - entryName, dpid); - return; - } - - writeFlowModToSwitch(HexString.toLong(dpid), flowMod); - return; - } - - /** - * Writes a list of OFMessages to a switch - * @param dpid The datapath ID of the switch to write to - * @param messages The list of OFMessages to write. - */ - @LogMessageDoc(level="ERROR", - message="Tried to write to switch {switch} but got {error}", - explanation="An I/O error occured while trying to write a " + - "static flow to a switch", - recommendation=LogMessageDoc.CHECK_SWITCH) - private void writeOFMessagesToSwitch(long dpid, List<OFMessage> messages) { - IOFSwitch ofswitch = floodlightProvider.getSwitch(dpid); - if (ofswitch != null) { // is the switch connected - try { - if (log.isDebugEnabled()) { - log.debug("Sending {} new entries to {}", messages.size(), dpid); - } - ofswitch.write(messages, null); - ofswitch.flush(); - } catch (IOException e) { - log.error("Tried to write to switch {} but got {}", dpid, e.getMessage()); - } - } - } - - /** - * Writes a single OFMessage to a switch - * @param dpid The datapath ID of the switch to write to - * @param message The OFMessage to write. - */ - @LogMessageDoc(level="ERROR", - message="Tried to write to switch {switch} but got {error}", - explanation="An I/O error occured while trying to write a " + - "static flow to a switch", - recommendation=LogMessageDoc.CHECK_SWITCH) - private void writeOFMessageToSwitch(long dpid, OFMessage message) { - IOFSwitch ofswitch = floodlightProvider.getSwitch(dpid); - if (ofswitch != null) { // is the switch connected - try { - if (log.isDebugEnabled()) { - log.debug("Sending 1 new entries to {}", HexString.toHexString(dpid)); - } - ofswitch.write(message, null); - ofswitch.flush(); - } catch (IOException e) { - log.error("Tried to write to switch {} but got {}", dpid, e.getMessage()); - } - } - } - - /** - * Writes an OFFlowMod to a switch. It checks to make sure the switch - * exists before it sends - * @param dpid The data to write the flow mod to - * @param flowMod The OFFlowMod to write - */ - private void writeFlowModToSwitch(long dpid, OFFlowMod flowMod) { - IOFSwitch ofSwitch = floodlightProvider.getSwitch(dpid); - if (ofSwitch == null) { - if (log.isDebugEnabled()) { - log.debug("Not deleting key {} :: switch {} not connected", - dpid); - } - return; - } - writeFlowModToSwitch(ofSwitch, flowMod); - } - - /** - * Writes an OFFlowMod to a switch - * @param sw The IOFSwitch to write to - * @param flowMod The OFFlowMod to write - */ - @LogMessageDoc(level="ERROR", - message="Tried to write OFFlowMod to {switch} but got {error}", - explanation="An I/O error occured while trying to write a " + - "static flow to a switch", - recommendation=LogMessageDoc.CHECK_SWITCH) - private void writeFlowModToSwitch(IOFSwitch sw, OFFlowMod flowMod) { - try { - sw.write(flowMod, null); - sw.flush(); - } catch (IOException e) { - log.error("Tried to write OFFlowMod to {} but failed: {}", - HexString.toHexString(sw.getId()), e.getMessage()); - } - } - - @Override - public String getName() { - return StaticFlowName; - } - - /** - * Handles a flow removed message from a switch. If the flow was removed - * and we did not explicitly delete it we re-install it. If we explicitly - * removed the flow we stop the processing of the flow removed message. - * @param sw The switch that sent the flow removed message. - * @param msg The flow removed message. - * @param cntx The associated context. - * @return Whether to continue processing this message. - */ - public Command handleFlowRemoved(IOFSwitch sw, OFFlowRemoved msg, FloodlightContext cntx) { - long cookie = msg.getCookie(); - /** - * This is just to sanity check our assumption that static flows - * never expire. - */ - if (AppCookie.extractApp(cookie) == STATIC_FLOW_APP_ID) { - if (msg.getReason() != OFFlowRemoved.OFFlowRemovedReason.OFPRR_DELETE) - log.error("Got a FlowRemove message for a infinite " + - "timeout flow: {} from switch {}", msg, sw); - // Stop the processing chain since we sent the delete. - return Command.STOP; - } - - return Command.CONTINUE; - } - - @Override - @LogMessageDoc(level="ERROR", - message="Got a FlowRemove message for a infinite " + - "timeout flow: {flow} from switch {switch}", - explanation="Flows with infinite timeouts should not expire. " + - "The switch has expired the flow anyway.", - recommendation=LogMessageDoc.REPORT_SWITCH_BUG) - public Command receive(IOFSwitch sw, OFMessage msg, FloodlightContext cntx) { - switch (msg.getType()) { - case FLOW_REMOVED: - return handleFlowRemoved(sw, (OFFlowRemoved) msg, cntx); - default: - return Command.CONTINUE; - } - } - - @Override - public boolean isCallbackOrderingPrereq(OFType type, String name) { - return false; // no dependency for non-packet in - } - - @Override - public boolean isCallbackOrderingPostreq(OFType type, String name) { - return false; // no dependency for non-packet in - } - - // IFloodlightModule - - @Override - public Collection<Class<? extends IFloodlightService>> getModuleServices() { - Collection<Class<? extends IFloodlightService>> l = - new ArrayList<Class<? extends IFloodlightService>>(); - l.add(IStaticFlowEntryPusherService.class); - return l; - } - - @Override - public Map<Class<? extends IFloodlightService>, IFloodlightService> getServiceImpls() { - Map<Class<? extends IFloodlightService>, - IFloodlightService> m = - new HashMap<Class<? extends IFloodlightService>, - IFloodlightService>(); - m.put(IStaticFlowEntryPusherService.class, this); - return m; - } - - @Override - public Collection<Class<? extends IFloodlightService>> getModuleDependencies() { - Collection<Class<? extends IFloodlightService>> l = - new ArrayList<Class<? extends IFloodlightService>>(); - l.add(IFloodlightProviderService.class); - l.add(IStorageSourceService.class); - l.add(IRestApiService.class); - return l; - } - - @Override - public void init(FloodlightModuleContext context) - throws FloodlightModuleException { - floodlightProvider = - context.getServiceImpl(IFloodlightProviderService.class); - storageSource = - context.getServiceImpl(IStorageSourceService.class); - restApi = - context.getServiceImpl(IRestApiService.class); - haListener = new HAListenerDelegate(); - } - - @Override - public void startUp(FloodlightModuleContext context) { - floodlightProvider.addOFMessageListener(OFType.FLOW_REMOVED, this); - floodlightProvider.addOFSwitchListener(this); - floodlightProvider.addHAListener(this.haListener); - - // assumes no switches connected at startup() - storageSource.createTable(TABLE_NAME, null); - storageSource.setTablePrimaryKeyName(TABLE_NAME, COLUMN_NAME); - storageSource.addListener(TABLE_NAME, this); - entriesFromStorage = readEntriesFromStorage(); - entry2dpid = computeEntry2DpidMap(entriesFromStorage); - restApi.addRestletRoutable(new StaticFlowEntryWebRoutable()); - } - - // IStaticFlowEntryPusherService methods - - @Override - public void addFlow(String name, OFFlowMod fm, String swDpid) { - Map<String, Object> fmMap = StaticFlowEntries.flowModToStorageEntry(fm, swDpid, name); - storageSource.insertRowAsync(TABLE_NAME, fmMap); - } - - @Override - public void deleteFlow(String name) { - storageSource.deleteRowAsync(TABLE_NAME, name); - } - - @Override - public void deleteAllFlows() { - for (String entry : entry2dpid.keySet()) { - deleteFlow(entry); - } - - /* +implements IOFSwitchListener, IFloodlightModule, IStaticFlowEntryPusherService, IStorageSourceListener, IOFMessageListener { + protected static Logger log = LoggerFactory.getLogger(StaticFlowEntryPusher.class); + public static final String StaticFlowName = "staticflowentry"; + + public static final int STATIC_FLOW_APP_ID = 10; + static { + AppCookie.registerApp(STATIC_FLOW_APP_ID, StaticFlowName); + } + + public static final String TABLE_NAME = "controller_staticflowtableentry"; + public static final String COLUMN_NAME = "name"; + public static final String COLUMN_SWITCH = "switch"; + public static final String COLUMN_ACTIVE = "active"; + public static final String COLUMN_IDLE_TIMEOUT = "idle_timeout"; + public static final String COLUMN_HARD_TIMEOUT = "hard_timeout"; + public static final String COLUMN_PRIORITY = "priority"; + public static final String COLUMN_COOKIE = "cookie"; + + // Common location for Match Strings. Still the same, but relocated. + public static final String COLUMN_IN_PORT = MatchUtils.STR_IN_PORT; + + public static final String COLUMN_DL_SRC = MatchUtils.STR_DL_SRC; + public static final String COLUMN_DL_DST = MatchUtils.STR_DL_DST; + public static final String COLUMN_DL_VLAN = MatchUtils.STR_DL_VLAN; + public static final String COLUMN_DL_VLAN_PCP = MatchUtils.STR_DL_VLAN_PCP; + public static final String COLUMN_DL_TYPE = MatchUtils.STR_DL_TYPE; + + public static final String COLUMN_NW_TOS = MatchUtils.STR_NW_TOS; + public static final String COLUMN_NW_PROTO = MatchUtils.STR_NW_PROTO; + public static final String COLUMN_NW_SRC = MatchUtils.STR_NW_SRC; // includes CIDR-style netmask, e.g. "128.8.128.0/24" + public static final String COLUMN_NW_DST = MatchUtils.STR_NW_DST; + + public static final String COLUMN_TP_SRC = MatchUtils.STR_TP_SRC; + public static final String COLUMN_TP_DST = MatchUtils.STR_TP_DST; + + /* newly added matches for OF1.3 port start here */ + public static final String COLUMN_ICMP_TYPE = MatchUtils.STR_ICMP_TYPE; + public static final String COLUMN_ICMP_CODE = MatchUtils.STR_ICMP_CODE; + + public static final String COLUMN_ARP_OPCODE = MatchUtils.STR_ARP_OPCODE; + public static final String COLUMN_ARP_SHA = MatchUtils.STR_ARP_SHA; + public static final String COLUMN_ARP_DHA = MatchUtils.STR_ARP_DHA; + public static final String COLUMN_ARP_SPA = MatchUtils.STR_ARP_SPA; + public static final String COLUMN_ARP_DPA = MatchUtils.STR_ARP_DPA; + + public static final String COLUMN_MPLS_LABEL = MatchUtils.STR_MPLS_LABEL; + public static final String COLUMN_MPLS_TC = MatchUtils.STR_MPLS_TC; + public static final String COLUMN_MPLS_BOS = MatchUtils.STR_MPLS_BOS; + + public static final String COLUMN_METADATA = MatchUtils.STR_METADATA; + public static final String COLUMN_TUNNEL_ID = MatchUtils.STR_TUNNEL_ID; + + public static final String COLUMN_PBB_ISID = MatchUtils.STR_PBB_ISID; + /* end newly added matches TODO @Ryan should look into full IPv6 support*/ + + public static final String COLUMN_ACTIONS = "actions"; + + public static String ColumnNames[] = { COLUMN_NAME, COLUMN_SWITCH, + COLUMN_ACTIVE, COLUMN_IDLE_TIMEOUT, COLUMN_HARD_TIMEOUT, + COLUMN_PRIORITY, COLUMN_COOKIE, COLUMN_IN_PORT, + COLUMN_DL_SRC, COLUMN_DL_DST, COLUMN_DL_VLAN, COLUMN_DL_VLAN_PCP, + COLUMN_DL_TYPE, COLUMN_NW_TOS, COLUMN_NW_PROTO, COLUMN_NW_SRC, + COLUMN_NW_DST, COLUMN_TP_DST, COLUMN_TP_SRC, + /* newly added matches for OF1.3 port start here */ + COLUMN_ICMP_TYPE, COLUMN_ICMP_CODE, + COLUMN_ARP_OPCODE, COLUMN_ARP_SHA, COLUMN_ARP_DHA, + COLUMN_ARP_SPA, COLUMN_ARP_DPA, + COLUMN_MPLS_LABEL, COLUMN_MPLS_TC, COLUMN_MPLS_BOS, + COLUMN_METADATA, COLUMN_TUNNEL_ID, COLUMN_PBB_ISID, + /* end newly added matches */ + COLUMN_ACTIONS }; + + + protected IFloodlightProviderService floodlightProviderService; + protected IOFSwitchService switchService; + protected IStorageSourceService storageSourceService; + protected IRestApiService restApiService; + + private IHAListener haListener; + + // Map<DPID, Map<Name, FlowMod>>; FlowMod can be null to indicate non-active + protected Map<String, Map<String, OFFlowMod>> entriesFromStorage; + // Entry Name -> DPID of Switch it's on + protected Map<String, String> entry2dpid; + + // Class to sort FlowMod's by priority, from lowest to highest + class FlowModSorter implements Comparator<String> { + private String dpid; + public FlowModSorter(String dpid) { + this.dpid = dpid; + } + @Override + public int compare(String o1, String o2) { + OFFlowMod f1 = entriesFromStorage.get(dpid).get(o1); + OFFlowMod f2 = entriesFromStorage.get(dpid).get(o2); + if (f1 == null || f2 == null) // sort active=false flows by key + return o1.compareTo(o2); + return U16.of(f1.getPriority()).getValue() - U16.of(f2.getPriority()).getValue(); + } + }; + + /** + * used for debugging and unittests + * @return the number of static flow entries as cached from storage + */ + public int countEntries() { + int size = 0; + if (entriesFromStorage == null) + return 0; + for (String ofswitch : entriesFromStorage.keySet()) + size += entriesFromStorage.get(ofswitch).size(); + return size; + } + + public IFloodlightProviderService getFloodlightProvider() { + return floodlightProviderService; + } + + public void setFloodlightProvider(IFloodlightProviderService floodlightProviderService) { + this.floodlightProviderService = floodlightProviderService; + } + + public void setStorageSource(IStorageSourceService storageSourceService) { + this.storageSourceService = storageSourceService; + } + + /** + * Reads from our entriesFromStorage for the specified switch and + * sends the FlowMods down to the controller in <b>sorted</b> order. + * + * Sorted is important to maintain correctness of the switch: + * if a packet would match both a lower and a higher priority + * rule, then we want it to match the higher priority or nothing, + * but never just the lower priority one. Inserting from high to + * low priority fixes this. + * + * TODO consider adding a "block all" flow mod and then removing it + * while starting up. + * + * @param sw The switch to send entries to + */ + protected void sendEntriesToSwitch(DatapathId switchId) { + IOFSwitch sw = switchService.getSwitch(switchId); + if (sw == null) + return; + String stringId = sw.getId().toString(); + + if ((entriesFromStorage != null) && (entriesFromStorage.containsKey(stringId))) { + Map<String, OFFlowMod> entries = entriesFromStorage.get(stringId); + List<String> sortedList = new ArrayList<String>(entries.keySet()); + // weird that Collections.sort() returns void + Collections.sort( sortedList, new FlowModSorter(stringId)); + for (String entryName : sortedList) { + OFFlowMod flowMod = entries.get(entryName); + if (flowMod != null) { + if (log.isDebugEnabled()) { + log.debug("Pushing static entry {} for {}", stringId, entryName); + } + writeFlowModToSwitch(sw, flowMod); + } + } + } + } + + /** + * Used only for bundle-local indexing + * + * @param map + * @return + */ + + protected Map<String, String> computeEntry2DpidMap( + Map<String, Map<String, OFFlowMod>> map) { + Map<String, String> ret = new ConcurrentHashMap<String, String>(); + for(String dpid : map.keySet()) { + for( String entry: map.get(dpid).keySet()) + ret.put(entry, dpid); + } + return ret; + } + + /** + * Read entries from storageSource, and store them in a hash + * + * @return + */ + @LogMessageDoc(level="ERROR", + message="failed to access storage: {reason}", + explanation="Could not retrieve static flows from the system " + + "database", + recommendation=LogMessageDoc.CHECK_CONTROLLER) + private Map<String, Map<String, OFFlowMod>> readEntriesFromStorage() { + Map<String, Map<String, OFFlowMod>> entries = new ConcurrentHashMap<String, Map<String, OFFlowMod>>(); + try { + Map<String, Object> row; + // null1=no predicate, null2=no ordering + IResultSet resultSet = storageSourceService.executeQuery(TABLE_NAME, ColumnNames, null, null); + for (Iterator<IResultSet> it = resultSet.iterator(); it.hasNext();) { + row = it.next().getRow(); + parseRow(row, entries); + } + } catch (StorageException e) { + log.error("failed to access storage: {}", e.getMessage()); + // if the table doesn't exist, then wait to populate later via + // setStorageSource() + } + return entries; + } + + /** + * Take a single row, turn it into a flowMod, and add it to the + * entries{$dpid}.{$entryName}=FlowMod + * + * IF an entry is in active, mark it with FlowMod = null + * + * @param row + * @param entries + */ + void parseRow(Map<String, Object> row, Map<String, Map<String, OFFlowMod>> entries) { + String switchName = null; + String entryName = null; + + StringBuffer matchString = new StringBuffer(); + OFFlowMod.Builder fmb = null; + + if (!row.containsKey(COLUMN_SWITCH) || !row.containsKey(COLUMN_NAME)) { + log.debug("skipping entry with missing required 'switch' or 'name' entry: {}", row); + return; + } + // most error checking done with ClassCastException + try { + // first, snag the required entries, for debugging info + switchName = (String) row.get(COLUMN_SWITCH); + entryName = (String) row.get(COLUMN_NAME); + if (!entries.containsKey(switchName)) { + entries.put(switchName, new HashMap<String, OFFlowMod>()); + } + + // get the correct builder for the OF version supported by the switch + // TODO @Ryan this should arguably be a FlowAdd, not a FlowModify + fmb = OFFactories.getFactory(switchService.getSwitch(DatapathId.of(switchName)).getOFFactory().getVersion()).buildFlowModify(); + + StaticFlowEntries.initDefaultFlowMod(fmb, entryName); + + for (String key : row.keySet()) { + if (row.get(key) == null) + continue; + if (key.equals(COLUMN_SWITCH) || key.equals(COLUMN_NAME) + || key.equals("id")) + continue; // already handled + // explicitly ignore timeouts and wildcards + if (key.equals(COLUMN_HARD_TIMEOUT) || key.equals(COLUMN_IDLE_TIMEOUT)) + continue; + if (key.equals(COLUMN_ACTIVE)) { + if (!Boolean.valueOf((String) row.get(COLUMN_ACTIVE))) { + log.debug("skipping inactive entry {} for switch {}", entryName, switchName); + entries.get(switchName).put(entryName, null); // mark this an inactive + return; + } + } else if (key.equals(COLUMN_ACTIONS)){ + ActionUtils.fromString(fmb, (String) row.get(COLUMN_ACTIONS), log); + } else if (key.equals(COLUMN_COOKIE)) { + fmb.setCookie(StaticFlowEntries.computeEntryCookie(Integer.valueOf((String) row.get(COLUMN_COOKIE)), entryName)); + } else if (key.equals(COLUMN_PRIORITY)) { + fmb.setPriority(U16.t(Integer.valueOf((String) row.get(COLUMN_PRIORITY)))); + } else { // the rest of the keys are for Match().fromString() + if (matchString.length() > 0) { + matchString.append(","); + } + matchString.append(key + "=" + row.get(key).toString()); + } + } + } catch (ClassCastException e) { + if (entryName != null && switchName != null) { + log.warn("Skipping entry {} on switch {} with bad data : " + e.getMessage(), entryName, switchName); + } else { + log.warn("Skipping entry with bad data: {} :: {} ", e.getMessage(), e.getStackTrace()); + } + } + + String match = matchString.toString(); + + try { + //TODO @Ryan new fromString() method here. Should verify it especially + fmb.setMatch(MatchUtils.fromString(match, fmb.getVersion())); + } catch (IllegalArgumentException e) { + log.debug("ignoring flow entry {} on switch {} with illegal OFMatch() key: " + match, entryName, switchName); + return; + } + + entries.get(switchName).put(entryName, fmb.build()); // add the FlowMod message to the table + } + + @Override + public void switchAdded(DatapathId switchId) { + log.debug("Switch {} connected; processing its static entries", + switchId.toString()); + sendEntriesToSwitch(switchId); + } + + @Override + public void switchRemoved(DatapathId switchId) { + // do NOT delete from our internal state; we're tracking the rules, + // not the switches + } + + @Override + public void switchActivated(DatapathId switchId) { + // no-op + } + + @Override + public void switchChanged(DatapathId switchId) { + // no-op + } + + @Override + public void switchPortChanged(DatapathId switchId, + OFPortDesc port, + PortChangeType type) { + // no-op + } + + + @Override + public void rowsModified(String tableName, Set<Object> rowKeys) { + // This handles both rowInsert() and rowUpdate() + log.debug("Modifying Table {}", tableName); + HashMap<String, Map<String, OFFlowMod>> entriesToAdd = + new HashMap<String, Map<String, OFFlowMod>>(); + // build up list of what was added + for (Object key: rowKeys) { + IResultSet resultSet = storageSourceService.getRow(tableName, key); + Iterator<IResultSet> it = resultSet.iterator(); + while (it.hasNext()) { + Map<String, Object> row = it.next().getRow(); + parseRow(row, entriesToAdd); + } + } + // batch updates by switch and blast them out + for (String dpid : entriesToAdd.keySet()) { + if (!entriesFromStorage.containsKey(dpid)) + entriesFromStorage.put(dpid, new HashMap<String, OFFlowMod>()); + + List<OFMessage> outQueue = new ArrayList<OFMessage>(); + for (String entry : entriesToAdd.get(dpid).keySet()) { + OFFlowMod newFlowMod = entriesToAdd.get(dpid).get(entry); + OFFlowMod oldFlowMod = null; + String dpidOldFlowMod = entry2dpid.get(entry); + if (dpidOldFlowMod != null) { + oldFlowMod = entriesFromStorage.get(dpidOldFlowMod).remove(entry); + } + // pre-existing case. should modify or delete, but not add + if (oldFlowMod != null && newFlowMod != null) { + // set the new flow mod to modify a pre-existing rule if these fields match + if (oldFlowMod.getMatch().equals(newFlowMod.getMatch()) + && oldFlowMod.getCookie() == newFlowMod.getCookie() + && oldFlowMod.getPriority() == newFlowMod.getPriority()) { + newFlowMod = FlowModUtils.toFlowModifyStrict(newFlowMod); + // if they don't match delete the old flow + } else { + oldFlowMod = FlowModUtils.toFlowDeleteStrict(oldFlowMod); + if (dpidOldFlowMod.equals(dpid)) { + outQueue.add(oldFlowMod); + } else { + writeOFMessageToSwitch(DatapathId.of(dpidOldFlowMod), oldFlowMod); + } + } + } + // new case. should add a flow, not modify or delete + if (newFlowMod != null) { + OFFlowAdd addTmp = FlowModUtils.toFlowAdd(newFlowMod); + entriesFromStorage.get(dpid).put(entry, addTmp); + outQueue.add(addTmp); + entry2dpid.put(entry, dpid); + } else { + entriesFromStorage.get(dpid).remove(entry); + entry2dpid.remove(entry); + } + } + writeOFMessagesToSwitch(DatapathId.of(dpid), outQueue); + } + } + + @Override + public void rowsDeleted(String tableName, Set<Object> rowKeys) { + if (log.isDebugEnabled()) { + log.debug("Deleting from table {}", tableName); + } + + for(Object obj : rowKeys) { + if (!(obj instanceof String)) { + log.debug("Tried to delete non-string key {}; ignoring", obj); + continue; + } + deleteStaticFlowEntry((String) obj); + } + } + + @LogMessageDoc(level="ERROR", + message="inconsistent internal state: no switch has rule {rule}", + explanation="Inconsistent internat state discovered while " + + "deleting a static flow rule", + recommendation=LogMessageDoc.REPORT_CONTROLLER_BUG) + private void deleteStaticFlowEntry(String entryName) { + String dpid = entry2dpid.remove(entryName); + + if (dpid == null) { + // assume state has been cleared by deleteFlowsForSwitch() or + // deleteAllFlows() + return; + } + + if (log.isDebugEnabled()) { + log.debug("Sending delete flow mod for flow {} for switch {}", entryName, dpid); + } + + // send flow_mod delete + OFFlowDeleteStrict flowMod = FlowModUtils.toFlowDeleteStrict(entriesFromStorage.get(dpid).get(entryName)); + + if (entriesFromStorage.containsKey(dpid) && entriesFromStorage.get(dpid).containsKey(entryName)) { + entriesFromStorage.get(dpid).remove(entryName); + } else { + log.debug("Tried to delete non-existent entry {} for switch {}", entryName, dpid); + return; + } + + writeFlowModToSwitch(DatapathId.of(dpid), flowMod); + return; + } + + /** + * Writes a list of OFMessages to a switch + * @param dpid The datapath ID of the switch to write to + * @param messages The list of OFMessages to write. + */ + @LogMessageDoc(level="ERROR", + message="Tried to write to switch {switch} but got {error}", + explanation="An I/O error occured while trying to write a " + + "static flow to a switch", + recommendation=LogMessageDoc.CHECK_SWITCH) + private void writeOFMessagesToSwitch(DatapathId dpid, List<OFMessage> messages) { + IOFSwitch ofswitch = switchService.getSwitch(dpid); + if (ofswitch != null) { // is the switch connected + if (log.isDebugEnabled()) { + log.debug("Sending {} new entries to {}", messages.size(), dpid); + } + ofswitch.write(messages); + ofswitch.flush(); + } + } + + /** + * Writes a single OFMessage to a switch + * @param dpid The datapath ID of the switch to write to + * @param message The OFMessage to write. + */ + @LogMessageDoc(level="ERROR", + message="Tried to write to switch {switch} but got {error}", + explanation="An I/O error occured while trying to write a " + + "static flow to a switch", + recommendation=LogMessageDoc.CHECK_SWITCH) + private void writeOFMessageToSwitch(DatapathId dpid, OFMessage message) { + IOFSwitch ofswitch = switchService.getSwitch(dpid); + if (ofswitch != null) { // is the switch connected + if (log.isDebugEnabled()) { + log.debug("Sending 1 new entries to {}", dpid.toString()); + } + ofswitch.write(message); + ofswitch.flush(); + } + } + + /** + * Writes an OFFlowMod to a switch. It checks to make sure the switch + * exists before it sends + * @param dpid The data to write the flow mod to + * @param flowMod The OFFlowMod to write + */ + private void writeFlowModToSwitch(DatapathId dpid, OFFlowMod flowMod) { + IOFSwitch ofSwitch = switchService.getSwitch(dpid); + if (ofSwitch == null) { + if (log.isDebugEnabled()) { + log.debug("Not deleting key {} :: switch {} not connected", dpid.toString()); + } + return; + } + writeFlowModToSwitch(ofSwitch, flowMod); + } + + /** + * Writes an OFFlowMod to a switch + * @param sw The IOFSwitch to write to + * @param flowMod The OFFlowMod to write + */ + @LogMessageDoc(level="ERROR", + message="Tried to write OFFlowMod to {switch} but got {error}", + explanation="An I/O error occured while trying to write a " + + "static flow to a switch", + recommendation=LogMessageDoc.CHECK_SWITCH) + private void writeFlowModToSwitch(IOFSwitch sw, OFFlowMod flowMod) { + sw.write(flowMod); + sw.flush(); + } + + @Override + public String getName() { + return StaticFlowName; + } + + /** + * Handles a flow removed message from a switch. If the flow was removed + * and we did not explicitly delete it we re-install it. If we explicitly + * removed the flow we stop the processing of the flow removed message. + * @param sw The switch that sent the flow removed message. + * @param msg The flow removed message. + * @param cntx The associated context. + * @return Whether to continue processing this message. + */ + public Command handleFlowRemoved(IOFSwitch sw, OFFlowRemoved msg, FloodlightContext cntx) { + U64 cookie = msg.getCookie(); + /** + * This is just to sanity check our assumption that static flows + * never expire. + */ + if (AppCookie.extractApp(cookie) == STATIC_FLOW_APP_ID) { + if (OFFlowRemovedReason.DELETE.equals(msg.getReason())) + log.error("Got a FlowRemove message for a infinite " + + "timeout flow: {} from switch {}", msg, sw); + // Stop the processing chain since we sent the delete. + return Command.STOP; + } + + return Command.CONTINUE; + } + + @Override + @LogMessageDoc(level="ERROR", + message="Got a FlowRemove message for a infinite " + + "timeout flow: {flow} from switch {switch}", + explanation="Flows with infinite timeouts should not expire. " + + "The switch has expired the flow anyway.", + recommendation=LogMessageDoc.REPORT_SWITCH_BUG) + public Command receive(IOFSwitch sw, OFMessage msg, FloodlightContext cntx) { + switch (msg.getType()) { + case FLOW_REMOVED: + return handleFlowRemoved(sw, (OFFlowRemoved) msg, cntx); + default: + return Command.CONTINUE; + } + } + + @Override + public boolean isCallbackOrderingPrereq(OFType type, String name) { + return false; // no dependency for non-packet in + } + + @Override + public boolean isCallbackOrderingPostreq(OFType type, String name) { + return false; // no dependency for non-packet in + } + + // IFloodlightModule + + @Override + public Collection<Class<? extends IFloodlightService>> getModuleServices() { + Collection<Class<? extends IFloodlightService>> l = + new ArrayList<Class<? extends IFloodlightService>>(); + l.add(IStaticFlowEntryPusherService.class); + return l; + } + + @Override + public Map<Class<? extends IFloodlightService>, IFloodlightService> getServiceImpls() { + Map<Class<? extends IFloodlightService>, + IFloodlightService> m = + new HashMap<Class<? extends IFloodlightService>, + IFloodlightService>(); + m.put(IStaticFlowEntryPusherService.class, this); + return m; + } + + @Override + public Collection<Class<? extends IFloodlightService>> getModuleDependencies() { + Collection<Class<? extends IFloodlightService>> l = + new ArrayList<Class<? extends IFloodlightService>>(); + l.add(IFloodlightProviderService.class); + l.add(IOFSwitchService.class); + l.add(IStorageSourceService.class); + l.add(IRestApiService.class); + return l; + } + + @Override + public void init(FloodlightModuleContext context) throws FloodlightModuleException { + floodlightProviderService = context.getServiceImpl(IFloodlightProviderService.class); + switchService = context.getServiceImpl(IOFSwitchService.class); + storageSourceService = context.getServiceImpl(IStorageSourceService.class); + restApiService = context.getServiceImpl(IRestApiService.class); + haListener = new HAListenerDelegate(); + } + + @Override + public void startUp(FloodlightModuleContext context) { + floodlightProviderService.addOFMessageListener(OFType.FLOW_REMOVED, this); + switchService.addOFSwitchListener(this); + floodlightProviderService.addHAListener(this.haListener); + // assumes no switches connected at startup() + storageSourceService.createTable(TABLE_NAME, null); + storageSourceService.setTablePrimaryKeyName(TABLE_NAME, COLUMN_NAME); + storageSourceService.addListener(TABLE_NAME, this); + entriesFromStorage = readEntriesFromStorage(); + entry2dpid = computeEntry2DpidMap(entriesFromStorage); + restApiService.addRestletRoutable(new StaticFlowEntryWebRoutable()); + } + + // IStaticFlowEntryPusherService methods + + @Override + public void addFlow(String name, OFFlowMod fm, DatapathId swDpid) { + Map<String, Object> fmMap = StaticFlowEntries.flowModToStorageEntry(fm, swDpid.toString(), name); + storageSourceService.insertRowAsync(TABLE_NAME, fmMap); + } + + @Override + public void deleteFlow(String name) { + storageSourceService.deleteRowAsync(TABLE_NAME, name); + } + + @Override + public void deleteAllFlows() { + for (String entry : entry2dpid.keySet()) { + deleteFlow(entry); + } + + /* FIXME: Since the OF spec 1.0 is not clear on how to match on cookies. Once all switches come to a common implementation we can possibly re-enable this @@ -724,19 +737,19 @@ public class StaticFlowEntryPusher // Reset our DB storageSource.deleteMatchingRowsAsync(TABLE_NAME, null); - */ - } + */ + } - @Override - public void deleteFlowsForSwitch(long dpid) { - String sDpid = HexString.toHexString(dpid); + @Override + public void deleteFlowsForSwitch(DatapathId dpid) { + String sDpid = dpid.toString(); - for (Entry<String, String> e : entry2dpid.entrySet()) { - if (e.getValue().equals(sDpid)) - deleteFlow(e.getKey()); - } + for (Entry<String, String> e : entry2dpid.entrySet()) { + if (e.getValue().equals(sDpid)) + deleteFlow(e.getKey()); + } - /* + /* FIXME: Since the OF spec 1.0 is not clear on how to match on cookies. Once all switches come to a common implementation we can possibly re-enable this @@ -756,17 +769,17 @@ public class StaticFlowEntryPusher } else { log.warn("Map of storage entries for switch {} was null", sDpid); } - */ - } - - /** - * Deletes all flows installed by static flow pusher on a given switch. - * We send a delete flow mod with the static flow pusher app ID in the cookie. - * Since OF1.0 doesn't support masking based on the cookie we have to - * disable having flow specific cookies. - * @param dpid The DPID of the switch to clear all it's flows. - */ - /* + */ + } + + /** + * Deletes all flows installed by static flow pusher on a given switch. + * We send a delete flow mod with the static flow pusher app ID in the cookie. + * Since OF1.0 doesn't support masking based on the cookie we have to + * disable having flow specific cookies. + * @param dpid The DPID of the switch to clear all it's flows. + */ + /* FIXME: Since the OF spec 1.0 is not clear on how to match on cookies. Once all switches come to a common implementation we can possibly re-enable this @@ -799,54 +812,58 @@ public class StaticFlowEntryPusher return; } } - */ - - @Override - public Map<String, Map<String, OFFlowMod>> getFlows() { - return entriesFromStorage; - } - - @Override - public Map<String, OFFlowMod> getFlows(String dpid) { - return entriesFromStorage.get(dpid); - } - - // IHAListener - - private class HAListenerDelegate implements IHAListener { - @Override - public void transitionToMaster() { - log.debug("Re-reading static flows from storage due " + - "to HA change from SLAVE->MASTER"); - entriesFromStorage = readEntriesFromStorage(); - entry2dpid = computeEntry2DpidMap(entriesFromStorage); - } - - @Override - public void controllerNodeIPsChanged( - Map<String, String> curControllerNodeIPs, - Map<String, String> addedControllerNodeIPs, - Map<String, String> removedControllerNodeIPs) { - // ignore - } - - @Override - public String getName() { - return StaticFlowEntryPusher.this.getName(); - } - - @Override - public boolean isCallbackOrderingPrereq(HAListenerTypeMarker type, - String name) { - // TODO Auto-generated method stub - return false; - } - - @Override - public boolean isCallbackOrderingPostreq(HAListenerTypeMarker type, - String name) { - // TODO Auto-generated method stub - return false; - } - } + */ + + @Override + public Map<String, Map<String, OFFlowMod>> getFlows() { + return entriesFromStorage; + } + + @Override + public Map<String, OFFlowMod> getFlows(DatapathId dpid) { + return entriesFromStorage.get(dpid.toString()); + } + + // IHAListener + + private class HAListenerDelegate implements IHAListener { + @Override + public void transitionToActive() { + log.debug("Re-reading static flows from storage due " + + "to HA change from STANDBY->ACTIVE"); + entriesFromStorage = readEntriesFromStorage(); + entry2dpid = computeEntry2DpidMap(entriesFromStorage); + } + + @Override + public void controllerNodeIPsChanged( + Map<String, String> curControllerNodeIPs, + Map<String, String> addedControllerNodeIPs, + Map<String, String> removedControllerNodeIPs) { + // ignore + } + + @Override + public String getName() { + return StaticFlowEntryPusher.this.getName(); + } + + @Override + public boolean isCallbackOrderingPrereq(HAListenerTypeMarker type, + String name) { + return false; + } + + @Override + public boolean isCallbackOrderingPostreq(HAListenerTypeMarker type, + String name) { + return false; + } + + @Override + public void transitionToStandby() { + log.debug("Controller is now in STANDBY role. Clearing static flow entries from store."); + deleteAllFlows(); + } + } } diff --git a/src/main/java/net/floodlightcontroller/staticflowentry/web/ClearStaticFlowEntriesResource.java b/src/main/java/net/floodlightcontroller/staticflowentry/web/ClearStaticFlowEntriesResource.java index 8705282bc358af59e8660e89d906226a65cb21d0..ced2ff535ec20970fb962c4536a85794d2f34dc5 100644 --- a/src/main/java/net/floodlightcontroller/staticflowentry/web/ClearStaticFlowEntriesResource.java +++ b/src/main/java/net/floodlightcontroller/staticflowentry/web/ClearStaticFlowEntriesResource.java @@ -19,7 +19,7 @@ package net.floodlightcontroller.staticflowentry.web; import net.floodlightcontroller.core.web.ControllerSwitchesResource; import net.floodlightcontroller.staticflowentry.IStaticFlowEntryPusherService; -import org.openflow.util.HexString; +import org.projectfloodlight.openflow.types.DatapathId; import org.restlet.data.Status; import org.restlet.resource.Get; import org.restlet.resource.ServerResource; @@ -43,7 +43,7 @@ public class ClearStaticFlowEntriesResource extends ServerResource { sfpService.deleteAllFlows(); } else { try { - sfpService.deleteFlowsForSwitch(HexString.toLong(param)); + sfpService.deleteFlowsForSwitch(DatapathId.of(param)); } catch (NumberFormatException e){ setStatus(Status.CLIENT_ERROR_BAD_REQUEST, ControllerSwitchesResource.DPID_ERROR); diff --git a/src/main/java/net/floodlightcontroller/staticflowentry/web/ListStaticFlowEntriesResource.java b/src/main/java/net/floodlightcontroller/staticflowentry/web/ListStaticFlowEntriesResource.java index b552381f7dbf4d1909a7a2cdf262dd99a61ead45..d9ec69c1eba4f379fb8d02a18ba42f24a29e6118 100644 --- a/src/main/java/net/floodlightcontroller/staticflowentry/web/ListStaticFlowEntriesResource.java +++ b/src/main/java/net/floodlightcontroller/staticflowentry/web/ListStaticFlowEntriesResource.java @@ -22,7 +22,8 @@ import java.util.Map; import net.floodlightcontroller.core.web.ControllerSwitchesResource; import net.floodlightcontroller.staticflowentry.IStaticFlowEntryPusherService; -import org.openflow.protocol.OFFlowMod; +import org.projectfloodlight.openflow.protocol.OFFlowMod; +import org.projectfloodlight.openflow.types.DatapathId; import org.restlet.data.Status; import org.restlet.resource.Get; import org.restlet.resource.ServerResource; @@ -46,14 +47,12 @@ public class ListStaticFlowEntriesResource extends ServerResource { return sfpService.getFlows(); } else { try { - Map<String, Map<String, OFFlowMod>> retMap = - new HashMap<String, Map<String, OFFlowMod>>(); - retMap.put(param, sfpService.getFlows(param)); + Map<String, Map<String, OFFlowMod>> retMap = new HashMap<String, Map<String, OFFlowMod>>(); + retMap.put(param, sfpService.getFlows(DatapathId.of(param))); return retMap; } catch (NumberFormatException e){ - setStatus(Status.CLIENT_ERROR_BAD_REQUEST, - ControllerSwitchesResource.DPID_ERROR); + setStatus(Status.CLIENT_ERROR_BAD_REQUEST, ControllerSwitchesResource.DPID_ERROR); } } return null; diff --git a/src/main/java/net/floodlightcontroller/storage/AbstractStorageSource.java b/src/main/java/net/floodlightcontroller/storage/AbstractStorageSource.java index 3033e99e4fd44eb799045b38f7b947ceddcf3c7b..911c1a12dcc31d06bced558ffbcc87b694eb6474 100644 --- a/src/main/java/net/floodlightcontroller/storage/AbstractStorageSource.java +++ b/src/main/java/net/floodlightcontroller/storage/AbstractStorageSource.java @@ -36,10 +36,8 @@ 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.counter.ICounter; -import net.floodlightcontroller.counter.CounterStore; -import net.floodlightcontroller.counter.ICounterStoreService; -import net.floodlightcontroller.counter.CounterValue.CounterType; +import net.floodlightcontroller.debugcounter.IDebugCounter; +import net.floodlightcontroller.debugcounter.IDebugCounterService; import net.floodlightcontroller.restserver.IRestApiService; import net.floodlightcontroller.storage.web.StorageWebRoutable; @@ -67,7 +65,7 @@ public abstract class AbstractStorageSource protected final static String STORAGE_DELETE_COUNTER_NAME = "StorageDelete"; protected Set<String> allTableNames = new CopyOnWriteArraySet<String>(); - protected ICounterStoreService counterStore; + protected IDebugCounterService debugCounterService; protected ExecutorService executorService = defaultExecutorService; protected IStorageExceptionHandler exceptionHandler; @@ -146,25 +144,26 @@ public abstract class AbstractStorageSource return allTableNames; } - public void setCounterStore(CounterStore counterStore) { - this.counterStore = counterStore; + public void setDebugCounterService(IDebugCounterService dcs) { + debugCounterService = dcs; } protected void updateCounters(String baseName, String tableName) { - if (counterStore != null) { + /*if (debugCounterService != null) { String counterName; if (tableName != null) { updateCounters(baseName, null); - counterName = baseName + CounterStore.TitleDelimitor + tableName; + counterName = baseName + "__" + tableName; //TODO @Ryan __ was CounterStore.Title } else { counterName = baseName; } - ICounter counter = counterStore.getCounter(counterName); + TODO @Ryan not sure what to do about this counter. It seems different than debug counters. + * IDebugCounter counter = debugCounterService.getCounter(counterName); if (counter == null) { counter = counterStore.createCounter(counterName, CounterType.LONG); } counter.increment(); - } + }*/ } @Override @@ -518,7 +517,7 @@ public abstract class AbstractStorageSource Collection<Class<? extends IFloodlightService>> l = new ArrayList<Class<? extends IFloodlightService>>(); l.add(IRestApiService.class); - l.add(ICounterStoreService.class); + l.add(IDebugCounterService.class); return l; } @@ -527,8 +526,8 @@ public abstract class AbstractStorageSource throws FloodlightModuleException { restApi = context.getServiceImpl(IRestApiService.class); - counterStore = - context.getServiceImpl(ICounterStoreService.class); + debugCounterService = + context.getServiceImpl(IDebugCounterService.class); } @Override diff --git a/src/main/java/net/floodlightcontroller/testmodule/TestModule.java b/src/main/java/net/floodlightcontroller/testmodule/TestModule.java new file mode 100644 index 0000000000000000000000000000000000000000..886a33750b3d0fb37b766c2b3eea2f1e59917405 --- /dev/null +++ b/src/main/java/net/floodlightcontroller/testmodule/TestModule.java @@ -0,0 +1,190 @@ +package net.floodlightcontroller.testmodule; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.projectfloodlight.openflow.protocol.OFFactories; +import org.projectfloodlight.openflow.protocol.OFFactory; +import org.projectfloodlight.openflow.protocol.OFFlowAdd; +import org.projectfloodlight.openflow.protocol.OFOxmClass; +import org.projectfloodlight.openflow.protocol.OFPortDesc; +import org.projectfloodlight.openflow.protocol.OFSetConfig; +import org.projectfloodlight.openflow.protocol.OFVersion; +import org.projectfloodlight.openflow.protocol.action.OFAction; +import org.projectfloodlight.openflow.protocol.action.OFActionOutput; +import org.projectfloodlight.openflow.protocol.action.OFActionSetField; +import org.projectfloodlight.openflow.protocol.action.OFActionSetNwSrc; +import org.projectfloodlight.openflow.protocol.match.Match; +import org.projectfloodlight.openflow.protocol.match.MatchField; +import org.projectfloodlight.openflow.protocol.oxm.OFOxm; +import org.projectfloodlight.openflow.protocol.oxm.OFOxmEthSrc; +import org.projectfloodlight.openflow.types.ArpOpcode; +import org.projectfloodlight.openflow.types.DatapathId; +import org.projectfloodlight.openflow.types.EthType; +import org.projectfloodlight.openflow.types.ICMPv4Code; +import org.projectfloodlight.openflow.types.ICMPv4Type; +import org.projectfloodlight.openflow.types.IPv4Address; +import org.projectfloodlight.openflow.types.IpDscp; +import org.projectfloodlight.openflow.types.IpEcn; +import org.projectfloodlight.openflow.types.IpProtocol; +import org.projectfloodlight.openflow.types.MacAddress; +import org.projectfloodlight.openflow.types.OFBufferId; +import org.projectfloodlight.openflow.types.OFMetadata; +import org.projectfloodlight.openflow.types.OFPort; +import org.projectfloodlight.openflow.types.OFValueType; +import org.projectfloodlight.openflow.types.OFVlanVidMatch; +import org.projectfloodlight.openflow.types.TransportPort; +import org.projectfloodlight.openflow.types.U32; +import org.projectfloodlight.openflow.types.U8; +import org.projectfloodlight.openflow.types.VlanPcp; +import org.projectfloodlight.openflow.types.VlanVid; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import net.floodlightcontroller.core.IOFSwitchListener; +import net.floodlightcontroller.core.PortChangeType; +import net.floodlightcontroller.core.internal.IOFSwitchService; +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.staticflowentry.IStaticFlowEntryPusherService; + +public class TestModule implements IFloodlightModule, IOFSwitchListener { + + private static IStaticFlowEntryPusherService sfps; + private static IOFSwitchService switchService; + private static Logger log; + + @Override + public Collection<Class<? extends IFloodlightService>> getModuleServices() { + return null; + } + + @Override + public Map<Class<? extends IFloodlightService>, IFloodlightService> getServiceImpls() { + return null; + } + + @Override + public Collection<Class<? extends IFloodlightService>> getModuleDependencies() { + Collection<Class<? extends IFloodlightService>> l = new ArrayList<Class<? extends IFloodlightService>>(); + l.add(IStaticFlowEntryPusherService.class); + l.add(IOFSwitchService.class); + return l; + } + + @Override + public void init(FloodlightModuleContext context) + throws FloodlightModuleException { + sfps = context.getServiceImpl(IStaticFlowEntryPusherService.class); + switchService = context.getServiceImpl(IOFSwitchService.class); + switchService.addOFSwitchListener(this); + log = LoggerFactory.getLogger(TestModule.class); + if (sfps == null) { + log.error("Static Flow Pusher Service not found!"); + } + } + + @Override + public void startUp(FloodlightModuleContext context) + throws FloodlightModuleException { + + + } + + @Override + public void switchAdded(DatapathId switchId) { + OFFactory factory = switchService.getSwitch(switchId).getOFFactory(); + OFFlowAdd.Builder fmb = factory.buildFlowAdd(); + List<OFAction> actions = new ArrayList<OFAction>(); + Match.Builder mb = factory.buildMatch(); + + /*try { + Thread.sleep(3000); + } catch (InterruptedException e) { + e.printStackTrace(); + }*/ + + //TODO @Ryan set a bunch of matches. Test for an OF1.0 and OF1.3 switch. See what happens if they are incorrectly applied. + /* L2 and ICMP TESTS mb.setExact(MatchField.ETH_TYPE, EthType.IPv4); + mb.setExact(MatchField.ETH_SRC, MacAddress.BROADCAST); + mb.setExact(MatchField.ETH_DST, MacAddress.BROADCAST); + mb.setExact(MatchField.IPV4_SRC, IPv4Address.of("127.1.1.1")); + mb.setExact(MatchField.IPV4_DST, IPv4Address.of("128.2.2.2")); + mb.setExact(MatchField.IP_PROTO, IpProtocol.ICMP); + mb.setExact(MatchField.ICMPV4_CODE, ICMPv4Code.of((short)1)); + mb.setExact(MatchField.ICMPV4_TYPE, ICMPv4Type.ECHO); */ + + /* ARP TESTS mb.setExact(MatchField.ETH_TYPE, EthType.ARP); + mb.setExact(MatchField.ARP_OP, ArpOpcode.REQUEST); + mb.setExact(MatchField.ARP_SHA, MacAddress.BROADCAST); + mb.setExact(MatchField.ARP_SPA, IPv4Address.of("130.127.39.241")); + mb.setExact(MatchField.ARP_THA, MacAddress.BROADCAST); + mb.setExact(MatchField.ARP_TPA, IPv4Address.of("130.127.39.241")); */ + + /* TP, IP OPT, VLAN TESTS */ mb.setExact(MatchField.ETH_TYPE, EthType.IPv4); + mb.setExact(MatchField.VLAN_PCP, VlanPcp.of((byte) 1)); // might as well test these now too + mb.setExact(MatchField.VLAN_VID, OFVlanVidMatch.ofVlan(512)); + mb.setExact(MatchField.MPLS_LABEL, U32.of(32)); + mb.setExact(MatchField.MPLS_TC, U8.of((short)64)); + mb.setExact(MatchField.IP_ECN, IpEcn.ECN_10); // and these + mb.setExact(MatchField.IP_DSCP, IpDscp.DSCP_16); + mb.setExact(MatchField.IP_PROTO, IpProtocol.UDP); // with tcp, udp, sctp + mb.setExact(MatchField.UDP_SRC, TransportPort.of(22)); + mb.setExact(MatchField.UDP_DST, TransportPort.of(80)); + + /* MPLS TESTS mb.setExact(MatchField.ETH_TYPE, EthType.MPLS_MULTICAST); + mb.setExact(MatchField.MPLS_LABEL, U32.of(18)); + mb.setExact(MatchField.MPLS_TC, U8.of((short)4));*/ + + /* METADATA TEST + mb.setExact(MatchField.METADATA, OFMetadata.ofRaw(1)); */ + + + //TODO @Ryan set a bunch of actions. "" "" """ """""" + OFActionOutput.Builder actionBuilder = factory.actions().buildOutput(); + actions.add(factory.actions().output(OFPort.of(1), Integer.MAX_VALUE)); + actions.add(factory.actions().setField(factory.oxms().ethSrc(MacAddress.BROADCAST))); + actions.add(factory.actions().setField(factory.oxms().ethDst(MacAddress.BROADCAST))); + actions.add(factory.actions().setField(factory.oxms().ipv4Src(IPv4Address.of("127.0.1.2")))); + actions.add(factory.actions().setField(factory.oxms().ipv4Src(IPv4Address.of("128.0.3.4")))); + + + fmb.setActions(actions); + fmb.setMatch(mb.build()); + + sfps.addFlow("test-flow", fmb.build(), switchId); + //sfps.deleteFlow("test-flow"); + + } + + @Override + public void switchRemoved(DatapathId switchId) { + // TODO Auto-generated method stub + + } + + @Override + public void switchActivated(DatapathId switchId) { + // TODO Auto-generated method stub + + } + + @Override + public void switchPortChanged(DatapathId switchId, OFPortDesc port, + PortChangeType type) { + // TODO Auto-generated method stub + + } + + @Override + public void switchChanged(DatapathId switchId) { + // TODO Auto-generated method stub + + } + +} diff --git a/src/main/java/net/floodlightcontroller/topology/Cluster.java b/src/main/java/net/floodlightcontroller/topology/Cluster.java index eb1489fb1a99a5c1b6efcacb8c8f74f885cd4616..914740f10246698c95c7cf0085cb09465c79a709 100644 --- a/src/main/java/net/floodlightcontroller/topology/Cluster.java +++ b/src/main/java/net/floodlightcontroller/topology/Cluster.java @@ -23,57 +23,51 @@ import java.util.Set; import net.floodlightcontroller.routing.Link; -import org.openflow.util.HexString; +import org.projectfloodlight.openflow.types.DatapathId; public class Cluster { - protected long id; // the lowest id of the nodes - protected Map<Long, Set<Link>> links; // set of links connected to a node. + protected DatapathId id; // the lowest id of the nodes + protected Map<DatapathId, Set<Link>> links; // set of links connected to a node. public Cluster() { - id = Long.MAX_VALUE; - links = new HashMap<Long, Set<Link>>(); + id = DatapathId.NONE; + links = new HashMap<DatapathId, Set<Link>>(); } - public long getId() { + public DatapathId getId() { return id; } - public void setId(long id) { + public void setId(DatapathId id) { this.id = id; } - public Map<Long, Set<Link>> getLinks() { + public Map<DatapathId, Set<Link>> getLinks() { return links; } - public Set<Long> getNodes() { + public Set<DatapathId> getNodes() { return links.keySet(); } - void add(long n) { + void add(DatapathId n) { if (links.containsKey(n) == false) { links.put(n, new HashSet<Link>()); - if (n < id) id = n; + if (n.getLong() < id.getLong()) id = n; } } void addLink(Link l) { - if (links.containsKey(l.getSrc()) == false) { - links.put(l.getSrc(), new HashSet<Link>()); - if (l.getSrc() < id) id = l.getSrc(); - } + add(l.getSrc()); links.get(l.getSrc()).add(l); - if (links.containsKey(l.getDst()) == false) { - links.put(l.getDst(), new HashSet<Link>()); - if (l.getDst() < id) id = l.getDst(); - } + add(l.getDst()); links.get(l.getDst()).add(l); } @Override public int hashCode() { - return (int) (id + id >>>32); + return (int) (id.getLong() + id.getLong() >>>32); } @Override @@ -90,6 +84,6 @@ public class Cluster { } public String toString() { - return "[Cluster id=" + HexString.toHexString(id) + ", " + links.keySet() + "]"; + return "[Cluster id=" + id.toString() + ", " + links.keySet() + "]"; } } diff --git a/src/main/java/net/floodlightcontroller/topology/ITopologyService.java b/src/main/java/net/floodlightcontroller/topology/ITopologyService.java index d518b80d21320095b9baaa3f5223768c414a414a..d6bd177550852501cded815cc2132abc9dc868b0 100644 --- a/src/main/java/net/floodlightcontroller/topology/ITopologyService.java +++ b/src/main/java/net/floodlightcontroller/topology/ITopologyService.java @@ -19,192 +19,195 @@ package net.floodlightcontroller.topology; import java.util.Date; import java.util.Set; +import org.projectfloodlight.openflow.types.DatapathId; +import org.projectfloodlight.openflow.types.OFPort; + import net.floodlightcontroller.core.module.IFloodlightService; public interface ITopologyService extends IFloodlightService { - public void addListener(ITopologyListener listener); - - public Date getLastUpdateTime(); - - /** - * Query to determine if devices must be learned on a given switch port. - */ - public boolean isAttachmentPointPort(long switchid, short port); - public boolean isAttachmentPointPort(long switchid, short port, - boolean tunnelEnabled); - - public long getOpenflowDomainId(long switchId); - public long getOpenflowDomainId(long switchId, boolean tunnelEnabled); - - /** - * Returns the identifier of the L2 domain of a given switch. - * @param switchId The DPID of the switch in long form - * @return The DPID of the switch that is the key for the cluster - */ - public long getL2DomainId(long switchId); - public long getL2DomainId(long switchId, boolean tunnelEnabled); - - /** - * Queries whether two switches are in the same cluster. - * @param switch1 - * @param switch2 - * @return true if the switches are in the same cluster - */ - public boolean inSameOpenflowDomain(long switch1, long switch2); - public boolean inSameOpenflowDomain(long switch1, long switch2, - boolean tunnelEnabled); - - public Set<Long> getSwitchesInOpenflowDomain(long switchDPID); - public Set<Long> getSwitchesInOpenflowDomain(long switchDPID, - boolean tunnelEnabled); - - /** - * Queries whether two switches are in the same island. - * Currently, island and cluster are the same. In future, - * islands could be different than clusters. - * @param switch1 - * @param switch2 - * @return True of they are in the same island, false otherwise - */ - public boolean inSameL2Domain(long switch1, long switch2); - public boolean inSameL2Domain(long switch1, long switch2, - boolean tunnelEnabled); - - public boolean isBroadcastDomainPort(long sw, short port); - public boolean isBroadcastDomainPort(long sw, short port, - boolean tunnelEnabled); - - - public boolean isAllowed(long sw, short portId); - public boolean isAllowed(long sw, short portId, boolean tunnelEnabled); - - /** - * Indicates if an attachment point on the new switch port is consistent - * with the attachment point on the old switch port or not. - */ - public boolean isConsistent(long oldSw, short oldPort, - long newSw, short newPort); - public boolean isConsistent(long oldSw, short oldPort, - long newSw, short newPort, - boolean tunnelEnabled); - - /** - * Indicates if the two switch ports are connected to the same - * broadcast domain or not. - * @param s1 - * @param p1 - * @param s2 - * @param p2 - * @return - */ - public boolean isInSameBroadcastDomain(long s1, short p1, - long s2, short p2); - public boolean isInSameBroadcastDomain(long s1, short p1, - long s2, short p2, - boolean tunnelEnabled); - - /** - * Gets a list of ports on a given switch that are known to topology. - * @param sw The switch DPID in long - * @return The set of ports on this switch - */ - public Set<Short> getPortsWithLinks(long sw); - public Set<Short> getPortsWithLinks(long sw, boolean tunnelEnabled); - - /** Get broadcast ports on a target switch for a given attachmentpoint - * point port. - */ - public Set<Short> getBroadcastPorts(long targetSw, long src, short srcPort); - - public Set<Short> getBroadcastPorts(long targetSw, long src, short srcPort, - boolean tunnelEnabled); - - /** - * - */ - public boolean isIncomingBroadcastAllowed(long sw, short portId); - public boolean isIncomingBroadcastAllowed(long sw, short portId, - boolean tunnelEnabled); - - - /** Get the proper outgoing switchport for a given pair of src-dst - * switchports. - */ - public NodePortTuple getOutgoingSwitchPort(long src, short srcPort, - long dst, short dstPort); - - - public NodePortTuple getOutgoingSwitchPort(long src, short srcPort, - long dst, short dstPort, - boolean tunnelEnabled); - - - public NodePortTuple getIncomingSwitchPort(long src, short srcPort, - long dst, short dstPort); - public NodePortTuple getIncomingSwitchPort(long src, short srcPort, - long dst, short dstPort, - boolean tunnelEnabled); - - /** - * If the dst is not allowed by the higher-level topology, - * this method provides the topologically equivalent broadcast port. - * @param src - * @param dst - * @return the allowed broadcast port - */ - public NodePortTuple - getAllowedOutgoingBroadcastPort(long src, - short srcPort, - long dst, - short dstPort); - - public NodePortTuple - getAllowedOutgoingBroadcastPort(long src, - short srcPort, - long dst, - short dstPort, - boolean tunnelEnabled); - - /** - * If the src broadcast domain port is not allowed for incoming - * broadcast, this method provides the topologically equivalent - * incoming broadcast-allowed - * src port. - * @param src - * @param dst - * @return the allowed broadcast port - */ - public NodePortTuple - getAllowedIncomingBroadcastPort(long src, - short srcPort); - - public NodePortTuple - getAllowedIncomingBroadcastPort(long src, - short srcPort, - boolean tunnelEnabled); - - - /** - * Gets the set of ports that belong to a broadcast domain. - * @return The set of ports that belong to a broadcast domain. - */ - public Set<NodePortTuple> getBroadcastDomainPorts(); - public Set<NodePortTuple> getTunnelPorts(); - - - /** - * Returns a set of blocked ports. The set of blocked - * ports is the union of all the blocked ports across all - * instances. - * @return - */ - public Set<NodePortTuple> getBlockedPorts(); - - /** - * Returns the enabled, non quarantined ports of the given switch. Returns - * an empty set if switch doesn't exists, doesn't have any enabled port, or - * has only quarantined ports. Will never return null. - */ - public Set<Short> getPorts(long sw); + public void addListener(ITopologyListener listener); + + public Date getLastUpdateTime(); + + /** + * Query to determine if devices must be learned on a given switch port. + */ + public boolean isAttachmentPointPort(DatapathId switchid, OFPort port); + public boolean isAttachmentPointPort(DatapathId switchid, OFPort port, + boolean tunnelEnabled); + + public DatapathId getOpenflowDomainId(DatapathId switchId); + public DatapathId getOpenflowDomainId(DatapathId switchId, boolean tunnelEnabled); + + /** + * Returns the identifier of the L2 domain of a given switch. + * @param switchId The DPID of the switch in long form + * @return The DPID of the switch that is the key for the cluster + */ + public DatapathId getL2DomainId(DatapathId switchId); + public DatapathId getL2DomainId(DatapathId switchId, boolean tunnelEnabled); + + /** + * Queries whether two switches are in the same cluster. + * @param switch1 + * @param switch2 + * @return true if the switches are in the same cluster + */ + public boolean inSameOpenflowDomain(DatapathId switch1, DatapathId switch2); + public boolean inSameOpenflowDomain(DatapathId switch1, DatapathId switch2, + boolean tunnelEnabled); + + public Set<DatapathId> getSwitchesInOpenflowDomain(DatapathId switchDPID); + public Set<DatapathId> getSwitchesInOpenflowDomain(DatapathId switchDPID, + boolean tunnelEnabled); + + /** + * Queries whether two switches are in the same island. + * Currently, island and cluster are the same. In future, + * islands could be different than clusters. + * @param switch1 + * @param switch2 + * @return True of they are in the same island, false otherwise + */ + public boolean inSameL2Domain(DatapathId switch1, DatapathId switch2); + public boolean inSameL2Domain(DatapathId switch1, DatapathId switch2, + boolean tunnelEnabled); + + public boolean isBroadcastDomainPort(DatapathId sw, OFPort port); + public boolean isBroadcastDomainPort(DatapathId sw, OFPort port, + boolean tunnelEnabled); + + + public boolean isAllowed(DatapathId sw, OFPort portId); + public boolean isAllowed(DatapathId sw, OFPort portId, boolean tunnelEnabled); + + /** + * Indicates if an attachment point on the new switch port is consistent + * with the attachment point on the old switch port or not. + */ + public boolean isConsistent(DatapathId oldSw, OFPort oldPort, + DatapathId newSw, OFPort newPort); + public boolean isConsistent(DatapathId oldSw, OFPort oldPort, + DatapathId newSw, OFPort newPort, + boolean tunnelEnabled); + + /** + * Indicates if the two switch ports are connected to the same + * broadcast domain or not. + * @param s1 + * @param p1 + * @param s2 + * @param p2 + * @return + */ + public boolean isInSameBroadcastDomain(DatapathId s1, OFPort p1, + DatapathId s2, OFPort p2); + public boolean isInSameBroadcastDomain(DatapathId s1, OFPort p1, + DatapathId s2, OFPort p2, + boolean tunnelEnabled); + + /** + * Gets a list of ports on a given switch that are known to topology. + * @param sw The switch DPID in long + * @return The set of ports on this switch + */ + public Set<OFPort> getPortsWithLinks(DatapathId sw); + public Set<OFPort> getPortsWithLinks(DatapathId sw, boolean tunnelEnabled); + + /** Get broadcast ports on a target switch for a given attachmentpoint + * point port. + */ + public Set<OFPort> getBroadcastPorts(DatapathId targetSw, DatapathId src, OFPort srcPort); + + public Set<OFPort> getBroadcastPorts(DatapathId targetSw, DatapathId src, OFPort srcPort, + boolean tunnelEnabled); + + /** + * + */ + public boolean isIncomingBroadcastAllowed(DatapathId sw, OFPort portId); + public boolean isIncomingBroadcastAllowed(DatapathId sw, OFPort portId, + boolean tunnelEnabled); + + + /** Get the proper outgoing switchport for a given pair of src-dst + * switchports. + */ + public NodePortTuple getOutgoingSwitchPort(DatapathId src, OFPort srcPort, + DatapathId dst, OFPort dstPort); + + + public NodePortTuple getOutgoingSwitchPort(DatapathId src, OFPort srcPort, + DatapathId dst, OFPort dstPort, + boolean tunnelEnabled); + + + public NodePortTuple getIncomingSwitchPort(DatapathId src, OFPort srcPort, + DatapathId dst, OFPort dstPort); + public NodePortTuple getIncomingSwitchPort(DatapathId src, OFPort srcPort, + DatapathId dst, OFPort dstPort, + boolean tunnelEnabled); + + /** + * If the dst is not allowed by the higher-level topology, + * this method provides the topologically equivalent broadcast port. + * @param src + * @param dst + * @return the allowed broadcast port + */ + public NodePortTuple + getAllowedOutgoingBroadcastPort(DatapathId src, + OFPort srcPort, + DatapathId dst, + OFPort dstPort); + + public NodePortTuple + getAllowedOutgoingBroadcastPort(DatapathId src, + OFPort srcPort, + DatapathId dst, + OFPort dstPort, + boolean tunnelEnabled); + + /** + * If the src broadcast domain port is not allowed for incoming + * broadcast, this method provides the topologically equivalent + * incoming broadcast-allowed + * src port. + * @param src + * @param dst + * @return the allowed broadcast port + */ + public NodePortTuple + getAllowedIncomingBroadcastPort(DatapathId src, + OFPort srcPort); + + public NodePortTuple + getAllowedIncomingBroadcastPort(DatapathId src, + OFPort srcPort, + boolean tunnelEnabled); + + + /** + * Gets the set of ports that belong to a broadcast domain. + * @return The set of ports that belong to a broadcast domain. + */ + public Set<NodePortTuple> getBroadcastDomainPorts(); + public Set<NodePortTuple> getTunnelPorts(); + + + /** + * Returns a set of blocked ports. The set of blocked + * ports is the union of all the blocked ports across all + * instances. + * @return + */ + public Set<NodePortTuple> getBlockedPorts(); + + /** + * Returns the enabled, non quarantined ports of the given switch. Returns + * an empty set if switch doesn't exists, doesn't have any enabled port, or + * has only quarantined ports. Will never return null. + */ + public Set<OFPort> getPorts(DatapathId sw); } diff --git a/src/main/java/net/floodlightcontroller/topology/NodePortTuple.java b/src/main/java/net/floodlightcontroller/topology/NodePortTuple.java index 8c5645143ac0edc54df86582d9b83273bd30ed56..8df5bcb83e8b8ed11d2dac7cdbf7556d82ce8192 100644 --- a/src/main/java/net/floodlightcontroller/topology/NodePortTuple.java +++ b/src/main/java/net/floodlightcontroller/topology/NodePortTuple.java @@ -21,7 +21,9 @@ import net.floodlightcontroller.core.web.serializers.UShortSerializer; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import org.openflow.util.HexString; + +import org.projectfloodlight.openflow.types.DatapathId; +import org.projectfloodlight.openflow.types.OFPort; /** * A NodePortTuple is similar to a SwitchPortTuple @@ -31,51 +33,46 @@ import org.openflow.util.HexString; */ public class NodePortTuple implements Comparable<NodePortTuple> { - protected long nodeId; // switch DPID - protected short portId; // switch port id + protected DatapathId nodeId; // switch DPID + protected OFPort portId; // switch port id /** * Creates a NodePortTuple * @param nodeId The DPID of the switch * @param portId The port of the switch */ - public NodePortTuple(long nodeId, short portId) { + public NodePortTuple(DatapathId nodeId, OFPort portId) { this.nodeId = nodeId; this.portId = portId; } - public NodePortTuple(long nodeId, int portId) { - this.nodeId = nodeId; - this.portId = (short) portId; - } - @JsonProperty("switch") @JsonSerialize(using=DPIDSerializer.class) - public long getNodeId() { + public DatapathId getNodeId() { return nodeId; } - public void setNodeId(long nodeId) { + public void setNodeId(DatapathId nodeId) { this.nodeId = nodeId; } @JsonProperty("port") @JsonSerialize(using=UShortSerializer.class) - public short getPortId() { + public OFPort getPortId() { return portId; } - public void setPortId(short portId) { + public void setPortId(OFPort portId) { this.portId = portId; } public String toString() { - return "[id=" + HexString.toHexString(nodeId) + ", port=" + new Short(portId) + "]"; + return "[id=" + nodeId.toString() + ", port=" + portId.toString() + "]"; } @Override public int hashCode() { final int prime = 31; int result = 1; - result = prime * result + (int) (nodeId ^ (nodeId >>> 32)); - result = prime * result + portId; + result = prime * result + (int) (nodeId.getLong() ^ (nodeId.getLong() >>> 32)); + result = prime * result + portId.getPortNumber(); return result; } @@ -102,7 +99,7 @@ public class NodePortTuple implements Comparable<NodePortTuple> { * @return */ public String toKeyString() { - return (HexString.toHexString(nodeId)+ "|" + (portId & 0xffff)); + return (nodeId.toString()+ "|" + portId.toString()); } @Override @@ -111,14 +108,14 @@ public class NodePortTuple implements Comparable<NodePortTuple> { final int EQUAL = 0; final int AFTER = 1; - if (this.getNodeId() < obj.getNodeId()) + if (this.getNodeId().getLong() < obj.getNodeId().getLong()) return BEFORE; - if (this.getNodeId() > obj.getNodeId()) + if (this.getNodeId().getLong() > obj.getNodeId().getLong()) return AFTER; - if (this.getPortId() < obj.getPortId()) + if (this.getPortId().getPortNumber() < obj.getPortId().getPortNumber()) return BEFORE; - if (this.getPortId() > obj.getPortId()) + if (this.getPortId().getPortNumber() > obj.getPortId().getPortNumber()) return AFTER; return EQUAL; diff --git a/src/main/java/net/floodlightcontroller/topology/TopologyInstance.java b/src/main/java/net/floodlightcontroller/topology/TopologyInstance.java index a143d45e73e6bff892766999926487fb6fcb2f03..57d27231d07e02d3fb16688599eba9fe94b354db 100644 --- a/src/main/java/net/floodlightcontroller/topology/TopologyInstance.java +++ b/src/main/java/net/floodlightcontroller/topology/TopologyInstance.java @@ -25,7 +25,9 @@ import java.util.Map; import java.util.PriorityQueue; import java.util.Set; - +import org.projectfloodlight.openflow.types.DatapathId; +import org.projectfloodlight.openflow.types.OFPort; +import org.projectfloodlight.openflow.types.U64; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -59,7 +61,7 @@ public class TopologyInstance { protected static Logger log = LoggerFactory.getLogger(TopologyInstance.class); - protected Map<Long, Set<Short>> switchPorts; // Set of ports for each switch + protected Map<DatapathId, Set<OFPort>> switchPorts; // Set of ports for each switch /** Set of switch ports that are marked as blocked. A set of blocked * switch ports may be provided at the time of instantiation. In addition, * we may add additional ports to this set. @@ -69,17 +71,17 @@ public class TopologyInstance { /** Set of links that are blocked. */ protected Set<Link> blockedLinks; - protected Set<Long> switches; + protected Set<DatapathId> switches; protected Set<NodePortTuple> broadcastDomainPorts; protected Set<NodePortTuple> tunnelPorts; protected Set<Cluster> clusters; // set of openflow domains - protected Map<Long, Cluster> switchClusterMap; // switch to OF domain map + protected Map<DatapathId, Cluster> switchClusterMap; // switch to OF domain map // States for routing - protected Map<Long, BroadcastTree> destinationRootedTrees; - protected Map<Long, Set<NodePortTuple>> clusterBroadcastNodePorts; - protected Map<Long, BroadcastTree> clusterBroadcastTrees; + protected Map<DatapathId, BroadcastTree> destinationRootedTrees; + protected Map<DatapathId, Set<NodePortTuple>> clusterBroadcastNodePorts; + protected Map<DatapathId, BroadcastTree> clusterBroadcastTrees; protected class PathCacheLoader extends CacheLoader<RouteId, Route> { TopologyInstance ti; @@ -99,8 +101,8 @@ public class TopologyInstance { protected LoadingCache<RouteId, Route> pathcache; public TopologyInstance() { - this.switches = new HashSet<Long>(); - this.switchPorts = new HashMap<Long, Set<Short>>(); + 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>(); @@ -108,12 +110,12 @@ public class TopologyInstance { this.blockedLinks = new HashSet<Link>(); } - public TopologyInstance(Map<Long, Set<Short>> switchPorts, + public TopologyInstance(Map<DatapathId, Set<OFPort>> switchPorts, Map<NodePortTuple, Set<Link>> switchPortLinks, Set<NodePortTuple> broadcastDomainPorts) { - this.switches = new HashSet<Long>(switchPorts.keySet()); - this.switchPorts = new HashMap<Long, Set<Short>>(switchPorts); + 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); @@ -122,19 +124,19 @@ public class TopologyInstance { this.blockedLinks = new HashSet<Link>(); clusters = new HashSet<Cluster>(); - switchClusterMap = new HashMap<Long, Cluster>(); + switchClusterMap = new HashMap<DatapathId, Cluster>(); } - public TopologyInstance(Map<Long, Set<Short>> switchPorts, + public TopologyInstance(Map<DatapathId, Set<OFPort>> switchPorts, Set<NodePortTuple> blockedPorts, Map<NodePortTuple, Set<Link>> switchPortLinks, Set<NodePortTuple> broadcastDomainPorts, Set<NodePortTuple> tunnelPorts){ // copy these structures - this.switches = new HashSet<Long>(switchPorts.keySet()); - this.switchPorts = new HashMap<Long, Set<Short>>(); - for(long sw: switchPorts.keySet()) { - this.switchPorts.put(sw, new HashSet<Short>(switchPorts.get(sw))); + 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.blockedPorts = new HashSet<NodePortTuple>(blockedPorts); @@ -148,10 +150,10 @@ public class TopologyInstance { blockedLinks = new HashSet<Link>(); clusters = new HashSet<Cluster>(); - switchClusterMap = new HashMap<Long, Cluster>(); - destinationRootedTrees = new HashMap<Long, BroadcastTree>(); - clusterBroadcastTrees = new HashMap<Long, BroadcastTree>(); - clusterBroadcastNodePorts = new HashMap<Long, Set<NodePortTuple>>(); + switchClusterMap = new HashMap<DatapathId, Cluster>(); + destinationRootedTrees = new HashMap<DatapathId, BroadcastTree>(); + clusterBroadcastTrees = new HashMap<DatapathId, BroadcastTree>(); + clusterBroadcastNodePorts = new HashMap<DatapathId, Set<NodePortTuple>>(); pathcache = CacheBuilder.newBuilder().concurrencyLevel(4) .maximumSize(1000L) @@ -204,9 +206,9 @@ public class TopologyInstance { } protected void addLinksToOpenflowDomains() { - for(long s: switches) { + for(DatapathId s: switches) { if (switchPorts.get(s) == null) continue; - for (short p: switchPorts.get(s)) { + for (OFPort p: switchPorts.get(s)) { NodePortTuple np = new NodePortTuple(s, p); if (switchPortLinks.get(np) == null) continue; if (isBroadcastDomainPort(np)) continue; @@ -242,17 +244,17 @@ public class TopologyInstance { explanation="The internal state of the topology module is corrupt", recommendation=LogMessageDoc.REPORT_CONTROLLER_BUG) public void identifyOpenflowDomains() { - Map<Long, ClusterDFS> dfsList = new HashMap<Long, ClusterDFS>(); + Map<DatapathId, ClusterDFS> dfsList = new HashMap<DatapathId, ClusterDFS>(); if (switches == null) return; - for (Long key: switches) { + for (DatapathId key: switches) { ClusterDFS cdfs = new ClusterDFS(); dfsList.put(key, cdfs); } - Set<Long> currSet = new HashSet<Long>(); + Set<DatapathId> currSet = new HashSet<DatapathId>(); - for (Long sw: switches) { + for (DatapathId sw: switches) { ClusterDFS cdfs = dfsList.get(sw); if (cdfs == null) { log.error("No DFS object for switch {} found.", sw); @@ -291,15 +293,15 @@ public class TopologyInstance { * @param currSet: Set of nodes in the current cluster in formation * @return long: DSF index to be used when a new node is visited */ - private long dfsTraverse (long parentIndex, long currIndex, long currSw, - Map<Long, ClusterDFS> dfsList, Set <Long> currSet) { + private long dfsTraverse (long parentIndex, long currIndex, DatapathId currSw, + Map<DatapathId, ClusterDFS> dfsList, Set <DatapathId> currSet) { //Get the DFS object corresponding to the current switch ClusterDFS currDFS = dfsList.get(currSw); // Get all the links corresponding to this switch - Set<Long> nodesInMyCluster = new HashSet<Long>(); - Set<Long> myCurrSet = new HashSet<Long>(); + Set<DatapathId> nodesInMyCluster = new HashSet<DatapathId>(); + Set<DatapathId> myCurrSet = new HashSet<DatapathId>(); //Assign the DFS object with right values. currDFS.setVisited(true); @@ -309,14 +311,14 @@ public class TopologyInstance { // Traverse the graph through every outgoing link. if (switchPorts.get(currSw) != null){ - for(Short p: switchPorts.get(currSw)) { + for(OFPort p: switchPorts.get(currSw)) { Set<Link> lset = switchPortLinks.get(new NodePortTuple(currSw, p)); if (lset == null) continue; for(Link l:lset) { - long dstSw = l.getDst(); + DatapathId dstSw = l.getDst(); // ignore incoming links. - if (dstSw == currSw) continue; + if (dstSw.equals(currSw)) continue; // ignore if the destination is already added to // another cluster @@ -368,7 +370,7 @@ public class TopologyInstance { // create a new switch cluster and the switches in the current // set to the switch cluster. Cluster sc = new Cluster(); - for(long sw: currSet){ + for(DatapathId sw: currSet){ sc.add(sw); switchClusterMap.put(sw, sc); } @@ -425,8 +427,8 @@ public class TopologyInstance { } protected class NodeDist implements Comparable<NodeDist> { - private final Long node; - public Long getNode() { + private final DatapathId node; + public DatapathId getNode() { return node; } @@ -435,7 +437,7 @@ public class TopologyInstance { return dist; } - public NodeDist(Long node, int dist) { + public NodeDist(DatapathId node, int dist) { this.node = node; this.dist = dist; } @@ -443,7 +445,7 @@ public class TopologyInstance { @Override public int compareTo(NodeDist o) { if (o.dist == this.dist) { - return (int)(this.node - o.node); + return (int)(this.node.getLong() - o.node.getLong()); } return this.dist - o.dist; } @@ -478,34 +480,34 @@ public class TopologyInstance { } } - protected BroadcastTree dijkstra(Cluster c, Long root, + protected BroadcastTree dijkstra(Cluster c, DatapathId root, Map<Link, Integer> linkCost, boolean isDstRooted) { - HashMap<Long, Link> nexthoplinks = new HashMap<Long, Link>(); + HashMap<DatapathId, Link> nexthoplinks = new HashMap<DatapathId, Link>(); //HashMap<Long, Long> nexthopnodes = new HashMap<Long, Long>(); - HashMap<Long, Integer> cost = new HashMap<Long, Integer>(); + HashMap<DatapathId, Integer> cost = new HashMap<DatapathId, Integer>(); int w; - for (Long node: c.links.keySet()) { + for (DatapathId node: c.links.keySet()) { nexthoplinks.put(node, null); //nexthopnodes.put(node, null); cost.put(node, MAX_PATH_WEIGHT); } - HashMap<Long, Boolean> seen = new HashMap<Long, Boolean>(); + 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(); - Long cnode = n.getNode(); + 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: c.links.get(cnode)) { - Long neighbor; + DatapathId neighbor; if (isDstRooted == true) neighbor = link.getSrc(); else neighbor = link.getDst(); @@ -554,7 +556,7 @@ public class TopologyInstance { } for(Cluster c: clusters) { - for (Long node : c.links.keySet()) { + for (DatapathId node : c.links.keySet()) { BroadcastTree tree = dijkstra(c, node, linkCost, true); destinationRootedTrees.put(node, tree); } @@ -581,9 +583,9 @@ public class TopologyInstance { //log.info("Broadcast Tree {}", tree); Set<NodePortTuple> nptSet = new HashSet<NodePortTuple>(); - Map<Long, Link> links = tree.getLinks(); + Map<DatapathId, Link> links = tree.getLinks(); if (links == null) continue; - for(long 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()); @@ -597,17 +599,15 @@ public class TopologyInstance { protected Route buildroute(RouteId id) { NodePortTuple npt; - long srcId = id.getSrc(); - long dstId = id.getDst(); + DatapathId srcId = id.getSrc(); + DatapathId dstId = id.getDst(); - LinkedList<NodePortTuple> switchPorts = - new LinkedList<NodePortTuple>(); + LinkedList<NodePortTuple> switchPorts = new LinkedList<NodePortTuple>(); if (destinationRootedTrees == null) return null; if (destinationRootedTrees.get(dstId) == null) return null; - Map<Long, Link> nexthoplinks = - destinationRootedTrees.get(dstId).getLinks(); + Map<DatapathId, Link> nexthoplinks = destinationRootedTrees.get(dstId).getLinks(); if (!switches.contains(srcId) || !switches.contains(dstId)) { // This is a switch that is not connected to any other switch @@ -618,7 +618,7 @@ public class TopologyInstance { // The only possible non-null path for this case is // if srcId equals dstId --- and that too is an 'empty' path [] - } else if ((nexthoplinks!=null) && (nexthoplinks.get(srcId)!=null)) { + } else if ((nexthoplinks!=null) && (nexthoplinks.get(srcId) != null)) { while (srcId != dstId) { Link l = nexthoplinks.get(srcId); @@ -632,15 +632,16 @@ public class TopologyInstance { // else, no path exists, and path equals null Route result = null; - if (switchPorts != null && !switchPorts.isEmpty()) + if (switchPorts != null && !switchPorts.isEmpty()) { result = new Route(id, switchPorts); + } if (log.isTraceEnabled()) { log.trace("buildroute: {}", result); } return result; } - protected int getCost(long srcId, long dstId) { + protected int getCost(DatapathId srcId, DatapathId dstId) { BroadcastTree bt = destinationRootedTrees.get(dstId); if (bt == null) return -1; return (bt.getCost(srcId)); @@ -655,7 +656,7 @@ public class TopologyInstance { } // IRoutingEngineService interfaces - protected boolean routeExists(long srcId, long dstId) { + protected boolean routeExists(DatapathId srcId, DatapathId dstId) { BroadcastTree bt = destinationRootedTrees.get(dstId); if (bt == null) return false; Link link = bt.getLinks().get(srcId); @@ -663,19 +664,19 @@ public class TopologyInstance { return true; } - protected Route getRoute(ServiceChain sc, long srcId, short srcPort, - long dstId, short dstPort, long cookie) { + 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. - if (srcId == dstId && srcPort == dstPort) + if (srcId.equals(dstId) && srcPort.equals(dstPort)) return null; List<NodePortTuple> nptList; NodePortTuple npt; - Route r = getRoute(srcId, dstId, 0); - if (r == null && srcId != dstId) return null; + Route r = getRoute(srcId, dstId, U64.of(0)); + if (r == null && !srcId.equals(dstId)) return null; if (r != null) { nptList= new ArrayList<NodePortTuple>(r.getPath()); @@ -695,9 +696,9 @@ public class TopologyInstance { // NOTE: Return a null route if srcId equals dstId. The null route // need not be stored in the cache. Moreover, the LoadingCache will // throw an exception if null route is returned. - protected Route getRoute(long srcId, long dstId, long cookie) { + protected Route getRoute(DatapathId srcId, DatapathId dstId, U64 cookie) { // Return null route if srcId equals dstId - if (srcId == dstId) return null; + if (srcId.equals(dstId)) return null; RouteId id = new RouteId(srcId, dstId); @@ -725,54 +726,54 @@ public class TopologyInstance { // ITopologyService interface method helpers. // - protected boolean isInternalToOpenflowDomain(long switchid, short port) { + protected boolean isInternalToOpenflowDomain(DatapathId switchid, OFPort port) { return !isAttachmentPointPort(switchid, port); } - public boolean isAttachmentPointPort(long switchid, short port) { + public boolean isAttachmentPointPort(DatapathId switchid, OFPort port) { NodePortTuple npt = new NodePortTuple(switchid, port); if (switchPortLinks.containsKey(npt)) return false; return true; } - protected long getOpenflowDomainId(long switchId) { + protected DatapathId getOpenflowDomainId(DatapathId switchId) { Cluster c = switchClusterMap.get(switchId); if (c == null) return switchId; return c.getId(); } - protected long getL2DomainId(long switchId) { + protected DatapathId getL2DomainId(DatapathId switchId) { return getOpenflowDomainId(switchId); } - protected Set<Long> getSwitchesInOpenflowDomain(long switchId) { + protected Set<DatapathId> getSwitchesInOpenflowDomain(DatapathId switchId) { Cluster c = switchClusterMap.get(switchId); if (c == null) { // The switch is not known to topology as there // are no links connected to it. - Set<Long> nodes = new HashSet<Long>(); + Set<DatapathId> nodes = new HashSet<DatapathId>(); nodes.add(switchId); return nodes; } return (c.getNodes()); } - protected boolean inSameOpenflowDomain(long switch1, long switch2) { + protected boolean inSameOpenflowDomain(DatapathId switch1, DatapathId switch2) { Cluster c1 = switchClusterMap.get(switch1); Cluster c2 = switchClusterMap.get(switch2); if (c1 != null && c2 != null) - return (c1.getId() == c2.getId()); - return (switch1 == switch2); + return (c1.getId().equals(c2.getId())); + return (switch1.equals(switch2)); } - public boolean isAllowed(long sw, short portId) { + public boolean isAllowed(DatapathId sw, OFPort portId) { return true; } protected boolean - isIncomingBroadcastAllowedOnSwitchPort(long sw, short portId) { + isIncomingBroadcastAllowedOnSwitchPort(DatapathId sw, OFPort portId) { if (isInternalToOpenflowDomain(sw, portId)) { - long clusterId = getOpenflowDomainId(sw); + DatapathId clusterId = getOpenflowDomainId(sw); NodePortTuple npt = new NodePortTuple(sw, portId); if (clusterBroadcastNodePorts.get(clusterId).contains(npt)) return true; @@ -781,52 +782,52 @@ public class TopologyInstance { return true; } - public boolean isConsistent(long oldSw, short oldPort, long newSw, - short newPort) { + public boolean isConsistent(DatapathId oldSw, OFPort oldPort, DatapathId newSw, + OFPort newPort) { if (isInternalToOpenflowDomain(newSw, newPort)) return true; - return (oldSw == newSw && oldPort == newPort); + return (oldSw.equals(newSw) && oldPort.equals(newPort)); } protected Set<NodePortTuple> - getBroadcastNodePortsInCluster(long sw) { - long clusterId = getOpenflowDomainId(sw); + getBroadcastNodePortsInCluster(DatapathId sw) { + DatapathId clusterId = getOpenflowDomainId(sw); return clusterBroadcastNodePorts.get(clusterId); } - public boolean inSameBroadcastDomain(long s1, short p1, long s2, short p2) { + public boolean inSameBroadcastDomain(DatapathId s1, OFPort p1, DatapathId s2, OFPort p2) { return false; } - public boolean inSameL2Domain(long switch1, long switch2) { + public boolean inSameL2Domain(DatapathId switch1, DatapathId switch2) { return inSameOpenflowDomain(switch1, switch2); } - public NodePortTuple getOutgoingSwitchPort(long src, short srcPort, - long dst, short dstPort) { + public NodePortTuple getOutgoingSwitchPort(DatapathId src, OFPort srcPort, + DatapathId dst, OFPort dstPort) { // Use this function to redirect traffic if needed. return new NodePortTuple(dst, dstPort); } - public NodePortTuple getIncomingSwitchPort(long src, short srcPort, - long dst, short dstPort) { + public NodePortTuple getIncomingSwitchPort(DatapathId src, OFPort srcPort, + DatapathId dst, OFPort dstPort) { // Use this function to reinject traffic from a // different port if needed. return new NodePortTuple(src, srcPort); } - public Set<Long> getSwitches() { + public Set<DatapathId> getSwitches() { return switches; } - public Set<Short> getPortsWithLinks(long sw) { + public Set<OFPort> getPortsWithLinks(DatapathId sw) { return switchPorts.get(sw); } - public Set<Short> getBroadcastPorts(long targetSw, long src, short srcPort) { - Set<Short> result = new HashSet<Short>(); - long clusterId = getOpenflowDomainId(targetSw); + 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)) { - if (npt.getNodeId() == targetSw) { + if (npt.getNodeId().equals(targetSw)) { result.add(npt.getPortId()); } } @@ -834,15 +835,13 @@ public class TopologyInstance { } public NodePortTuple - getAllowedOutgoingBroadcastPort(long src, short srcPort, long dst, - short dstPort) { - // TODO Auto-generated method stub + getAllowedOutgoingBroadcastPort(DatapathId src, OFPort srcPort, DatapathId dst, + OFPort dstPort) { return null; } public NodePortTuple - getAllowedIncomingBroadcastPort(long src, short srcPort) { - // TODO Auto-generated method stub + getAllowedIncomingBroadcastPort(DatapathId src, OFPort srcPort) { return null; } } diff --git a/src/main/java/net/floodlightcontroller/topology/TopologyManager.java b/src/main/java/net/floodlightcontroller/topology/TopologyManager.java index 25b91464a1dd7aa8213e4987a0d0bf77abc6d39a..605dc65f6e4435bfc77947504f09402024edb7a6 100644 --- a/src/main/java/net/floodlightcontroller/topology/TopologyManager.java +++ b/src/main/java/net/floodlightcontroller/topology/TopologyManager.java @@ -16,7 +16,6 @@ package net.floodlightcontroller.topology; -import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -33,31 +32,27 @@ import java.util.concurrent.TimeUnit; import net.floodlightcontroller.core.FloodlightContext; import net.floodlightcontroller.core.HAListenerTypeMarker; +import net.floodlightcontroller.core.HARole; 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.LogicalOFMessageCategory; import net.floodlightcontroller.core.annotations.LogMessageCategory; import net.floodlightcontroller.core.annotations.LogMessageDoc; +import net.floodlightcontroller.core.internal.IOFSwitchService; 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.IEventCategory; 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; @@ -70,13 +65,17 @@ 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.projectfloodlight.openflow.protocol.OFMessage; +import org.projectfloodlight.openflow.protocol.OFPacketIn; +import org.projectfloodlight.openflow.protocol.OFPacketOut; +import org.projectfloodlight.openflow.protocol.OFType; +import org.projectfloodlight.openflow.protocol.OFVersion; +import org.projectfloodlight.openflow.protocol.action.OFAction; +import org.projectfloodlight.openflow.protocol.match.MatchField; +import org.projectfloodlight.openflow.types.DatapathId; +import org.projectfloodlight.openflow.types.OFBufferId; +import org.projectfloodlight.openflow.types.OFPort; +import org.projectfloodlight.openflow.types.U64; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -86,1465 +85,1451 @@ import org.slf4j.LoggerFactory; * 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; - } +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 HARole role; + + /** + * Set of ports for each switch + */ + protected Map<DatapathId, Set<OFPort>> 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 linkDiscoveryService; + protected IThreadPoolService threadPoolService; + protected IFloodlightProviderService floodlightProviderService; + protected IOFSwitchService switchService; + protected IRestApiService restApiService; + protected IDebugCounterService debugCounterService; + + // 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 debugEventService; + + /* + * Topology Event Updater + */ + protected IEventCategory<TopologyEvent> eventCategory; + + /** + * 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<DatapathId, List<NodePortTuple>> externalPortsMap; + private final int numTunnelPorts; + public TopologyEventInfo(int numOpenflowClustersWithTunnels, + int numOpenflowClustersWithoutTunnels, + Map<DatapathId, 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 (DatapathId 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 (floodlightProviderService.getRole() != HARole.STANDBY) + 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(DatapathId switchid, OFPort port) { + return isAttachmentPointPort(switchid, port, true); + } + + @Override + public boolean isAttachmentPointPort(DatapathId switchid, OFPort 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 (linkDiscoveryService.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. + //TODO @Ryan port numbers should be handled as ints now, not shorts. I suppose anything above 65280 up to 65533 is a "special" non-physical port. + if ((port.getShortPortNumber() & 0xff00) == 0xff00 && port.getShortPortNumber() != (short)0xfffe) return false; + + // Make sure that the port is enabled. + IOFSwitch sw = switchService.getActiveSwitch(switchid); + if (sw == null) return false; + return (sw.portEnabled(port)); + } + + @Override + public DatapathId getOpenflowDomainId(DatapathId switchId) { + return getOpenflowDomainId(switchId, true); + } + + @Override + public DatapathId getOpenflowDomainId(DatapathId switchId, boolean tunnelEnabled) { + TopologyInstance ti = getCurrentInstance(tunnelEnabled); + return ti.getOpenflowDomainId(switchId); + } + + @Override + public DatapathId getL2DomainId(DatapathId switchId) { + return getL2DomainId(switchId, true); + } + + @Override + public DatapathId getL2DomainId(DatapathId switchId, boolean tunnelEnabled) { + TopologyInstance ti = getCurrentInstance(tunnelEnabled); + return ti.getL2DomainId(switchId); + } + + @Override + public boolean inSameOpenflowDomain(DatapathId switch1, DatapathId switch2) { + return inSameOpenflowDomain(switch1, switch2, true); + } + + @Override + public boolean inSameOpenflowDomain(DatapathId switch1, DatapathId switch2, + boolean tunnelEnabled) { + TopologyInstance ti = getCurrentInstance(tunnelEnabled); + return ti.inSameOpenflowDomain(switch1, switch2); + } + + @Override + public boolean isAllowed(DatapathId sw, OFPort portId) { + return isAllowed(sw, portId, true); + } + + @Override + public boolean isAllowed(DatapathId sw, OFPort portId, boolean tunnelEnabled) { + TopologyInstance ti = getCurrentInstance(tunnelEnabled); + return ti.isAllowed(sw, portId); + } + + //////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////// + @Override + public boolean isIncomingBroadcastAllowed(DatapathId sw, OFPort portId) { + return isIncomingBroadcastAllowed(sw, portId, true); + } + + @Override + public boolean isIncomingBroadcastAllowed(DatapathId sw, OFPort portId, + boolean tunnelEnabled) { + TopologyInstance ti = getCurrentInstance(tunnelEnabled); + return ti.isIncomingBroadcastAllowedOnSwitchPort(sw, portId); + } + + //////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////// + /** Get all the ports connected to the switch */ + @Override + public Set<OFPort> getPortsWithLinks(DatapathId sw) { + return getPortsWithLinks(sw, true); + } + + /** Get all the ports connected to the switch */ + @Override + public Set<OFPort> getPortsWithLinks(DatapathId 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<OFPort> getBroadcastPorts(DatapathId targetSw, + DatapathId src, OFPort 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<OFPort> getBroadcastPorts(DatapathId targetSw, + DatapathId src, OFPort srcPort, + boolean tunnelEnabled) { + TopologyInstance ti = getCurrentInstance(tunnelEnabled); + return ti.getBroadcastPorts(targetSw, src, srcPort); + } + + //////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////// + @Override + public NodePortTuple getOutgoingSwitchPort(DatapathId src, OFPort srcPort, + DatapathId dst, OFPort dstPort) { + // Use this function to redirect traffic if needed. + return getOutgoingSwitchPort(src, srcPort, dst, dstPort, true); + } + + @Override + public NodePortTuple getOutgoingSwitchPort(DatapathId src, OFPort srcPort, + DatapathId dst, OFPort 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(DatapathId src, OFPort srcPort, + DatapathId dst, OFPort dstPort) { + return getIncomingSwitchPort(src, srcPort, dst, dstPort, true); + } + + @Override + public NodePortTuple getIncomingSwitchPort(DatapathId src, OFPort srcPort, + DatapathId dst, OFPort 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(DatapathId s1, OFPort p1, DatapathId s2, + OFPort p2) { + return isInSameBroadcastDomain(s1, p1, s2, p2, true); + + } + + @Override + public boolean isInSameBroadcastDomain(DatapathId s1, OFPort p1, + DatapathId s2, OFPort 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(DatapathId sw, OFPort port) { + return isBroadcastDomainPort(sw, port, true); + } + + @Override + public boolean isBroadcastDomainPort(DatapathId sw, OFPort 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(DatapathId oldSw, OFPort oldPort, + DatapathId newSw, OFPort newPort) { + return isConsistent(oldSw, oldPort, + newSw, newPort, true); + } + + @Override + public boolean isConsistent(DatapathId oldSw, OFPort oldPort, + DatapathId newSw, OFPort 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(DatapathId switch1, DatapathId switch2) { + return inSameL2Domain(switch1, switch2, true); + } + + @Override + public boolean inSameL2Domain(DatapathId switch1, DatapathId switch2, + boolean tunnelEnabled) { + TopologyInstance ti = getCurrentInstance(tunnelEnabled); + return ti.inSameL2Domain(switch1, switch2); + } + + //////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////// + @Override + public NodePortTuple getAllowedOutgoingBroadcastPort(DatapathId src, + OFPort srcPort, + DatapathId dst, + OFPort dstPort) { + return getAllowedOutgoingBroadcastPort(src, srcPort, + dst, dstPort, true); + } + + @Override + public NodePortTuple getAllowedOutgoingBroadcastPort(DatapathId src, + OFPort srcPort, + DatapathId dst, + OFPort dstPort, + boolean tunnelEnabled){ + TopologyInstance ti = getCurrentInstance(tunnelEnabled); + return ti.getAllowedOutgoingBroadcastPort(src, srcPort, + dst, dstPort); + } + //////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////// + @Override + public NodePortTuple + getAllowedIncomingBroadcastPort(DatapathId src, OFPort srcPort) { + return getAllowedIncomingBroadcastPort(src,srcPort, true); + } + + @Override + public NodePortTuple + getAllowedIncomingBroadcastPort(DatapathId src, OFPort srcPort, + boolean tunnelEnabled) { + TopologyInstance ti = getCurrentInstance(tunnelEnabled); + return ti.getAllowedIncomingBroadcastPort(src,srcPort); + } + + //////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////// + @Override + public Set<DatapathId> getSwitchesInOpenflowDomain(DatapathId switchDPID) { + return getSwitchesInOpenflowDomain(switchDPID, true); + } + + @Override + public Set<DatapathId> getSwitchesInOpenflowDomain(DatapathId 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(DatapathId src, DatapathId dst, U64 cookie) { + return getRoute(src, dst, cookie, true); + } + + @Override + public Route getRoute(DatapathId src, DatapathId dst, U64 cookie, boolean tunnelEnabled) { + TopologyInstance ti = getCurrentInstance(tunnelEnabled); + return ti.getRoute(src, dst, cookie); + } + + @Override + public Route getRoute(DatapathId src, OFPort srcPort, DatapathId dst, OFPort dstPort, U64 cookie) { + return getRoute(src, srcPort, dst, dstPort, cookie, true); + } + + @Override + public Route getRoute(DatapathId src, OFPort srcPort, DatapathId dst, OFPort dstPort, U64 cookie, + boolean tunnelEnabled) { + TopologyInstance ti = getCurrentInstance(tunnelEnabled); + return ti.getRoute(null, src, srcPort, dst, dstPort, cookie); + } + + @Override + public boolean routeExists(DatapathId src, DatapathId dst) { + return routeExists(src, dst, true); + } + + @Override + public boolean routeExists(DatapathId src, DatapathId dst, boolean tunnelEnabled) { + TopologyInstance ti = getCurrentInstance(tunnelEnabled); + return ti.routeExists(src, dst); + } + + @Override + public ArrayList<Route> getRoutes(DatapathId srcDpid, DatapathId dstDpid, + boolean tunnelEnabled) { + // Floodlight supports single path routing now + + // return single path now + ArrayList<Route> result=new ArrayList<Route>(); + result.add(getRoute(srcDpid, dstDpid, U64.of(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.increment(); + return this.processPacketInMessage(sw, (OFPacketIn) msg, cntx); + default: + break; + } + + return Command.CONTINUE; + } + + // *************** + // IHAListener + // *************** + + private class HAListenerDelegate implements IHAListener { + @Override + public void transitionToActive() { + role = HARole.ACTIVE; + log.debug("Re-computing topology due " + + "to HA change from STANDBY->ACTIVE"); + 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; + } + + @Override + public void transitionToStandby() { + // TODO Auto-generated method stub + + } + } + + // ***************** + // 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(IOFSwitchService.class); + l.add(IDebugCounterService.class); + l.add(IDebugEventService.class); + l.add(IRestApiService.class); + return l; + } + + @Override + public void init(FloodlightModuleContext context) + throws FloodlightModuleException { + linkDiscoveryService = context.getServiceImpl(ILinkDiscoveryService.class); + threadPoolService = context.getServiceImpl(IThreadPoolService.class); + floodlightProviderService = context.getServiceImpl(IFloodlightProviderService.class); + switchService = context.getServiceImpl(IOFSwitchService.class); + restApiService = context.getServiceImpl(IRestApiService.class); + debugCounterService = context.getServiceImpl(IDebugCounterService.class); + debugEventService = context.getServiceImpl(IDebugEventService.class); + + switchPorts = new HashMap<DatapathId, Set<OFPort>>(); + 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 (debugEventService == null) { + log.error("debugEventService should not be null. Has IDebugEventService been loaded previously?"); + } + eventCategory = debugEventService.buildEvent(TopologyEvent.class) + .setModuleName(PACKAGE) + .setEventName("topologyevent") + .setEventDescription("Topology Computation") + .setEventType(EventType.ALWAYS_LOG) + .setBufferCapacity(100) + .register(); + } + + @Override + public void startUp(FloodlightModuleContext context) { + clearCurrentTopology(); + // Initialize role to floodlight provider role. + this.role = floodlightProviderService.getRole(); + + ScheduledExecutorService ses = threadPoolService.getScheduledExecutor(); + newInstanceTask = new SingletonTask(ses, new UpdateTopologyWorker()); + + if (role != HARole.STANDBY) + newInstanceTask.reschedule(TOPOLOGY_COMPUTE_INTERVAL_MS, + TimeUnit.MILLISECONDS); + + linkDiscoveryService.addListener(this); + floodlightProviderService.addOFMessageListener(OFType.PACKET_IN, this); + floodlightProviderService.addHAListener(this.haListener); + addRestletRoutable(); + } + + private void registerTopologyDebugCounters() throws FloodlightModuleException { + if (debugCounterService == null) { + log.error("debugCounterService should not be null. Has IDebugEventService been loaded previously?"); + } + debugCounterService.registerModule(PACKAGE); + ctrIncoming = debugCounterService.registerCounter( + PACKAGE, "incoming", + "All incoming packets seen by this module"); + } + + protected void addRestletRoutable() { + restApiService.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(DatapathId sw, OFPacketIn pi, + FloodlightContext cntx) { + Command result = Command.CONTINUE; + OFPort port = (pi.getVersion().compareTo(OFVersion.OF_13) < 0 ? pi.getInPort() : pi.getMatch().get(MatchField.IN_PORT)); + + // 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.getLong(), port.getPortNumber()); + 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<OFPort> ports, + FloodlightContext cntx) { + + if (ports == null) return; + if (packetData == null || packetData.length <= 0) return; + + //OFPacketOut po = (OFPacketOut) floodlightProvider.getOFMessageFactory().getMessage(OFType.PACKET_OUT); + OFPacketOut.Builder pob = sw.getOFFactory().buildPacketOut(); + List<OFAction> actions = new ArrayList<OFAction>(); + for(OFPort p: ports) { + //actions.add(new OFActionOutput(p, (short) 0)); + actions.add(sw.getOFFactory().actions().output(p, 0)); + } + + // set actions + pob.setActions(actions); + // set action length + //po.setActionsLength((short) (OFActionOutput.MINIMUM_LENGTH * ports.size())); + // set buffer-id to BUFFER_ID_NONE + pob.setBufferId(OFBufferId.NO_BUFFER); + // set in-port to OFPP_NONE + pob.setInPort(OFPort.ZERO); + + // set packet data + pob.setData(packetData); + + // compute and set packet length. + //short poLength = (short)(OFPacketOut.MINIMUM_LENGTH + po.getActionsLength() + packetData.length); + + //po.setLength(poLength); + + //ctrIncoming.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, pob.build()}); + } + sw.write(pob.build(), LogicalOFMessageCategory.MAIN); + } + + /** + * 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<OFPort> getPortsToEliminateForBDDP(DatapathId sid) { + Set<NodePortTuple> suppressedNptList = linkDiscoveryService.getSuppressLLDPsInfo(); + if (suppressedNptList == null) return null; + + Set<OFPort> resultPorts = new HashSet<OFPort>(); + 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(DatapathId pinSwitch, OFPacketIn pi, + FloodlightContext cntx) { + + TopologyInstance ti = getCurrentInstance(false); + + Set<DatapathId> switches = ti.getSwitchesInOpenflowDomain(pinSwitch); + + if (switches == null) + { + // indicates no links are connected to the switches + switches = new HashSet<DatapathId>(); + switches.add(pinSwitch); + } + + for(DatapathId sid: switches) { + IOFSwitch sw = switchService.getSwitch(sid); + if (sw == null) continue; + Collection<OFPort> enabledPorts = sw.getEnabledPortNumbers(); + if (enabledPorts == null) + continue; + Set<OFPort> ports = new HashSet<OFPort>(); + 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<OFPort> portsKnownToTopo = ti.getPortsWithLinks(sid); + + if (portsKnownToTopo != null) { + for(OFPort p: portsKnownToTopo) { + NodePortTuple npt = + new NodePortTuple(sid, p); + if (ti.isBroadcastDomainPort(npt) == false) { + ports.remove(p); + } + } + } + + Set<OFPort> 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.getData(), 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(DatapathId sw) { + // nothing to do here for the time being. + return; + } + + public void addTunnelPort(DatapathId sw, OFPort port) { + NodePortTuple npt = new NodePortTuple(sw, port); + tunnelPorts.add(npt); + tunnelPortsUpdated = true; + } + + public void removeTunnelPort(DatapathId sw, OFPort 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<DatapathId, List<NodePortTuple>>(), + 0); + eventCategory.newEventWithFlush(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 != HARole.ACTIVE) + return; + + for(int i=0; i < topologyAware.size(); ++i) { + ITopologyListener listener = topologyAware.get(i); + listener.topologyChanged(linkUpdates); + } + } + + public void addSwitch(DatapathId sid) { + if (switchPorts.containsKey(sid) == false) { + switchPorts.put(sid, new HashSet<OFPort>()); + } + } + + private void addPortToSwitch(DatapathId s, OFPort p) { + addSwitch(s); + switchPorts.get(s).add(p); + } + + public void removeSwitch(DatapathId 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(OFPort 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(DatapathId srcId, OFPort srcPort, DatapathId dstId, + OFPort dstPort) { + // If you need to handle tunnel links, this is a placeholder. + } + + public void addOrUpdateLink(DatapathId srcId, OFPort srcPort, DatapathId dstId, + OFPort 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(DatapathId srcId, OFPort srcPort, + DatapathId dstId, OFPort 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<DatapathId, Set<OFPort>> 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<OFPort> getPorts(DatapathId sw) { + IOFSwitch iofSwitch = switchService.getSwitch(sw); + if (iofSwitch == null) return Collections.emptySet(); + + Collection<OFPort> ofpList = iofSwitch.getEnabledPortNumbers(); + if (ofpList == null) return Collections.emptySet(); + + Set<OFPort> ports = new HashSet<OFPort>(ofpList); + Set<OFPort> qPorts = linkDiscoveryService.getQuarantinedPorts(sw); + if (qPorts != null) + ports.removeAll(qPorts); + + return ports; + } } diff --git a/src/main/java/net/floodlightcontroller/topology/web/EnabledPortsResource.java b/src/main/java/net/floodlightcontroller/topology/web/EnabledPortsResource.java index af1467a2d5544d47290ce879191455ab7263be2d..fe2e699a72661dde0f1dfb896f9b54a467d6ec92 100644 --- a/src/main/java/net/floodlightcontroller/topology/web/EnabledPortsResource.java +++ b/src/main/java/net/floodlightcontroller/topology/web/EnabledPortsResource.java @@ -20,10 +20,12 @@ import java.util.ArrayList; import java.util.List; import java.util.Set; -import net.floodlightcontroller.core.IFloodlightProviderService; +import net.floodlightcontroller.core.internal.IOFSwitchService; import net.floodlightcontroller.topology.ITopologyService; import net.floodlightcontroller.topology.NodePortTuple; +import org.projectfloodlight.openflow.types.DatapathId; +import org.projectfloodlight.openflow.types.OFPort; import org.restlet.resource.Get; import org.restlet.resource.ServerResource; @@ -32,24 +34,24 @@ public class EnabledPortsResource extends ServerResource { public List<NodePortTuple> retrieve() { List<NodePortTuple> result = new ArrayList<NodePortTuple>(); - IFloodlightProviderService floodlightProvider = - (IFloodlightProviderService)getContext().getAttributes(). - get(IFloodlightProviderService.class.getCanonicalName()); + IOFSwitchService switchService = + (IOFSwitchService) getContext().getAttributes(). + get(IOFSwitchService.class.getCanonicalName()); - ITopologyService topology= - (ITopologyService)getContext().getAttributes(). + ITopologyService topologyService = + (ITopologyService) getContext().getAttributes(). get(ITopologyService.class.getCanonicalName()); - if (floodlightProvider == null || topology == null) + if (switchService == null || topologyService == null) return result; - Set<Long> switches = floodlightProvider.getAllSwitchDpids(); + Set<DatapathId> switches = switchService.getAllSwitchDpids(); if (switches == null) return result; - for(long sw: switches) { - Set<Short> ports = topology.getPorts(sw); + for(DatapathId sw: switches) { + Set<OFPort> ports = topologyService.getPorts(sw); if (ports == null) continue; - for(short p: ports) { + for(OFPort p: ports) { result.add(new NodePortTuple(sw, p)); } } diff --git a/src/main/java/net/floodlightcontroller/topology/web/RouteResource.java b/src/main/java/net/floodlightcontroller/topology/web/RouteResource.java index b6bfe8d6615831bf2599febb19529271addf7ea1..ceddc37599d7b46d8171b5df3ea454ba8da77586 100644 --- a/src/main/java/net/floodlightcontroller/topology/web/RouteResource.java +++ b/src/main/java/net/floodlightcontroller/topology/web/RouteResource.java @@ -22,7 +22,9 @@ import net.floodlightcontroller.routing.IRoutingService; import net.floodlightcontroller.routing.Route; import net.floodlightcontroller.topology.NodePortTuple; -import org.openflow.util.HexString; +import org.projectfloodlight.openflow.types.DatapathId; +import org.projectfloodlight.openflow.types.OFPort; +import org.projectfloodlight.openflow.types.U64; import org.restlet.resource.Get; import org.restlet.resource.ServerResource; import org.slf4j.Logger; @@ -45,15 +47,15 @@ public class RouteResource extends ServerResource { log.debug( srcDpid + "--" + srcPort + "--" + dstDpid + "--" + dstPort); - long longSrcDpid = HexString.toLong(srcDpid); - short shortSrcPort = Short.parseShort(srcPort); - long longDstDpid = HexString.toLong(dstDpid); - short shortDstPort = Short.parseShort(dstPort); + DatapathId longSrcDpid = DatapathId.of(srcDpid); + OFPort shortSrcPort = OFPort.of(Integer.parseInt(srcPort)); + DatapathId longDstDpid = DatapathId.of(dstDpid); + OFPort shortDstPort = OFPort.of(Integer.parseInt(dstPort)); - Route result = routing.getRoute(longSrcDpid, shortSrcPort, longDstDpid, shortDstPort, 0); + Route result = routing.getRoute(longSrcDpid, shortSrcPort, longDstDpid, shortDstPort, U64.of(0)); - if (result!=null) { - return routing.getRoute(longSrcDpid, shortSrcPort, longDstDpid, shortDstPort, 0).getPath(); + if (result != null) { + return routing.getRoute(longSrcDpid, shortSrcPort, longDstDpid, shortDstPort, U64.of(0)).getPath(); } else { log.debug("ERROR! no route found"); diff --git a/src/main/java/net/floodlightcontroller/topology/web/SwitchClustersResource.java b/src/main/java/net/floodlightcontroller/topology/web/SwitchClustersResource.java index 6ba1704967f6b9351fa0b4ea261f618966e23e60..ba6df10094a633498a70f924f6fbdb59996d7cc2 100644 --- a/src/main/java/net/floodlightcontroller/topology/web/SwitchClustersResource.java +++ b/src/main/java/net/floodlightcontroller/topology/web/SwitchClustersResource.java @@ -22,10 +22,10 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import net.floodlightcontroller.core.IFloodlightProviderService; +import net.floodlightcontroller.core.internal.IOFSwitchService; import net.floodlightcontroller.topology.ITopologyService; -import org.openflow.util.HexString; +import org.projectfloodlight.openflow.types.DatapathId; import org.restlet.data.Form; import org.restlet.resource.Get; import org.restlet.resource.ServerResource; @@ -36,11 +36,11 @@ import org.restlet.resource.ServerResource; public class SwitchClustersResource extends ServerResource { @Get("json") public Map<String, List<String>> retrieve() { - IFloodlightProviderService floodlightProvider = - (IFloodlightProviderService)getContext().getAttributes(). - get(IFloodlightProviderService.class.getCanonicalName()); - ITopologyService topology = - (ITopologyService)getContext().getAttributes(). + IOFSwitchService switchService = + (IOFSwitchService) getContext().getAttributes(). + get(IOFSwitchService.class.getCanonicalName()); + ITopologyService topologyService = + (ITopologyService) getContext().getAttributes(). get(ITopologyService.class.getCanonicalName()); Form form = getQuery(); @@ -51,18 +51,18 @@ public class SwitchClustersResource extends ServerResource { } Map<String, List<String>> switchClusterMap = new HashMap<String, List<String>>(); - for (Long dpid: floodlightProvider.getAllSwitchDpids()) { - Long clusterDpid = + for (DatapathId dpid: switchService.getAllSwitchDpids()) { + DatapathId clusterDpid = (openflowDomain - ? topology.getOpenflowDomainId(dpid) - :topology.getL2DomainId(dpid)); - List<String> switchesInCluster = switchClusterMap.get(HexString.toHexString(clusterDpid)); + ? topologyService.getOpenflowDomainId(dpid) + :topologyService.getL2DomainId(dpid)); + List<String> switchesInCluster = switchClusterMap.get(clusterDpid.toString()); if (switchesInCluster != null) { - switchesInCluster.add(HexString.toHexString(dpid)); + switchesInCluster.add(dpid.toString()); } else { List<String> l = new ArrayList<String>(); - l.add(HexString.toHexString(dpid)); - switchClusterMap.put(HexString.toHexString(clusterDpid), l); + l.add(dpid.toString()); + switchClusterMap.put(clusterDpid.toString(), l); } } return switchClusterMap; diff --git a/src/main/java/net/floodlightcontroller/util/ActionUtils.java b/src/main/java/net/floodlightcontroller/util/ActionUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..cd6962531b5fbf42f3fd148b9bce5b6118e4b8bb --- /dev/null +++ b/src/main/java/net/floodlightcontroller/util/ActionUtils.java @@ -0,0 +1,549 @@ +package net.floodlightcontroller.util; + +import java.util.LinkedList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import net.floodlightcontroller.core.annotations.LogMessageDoc; + +import org.projectfloodlight.openflow.protocol.OFFactories; +import org.projectfloodlight.openflow.protocol.OFFlowMod; +import org.projectfloodlight.openflow.protocol.OFVersion; +import org.projectfloodlight.openflow.protocol.action.OFAction; +import org.projectfloodlight.openflow.protocol.action.OFActionEnqueue; +import org.projectfloodlight.openflow.protocol.action.OFActionOutput; +import org.projectfloodlight.openflow.protocol.action.OFActionSetDlDst; +import org.projectfloodlight.openflow.protocol.action.OFActionSetDlSrc; +import org.projectfloodlight.openflow.protocol.action.OFActionSetNwDst; +import org.projectfloodlight.openflow.protocol.action.OFActionSetNwSrc; +import org.projectfloodlight.openflow.protocol.action.OFActionSetNwTos; +import org.projectfloodlight.openflow.protocol.action.OFActionSetTpDst; +import org.projectfloodlight.openflow.protocol.action.OFActionSetTpSrc; +import org.projectfloodlight.openflow.protocol.action.OFActionSetVlanPcp; +import org.projectfloodlight.openflow.protocol.action.OFActionSetVlanVid; +import org.projectfloodlight.openflow.protocol.action.OFActionStripVlan; +import org.projectfloodlight.openflow.types.IPv4Address; +import org.projectfloodlight.openflow.types.MacAddress; +import org.projectfloodlight.openflow.types.OFPort; +import org.projectfloodlight.openflow.types.TransportPort; +import org.projectfloodlight.openflow.types.VlanPcp; +import org.projectfloodlight.openflow.types.VlanVid; +import org.slf4j.Logger; + +/** + * OFAction helper functions. Use with any OpenFlowJ-Loxi Action. + * + * Includes string methods refactored from StaticFlowEntryPusher + * + * @author Ryan Izard <ryan.izard@bigswitch.com, rizard@g.clemson.edu> + */ +public class ActionUtils { + public static final String STR_OUTPUT = "output"; + public static final String STR_ENQUEUE = "enqueue"; + public static final String STR_VLAN_STRIP = "strip_vlan"; + public static final String STR_VLAN_POP = "pop_vlan"; + public static final String STR_VLAN_PUSH = "push_vlan"; + public static final String STR_VLAN_SET_PCP = "set_vlan_priority"; + public static final String STR_VLAN_SET_VID = "set_vlan_id"; + public static final String STR_QUEUE_SET = "set_queue"; + public static final String STR_DL_SRC_SET = "set_src_mac"; + public static final String STR_DL_DST_SET = "set_dst_mac"; + public static final String STR_NW_SRC_SET = "set_src_ip"; + public static final String STR_NW_DST_SET = "set_dst_ip"; + public static final String STR_NW_ECN_SET = "set_nw_ecn"; + public static final String STR_NW_TOS_SET = "set_tos_bits"; + public static final String STR_NW_TTL_SET = "set_ip_ttl"; + public static final String STR_NW_TTL_DEC = "dec_ip_ttl"; + public static final String STR_MPLS_LABEL_SET = "set_mpls_label"; + public static final String STR_MPLS_TC_SET = "set_mpls_tc"; + public static final String STR_MPLS_TTL_SET = "set_mpls_ttl"; + public static final String STR_MPLS_TTL_DEC = "dec_mpls_ttl"; + public static final String STR_MPLS_PUSH = "push_mpls"; + public static final String STR_MPLS_POP = "pop_mpls"; + public static final String STR_TP_SRC_SET = "set_src_port"; + public static final String STR_TP_DST_SET = "set_dst_port"; + public static final String STR_TTL_IN_COPY = "copy_ttl_in"; + public static final String STR_TTL_OUT_COPY = "copy_ttl_out"; + public static final String STR_PBB_PUSH = "push_pbb"; + public static final String STR_PBB_POP = "pop_pbb"; + public static final String STR_EXPERIMENTER = "experimenter"; + public static final String STR_GROUP = "group"; + public static final String STR_FIELD_SET = "set_field"; + + /** + * Returns a String representation of all the OpenFlow actions. + * @param actions A list of OFActions to encode into one string + * @return A dpctl-style string of the actions + */ + @LogMessageDoc(level="ERROR", + message="Could not decode action {action}", + explanation="A static flow entry contained an invalid action", + recommendation=LogMessageDoc.REPORT_CONTROLLER_BUG) + public static String actionsToString(List<OFAction> actions, Logger log) { + StringBuilder sb = new StringBuilder(); + for (OFAction a : actions) { + if (sb.length() > 0) { + sb.append(','); + } + switch(a.getType()) { + case OUTPUT: + sb.append(STR_OUTPUT + "=" + ((OFActionOutput)a).getPort().toString()); + break; + case ENQUEUE: + long queue = ((OFActionEnqueue)a).getQueueId(); + OFPort port = ((OFActionEnqueue)a).getPort(); + sb.append(STR_ENQUEUE + "=" + Integer.toString(port.getPortNumber()) + ":0x" + String.format("%02x", queue)); + break; + case STRIP_VLAN: + sb.append(STR_VLAN_STRIP); + break; + case SET_VLAN_VID: + sb.append(STR_VLAN_SET_VID + "=" + + ((OFActionSetVlanVid)a).getVlanVid().toString()); + break; + case SET_VLAN_PCP: + sb.append(STR_VLAN_SET_PCP + "=" + + Byte.toString(((OFActionSetVlanPcp)a).getVlanPcp().getValue())); + break; + case SET_DL_SRC: + sb.append(STR_DL_SRC_SET + "=" + + ((OFActionSetDlSrc)a).getDlAddr().toString()); + break; + case SET_DL_DST: + sb.append(STR_DL_DST_SET + "=" + + ((OFActionSetDlDst)a).getDlAddr().toString()); + break; + case SET_NW_TOS: + sb.append(STR_NW_TOS_SET + "=" + + Short.toString(((OFActionSetNwTos)a).getNwTos())); + break; + case SET_NW_SRC: + sb.append(STR_NW_SRC_SET + "=" + + ((OFActionSetNwSrc)a).getNwAddr().toString()); + break; + case SET_NW_DST: + sb.append(STR_NW_DST_SET + "=" + + ((OFActionSetNwDst)a).getNwAddr().toString()); + break; + case SET_TP_SRC: + sb.append(STR_TP_SRC_SET + "=" + + ((OFActionSetTpSrc)a).getTpPort().toString()); + break; + case SET_TP_DST: + sb.append(STR_TP_DST_SET + "=" + + ((OFActionSetTpDst)a).getTpPort().toString()); + break; + default: + log.error("Could not decode action: {}", a); + break; + } + + } + return sb.toString(); + } + + /** + * Parses OFFlowMod actions from strings. + * @param fmb The OFFlowMod.Builder to set the actions for + * @param bigString The string containing all the actions + * @param log A logger to log for errors. + */ + @LogMessageDoc(level="ERROR", + message="Unexpected action '{action}', '{subaction}'", + explanation="A static flow entry contained an invalid action", + recommendation=LogMessageDoc.REPORT_CONTROLLER_BUG) + public static void fromString(OFFlowMod.Builder fmb, String bigString, Logger log) { + List<OFAction> actions = new LinkedList<OFAction>(); + if (bigString != null) { + bigString = bigString.toLowerCase(); + for (String actionToDecode : bigString.split(",")) { + String action = actionToDecode.split("[=:]")[0]; + OFAction a = null; + + if (action.equals(STR_OUTPUT)) { + a = decode_output(actionToDecode, fmb.getVersion(), log); + } + else if (action.equals(STR_ENQUEUE)) { + a = decode_enqueue(actionToDecode, fmb.getVersion(), log); + } + else if (action.equals(STR_VLAN_STRIP)) { + a = decode_strip_vlan(actionToDecode, fmb.getVersion(), log); + } + else if (action.equals(STR_VLAN_SET_VID)) { + a = decode_set_vlan_id(actionToDecode, fmb.getVersion(), log); + } + else if (action.equals(STR_VLAN_SET_PCP)) { + a = decode_set_vlan_priority(actionToDecode, fmb.getVersion(), log); + } + else if (action.equals(STR_DL_SRC_SET)) { + a = decode_set_src_mac(actionToDecode, fmb.getVersion(), log); + } + else if (action.equals(STR_DL_DST_SET)) { + a = decode_set_dst_mac(actionToDecode, fmb.getVersion(), log); + } + else if (action.equals(STR_NW_TOS_SET)) { + a = decode_set_tos_bits(actionToDecode, fmb.getVersion(), log); + } + else if (action.equals(STR_NW_SRC_SET)) { + a = decode_set_src_ip(actionToDecode, fmb.getVersion(), log); + } + else if (action.equals(STR_NW_DST_SET)) { + a = decode_set_dst_ip(actionToDecode, fmb.getVersion(), log); + } + else if (action.equals(STR_TP_SRC_SET)) { + a = decode_set_src_port(actionToDecode, fmb.getVersion(), log); + } + else if (action.equals(STR_TP_DST_SET)) { + a = decode_set_dst_port(actionToDecode, fmb.getVersion(), log); + } + else { + log.error("Unexpected action '{}', '{}'", action, actionToDecode); + } + + if (a != null) { + actions.add(a); + } + } + } + log.debug("action {}", actions); + + fmb.setActions(actions); + return; + } + + @LogMessageDoc(level="ERROR", + message="Invalid subaction: '{subaction}'", + explanation="A static flow entry contained an invalid subaction", + recommendation=LogMessageDoc.REPORT_CONTROLLER_BUG) + private static OFActionOutput decode_output(String actionToDecode, OFVersion version, Logger log) { + Matcher n = Pattern.compile(STR_OUTPUT + + "=(?:((?:0x)?\\d+)|(all)|(controller)|(local)|(ingress-port)|(normal)|(flood))").matcher(actionToDecode); + if (n.matches()) { + OFActionOutput.Builder ab = OFFactories.getFactory(version).actions().buildOutput(); + OFPort port = OFPort.ANY; + if (n.group(1) != null) { + try { + port = OFPort.of(get_short(n.group(1))); + } + catch (NumberFormatException e) { + log.debug("Invalid port in: '{}' (error ignored)", actionToDecode); + return null; + } + } + else if (n.group(2) != null) + port = OFPort.ALL; + else if (n.group(3) != null) + port = OFPort.CONTROLLER; + else if (n.group(4) != null) + port = OFPort.LOCAL; + else if (n.group(5) != null) + port = OFPort.IN_PORT; + else if (n.group(6) != null) + port = OFPort.NORMAL; + else if (n.group(7) != null) + port = OFPort.FLOOD; + ab.setPort(port); + log.debug("action {}", ab.build()); + return ab.build(); + } + else { + log.error("Invalid subaction: '{}'", actionToDecode); + return null; + } + } + + private static OFActionEnqueue decode_enqueue(String actionToDecode, OFVersion version, Logger log) { + Matcher n = Pattern.compile(STR_ENQUEUE + "=(?:((?:0x)?\\d+)\\:((?:0x)?\\d+))").matcher(actionToDecode); + if (n.matches()) { + OFPort port = OFPort.of(0); + if (n.group(1) != null) { + try { + port = OFPort.of(get_short(n.group(1))); + } + catch (NumberFormatException e) { + log.debug("Invalid port-num in: '{}' (error ignored)", actionToDecode); + return null; + } + } + + int queueid = 0; + if (n.group(2) != null) { + try { + queueid = get_int(n.group(2)); + } + catch (NumberFormatException e) { + log.debug("Invalid queue-id in: '{}' (error ignored)", actionToDecode); + return null; + } + } + OFActionEnqueue.Builder ab = OFFactories.getFactory(version).actions().buildEnqueue(); + ab.setPort(port); + ab.setQueueId(queueid); + log.debug("action {}", ab.build()); + return ab.build(); + } + else { + log.debug("Invalid action: '{}'", actionToDecode); + return null; + } + } + + private static OFActionStripVlan decode_strip_vlan(String actionToDecode, OFVersion version, Logger log) { + Matcher n = Pattern.compile(STR_VLAN_STRIP).matcher(actionToDecode); + if (n.matches()) { + OFActionStripVlan a = OFFactories.getFactory(version).actions().stripVlan(); + log.debug("action {}", a); + return a; + } + else { + log.debug("Invalid action: '{}'", actionToDecode); + return null; + } + } + + private static OFActionSetVlanVid decode_set_vlan_id(String actionToDecode, OFVersion version, Logger log) { + Matcher n = Pattern.compile(STR_VLAN_SET_VID + "=((?:0x)?\\d+)").matcher(actionToDecode); + if (n.matches()) { + if (n.group(1) != null) { + try { + VlanVid vlanid = VlanVid.ofVlan(get_short(n.group(1))); + OFActionSetVlanVid.Builder ab = OFFactories.getFactory(version).actions().buildSetVlanVid(); + ab.setVlanVid(vlanid); + log.debug("action {}", ab.build()); + return ab.build(); + } + catch (NumberFormatException e) { + log.debug("Invalid VLAN in: {} (error ignored)", actionToDecode); + return null; + } + } + } + else { + log.debug("Invalid action: '{}'", actionToDecode); + return null; + } + return null; + } + + private static OFActionSetVlanPcp decode_set_vlan_priority(String actionToDecode, OFVersion version, Logger log) { + Matcher n = Pattern.compile(STR_VLAN_SET_PCP + "=((?:0x)?\\d+)").matcher(actionToDecode); + if (n.matches()) { + if (n.group(1) != null) { + try { + VlanPcp prior = VlanPcp.of(get_byte(n.group(1))); + OFActionSetVlanPcp.Builder ab = OFFactories.getFactory(OFVersion.OF_13).actions().buildSetVlanPcp(); + ab.setVlanPcp(prior); + log.debug("action {}", ab.build()); + return ab.build(); + } + catch (NumberFormatException e) { + log.debug("Invalid VLAN priority in: {} (error ignored)", actionToDecode); + return null; + } + } + } + else { + log.debug("Invalid action: '{}'", actionToDecode); + return null; + } + return null; + } + + private static OFActionSetDlSrc decode_set_src_mac(String actionToDecode, OFVersion version, Logger log) { + Matcher n = Pattern.compile(STR_DL_SRC_SET + + "=(?:(\\p{XDigit}+)\\:(\\p{XDigit}+)\\:(\\p{XDigit}+)\\:(\\p{XDigit}+)\\:(\\p{XDigit}+)\\:(\\p{XDigit}+))").matcher(actionToDecode); + if (n.matches()) { + MacAddress macaddr = MacAddress.of(get_mac_addr(n, actionToDecode, log)); + if (macaddr != null) { + OFActionSetDlSrc.Builder ab = OFFactories.getFactory(version).actions().buildSetDlSrc(); + ab.setDlAddr(macaddr); + log.debug("action {}", ab.build()); + return ab.build(); + } + } + else { + log.debug("Invalid action: '{}'", actionToDecode); + return null; + } + return null; + } + + private static OFActionSetDlDst decode_set_dst_mac(String actionToDecode, OFVersion version, Logger log) { + Matcher n = Pattern.compile(STR_DL_DST_SET + + "=(?:(\\p{XDigit}+)\\:(\\p{XDigit}+)\\:(\\p{XDigit}+)\\:(\\p{XDigit}+)\\:(\\p{XDigit}+)\\:(\\p{XDigit}+))").matcher(actionToDecode); + if (n.matches()) { + MacAddress macaddr = MacAddress.of(get_mac_addr(n, actionToDecode, log)); + if (macaddr != null) { + OFActionSetDlDst.Builder ab = OFFactories.getFactory(version).actions().buildSetDlDst(); + ab.setDlAddr(macaddr); + log.debug("action {}", ab.build()); + return ab.build(); + } + } + else { + log.debug("Invalid action: '{}'", actionToDecode); + return null; + } + return null; + } + + private static OFActionSetNwTos decode_set_tos_bits(String actionToDecode, OFVersion version, Logger log) { + Matcher n = Pattern.compile(STR_NW_TOS_SET + "=((?:0x)?\\d+)").matcher(actionToDecode); + if (n.matches()) { + if (n.group(1) != null) { + try { + byte tosbits = get_byte(n.group(1)); + OFActionSetNwTos.Builder ab = OFFactories.getFactory(version).actions().buildSetNwTos(); + ab.setNwTos(tosbits); + log.debug("action {}", ab.build()); + return ab.build(); + } + catch (NumberFormatException e) { + log.debug("Invalid dst-port in: {} (error ignored)", actionToDecode); + return null; + } + } + } + else { + log.debug("Invalid action: '{}'", actionToDecode); + return null; + } + return null; + } + + private static OFActionSetNwSrc decode_set_src_ip(String actionToDecode, OFVersion version, Logger log) { + Matcher n = Pattern.compile(STR_NW_SRC_SET + "=(?:(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+))").matcher(actionToDecode); + if (n.matches()) { + IPv4Address ipaddr = IPv4Address.of(get_ip_addr(n, actionToDecode, log)); + OFActionSetNwSrc.Builder ab = OFFactories.getFactory(version).actions().buildSetNwSrc(); + ab.setNwAddr(ipaddr); + log.debug("action {}", ab.build()); + return ab.build(); + } + else { + log.debug("Invalid action: '{}'", actionToDecode); + return null; + } + } + + private static OFActionSetNwDst decode_set_dst_ip(String actionToDecode, OFVersion version, Logger log) { + Matcher n = Pattern.compile(STR_NW_DST_SET + "=(?:(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+))").matcher(actionToDecode); + if (n.matches()) { + IPv4Address ipaddr = IPv4Address.of(get_ip_addr(n, actionToDecode, log)); + OFActionSetNwDst.Builder ab = OFFactories.getFactory(version).actions().buildSetNwDst(); + ab.setNwAddr(ipaddr); + log.debug("action {}", ab.build()); + return ab.build(); + } + else { + log.debug("Invalid action: '{}'", actionToDecode); + return null; + } + } + + private static OFActionSetTpSrc decode_set_src_port(String actionToDecode, OFVersion version, Logger log) { + Matcher n = Pattern.compile(STR_TP_SRC_SET + "=((?:0x)?\\d+)").matcher(actionToDecode); + if (n.matches()) { + if (n.group(1) != null) { + try { + TransportPort portnum = TransportPort.of(get_short(n.group(1))); + OFActionSetTpSrc.Builder ab = OFFactories.getFactory(version).actions().buildSetTpSrc(); + ab.setTpPort(portnum); + log.debug("action {}", ab.build()); + return ab.build(); + } + catch (NumberFormatException e) { + log.debug("Invalid src-port in: {} (error ignored)", actionToDecode); + return null; + } + } + } + else { + log.debug("Invalid action: '{}'", actionToDecode); + return null; + } + return null; + } + + private static OFAction decode_set_dst_port(String actionToDecode, OFVersion version, Logger log) { + Matcher n = Pattern.compile(STR_TP_DST_SET + "=((?:0x)?\\d+)").matcher(actionToDecode); + if (n.matches()) { + if (n.group(1) != null) { + try { + TransportPort portnum = TransportPort.of(get_short(n.group(1))); + OFActionSetTpDst.Builder ab = OFFactories.getFactory(version).actions().buildSetTpDst(); + ab.setTpPort(portnum); + log.debug("action {}", ab.build()); + return ab.build(); + } + catch (NumberFormatException e) { + log.debug("Invalid dst-port in: {} (error ignored)", actionToDecode); + return null; + } + } + } + else { + log.debug("Invalid action: '{}'", actionToDecode); + return null; + } + return null; + } + + private static byte[] get_mac_addr(Matcher n, String actionToDecode, Logger log) { + byte[] macaddr = new byte[6]; + for (int i=0; i<6; i++) { + if (n.group(i+1) != null) { + try { + macaddr[i] = get_byte("0x" + n.group(i+1)); + } + catch (NumberFormatException e) { + log.debug("Invalid src-mac in: '{}' (error ignored)", actionToDecode); + return null; + } + } + else { + log.debug("Invalid src-mac in: '{}' (null, error ignored)", actionToDecode); + return null; + } + } + return macaddr; + } + + private static int get_ip_addr(Matcher n, String actionToDecode, Logger log) { + int ipaddr = 0; + for (int i=0; i<4; i++) { + if (n.group(i+1) != null) { + try { + ipaddr = ipaddr<<8; + ipaddr = ipaddr | get_int(n.group(i+1)); + } + catch (NumberFormatException e) { + log.debug("Invalid src-ip in: '{}' (error ignored)", actionToDecode); + return 0; + } + } + else { + log.debug("Invalid src-ip in: '{}' (null, error ignored)", actionToDecode); + return 0; + } + } + return ipaddr; + } + + // Parse int as decimal, hex (start with 0x or #) or octal (starts with 0) + private static int get_int(String str) { + return Integer.decode(str); + } + + // Parse short as decimal, hex (start with 0x or #) or octal (starts with 0) + private static short get_short(String str) { + return (short)(int)Integer.decode(str); + } + + // Parse byte as decimal, hex (start with 0x or #) or octal (starts with 0) + private static byte get_byte(String str) { + return Integer.decode(str).byteValue(); + } + +} diff --git a/src/main/java/net/floodlightcontroller/util/FlowModUtils.java b/src/main/java/net/floodlightcontroller/util/FlowModUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..94ddeeb6eccd79bba2b8d3ddbcee8a952e338aea --- /dev/null +++ b/src/main/java/net/floodlightcontroller/util/FlowModUtils.java @@ -0,0 +1,133 @@ +package net.floodlightcontroller.util; + +import org.projectfloodlight.openflow.protocol.OFFactories; +import org.projectfloodlight.openflow.protocol.OFFlowAdd; +import org.projectfloodlight.openflow.protocol.OFFlowDelete; +import org.projectfloodlight.openflow.protocol.OFFlowDeleteStrict; +import org.projectfloodlight.openflow.protocol.OFFlowMod; +import org.projectfloodlight.openflow.protocol.OFFlowModify; +import org.projectfloodlight.openflow.protocol.OFFlowModifyStrict; +import org.projectfloodlight.openflow.protocol.OFVersion; + +/** + * Convert an OFFlowMod to a specific OFFlowMod-OFFlowModCommand. + * These function as setCommand(OFFlowModCommand) methods for an OFFlowMod. + * Used initially in the static flow pusher, but will likely find merit elsewhere. + * + * Other useful FlowMod utility functions and constants are also included. + * + * @author Ryan Izard <ryan.izard@bigswitch.com, rizard@g.clemson.edu> + */ +public class FlowModUtils { + public static final int INFINITE_TIMEOUT = 0; + + public static final int PRIORITY_MAX = 32768; + public static final int PRIORITY_VERY_HIGH = 28672; + public static final int PRIORITY_HIGH = 24576; + public static final int PRIORITY_MED_HIGH = 20480; + public static final int PRIORITY_MED = 16384; + public static final int PRIORITY_MED_LOW = 12288; + public static final int PRIORITY_LOW = 8192; + public static final int PRIORITY_VERY_LOW = 4096; + public static final int PRIORITY_MIN = 0; + + public static OFFlowAdd toFlowAdd(OFFlowMod fm) { + OFVersion version = fm.getVersion(); + OFFlowAdd.Builder b = OFFactories.getFactory(version).buildFlowAdd(); + return b.setActions(fm.getActions()) + .setBufferId(fm.getBufferId()) + .setCookie(fm.getCookie()) + .setCookieMask(fm.getCookieMask()) + .setFlags(fm.getFlags()) + .setHardTimeout(fm.getHardTimeout()) + .setIdleTimeout(fm.getIdleTimeout()) + .setInstructions(fm.getInstructions()) + .setMatch(fm.getMatch()) + .setOutGroup(fm.getOutGroup()) + .setOutPort(fm.getOutPort()) + .setPriority(fm.getPriority()) + .setTableId(fm.getTableId()) + .setXid(fm.getXid()) + .build(); + } + + public static OFFlowDelete toFlowDelete(OFFlowMod fm) { + OFVersion version = fm.getVersion(); + OFFlowDelete.Builder b = OFFactories.getFactory(version).buildFlowDelete(); + return b.setActions(fm.getActions()) + .setBufferId(fm.getBufferId()) + .setCookie(fm.getCookie()) + .setCookieMask(fm.getCookieMask()) + .setFlags(fm.getFlags()) + .setHardTimeout(fm.getHardTimeout()) + .setIdleTimeout(fm.getIdleTimeout()) + .setInstructions(fm.getInstructions()) + .setMatch(fm.getMatch()) + .setOutGroup(fm.getOutGroup()) + .setOutPort(fm.getOutPort()) + .setPriority(fm.getPriority()) + .setTableId(fm.getTableId()) + .setXid(fm.getXid()) + .build(); + } + + public static OFFlowDeleteStrict toFlowDeleteStrict(OFFlowMod fm) { + OFVersion version = fm.getVersion(); + OFFlowDeleteStrict.Builder b = OFFactories.getFactory(version).buildFlowDeleteStrict(); + return b.setActions(fm.getActions()) + .setBufferId(fm.getBufferId()) + .setCookie(fm.getCookie()) + .setCookieMask(fm.getCookieMask()) + .setFlags(fm.getFlags()) + .setHardTimeout(fm.getHardTimeout()) + .setIdleTimeout(fm.getIdleTimeout()) + .setInstructions(fm.getInstructions()) + .setMatch(fm.getMatch()) + .setOutGroup(fm.getOutGroup()) + .setOutPort(fm.getOutPort()) + .setPriority(fm.getPriority()) + .setTableId(fm.getTableId()) + .setXid(fm.getXid()) + .build(); + } + + public static OFFlowModify toFlowModify(OFFlowMod fm) { + OFVersion version = fm.getVersion(); + OFFlowModify.Builder b = OFFactories.getFactory(version).buildFlowModify(); + return b.setActions(fm.getActions()) + .setBufferId(fm.getBufferId()) + .setCookie(fm.getCookie()) + .setCookieMask(fm.getCookieMask()) + .setFlags(fm.getFlags()) + .setHardTimeout(fm.getHardTimeout()) + .setIdleTimeout(fm.getIdleTimeout()) + .setInstructions(fm.getInstructions()) + .setMatch(fm.getMatch()) + .setOutGroup(fm.getOutGroup()) + .setOutPort(fm.getOutPort()) + .setPriority(fm.getPriority()) + .setTableId(fm.getTableId()) + .setXid(fm.getXid()) + .build(); + } + + public static OFFlowModifyStrict toFlowModifyStrict(OFFlowMod fm) { + OFVersion version = fm.getVersion(); + OFFlowModifyStrict.Builder b = OFFactories.getFactory(version).buildFlowModifyStrict(); + return b.setActions(fm.getActions()) + .setBufferId(fm.getBufferId()) + .setCookie(fm.getCookie()) + .setCookieMask(fm.getCookieMask()) + .setFlags(fm.getFlags()) + .setHardTimeout(fm.getHardTimeout()) + .setIdleTimeout(fm.getIdleTimeout()) + .setInstructions(fm.getInstructions()) + .setMatch(fm.getMatch()) + .setOutGroup(fm.getOutGroup()) + .setOutPort(fm.getOutPort()) + .setPriority(fm.getPriority()) + .setTableId(fm.getTableId()) + .setXid(fm.getXid()) + .build(); + } +} diff --git a/src/main/java/net/floodlightcontroller/util/MACAddress.java b/src/main/java/net/floodlightcontroller/util/MACAddress.java deleted file mode 100644 index 808a37797f857c54fb7da07f4846d3896f465f25..0000000000000000000000000000000000000000 --- a/src/main/java/net/floodlightcontroller/util/MACAddress.java +++ /dev/null @@ -1,173 +0,0 @@ -/** - * 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.util; - -import java.util.Arrays; - -/** - * The class representing MAC address. - * - * @author Sho Shimizu (sho.shimizu@gmail.com) - */ -public class MACAddress { - public static final int MAC_ADDRESS_LENGTH = 6; - private byte[] address = new byte[MAC_ADDRESS_LENGTH]; - - public MACAddress(byte[] address) { - this.address = Arrays.copyOf(address, MAC_ADDRESS_LENGTH); - } - - /** - * Returns a MAC address instance representing the value of the specified {@code String}. - * @param address the String representation of the MAC Address to be parsed. - * @return a MAC Address instance representing the value of the specified {@code String}. - * @throws IllegalArgumentException if the string cannot be parsed as a MAC address. - */ - public static MACAddress valueOf(String address) { - String[] elements = address.split(":"); - if (elements.length != MAC_ADDRESS_LENGTH) { - throw new IllegalArgumentException( - "Specified MAC Address must contain 12 hex digits" + - " separated pairwise by :'s."); - } - - byte[] addressInBytes = new byte[MAC_ADDRESS_LENGTH]; - for (int i = 0; i < MAC_ADDRESS_LENGTH; i++) { - String element = elements[i]; - addressInBytes[i] = (byte)Integer.parseInt(element, 16); - } - - return new MACAddress(addressInBytes); - } - - /** - * Returns a MAC address instance representing the specified {@code byte} array. - * @param address the byte array to be parsed. - * @return a MAC address instance representing the specified {@code byte} array. - * @throws IllegalArgumentException if the byte array cannot be parsed as a MAC address. - */ - public static MACAddress valueOf(byte[] address) { - if (address.length != MAC_ADDRESS_LENGTH) { - throw new IllegalArgumentException("the length is not " + MAC_ADDRESS_LENGTH); - } - - return new MACAddress(address); - } - - /** - * Returns a MAC address instance representing the specified {@code long} value. - * The lower 48 bits of the long value are used to parse as a MAC address. - * @param address the long value to be parsed. The lower 48 bits are used for a MAC address. - * @return a MAC address instance representing the specified {@code long} value. - * @throws IllegalArgumentException if the long value cannot be parsed as a MAC address. - */ - public static MACAddress valueOf(long address) { - byte[] addressInBytes = new byte[] { - (byte)((address >> 40) & 0xff), - (byte)((address >> 32) & 0xff), - (byte)((address >> 24) & 0xff), - (byte)((address >> 16) & 0xff), - (byte)((address >> 8 ) & 0xff), - (byte)((address >> 0) & 0xff) - }; - - return new MACAddress(addressInBytes); - } - - /** - * Returns the length of the {@code MACAddress}. - * @return the length of the {@code MACAddress}. - */ - public int length() { - return address.length; - } - - /** - * Returns the value of the {@code MACAddress} as a {@code byte} array. - * @return the numeric value represented by this object after conversion to type {@code byte} array. - */ - public byte[] toBytes() { - return Arrays.copyOf(address, address.length); - } - - /** - * Returns the value of the {@code MACAddress} as a {@code long}. - * @return the numeric value represented by this object after conversion to type {@code long}. - */ - public long toLong() { - long mac = 0; - for (int i = 0; i < 6; i++) { - long t = (address[i] & 0xffL) << ((5 - i) * 8); - mac |= t; - } - return mac; - } - - /** - * Returns {@code true} if the MAC address is the broadcast address. - * @return {@code true} if the MAC address is the broadcast address. - */ - public boolean isBroadcast() { - for (byte b : address) { - if (b != -1) // checks if equal to 0xff - return false; - } - return true; - } - - /** - * Returns {@code true} if the MAC address is the multicast address. - * @return {@code true} if the MAC address is the multicast address. - */ - public boolean isMulticast() { - if (isBroadcast()) { - return false; - } - return (address[0] & 0x01) != 0; - } - - @Override - public boolean equals(Object o) { - if (o == this) { - return true; - } - - if (!(o instanceof MACAddress)) { - return false; - } - - MACAddress other = (MACAddress)o; - return Arrays.equals(this.address, other.address); - } - - @Override - public int hashCode() { - return Arrays.hashCode(this.address); - } - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - for (byte b: address) { - if (builder.length() > 0) { - builder.append(":"); - } - builder.append(String.format("%02X", b & 0xFF)); - } - return builder.toString(); - } -} diff --git a/src/main/java/net/floodlightcontroller/util/MatchUtils.java b/src/main/java/net/floodlightcontroller/util/MatchUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..e10cf4a3fdf52b9466c9573f94ad06c49389f3e8 --- /dev/null +++ b/src/main/java/net/floodlightcontroller/util/MatchUtils.java @@ -0,0 +1,436 @@ +package net.floodlightcontroller.util; + +import java.util.ArrayDeque; +import java.util.Arrays; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +import org.projectfloodlight.openflow.protocol.OFFactories; +import org.projectfloodlight.openflow.protocol.OFVersion; +import org.projectfloodlight.openflow.protocol.match.Match; +import org.projectfloodlight.openflow.protocol.match.MatchField; +import org.projectfloodlight.openflow.types.ArpOpcode; +import org.projectfloodlight.openflow.types.EthType; +import org.projectfloodlight.openflow.types.ICMPv4Code; +import org.projectfloodlight.openflow.types.ICMPv4Type; +import org.projectfloodlight.openflow.types.IPv4Address; +import org.projectfloodlight.openflow.types.IPv4AddressWithMask; +import org.projectfloodlight.openflow.types.IpDscp; +import org.projectfloodlight.openflow.types.IpEcn; +import org.projectfloodlight.openflow.types.IpProtocol; +import org.projectfloodlight.openflow.types.MacAddress; +import org.projectfloodlight.openflow.types.OFMetadata; +import org.projectfloodlight.openflow.types.OFPort; +import org.projectfloodlight.openflow.types.OFVlanVidMatch; +import org.projectfloodlight.openflow.types.TransportPort; +import org.projectfloodlight.openflow.types.U32; +import org.projectfloodlight.openflow.types.U8; +import org.projectfloodlight.openflow.types.VlanPcp; + +/** + * Match helper functions. Use with any OpenFlowJ-Loxi Match. + * + * Includes string methods adopted from OpenFlowJ for OpenFlow 1.0. + * + * @author Ryan Izard <ryan.izard@bigswitch.com, rizard@g.clemson.edu> + * @author David Erickson (daviderickson@cs.stanford.edu) + * @author Rob Sherwood (rob.sherwood@stanford.edu) + */ +public class MatchUtils { + /* List of Strings for marshalling and unmarshalling to human readable forms. + * Classes that convert from Match and String should reference these fields for a + * common string representation throughout the controller. The StaticFlowEntryPusher + * is one such example that references these strings. + */ + public static final String STR_IN_PORT = "ingress_port"; + + public static final String STR_DL_DST = "dl_dst"; + public static final String STR_DL_SRC = "dl_src"; + public static final String STR_DL_TYPE = "dl_type"; + public static final String STR_DL_VLAN = "dl_vlan"; + public static final String STR_DL_VLAN_PCP = "dl_vpcp"; + + public static final String STR_NW_DST = "nw_dst"; + public static final String STR_NW_SRC = "nw_src"; + public static final String STR_NW_PROTO = "nw_proto"; + public static final String STR_NW_TOS = "nw_tos"; + + public static final String STR_TP_DST = "tp_dst"; + public static final String STR_TP_SRC = "tp_src"; + + public static final String STR_ICMP_TYPE = "icmp_type"; + public static final String STR_ICMP_CODE = "icmp_code"; + + public static final String STR_ARP_OPCODE = "arp_opcode"; + public static final String STR_ARP_SHA = "arp_sha"; + public static final String STR_ARP_DHA = "arp_dha"; + public static final String STR_ARP_SPA = "arp_spa"; + public static final String STR_ARP_DPA = "arp_dpa"; + + public static final String STR_MPLS_LABEL = "mpls_label"; + public static final String STR_MPLS_TC = "mpls_tc"; + public static final String STR_MPLS_BOS = "mpls_bos"; + + public static final String STR_METADATA = "metadata"; + public static final String STR_TUNNEL_ID = "tunnel_id"; + + public static final String STR_PBB_ISID = "pbb_isid"; + + /** + * Create a point-to-point match for two devices at the IP layer. + * Takes an existing match (e.g. from a PACKET_IN), and masks all + * MatchFields leaving behind: + * IN_PORT + * VLAN_VID + * ETH_TYPE + * ETH_SRC + * ETH_DST + * IPV4_SRC + * IPV4_DST + * IP_PROTO (might remove this) + * + * If one of the above MatchFields is wildcarded in Match m, + * that MatchField will be wildcarded in the returned Match. + * + * @param m The match to remove all L4+ MatchFields from + * @return A new Match object with all MatchFields masked/wildcared + * except for those listed above. + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + public static Match maskL4AndUp(Match m) { + Match.Builder mb = m.createBuilder(); + Iterator<MatchField<?>> itr = m.getMatchFields().iterator(); // only get exact or masked fields (not fully wildcarded) + while(itr.hasNext()) { + MatchField mf = itr.next(); + // restrict MatchFields only to L3 and below: IN_PORT, ETH_TYPE, ETH_SRC, ETH_DST, IPV4_SRC, IPV4_DST, IP_PROTO (this one debatable...) + // if a MatchField is not in the access list below, it will not be set --> it will be left wildcarded (default) + if (mf.equals(MatchField.IN_PORT) || mf.equals(MatchField.ETH_TYPE) || mf.equals(MatchField.ETH_SRC) || mf.equals(MatchField.ETH_DST) || + mf.equals(MatchField.IPV4_SRC) || mf.equals(MatchField.IPV4_DST) || mf.equals(MatchField.IP_PROTO)) { + if (m.isExact(mf)) { + mb.setExact(mf, m.get(mf)); + } else if (m.isPartiallyMasked(mf)) { + mb.setMasked(mf, m.getMasked(mf)); + } else { + // it's either exact, masked, or wildcarded + // itr only contains exact and masked MatchFields + // we should never get here + } + } + } + return mb.build(); + } + + /** + * Create a builder from an existing Match object. Unlike Match's + * createBuilder(), this utility function will preserve all of + * Match m's MatchFields, even if new MatchFields are set or modified + * with the builder after it is returned to the calling function. + * + * All original MatchFields in m will be set if the build() method is + * invoked upon the returned builder. After the builder is returned, if + * a MatchField is modified via setExact(), setMasked(), or wildcard(), + * the newly modified MatchField will replace the original found in m. + * + * @param m; the match to create the builder from + * @return Match.Builder; the builder that can be modified, and when built, + * will retain all of m's MatchFields, unless you explicitly overwrite them. + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static Match.Builder createRetentiveBuilder(Match m) { + /* Builder retains a parent MatchField list, but list will not be used to + * build the new match if the builder's set methods have been invoked; only + * additions will be built, and all parent MatchFields will be ignored, + * even if they were not modified by the new builder. Create a builder, and + * walk through m's list of non-wildcarded MatchFields. Set them all in the + * new builder by invoking a set method for each. This will make them persist + * in the Match built from this builder if the user decides to add or subtract + * from the MatchField list. + */ + Match.Builder mb = m.createBuilder(); + Iterator<MatchField<?>> itr = m.getMatchFields().iterator(); // only get exact or masked fields (not fully wildcarded) + while(itr.hasNext()) { + MatchField mf = itr.next(); + if (m.isExact(mf)) { + mb.setExact(mf, m.get(mf)); + } else if (m.isPartiallyMasked(mf)) { + mb.setMasked(mf, m.getMasked(mf)); + } else { + // it's either exact, masked, or wildcarded + // itr only contains exact and masked MatchFields + // we should never get here + } + } + return mb; + } + + /** + * Create a Match builder the same OF version as Match m. The returned builder + * will not retain any MatchField information from Match m and will + * essentially return a clean-slate Match builder with no parent history. + * This simple method is included as a wrapper to provide the opposite functionality + * of createRetentiveBuilder(). + * + * @param m; the match to create the builder from + * @return Match.Builder; the builder retains no history from the parent Match m + */ + public static Match.Builder createForgetfulBuilder(Match m) { + return OFFactories.getFactory(m.getVersion()).buildMatch(); + } + + /** + * Create a duplicate Match object from Match m. + * + * @param m; the match to copy + * @return Match; the new copy of Match m + */ + public static Match createCopy(Match m) { + return m.createBuilder().build(); // will use parent MatchFields to produce the new Match only if the builder is never modified + } + + /** + * TODO @Ryan NOT IMPLEMENTED! Returns empty string right now. + * Output a dpctl-styled string, i.e., only list the elements that are not wildcarded. + * + * A match-everything Match outputs "Match[]" + * + * @return "Match[dl_src:00:20:01:11:22:33,nw_src:192.168.0.0/24,tp_dst:80]" + */ + public static String toString(Match match) { + /*String str = ""; + + match + + // l1 + if ((wildcards & OFPFW_IN_PORT) == 0) + str += "," + STR_IN_PORT + "=" + U16.f(this.inputPort); + + // l2 + if ((wildcards & OFPFW_DL_DST) == 0) + str += "," + STR_DL_DST + "=" + + match.); + if ((wildcards & OFPFW_DL_SRC) == 0) + str += "," + STR_DL_SRC + "=" + + HexString.toHexString(this.dataLayerSource); + if ((wildcards & OFPFW_DL_TYPE) == 0) + str += "," + STR_DL_TYPE + "=0x" + + Integer.toHexString(U16.f(this.dataLayerType)); + if ((wildcards & OFPFW_DL_VLAN) == 0) + str += "," + STR_DL_VLAN + "=0x" + + Integer.toHexString(U16.f(this.dataLayerVirtualLan)); + if ((wildcards & OFPFW_DL_VLAN_PCP) == 0) + str += "," + + STR_DL_VLAN_PCP + + "=" + + Integer.toHexString(U8 + .f(this.dataLayerVirtualLanPriorityCodePoint)); + + // l3 + if (getNetworkDestinationMaskLen() > 0) + str += "," + + STR_NW_DST + + "=" + + cidrToString(networkDestination, + getNetworkDestinationMaskLen()); + if (getNetworkSourceMaskLen() > 0) + str += "," + STR_NW_SRC + "=" + + cidrToString(networkSource, getNetworkSourceMaskLen()); + if ((wildcards & OFPFW_NW_PROTO) == 0) + str += "," + STR_NW_PROTO + "=" + U8.f(this.networkProtocol); + if ((wildcards & OFPFW_NW_TOS) == 0) + str += "," + STR_NW_TOS + "=" + U8.f(this.networkTypeOfService); + + // l4 + if ((wildcards & OFPFW_TP_DST) == 0) + str += "," + STR_TP_DST + "=" + U16.f(this.transportDestination); + if ((wildcards & OFPFW_TP_SRC) == 0) + str += "," + STR_TP_SRC + "=" + U16.f(this.transportSource); + if ((str.length() > 0) && (str.charAt(0) == ',')) + str = str.substring(1); // trim the leading "," + // done + return "OFMatch[" + str + "]"; */ + return ""; + } + + /** + * Based on the method from OFMatch in openflowj 1.0. + * Set this Match's parameters based on a comma-separated key=value pair + * dpctl-style string, e.g., from the output of OFMatch.toString() <br> + * <p> + * Supported keys/values include <br> + * <p> + * <TABLE border=1> + * <TR> + * <TD>KEY(s) + * <TD>VALUE + * </TR> + * <TR> + * <TD>"in_port","input_port" + * <TD>integer + * </TR> + * <TR> + * <TD>"dl_src", "dl_dst" + * <TD>hex-string + * </TR> + * <TR> + * <TD>"dl_type", "dl_vlan", "dl_vlan_pcp" + * <TD>integer + * </TR> + * <TR> + * <TD>"nw_src", "nw_dst" + * <TD>CIDR-style netmask + * </TR> + * <TR> + * <TD>"tp_src","tp_dst" + * <TD>integer (max 64k) + * </TR> + * </TABLE> + * <p> + * The CIDR-style netmasks assume 32 netmask if none given, so: + * "128.8.128.118/32" is the same as "128.8.128.118" + * + * @param match + * a key=value comma separated string, e.g. + * "in_port=5,nw_dst=192.168.0.0/16,tp_src=80" + * @throws IllegalArgumentException + * on unexpected key or value + */ + public static Match fromString(String match, OFVersion ofVersion) throws IllegalArgumentException { + if (match.equals("") || match.equalsIgnoreCase("any") || match.equalsIgnoreCase("all") || match.equals("[]")) { + match = "Match[]"; + } + + // Split into pairs of key=value + String[] tokens = match.split("[\\[,\\]]"); + int initArg = 0; + if (tokens[0].equals("Match")) { + initArg = 1; + } + + // Split up key=value pairs into [key, value], and insert into linked list + int i; + String[] tmp; + ArrayDeque<String[]> llValues = new ArrayDeque<String[]>(); + for (i = initArg; i < tokens.length; i++) { + tmp = tokens[i].split("="); + if (tmp.length != 2) { + throw new IllegalArgumentException("Token " + tokens[i] + " does not have form 'key=value' parsing " + match); + } + tmp[0] = tmp[0].toLowerCase(); // try to make key parsing case insensitive + llValues.add(tmp); // llValues contains [key, value] pairs. Create a queue of pairs to process. + } + + Match.Builder mb = OFFactories.getFactory(ofVersion).buildMatch(); + + while (!llValues.isEmpty()) { + IpProtocol ipProto; // used to prevent lots of match.get()'s for detecting transport protocol + String[] key_value = llValues.pollFirst(); // pop off the first element; this completely removes it from the queue. + switch (key_value[0]) { + case STR_IN_PORT: + mb.setExact(MatchField.IN_PORT, OFPort.of(Integer.valueOf(key_value[1]))); + break; + case STR_DL_DST: + mb.setExact(MatchField.ETH_DST, MacAddress.of(key_value[1])); + break; + case STR_DL_SRC: + mb.setExact(MatchField.ETH_SRC, MacAddress.of(key_value[1])); + break; + case STR_DL_TYPE: + if (key_value[1].startsWith("0x")) { + mb.setExact(MatchField.ETH_TYPE, EthType.of(Integer.valueOf(key_value[1].replaceFirst("0x", ""), 16))); + } else { + mb.setExact(MatchField.ETH_TYPE, EthType.of(Integer.valueOf(key_value[1]))); + } + break; + case STR_DL_VLAN: + if (key_value[1].contains("0x")) { + mb.setExact(MatchField.VLAN_VID, OFVlanVidMatch.ofVlan(Integer.valueOf(key_value[1].replaceFirst("0x", ""), 16))); + } else { + mb.setExact(MatchField.VLAN_VID, OFVlanVidMatch.ofVlan(Integer.valueOf(key_value[1]))); + } + break; + case STR_DL_VLAN_PCP: + mb.setExact(MatchField.VLAN_PCP, VlanPcp.of(U8.t(Short.valueOf(key_value[1])))); + break; + case STR_NW_DST: + mb.setMasked(MatchField.IPV4_DST, IPv4AddressWithMask.of(key_value[1])); + break; + case STR_NW_SRC: + mb.setMasked(MatchField.IPV4_SRC, IPv4AddressWithMask.of(key_value[1])); + break; + case STR_NW_PROTO: + mb.setExact(MatchField.IP_PROTO, IpProtocol.of(Short.valueOf(key_value[1]))); + break; + case STR_NW_TOS: + mb.setExact(MatchField.IP_ECN, IpEcn.of(U8.t(Short.valueOf(key_value[1])))); + mb.setExact(MatchField.IP_DSCP, IpDscp.of(U8.t(Short.valueOf(key_value[1])))); + break; + case STR_TP_DST: + // if we don't know the transport protocol yet, postpone parsing this [key, value] pair until we know. Put it at the back of the queue. + if ((ipProto = mb.get(MatchField.IP_PROTO)) == null) { + llValues.add(key_value); // place it back if we can't proceed yet + } else if (ipProto.equals(IpProtocol.TCP)) { + mb.setExact(MatchField.TCP_DST, TransportPort.of(Integer.valueOf(key_value[1]))); + } else if (ipProto.equals(IpProtocol.UDP)) { + mb.setExact(MatchField.UDP_DST, TransportPort.of(Integer.valueOf(key_value[1]))); + } else if (ipProto.equals(IpProtocol.SCTP)) { + mb.setExact(MatchField.SCTP_DST, TransportPort.of(Integer.valueOf(key_value[1]))); + } + break; + case STR_TP_SRC: + if ((ipProto = mb.get(MatchField.IP_PROTO)) == null) { + llValues.add(key_value); // place it back if we can't proceed yet + } else if (mb.get(MatchField.IP_PROTO).equals(IpProtocol.TCP)) { + mb.setExact(MatchField.TCP_SRC, TransportPort.of(Integer.valueOf(key_value[1]))); + } else if (mb.get(MatchField.IP_PROTO).equals(IpProtocol.UDP)) { + mb.setExact(MatchField.UDP_SRC, TransportPort.of(Integer.valueOf(key_value[1]))); + } else if (mb.get(MatchField.IP_PROTO).equals(IpProtocol.SCTP)) { + mb.setExact(MatchField.SCTP_SRC, TransportPort.of(Integer.valueOf(key_value[1]))); + } + break; + case STR_ICMP_TYPE: + mb.setExact(MatchField.ICMPV4_TYPE, ICMPv4Type.of(Short.parseShort(key_value[1]))); + break; + case STR_ICMP_CODE: + mb.setExact(MatchField.ICMPV4_CODE, ICMPv4Code.of(Short.parseShort(key_value[1]))); + break; + case STR_ARP_OPCODE: + mb.setExact(MatchField.ARP_OP, ArpOpcode.of(Integer.parseInt(key_value[1]))); + break; + case STR_ARP_SHA: + mb.setExact(MatchField.ARP_SHA, MacAddress.of(key_value[1])); + break; + case STR_ARP_DHA: + mb.setExact(MatchField.ARP_THA, MacAddress.of(key_value[1])); + break; + case STR_ARP_SPA: + mb.setExact(MatchField.ARP_SPA, IPv4Address.of(key_value[1])); + break; + case STR_ARP_DPA: + mb.setExact(MatchField.ARP_TPA, IPv4Address.of(key_value[1])); + break; + case STR_MPLS_LABEL: + mb.setExact(MatchField.MPLS_LABEL, U32.of(Long.parseLong(key_value[1]))); + break; + case STR_MPLS_TC: + mb.setExact(MatchField.MPLS_TC, U8.of(Short.parseShort(key_value[1]))); + break; + case STR_MPLS_BOS: + //no-op. Not implemented. + break; + case STR_METADATA: + mb.setExact(MatchField.METADATA, OFMetadata.ofRaw(Long.parseLong(key_value[1]))); + break; + case STR_TUNNEL_ID: + //no-op. Not implemented. + break; + case STR_PBB_ISID: + //no-op. Not implemented. + break; + default: + throw new IllegalArgumentException("unknown token " + key_value + " parsing " + match); + } + } + return mb.build(); + } +} diff --git a/src/main/java/net/floodlightcontroller/util/OFMatchWithSwDpid.java b/src/main/java/net/floodlightcontroller/util/OFMatchWithSwDpid.java new file mode 100644 index 0000000000000000000000000000000000000000..b71ed6c3dabfac9ab6741b6aa14e425fb2f32a15 --- /dev/null +++ b/src/main/java/net/floodlightcontroller/util/OFMatchWithSwDpid.java @@ -0,0 +1,35 @@ +package net.floodlightcontroller.util; + +import org.projectfloodlight.openflow.protocol.match.Match; +import org.projectfloodlight.openflow.types.DatapathId; + +public class OFMatchWithSwDpid { + private Match match; + private DatapathId dpid; + + public OFMatchWithSwDpid(Match match, DatapathId dpid) { + this.match = match; + this.dpid = dpid; + } + public OFMatchWithSwDpid() { + this.match = null; + this.dpid = DatapathId.NONE; + } + + public Match getMatch() { + return match; + } + + public void setMatch(Match match) { + this.match = match; + } + + public DatapathId getDpid() { + return dpid; + } + + public void setDpid(DatapathId dpid) { + this.dpid = dpid; + } + +} diff --git a/src/main/java/net/floodlightcontroller/util/OFMessageDamper.java b/src/main/java/net/floodlightcontroller/util/OFMessageDamper.java index 75b0b00d3a9fb082e14a18e3da34712fcdee4e36..e599968ecd78e2f8dd937758094a2b36b1a19602 100644 --- a/src/main/java/net/floodlightcontroller/util/OFMessageDamper.java +++ b/src/main/java/net/floodlightcontroller/util/OFMessageDamper.java @@ -22,9 +22,10 @@ import java.util.Set; import net.floodlightcontroller.core.FloodlightContext; import net.floodlightcontroller.core.IOFSwitch; +import net.floodlightcontroller.core.LogicalOFMessageCategory; -import org.openflow.protocol.OFMessage; -import org.openflow.protocol.OFType; +import org.projectfloodlight.openflow.protocol.OFMessage; +import org.projectfloodlight.openflow.protocol.OFType; /** * Dampens OFMessages sent to an OF switch. A message is only written to @@ -115,8 +116,7 @@ public class OFMessageDamper { * the message was dampened. * @throws IOException */ - public boolean write(IOFSwitch sw, OFMessage msg, FloodlightContext cntx) - throws IOException { + public boolean write(IOFSwitch sw, OFMessage msg, FloodlightContext cntx) throws IOException { return write(sw, msg, cntx, false); } @@ -130,11 +130,9 @@ public class OFMessageDamper { * the message was dampened. * @throws IOException */ - public boolean write(IOFSwitch sw, OFMessage msg, - FloodlightContext cntx, boolean flush) - throws IOException { + public boolean write(IOFSwitch sw, OFMessage msg, FloodlightContext cntx, boolean flush) throws IOException { if (! msgTypesToCache.contains(msg.getType())) { - sw.writeThrottled(msg, cntx); + sw.write(msg, LogicalOFMessageCategory.MAIN); if (flush) { sw.flush(); } @@ -146,7 +144,7 @@ public class OFMessageDamper { // entry exists in cache. Dampening. return false; } else { - sw.writeThrottled(msg, cntx); + sw.write(msg); if (flush) { sw.flush(); } diff --git a/src/main/java/net/floodlightcontroller/virtualnetwork/HostResource.java b/src/main/java/net/floodlightcontroller/virtualnetwork/HostResource.java index 03f0a63ae3b93f20a1b370048fd81884bbd83cf5..b2eae7c74e3f09990e1e40fd7a1611832a6d7d2c 100644 --- a/src/main/java/net/floodlightcontroller/virtualnetwork/HostResource.java +++ b/src/main/java/net/floodlightcontroller/virtualnetwork/HostResource.java @@ -18,12 +18,12 @@ package net.floodlightcontroller.virtualnetwork; import java.io.IOException; -import net.floodlightcontroller.util.MACAddress; - import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.databind.MappingJsonFactory; + +import org.projectfloodlight.openflow.types.MacAddress; import org.restlet.data.Status; import org.restlet.resource.Delete; import org.restlet.resource.Put; @@ -92,7 +92,7 @@ public class HostResource extends org.restlet.resource.ServerResource { } catch (IOException e) { log.error("Could not parse JSON {}", e.getMessage()); } - vns.addHost(MACAddress.valueOf(host.mac), host.guid, host.port); + vns.addHost(MacAddress.of(host.mac), host.guid, host.port); setStatus(Status.SUCCESS_OK); return "{\"status\":\"ok\"}"; } diff --git a/src/main/java/net/floodlightcontroller/virtualnetwork/IVirtualNetworkService.java b/src/main/java/net/floodlightcontroller/virtualnetwork/IVirtualNetworkService.java index 993b2ffd04553bdab3ff9c73f5ac58fa1a0f283b..d3ebada8e3d56836dbf224c10f8e319341aadd43 100644 --- a/src/main/java/net/floodlightcontroller/virtualnetwork/IVirtualNetworkService.java +++ b/src/main/java/net/floodlightcontroller/virtualnetwork/IVirtualNetworkService.java @@ -17,8 +17,11 @@ package net.floodlightcontroller.virtualnetwork; import java.util.Collection; + +import org.projectfloodlight.openflow.types.IPv4Address; +import org.projectfloodlight.openflow.types.MacAddress; + import net.floodlightcontroller.core.module.IFloodlightService; -import net.floodlightcontroller.util.MACAddress; public interface IVirtualNetworkService extends IFloodlightService { /** @@ -29,7 +32,7 @@ public interface IVirtualNetworkService extends IFloodlightService { * @param guid The ID of the network. Must be unique. * @param gateway The IP address of the network gateway, null if none. */ - public void createNetwork(String guid, String network, Integer gateway); + public void createNetwork(String guid, String network, IPv4Address gateway); /** * Deletes a virtual network. @@ -44,7 +47,7 @@ public interface IVirtualNetworkService extends IFloodlightService { * @param network The network to add the host to. * @param port The logical port name to attach the host to. Must be unique. */ - public void addHost(MACAddress mac, String network, String port); + public void addHost(MacAddress mac, String network, String port); /** * Deletes a host from a virtual network. Either the MAC or Port must @@ -52,7 +55,7 @@ public interface IVirtualNetworkService extends IFloodlightService { * @param mac The MAC address to delete. * @param port The logical port the host is attached to. */ - public void deleteHost(MACAddress mac, String port); + public void deleteHost(MacAddress mac, String port); /** * Return list of all virtual networks. diff --git a/src/main/java/net/floodlightcontroller/virtualnetwork/NetworkResource.java b/src/main/java/net/floodlightcontroller/virtualnetwork/NetworkResource.java index 35ff629f4c557f5185585c3440fbaa22e77d43c9..735415d828e8e4f7d577405a796fbccfd2e44aef 100644 --- a/src/main/java/net/floodlightcontroller/virtualnetwork/NetworkResource.java +++ b/src/main/java/net/floodlightcontroller/virtualnetwork/NetworkResource.java @@ -19,12 +19,12 @@ package net.floodlightcontroller.virtualnetwork; import java.io.IOException; import java.util.Collection; -import net.floodlightcontroller.packet.IPv4; - import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.databind.MappingJsonFactory; + +import org.projectfloodlight.openflow.types.IPv4Address; import org.restlet.data.Status; import org.restlet.resource.Delete; import org.restlet.resource.Get; @@ -122,10 +122,10 @@ public class NetworkResource extends ServerResource { (IVirtualNetworkService)getContext().getAttributes(). get(IVirtualNetworkService.class.getCanonicalName()); - Integer gw = null; + IPv4Address gw = null; if (network.gateway != null) { try { - gw = IPv4.toIPv4Address(network.gateway); + gw = IPv4Address.of(network.gateway); } catch (IllegalArgumentException e) { log.warn("Could not parse gateway {} as IP for network {}, setting as null", network.gateway, network.name); diff --git a/src/main/java/net/floodlightcontroller/virtualnetwork/VirtualNetwork.java b/src/main/java/net/floodlightcontroller/virtualnetwork/VirtualNetwork.java index 829be702a36039fcfafa1140c68c6c958dffed2c..32a2e72f2b129f5b79338f73a8d18c8215706528 100644 --- a/src/main/java/net/floodlightcontroller/virtualnetwork/VirtualNetwork.java +++ b/src/main/java/net/floodlightcontroller/virtualnetwork/VirtualNetwork.java @@ -19,9 +19,10 @@ package net.floodlightcontroller.virtualnetwork; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import net.floodlightcontroller.util.MACAddress; +import org.projectfloodlight.openflow.types.MacAddress; + +import com.fasterxml.jackson.databind.annotation.JsonSerialize; /** * Data structure for storing and outputing information of a virtual network created @@ -35,7 +36,7 @@ public class VirtualNetwork{ protected String name; // network name protected String guid; // network id protected String gateway; // network gateway - protected Map<String,MACAddress> portToMac; //port's logical namd and the host's mac address connected + protected Map<String, MacAddress> portToMac; //port's logical namd and the host's mac address connected /** * Constructor requires network name and id * @param name: network name @@ -45,7 +46,7 @@ public class VirtualNetwork{ this.name = name; this.guid = guid; this.gateway = null; - this.portToMac = new ConcurrentHashMap<String,MACAddress>(); + this.portToMac = new ConcurrentHashMap<String,MacAddress>(); return; } @@ -71,9 +72,9 @@ public class VirtualNetwork{ * Adds a host to this network record * @param host: MAC address as MACAddress */ - public void addHost(String port,MACAddress host){ - this.portToMac.put(port,host); // ignore old mapping - return; + public void addHost(String port, MacAddress host){ + this.portToMac.put(port, host); // ignore old mapping + return; } /** @@ -81,9 +82,9 @@ public class VirtualNetwork{ * @param host: MAC address as MACAddress * @return boolean: true: removed, false: host not found */ - public boolean removeHost(MACAddress host){ - for (Entry<String,MACAddress> entry : this.portToMac.entrySet()){ - if(entry.getValue().equals(host)){ + public boolean removeHost(MacAddress host){ + for (Entry<String, MacAddress> entry : this.portToMac.entrySet()) { + if (entry.getValue().equals(host)){ this.portToMac.remove(entry.getKey()); return true; } diff --git a/src/main/java/net/floodlightcontroller/virtualnetwork/VirtualNetworkFilter.java b/src/main/java/net/floodlightcontroller/virtualnetwork/VirtualNetworkFilter.java index 1d64144dcf19a3d9a73fce36b3976f8b7d4cbaec..f5ea145c4af628df9331907d7ca7ca5942969f34 100644 --- a/src/main/java/net/floodlightcontroller/virtualnetwork/VirtualNetworkFilter.java +++ b/src/main/java/net/floodlightcontroller/virtualnetwork/VirtualNetworkFilter.java @@ -16,7 +16,6 @@ package net.floodlightcontroller.virtualnetwork; -import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -28,14 +27,15 @@ import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -import org.openflow.protocol.OFFlowMod; -import org.openflow.protocol.OFMatch; -import org.openflow.protocol.OFMessage; -import org.openflow.protocol.OFPacketIn; -import org.openflow.protocol.OFPacketOut; -import org.openflow.protocol.OFType; -import org.openflow.protocol.action.OFAction; -import org.openflow.util.HexString; +import org.projectfloodlight.openflow.protocol.OFFlowMod; +import org.projectfloodlight.openflow.protocol.OFMessage; +import org.projectfloodlight.openflow.protocol.OFPacketIn; +import org.projectfloodlight.openflow.protocol.OFType; +import org.projectfloodlight.openflow.protocol.action.OFAction; +import org.projectfloodlight.openflow.types.IPv4Address; +import org.projectfloodlight.openflow.types.MacAddress; +import org.projectfloodlight.openflow.types.OFBufferId; +import org.projectfloodlight.openflow.types.U64; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -54,10 +54,8 @@ import net.floodlightcontroller.devicemanager.IDeviceService; import net.floodlightcontroller.packet.DHCP; import net.floodlightcontroller.packet.Ethernet; import net.floodlightcontroller.packet.IPacket; -import net.floodlightcontroller.packet.IPv4; import net.floodlightcontroller.restserver.IRestApiService; import net.floodlightcontroller.routing.ForwardingBase; -import net.floodlightcontroller.util.MACAddress; /** * A simple Layer 2 (MAC based) network virtualization module. This module allows @@ -73,492 +71,480 @@ import net.floodlightcontroller.util.MACAddress; * @author alexreimers */ public class VirtualNetworkFilter - implements IFloodlightModule, IVirtualNetworkService, IOFMessageListener { - protected static Logger log = LoggerFactory.getLogger(VirtualNetworkFilter.class); - - private static final short APP_ID = 20; - static { - AppCookie.registerApp(APP_ID, "VirtualNetworkFilter"); - } - - // Our dependencies - IFloodlightProviderService floodlightProvider; - IRestApiService restApi; - IDeviceService deviceService; - - // Our internal state - protected Map<String, VirtualNetwork> vNetsByGuid; // List of all created virtual networks - protected Map<String, String> nameToGuid; // Logical name -> Network ID - protected Map<String, Integer> guidToGateway; // Network ID -> Gateway IP - protected Map<Integer, Set<String>> gatewayToGuid; // Gateway IP -> Network ID - protected Map<MACAddress, Integer> macToGateway; // Gateway MAC -> Gateway IP - protected Map<MACAddress, String> macToGuid; // Host MAC -> Network ID - protected Map<String, MACAddress> portToMac; // Host MAC -> logical port name - - // Device Listener impl class - protected DeviceListenerImpl deviceListener; - - /** - * Adds a gateway to a virtual network. - * @param guid The ID (not name) of the network. - * @param ip The IP addresses of the gateway. - */ - protected void addGateway(String guid, Integer ip) { - if (ip.intValue() != 0) { - if (log.isDebugEnabled()) { - log.debug("Adding {} as gateway for GUID {}", - IPv4.fromIPv4Address(ip), guid); - } - - guidToGateway.put(guid, ip); - if (vNetsByGuid.get(guid) != null) - vNetsByGuid.get(guid).setGateway(IPv4.fromIPv4Address(ip)); - if (gatewayToGuid.containsKey(ip)) { - Set<String> gSet = gatewayToGuid.get(ip); - gSet.add(guid); - } else { - Set<String> gSet = Collections.synchronizedSet(new HashSet<String>()); - gSet.add(guid); - gatewayToGuid.put(ip, gSet); - } - } - } - - /** - * Deletes a gateway for a virtual network. - * @param guid The ID (not name) of the network to delete - * the gateway for. - */ - protected void deleteGateway(String guid) { - Integer gwIp = guidToGateway.remove(guid); - if (gwIp == null) return; - Set<String> gSet = gatewayToGuid.get(gwIp); - gSet.remove(guid); - if(vNetsByGuid.get(guid)!=null) - vNetsByGuid.get(guid).setGateway(null); - } - - // IVirtualNetworkService - - @Override - public void createNetwork(String guid, String network, Integer gateway) { - if (log.isDebugEnabled()) { - String gw = null; - try { - gw = IPv4.fromIPv4Address(gateway); - } catch (Exception e) { - // fail silently - } - log.debug("Creating network {} with ID {} and gateway {}", - new Object[] {network, guid, gw}); - } - - if (!nameToGuid.isEmpty()) { - // We have to iterate all the networks to handle name/gateway changes - for (Entry<String, String> entry : nameToGuid.entrySet()) { - if (entry.getValue().equals(guid)) { - nameToGuid.remove(entry.getKey()); - break; - } - } - } - if(network != null) - nameToGuid.put(network, guid); - if (vNetsByGuid.containsKey(guid)) - vNetsByGuid.get(guid).setName(network); //network already exists, just updating name - else - vNetsByGuid.put(guid, new VirtualNetwork(network, guid)); //new network - - // If they don't specify a new gateway the old one will be preserved - if ((gateway != null) && (gateway != 0)) { - addGateway(guid, gateway); - if(vNetsByGuid.get(guid)!=null) - vNetsByGuid.get(guid).setGateway(IPv4.fromIPv4Address(gateway)); - } - } - - @Override - public void deleteNetwork(String guid) { - String name = null; - if (nameToGuid.isEmpty()) { - log.warn("Could not delete network with ID {}, network doesn't exist", - guid); - return; - } - for (Entry<String, String> entry : nameToGuid.entrySet()) { - if (entry.getValue().equals(guid)) { - name = entry.getKey(); - break; - } - log.warn("Could not delete network with ID {}, network doesn't exist", - guid); - } - - if (log.isDebugEnabled()) - log.debug("Deleting network with name {} ID {}", name, guid); - - nameToGuid.remove(name); - deleteGateway(guid); - if(vNetsByGuid.get(guid)!=null){ - vNetsByGuid.get(guid).clearHosts(); - vNetsByGuid.remove(guid); - } - Collection<MACAddress> deleteList = new ArrayList<MACAddress>(); - for (MACAddress host : macToGuid.keySet()) { - if (macToGuid.get(host).equals(guid)) { - deleteList.add(host); - } - } - for (MACAddress mac : deleteList) { - if (log.isDebugEnabled()) { - log.debug("Removing host {} from network {}", - HexString.toHexString(mac.toBytes()), guid); - } - macToGuid.remove(mac); - for (Entry<String, MACAddress> entry : portToMac.entrySet()) { - if (entry.getValue().equals(mac)) { - portToMac.remove(entry.getKey()); - break; - } - } - } - } - - @Override - public void addHost(MACAddress mac, String guid, String port) { - if (guid != null) { - if (log.isDebugEnabled()) { - log.debug("Adding {} to network ID {} on port {}", - new Object[] {mac, guid, port}); - } - // We ignore old mappings - macToGuid.put(mac, guid); - portToMac.put(port, mac); - if(vNetsByGuid.get(guid)!=null) - vNetsByGuid.get(guid).addHost(port,new MACAddress(mac.toBytes())); - } else { - log.warn("Could not add MAC {} to network ID {} on port {}, the network does not exist", - new Object[] {mac, guid, port}); - } - } - - @Override - public void deleteHost(MACAddress mac, String port) { - if (log.isDebugEnabled()) { - log.debug("Removing host {} from port {}", mac, port); - } - if (mac == null && port == null) return; - if (port != null) { - MACAddress host = portToMac.remove(port); - if(host !=null && vNetsByGuid.get(macToGuid.get(host)) != null) - vNetsByGuid.get(macToGuid.get(host)).removeHost(host); - if(host !=null) - macToGuid.remove(host); - } else if (mac != null) { - if (!portToMac.isEmpty()) { - for (Entry<String, MACAddress> entry : portToMac.entrySet()) { - if (entry.getValue().equals(mac)) { - if(vNetsByGuid.get(macToGuid.get(entry.getValue())) != null) - vNetsByGuid.get(macToGuid.get(entry.getValue())).removeHost(entry.getValue()); - portToMac.remove(entry.getKey()); - macToGuid.remove(entry.getValue()); - return; - } - } - } - } - } - - // IFloodlightModule - - @Override - public Collection<Class<? extends IFloodlightService>> getModuleServices() { - Collection<Class<? extends IFloodlightService>> l = - new ArrayList<Class<? extends IFloodlightService>>(); - l.add(IVirtualNetworkService.class); - return l; - } - - @Override - public Map<Class<? extends IFloodlightService>, IFloodlightService> - getServiceImpls() { - Map<Class<? extends IFloodlightService>, - IFloodlightService> m = - new HashMap<Class<? extends IFloodlightService>, - IFloodlightService>(); - m.put(IVirtualNetworkService.class, this); - return m; - } - - @Override - public Collection<Class<? extends IFloodlightService>> getModuleDependencies() { - Collection<Class<? extends IFloodlightService>> l = - new ArrayList<Class<? extends IFloodlightService>>(); - l.add(IFloodlightProviderService.class); - l.add(IRestApiService.class); - l.add(IDeviceService.class); - return l; - } - - @Override - public void init(FloodlightModuleContext context) - throws FloodlightModuleException { - floodlightProvider = context.getServiceImpl(IFloodlightProviderService.class); - restApi = context.getServiceImpl(IRestApiService.class); - deviceService = context.getServiceImpl(IDeviceService.class); - - vNetsByGuid = new ConcurrentHashMap<String, VirtualNetwork>(); - nameToGuid = new ConcurrentHashMap<String, String>(); - guidToGateway = new ConcurrentHashMap<String, Integer>(); - gatewayToGuid = new ConcurrentHashMap<Integer, Set<String>>(); - macToGuid = new ConcurrentHashMap<MACAddress, String>(); - portToMac = new ConcurrentHashMap<String, MACAddress>(); - macToGateway = new ConcurrentHashMap<MACAddress, Integer>(); - deviceListener = new DeviceListenerImpl(); - - } - - @Override - public void startUp(FloodlightModuleContext context) { - floodlightProvider.addOFMessageListener(OFType.PACKET_IN, this); - restApi.addRestletRoutable(new VirtualNetworkWebRoutable()); - deviceService.addListener(this.deviceListener); - } - - // IOFMessageListener - - @Override - public String getName() { - return "virtualizer"; - } - - @Override - public boolean isCallbackOrderingPrereq(OFType type, String name) { - // Link discovery should go before us so we don't block LLDPs - return (type.equals(OFType.PACKET_IN) && - (name.equals("linkdiscovery") || (name.equals("devicemanager")))); - } - - @Override - public boolean isCallbackOrderingPostreq(OFType type, String name) { - // We need to go before forwarding - return (type.equals(OFType.PACKET_IN) && name.equals("forwarding")); - } - - @Override - public Command receive(IOFSwitch sw, OFMessage msg, FloodlightContext cntx) { - switch (msg.getType()) { - case PACKET_IN: - return processPacketIn(sw, (OFPacketIn)msg, cntx); - default: - break; - } - log.warn("Received unexpected message {}", msg); - return Command.CONTINUE; - } - - /** - * Checks whether the frame is destined to or from a gateway. - * @param frame The ethernet frame to check. - * @return True if it is to/from a gateway, false otherwise. - */ - protected boolean isDefaultGateway(Ethernet frame) { - if (macToGateway.containsKey(frame.getSourceMAC())) - return true; - - Integer gwIp = macToGateway.get(frame.getDestinationMAC()); - if (gwIp != null) { - MACAddress host = frame.getSourceMAC(); - String srcNet = macToGuid.get(host); - if (srcNet != null) { - Integer gwIpSrcNet = guidToGateway.get(srcNet); - if ((gwIpSrcNet != null) && (gwIp.equals(gwIpSrcNet))) - return true; - } - } - - return false; - } - - /** - * Checks to see if two MAC Addresses are on the same network. - * @param m1 The first MAC. - * @param m2 The second MAC. - * @return True if they are on the same virtual network, - * false otherwise. - */ - protected boolean oneSameNetwork(MACAddress m1, MACAddress m2) { - String net1 = macToGuid.get(m1); - String net2 = macToGuid.get(m2); - if (net1 == null) return false; - if (net2 == null) return false; - return net1.equals(net2); - } - - /** - * Checks to see if an Ethernet frame is a DHCP packet. - * @param frame The Ethernet frame. - * @return True if it is a DHCP frame, false otherwise. - */ - protected boolean isDhcpPacket(Ethernet frame) { - IPacket payload = frame.getPayload(); // IP - if (payload == null) return false; - IPacket p2 = payload.getPayload(); // TCP or UDP - if (p2 == null) return false; - IPacket p3 = p2.getPayload(); // Application - if ((p3 != null) && (p3 instanceof DHCP)) return true; - return false; - } - - /** - * Processes an OFPacketIn message and decides if the OFPacketIn should be dropped - * or the processing should continue. - * @param sw The switch the PacketIn came from. - * @param msg The OFPacketIn message from the switch. - * @param cntx The FloodlightContext for this message. - * @return Command.CONTINUE if processing should be continued, Command.STOP otherwise. - */ - protected Command processPacketIn(IOFSwitch sw, OFPacketIn msg, FloodlightContext cntx) { - Ethernet eth = IFloodlightProviderService.bcStore.get(cntx, - IFloodlightProviderService.CONTEXT_PI_PAYLOAD); - Command ret = Command.STOP; - String srcNetwork = macToGuid.get(eth.getSourceMAC()); - // If the host is on an unknown network we deny it. - // We make exceptions for ARP and DHCP. - if (eth.isBroadcast() || eth.isMulticast() || isDefaultGateway(eth) || isDhcpPacket(eth)) { - ret = Command.CONTINUE; - } else if (srcNetwork == null) { - log.trace("Blocking traffic from host {} because it is not attached to any network.", - HexString.toHexString(eth.getSourceMACAddress())); - ret = Command.STOP; - } else if (oneSameNetwork(eth.getSourceMAC(), eth.getDestinationMAC())) { - // if they are on the same network continue - ret = Command.CONTINUE; - } - - if (log.isTraceEnabled()) - log.trace("Results for flow between {} and {} is {}", - new Object[] {eth.getSourceMAC(), eth.getDestinationMAC(), ret}); - /* - * TODO - figure out how to still detect gateways while using - * drop mods +implements IFloodlightModule, IVirtualNetworkService, IOFMessageListener { + protected static Logger log = LoggerFactory.getLogger(VirtualNetworkFilter.class); + + private static final short APP_ID = 20; + static { + AppCookie.registerApp(APP_ID, "VirtualNetworkFilter"); + } + + // Our dependencies + IFloodlightProviderService floodlightProviderService; + IRestApiService restApiService; + IDeviceService deviceService; + + // Our internal state + protected Map<String, VirtualNetwork> vNetsByGuid; // List of all created virtual networks + protected Map<String, String> nameToGuid; // Logical name -> Network ID + protected Map<String, IPv4Address> guidToGateway; // Network ID -> Gateway IP + protected Map<IPv4Address, Set<String>> gatewayToGuid; // Gateway IP -> Network ID + protected Map<MacAddress, IPv4Address> macToGateway; // Gateway MAC -> Gateway IP + protected Map<MacAddress, String> macToGuid; // Host MAC -> Network ID + protected Map<String, MacAddress> portToMac; // Host MAC -> logical port name + + // Device Listener impl class + protected DeviceListenerImpl deviceListener; + + /** + * Adds a gateway to a virtual network. + * @param guid The ID (not name) of the network. + * @param ip The IP addresses of the gateway. + */ + protected void addGateway(String guid, IPv4Address ip) { + if (ip.getInt() != 0) { + if (log.isDebugEnabled()) { + log.debug("Adding {} as gateway for GUID {}", ip.toString(), guid); + } + + guidToGateway.put(guid, ip); + if (vNetsByGuid.get(guid) != null) + vNetsByGuid.get(guid).setGateway(ip.toString()); + if (gatewayToGuid.containsKey(ip)) { + Set<String> gSet = gatewayToGuid.get(ip); + gSet.add(guid); + } else { + Set<String> gSet = Collections.synchronizedSet(new HashSet<String>()); + gSet.add(guid); + gatewayToGuid.put(ip, gSet); + } + } + } + + /** + * Deletes a gateway for a virtual network. + * @param guid The ID (not name) of the network to delete + * the gateway for. + */ + protected void deleteGateway(String guid) { + IPv4Address gwIp = guidToGateway.remove(guid); + if (gwIp == null) return; + Set<String> gSet = gatewayToGuid.get(gwIp); + gSet.remove(guid); + if (vNetsByGuid.get(guid) != null) + vNetsByGuid.get(guid).setGateway(null); + } + + // IVirtualNetworkService + + @Override + public void createNetwork(String guid, String network, IPv4Address gateway) { + if (log.isDebugEnabled()) { + String gw = null; + try { + gw = gateway.toString(); + } catch (Exception e) { + // fail silently + } + log.debug("Creating network {} with ID {} and gateway {}", + new Object[] {network, guid, gw}); + } + + if (!nameToGuid.isEmpty()) { + // We have to iterate all the networks to handle name/gateway changes + for (Entry<String, String> entry : nameToGuid.entrySet()) { + if (entry.getValue().equals(guid)) { + nameToGuid.remove(entry.getKey()); + break; + } + } + } + if(network != null) + nameToGuid.put(network, guid); + if (vNetsByGuid.containsKey(guid)) + vNetsByGuid.get(guid).setName(network); //network already exists, just updating name + else + vNetsByGuid.put(guid, new VirtualNetwork(network, guid)); //new network + + // If they don't specify a new gateway the old one will be preserved + if ((gateway != null) && (gateway.getInt() != 0)) { + addGateway(guid, gateway); + if (vNetsByGuid.get(guid) != null) + vNetsByGuid.get(guid).setGateway(gateway.toString()); + } + } + + @Override + public void deleteNetwork(String guid) { + String name = null; + if (nameToGuid.isEmpty()) { + log.warn("Could not delete network with ID {}, network doesn't exist", + guid); + return; + } + for (Entry<String, String> entry : nameToGuid.entrySet()) { + if (entry.getValue().equals(guid)) { + name = entry.getKey(); + break; + } + log.warn("Could not delete network with ID {}, network doesn't exist", guid); + } + + if (log.isDebugEnabled()) + log.debug("Deleting network with name {} ID {}", name, guid); + + nameToGuid.remove(name); + deleteGateway(guid); + if (vNetsByGuid.get(guid) != null){ + vNetsByGuid.get(guid).clearHosts(); + vNetsByGuid.remove(guid); + } + Collection<MacAddress> deleteList = new ArrayList<MacAddress>(); + for (MacAddress host : macToGuid.keySet()) { + if (macToGuid.get(host).equals(guid)) { + deleteList.add(host); + } + } + for (MacAddress mac : deleteList) { + if (log.isDebugEnabled()) { + log.debug("Removing host {} from network {}", mac.toString(), guid); + } + macToGuid.remove(mac); + for (Entry<String, MacAddress> entry : portToMac.entrySet()) { + if (entry.getValue().equals(mac)) { + portToMac.remove(entry.getKey()); + break; + } + } + } + } + + @Override + public void addHost(MacAddress mac, String guid, String port) { + if (guid != null) { + if (log.isDebugEnabled()) { + log.debug("Adding {} to network ID {} on port {}", + new Object[] {mac, guid, port}); + } + // We ignore old mappings + macToGuid.put(mac, guid); + portToMac.put(port, mac); + if (vNetsByGuid.get(guid) != null) + vNetsByGuid.get(guid).addHost(port, mac); + } else { + log.warn("Could not add MAC {} to network ID {} on port {}, the network does not exist", + new Object[] {mac.toString(), guid, port}); + } + } + + @Override + public void deleteHost(MacAddress mac, String port) { + if (log.isDebugEnabled()) { + log.debug("Removing host {} from port {}", mac, port); + } + if (mac == null && port == null) return; + if (port != null) { + MacAddress host = portToMac.remove(port); + if (host != null && vNetsByGuid.get(macToGuid.get(host)) != null) + vNetsByGuid.get(macToGuid.get(host)).removeHost(host); + if (host != null) + macToGuid.remove(host); + } else if (mac != null) { + if (!portToMac.isEmpty()) { + for (Entry<String, MacAddress> entry : portToMac.entrySet()) { + if (entry.getValue().equals(mac)) { + if (vNetsByGuid.get(macToGuid.get(entry.getValue())) != null) + vNetsByGuid.get(macToGuid.get(entry.getValue())).removeHost(entry.getValue()); + portToMac.remove(entry.getKey()); + macToGuid.remove(entry.getValue()); + return; + } + } + } + } + } + + // IFloodlightModule + + @Override + public Collection<Class<? extends IFloodlightService>> getModuleServices() { + Collection<Class<? extends IFloodlightService>> l = + new ArrayList<Class<? extends IFloodlightService>>(); + l.add(IVirtualNetworkService.class); + return l; + } + + @Override + public Map<Class<? extends IFloodlightService>, IFloodlightService> + getServiceImpls() { + Map<Class<? extends IFloodlightService>, + IFloodlightService> m = + new HashMap<Class<? extends IFloodlightService>, + IFloodlightService>(); + m.put(IVirtualNetworkService.class, this); + return m; + } + + @Override + public Collection<Class<? extends IFloodlightService>> getModuleDependencies() { + Collection<Class<? extends IFloodlightService>> l = + new ArrayList<Class<? extends IFloodlightService>>(); + l.add(IFloodlightProviderService.class); + l.add(IRestApiService.class); + l.add(IDeviceService.class); + return l; + } + + @Override + public void init(FloodlightModuleContext context) + throws FloodlightModuleException { + floodlightProviderService = context.getServiceImpl(IFloodlightProviderService.class); + restApiService = context.getServiceImpl(IRestApiService.class); + deviceService = context.getServiceImpl(IDeviceService.class); + + vNetsByGuid = new ConcurrentHashMap<String, VirtualNetwork>(); + nameToGuid = new ConcurrentHashMap<String, String>(); + guidToGateway = new ConcurrentHashMap<String, IPv4Address>(); + gatewayToGuid = new ConcurrentHashMap<IPv4Address, Set<String>>(); + macToGuid = new ConcurrentHashMap<MacAddress, String>(); + portToMac = new ConcurrentHashMap<String, MacAddress>(); + macToGateway = new ConcurrentHashMap<MacAddress, IPv4Address>(); + deviceListener = new DeviceListenerImpl(); + + } + + @Override + public void startUp(FloodlightModuleContext context) { + floodlightProviderService.addOFMessageListener(OFType.PACKET_IN, this); + restApiService.addRestletRoutable(new VirtualNetworkWebRoutable()); + deviceService.addListener(this.deviceListener); + } + + // IOFMessageListener + + @Override + public String getName() { + return "virtualizer"; + } + + @Override + public boolean isCallbackOrderingPrereq(OFType type, String name) { + // Link discovery should go before us so we don't block LLDPs + return (type.equals(OFType.PACKET_IN) && + (name.equals("linkdiscovery") || (name.equals("devicemanager")))); + } + + @Override + public boolean isCallbackOrderingPostreq(OFType type, String name) { + // We need to go before forwarding + return (type.equals(OFType.PACKET_IN) && name.equals("forwarding")); + } + + @Override + public Command receive(IOFSwitch sw, OFMessage msg, FloodlightContext cntx) { + switch (msg.getType()) { + case PACKET_IN: + return processPacketIn(sw, (OFPacketIn)msg, cntx); + default: + break; + } + log.warn("Received unexpected message {}", msg); + return Command.CONTINUE; + } + + /** + * Checks whether the frame is destined to or from a gateway. + * @param frame The ethernet frame to check. + * @return True if it is to/from a gateway, false otherwise. + */ + protected boolean isDefaultGateway(Ethernet frame) { + if (macToGateway.containsKey(frame.getSourceMACAddress())) + return true; + + IPv4Address gwIp = macToGateway.get(frame.getDestinationMACAddress()); + if (gwIp != null) { + MacAddress host = frame.getSourceMACAddress(); + String srcNet = macToGuid.get(host); + if (srcNet != null) { + IPv4Address gwIpSrcNet = guidToGateway.get(srcNet); + if ((gwIpSrcNet != null) && (gwIp.equals(gwIpSrcNet))) + return true; + } + } + + return false; + } + + /** + * Checks to see if two MAC Addresses are on the same network. + * @param m1 The first MAC. + * @param m2 The second MAC. + * @return True if they are on the same virtual network, + * false otherwise. + */ + protected boolean oneSameNetwork(MacAddress m1, MacAddress m2) { + String net1 = macToGuid.get(m1); + String net2 = macToGuid.get(m2); + if (net1 == null) return false; + if (net2 == null) return false; + return net1.equals(net2); + } + + /** + * Checks to see if an Ethernet frame is a DHCP packet. + * @param frame The Ethernet frame. + * @return True if it is a DHCP frame, false otherwise. + */ + protected boolean isDhcpPacket(Ethernet frame) { + IPacket payload = frame.getPayload(); // IP + if (payload == null) return false; + IPacket p2 = payload.getPayload(); // TCP or UDP + if (p2 == null) return false; + IPacket p3 = p2.getPayload(); // Application + if ((p3 != null) && (p3 instanceof DHCP)) return true; + return false; + } + + /** + * Processes an OFPacketIn message and decides if the OFPacketIn should be dropped + * or the processing should continue. + * @param sw The switch the PacketIn came from. + * @param msg The OFPacketIn message from the switch. + * @param cntx The FloodlightContext for this message. + * @return Command.CONTINUE if processing should be continued, Command.STOP otherwise. + */ + protected Command processPacketIn(IOFSwitch sw, OFPacketIn msg, FloodlightContext cntx) { + Ethernet eth = IFloodlightProviderService.bcStore.get(cntx, + IFloodlightProviderService.CONTEXT_PI_PAYLOAD); + Command ret = Command.STOP; + String srcNetwork = macToGuid.get(eth.getSourceMACAddress()); + // If the host is on an unknown network we deny it. + // We make exceptions for ARP and DHCP. + if (eth.isBroadcast() || eth.isMulticast() || isDefaultGateway(eth) || isDhcpPacket(eth)) { + ret = Command.CONTINUE; + } else if (srcNetwork == null) { + log.trace("Blocking traffic from host {} because it is not attached to any network.", + eth.getSourceMACAddress().toString()); + ret = Command.STOP; + } else if (oneSameNetwork(eth.getSourceMACAddress(), eth.getDestinationMACAddress())) { + // if they are on the same network continue + ret = Command.CONTINUE; + } + + if (log.isTraceEnabled()) + log.trace("Results for flow between {} and {} is {}", + new Object[] {eth.getSourceMACAddress(), eth.getDestinationMACAddress(), ret}); + /* + * TODO - figure out how to still detect gateways while using + * drop mods if (ret == Command.STOP) { if (!(eth.getPayload() instanceof ARP)) doDropFlow(sw, msg, cntx); } - */ - return ret; - } - - /** - * Writes a FlowMod to a switch that inserts a drop flow. - * @param sw The switch to write the FlowMod to. - * @param pi The corresponding OFPacketIn. Used to create the OFMatch structure. - * @param cntx The FloodlightContext that gets passed to the switch. - */ - protected void doDropFlow(IOFSwitch sw, OFPacketIn pi, FloodlightContext cntx) { - if (log.isTraceEnabled()) { - log.trace("doDropFlow pi={} srcSwitch={}", - new Object[] { pi, sw }); - } - - if (sw == null) { - log.warn("Switch is null, not installing drop flowmod for PacketIn {}", pi); - return; - } - - // Create flow-mod based on packet-in and src-switch - OFFlowMod fm = - (OFFlowMod) floodlightProvider.getOFMessageFactory().getMessage(OFType.FLOW_MOD); - OFMatch match = new OFMatch(); - match.loadFromPacket(pi.getPacketData(), pi.getInPort()); - List<OFAction> actions = new ArrayList<OFAction>(); // no actions = drop - long cookie = AppCookie.makeCookie(APP_ID, 0); - fm.setCookie(cookie) - .setIdleTimeout(ForwardingBase.FLOWMOD_DEFAULT_IDLE_TIMEOUT) - .setHardTimeout(ForwardingBase.FLOWMOD_DEFAULT_HARD_TIMEOUT) - .setBufferId(OFPacketOut.BUFFER_ID_NONE) - .setMatch(match) - .setActions(actions) - .setLengthU(OFFlowMod.MINIMUM_LENGTH); -// fm.setFlags(OFFlowMod.OFPFF_SEND_FLOW_REM); - try { - if (log.isTraceEnabled()) { - log.trace("write drop flow-mod srcSwitch={} match={} " + - "pi={} flow-mod={}", - new Object[] {sw, match, pi, fm}); - } - sw.write(fm, cntx); - } catch (IOException e) { - log.error("Failure writing drop flow mod", e); - } - return; - } - - @Override - public Collection <VirtualNetwork> listNetworks() { - return vNetsByGuid.values(); - } - - // IDeviceListener - class DeviceListenerImpl implements IDeviceListener{ - @Override - public void deviceAdded(IDevice device) { - if (device.getIPv4Addresses() == null) return; - for (Integer i : device.getIPv4Addresses()) { - if (gatewayToGuid.containsKey(i)) { - MACAddress mac = MACAddress.valueOf(device.getMACAddress()); - if (log.isDebugEnabled()) - log.debug("Adding MAC {} with IP {} a a gateway", - HexString.toHexString(mac.toBytes()), - IPv4.fromIPv4Address(i)); - macToGateway.put(mac, i); - } - } - } - - @Override - public void deviceRemoved(IDevice device) { - // if device is a gateway remove - MACAddress mac = MACAddress.valueOf(device.getMACAddress()); - if (macToGateway.containsKey(mac)) { - if (log.isDebugEnabled()) - log.debug("Removing MAC {} as a gateway", - HexString.toHexString(mac.toBytes())); - macToGateway.remove(mac); - } - } - - @Override - public void deviceIPV4AddrChanged(IDevice device) { - // add or remove entry as gateway - deviceAdded(device); - } - - @Override - public void deviceMoved(IDevice device) { - // ignore - } - - @Override - public void deviceVlanChanged(IDevice device) { - // ignore - } - - @Override - public String getName() { - return VirtualNetworkFilter.this.getName(); - } - - @Override - public boolean isCallbackOrderingPrereq(String type, String name) { - return false; - } - - @Override - public boolean isCallbackOrderingPostreq(String type, String name) { - // We need to go before forwarding - return false; - } - } + */ + return ret; + } + + /** + * Writes a FlowMod to a switch that inserts a drop flow. + * @param sw The switch to write the FlowMod to. + * @param pi The corresponding OFPacketIn. Used to create the OFMatch structure. + * @param cntx The FloodlightContext that gets passed to the switch. + */ + protected void doDropFlow(IOFSwitch sw, OFPacketIn pi, FloodlightContext cntx) { + if (log.isTraceEnabled()) { + log.trace("doDropFlow pi={} srcSwitch={}", + new Object[] { pi, sw }); + } + + if (sw == null) { + log.warn("Switch is null, not installing drop flowmod for PacketIn {}", pi); + return; + } + + // Create flow-mod based on packet-in and src-switch + OFFlowMod.Builder fmb = sw.getOFFactory().buildFlowModify(); + List<OFAction> actions = new ArrayList<OFAction>(); // no actions = drop + U64 cookie = AppCookie.makeCookie(APP_ID, 0); + fmb.setCookie(cookie) + .setIdleTimeout(ForwardingBase.FLOWMOD_DEFAULT_IDLE_TIMEOUT) + .setHardTimeout(ForwardingBase.FLOWMOD_DEFAULT_HARD_TIMEOUT) + .setBufferId(OFBufferId.NO_BUFFER) + .setMatch(pi.getMatch()) + .setActions(actions); + + if (log.isTraceEnabled()) { + log.trace("write drop flow-mod srcSwitch={} match={} " + + "pi={} flow-mod={}", + new Object[] {sw, pi.getMatch(), pi, fmb.build()}); + } + sw.write(fmb.build()); + return; + } + + @Override + public Collection <VirtualNetwork> listNetworks() { + return vNetsByGuid.values(); + } + + // IDeviceListener + class DeviceListenerImpl implements IDeviceListener{ + @Override + public void deviceAdded(IDevice device) { + if (device.getIPv4Addresses() == null) return; + for (IPv4Address i : device.getIPv4Addresses()) { + if (gatewayToGuid.containsKey(i)) { + MacAddress mac = device.getMACAddress(); + if (log.isDebugEnabled()) + log.debug("Adding MAC {} with IP {} a a gateway", + mac.toString(), + i.toString()); + macToGateway.put(mac, i); + } + } + } + + @Override + public void deviceRemoved(IDevice device) { + // if device is a gateway remove + MacAddress mac = device.getMACAddress(); + if (macToGateway.containsKey(mac)) { + if (log.isDebugEnabled()) + log.debug("Removing MAC {} as a gateway", mac.toString()); + macToGateway.remove(mac); + } + } + + @Override + public void deviceIPV4AddrChanged(IDevice device) { + // add or remove entry as gateway + deviceAdded(device); + } + + @Override + public void deviceMoved(IDevice device) { + // ignore + } + + @Override + public void deviceVlanChanged(IDevice device) { + // ignore + } + + @Override + public String getName() { + return VirtualNetworkFilter.this.getName(); + } + + @Override + public boolean isCallbackOrderingPrereq(String type, String name) { + return false; + } + + @Override + public boolean isCallbackOrderingPostreq(String type, String name) { + // We need to go before forwarding + return false; + } + } } diff --git a/src/main/java/net/floodlightcontroller/virtualnetwork/VirtualNetworkSerializer.java b/src/main/java/net/floodlightcontroller/virtualnetwork/VirtualNetworkSerializer.java index 190432ab7aeacefcaa89054ecebbb59e26845811..1a345de45b43f62c08fc27efeab94e4a9d90fc25 100644 --- a/src/main/java/net/floodlightcontroller/virtualnetwork/VirtualNetworkSerializer.java +++ b/src/main/java/net/floodlightcontroller/virtualnetwork/VirtualNetworkSerializer.java @@ -20,7 +20,7 @@ import java.io.IOException; import java.util.Iterator; import java.util.Map.Entry; -import net.floodlightcontroller.util.MACAddress; +import org.projectfloodlight.openflow.types.MacAddress; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonProcessingException; @@ -44,10 +44,10 @@ public class VirtualNetworkSerializer extends JsonSerializer<VirtualNetwork> { jGen.writeStringField("gateway", vNet.gateway); jGen.writeArrayFieldStart("portMac"); - Iterator entries = vNet.portToMac.entrySet().iterator(); + Iterator<Entry<String, MacAddress>> entries = vNet.portToMac.entrySet().iterator(); while (entries.hasNext()){ jGen.writeStartObject(); - Entry entry = (Entry)entries.next(); + Entry<String, MacAddress> entry = entries.next(); jGen.writeStringField("port",entry.getKey().toString()); jGen.writeStringField("mac",entry.getValue().toString()); jGen.writeEndObject(); diff --git a/src/main/java/org/sdnplatform/sync/ISyncService.java b/src/main/java/org/sdnplatform/sync/ISyncService.java index 75f3948a8e7b20d669492573b3dc616f8d9c54a7..48feead4a3c378e61147292203db3ccaa6435900 100644 --- a/src/main/java/org/sdnplatform/sync/ISyncService.java +++ b/src/main/java/org/sdnplatform/sync/ISyncService.java @@ -40,6 +40,8 @@ public interface ISyncService extends IFloodlightService { */ UNSYNCHRONIZED } + + public short getLocalNodeId(); /** * Create a store with the given store name and scope diff --git a/src/main/java/org/sdnplatform/sync/internal/SyncManager.java b/src/main/java/org/sdnplatform/sync/internal/SyncManager.java index 952918c50217c91cc5a2c30b39a1f9c7cbf4b60b..cfe27e00e7738d7daeab5cd02162512e1ab19f3b 100644 --- a/src/main/java/org/sdnplatform/sync/internal/SyncManager.java +++ b/src/main/java/org/sdnplatform/sync/internal/SyncManager.java @@ -60,8 +60,6 @@ import net.floodlightcontroller.core.module.IFloodlightService; import net.floodlightcontroller.core.util.SingletonTask; 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.storage.IStorageSourceService; import net.floodlightcontroller.threadpool.IThreadPoolService; @@ -513,39 +511,28 @@ public class SyncManager extends AbstractSyncManager { private void registerDebugCounters(FloodlightModuleContext context) throws FloodlightModuleException { - if (context != null) { - try { - counterHints = debugCounter.registerCounter(PACKAGE, "hints", - "Queued sync events processed", - CounterType.ALWAYS_COUNT); - counterSentValues = debugCounter.registerCounter(PACKAGE, "sent-values", - "Values synced to remote node", - CounterType.ALWAYS_COUNT); - counterReceivedValues = debugCounter.registerCounter(PACKAGE, "received-values", - "Values received from remote node", - CounterType.ALWAYS_COUNT); - counterPuts = debugCounter.registerCounter(PACKAGE, "puts", - "Local puts to store", - CounterType.ALWAYS_COUNT); - counterGets = debugCounter.registerCounter(PACKAGE, "gets", - "Local gets from store", - CounterType.ALWAYS_COUNT); - counterIterators = debugCounter.registerCounter(PACKAGE, "iterators", - "Local iterators created over store", - CounterType.ALWAYS_COUNT); - counterErrorRemote = debugCounter.registerCounter(PACKAGE, "error-remote", - "Number of errors sent from remote clients", - CounterType.ALWAYS_COUNT, - IDebugCounterService.CTR_MDATA_ERROR); - counterErrorProcessing = debugCounter.registerCounter(PACKAGE, - "error-processing", - "Number of errors processing messages from remote clients", - CounterType.ALWAYS_COUNT, - IDebugCounterService.CTR_MDATA_ERROR); - } catch (CounterException e) { - throw new FloodlightModuleException(e.getMessage()); - } - } + if (context != null) { + debugCounter.registerModule(PACKAGE); + counterHints = debugCounter.registerCounter(PACKAGE, "hints", + "Queued sync events processed"); + counterSentValues = debugCounter.registerCounter(PACKAGE, "sent-values", + "Values synced to remote node"); + counterReceivedValues = debugCounter.registerCounter(PACKAGE, "received-values", + "Values received from remote node"); + counterPuts = debugCounter.registerCounter(PACKAGE, "puts", + "Local puts to store"); + counterGets = debugCounter.registerCounter(PACKAGE, "gets", + "Local gets from store"); + counterIterators = debugCounter.registerCounter(PACKAGE, "iterators", + "Local iterators created over store"); + counterErrorRemote = debugCounter.registerCounter(PACKAGE, "error-remote", + "Number of errors sent from remote clients", + IDebugCounterService.MetaData.ERROR); + counterErrorProcessing = debugCounter.registerCounter(PACKAGE, + "error-processing", + "Number of errors processing messages from remote clients", + IDebugCounterService.MetaData.ERROR); + } } @@ -780,7 +767,7 @@ public class SyncManager extends AbstractSyncManager { // XXX - todo - handle hints targeted to specific nodes storeRegistry.takeHints(tasks, 50); for (Hint task : tasks) { - counterHints.updateCounterWithFlush(); + counterHints.increment(); SynchronizingStorageEngine store = storeRegistry.get(task.getHintKey(). getStoreName()); @@ -811,10 +798,8 @@ public class SyncManager extends AbstractSyncManager { } svm.getHeader(). - setTransactionId(rpcService. - getTransactionId()); - counterSentValues.updateCounterWithFlush(bsm.getSyncValue(). - getValuesSize()); + setTransactionId(rpcService.getTransactionId()); + counterSentValues.add(bsm.getSyncValue().getValuesSize()); rpcService.writeToNode(n.getNodeId(), bsm); } } diff --git a/src/main/java/org/sdnplatform/sync/internal/SyncTorture.java b/src/main/java/org/sdnplatform/sync/internal/SyncTorture.java index 74e4ae96648089a28dd38758b817886a01f57f63..c6e218132a6f7d4b1cba9c5fb5c3724f77ecbc4f 100644 --- a/src/main/java/org/sdnplatform/sync/internal/SyncTorture.java +++ b/src/main/java/org/sdnplatform/sync/internal/SyncTorture.java @@ -184,7 +184,6 @@ public class SyncTorture implements IFloodlightModule { logger.error("Error in worker: ", e); } long iterend = System.currentTimeMillis(); - debugCounter.flushCounters(); logger.info("Completed iteration of {} values in {}ms" + " ({}/s)", new Object[]{values.size(), (iterend-start), diff --git a/src/main/java/org/sdnplatform/sync/internal/config/ClusterConfig.java b/src/main/java/org/sdnplatform/sync/internal/config/ClusterConfig.java index 27addf7fe1ff448d154af8222d561604a31e86b3..416f4ebaacddf5455a40e404ceefe251526a2617 100644 --- a/src/main/java/org/sdnplatform/sync/internal/config/ClusterConfig.java +++ b/src/main/java/org/sdnplatform/sync/internal/config/ClusterConfig.java @@ -15,6 +15,7 @@ import org.sdnplatform.sync.error.SyncException; * @author readams */ public class ClusterConfig { + public static final short NODE_ID_UNCONFIGURED = Short.MAX_VALUE; private HashMap<Short, Node> allNodes = new HashMap<Short, Node>(); diff --git a/src/main/java/org/sdnplatform/sync/internal/config/FallbackCCProvider.java b/src/main/java/org/sdnplatform/sync/internal/config/FallbackCCProvider.java index fcdfb6fafc76e25a3f29154828b2072cd8449a03..09278ae2c76ae9182858cd7f3bc7afd8e4f9ebe2 100644 --- a/src/main/java/org/sdnplatform/sync/internal/config/FallbackCCProvider.java +++ b/src/main/java/org/sdnplatform/sync/internal/config/FallbackCCProvider.java @@ -25,6 +25,7 @@ public class FallbackCCProvider implements IClusterConfigProvider { AuthScheme authScheme; String keyStorePath; String keyStorePassword; + int syncPort = 6642; public FallbackCCProvider() throws SyncException { @@ -43,7 +44,7 @@ public class FallbackCCProvider implements IClusterConfigProvider { } return new ClusterConfig(Collections. singletonList(new Node("localhost", - 6642, + syncPort, Short.MAX_VALUE, Short.MAX_VALUE)), Short.MAX_VALUE, @@ -61,6 +62,7 @@ public class FallbackCCProvider implements IClusterConfigProvider { authScheme = AuthScheme.NO_AUTH; try { authScheme = AuthScheme.valueOf(config.get("authScheme")); + syncPort = Integer.parseInt(config.get("port")); } catch (Exception e) {} } } diff --git a/src/main/java/org/sdnplatform/sync/internal/rpc/RPCChannelHandler.java b/src/main/java/org/sdnplatform/sync/internal/rpc/RPCChannelHandler.java index af5dafb1b147a247cd696834618cb4c4b7f349ab..516f387a291f2b655c1a6d0af2cd4f2fc7abed29 100644 --- a/src/main/java/org/sdnplatform/sync/internal/rpc/RPCChannelHandler.java +++ b/src/main/java/org/sdnplatform/sync/internal/rpc/RPCChannelHandler.java @@ -596,7 +596,7 @@ public class RPCChannelHandler extends AbstractRPCChannelHandler { // ***************** protected void updateCounter(IDebugCounter counter, int incr) { - counter.updateCounterWithFlush(incr); + counter.add(incr); } protected void startAntientropy() { diff --git a/src/main/java/org/sdnplatform/sync/internal/store/ListenerStorageEngine.java b/src/main/java/org/sdnplatform/sync/internal/store/ListenerStorageEngine.java index d6e730c21484b050df395dbe6b68d4aa66c7093a..9d6f9d79e9e1612eaaf9a98620d3add0941e5b95 100644 --- a/src/main/java/org/sdnplatform/sync/internal/store/ListenerStorageEngine.java +++ b/src/main/java/org/sdnplatform/sync/internal/store/ListenerStorageEngine.java @@ -160,7 +160,7 @@ public class ListenerStorageEngine protected void updateCounter(IDebugCounter counter) { if (debugCounter != null) { - counter.updateCounterWithFlush(); + counter.increment(); } } } diff --git a/src/main/java/org/sdnplatform/sync/internal/util/ByteArray.java b/src/main/java/org/sdnplatform/sync/internal/util/ByteArray.java index 5a9473562e4d226fb7e8f5f22e805074bcd90129..733f03af894c09f3e310ed84f7d5feada55a41b0 100644 --- a/src/main/java/org/sdnplatform/sync/internal/util/ByteArray.java +++ b/src/main/java/org/sdnplatform/sync/internal/util/ByteArray.java @@ -4,7 +4,7 @@ import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; -import org.openflow.util.HexString; +import org.projectfloodlight.openflow.util.HexString; /** * A byte array container that provides an equals and hashCode pair based on the diff --git a/src/main/resources/META-INF/services/net.floodlightcontroller.core.module.IFloodlightModule b/src/main/resources/META-INF/services/net.floodlightcontroller.core.module.IFloodlightModule index ce4fea11dd6e2355e6ede131d8ee5816e9efa29f..40d0cd1bc7e878267c1a195708b9c5ef1094809b 100644 --- a/src/main/resources/META-INF/services/net.floodlightcontroller.core.module.IFloodlightModule +++ b/src/main/resources/META-INF/services/net.floodlightcontroller.core.module.IFloodlightModule @@ -1,28 +1,26 @@ -net.floodlightcontroller.core.module.ApplicationLoader net.floodlightcontroller.core.internal.FloodlightProvider net.floodlightcontroller.storage.memory.MemoryStorageSource -net.floodlightcontroller.devicemanager.internal.DeviceManagerImpl -net.floodlightcontroller.linkdiscovery.internal.LinkDiscoveryManager -net.floodlightcontroller.topology.TopologyManager -net.floodlightcontroller.forwarding.Forwarding net.floodlightcontroller.flowcache.FlowReconcileManager -net.floodlightcontroller.core.OFMessageFilterManager -net.floodlightcontroller.staticflowentry.StaticFlowEntryPusher -net.floodlightcontroller.perfmon.PktInProcessingTime -net.floodlightcontroller.perfmon.NullPktInProcessingTime -net.floodlightcontroller.restserver.RestApiServer -net.floodlightcontroller.learningswitch.LearningSwitch net.floodlightcontroller.hub.Hub net.floodlightcontroller.jython.JythonDebugInterface -net.floodlightcontroller.counter.CounterStore -net.floodlightcontroller.counter.NullCounterStore -net.floodlightcontroller.debugcounter.DebugCounter -net.floodlightcontroller.debugevent.DebugEvent +net.floodlightcontroller.debugcounter.DebugCounterServiceImpl +net.floodlightcontroller.debugevent.DebugEventService net.floodlightcontroller.threadpool.ThreadPool +net.floodlightcontroller.perfmon.PktInProcessingTime +net.floodlightcontroller.restserver.RestApiServer net.floodlightcontroller.ui.web.StaticWebRoutable net.floodlightcontroller.virtualnetwork.VirtualNetworkFilter -net.floodlightcontroller.firewall.Firewall -net.floodlightcontroller.loadbalancer.LoadBalancer +net.floodlightcontroller.devicemanager.internal.DefaultEntityClassifier +net.floodlightcontroller.core.internal.OFSwitchManager +net.floodlightcontroller.threadpool.ThreadPool +net.floodlightcontroller.core.internal.ShutdownServiceImpl org.sdnplatform.sync.internal.SyncManager org.sdnplatform.sync.internal.SyncTorture -net.floodlightcontroller.devicemanager.internal.DefaultEntityClassifier +net.floodlightcontroller.hub.Hub +net.floodlightcontroller.staticflowentry.StaticFlowEntryPusher +net.floodlightcontroller.testmodule.TestModule +net.floodlightcontroller.topology.TopologyManager +net.floodlightcontroller.forwarding.Forwarding +net.floodlightcontroller.loadbalancer.LoadBalancer +net.floodlightcontroller.linkdiscovery.internal.LinkDiscoveryManager +net.floodlightcontroller.devicemanager.internal.DeviceManagerImpl \ No newline at end of file diff --git a/src/main/resources/floodlightdefault.properties b/src/main/resources/floodlightdefault.properties index 47d2ae4c164061064f6aae682f6a5993321f51fe..3dbacb7bae8f06c048021048013811e26471de85 100644 --- a/src/main/resources/floodlightdefault.properties +++ b/src/main/resources/floodlightdefault.properties @@ -1,24 +1,19 @@ floodlight.modules=\ net.floodlightcontroller.jython.JythonDebugInterface,\ -net.floodlightcontroller.counter.CounterStore,\ net.floodlightcontroller.storage.memory.MemoryStorageSource,\ net.floodlightcontroller.core.internal.FloodlightProvider,\ net.floodlightcontroller.threadpool.ThreadPool,\ -net.floodlightcontroller.devicemanager.internal.DeviceManagerImpl,\ -net.floodlightcontroller.devicemanager.internal.DefaultEntityClassifier,\ +net.floodlightcontroller.debugcounter.DebugCounterServiceImpl,\ +net.floodlightcontroller.restserver.RestApiServer,\ +net.floodlightcontroller.perfmon.PktInProcessingTime,\ +net.floodlightcontroller.debugevent.DebugEventService,\ net.floodlightcontroller.staticflowentry.StaticFlowEntryPusher,\ -net.floodlightcontroller.firewall.Firewall,\ +net.floodlightcontroller.restserver.RestApiServer,\ +net.floodlightcontroller.topology.TopologyManager,\ net.floodlightcontroller.forwarding.Forwarding,\ net.floodlightcontroller.linkdiscovery.internal.LinkDiscoveryManager,\ -net.floodlightcontroller.topology.TopologyManager,\ -net.floodlightcontroller.flowcache.FlowReconcileManager,\ -net.floodlightcontroller.debugcounter.DebugCounter,\ -net.floodlightcontroller.debugevent.DebugEvent,\ -net.floodlightcontroller.perfmon.PktInProcessingTime,\ -net.floodlightcontroller.ui.web.StaticWebRoutable,\ -net.floodlightcontroller.loadbalancer.LoadBalancer,\ -org.sdnplatform.sync.internal.SyncManager,\ -net.floodlightcontroller.devicemanager.internal.DefaultEntityClassifier +net.floodlightcontroller.devicemanager.internal.DeviceManagerImpl org.sdnplatform.sync.internal.SyncManager.authScheme=CHALLENGE_RESPONSE org.sdnplatform.sync.internal.SyncManager.keyStorePath=/etc/floodlight/auth_credentials.jceks org.sdnplatform.sync.internal.SyncManager.dbPath=/var/lib/floodlight/ +net.floodlightcontroller.core.internal.FloodlightProvider.role=ACTIVE diff --git a/src/main/resources/logback-test.xml b/src/main/resources/logback-test.xml index e3f2a4fcf21aaf13d7faa25ccbc97da54459a7c5..7f61e32e22bac17b1f72ccd1562370afe70d2673 100644 --- a/src/main/resources/logback-test.xml +++ b/src/main/resources/logback-test.xml @@ -6,13 +6,16 @@ </appender> <appender name="EV_WARN_ERR" class="net.floodlightcontroller.debugevent.DebugEventAppender"> </appender> - <root level="INFO"> + <root level="DEBUG"> <appender-ref ref="STDOUT" /> <appender-ref ref="EV_WARN_ERR" /> </root> - <logger name="org" level="WARN"/> - <logger name="LogService" level="WARN"/> <!-- Restlet access logging --> - <logger name="net.bigdb" level="INFO"/> - <logger name="net.floodlightcontroller" level="INFO"/> - <logger name="org.sdnplatform" level="INFO"/> + <logger name="org" level="DEBUG"/> + <logger name="LogService" level="DEBUG"/> <!-- Restlet access logging --> + <logger name="net.floodlightcontroller" level="DEBUG"/> + <logger name="org.sdnplatform" level="INFO"></logger> + <logger name="net.floodlightcontroller.devicemanager" level="TRACE"></logger> + <logger name="net.floodlightcontroller.packet" level="TRACE"></logger> + <logger name="net.floodlightcontroller.forwarding" level="TRACE"></logger> + <logger name="net.floodlightcontroller.routing" level="TRACE"></logger> </configuration> diff --git a/src/test/java/com/bigswitch/floodlight/vendor/OFActionNiciraTtlDecrementTest.java b/src/test/java/com/bigswitch/floodlight/vendor/OFActionNiciraTtlDecrementTest.java deleted file mode 100644 index a8214b6dfc47b5e8c34c52184aa09397e952ccd4..0000000000000000000000000000000000000000 --- a/src/test/java/com/bigswitch/floodlight/vendor/OFActionNiciraTtlDecrementTest.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.bigswitch.floodlight.vendor; - -import org.jboss.netty.buffer.ChannelBuffer; -import org.jboss.netty.buffer.ChannelBuffers; -import org.junit.Test; -import org.openflow.protocol.action.OFAction; -import org.openflow.protocol.action.OFActionType; -import org.openflow.protocol.action.OFActionVendor; - -import static org.junit.Assert.*; - -public class OFActionNiciraTtlDecrementTest { - protected static byte[] expectedWireFormat = { - (byte) 0xff, (byte) 0xff, // action vendor - 0x00, 0x10, // length - 0x00, 0x00, 0x23, 0x20, // nicira - 0x00, 0x12, // subtype 18 == 0x12 - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // pad - }; - - @Test - public void testAction() { - ChannelBuffer buf = ChannelBuffers.buffer(32); - - OFActionNiciraTtlDecrement act = new OFActionNiciraTtlDecrement(); - - assertEquals(true, act instanceof OFActionNiciraVendor); - assertEquals(true, act instanceof OFActionVendor); - assertEquals(true, act instanceof OFAction); - - act.writeTo(buf); - - ChannelBuffer buf2 = buf.copy(); - - assertEquals(16, buf.readableBytes()); - byte fromBuffer[] = new byte[16]; - buf.readBytes(fromBuffer); - assertArrayEquals(expectedWireFormat, fromBuffer); - - // Test parsing. TODO: we don't really have the proper parsing - // infrastructure.... - OFActionNiciraVendor act2 = new OFActionNiciraTtlDecrement(); - act2.readFrom(buf2); - assertEquals(act, act2); - assertNotSame(act, act2); - - assertEquals(OFActionType.VENDOR, act2.getType()); - assertEquals(16, act2.getLength()); - assertEquals(OFActionNiciraVendor.NICIRA_VENDOR_ID, act2.getVendor()); - assertEquals((short)18, act2.getSubtype()); - } - -} diff --git a/src/test/java/com/bigswitch/floodlight/vendor/OFActionTunnelDstIPTest.java b/src/test/java/com/bigswitch/floodlight/vendor/OFActionTunnelDstIPTest.java deleted file mode 100644 index 38daa746ef6d310a1ce14f02e4e5c21f5f897b4e..0000000000000000000000000000000000000000 --- a/src/test/java/com/bigswitch/floodlight/vendor/OFActionTunnelDstIPTest.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.bigswitch.floodlight.vendor; - -import net.floodlightcontroller.packet.IPv4; -import net.floodlightcontroller.test.FloodlightTestCase; - -import org.jboss.netty.buffer.ChannelBuffer; -import org.jboss.netty.buffer.ChannelBuffers; -import org.junit.Test; -import org.openflow.protocol.action.OFActionType; - -import static org.junit.Assert.*; - -public class OFActionTunnelDstIPTest extends FloodlightTestCase{ - protected static byte[] expectedWireFormat1 = { - (byte) 0xff, (byte) 0xff, // ActionVendor - 0x00, 0x10, // 16 bytes - 0x00, 0x5c, 0x16, (byte)0xc7, // VendorId BSN - 0x00, 0x00, 0x00, 0x02, // subtype 2 (32 bit) - 0x11, 0x21, 0x31, 0x41 // IP 17.33.49.65 - }; - - @Test - public void testAction() { - OFActionTunnelDstIP tunnAct1 = new OFActionTunnelDstIP(); - assertEquals(0, tunnAct1.dstIPAddr); - - OFActionTunnelDstIP tunnAct2 = new OFActionTunnelDstIP(1); - - assertEquals(false, tunnAct1.equals(tunnAct2)); - tunnAct1.setTunnelDstIP(1); - assertEquals(tunnAct1, tunnAct2); - - testAll(tunnAct1); - testAll(tunnAct2); - } - - private void testAll(OFActionTunnelDstIP tip) { - assertEquals(OFActionType.VENDOR, tip.getType()); - assertEquals(2, tip.getSubtype()); - assertEquals(16, tip.getLength()); - assertEquals(0x005c16c7, tip.getVendor()); - - tip.setTunnelDstIP(24); - assertEquals(24, tip.getTunnelDstIP()); - - // Test wire format - int ip = IPv4.toIPv4Address("17.33.49.65"); - tip.setTunnelDstIP(ip); - ChannelBuffer buf = ChannelBuffers.buffer(32); - tip.writeTo(buf); - ChannelBuffer buf2 = buf.copy(); - assertEquals(16, buf.readableBytes()); - byte fromBuffer[] = new byte[16]; - buf.readBytes(fromBuffer); - assertArrayEquals(expectedWireFormat1, fromBuffer); - - OFActionTunnelDstIP act2 = new OFActionTunnelDstIP(); - act2.readFrom(buf2); - assertEquals(tip, act2); - - - } - - -} diff --git a/src/test/java/com/bigswitch/floodlight/vendor/OFBsnPktinSupressionSetRequestVendorDataTest.java b/src/test/java/com/bigswitch/floodlight/vendor/OFBsnPktinSupressionSetRequestVendorDataTest.java deleted file mode 100644 index 2be162e1ca457c6f03e41ef03c9bbd84cb708f16..0000000000000000000000000000000000000000 --- a/src/test/java/com/bigswitch/floodlight/vendor/OFBsnPktinSupressionSetRequestVendorDataTest.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.bigswitch.floodlight.vendor; - -import org.jboss.netty.buffer.ChannelBuffer; -import org.jboss.netty.buffer.ChannelBuffers; -import org.junit.Test; - -import static org.junit.Assert.*; - -public class OFBsnPktinSupressionSetRequestVendorDataTest { - protected static byte[] expectedWireFormat = { - 0x00, 0x00, 0x00, 0x0b, // type == 11 - 0x01, // enabled - 0x00, // pad - 0x00, 0x5a, // idle timeout - (byte) 0xf0, (byte) 0xe0, // hard timeout - 0x12, 0x34, // priority - 0x33, 0x33, 0x66, 0x66, - 0x77, 0x77, (byte) 0x99, (byte) 0x99 // cookie - }; - - @Test - public void test() { - ChannelBuffer buf = ChannelBuffers.buffer(32); - - OFBsnPktinSuppressionSetRequestVendorData vendorData = - new OFBsnPktinSuppressionSetRequestVendorData( - true, - (short)0x5a, - (short)0xf0e0, - (short)0x1234, - 0x3333666677779999L); - assertEquals(11, vendorData.getDataType()); - - assertEquals(true, vendorData instanceof OFBigSwitchVendorData); - - vendorData.writeTo(buf); - - ChannelBuffer buf2 = buf.copy(); - assertEquals(20, buf.readableBytes()); - byte fromBuffer[] = new byte[20]; - buf.readBytes(fromBuffer); - assertArrayEquals(expectedWireFormat, fromBuffer); - - OFBsnPktinSuppressionSetRequestVendorData vendorData2 = - new OFBsnPktinSuppressionSetRequestVendorData(); - - assertEquals(11, vendorData2.getDataType()); - - vendorData2.setIdleTimeout((short)1); - assertEquals((short)1, vendorData2.getIdleTimeout()); - - vendorData2.setHardTimeout((short)2); - assertEquals((short)2, vendorData2.getHardTimeout()); - - vendorData2.setPriority((short)3); - assertEquals((short)3, vendorData2.getPriority()); - - vendorData2.setCookie(12345678901234L); - assertEquals(12345678901234L, vendorData2.getCookie()); - - vendorData2.readFrom(buf2, buf2.readableBytes()); - assertEquals(vendorData, vendorData2); - } - - -} diff --git a/src/test/java/com/bigswitch/floodlight/vendor/OFVendorActionFactoriesTest.java b/src/test/java/com/bigswitch/floodlight/vendor/OFVendorActionFactoriesTest.java deleted file mode 100644 index 4ba390bf5cd35c97312a1cfd77b0d0e4b5fd5e22..0000000000000000000000000000000000000000 --- a/src/test/java/com/bigswitch/floodlight/vendor/OFVendorActionFactoriesTest.java +++ /dev/null @@ -1,72 +0,0 @@ -package com.bigswitch.floodlight.vendor; - -import static org.junit.Assert.assertEquals; - -import java.beans.BeanInfo; -import java.beans.IntrospectionException; -import java.beans.Introspector; -import java.beans.PropertyDescriptor; -import java.lang.reflect.InvocationTargetException; - -import org.jboss.netty.buffer.ChannelBuffer; -import org.jboss.netty.buffer.ChannelBuffers; -import org.junit.Test; -import org.openflow.protocol.action.OFActionVendor; -import org.openflow.protocol.factory.OFVendorActionFactory; - -public class OFVendorActionFactoriesTest { - - @Test - public void testNiciraVendorActionFactory() { - OFNiciraVendorActionFactory factory = - new OFNiciraVendorActionFactory(); - - OFActionNiciraTtlDecrement ttl = new OFActionNiciraTtlDecrement(); - rereadAndCheck(factory, ttl); - } - - @Test - public void testBSNVendorActionFactory() { - OFBigSwitchVendorActionFactory factory = - new OFBigSwitchVendorActionFactory(); - - OFActionMirror mirror = new OFActionMirror((short) 12); - mirror.setCopyStage((byte) 96); - mirror.setDestPort(123); - mirror.setVlanTag(42); - rereadAndCheck(factory, mirror); - - OFActionTunnelDstIP dstIP = new OFActionTunnelDstIP((short) 12); - dstIP.setTunnelDstIP(0x01020304); - rereadAndCheck(factory, dstIP); - - } - - - protected void rereadAndCheck(OFVendorActionFactory factory, OFActionVendor action) { - ChannelBuffer buf= ChannelBuffers.buffer(action.getLengthU()); - action.writeTo(buf); - OFActionVendor readAction = factory.readFrom(buf); - assertBeansEqual(action, readAction); - } - - public void assertBeansEqual(Object expected, Object actual) { - BeanInfo beanInfo; - try { - beanInfo = Introspector.getBeanInfo(expected.getClass()); - for (PropertyDescriptor propertyDesc : beanInfo.getPropertyDescriptors()) { - Object srcValue = propertyDesc.getReadMethod().invoke(expected); - Object dstValue = propertyDesc.getReadMethod().invoke(actual); - assertEquals("Bean Value: "+propertyDesc.getName() + " expected: "+srcValue + " != actual: "+dstValue, srcValue, dstValue); - } - } catch (IntrospectionException e) { - throw new RuntimeException(e); - } catch (IllegalArgumentException e) { - throw new RuntimeException(e); - } catch (IllegalAccessException e) { - throw new RuntimeException(e); - } catch (InvocationTargetException e) { - throw new RuntimeException(e); - } - } -} diff --git a/src/test/java/net/floodlightcontroller/core/OFConnectionTest.java b/src/test/java/net/floodlightcontroller/core/OFConnectionTest.java new file mode 100644 index 0000000000000000000000000000000000000000..af62aa9cdf228c1f121ca979ecafc939940aacfb --- /dev/null +++ b/src/test/java/net/floodlightcontroller/core/OFConnectionTest.java @@ -0,0 +1,271 @@ +package net.floodlightcontroller.core; + +import static org.easymock.EasyMock.capture; +import static org.easymock.EasyMock.expect; +import static org.easymock.EasyMock.replay; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.junit.Assert.assertThat; + +import java.util.List; +import java.util.concurrent.ExecutionException; + +import org.easymock.Capture; +import org.easymock.EasyMock; +import org.hamcrest.CoreMatchers; +import org.hamcrest.Matchers; +import org.jboss.netty.channel.Channel; +import org.jboss.netty.util.HashedWheelTimer; +import org.jboss.netty.util.Timer; +import org.junit.Before; +import org.junit.Test; +import net.floodlightcontroller.debugcounter.DebugCounterServiceImpl; +import net.floodlightcontroller.debugcounter.IDebugCounterService; +import org.projectfloodlight.openflow.protocol.OFControllerRole; +import org.projectfloodlight.openflow.protocol.OFEchoReply; +import org.projectfloodlight.openflow.protocol.OFEchoRequest; +import org.projectfloodlight.openflow.protocol.OFErrorMsg; +import org.projectfloodlight.openflow.protocol.OFFactories; +import org.projectfloodlight.openflow.protocol.OFFactory; +import org.projectfloodlight.openflow.protocol.OFFlowStatsReply; +import org.projectfloodlight.openflow.protocol.OFFlowStatsRequest; +import org.projectfloodlight.openflow.protocol.OFHello; +import org.projectfloodlight.openflow.protocol.OFHelloElem; +import org.projectfloodlight.openflow.protocol.OFMessage; +import org.projectfloodlight.openflow.protocol.OFPacketOut; +import org.projectfloodlight.openflow.protocol.OFRoleReply; +import org.projectfloodlight.openflow.protocol.OFRoleRequest; +import org.projectfloodlight.openflow.protocol.OFRoleRequestFailedCode; +import org.projectfloodlight.openflow.protocol.OFStatsReplyFlags; +import org.projectfloodlight.openflow.protocol.OFVersion; +import org.projectfloodlight.openflow.protocol.action.OFAction; +import org.projectfloodlight.openflow.protocol.errormsg.OFRoleRequestFailedErrorMsg; +import org.projectfloodlight.openflow.types.DatapathId; +import org.projectfloodlight.openflow.types.OFAuxId; +import org.projectfloodlight.openflow.types.OFPort; +import net.floodlightcontroller.util.FutureTestUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Sets; +import com.google.common.util.concurrent.ListenableFuture; + +public class OFConnectionTest { + private static final Logger logger = LoggerFactory.getLogger(OFConnectionTest.class); + + private OFFactory factory; + private Channel channel; + private OFConnection conn; + private DatapathId switchId; + private Timer timer; + + @Before + public void setUp() throws Exception { + factory = OFFactories.getFactory(OFVersion.OF_13); + switchId = DatapathId.of(1); + timer = new HashedWheelTimer(); + channel = EasyMock.createMock(Channel.class); + IDebugCounterService debugCounterService = new DebugCounterServiceImpl(); + debugCounterService.registerModule(OFConnectionCounters.COUNTER_MODULE); + conn = new OFConnection(switchId, factory, channel, OFAuxId.MAIN, + debugCounterService, timer); + } + + @Test(timeout = 5000) + public void testWriteRequestSuccess() throws InterruptedException, ExecutionException { + Capture<List<OFMessage>> cMsgList = prepareChannelForWriteList(); + + OFEchoRequest echoRequest = factory.echoRequest(new byte[] {}); + ListenableFuture<OFEchoReply> future = conn.writeRequest(echoRequest); + assertThat("Connection should have 1 pending request", + conn.getPendingRequestIds().size(), equalTo(1)); + + assertThat("Should have captured MsgList", cMsgList.getValue(), + Matchers.<OFMessage> contains(echoRequest)); + + assertThat("Future should not be complete yet", future.isDone(), equalTo(false)); + + OFEchoReply echoReply = factory.buildEchoReply() + .setXid(echoRequest.getXid()) + .build(); + + assertThat("Connection should have accepted the response", + conn.deliverResponse(echoReply), + equalTo(true)); + assertThat("Future should be complete ", future.isDone(), equalTo(true)); + assertThat(future.get(), equalTo(echoReply)); + assertThat("Connection should have no pending requests", + conn.getPendingRequestIds().isEmpty(), equalTo(true)); + } + + /** write a stats request message that triggers two responses */ + @Test(timeout = 5000) + public void testWriteStatsRequestSuccess() throws InterruptedException, ExecutionException { + Capture<List<OFMessage>> cMsgList = prepareChannelForWriteList(); + + OFFlowStatsRequest flowStatsRequest = factory.buildFlowStatsRequest().build(); + ListenableFuture<List<OFFlowStatsReply>> future = conn.writeStatsRequest(flowStatsRequest); + assertThat("Connection should have 1 pending request", + conn.getPendingRequestIds().size(), equalTo(1)); + + assertThat("Should have captured MsgList", cMsgList.getValue(), + Matchers.<OFMessage> contains(flowStatsRequest)); + + assertThat("Future should not be complete yet", future.isDone(), equalTo(false)); + + OFFlowStatsReply statsReply1 = factory.buildFlowStatsReply() + .setXid(flowStatsRequest.getXid()) + .setFlags(Sets.immutableEnumSet(OFStatsReplyFlags.REPLY_MORE)) + .build(); + + assertThat("Connection should have accepted the response", + conn.deliverResponse(statsReply1), + equalTo(true)); + assertThat("Future should not be complete ", future.isDone(), equalTo(false)); + + OFFlowStatsReply statsReply2 = factory.buildFlowStatsReply() + .setXid(flowStatsRequest.getXid()) + .build(); + + assertThat("Connection should have accepted the response", + conn.deliverResponse(statsReply2), + equalTo(true)); + assertThat("Future should be complete ", future.isDone(), equalTo(true)); + + assertThat(future.get(), Matchers.contains(statsReply1, statsReply2)); + assertThat("Connection should have no pending requests", + conn.getPendingRequestIds().isEmpty(), equalTo(true)); + } + + private Capture<List<OFMessage>> prepareChannelForWriteList() { + EasyMock.expect(channel.isConnected()).andReturn(Boolean.TRUE).anyTimes(); + Capture<List<OFMessage>> cMsgList = new Capture<>(); + expect(channel.write(capture(cMsgList))).andReturn(null).once(); + replay(channel); + return cMsgList; + } + + /** write a request which triggers an OFErrorMsg response */ + @Test(timeout = 5000) + public void testWriteRequestOFErrorMsg() throws InterruptedException, ExecutionException { + Capture<List<OFMessage>> cMsgList = prepareChannelForWriteList(); + + OFRoleRequest roleRequest = factory.buildRoleRequest().setRole(OFControllerRole.ROLE_MASTER).build(); + ListenableFuture<OFRoleReply> future = conn.writeRequest(roleRequest); + assertThat("Connection should have 1 pending request", + conn.getPendingRequestIds().size(), equalTo(1)); + + assertThat("Should have captured MsgList", cMsgList.getValue(), + Matchers.<OFMessage> contains(roleRequest)); + + assertThat("Future should not be complete yet", future.isDone(), equalTo(false)); + OFRoleRequestFailedErrorMsg roleError = factory.errorMsgs().buildRoleRequestFailedErrorMsg() + .setXid(roleRequest.getXid()) + .setCode(OFRoleRequestFailedCode.STALE) + .build(); + + assertThat("Connection should have accepted the response", + conn.deliverResponse(roleError), + equalTo(true)); + + OFErrorMsgException e = + FutureTestUtils.assertFutureFailedWithException(future, + OFErrorMsgException.class); + assertThat(e.getErrorMessage(), CoreMatchers.<OFErrorMsg>equalTo(roleError)); + } + + @Test(timeout = 5000) + public void testWriteRequestNotConnectedFailure() throws InterruptedException, + ExecutionException { + EasyMock.expect(channel.isConnected()).andReturn(Boolean.FALSE).anyTimes(); + replay(channel); + + OFEchoRequest echoRequest = factory.echoRequest(new byte[] {}); + ListenableFuture<OFEchoReply> future = conn.writeRequest(echoRequest); + + SwitchDisconnectedException e = + FutureTestUtils.assertFutureFailedWithException(future, + SwitchDisconnectedException.class); + assertThat(e.getId(), equalTo(switchId)); + assertThat("Connection should have no pending requests", + conn.getPendingRequestIds().isEmpty(), equalTo(true)); + } + + @Test(timeout = 5000) + public void testWriteRequestDisconnectFailure() throws InterruptedException, ExecutionException { + prepareChannelForWriteList(); + + OFEchoRequest echoRequest = factory.echoRequest(new byte[] {}); + ListenableFuture<OFEchoReply> future = conn.writeRequest(echoRequest); + + assertThat("Connection should have 1 pending request", conn.getPendingRequestIds().size(), + equalTo(1)); + assertThat("Future should not be complete yet", future.isDone(), equalTo(false)); + + conn.disconnected(); + + SwitchDisconnectedException e = + FutureTestUtils.assertFutureFailedWithException(future, + SwitchDisconnectedException.class); + assertThat(e.getId(), equalTo(switchId)); + assertThat("Connection should have no pending requests", + conn.getPendingRequestIds().isEmpty(), equalTo(true)); + } + + /** write a packetOut, which is buffered */ + @Test(timeout = 5000) + public void testMessageWriteBuffered() throws InterruptedException, ExecutionException { + Capture<List<OFMessage>> cMsgList = prepareChannelForWriteList(); + + OFPacketOut packetOut = factory.buildPacketOut() + .setData(new byte[] { 0x01, 0x02, 0x03, 0x04 }) + .setActions(ImmutableList.<OFAction>of( factory.actions().output(OFPort.of(1), 0))) + .build(); + conn.write(packetOut); + assertThat("Write should have been buffered", cMsgList.hasCaptured(), equalTo(false)); + + conn.flush(); + + assertThat("Write should have been flushed", cMsgList.hasCaptured(), equalTo(true)); + List<OFMessage> value = cMsgList.getValue(); + logger.info("Captured channel write: "+value); + assertThat("Should have captured MsgList", cMsgList.getValue(), + Matchers.<OFMessage> contains(packetOut)); + } + + /** write a hello, which is not buffered */ + @Test(timeout = 5000) + public void testMessageWriteNonBuffered() throws InterruptedException, ExecutionException { + Capture<List<OFMessage>> cMsgList = prepareChannelForWriteList(); + + OFHello hello = factory.hello(ImmutableList.<OFHelloElem>of()); + conn.write(hello); + + assertThat("Write should have been written immediately", cMsgList.hasCaptured(), equalTo(true)); + List<OFMessage> value = cMsgList.getValue(); + logger.info("Captured channel write: "+value); + assertThat("Should have captured MsgList", cMsgList.getValue(), + Matchers.<OFMessage> contains(hello)); + } + + /** write a list of messages */ + @Test(timeout = 5000) + public void testMessageWriteList() throws InterruptedException, ExecutionException { + Capture<List<OFMessage>> cMsgList = prepareChannelForWriteList(); + + OFHello hello = factory.hello(ImmutableList.<OFHelloElem>of()); + OFPacketOut packetOut = factory.buildPacketOut() + .setData(new byte[] { 0x01, 0x02, 0x03, 0x04 }) + .setActions(ImmutableList.<OFAction>of( factory.actions().output(OFPort.of(1), 0))) + .build(); + + conn.write(ImmutableList.of(hello, packetOut)); + + assertThat("Write should have been written", cMsgList.hasCaptured(), equalTo(true)); + List<OFMessage> value = cMsgList.getValue(); + logger.info("Captured channel write: "+value); + assertThat("Should have captured MsgList", cMsgList.getValue(), + Matchers.<OFMessage> contains(hello, packetOut)); + } + +} diff --git a/src/test/java/net/floodlightcontroller/core/internal/OFSwitchBaseTest.java b/src/test/java/net/floodlightcontroller/core/OFSwitchBaseTest.java similarity index 65% rename from src/test/java/net/floodlightcontroller/core/internal/OFSwitchBaseTest.java rename to src/test/java/net/floodlightcontroller/core/OFSwitchBaseTest.java index 2899d44400ad49663c009ede80e5141cebff077e..81e065c2bdf2a25763138dd15e0ed697017a7e3a 100644 --- a/src/test/java/net/floodlightcontroller/core/internal/OFSwitchBaseTest.java +++ b/src/test/java/net/floodlightcontroller/core/OFSwitchBaseTest.java @@ -14,97 +14,71 @@ * under the License. **/ -package net.floodlightcontroller.core.internal; - -import static org.easymock.EasyMock.*; -import static org.junit.Assert.*; +package net.floodlightcontroller.core; + +import static org.easymock.EasyMock.createMock; +import static org.easymock.EasyMock.expect; +import static org.easymock.EasyMock.expectLastCall; +import static org.easymock.EasyMock.replay; +import static org.easymock.EasyMock.reset; +import static org.easymock.EasyMock.verify; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -import net.floodlightcontroller.core.FloodlightContext; -import net.floodlightcontroller.core.IFloodlightProviderService; -import net.floodlightcontroller.core.IOFSwitch; -import net.floodlightcontroller.core.SwitchDriverSubHandshakeAlreadyStarted; -import net.floodlightcontroller.core.SwitchDriverSubHandshakeCompleted; -import net.floodlightcontroller.core.SwitchDriverSubHandshakeNotStarted; -import net.floodlightcontroller.core.IOFSwitch.PortChangeEvent; -import net.floodlightcontroller.core.IOFSwitch.PortChangeType; -import net.floodlightcontroller.core.ImmutablePort; -import net.floodlightcontroller.core.OFSwitchBase; -import net.floodlightcontroller.debugcounter.DebugCounter; -import net.floodlightcontroller.debugcounter.IDebugCounterService; -import net.floodlightcontroller.packet.ARP; -import net.floodlightcontroller.packet.Ethernet; -import net.floodlightcontroller.packet.IPacket; -import net.floodlightcontroller.packet.IPv4; - +import org.easymock.Capture; +import org.easymock.EasyMock; import org.junit.Before; import org.junit.Test; -import org.openflow.protocol.OFFlowMod; -import org.openflow.protocol.OFMatch; -import org.openflow.protocol.OFMessage; -import org.openflow.protocol.OFPacketIn; -import org.openflow.protocol.OFPortStatus; -import org.openflow.protocol.OFType; -import org.openflow.protocol.OFPacketIn.OFPacketInReason; -import org.openflow.protocol.OFPhysicalPort.OFPortConfig; -import org.openflow.protocol.OFPhysicalPort.OFPortFeatures; -import org.openflow.protocol.OFPortStatus.OFPortReason; -import org.openflow.protocol.factory.BasicFactory; -import org.openflow.protocol.statistics.OFDescriptionStatistics; -import org.openflow.util.HexString; -public class OFSwitchBaseTest { - private static final String srcMac = "00:44:33:22:11:00"; - IFloodlightProviderService floodlightProvider; - Map<Long, IOFSwitch> switches; - private OFMessage blockMessage; - private OFPacketIn pi; - private IPacket testPacket; - private byte[] testPacketSerialized; - - private class OFSwitchTest extends OFSwitchBase { - public OFSwitchTest(IFloodlightProviderService fp) { - super(); - stringId = "whatever"; - datapathId = 1L; - floodlightProvider = fp; - } +import net.floodlightcontroller.core.internal.IOFSwitchManager; +import net.floodlightcontroller.core.internal.SwitchManagerCounters; +import net.floodlightcontroller.debugcounter.DebugCounterServiceImpl; +import net.floodlightcontroller.debugcounter.IDebugCounterService; - @Override - public void setSwitchProperties(OFDescriptionStatistics description) { - // TODO Auto-generated method stub - } +import org.projectfloodlight.openflow.protocol.OFControllerRole; +import org.projectfloodlight.openflow.protocol.OFFactories; +import org.projectfloodlight.openflow.protocol.OFFactory; +import org.projectfloodlight.openflow.protocol.OFMessage; +import org.projectfloodlight.openflow.protocol.OFPortConfig; +import org.projectfloodlight.openflow.protocol.OFPortDesc; +import org.projectfloodlight.openflow.protocol.OFPortFeatures; +import org.projectfloodlight.openflow.protocol.OFPortReason; +import org.projectfloodlight.openflow.protocol.OFPortState; +import org.projectfloodlight.openflow.protocol.OFPortStatus; +import org.projectfloodlight.openflow.protocol.OFVersion; +import org.projectfloodlight.openflow.types.DatapathId; +import org.projectfloodlight.openflow.types.OFAuxId; +import org.projectfloodlight.openflow.types.OFPort; - @Override - public OFPortType getPortType(short port_num) { - // TODO Auto-generated method stub - return null; +public class OFSwitchBaseTest { + IOFSwitchManager switchManager; + Map<DatapathId, IOFSwitchBackend> switches; + //OFFactory factory = OFFactories.getFactory(OFVersion.OF_10); + private OFMessage testMessage; + + private static class OFSwitchTest extends OFSwitch { + public OFSwitchTest(IOFConnectionBackend connection, IOFSwitchManager switchManager) { + super(connection, OFFactories.getFactory(OFVersion.OF_13), switchManager, DatapathId.of(1)); } @Override - public boolean isFastPort(short port_num) { - // TODO Auto-generated method stub - return false; + public void setSwitchProperties(SwitchDescription description) { } @Override - public void write(OFMessage msg, FloodlightContext cntx) { - blockMessage = msg; - } - - public void setThresholds(int high, int low, int host, int port) { - sw.setInputThrottleThresholds(high, low, host, port); - } - - public boolean inputThrottleEnabled() { - return packetInThrottleEnabled; + public OFFactory getOFFactory() { + return OFFactories.getFactory(OFVersion.OF_13); } @Override @@ -113,15 +87,15 @@ public class OFSwitchBaseTest { } } private OFSwitchTest sw; - private ImmutablePort p1a; - private ImmutablePort p1b; - private ImmutablePort p2a; - private ImmutablePort p2b; - private ImmutablePort p3; - private final ImmutablePort portFoo1 = ImmutablePort.create("foo", (short)11); - private final ImmutablePort portFoo2 = ImmutablePort.create("foo", (short)12); - private final ImmutablePort portBar1 = ImmutablePort.create("bar", (short)11); - private final ImmutablePort portBar2 = ImmutablePort.create("bar", (short)12); + private OFPortDesc p1a; + private OFPortDesc p1b; + private OFPortDesc p2a; + private OFPortDesc p2b; + private OFPortDesc p3; + private final OFPortDesc portFoo1 = OFFactories.getFactory(OFVersion.OF_13).buildPortDesc().setPortNo(OFPort.of(11)).setName("foo").build(); + private final OFPortDesc portFoo2 = OFFactories.getFactory(OFVersion.OF_13).buildPortDesc().setPortNo(OFPort.of(12)).setName("foo").build(); + private final OFPortDesc portBar1 = OFFactories.getFactory(OFVersion.OF_13).buildPortDesc().setPortNo(OFPort.of(11)).setName("bar").build(); + private final OFPortDesc portBar2 = OFFactories.getFactory(OFVersion.OF_13).buildPortDesc().setPortNo(OFPort.of(12)).setName("bar").build(); private final PortChangeEvent portFoo1Add = new PortChangeEvent(portFoo1, PortChangeType.ADD); private final PortChangeEvent portFoo2Add = @@ -138,247 +112,105 @@ public class OFSwitchBaseTest { new PortChangeEvent(portBar1, PortChangeType.DELETE); private final PortChangeEvent portBar2Del = new PortChangeEvent(portBar2, PortChangeType.DELETE); + private Capture<OFMessage> capturedMessage; + private OFFactory factory; @Before public void setUp() throws Exception { - blockMessage = null; + // Build our test packet - testPacket = new Ethernet() - .setSourceMACAddress(srcMac) - .setDestinationMACAddress("00:11:22:33:44:55") - .setEtherType(Ethernet.TYPE_ARP) - .setPayload( - new ARP() - .setHardwareType(ARP.HW_TYPE_ETHERNET) - .setProtocolType(ARP.PROTO_TYPE_IP) - .setHardwareAddressLength((byte) 6) - .setProtocolAddressLength((byte) 4) - .setOpCode(ARP.OP_REPLY) - .setSenderHardwareAddress(Ethernet.toMACAddress("00:44:33:22:11:00")) - .setSenderProtocolAddress(IPv4.toIPv4AddressBytes("192.168.1.1")) - .setTargetHardwareAddress(Ethernet.toMACAddress("00:11:22:33:44:55")) - .setTargetProtocolAddress(IPv4.toIPv4AddressBytes("192.168.1.2"))); - testPacketSerialized = testPacket.serialize(); - - pi = ((OFPacketIn) BasicFactory.getInstance().getMessage(OFType.PACKET_IN)) - .setBufferId(-1) - .setInPort((short) 1) - .setPacketData(testPacketSerialized) - .setReason(OFPacketInReason.NO_MATCH) - .setTotalLength((short) testPacketSerialized.length); - floodlightProvider = createMock(IFloodlightProviderService.class); - sw = new OFSwitchTest(floodlightProvider); - IDebugCounterService debugCounter = new DebugCounter(); - sw.setDebugCounterService(debugCounter); - switches = new ConcurrentHashMap<Long, IOFSwitch>(); + IDebugCounterService debugCounter = new DebugCounterServiceImpl(); + switchManager = createMock(IOFSwitchManager.class); + SwitchManagerCounters counters = new SwitchManagerCounters(debugCounter); + + expect(switchManager.getCounters()).andReturn(counters).anyTimes(); + replay(switchManager); + + factory = OFFactories.getFactory(OFVersion.OF_13); + + testMessage = factory.buildRoleReply() + .setXid(1) + .setRole(OFControllerRole.ROLE_MASTER) + .build(); + + IOFConnectionBackend conn = EasyMock.createNiceMock(IOFConnectionBackend.class); + capturedMessage = new Capture<OFMessage>(); + conn.write(EasyMock.capture(capturedMessage)); + expectLastCall().anyTimes(); + expect(conn.getOFFactory()).andReturn(factory).anyTimes(); + expect(conn.getAuxId()).andReturn(OFAuxId.MAIN).anyTimes(); + EasyMock.replay(conn); + + IOFConnectionBackend auxConn = EasyMock.createNiceMock(IOFConnectionBackend.class); + expect(auxConn.getOFFactory()).andReturn(factory).anyTimes(); + expect(auxConn.getAuxId()).andReturn(OFAuxId.of(1)).anyTimes(); + EasyMock.replay(auxConn); + + sw = new OFSwitchTest(conn, switchManager); + sw.registerConnection(auxConn); + + switches = new ConcurrentHashMap<DatapathId, IOFSwitchBackend>(); switches.put(sw.getId(), sw); - expect(floodlightProvider.getSwitch(sw.getId())).andReturn(sw).anyTimes(); - expect(floodlightProvider.getOFMessageFactory()) - .andReturn(BasicFactory.getInstance()).anyTimes(); + reset(switchManager); + //expect(switchManager.getSwitch(sw.getId())).andReturn(sw).anyTimes(); } @Before public void setUpPorts() { - ImmutablePort.Builder bld = new ImmutablePort.Builder(); + OFPortDesc.Builder pdb = OFFactories.getFactory(OFVersion.OF_13).buildPortDesc(); // p1a is disabled - p1a = bld.setName("port1") - .setPortNumber((short)1) - .setPortStateLinkDown(true) - .build(); - assertFalse("Sanity check portEnabled", p1a.isEnabled()); + pdb.setName("port1"); + pdb.setPortNo(OFPort.of(1)); + Set<OFPortState> portState = new HashSet<OFPortState>(); + portState.add(OFPortState.LINK_DOWN); + pdb.setState(portState); + p1a = pdb.build(); + assertFalse("Sanity check portEnabled", !p1a.getState().contains(OFPortState.LINK_DOWN)); // p1b is enabled // p1b has different feature from p1a - p1b = bld.addCurrentFeature(OFPortFeatures.OFPPF_1GB_FD) - .setPortStateLinkDown(false) - .build(); - assertTrue("Sanity check portEnabled", p1b.isEnabled()); + pdb = OFFactories.getFactory(OFVersion.OF_13).buildPortDesc(); + pdb.setName("port1"); + pdb.setPortNo(OFPort.of(1)); + Set<OFPortFeatures> portFeatures = new HashSet<OFPortFeatures>(); + portFeatures.add(OFPortFeatures.PF_1GB_FD); + pdb.setCurr(portFeatures); + p1b = pdb.build(); + assertTrue("Sanity check portEnabled", !p1b.getState().contains(OFPortState.LIVE)); // p2 is disabled // p2 has mixed case - bld = new ImmutablePort.Builder(); - p2a = bld.setName("Port2") - .setPortNumber((short)2) - .setPortStateLinkDown(false) - .addConfig(OFPortConfig.OFPPC_PORT_DOWN) - .build(); + pdb = OFFactories.getFactory(OFVersion.OF_13).buildPortDesc(); + portState = new HashSet<OFPortState>(); + Set<OFPortConfig> portConfig = new HashSet<OFPortConfig>(); + portFeatures = new HashSet<OFPortFeatures>(); + pdb.setName("Port2"); + pdb.setPortNo(OFPort.of(2)); + portConfig.add(OFPortConfig.PORT_DOWN); + pdb.setConfig(portConfig); + p2a = pdb.build(); // p2b only differs in PortFeatures - p2b = bld.addCurrentFeature(OFPortFeatures.OFPPF_100MB_HD) - .build(); - assertFalse("Sanity check portEnabled", p2a.isEnabled()); + pdb = OFFactories.getFactory(OFVersion.OF_13).buildPortDesc(); + portState = new HashSet<OFPortState>(); + portConfig = new HashSet<OFPortConfig>(); + portFeatures = new HashSet<OFPortFeatures>(); + pdb.setName("Port2"); + pdb.setPortNo(OFPort.of(2)); + portConfig.add(OFPortConfig.PORT_DOWN); + pdb.setConfig(portConfig); + portFeatures.add(OFPortFeatures.PF_100MB_HD); + pdb.setCurr(portFeatures); + p2b = pdb.build(); + //TODO @Ryan does setting the config as PORT_DOWN undo a prior setting of state = LIVE? Answer: NO + assertFalse("Sanity check portEnabled", p2a.getState().contains(OFPortState.LIVE)); // p3 is enabled // p3 has mixed case - bld = new ImmutablePort.Builder(); - p3 = bld.setName("porT3") - .setPortNumber((short)3) - .setPortStateLinkDown(false) - .build(); - assertTrue("Sanity check portEnabled", p3.isEnabled()); - - } + pdb = OFFactories.getFactory(OFVersion.OF_13).buildPortDesc(); + pdb.setName("porT3"); + pdb.setPortNo(OFPort.of(3)); + p3 = pdb.build(); + assertTrue("Sanity check portEnabled", !p3.getState().contains(OFPortState.LINK_DOWN)); - /** - * By default, high threshold is infinite - */ - @Test - public void testNoPacketInThrottle() { - replay(floodlightProvider); - /* disable input throttle */ - sw.setThresholds(Integer.MAX_VALUE, 1, 0, 0); - for (int i = 0; i < 200; i++) { - assertFalse(sw.inputThrottled(pi)); - } - assertTrue(blockMessage == null); - assertFalse(sw.inputThrottleEnabled()); - } - - /** - * The test sends packet in at infinite rate (< 1ms), - * so throttling should be enabled on 100th packet, when the first - * rate measurement is done. - */ - @Test - public void testPacketInStartThrottle() { - floodlightProvider.addSwitchEvent(anyLong(), - (String)anyObject(), anyBoolean()); - replay(floodlightProvider); - - int high = 500; - sw.setThresholds(high, 10, 50, 200); - // We measure time lapse every 1000 packets - for (int i = 0; i < 1000; i++) { - assertFalse(sw.inputThrottleEnabled()); - assertFalse(sw.inputThrottled(pi)); - } - assertTrue(sw.inputThrottleEnabled()); - assertTrue(sw.inputThrottled(pi)); - assertTrue(sw.inputThrottled(pi)); - assertTrue(blockMessage == null); - } - - /** - * With throttling enabled, raise the low water mark threshold, - * verify throttling stops. - * @throws InterruptedException - */ - @Test - public void testPacketInStopThrottle() throws InterruptedException { - floodlightProvider.addSwitchEvent(anyLong(), - (String)anyObject(), anyBoolean()); - expectLastCall().times(2); - replay(floodlightProvider); - - sw.setThresholds(100, 10, 50, 200); - // First, enable throttling - for (int i = 0; i < 1000; i++) { - assertFalse(sw.inputThrottleEnabled()); - assertFalse(sw.inputThrottled(pi)); - } - assertTrue(sw.inputThrottleEnabled()); - - sw.setThresholds(Integer.MAX_VALUE, 100000, 50, 200); - for (int i = 0; i < 999; i++) { - assertTrue(sw.inputThrottled(pi)); - assertTrue(sw.inputThrottleEnabled()); - } - // Sleep for 2 msec, next packet should disable throttling - Thread.sleep(2); - assertFalse(sw.inputThrottled(pi)); - assertFalse(sw.inputThrottleEnabled()); - } - - /** - * With throttling enabled, if rate of unique flows from a host - * exceeds set threshold, a flow mod should be emitted to block host - */ - @Test - public void testPacketInBlockHost() { - floodlightProvider.addSwitchEvent(anyLong(), - (String)anyObject(), anyBoolean()); - expectLastCall().times(2); - replay(floodlightProvider); - - int high = 500; - int perMac = 50; - sw.setThresholds(high, 10, perMac, 200); - // First, enable throttling - for (int i = 0; i < 1000; i++) { - assertFalse(sw.inputThrottleEnabled()); - assertFalse(sw.inputThrottled(pi)); - } - assertTrue(sw.inputThrottleEnabled()); - assertTrue(blockMessage == null); - - // Build unique flows with the same source mac - for (int j = 0; j < perMac - 1; j++) { - testPacketSerialized[5]++; - pi.setPacketData(testPacketSerialized); - assertFalse(sw.inputThrottled(pi)); - } - assertTrue(blockMessage == null); - testPacketSerialized[5]++; - pi.setPacketData(testPacketSerialized); - assertFalse(sw.inputThrottled(pi)); - - // Verify the message is a flowmod with a hard timeout and srcMac - assertTrue(blockMessage != null); - assertTrue(blockMessage instanceof OFFlowMod); - OFFlowMod fm = (OFFlowMod) blockMessage; - assertTrue(fm.getHardTimeout() == 5); - OFMatch match = fm.getMatch(); - assertTrue((match.getWildcards() & OFMatch.OFPFW_DL_SRC) == 0); - assertTrue(Arrays.equals(match.getDataLayerSource(), - HexString.fromHexString(srcMac))); - - // Verify non-unique OFMatches are throttled - assertTrue(sw.inputThrottled(pi)); - } - - /** - * With throttling enabled, if rate of unique flows from a port - * exceeds set threshold, a flow mod should be emitted to block port - */ - @Test - public void testPacketInBlockPort() { - floodlightProvider.addSwitchEvent(anyLong(), - (String)anyObject(), anyBoolean()); - expectLastCall().times(2); - replay(floodlightProvider); - - int high = 500; - int perPort = 200; - sw.setThresholds(high, 10, 50, perPort); - // First, enable throttling - for (int i = 0; i < 1000; i++) { - assertFalse(sw.inputThrottleEnabled()); - assertFalse(sw.inputThrottled(pi)); - } - assertTrue(sw.inputThrottleEnabled()); - assertTrue(blockMessage == null); - - // Build unique flows with different source mac - for (int j = 0; j < perPort - 1; j++) { - testPacketSerialized[11]++; - pi.setPacketData(testPacketSerialized); - assertFalse(sw.inputThrottled(pi)); - } - assertTrue(blockMessage == null); - testPacketSerialized[11]++; - pi.setPacketData(testPacketSerialized); - assertFalse(sw.inputThrottled(pi)); - - // Verify the message is a flowmod with a hard timeout and per port - assertTrue(blockMessage != null); - assertTrue(blockMessage instanceof OFFlowMod); - OFFlowMod fm = (OFFlowMod) blockMessage; - assertTrue(fm.getHardTimeout() == 5); - OFMatch match = fm.getMatch(); - assertTrue((match.getWildcards() & OFMatch.OFPFW_DL_SRC) != 0); - assertTrue((match.getWildcards() & OFMatch.OFPFW_IN_PORT) == 0); - assertTrue(match.getInputPort() == 1); - - // Verify non-unique OFMatches are throttled - assertTrue(sw.inputThrottled(pi)); } /** @@ -390,7 +222,7 @@ public class OFSwitchBaseTest { private static <T> void assertCollectionEqualsNoOrder(Collection<T> expected, Collection<T> actual) { String msg = String.format("expected=%s, actual=%s", - expected, actual); + expected.toString(), actual.toString()); assertEquals(msg, expected.size(), actual.size()); for(T e: expected) { if (!actual.contains(e)) { @@ -409,14 +241,14 @@ public class OFSwitchBaseTest { */ @Test public void testBasicSetPortOperations() { - Collection<ImmutablePort> oldPorts = Collections.emptyList(); - Collection<ImmutablePort> oldEnabledPorts = Collections.emptyList(); - Collection<Short> oldEnabledPortNumbers = Collections.emptyList(); - List<ImmutablePort> ports = new ArrayList<ImmutablePort>(); + Collection<OFPortDesc> oldPorts = Collections.emptyList(); + Collection<OFPortDesc> oldEnabledPorts = Collections.emptyList(); + Collection<OFPort> oldEnabledPortNumbers = Collections.emptyList(); + List<OFPortDesc> ports = new ArrayList<OFPortDesc>(); Collection<PortChangeEvent> expectedChanges = - new ArrayList<IOFSwitch.PortChangeEvent>(); + new ArrayList<PortChangeEvent>(); Collection<PortChangeEvent> actualChanges = sw.comparePorts(ports); assertCollectionEqualsNoOrder(expectedChanges, actualChanges); @@ -460,15 +292,15 @@ public class OFSwitchBaseTest { sw.getEnabledPortNumbers().isEmpty()); assertTrue("enabled ports should be empty", sw.getEnabledPorts().isEmpty()); - assertEquals(p1a, sw.getPort((short)1)); + assertEquals(p1a, sw.getPort(OFPort.of(1))); assertEquals(p1a, sw.getPort("port1")); assertEquals(p1a, sw.getPort("PoRt1")); // case insensitive get - assertEquals(p2a, sw.getPort((short)2)); + assertEquals(p2a, sw.getPort(OFPort.of(2))); assertEquals(p2a, sw.getPort("port2")); assertEquals(p2a, sw.getPort("PoRt2")); // case insensitive get - assertEquals(null, sw.getPort((short)3)); + assertEquals(null, sw.getPort(OFPort.of(3))); assertEquals(null, sw.getPort("port3")); assertEquals(null, sw.getPort("PoRt3")); // case insensitive get @@ -498,15 +330,15 @@ public class OFSwitchBaseTest { sw.getEnabledPortNumbers().isEmpty()); assertTrue("enabled ports should be empty", sw.getEnabledPorts().isEmpty()); - assertEquals(p1a, sw.getPort((short)1)); + assertEquals(p1a, sw.getPort(OFPort.of(1))); assertEquals(p1a, sw.getPort("port1")); assertEquals(p1a, sw.getPort("PoRt1")); // case insensitive get - assertEquals(p2a, sw.getPort((short)2)); + assertEquals(p2a, sw.getPort(OFPort.of(2))); assertEquals(p2a, sw.getPort("port2")); assertEquals(p2a, sw.getPort("PoRt2")); // case insensitive get - assertEquals(null, sw.getPort((short)3)); + assertEquals(null, sw.getPort(OFPort.of(3))); assertEquals(null, sw.getPort("port3")); assertEquals(null, sw.getPort("PoRt3")); // case insensitive get @@ -533,22 +365,22 @@ public class OFSwitchBaseTest { assertEquals(1, actualChanges.size()); assertTrue("No UP event for port1", actualChanges.contains(evP1bUp)); assertCollectionEqualsNoOrder(ports, sw.getPorts()); - List<ImmutablePort> enabledPorts = new ArrayList<ImmutablePort>(); + List<OFPortDesc> enabledPorts = new ArrayList<OFPortDesc>(); enabledPorts.add(p1b); - List<Short> enabledPortNumbers = new ArrayList<Short>(); - enabledPortNumbers.add((short)1); + List<OFPort> enabledPortNumbers = new ArrayList<OFPort>(); + enabledPortNumbers.add(OFPort.of(1)); assertCollectionEqualsNoOrder(enabledPorts, sw.getEnabledPorts()); assertCollectionEqualsNoOrder(enabledPortNumbers, sw.getEnabledPortNumbers()); - assertEquals(p1b, sw.getPort((short)1)); + assertEquals(p1b, sw.getPort(OFPort.of(1))); assertEquals(p1b, sw.getPort("port1")); assertEquals(p1b, sw.getPort("PoRt1")); // case insensitive get - assertEquals(p2a, sw.getPort((short)2)); + assertEquals(p2a, sw.getPort(OFPort.of(2))); assertEquals(p2a, sw.getPort("port2")); assertEquals(p2a, sw.getPort("PoRt2")); // case insensitive get - assertEquals(null, sw.getPort((short)3)); + assertEquals(null, sw.getPort(OFPort.of(3))); assertEquals(null, sw.getPort("port3")); assertEquals(null, sw.getPort("PoRt3")); // case insensitive get @@ -579,22 +411,22 @@ public class OFSwitchBaseTest { assertTrue("No OTHER_CHANGE event for port2", actualChanges.contains(evP2bModified)); assertCollectionEqualsNoOrder(ports, sw.getPorts()); - enabledPorts = new ArrayList<ImmutablePort>(); + enabledPorts = new ArrayList<OFPortDesc>(); enabledPorts.add(p1b); - enabledPortNumbers = new ArrayList<Short>(); - enabledPortNumbers.add((short)1); + enabledPortNumbers = new ArrayList<OFPort>(); + enabledPortNumbers.add(OFPort.of(1)); assertCollectionEqualsNoOrder(enabledPorts, sw.getEnabledPorts()); assertCollectionEqualsNoOrder(enabledPortNumbers, sw.getEnabledPortNumbers()); - assertEquals(p1b, sw.getPort((short)1)); + assertEquals(p1b, sw.getPort(OFPort.of(1))); assertEquals(p1b, sw.getPort("port1")); assertEquals(p1b, sw.getPort("PoRt1")); // case insensitive get - assertEquals(p2b, sw.getPort((short)2)); + assertEquals(p2b, sw.getPort(OFPort.of(2))); assertEquals(p2b, sw.getPort("port2")); assertEquals(p2b, sw.getPort("PoRt2")); // case insensitive get - assertEquals(null, sw.getPort((short)3)); + assertEquals(null, sw.getPort(OFPort.of(3))); assertEquals(null, sw.getPort("port3")); assertEquals(null, sw.getPort("PoRt3")); // case insensitive get @@ -636,19 +468,19 @@ public class OFSwitchBaseTest { enabledPorts.clear(); enabledPorts.add(p3); enabledPortNumbers.clear(); - enabledPortNumbers.add((short)3); + enabledPortNumbers.add(OFPort.of(3)); assertCollectionEqualsNoOrder(enabledPorts, sw.getEnabledPorts()); assertCollectionEqualsNoOrder(enabledPortNumbers, sw.getEnabledPortNumbers()); - assertEquals(p1a, sw.getPort((short)1)); + assertEquals(p1a, sw.getPort(OFPort.of(1))); assertEquals(p1a, sw.getPort("port1")); assertEquals(p1a, sw.getPort("PoRt1")); // case insensitive get - assertEquals(p2a, sw.getPort((short)2)); + assertEquals(p2a, sw.getPort(OFPort.of(2))); assertEquals(p2a, sw.getPort("port2")); assertEquals(p2a, sw.getPort("PoRt2")); // case insensitive get - assertEquals(p3, sw.getPort((short)3)); + assertEquals(p3, sw.getPort(OFPort.of(3))); assertEquals(p3, sw.getPort("port3")); assertEquals(p3, sw.getPort("PoRt3")); // case insensitive get @@ -684,12 +516,12 @@ public class OFSwitchBaseTest { enabledPorts.clear(); enabledPorts.add(p3); enabledPortNumbers.clear(); - enabledPortNumbers.add((short)3); + enabledPortNumbers.add(OFPort.of(3)); assertCollectionEqualsNoOrder(enabledPorts, sw.getEnabledPorts()); assertCollectionEqualsNoOrder(enabledPortNumbers, sw.getEnabledPortNumbers()); - assertEquals(p3, sw.getPort((short)3)); + assertEquals(p3, sw.getPort(OFPort.of(3))); assertEquals(p3, sw.getPort("port3")); assertEquals(p3, sw.getPort("PoRt3")); // case insensitive get } @@ -701,9 +533,8 @@ public class OFSwitchBaseTest { */ @Test public void testBasicPortStatusOperation() { - OFPortStatus ps = (OFPortStatus) - BasicFactory.getInstance().getMessage(OFType.PORT_STATUS); - List<ImmutablePort> ports = new ArrayList<ImmutablePort>(); + OFPortStatus.Builder builder = sw.getOFFactory().buildPortStatus(); + List<OFPortDesc> ports = new ArrayList<OFPortDesc>(); ports.add(p1a); ports.add(p2a); @@ -715,7 +546,7 @@ public class OFSwitchBaseTest { new PortChangeEvent(p2a, PortChangeType.ADD); Collection<PortChangeEvent> expectedChanges = - new ArrayList<IOFSwitch.PortChangeEvent>(); + new ArrayList<PortChangeEvent>(); expectedChanges.add(evP1aAdded); expectedChanges.add(evP2aAdded); @@ -735,11 +566,11 @@ public class OFSwitchBaseTest { sw.getEnabledPortNumbers().isEmpty()); assertTrue("enabled ports should be empty", sw.getEnabledPorts().isEmpty()); - assertEquals(p1a, sw.getPort((short)1)); + assertEquals(p1a, sw.getPort(OFPort.of(1))); assertEquals(p1a, sw.getPort("port1")); assertEquals(p1a, sw.getPort("PoRt1")); // case insensitive get - assertEquals(p2a, sw.getPort((short)2)); + assertEquals(p2a, sw.getPort(OFPort.of(2))); assertEquals(p2a, sw.getPort("port2")); assertEquals(p2a, sw.getPort("PoRt2")); // case insensitive get @@ -749,27 +580,27 @@ public class OFSwitchBaseTest { ports.add(p2a); ports.add(p1b); - ps.setReason(OFPortReason.OFPPR_MODIFY.getReasonCode()); - ps.setDesc(p1b.toOFPhysicalPort()); + builder.setReason(OFPortReason.MODIFY); + builder.setDesc(p1b); PortChangeEvent evP1bUp = new PortChangeEvent(p1b, PortChangeType.UP); - actualChanges = sw.processOFPortStatus(ps); + actualChanges = sw.processOFPortStatus(builder.build()); expectedChanges.clear(); expectedChanges.add(evP1bUp); assertCollectionEqualsNoOrder(expectedChanges, actualChanges); assertCollectionEqualsNoOrder(ports, sw.getPorts()); - List<ImmutablePort> enabledPorts = new ArrayList<ImmutablePort>(); + List<OFPortDesc> enabledPorts = new ArrayList<OFPortDesc>(); enabledPorts.add(p1b); - List<Short> enabledPortNumbers = new ArrayList<Short>(); - enabledPortNumbers.add((short)1); + List<OFPort> enabledPortNumbers = new ArrayList<OFPort>(); + enabledPortNumbers.add(OFPort.of(1)); assertCollectionEqualsNoOrder(enabledPorts, sw.getEnabledPorts()); assertCollectionEqualsNoOrder(enabledPortNumbers, sw.getEnabledPortNumbers()); - assertEquals(p1b, sw.getPort((short)1)); + assertEquals(p1b, sw.getPort(OFPort.of(1))); assertEquals(p1b, sw.getPort("port1")); assertEquals(p1b, sw.getPort("PoRt1")); // case insensitive get - assertEquals(p2a, sw.getPort((short)2)); + assertEquals(p2a, sw.getPort(OFPort.of(2))); assertEquals(p2a, sw.getPort("port2")); assertEquals(p2a, sw.getPort("PoRt2")); // case insensitive get @@ -782,47 +613,47 @@ public class OFSwitchBaseTest { PortChangeEvent evP2bModified = new PortChangeEvent(p2b, PortChangeType.OTHER_UPDATE); - ps.setReason(OFPortReason.OFPPR_MODIFY.getReasonCode()); - ps.setDesc(p2b.toOFPhysicalPort()); + builder.setReason(OFPortReason.MODIFY); + builder.setDesc(p2b); - actualChanges = sw.processOFPortStatus(ps); + actualChanges = sw.processOFPortStatus(builder.build()); expectedChanges.clear(); expectedChanges.add(evP2bModified); assertCollectionEqualsNoOrder(expectedChanges, actualChanges); assertCollectionEqualsNoOrder(ports, sw.getPorts()); - enabledPorts = new ArrayList<ImmutablePort>(); + enabledPorts = new ArrayList<OFPortDesc>(); enabledPorts.add(p1b); - enabledPortNumbers = new ArrayList<Short>(); - enabledPortNumbers.add((short)1); + enabledPortNumbers = new ArrayList<OFPort>(); + enabledPortNumbers.add(OFPort.of(1)); assertCollectionEqualsNoOrder(enabledPorts, sw.getEnabledPorts()); assertCollectionEqualsNoOrder(enabledPortNumbers, sw.getEnabledPortNumbers()); - assertEquals(p1b, sw.getPort((short)1)); + assertEquals(p1b, sw.getPort(OFPort.of(1))); assertEquals(p1b, sw.getPort("port1")); assertEquals(p1b, sw.getPort("PoRt1")); // case insensitive get - assertEquals(p2b, sw.getPort((short)2)); + assertEquals(p2b, sw.getPort(OFPort.of(2))); assertEquals(p2b, sw.getPort("port2")); assertEquals(p2b, sw.getPort("PoRt2")); // case insensitive get - assertEquals(null, sw.getPort((short)3)); + assertEquals(null, sw.getPort(OFPort.of(3))); assertEquals(null, sw.getPort("port3")); assertEquals(null, sw.getPort("PoRt3")); // case insensitive get //---------------------------------------------------- - // p1b -> p1a. Via an OFPPR_ADD, Should receive a port DOWN + // p1b -> p1a. Via an ADD, Should receive a port DOWN ports.clear(); ports.add(p2b); ports.add(p1a); // we use an ADD here. We treat ADD and MODIFY the same way - ps.setReason(OFPortReason.OFPPR_ADD.getReasonCode()); - ps.setDesc(p1a.toOFPhysicalPort()); + builder.setReason(OFPortReason.ADD); + builder.setDesc(p1a); PortChangeEvent evP1aDown = new PortChangeEvent(p1a, PortChangeType.DOWN); - actualChanges = sw.processOFPortStatus(ps); + actualChanges = sw.processOFPortStatus(builder.build()); expectedChanges.clear(); expectedChanges.add(evP1aDown); assertCollectionEqualsNoOrder(expectedChanges, actualChanges); @@ -832,28 +663,28 @@ public class OFSwitchBaseTest { assertCollectionEqualsNoOrder(enabledPorts, sw.getEnabledPorts()); assertCollectionEqualsNoOrder(enabledPortNumbers, sw.getEnabledPortNumbers()); - assertEquals(p1a, sw.getPort((short)1)); + assertEquals(p1a, sw.getPort(OFPort.of(1))); assertEquals(p1a, sw.getPort("port1")); assertEquals(p1a, sw.getPort("PoRt1")); // case insensitive get - assertEquals(p2b, sw.getPort((short)2)); + assertEquals(p2b, sw.getPort(OFPort.of(2))); assertEquals(p2b, sw.getPort("port2")); assertEquals(p2b, sw.getPort("PoRt2")); // case insensitive get //---------------------------------------------------- - // p2b -> p2a. Via an OFPPR_ADD, Should receive a port MODIFY + // p2b -> p2a. Via an ADD, Should receive a port MODIFY ports.clear(); ports.add(p2a); ports.add(p1a); // we use an ADD here. We treat ADD and MODIFY the same way - ps.setReason(OFPortReason.OFPPR_ADD.getReasonCode()); - ps.setDesc(p2a.toOFPhysicalPort()); + builder.setReason(OFPortReason.ADD); + builder.setDesc(p2a); PortChangeEvent evP2aModify = new PortChangeEvent(p2a, PortChangeType.OTHER_UPDATE); - actualChanges = sw.processOFPortStatus(ps); + actualChanges = sw.processOFPortStatus(builder.build()); expectedChanges.clear(); expectedChanges.add(evP2aModify); assertCollectionEqualsNoOrder(expectedChanges, actualChanges); @@ -863,11 +694,11 @@ public class OFSwitchBaseTest { assertCollectionEqualsNoOrder(enabledPorts, sw.getEnabledPorts()); assertCollectionEqualsNoOrder(enabledPortNumbers, sw.getEnabledPortNumbers()); - assertEquals(p1a, sw.getPort((short)1)); + assertEquals(p1a, sw.getPort(OFPort.of(1))); assertEquals(p1a, sw.getPort("port1")); assertEquals(p1a, sw.getPort("PoRt1")); // case insensitive get - assertEquals(p2a, sw.getPort((short)2)); + assertEquals(p2a, sw.getPort(OFPort.of(2))); assertEquals(p2a, sw.getPort("port2")); assertEquals(p2a, sw.getPort("PoRt2")); // case insensitive get @@ -877,12 +708,12 @@ public class OFSwitchBaseTest { ports.clear(); ports.add(p1a); - ps.setReason(OFPortReason.OFPPR_DELETE.getReasonCode()); - ps.setDesc(p2a.toOFPhysicalPort()); + builder.setReason(OFPortReason.DELETE); + builder.setDesc(p2a); PortChangeEvent evP2aDel = new PortChangeEvent(p2a, PortChangeType.DELETE); - actualChanges = sw.processOFPortStatus(ps); + actualChanges = sw.processOFPortStatus(builder.build()); expectedChanges.clear(); expectedChanges.add(evP2aDel); assertCollectionEqualsNoOrder(expectedChanges, actualChanges); @@ -892,11 +723,11 @@ public class OFSwitchBaseTest { assertCollectionEqualsNoOrder(enabledPorts, sw.getEnabledPorts()); assertCollectionEqualsNoOrder(enabledPortNumbers, sw.getEnabledPortNumbers()); - assertEquals(p1a, sw.getPort((short)1)); + assertEquals(p1a, sw.getPort(OFPort.of(1))); assertEquals(p1a, sw.getPort("port1")); assertEquals(p1a, sw.getPort("PoRt1")); // case insensitive get - assertEquals(null, sw.getPort((short)2)); + assertEquals(null, sw.getPort(OFPort.of(2))); assertEquals(null, sw.getPort("port2")); assertEquals(null, sw.getPort("PoRt2")); // case insensitive get @@ -905,10 +736,10 @@ public class OFSwitchBaseTest { ports.clear(); ports.add(p1a); - ps.setReason(OFPortReason.OFPPR_DELETE.getReasonCode()); - ps.setDesc(p2a.toOFPhysicalPort()); + builder.setReason(OFPortReason.DELETE); + builder.setDesc(p2a); - actualChanges = sw.processOFPortStatus(ps); + actualChanges = sw.processOFPortStatus(builder.build()); expectedChanges.clear(); assertCollectionEqualsNoOrder(expectedChanges, actualChanges); assertCollectionEqualsNoOrder(ports, sw.getPorts()); @@ -917,11 +748,11 @@ public class OFSwitchBaseTest { assertCollectionEqualsNoOrder(enabledPorts, sw.getEnabledPorts()); assertCollectionEqualsNoOrder(enabledPortNumbers, sw.getEnabledPortNumbers()); - assertEquals(p1a, sw.getPort((short)1)); + assertEquals(p1a, sw.getPort(OFPort.of(1))); assertEquals(p1a, sw.getPort("port1")); assertEquals(p1a, sw.getPort("PoRt1")); // case insensitive get - assertEquals(null, sw.getPort((short)2)); + assertEquals(null, sw.getPort(OFPort.of(2))); assertEquals(null, sw.getPort("port2")); assertEquals(null, sw.getPort("PoRt2")); // case insensitive get @@ -930,12 +761,12 @@ public class OFSwitchBaseTest { // Remove p1a ports.clear(); - ps.setReason(OFPortReason.OFPPR_DELETE.getReasonCode()); - ps.setDesc(p1a.toOFPhysicalPort()); + builder.setReason(OFPortReason.DELETE); + builder.setDesc(p1a); PortChangeEvent evP1aDel = new PortChangeEvent(p1a, PortChangeType.DELETE); - actualChanges = sw.processOFPortStatus(ps); + actualChanges = sw.processOFPortStatus(builder.build()); expectedChanges.clear(); expectedChanges.add(evP1aDel); assertCollectionEqualsNoOrder(expectedChanges, actualChanges); @@ -945,11 +776,11 @@ public class OFSwitchBaseTest { assertCollectionEqualsNoOrder(enabledPorts, sw.getEnabledPorts()); assertCollectionEqualsNoOrder(enabledPortNumbers, sw.getEnabledPortNumbers()); - assertEquals(null, sw.getPort((short)1)); + assertEquals(null, sw.getPort(OFPort.of(1))); assertEquals(null, sw.getPort("port1")); assertEquals(null, sw.getPort("PoRt1")); // case insensitive get - assertEquals(null, sw.getPort((short)2)); + assertEquals(null, sw.getPort(OFPort.of(2))); assertEquals(null, sw.getPort("port2")); assertEquals(null, sw.getPort("PoRt2")); // case insensitive get @@ -964,28 +795,28 @@ public class OFSwitchBaseTest { expectedChanges.clear(); expectedChanges.add(evP3Add); - ps.setReason(OFPortReason.OFPPR_ADD.getReasonCode()); - ps.setDesc(p3.toOFPhysicalPort()); + builder.setReason(OFPortReason.ADD); + builder.setDesc(p3); - actualChanges = sw.processOFPortStatus(ps); + actualChanges = sw.processOFPortStatus(builder.build()); assertCollectionEqualsNoOrder(expectedChanges, actualChanges); assertCollectionEqualsNoOrder(ports, sw.getPorts()); enabledPorts.clear(); enabledPorts.add(p3); enabledPortNumbers.clear(); - enabledPortNumbers.add((short)3); + enabledPortNumbers.add(OFPort.of(3)); assertCollectionEqualsNoOrder(enabledPorts, sw.getEnabledPorts()); assertCollectionEqualsNoOrder(enabledPortNumbers, sw.getEnabledPortNumbers()); - assertEquals(null, sw.getPort((short)1)); + assertEquals(null, sw.getPort(OFPort.of(1))); assertEquals(null, sw.getPort("port1")); assertEquals(null, sw.getPort("PoRt1")); // case insensitive get - assertEquals(null, sw.getPort((short)2)); + assertEquals(null, sw.getPort(OFPort.of(2))); assertEquals(null, sw.getPort("port2")); assertEquals(null, sw.getPort("PoRt2")); // case insensitive get - assertEquals(p3, sw.getPort((short)3)); + assertEquals(p3, sw.getPort(OFPort.of(3))); assertEquals(p3, sw.getPort("port3")); assertEquals(p3, sw.getPort("PoRt3")); // case insensitive get @@ -1001,30 +832,30 @@ public class OFSwitchBaseTest { expectedChanges.add(evP1bAdd); // use a modify to add the port - ps.setReason(OFPortReason.OFPPR_MODIFY.getReasonCode()); - ps.setDesc(p1b.toOFPhysicalPort()); + builder.setReason(OFPortReason.MODIFY); + builder.setDesc(p1b); - actualChanges = sw.processOFPortStatus(ps); + actualChanges = sw.processOFPortStatus(builder.build()); assertCollectionEqualsNoOrder(expectedChanges, actualChanges); assertCollectionEqualsNoOrder(ports, sw.getPorts()); enabledPorts.clear(); enabledPorts.add(p3); enabledPorts.add(p1b); enabledPortNumbers.clear(); - enabledPortNumbers.add((short)3); - enabledPortNumbers.add((short)1); + enabledPortNumbers.add(OFPort.of(3)); + enabledPortNumbers.add(OFPort.of(1)); assertCollectionEqualsNoOrder(enabledPorts, sw.getEnabledPorts()); assertCollectionEqualsNoOrder(enabledPortNumbers, sw.getEnabledPortNumbers()); - assertEquals(p1b, sw.getPort((short)1)); + assertEquals(p1b, sw.getPort(OFPort.of(1))); assertEquals(p1b, sw.getPort("port1")); assertEquals(p1b, sw.getPort("PoRt1")); // case insensitive get - assertEquals(null, sw.getPort((short)2)); + assertEquals(null, sw.getPort(OFPort.of(2))); assertEquals(null, sw.getPort("port2")); assertEquals(null, sw.getPort("PoRt2")); // case insensitive get - assertEquals(p3, sw.getPort((short)3)); + assertEquals(p3, sw.getPort(OFPort.of(3))); assertEquals(p3, sw.getPort("port3")); assertEquals(p3, sw.getPort("PoRt3")); // case insensitive get @@ -1037,30 +868,30 @@ public class OFSwitchBaseTest { expectedChanges.clear(); // use a modify to add the port - ps.setReason(OFPortReason.OFPPR_MODIFY.getReasonCode()); - ps.setDesc(p1b.toOFPhysicalPort()); + builder.setReason(OFPortReason.MODIFY); + builder.setDesc(p1b); - actualChanges = sw.processOFPortStatus(ps); + actualChanges = sw.processOFPortStatus(builder.build()); assertCollectionEqualsNoOrder(expectedChanges, actualChanges); assertCollectionEqualsNoOrder(ports, sw.getPorts()); enabledPorts.clear(); enabledPorts.add(p3); enabledPorts.add(p1b); enabledPortNumbers.clear(); - enabledPortNumbers.add((short)3); - enabledPortNumbers.add((short)1); + enabledPortNumbers.add(OFPort.of(3)); + enabledPortNumbers.add(OFPort.of(1)); assertCollectionEqualsNoOrder(enabledPorts, sw.getEnabledPorts()); assertCollectionEqualsNoOrder(enabledPortNumbers, sw.getEnabledPortNumbers()); - assertEquals(p1b, sw.getPort((short)1)); + assertEquals(p1b, sw.getPort(OFPort.of(1))); assertEquals(p1b, sw.getPort("port1")); assertEquals(p1b, sw.getPort("PoRt1")); // case insensitive get - assertEquals(null, sw.getPort((short)2)); + assertEquals(null, sw.getPort(OFPort.of(2))); assertEquals(null, sw.getPort("port2")); assertEquals(null, sw.getPort("PoRt2")); // case insensitive get - assertEquals(p3, sw.getPort((short)3)); + assertEquals(p3, sw.getPort(OFPort.of(3))); assertEquals(p3, sw.getPort("port3")); assertEquals(p3, sw.getPort("PoRt3")); // case insensitive get } @@ -1073,107 +904,109 @@ public class OFSwitchBaseTest { public void testSetPortExceptions() { try { sw.setPorts(null); - fail("Excpeted exception not thrown"); + fail("Expected exception not thrown"); } catch (NullPointerException e) { }; // two ports with same name - List<ImmutablePort> ports = new ArrayList<ImmutablePort>(); - ports.add(ImmutablePort.create("port1", (short)1)); - ports.add(ImmutablePort.create("port1", (short)2)); + List<OFPortDesc> ports = new ArrayList<OFPortDesc>(); + ports.add(sw.getOFFactory().buildPortDesc().setName("port1").setPortNo(OFPort.of(1)).build()); + ports.add(sw.getOFFactory().buildPortDesc().setName("port1").setPortNo(OFPort.of(2)).build()); try { sw.setPorts(ports); - fail("Excpeted exception not thrown"); + fail("Expected exception not thrown"); } catch (IllegalArgumentException e) { }; // two ports with same number ports.clear(); - ports.add(ImmutablePort.create("port1", (short)1)); - ports.add(ImmutablePort.create("port2", (short)1)); + ports.add(sw.getOFFactory().buildPortDesc().setName("port1").setPortNo(OFPort.of(1)).build()); + ports.add(sw.getOFFactory().buildPortDesc().setName("port2").setPortNo(OFPort.of(1)).build()); try { sw.setPorts(ports); - fail("Excpeted exception not thrown"); + fail("Expected exception not thrown"); } catch (IllegalArgumentException e) { }; // null port in list ports.clear(); - ports.add(ImmutablePort.create("port1", (short)1)); + ports.add(sw.getOFFactory().buildPortDesc().setName("port1").setPortNo(OFPort.of(1)).build()); ports.add(null); try { sw.setPorts(ports); - fail("Excpeted exception not thrown"); + fail("Expected exception not thrown"); } catch (NullPointerException e) { }; // try getPort(null) try { - sw.getPort(null); - fail("Excpeted exception not thrown"); + sw.getPort((String)null); + fail("Expected exception not thrown"); } catch (NullPointerException e) { }; //-------------------------- // comparePorts() try { sw.comparePorts(null); - fail("Excpeted exception not thrown"); + fail("Expected exception not thrown"); } catch (NullPointerException e) { }; // two ports with same name - ports = new ArrayList<ImmutablePort>(); - ports.add(ImmutablePort.create("port1", (short)1)); - ports.add(ImmutablePort.create("port1", (short)2)); + ports = new ArrayList<OFPortDesc>(); + ports.add(sw.getOFFactory().buildPortDesc().setName("port1").setPortNo(OFPort.of(1)).build()); + ports.add(sw.getOFFactory().buildPortDesc().setName("port1").setPortNo(OFPort.of(2)).build()); try { sw.comparePorts(ports); - fail("Excpeted exception not thrown"); + fail("Expected exception not thrown"); } catch (IllegalArgumentException e) { }; // two ports with same number ports.clear(); - ports.add(ImmutablePort.create("port1", (short)1)); - ports.add(ImmutablePort.create("port2", (short)1)); + ports.add(sw.getOFFactory().buildPortDesc().setName("port1").setPortNo(OFPort.of(1)).build()); + ports.add(sw.getOFFactory().buildPortDesc().setName("port2").setPortNo(OFPort.of(1)).build()); try { sw.comparePorts(ports); - fail("Excpeted exception not thrown"); + fail("Expected exception not thrown"); } catch (IllegalArgumentException e) { }; // null port in list ports.clear(); - ports.add(ImmutablePort.create("port1", (short)1)); + ports.add(sw.getOFFactory().buildPortDesc().setName("port1").setPortNo(OFPort.of(1)).build()); ports.add(null); try { sw.comparePorts(ports); - fail("Excpeted exception not thrown"); + fail("Expected exception not thrown"); } catch (NullPointerException e) { }; // try getPort(null) try { - sw.getPort(null); - fail("Excpeted exception not thrown"); + sw.getPort((String)null); + fail("Expected exception not thrown"); } catch (NullPointerException e) { }; } @Test public void testPortStatusExceptions() { - OFPortStatus ps = (OFPortStatus) - BasicFactory.getInstance().getMessage(OFType.PORT_STATUS); + OFPortStatus.Builder builder = sw.getOFFactory().buildPortStatus(); try { sw.processOFPortStatus(null); fail("Expected exception not thrown"); } catch (NullPointerException e) { } + /* There's no way with the new LOXI openflowj to create messages with + * invalid fields. // illegal reason code - ps.setReason((byte)0x42); - ps.setDesc(ImmutablePort.create("p1", (short)1).toOFPhysicalPort()); + builder.setReason((byte)0x42); + builder.setDesc(ImmutablePort.create("p1", OFPort.of(1)).toOFPortDesc(sw)); try { - sw.processOFPortStatus(ps); + sw.processOFPortStatus(builder.build()); fail("Expected exception not thrown"); } catch (IllegalArgumentException e) { } + */ // null port - ps.setReason(OFPortReason.OFPPR_ADD.getReasonCode()); - ps.setDesc(null); + builder.setReason(OFPortReason.ADD); + builder.setDesc(null); try { - sw.processOFPortStatus(ps); + sw.processOFPortStatus(builder.build()); fail("Expected exception not thrown"); } catch (NullPointerException e) { } } @@ -1198,7 +1031,7 @@ public class OFSwitchBaseTest { Collection<PortChangeEvent> actualEvents) { String inputDesc = String.format("earlyEvents=%s, lateEvents=%s, " + "anytimeEvents=%s, actualEvents=%s", - earlyEvents, lateEvents, anytimeEvents, actualEvents); + earlyEvents.toString(), lateEvents.toString(), anytimeEvents.toString(), actualEvents.toString()); // Make copies of expected lists, so we can modify them Collection<PortChangeEvent> early = new ArrayList<PortChangeEvent>(earlyEvents); @@ -1260,7 +1093,7 @@ public class OFSwitchBaseTest { @Test public void testSetPortNameNumberMappingChange() { - List<ImmutablePort> ports = new ArrayList<ImmutablePort>(); + List<OFPortDesc> ports = new ArrayList<OFPortDesc>(); Collection<PortChangeEvent> early = new ArrayList<PortChangeEvent>(); Collection<PortChangeEvent> late = new ArrayList<PortChangeEvent>(); Collection<PortChangeEvent> anytime = new ArrayList<PortChangeEvent>(); @@ -1364,7 +1197,7 @@ public class OFSwitchBaseTest { @Test public void testPortStatusNameNumberMappingChange() { - List<ImmutablePort> ports = new ArrayList<ImmutablePort>(); + List<OFPortDesc> ports = new ArrayList<OFPortDesc>(); Collection<PortChangeEvent> early = new ArrayList<PortChangeEvent>(); Collection<PortChangeEvent> late = new ArrayList<PortChangeEvent>(); Collection<PortChangeEvent> anytime = new ArrayList<PortChangeEvent>(); @@ -1376,34 +1209,33 @@ public class OFSwitchBaseTest { sw.setPorts(ports); assertCollectionEqualsNoOrder(ports, sw.getPorts()); - OFPortStatus ps = (OFPortStatus) - BasicFactory.getInstance().getMessage(OFType.PORT_STATUS); + OFPortStatus.Builder builder = sw.getOFFactory().buildPortStatus(); // portFoo1 -> portFoo2 via MODIFY : name collision - ps.setReason(OFPortReason.OFPPR_MODIFY.getReasonCode()); - ps.setDesc(portFoo2.toOFPhysicalPort()); + builder.setReason(OFPortReason.MODIFY); + builder.setDesc(portFoo2); ports.clear(); ports.add(portFoo2); ports.add(p1a); early.clear(); late.clear(); anytime.clear(); - actualChanges = sw.processOFPortStatus(ps); + actualChanges = sw.processOFPortStatus(builder.build()); early.add(portFoo1Del); late.add(portFoo2Add); assertChangeEvents(early, late, anytime, actualChanges); assertCollectionEqualsNoOrder(ports, sw.getPorts()); // portFoo2 -> portBar2 via ADD number collision - ps.setReason(OFPortReason.OFPPR_ADD.getReasonCode()); - ps.setDesc(portBar2.toOFPhysicalPort()); + builder.setReason(OFPortReason.ADD); + builder.setDesc(portBar2); ports.clear(); ports.add(portBar2); ports.add(p1a); early.clear(); late.clear(); anytime.clear(); - actualChanges = sw.processOFPortStatus(ps); + actualChanges = sw.processOFPortStatus(builder.build()); early.add(portFoo2Del); late.add(portBar2Add); assertChangeEvents(early, late, anytime, actualChanges); @@ -1417,14 +1249,14 @@ public class OFSwitchBaseTest { assertCollectionEqualsNoOrder(ports, sw.getPorts()); // portFoo1 + portBar2 -> portFoo2: name and number collision - ps.setReason(OFPortReason.OFPPR_MODIFY.getReasonCode()); - ps.setDesc(portFoo2.toOFPhysicalPort()); + builder.setReason(OFPortReason.MODIFY); + builder.setDesc(portFoo2); ports.clear(); ports.add(portFoo2); early.clear(); late.clear(); anytime.clear(); - actualChanges = sw.processOFPortStatus(ps); + actualChanges = sw.processOFPortStatus(builder.build()); early.add(portFoo1Del); early.add(portBar2Del); late.add(portFoo2Add); @@ -1435,13 +1267,13 @@ public class OFSwitchBaseTest { // Test DELETEs // del portFoo1: name exists (portFoo2), but number doesn't. - ps.setReason(OFPortReason.OFPPR_DELETE.getReasonCode()); - ps.setDesc(portFoo1.toOFPhysicalPort()); + builder.setReason(OFPortReason.DELETE); + builder.setDesc(portFoo1); ports.clear(); early.clear(); late.clear(); anytime.clear(); - actualChanges = sw.processOFPortStatus(ps); + actualChanges = sw.processOFPortStatus(builder.build()); anytime.add(portFoo2Del); assertChangeEvents(early, late, anytime, actualChanges); assertCollectionEqualsNoOrder(ports, sw.getPorts()); @@ -1453,13 +1285,13 @@ public class OFSwitchBaseTest { assertCollectionEqualsNoOrder(ports, sw.getPorts()); // del portBar1: number exists (portFoo1), but name doesn't. - ps.setReason(OFPortReason.OFPPR_DELETE.getReasonCode()); - ps.setDesc(portBar1.toOFPhysicalPort()); + builder.setReason(OFPortReason.DELETE); + builder.setDesc(portBar1); ports.clear(); early.clear(); late.clear(); anytime.clear(); - actualChanges = sw.processOFPortStatus(ps); + actualChanges = sw.processOFPortStatus(builder.build()); anytime.add(portFoo1Del); assertChangeEvents(early, late, anytime, actualChanges); assertCollectionEqualsNoOrder(ports, sw.getPorts()); @@ -1473,13 +1305,13 @@ public class OFSwitchBaseTest { assertCollectionEqualsNoOrder(ports, sw.getPorts()); // del portFoo2: name and number exists - ps.setReason(OFPortReason.OFPPR_DELETE.getReasonCode()); - ps.setDesc(portFoo2.toOFPhysicalPort()); + builder.setReason(OFPortReason.DELETE); + builder.setDesc(portFoo2); ports.clear(); early.clear(); late.clear(); anytime.clear(); - actualChanges = sw.processOFPortStatus(ps); + actualChanges = sw.processOFPortStatus(builder.build()); anytime.add(portFoo1Del); anytime.add(portBar2Del); assertChangeEvents(early, late, anytime, actualChanges); @@ -1488,7 +1320,10 @@ public class OFSwitchBaseTest { @Test public void testSubHandshake() { - OFMessage m = BasicFactory.getInstance().getMessage(OFType.VENDOR); + OFMessage m = sw.getOFFactory().buildRoleReply() + .setXid(1) + .setRole(OFControllerRole.ROLE_MASTER) + .build(); // test execptions before handshake is started try { sw.processDriverHandshakeMessage(m); @@ -1515,4 +1350,55 @@ public class OFSwitchBaseTest { } catch (SwitchDriverSubHandshakeAlreadyStarted e) { /* expected */ } } + // This should throw an error + @Test + public void testMissingConnection() { + + // Just to make sure so this test is worth it + assertFalse("Switch should not have a connection with auxId 5", sw.getConnections().contains(OFAuxId.of(5))); + + try{ + sw.getConnection(OFAuxId.of(5)); + fail("Expected exception not thrown"); + } + catch(IllegalArgumentException e){ /* expected */ } + } + + + // This should throw an error + @Test + public void testInvalidLogicalOFMessageCategory() { + + LogicalOFMessageCategory bad = new LogicalOFMessageCategory("bad", 2); + assertFalse("Controller should not any logical OFMessage categories", switchManager.isCategoryRegistered(bad)); + + reset(switchManager); + expect(switchManager.isCategoryRegistered(bad)).andReturn(false); + replay(switchManager); + + try{ + sw.write(testMessage, bad); + fail("Expected exception not thrown"); + } + catch(IllegalArgumentException e){ /* expected */ } + + verify(switchManager); + } + + // This should not throw an error + @Test + public void testValidLogicalOFMessageCategory() { + + LogicalOFMessageCategory category = new LogicalOFMessageCategory("test", 1); + assertFalse("Controller should not have any logical OFMessage categories", switchManager.isCategoryRegistered(category)); + + reset(switchManager); + expect(switchManager.isCategoryRegistered(category)).andReturn(true); + replay(switchManager); + + sw.write(testMessage, category); + + verify(switchManager); + } + } diff --git a/src/test/java/net/floodlightcontroller/core/internal/ControllerTest.java b/src/test/java/net/floodlightcontroller/core/internal/ControllerTest.java index b06589a30a6493bd22d7da347a7bab7e6d4fd642..2148b14df8e3299728d094e332243e0361a541ac 100644 --- a/src/test/java/net/floodlightcontroller/core/internal/ControllerTest.java +++ b/src/test/java/net/floodlightcontroller/core/internal/ControllerTest.java @@ -18,44 +18,30 @@ package net.floodlightcontroller.core.internal; import static org.easymock.EasyMock.*; - import static org.junit.Assert.*; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; +import org.junit.Before; +import org.junit.Test; + +import net.floodlightcontroller.test.FloodlightTestCase; + import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; + import net.floodlightcontroller.core.FloodlightContext; -import net.floodlightcontroller.core.HAListenerTypeMarker; +import net.floodlightcontroller.core.HARole; import net.floodlightcontroller.core.IFloodlightProviderService; -import net.floodlightcontroller.core.IFloodlightProviderService.Role; import net.floodlightcontroller.core.IHAListener; import net.floodlightcontroller.core.IListener; import net.floodlightcontroller.core.IListener.Command; -import net.floodlightcontroller.core.IOFMessageFilterManagerService; import net.floodlightcontroller.core.IOFMessageListener; import net.floodlightcontroller.core.IOFSwitch; -import net.floodlightcontroller.core.IOFSwitch.PortChangeType; -import net.floodlightcontroller.core.IOFSwitchDriver; -import net.floodlightcontroller.core.IOFSwitchListener; -import net.floodlightcontroller.core.IReadyForReconcileListener; -import net.floodlightcontroller.core.ImmutablePort; -import net.floodlightcontroller.core.OFMessageFilterManager; import net.floodlightcontroller.core.RoleInfo; -import net.floodlightcontroller.core.SwitchSyncRepresentation; +import net.floodlightcontroller.core.SwitchDescription; import net.floodlightcontroller.core.module.FloodlightModuleContext; -import net.floodlightcontroller.core.test.MockFloodlightProvider; import net.floodlightcontroller.core.test.MockThreadPoolService; -import net.floodlightcontroller.counter.CounterStore; -import net.floodlightcontroller.counter.ICounterStoreService; -import net.floodlightcontroller.debugcounter.DebugCounter; import net.floodlightcontroller.debugcounter.IDebugCounterService; -import net.floodlightcontroller.debugevent.DebugEvent; +import net.floodlightcontroller.debugcounter.DebugCounterServiceImpl; +import net.floodlightcontroller.debugevent.DebugEventService; import net.floodlightcontroller.debugevent.IDebugEventService; import net.floodlightcontroller.packet.ARP; import net.floodlightcontroller.packet.Ethernet; @@ -67,53 +53,61 @@ import net.floodlightcontroller.restserver.IRestApiService; import net.floodlightcontroller.restserver.RestApiServer; import net.floodlightcontroller.storage.IStorageSourceService; import net.floodlightcontroller.storage.memory.MemoryStorageSource; -import net.floodlightcontroller.test.FloodlightTestCase; import net.floodlightcontroller.threadpool.IThreadPoolService; +import net.floodlightcontroller.threadpool.ThreadPool; import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.openflow.protocol.OFFeaturesReply; -import org.openflow.protocol.OFFlowMod; -import org.openflow.protocol.OFMessage; -import org.openflow.protocol.OFPacketIn; -import org.openflow.protocol.OFPacketIn.OFPacketInReason; -import org.openflow.protocol.OFPacketOut; -import org.openflow.protocol.OFPhysicalPort; -import org.openflow.protocol.OFPort; -import org.openflow.protocol.OFType; -import org.openflow.protocol.action.OFAction; -import org.openflow.protocol.action.OFActionOutput; -import org.openflow.protocol.factory.BasicFactory; -import org.openflow.protocol.statistics.OFDescriptionStatistics; -import org.openflow.util.HexString; -import org.sdnplatform.sync.IStoreClient; + +import net.floodlightcontroller.core.IShutdownListener; +import net.floodlightcontroller.core.IShutdownService; +import net.floodlightcontroller.core.internal.IOFSwitchService; +import net.floodlightcontroller.core.test.MockSwitchManager; + +import net.floodlightcontroller.core.IOFSwitchBackend; +import org.projectfloodlight.openflow.protocol.OFControllerRole; +import org.projectfloodlight.openflow.protocol.OFFactories; +import org.projectfloodlight.openflow.protocol.OFFeaturesReply; +import org.projectfloodlight.openflow.protocol.OFFlowMod; +import org.projectfloodlight.openflow.protocol.OFMessage; +import org.projectfloodlight.openflow.protocol.OFPacketIn; +import org.projectfloodlight.openflow.protocol.OFPacketInReason; +import org.projectfloodlight.openflow.protocol.OFVersion; +import org.projectfloodlight.openflow.types.DatapathId; +import org.projectfloodlight.openflow.types.OFBufferId; +import org.projectfloodlight.openflow.types.OFPort; +import org.projectfloodlight.openflow.protocol.OFPortDesc; +import org.projectfloodlight.openflow.protocol.OFType; +import org.projectfloodlight.openflow.protocol.OFFactory; import org.sdnplatform.sync.ISyncService; -import org.sdnplatform.sync.IStoreListener.UpdateType; import org.sdnplatform.sync.test.MockSyncService; +import com.google.common.collect.ImmutableList; + public class ControllerTest extends FloodlightTestCase { private Controller controller; private MockThreadPoolService tp; private MockSyncService syncService; - private IStoreClient<Long, SwitchSyncRepresentation> storeClient; private IPacket testPacket; private OFPacketIn pi; + + // FIXME:LOJI: For now just work with OF 1.0 + private final OFFactory factory = OFFactories.getFactory(OFVersion.OF_10); + private static DatapathId DATAPATH_ID_0 = DatapathId.of(0); @Override @Before public void setUp() throws Exception { - doSetUp(Role.MASTER); + doSetUp(HARole.ACTIVE); } - public void doSetUp(Role role) throws Exception { + public void doSetUp(HARole role) throws Exception { super.setUp(); FloodlightModuleContext fmc = new FloodlightModuleContext(); - FloodlightProvider cm = new FloodlightProvider(); + fmc.addConfigParam(cm, "role", role.toString()); controller = (Controller)cm.getServiceImpls().get(IFloodlightProviderService.class); fmc.addService(IFloodlightProviderService.class, controller); @@ -123,31 +117,40 @@ public class ControllerTest extends FloodlightTestCase { RestApiServer restApi = new RestApiServer(); fmc.addService(IRestApiService.class, restApi); - - CounterStore cs = new CounterStore(); - fmc.addService(ICounterStoreService.class, cs); + + ThreadPool threadPool = new ThreadPool(); + fmc.addService(IThreadPoolService.class, threadPool); + + MockSwitchManager switchService = new MockSwitchManager(); + fmc.addService(IOFSwitchService.class, switchService); PktInProcessingTime ppt = new PktInProcessingTime(); fmc.addService(IPktInProcessingTimeService.class, ppt); // TODO: should mock IDebugCounterService and make sure // the expected counters are updated. - DebugCounter debugCounterService = new DebugCounter(); + DebugCounterServiceImpl debugCounterService = new DebugCounterServiceImpl(); fmc.addService(IDebugCounterService.class, debugCounterService); - DebugEvent debugEventService = new DebugEvent(); + DebugEventService debugEventService = new DebugEventService(); fmc.addService(IDebugEventService.class, debugEventService); + IShutdownService shutdownService = createMock(IShutdownService.class); + shutdownService.registerShutdownListener(anyObject(IShutdownListener.class)); + expectLastCall().anyTimes(); + replay(shutdownService); + fmc.addService(IShutdownService.class, shutdownService); + verify(shutdownService); + tp = new MockThreadPoolService(); fmc.addService(IThreadPoolService.class, tp); syncService = new MockSyncService(); fmc.addService(ISyncService.class, syncService); - - ppt.init(fmc); restApi.init(fmc); + threadPool.init(fmc); memstorage.init(fmc); tp.init(fmc); debugCounterService.init(fmc); @@ -157,6 +160,7 @@ public class ControllerTest extends FloodlightTestCase { ppt.startUp(fmc); restApi.startUp(fmc); + threadPool.startUp(fmc); memstorage.startUp(fmc); tp.startUp(fmc); debugCounterService.startUp(fmc); @@ -164,11 +168,6 @@ public class ControllerTest extends FloodlightTestCase { syncService.startUp(fmc); cm.startUp(fmc); - storeClient = - syncService.getStoreClient(Controller.SWITCH_SYNC_STORE_NAME, - Long.class, - SwitchSyncRepresentation.class); - testPacket = new Ethernet() .setSourceMACAddress("00:44:33:22:11:00") .setDestinationMACAddress("00:11:22:33:44:55") @@ -186,16 +185,17 @@ public class ControllerTest extends FloodlightTestCase { .setTargetProtocolAddress(IPv4.toIPv4AddressBytes("192.168.1.2"))); byte[] testPacketSerialized = testPacket.serialize(); - pi = ((OFPacketIn) BasicFactory.getInstance().getMessage(OFType.PACKET_IN)) - .setBufferId(-1) - .setInPort((short) 1) - .setPacketData(testPacketSerialized) + //TODO @Ryan should this be for any version? Should 1.0 and 1.3 be tested here? + // The specific factory can be obtained from the switch, but we don't have one + pi = (OFPacketIn) factory.buildPacketIn() + .setBufferId(OFBufferId.NO_BUFFER) + .setInPort(OFPort.of(1)) + .setData(testPacketSerialized) .setReason(OFPacketInReason.NO_MATCH) - .setTotalLength((short) testPacketSerialized.length); + .setTotalLen(testPacketSerialized.length).build(); } - @Override @After public void tearDown() { tp.getScheduledExecutor().shutdownNow(); @@ -208,19 +208,16 @@ public class ControllerTest extends FloodlightTestCase { return controller; } - private static OFDescriptionStatistics createOFDescriptionStatistics() { - OFDescriptionStatistics desc = new OFDescriptionStatistics(); - desc.setDatapathDescription(""); - desc.setHardwareDescription(""); - desc.setManufacturerDescription(""); - desc.setSerialNumber(""); - desc.setSoftwareDescription(""); - return desc; + private static SwitchDescription createSwitchDescription() { + return new SwitchDescription(); } - private static OFFeaturesReply createOFFeaturesReply() { - OFFeaturesReply fr = new OFFeaturesReply(); - fr.setPorts(Collections.<OFPhysicalPort>emptyList()); + private OFFeaturesReply createOFFeaturesReply(DatapathId datapathId) { + OFFeaturesReply fr = factory.buildFeaturesReply() + .setXid(0) + .setDatapathId(datapathId) + .setPorts(ImmutableList.<OFPortDesc>of()) + .build(); return fr; } @@ -228,28 +225,26 @@ public class ControllerTest extends FloodlightTestCase { /** Set the mock expectations for sw when sw is passed to addSwitch * The same expectations can be used when a new SwitchSyncRepresentation * is created from the given mocked switch */ - protected void setupSwitchForAddSwitch(IOFSwitch sw, long dpid, - OFDescriptionStatistics desc, + protected void setupSwitchForAddSwitch(IOFSwitch sw, DatapathId datapathId, + SwitchDescription description, OFFeaturesReply featuresReply) { - String dpidString = HexString.toHexString(dpid); - - if (desc == null) { - desc = createOFDescriptionStatistics(); + String dpidString = datapathId.toString(); + if (description == null) { + description = createSwitchDescription(); } if (featuresReply == null) { - featuresReply = createOFFeaturesReply(); - featuresReply.setDatapathId(dpid); + featuresReply = createOFFeaturesReply(datapathId); } - List<ImmutablePort> ports = - ImmutablePort.immutablePortListOf(featuresReply.getPorts()); + List<OFPortDesc> ports = featuresReply.getPorts(); - expect(sw.getId()).andReturn(dpid).anyTimes(); + expect(sw.getOFFactory()).andReturn(OFFactories.getFactory(OFVersion.OF_10)).anyTimes(); + expect(sw.getId()).andReturn(datapathId).anyTimes(); expect(sw.getStringId()).andReturn(dpidString).anyTimes(); - expect(sw.getDescriptionStatistics()) .andReturn(desc).atLeastOnce(); + expect(sw.getSwitchDescription()).andReturn(description).atLeastOnce(); expect(sw.getBuffers()) - .andReturn(featuresReply.getBuffers()).atLeastOnce(); + .andReturn(featuresReply.getNBuffers()).atLeastOnce(); expect(sw.getTables()) - .andReturn(featuresReply.getTables()).atLeastOnce(); + .andReturn(featuresReply.getNTables()).atLeastOnce(); expect(sw.getCapabilities()) .andReturn(featuresReply.getCapabilities()).atLeastOnce(); expect(sw.getActions()) @@ -274,9 +269,9 @@ public class ControllerTest extends FloodlightTestCase { @Test public void testHandleMessagesNoListeners() throws Exception { - IOFSwitch sw = createMock(IOFSwitch.class); - expect(sw.getId()).andReturn(0L).anyTimes(); - expect(sw.getStringId()).andReturn("00:00:00:00:00:00:00").anyTimes(); + IOFSwitch sw = createMock(IOFSwitch.class); + expect(sw.getId()).andReturn(DatapathId.NONE).anyTimes(); + expect(sw.getStringId()).andReturn(DATAPATH_ID_0.toString()).anyTimes(); replay(sw); controller.handleMessage(sw, pi, null); verify(sw); @@ -296,7 +291,7 @@ public class ControllerTest extends FloodlightTestCase { controller.removeOFMessageListeners(OFType.PACKET_IN); IOFSwitch sw = createMock(IOFSwitch.class); - expect(sw.getId()).andReturn(0L).anyTimes(); + expect(sw.getId()).andReturn(DatapathId.NONE).anyTimes(); expect(sw.getStringId()).andReturn("00:00:00:00:00:00:00").anyTimes(); // Setup listener orderings @@ -390,8 +385,7 @@ public class ControllerTest extends FloodlightTestCase { verify(test2); verify(test3); - OFFlowMod fm = (OFFlowMod) - BasicFactory.getInstance().getMessage(OFType.FLOW_MOD); + OFFlowMod fm = (OFFlowMod) factory.buildFlowModify().build(); //------------------ // Test FlowMod handling: all listeners return CONTINUE @@ -425,10 +419,10 @@ public class ControllerTest extends FloodlightTestCase { @Test public void testHandleMessagesSlave() throws Exception { - doSetUp(Role.SLAVE); + doSetUp(HARole.STANDBY); IOFSwitch sw = createMock(IOFSwitch.class); - expect(sw.getId()).andReturn(0L).anyTimes(); - expect(sw.getStringId()).andReturn("00:00:00:00:00:00:00").anyTimes(); + expect(sw.getId()).andReturn(DatapathId.NONE).anyTimes(); + expect(sw.getStringId()).andReturn(DatapathId.NONE.toString()).anyTimes(); IOFMessageListener test1 = createMock(IOFMessageListener.class); expect(test1.getName()).andReturn("test1").atLeastOnce(); @@ -448,7 +442,7 @@ public class ControllerTest extends FloodlightTestCase { //--------------------------------- // transition to Master //-------------------------------- - controller.setRole(Role.MASTER, "FooBar"); + controller.setRole(HARole.ACTIVE, "FooBar"); // transitioned but HA listeneres not yet notified. // message should not be dispatched @@ -473,8 +467,8 @@ public class ControllerTest extends FloodlightTestCase { @Test public void testHandleMessageWithContext() throws Exception { IOFSwitch sw = createMock(IOFSwitch.class); - expect(sw.getId()).andReturn(0L).anyTimes(); - expect(sw.getStringId()).andReturn("00:00:00:00:00:00:00").anyTimes(); + expect(sw.getId()).andReturn(DatapathId.NONE).anyTimes(); + expect(sw.getStringId()).andReturn(DatapathId.NONE.toString()).anyTimes(); IOFMessageListener test1 = createMock(IOFMessageListener.class); expect(test1.getName()).andReturn("test1").anyTimes(); @@ -509,129 +503,16 @@ public class ControllerTest extends FloodlightTestCase { assertArrayEquals(testPacket.serialize(), eth.serialize()); } - - /** - * Test injectMessage and also do some more tests for listener ordering - * and handling of Command.STOP - * @throws Exception - */ - @Test - public void testInjectMessage() throws Exception { - FloodlightContext cntx = new FloodlightContext(); - IOFSwitch sw = createMock(IOFSwitch.class); - expect(sw.getId()).andReturn(0L).anyTimes(); - expect(sw.getStringId()).andReturn("00:00:00:00:00:00:00").anyTimes(); - - // Add listeners - IOFMessageListener test1 = createMock(IOFMessageListener.class); - expect(test1.getName()).andReturn("test1").anyTimes(); - setupListenerOrdering(test1); - - IOFMessageListener test2 = createMock(IOFMessageListener.class); - expect(test2.getName()).andReturn("test2").anyTimes(); - test2.isCallbackOrderingPostreq(OFType.PACKET_IN, "test1"); - expectLastCall().andReturn(true).atLeastOnce(); - setupListenerOrdering(test2); - replay(test1, test2); - controller.addOFMessageListener(OFType.PACKET_IN, test1); - controller.addOFMessageListener(OFType.PACKET_IN, test2); - verify(test1); - verify(test2); - - // Test inject with null switch and no message. Should not work. - reset(test1, test2); - replay(test1, test2, sw); - try { - controller.injectOfMessage(null, pi); - fail("InjectOfMessage should have thrown a NPE"); - } catch (NullPointerException e) { - // expected - } - try { - controller.injectOfMessage(null, pi, cntx); - fail("InjectOfMessage should have thrown a NPE"); - } catch (NullPointerException e) { - // expected - } - try { - controller.injectOfMessage(sw, null); - fail("InjectOfMessage should have thrown a NPE"); - } catch (NullPointerException e) { - // expected - } - try { - controller.injectOfMessage(sw, null, cntx); - fail("InjectOfMessage should have thrown a NPE"); - } catch (NullPointerException e) { - // expected - } - verify(test1); - verify(test2); - verify(sw); - - // - // Test inject with inActive switch. Should not work. - reset(test1, test2, sw); - expect(sw.getId()).andReturn(0L).anyTimes(); - expect(sw.getStringId()).andReturn("00:00:00:00:00:00:00").anyTimes(); - expect(sw.isActive()).andReturn(false).atLeastOnce(); - replay(test1, test2, sw); - assertFalse("Inject should have failed", - controller.injectOfMessage(sw, pi)); - assertFalse("Inject should have failed", - controller.injectOfMessage(sw, pi, cntx)); - verify(test1); - verify(test2); - verify(sw); - - - // Test inject in the "normal" case without context - reset(test1, test2, sw); - expect(sw.getId()).andReturn(0L).anyTimes(); - expect(sw.getStringId()).andReturn("00:00:00:00:00:00:00").anyTimes(); - expect(sw.isActive()).andReturn(true).atLeastOnce(); - expect(test2.receive(same(sw), same(pi) , isA(FloodlightContext.class))) - .andReturn(Command.STOP); - // test1 will not receive any message! - replay(test1, test2, sw); - assertTrue("Inject should have worked", - controller.injectOfMessage(sw, pi)); - verify(test1); - verify(test2); - verify(sw); - - // Test inject in the "normal" case with context - reset(test1, test2, sw); - expect(sw.getId()).andReturn(0L).anyTimes(); - expect(sw.getStringId()).andReturn("00:00:00:00:00:00:00").anyTimes(); - expect(sw.isActive()).andReturn(true).atLeastOnce(); - expect(test2.receive(same(sw), same(pi) , same(cntx))) - .andReturn(Command.STOP); - // test1 will not receive any message! - replay(test1, test2, sw); - assertTrue("Inject should have worked", - controller.injectOfMessage(sw, pi, cntx)); - verify(test1); - verify(test2); - verify(sw); - - Ethernet eth = IFloodlightProviderService.bcStore.get(cntx, - IFloodlightProviderService.CONTEXT_PI_PAYLOAD); - assertArrayEquals(testPacket.serialize(), eth.serialize()); - } - - /** * Test handleOutgoingMessage and also test listener ordering * @throws Exception */ @Test public void testHandleOutgoingMessage() throws Exception { - OFMessage m = BasicFactory.getInstance().getMessage(OFType.ECHO_REQUEST); - FloodlightContext cntx = new FloodlightContext(); - IOFSwitch sw = createMock(IOFSwitch.class); - expect(sw.getId()).andReturn(0L).anyTimes(); - expect(sw.getStringId()).andReturn("00:00:00:00:00:00:00").anyTimes(); + OFMessage m = factory.buildEchoRequest().build(); + IOFSwitchBackend sw = createMock(IOFSwitchBackend.class); + expect(sw.getId()).andReturn(DATAPATH_ID_0).anyTimes(); + expect(sw.getStringId()).andReturn(DATAPATH_ID_0.toString()).anyTimes(); // Add listeners IOFMessageListener test1 = createMock(IOFMessageListener.class); @@ -664,13 +545,13 @@ public class ControllerTest extends FloodlightTestCase { reset(test1, test2, test3); replay(test1, test2, test3, sw); try { - controller.handleOutgoingMessage(null, pi, cntx); + controller.handleOutgoingMessage(null, pi); fail("handleOutgoindMessage should have thrown a NPE"); } catch (NullPointerException e) { // expected } try { - controller.handleOutgoingMessage(sw, null, cntx); + controller.handleOutgoingMessage(sw, null); fail("handleOutgoingMessage should have thrown a NPE"); } catch (NullPointerException e) { // expected @@ -682,15 +563,15 @@ public class ControllerTest extends FloodlightTestCase { // Test the handleOutgoingMessage reset(test1, test2, test3, sw); - expect(sw.getId()).andReturn(0L).anyTimes(); - expect(sw.getStringId()).andReturn("00:00:00:00:00:00:00").anyTimes(); - expect(test2.receive(same(sw), same(m) , same(cntx))) + expect(sw.getId()).andReturn(DATAPATH_ID_0).anyTimes(); + expect(sw.getStringId()).andReturn(DATAPATH_ID_0.toString()).anyTimes(); + expect(test2.receive(same(sw), same(m) , isA(FloodlightContext.class))) .andReturn(Command.STOP); - expect(test3.receive(same(sw), same(m) , same(cntx))) + expect(test3.receive(same(sw), same(m) , isA(FloodlightContext.class))) .andReturn(Command.CONTINUE); // test1 will not receive any message! replay(test1, test2, test3, sw); - controller.handleOutgoingMessage(sw, m, cntx); + controller.handleOutgoingMessage(sw, m); verify(test1); verify(test2); verify(test3); @@ -698,15 +579,15 @@ public class ControllerTest extends FloodlightTestCase { // Test the handleOutgoingMessage with null context reset(test1, test2, test3, sw); - expect(sw.getId()).andReturn(0L).anyTimes(); - expect(sw.getStringId()).andReturn("00:00:00:00:00:00:00").anyTimes(); + expect(sw.getId()).andReturn(DATAPATH_ID_0).anyTimes(); + expect(sw.getStringId()).andReturn(DATAPATH_ID_0.toString()).anyTimes(); expect(test2.receive(same(sw), same(m) , isA(FloodlightContext.class))) .andReturn(Command.STOP); expect(test3.receive(same(sw), same(m) , isA(FloodlightContext.class))) .andReturn(Command.CONTINUE); // test1 will not receive any message! replay(test1, test2, test3, sw); - controller.handleOutgoingMessage(sw, m, null); + controller.handleOutgoingMessage(sw, m); verify(test1); verify(test2); verify(test3); @@ -715,141 +596,20 @@ public class ControllerTest extends FloodlightTestCase { // Test for message without listeners reset(test1, test2, test3, sw); replay(test1, test2, test3, sw); - m = BasicFactory.getInstance().getMessage(OFType.ECHO_REPLY); - controller.handleOutgoingMessage(sw, m, cntx); + m = factory.buildEchoReply().build(); + controller.handleOutgoingMessage(sw, m); verify(test1); verify(test2); verify(test3); verify(sw); } - - @Test - public void testMessageFilterManager() throws Exception { - class MyOFMessageFilterManager extends OFMessageFilterManager { - public MyOFMessageFilterManager(int timer_interval) { - super(); - TIMER_INTERVAL = timer_interval; - } - } - FloodlightModuleContext fmCntx = new FloodlightModuleContext(); - MockFloodlightProvider mfp = new MockFloodlightProvider(); - OFMessageFilterManager mfm = new MyOFMessageFilterManager(100); - MockThreadPoolService mtp = new MockThreadPoolService(); - fmCntx.addService(IOFMessageFilterManagerService.class, mfm); - fmCntx.addService(IFloodlightProviderService.class, mfp); - fmCntx.addService(IThreadPoolService.class, mtp); - String sid = null; - - mfm.init(fmCntx); - mfm.startUp(fmCntx); - - ConcurrentHashMap <String, String> filter; - int i; - - //Adding the filter works -- adds up to the maximum filter size. - for(i=mfm.getMaxFilterSize(); i > 0; --i) { - filter = new ConcurrentHashMap<String,String>(); - filter.put("mac", String.format("00:11:22:33:44:%d%d", i,i)); - sid = mfm.setupFilter(null, filter, 60); - assertTrue(mfm.getNumberOfFilters() == mfm.getMaxFilterSize() - i +1); - } - - // Add one more to see if you can't - filter = new ConcurrentHashMap<String,String>(); - filter.put("mac", "mac2"); - mfm.setupFilter(null, filter, 10*1000); - - assertTrue(mfm.getNumberOfFilters() == mfm.getMaxFilterSize()); - - // Deleting the filter works. - mfm.setupFilter(sid, null, -1); - assertTrue(mfm.getNumberOfFilters() == mfm.getMaxFilterSize()-1); - - // Creating mock switch to which we will send packet out and - IOFSwitch sw = createMock(IOFSwitch.class); - expect(sw.getId()).andReturn(new Long(0)); - - // Mock Packet-in - IPacket testPacket = new Ethernet() - .setSourceMACAddress("00:44:33:22:11:00") - .setDestinationMACAddress("00:11:22:33:44:55") - .setEtherType(Ethernet.TYPE_ARP) - .setPayload( - new ARP() - .setHardwareType(ARP.HW_TYPE_ETHERNET) - .setProtocolType(ARP.PROTO_TYPE_IP) - .setHardwareAddressLength((byte) 6) - .setProtocolAddressLength((byte) 4) - .setOpCode(ARP.OP_REPLY) - .setSenderHardwareAddress(Ethernet.toMACAddress("00:44:33:22:11:00")) - .setSenderProtocolAddress(IPv4.toIPv4AddressBytes("192.168.1.1")) - .setTargetHardwareAddress(Ethernet.toMACAddress("00:11:22:33:44:55")) - .setTargetProtocolAddress(IPv4.toIPv4AddressBytes("192.168.1.2"))); - byte[] testPacketSerialized = testPacket.serialize(); - - // Build the PacketIn - OFPacketIn pi = ((OFPacketIn) BasicFactory.getInstance().getMessage(OFType.PACKET_IN)) - .setBufferId(-1) - .setInPort((short) 1) - .setPacketData(testPacketSerialized) - .setReason(OFPacketInReason.NO_MATCH) - .setTotalLength((short) testPacketSerialized.length); - - // Mock Packet-out - OFPacketOut packetOut = - (OFPacketOut) mockFloodlightProvider.getOFMessageFactory().getMessage(OFType.PACKET_OUT); - packetOut.setBufferId(pi.getBufferId()) - .setInPort(pi.getInPort()); - List<OFAction> poactions = new ArrayList<OFAction>(); - poactions.add(new OFActionOutput(OFPort.OFPP_TABLE.getValue(), (short) 0)); - packetOut.setActions(poactions) - .setActionsLength((short) OFActionOutput.MINIMUM_LENGTH) - .setPacketData(testPacketSerialized) - .setLengthU(OFPacketOut.MINIMUM_LENGTH+packetOut.getActionsLength()+testPacketSerialized.length); - - FloodlightContext cntx = new FloodlightContext(); - IFloodlightProviderService.bcStore.put(cntx, IFloodlightProviderService.CONTEXT_PI_PAYLOAD, (Ethernet) testPacket); - - - // Let's check the listeners. - List <IOFMessageListener> lm; - - // Check to see if all the listeners are active. - lm = mfp.getListeners().get(OFType.PACKET_OUT); - assertTrue(lm.size() == 1); - assertTrue(lm.get(0).equals(mfm)); - - lm = mfp.getListeners().get(OFType.FLOW_MOD); - assertTrue(lm.size() == 1); - assertTrue(lm.get(0).equals(mfm)); - - lm = mfp.getListeners().get(OFType.PACKET_IN); - assertTrue(lm.size() == 1); - assertTrue(lm.get(0).equals(mfm)); - - HashSet<String> matchedFilters; - - // Send a packet in and check if it matches a filter. - matchedFilters = mfm.getMatchedFilters(pi, cntx); - assertTrue(matchedFilters.size() == 1); - - // Send a packet out and check if it matches a filter - matchedFilters = mfm.getMatchedFilters(packetOut, cntx); - assertTrue(matchedFilters.size() == 1); - - // Wait for all filters to be timed out. - Thread.sleep(150); - assertEquals(0, mfm.getNumberOfFilters()); - } - - @Test public void testGetRoleInfoDefault() { RoleInfo info = controller.getRoleInfo(); - assertEquals(Role.MASTER.toString(), info.getRole()); + assertEquals(HARole.ACTIVE, info.getRole()); assertNotNull(info.getRoleChangeDescription()); - assertEquals(Role.MASTER, controller.getRole()); + assertEquals(HARole.ACTIVE, controller.getRole()); // FIXME: RoleInfo's date. but the format is kinda broken } @@ -859,26 +619,19 @@ public class ControllerTest extends FloodlightTestCase { */ @Test public void testChannelHandlerMaster() { - OFChannelHandler h = createMock(OFChannelHandler.class); - - // Add the handler. The controller should call sendRoleRequest - h.sendRoleRequest(Role.MASTER); - expectLastCall().once(); - replay(h); - controller.addSwitchChannelAndSendInitialRole(h); - verify(h); + OFSwitchHandshakeHandler h = createMock(OFSwitchHandshakeHandler.class); // Reassert the role. reset(h); - h.sendRoleRequestIfNotPending(Role.MASTER); + h.sendRoleRequestIfNotPending(OFControllerRole.ROLE_MASTER); replay(h); - controller.reassertRole(h, Role.MASTER); + controller.reassertRole(h, HARole.ACTIVE); verify(h); // reassert a different role: no-op reset(h); replay(h); - controller.reassertRole(h, Role.SLAVE); + controller.reassertRole(h, HARole.STANDBY); verify(h); } @@ -889,56 +642,41 @@ public class ControllerTest extends FloodlightTestCase { */ @Test public void testSetRole() throws Exception { - doSetUp(Role.SLAVE); + doSetUp(HARole.STANDBY); RoleInfo info = controller.getRoleInfo(); - assertEquals(Role.SLAVE.toString(), info.getRole()); - assertEquals(Role.SLAVE, controller.getRole()); + assertEquals(HARole.STANDBY, info.getRole()); + assertEquals(HARole.STANDBY, controller.getRole()); - OFChannelHandler h = createMock(OFChannelHandler.class); - - // Add the channel handler. The controller should call sendRoleRequest - h.sendRoleRequest(Role.SLAVE); - expectLastCall().once(); - replay(h); - controller.addSwitchChannelAndSendInitialRole(h); - verify(h); + OFSwitchHandshakeHandler h = createMock(OFSwitchHandshakeHandler.class); // Reassert the role. reset(h); - h.sendRoleRequestIfNotPending(Role.SLAVE); + h.sendRoleRequestIfNotPending(OFControllerRole.ROLE_SLAVE); replay(h); - controller.reassertRole(h, Role.SLAVE); + controller.reassertRole(h, HARole.STANDBY); verify(h); // reassert a different role: no-op reset(h); replay(h); - controller.reassertRole(h, Role.MASTER); + controller.reassertRole(h, HARole.ACTIVE); verify(h); - // Change role to MASTER - reset(h); - h.sendRoleRequest(Role.MASTER); - expectLastCall().once(); IHAListener listener = createMock(IHAListener.class); expect(listener.getName()).andReturn("foo").anyTimes(); setupListenerOrdering(listener); - listener.transitionToMaster(); + listener.transitionToActive(); expectLastCall().once(); replay(listener); - replay(h); controller.addHAListener(listener); - controller.setRole(Role.MASTER, "FooBar"); + controller.setRole(HARole.ACTIVE, "FooBar"); controller.processUpdateQueueForTesting(); - verify(h); verify(listener); info = controller.getRoleInfo(); - assertEquals(Role.MASTER.toString(), info.getRole()); + assertEquals(HARole.ACTIVE, info.getRole()); assertEquals("FooBar", info.getRoleChangeDescription()); - assertEquals(Role.MASTER, controller.getRole()); - - + assertEquals(HARole.ACTIVE, controller.getRole()); } /** Test other setRole cases: re-setting role to the current role, @@ -946,21 +684,7 @@ public class ControllerTest extends FloodlightTestCase { */ @Test public void testSetRoleOthercases() throws Exception { - doSetUp(Role.SLAVE); - - OFChannelHandler h = createMock(OFChannelHandler.class); - - // Add the channel handler. The controller should call sendRoleRequest - h.sendRoleRequest(Role.SLAVE); - expectLastCall().once(); - replay(h); - controller.addSwitchChannelAndSendInitialRole(h); - verify(h); - - // remove the channel. Nothing should - reset(h); - replay(h); - controller.removeSwitchChannel(h); + doSetUp(HARole.STANDBY); // Create and add the HA listener IHAListener listener = createMock(IHAListener.class); @@ -970,30 +694,17 @@ public class ControllerTest extends FloodlightTestCase { controller.addHAListener(listener); // Set role to slave again. Nothing should happen - controller.setRole(Role.SLAVE, "FooBar"); + controller.setRole(HARole.STANDBY, "FooBar"); controller.processUpdateQueueForTesting(); verify(listener); reset(listener); - listener.transitionToMaster(); + expect(listener.getName()).andReturn("foo").anyTimes(); + listener.transitionToActive(); expectLastCall().once(); replay(listener); - - // set role to equal. Should set to master internally - controller.setRole(Role.EQUAL, "ToEqual"); - controller.processUpdateQueueForTesting(); - verify(listener); - RoleInfo info = controller.getRoleInfo(); - assertEquals(Role.MASTER.toString(), info.getRole()); - assertEquals("ToEqual", info.getRoleChangeDescription()); - assertEquals(Role.MASTER, controller.getRole()); - - - verify(h); // no calls should have happened on h } - - @Test public void testSetRoleNPE() { try { @@ -1004,1432 +715,11 @@ public class ControllerTest extends FloodlightTestCase { //exptected } try { - controller.setRole(Role.MASTER, null); + controller.setRole(HARole.ACTIVE, null); fail("Should have thrown an Exception"); } catch (NullPointerException e) { //exptected } } - - - - - - @Test - /** - * Test switchActivated for a new switch, i.e., a switch that was not - * previously known to the controller cluser. We expect that all - * flow mods are cleared and we expect a switchAdded - */ - public void testNewSwitchActivated() throws Exception { - // We set AlwaysClearFlowsOnSwActivate to false but we still - // expect a clearAllFlowMods() because the AlwaysClearFlowsOnSwActivate - // is only relevant if a switch that was previously known is activated!! - controller.setAlwaysClearFlowsOnSwActivate(false); - - IOFSwitch sw = createMock(IOFSwitch.class); - setupSwitchForAddSwitch(sw, 0L, null, null); - sw.clearAllFlowMods(); - expectLastCall().once(); - - // strict mock. Order of events matters! - IOFSwitchListener listener = createStrictMock(IOFSwitchListener.class); - listener.switchAdded(0L); - expectLastCall().once(); - listener.switchActivated(0L); - expectLastCall().once(); - replay(listener); - controller.addOFSwitchListener(listener); - - replay(sw); - controller.switchActivated(sw); - verify(sw); - assertEquals(sw, controller.getSwitch(0L)); - controller.processUpdateQueueForTesting(); - verify(listener); - - SwitchSyncRepresentation storedSwitch = storeClient.getValue(0L); - assertEquals(createOFFeaturesReply(), storedSwitch.getFeaturesReply()); - assertEquals(createOFDescriptionStatistics(), - storedSwitch.getDescription()); - } - - /** - * Test switchActivated for a new switch while in slave: a no-op - */ - @Test - public void testNewSwitchActivatedWhileSlave() throws Exception { - doSetUp(Role.SLAVE); - IOFSwitch sw = createMock(IOFSwitch.class); - - IOFSwitchListener listener = createMock(IOFSwitchListener.class); - controller.addOFSwitchListener(listener); - - replay(sw, listener); // nothing recorded - controller.switchActivated(sw); - verify(sw); - verify(listener); - } - - - /** - * Create and activate a switch, either completely new or reconnected - * The mocked switch instance will be returned. It wil be reset. - */ - private IOFSwitch doActivateSwitchInt(long dpid, - OFDescriptionStatistics desc, - OFFeaturesReply featuresReply, - boolean clearFlows) - throws Exception { - controller.setAlwaysClearFlowsOnSwActivate(true); - - IOFSwitch sw = createMock(IOFSwitch.class); - if (featuresReply == null) { - featuresReply = createOFFeaturesReply(); - featuresReply.setDatapathId(dpid); - } - if (desc == null) { - desc = createOFDescriptionStatistics(); - } - setupSwitchForAddSwitch(sw, dpid, desc, featuresReply); - if (clearFlows) { - sw.clearAllFlowMods(); - expectLastCall().once(); - } - - replay(sw); - controller.switchActivated(sw); - verify(sw); - assertEquals(sw, controller.getSwitch(dpid)); - // drain updates and ignore - controller.processUpdateQueueForTesting(); - - SwitchSyncRepresentation storedSwitch = storeClient.getValue(dpid); - assertEquals(featuresReply, storedSwitch.getFeaturesReply()); - assertEquals(desc, storedSwitch.getDescription()); - reset(sw); - return sw; - } - - /** - * Create and activate a new switch with the given dpid, features reply - * and description. If description and/or features reply are null we'll - * allocate the default one - * The mocked switch instance will be returned. It wil be reset. - */ - private IOFSwitch doActivateNewSwitch(long dpid, - OFDescriptionStatistics desc, - OFFeaturesReply featuresReply) - throws Exception { - return doActivateSwitchInt(dpid, desc, featuresReply, true); - } - - /** - * Create and activate a switch that's just been disconnected. - * The mocked switch instance will be returned. It wil be reset. - */ - private IOFSwitch doActivateOldSwitch(long dpid, - OFDescriptionStatistics desc, - OFFeaturesReply featuresReply) - throws Exception { - return doActivateSwitchInt(dpid, desc, featuresReply, false); - } - - - /** - * Create a switch sync representation and add it to the store and - * notify the store listener. - * If the description and/or features reply are null, we'll allocate - * the default one - */ - public void doAddSwitchToStore(long dpid, - OFDescriptionStatistics desc, - OFFeaturesReply featuresReply) - throws Exception { - if (featuresReply == null) { - featuresReply = createOFFeaturesReply(); - featuresReply.setDatapathId(dpid); - } - if (desc == null) { - desc = createOFDescriptionStatistics(); - } - - SwitchSyncRepresentation ssr = - new SwitchSyncRepresentation(featuresReply, desc); - storeClient.put(dpid, ssr); - - Iterator<Long> keysToNotify = Collections.singletonList(dpid).iterator(); - controller.getStoreListener().keysModified(keysToNotify, - UpdateType.REMOTE); - } - - /** - * Remove a switch from the sync store and - * notify the store listener. - */ - public void doRemoveSwitchFromStore(long dpid) throws Exception { - storeClient.delete(dpid); - - Iterator<Long> keysToNotify = Collections.singletonList(dpid).iterator(); - controller.getStoreListener().keysModified(keysToNotify, - UpdateType.REMOTE); - } - - - /** (remotely) add switch to store and then remove while master. no-op */ - @Test - public void testAddSwitchToStoreMaster() throws Exception { - IOFSwitchListener listener = createMock(IOFSwitchListener.class); - controller.addOFSwitchListener(listener); - replay(listener); - - //-------------- - // add switch - doAddSwitchToStore(1L, null, null); - controller.processUpdateQueueForTesting(); - IOFSwitch sw = controller.getSwitch(1L); - assertNull("There shouldn't be a switch", sw); - verify(listener); - - //-------------- - // add a real switch - controller.setAlwaysClearFlowsOnSwActivate(true); - sw = createMock(IOFSwitch.class); - setupSwitchForAddSwitch(sw, 1L, null, null); - sw.clearAllFlowMods(); - expectLastCall().once(); - reset(listener); - listener.switchAdded(1L); - expectLastCall().once(); - listener.switchActivated(1L); - expectLastCall().once(); - replay(listener); - replay(sw); - controller.switchActivated(sw); - verify(sw); - assertEquals(sw, controller.getSwitch(1L)); - controller.processUpdateQueueForTesting(); - verify(listener); - - //----------- - // remove switch from store. - reset(listener); - replay(listener); - doRemoveSwitchFromStore(1L); - controller.processUpdateQueueForTesting(); - verify(listener); - assertEquals(sw, controller.getSwitch(1L)); - } - - - /** - * add switch to store then remove it again while slave. - * should get notification and switch should be added and then removed - */ - @Test - public void testAddSwitchRemoveSwitchStoreSlave() throws Exception { - doSetUp(Role.SLAVE); - - IOFSwitchListener listener = createMock(IOFSwitchListener.class); - controller.addOFSwitchListener(listener); - - //------ - // Add switch - listener.switchAdded(1L); - expectLastCall().once(); - replay(listener); - - OFDescriptionStatistics desc = createOFDescriptionStatistics(); - desc.setDatapathDescription("The Switch"); - doAddSwitchToStore(1L, desc, null); - controller.processUpdateQueueForTesting(); - verify(listener); - - IOFSwitch sw = controller.getSwitch(1L); - assertNotNull("Switch should be present", sw); - assertEquals(1L, sw.getId()); - assertFalse("Switch should be inactive", sw.isActive()); - assertEquals("The Switch", - sw.getDescriptionStatistics().getDatapathDescription()); - - //------ - // remove switch - reset(listener); - listener.switchRemoved(1L); - replay(listener); - doRemoveSwitchFromStore(1L); - controller.processUpdateQueueForTesting(); - verify(listener); - assertNull("Switch should not exist anymore", controller.getSwitch(1L)); - } - - /** Add switch to store with inconsistent DPID - * @throws Exception - */ - @Test - public void testInconsistentStoreDpid() throws Exception { - doSetUp(Role.SLAVE); - - IOFSwitchListener listener = createMock(IOFSwitchListener.class); - controller.addOFSwitchListener(listener); - replay(listener); - - - OFFeaturesReply featuresReply = createOFFeaturesReply(); - featuresReply.setDatapathId(42L); - OFDescriptionStatistics desc = createOFDescriptionStatistics(); - SwitchSyncRepresentation ssr = - new SwitchSyncRepresentation(featuresReply, desc); - storeClient.put(1L, ssr); - - Iterator<Long> keysToNotify = Collections.singletonList(1L).iterator(); - controller.getStoreListener().keysModified(keysToNotify, - UpdateType.REMOTE); - controller.processUpdateQueueForTesting(); - verify(listener); - - assertNull("Switch should not have been added", - controller.getSwitch(1L)); - assertNull("Switch should not have been added", - controller.getSwitch(42L)); - } - - - /** - * This test goes through the SLAVE->MASTER program flow. We'll start as - * SLAVE. Add switches to the store while slave, update these switches - * then transition to master, make most (but not all switches) "connect" - * We also check correct behavior of getAllSwitchDpids() and - * getAllSwitchMap() - * - * We also change ports to verify that we receive port changed notifications - * if ports are changes in the sync store or when we transition from - * inactive to active - */ - @Test - public void testSwitchAddWithRoleChangeSomeReconnect() throws Exception { - int consolidateStoreDelayMs = 50; - doSetUp(Role.SLAVE); - - // Add HA Listener - IHAListener haListener = createMock(IHAListener.class); - expect(haListener.getName()).andReturn("foo").anyTimes(); - setupListenerOrdering(haListener); - replay(haListener); - controller.addHAListener(haListener); - verify(haListener); - reset(haListener); - - // Add switch listener - IOFSwitchListener switchListener = createMock(IOFSwitchListener.class); - controller.addOFSwitchListener(switchListener); - - // Add readyForReconcile listener - IReadyForReconcileListener readyForReconcileListener = - createMock(IReadyForReconcileListener.class); - controller.addReadyForReconcileListener(readyForReconcileListener); - - //--------------------------------------- - // Initialization - //--------------------------------------- - - // Switch 1 - // no actual IOFSwitch here because we simply add features reply - // and desc stats to store - OFFeaturesReply fr1a = createOFFeaturesReply(); - fr1a.setDatapathId(1L); - OFPhysicalPort p = createOFPhysicalPort("P1", 1); - ImmutablePort sw1p1 = ImmutablePort.fromOFPhysicalPort(p); - List<OFPhysicalPort> ports1a = Collections.singletonList(p); - fr1a.setPorts(ports1a); - List<ImmutablePort> ports1aImmutable = - ImmutablePort.immutablePortListOf(ports1a); - // an alternative featuers reply - OFFeaturesReply fr1b = createOFFeaturesReply(); - fr1b.setDatapathId(1L); - p = new OFPhysicalPort(); - p = createOFPhysicalPort("P1", 1); // same port as above - List<OFPhysicalPort> ports1b = new ArrayList<OFPhysicalPort>(); - ports1b.add(p); - p = createOFPhysicalPort("P2", 42000); - ImmutablePort sw1p2 = ImmutablePort.fromOFPhysicalPort(p); - ports1b.add(p); - fr1b.setPorts(ports1b); - List<ImmutablePort> ports1bImmutable = - ImmutablePort.immutablePortListOf(ports1b); - - // Switch 2 - // no actual IOFSwitch here because we simply add features reply - // and desc stats to store - OFFeaturesReply fr2a = createOFFeaturesReply(); - fr2a.setDatapathId(2L); - ImmutablePort sw2p1 = sw1p1; - List<OFPhysicalPort> ports2a = new ArrayList<OFPhysicalPort>(ports1a); - fr2a.setPorts(ports2a); - List<ImmutablePort> ports2aImmutable = - ImmutablePort.immutablePortListOf(ports2a); - // an alternative features reply - OFFeaturesReply fr2b = createOFFeaturesReply(); - fr2b.setDatapathId(2L); - p = new OFPhysicalPort(); - p = createOFPhysicalPort("P1", 2); // port number changed - ImmutablePort sw2p1Changed = ImmutablePort.fromOFPhysicalPort(p); - List<OFPhysicalPort> ports2b = Collections.singletonList(p); - fr2b.setPorts(ports2b); - - // Switches 3 and 4 are create with default features reply and desc - // so nothing to do here - - //--------------------------------------- - // Adding switches to store - //--------------------------------------- - - replay(haListener); // nothing should happen to haListener - replay(readyForReconcileListener); // nothing should happen to - // readyForReconcileListener - - // add switch1 with fr1a to store - reset(switchListener); - switchListener.switchAdded(1L); - expectLastCall().once(); - replay(switchListener); - doAddSwitchToStore(1L, null, fr1a); - controller.processUpdateQueueForTesting(); - verify(switchListener); - reset(switchListener); - - IOFSwitch sw = controller.getSwitch(1L); - assertNotNull("Switch should be present", sw); - assertEquals(1L, sw.getId()); - assertFalse("Switch should be inactive", sw.isActive()); - assertEquals(new HashSet<ImmutablePort>(ports1aImmutable), - new HashSet<ImmutablePort>(sw.getPorts())); - - // add switch 2 with fr2a to store - reset(switchListener); - switchListener.switchAdded(2L); - expectLastCall().once(); - replay(switchListener); - doAddSwitchToStore(2L, null, fr2a); - controller.processUpdateQueueForTesting(); - verify(switchListener); - reset(switchListener); - - sw = controller.getSwitch(2L); - assertNotNull("Switch should be present", sw); - assertEquals(2L, sw.getId()); - assertFalse("Switch should be inactive", sw.isActive()); - assertEquals(new HashSet<ImmutablePort>(ports2aImmutable), - new HashSet<ImmutablePort>(sw.getPorts())); - - // add switch 3 to store - reset(switchListener); - switchListener.switchAdded(3L); - expectLastCall().once(); - replay(switchListener); - doAddSwitchToStore(3L, null, null); - controller.processUpdateQueueForTesting(); - verify(switchListener); - reset(switchListener); - - sw = controller.getSwitch(3L); - assertNotNull("Switch should be present", sw); - assertEquals(3L, sw.getId()); - assertFalse("Switch should be inactive", sw.isActive()); - - // add switch 4 to store - reset(switchListener); - switchListener.switchAdded(4L); - expectLastCall().once(); - replay(switchListener); - doAddSwitchToStore(4L, null, null); - controller.processUpdateQueueForTesting(); - verify(switchListener); - reset(switchListener); - - sw = controller.getSwitch(4L); - assertNotNull("Switch should be present", sw); - assertEquals(4L, sw.getId()); - assertFalse("Switch should be inactive", sw.isActive()); - - // update switch 1 with fr1b - reset(switchListener); - switchListener.switchPortChanged(1L, sw1p2, PortChangeType.ADD); - expectLastCall().once(); - replay(switchListener); - doAddSwitchToStore(1L, null, fr1b); - controller.processUpdateQueueForTesting(); - verify(switchListener); - reset(switchListener); - - sw = controller.getSwitch(1L); - assertNotNull("Switch should be present", sw); - assertEquals(1L, sw.getId()); - assertFalse("Switch should be inactive", sw.isActive()); - assertEquals(new HashSet<ImmutablePort>(ports1bImmutable), - new HashSet<ImmutablePort>(sw.getPorts())); - - // Check getAllSwitchDpids() and getAllSwitchMap() - Set<Long> expectedDpids = new HashSet<Long>(); - expectedDpids.add(1L); - expectedDpids.add(2L); - expectedDpids.add(3L); - expectedDpids.add(4L); - assertEquals(expectedDpids, controller.getAllSwitchDpids()); - Map<Long, IOFSwitch> expectedSwitchMap = new HashMap<Long, IOFSwitch>(); - expectedSwitchMap.put(1L, controller.getSwitch(1L)); - expectedSwitchMap.put(2L, controller.getSwitch(2L)); - expectedSwitchMap.put(3L, controller.getSwitch(3L)); - expectedSwitchMap.put(4L, controller.getSwitch(4L)); - assertEquals(expectedSwitchMap, controller.getAllSwitchMap()); - - verify(haListener); - //-------------------------------------- - // Transition to master - //-------------------------------------- - reset(haListener); - haListener.transitionToMaster(); - expectLastCall().once(); - replay(haListener); - controller.setConsolidateStoreTaskDelay(consolidateStoreDelayMs); - controller.setRole(Role.MASTER, "FooBar"); - controller.processUpdateQueueForTesting(); - verify(haListener); - reset(haListener); - replay(haListener); - - //-------------------------------------- - // Activate switches - //-------------------------------------- - - // Activate switch 1 - IOFSwitch sw1 = createMock(IOFSwitch.class); - setupSwitchForAddSwitch(sw1, 1L, null, fr1b); - reset(switchListener); - switchListener.switchActivated(1L); - expectLastCall().once(); - replay(sw1); - replay(switchListener); - controller.switchActivated(sw1); - controller.processUpdateQueueForTesting(); - verify(switchListener); - verify(sw1); - - sw = controller.getSwitch(1L); - assertNotNull("Switch should be present", sw); - assertSame(sw1, sw); // the mock switch should be returned - - // Activate switch 2 with different features reply - // should get portChanged - // also set alwaysClearFlorModOnSwAcitvate to true; - controller.setAlwaysClearFlowsOnSwActivate(true); - IOFSwitch sw2 = createMock(IOFSwitch.class); - setupSwitchForAddSwitch(sw2, 2L, null, fr2b); - sw2.clearAllFlowMods(); - expectLastCall().once(); - reset(switchListener); - switchListener.switchActivated(2L); - expectLastCall().once(); - switchListener.switchPortChanged(2L, sw2p1, PortChangeType.DELETE); - switchListener.switchPortChanged(2L, sw2p1Changed, PortChangeType.ADD); - expectLastCall().once(); - replay(sw2); - replay(switchListener); - controller.switchActivated(sw2); - controller.processUpdateQueueForTesting(); - verify(switchListener); - verify(sw2); - - sw = controller.getSwitch(2L); - assertNotNull("Switch should be present", sw); - assertSame(sw2, sw); // the mock switch should be returned - - - // Do not activate switch 3, but it should still be present - sw = controller.getSwitch(3L); - IOFSwitch sw3 = sw; - assertNotNull("Switch should be present", sw); - assertEquals(3L, sw.getId()); - assertFalse("Switch should be inactive", sw.isActive()); - - // Do not activate switch 4, but it should still be present - sw = controller.getSwitch(4L); - IOFSwitch sw4 = sw; - assertNotNull("Switch should be present", sw); - assertEquals(4L, sw.getId()); - assertFalse("Switch should be inactive", sw.isActive()); - - // Check getAllSwitchDpids() and getAllSwitchMap() - expectedDpids = new HashSet<Long>(); - expectedDpids.add(1L); - expectedDpids.add(2L); - expectedDpids.add(3L); - expectedDpids.add(4L); - assertEquals(expectedDpids, controller.getAllSwitchDpids()); - expectedSwitchMap = new HashMap<Long, IOFSwitch>(); - expectedSwitchMap.put(1L, sw1); - expectedSwitchMap.put(2L, sw2); - expectedSwitchMap.put(3L, sw3); - expectedSwitchMap.put(4L, sw4); - assertEquals(expectedSwitchMap, controller.getAllSwitchMap()); - - // silently remove switch 4 from the store and notify the - // store listener. Since the controller is MASTER it will ignore - // this notification. - reset(switchListener); - replay(switchListener); - doRemoveSwitchFromStore(4L); - controller.processUpdateQueueForTesting(); - verify(switchListener); - // Switch should still be queryable - sw = controller.getSwitch(4L); - assertNotNull("Switch should be present", sw); - assertEquals(4L, sw.getId()); - assertFalse("Switch should be inactive", sw.isActive()); - - //-------------------------------- - // Wait for consolidateStore - //-------------------------------- - verify(readyForReconcileListener); - reset(readyForReconcileListener); - readyForReconcileListener.readyForReconcile(); - replay(readyForReconcileListener); - reset(switchListener); - switchListener.switchRemoved(3L); - switchListener.switchRemoved(4L); - replay(switchListener); - Thread.sleep(2*consolidateStoreDelayMs); - controller.processUpdateQueueForTesting(); - verify(switchListener); - verify(readyForReconcileListener); - reset(readyForReconcileListener); - replay(readyForReconcileListener); - - // Verify the expected switches are all there. no more no less - sw = controller.getSwitch(1L); - assertNotNull("Switch should be present", sw); - assertSame(sw1, sw); // the mock switch should be returned - - sw = controller.getSwitch(2L); - assertNotNull("Switch should be present", sw); - assertSame(sw2, sw); // the mock switch should be returned - - // Do not activate switch 3, but it should still be present - sw = controller.getSwitch(3L); - assertNull("Switch should NOT be present", sw); - - // Check getAllSwitchDpids() and getAllSwitchMap() - expectedDpids = new HashSet<Long>(); - expectedDpids.add(1L); - expectedDpids.add(2L); - assertEquals(expectedDpids, controller.getAllSwitchDpids()); - expectedSwitchMap = new HashMap<Long, IOFSwitch>(); - expectedSwitchMap.put(1L, sw1); - expectedSwitchMap.put(2L, sw2); - assertEquals(expectedSwitchMap, controller.getAllSwitchMap()); - - verify(haListener); - verify(readyForReconcileListener); - } - - /** - * This test goes through the SLAVE->MASTER program flow. We'll start as - * SLAVE. Add switches to the store while slave, update these switches - * then transition to master, make all "connect" - * - * Supplements testSwitchAddWithRoleChangeSomeReconnect() and thus does - * less extensive testing. We are really only interested in verifying - * that we get the readyForReconciliation event before - * consolidateStore runs. - */ - @Test - public void testSwitchAddWithRoleChangeAllReconnect() throws Exception { - int consolidateStoreDelayMs = 50; - doSetUp(Role.SLAVE); - - // Add HA Listener - IHAListener haListener = createMock(IHAListener.class); - expect(haListener.getName()).andReturn("foo").anyTimes(); - setupListenerOrdering(haListener); - replay(haListener); - controller.addHAListener(haListener); - verify(haListener); - reset(haListener); - - // Add switch listener - IOFSwitchListener switchListener = createMock(IOFSwitchListener.class); - controller.addOFSwitchListener(switchListener); - - // Add readyForReconcile listener - IReadyForReconcileListener readyForReconcileListener = - createMock(IReadyForReconcileListener.class); - controller.addReadyForReconcileListener(readyForReconcileListener); - - //--------------------------------------- - // Adding switches to store - //--------------------------------------- - - replay(haListener); // nothing should happen to haListener - replay(readyForReconcileListener); // nothing should happen to - // readyForReconcileListener - - // add switch 1 to store - reset(switchListener); - switchListener.switchAdded(1L); - expectLastCall().once(); - replay(switchListener); - doAddSwitchToStore(1L, null, null); - controller.processUpdateQueueForTesting(); - verify(switchListener); - reset(switchListener); - - IOFSwitch sw = controller.getSwitch(1L); - assertNotNull("Switch should be present", sw); - assertEquals(1L, sw.getId()); - assertFalse("Switch should be inactive", sw.isActive()); - - // add switch 2 to store - reset(switchListener); - switchListener.switchAdded(2L); - expectLastCall().once(); - replay(switchListener); - doAddSwitchToStore(2L, null, null); - controller.processUpdateQueueForTesting(); - verify(switchListener); - reset(switchListener); - - sw = controller.getSwitch(2L); - assertNotNull("Switch should be present", sw); - assertEquals(2L, sw.getId()); - assertFalse("Switch should be inactive", sw.isActive()); - - // Check getAllSwitchDpids() and getAllSwitchMap() - Set<Long> expectedDpids = new HashSet<Long>(); - expectedDpids.add(1L); - expectedDpids.add(2L); - assertEquals(expectedDpids, controller.getAllSwitchDpids()); - Map<Long, IOFSwitch> expectedSwitchMap = new HashMap<Long, IOFSwitch>(); - expectedSwitchMap.put(1L, controller.getSwitch(1L)); - expectedSwitchMap.put(2L, controller.getSwitch(2L)); - assertEquals(expectedSwitchMap, controller.getAllSwitchMap()); - - verify(haListener); - //-------------------------------------- - // Transition to master - //-------------------------------------- - reset(haListener); - haListener.transitionToMaster(); - expectLastCall().once(); - replay(haListener); - controller.setConsolidateStoreTaskDelay(consolidateStoreDelayMs); - controller.setRole(Role.MASTER, "FooBar"); - controller.processUpdateQueueForTesting(); - verify(haListener); - reset(haListener); - replay(haListener); - - //-------------------------------------- - // Activate switches - //-------------------------------------- - - // Activate switch 1 - IOFSwitch sw1 = createMock(IOFSwitch.class); - setupSwitchForAddSwitch(sw1, 1L, null, null); - reset(switchListener); - switchListener.switchActivated(1L); - expectLastCall().once(); - replay(sw1); - replay(switchListener); - controller.switchActivated(sw1); - controller.processUpdateQueueForTesting(); - verify(switchListener); - verify(sw1); - - sw = controller.getSwitch(1L); - assertNotNull("Switch should be present", sw); - assertSame(sw1, sw); // the mock switch should be returned - - // Activate switch 2 - // Since this is the last inactive switch to activate we should - // get the readyForReconcile notifiction - verify(readyForReconcileListener); - reset(readyForReconcileListener); - readyForReconcileListener.readyForReconcile(); - replay(readyForReconcileListener); - controller.setAlwaysClearFlowsOnSwActivate(true); - IOFSwitch sw2 = createMock(IOFSwitch.class); - setupSwitchForAddSwitch(sw2, 2L, null, null); - sw2.clearAllFlowMods(); - expectLastCall().once(); - reset(switchListener); - switchListener.switchActivated(2L); - expectLastCall().once(); - replay(sw2); - replay(switchListener); - controller.switchActivated(sw2); - controller.processUpdateQueueForTesting(); - verify(switchListener); - verify(sw2); - verify(readyForReconcileListener); - - - sw = controller.getSwitch(2L); - assertNotNull("Switch should be present", sw); - assertSame(sw2, sw); // the mock switch should be returned - - - // Check getAllSwitchDpids() and getAllSwitchMap() - expectedDpids = new HashSet<Long>(); - expectedDpids.add(1L); - expectedDpids.add(2L); - assertEquals(expectedDpids, controller.getAllSwitchDpids()); - expectedSwitchMap = new HashMap<Long, IOFSwitch>(); - expectedSwitchMap.put(1L, sw1); - expectedSwitchMap.put(2L, sw2); - assertEquals(expectedSwitchMap, controller.getAllSwitchMap()); - - //-------------------------------- - // Wait for consolidateStore: a no-op - //-------------------------------- - reset(switchListener); - replay(switchListener); - reset(readyForReconcileListener); - replay(readyForReconcileListener); - Thread.sleep(2*consolidateStoreDelayMs); - controller.processUpdateQueueForTesting(); - verify(switchListener); - verify(readyForReconcileListener); - - // Verify the expected switches are all there. no more no less - sw = controller.getSwitch(1L); - assertNotNull("Switch should be present", sw); - assertSame(sw1, sw); // the mock switch should be returned - - sw = controller.getSwitch(2L); - assertNotNull("Switch should be present", sw); - assertSame(sw2, sw); // the mock switch should be returned - - // Check getAllSwitchDpids() and getAllSwitchMap() - expectedDpids = new HashSet<Long>(); - expectedDpids.add(1L); - expectedDpids.add(2L); - assertEquals(expectedDpids, controller.getAllSwitchDpids()); - expectedSwitchMap = new HashMap<Long, IOFSwitch>(); - expectedSwitchMap.put(1L, sw1); - expectedSwitchMap.put(2L, sw2); - assertEquals(expectedSwitchMap, controller.getAllSwitchMap()); - - verify(haListener); - } - - /** - * Disconnect a switch. normal program flow - */ - @Test - private void doTestSwitchConnectReconnect(boolean reconnect) - throws Exception { - IOFSwitch sw = doActivateNewSwitch(1L, null, null); - expect(sw.getId()).andReturn(1L).anyTimes(); - expect(sw.getStringId()).andReturn(HexString.toHexString(1L)).anyTimes(); - sw.cancelAllStatisticsReplies(); - expectLastCall().once(); - IOFSwitchListener listener = createMock(IOFSwitchListener.class); - listener.switchRemoved(1L); - expectLastCall().once(); - controller.addOFSwitchListener(listener); - replay(sw, listener); - controller.switchDisconnected(sw); - controller.processUpdateQueueForTesting(); - verify(sw, listener); - - assertNull(controller.getSwitch(1L)); - assertNull(storeClient.getValue(1L)); - if (reconnect) { - controller.removeOFSwitchListener(listener); - sw = doActivateOldSwitch(1L, null, null); - } - } - - @Test - public void testSwitchDisconnected() throws Exception { - doTestSwitchConnectReconnect(false); - } - - /** - * Disconnect a switch and reconnect, verify no clearAllFlowmods() - */ - @Test - public void testSwitchReconnect() throws Exception { - doTestSwitchConnectReconnect(true); - } - - /** - * Remove a nonexisting switch. should be ignored - */ - @Test - public void testNonexistingSwitchDisconnected() throws Exception { - IOFSwitch sw = createMock(IOFSwitch.class); - expect(sw.getId()).andReturn(1L).anyTimes(); - expect(sw.getStringId()).andReturn(HexString.toHexString(1L)).anyTimes(); - IOFSwitchListener listener = createMock(IOFSwitchListener.class); - controller.addOFSwitchListener(listener); - replay(sw, listener); - controller.switchDisconnected(sw); - controller.processUpdateQueueForTesting(); - verify(sw, listener); - - assertNull(controller.getSwitch(1L)); - assertNull(storeClient.getValue(1L)); - } - - /** - * Try to remove a switch that's different from what's in the active - * switch map. Should be ignored - */ - @Test - public void testSwitchDisconnectedOther() throws Exception { - IOFSwitch origSw = doActivateNewSwitch(1L, null, null); - // create a new mock switch - IOFSwitch sw = createMock(IOFSwitch.class); - expect(sw.getId()).andReturn(1L).anyTimes(); - expect(sw.getStringId()).andReturn(HexString.toHexString(1L)).anyTimes(); - IOFSwitchListener listener = createMock(IOFSwitchListener.class); - controller.addOFSwitchListener(listener); - replay(sw, listener); - controller.switchDisconnected(sw); - controller.processUpdateQueueForTesting(); - verify(sw, listener); - - assertSame(origSw, controller.getSwitch(1L)); - assertNotNull(storeClient.getValue(1L)); - } - - - - /** - * Try to activate a switch that's already active (which can happen if - * two different switches have the same DPIP or if a switch reconnects - * while the old TCP connection is still alive - */ - @Test - public void testSwitchActivatedWithAlreadyActiveSwitch() throws Exception { - OFDescriptionStatistics oldDesc = createOFDescriptionStatistics(); - oldDesc.setDatapathDescription("Ye Olde Switch"); - OFDescriptionStatistics newDesc = createOFDescriptionStatistics(); - newDesc.setDatapathDescription("The new Switch"); - OFFeaturesReply featuresReply = createOFFeaturesReply(); - - - // Setup: add a switch to the controller - IOFSwitch oldsw = createMock(IOFSwitch.class); - setupSwitchForAddSwitch(oldsw, 0L, oldDesc, featuresReply); - oldsw.clearAllFlowMods(); - expectLastCall().once(); - replay(oldsw); - controller.switchActivated(oldsw); - verify(oldsw); - // drain the queue, we don't care what's in it - controller.processUpdateQueueForTesting(); - assertEquals(oldsw, controller.getSwitch(0L)); - - // Now the actual test: add a new switch with the same dpid to - // the controller - reset(oldsw); - expect(oldsw.getId()).andReturn(0L).anyTimes(); - oldsw.cancelAllStatisticsReplies(); - expectLastCall().once(); - oldsw.disconnectOutputStream(); - expectLastCall().once(); - - - IOFSwitch newsw = createMock(IOFSwitch.class); - setupSwitchForAddSwitch(newsw, 0L, newDesc, featuresReply); - newsw.clearAllFlowMods(); - expectLastCall().once(); - - // Strict mock. We need to get the removed notification before the - // add notification - IOFSwitchListener listener = createStrictMock(IOFSwitchListener.class); - listener.switchRemoved(0L); - listener.switchAdded(0L); - listener.switchActivated(0L); - replay(listener); - controller.addOFSwitchListener(listener); - - - replay(newsw, oldsw); - controller.switchActivated(newsw); - verify(newsw, oldsw); - - assertEquals(newsw, controller.getSwitch(0L)); - controller.processUpdateQueueForTesting(); - verify(listener); - } - - - - /** - * Tests that you can't remove a switch from the map returned by - * getSwitches() (because getSwitches should return an unmodifiable - * map) - */ - @Test - public void testRemoveActiveSwitch() { - IOFSwitch sw = createNiceMock(IOFSwitch.class); - setupSwitchForAddSwitch(sw, 1L, null, null); - replay(sw); - getController().switchActivated(sw); - assertEquals(sw, getController().getSwitch(1L)); - getController().getAllSwitchMap().remove(1L); - assertEquals(sw, getController().getSwitch(1L)); - verify(sw); - // we don't care for updates. drain queue. - controller.processUpdateQueueForTesting(); - } - - - - /** - * Test that notifyPortChanged() results in an IOFSwitchListener - * update and that its arguments are passed through to - * the listener call - */ - @Test - public void testNotifySwitchPoArtChanged() throws Exception { - long dpid = 42L; - - OFFeaturesReply fr1 = createOFFeaturesReply(); - fr1.setDatapathId(dpid); - OFPhysicalPort p1 = createOFPhysicalPort("Port1", 1); - fr1.setPorts(Collections.singletonList(p1)); - - OFFeaturesReply fr2 = createOFFeaturesReply(); - fr1.setDatapathId(dpid); - OFPhysicalPort p2 = createOFPhysicalPort("Port1", 1); - p2.setAdvertisedFeatures(0x2); // just some bogus values - fr2.setPorts(Collections.singletonList(p2)); - - OFDescriptionStatistics desc = createOFDescriptionStatistics(); - - // activate switch - IOFSwitch sw = doActivateNewSwitch(dpid, desc, fr1); - - // check the store - SwitchSyncRepresentation ssr = storeClient.getValue(dpid); - assertNotNull(ssr); - assertEquals(dpid, ssr.getDpid()); - assertEquals(1, ssr.getPorts().size()); - assertEquals(p1, ssr.getPorts().get(0).toOFPhysicalPort()); - - IOFSwitchListener listener = createMock(IOFSwitchListener.class); - controller.addOFSwitchListener(listener); - // setup switch with the new, second features reply (and thus ports) - setupSwitchForAddSwitch(sw, dpid, desc, fr2); - listener.switchPortChanged(dpid, ImmutablePort.fromOFPhysicalPort(p2), - PortChangeType.OTHER_UPDATE); - expectLastCall().once(); - replay(listener); - replay(sw); - controller.notifyPortChanged(sw, ImmutablePort.fromOFPhysicalPort(p2), - PortChangeType.OTHER_UPDATE); - controller.processUpdateQueueForTesting(); - verify(listener); - verify(sw); - - // check the store - ssr = storeClient.getValue(dpid); - assertNotNull(ssr); - assertEquals(dpid, ssr.getDpid()); - assertEquals(1, ssr.getPorts().size()); - assertEquals(p2, ssr.getPorts().get(0).toOFPhysicalPort()); - } - - private Map<String,Object> getFakeControllerIPRow(String id, String controllerId, - String type, int number, String discoveredIP ) { - HashMap<String, Object> row = new HashMap<String,Object>(); - row.put(Controller.CONTROLLER_INTERFACE_ID, id); - row.put(Controller.CONTROLLER_INTERFACE_CONTROLLER_ID, controllerId); - row.put(Controller.CONTROLLER_INTERFACE_TYPE, type); - row.put(Controller.CONTROLLER_INTERFACE_NUMBER, number); - row.put(Controller.CONTROLLER_INTERFACE_DISCOVERED_IP, discoveredIP); - return row; - } - - /** - * Test notifications for controller node IP changes. This requires - * synchronization between the main test thread and another thread - * that runs Controller's main loop and takes / handles updates. We - * synchronize with wait(timeout) / notifyAll(). We check for the - * expected condition after the wait returns. However, if wait returns - * due to the timeout (or due to spurious awaking) and the check fails we - * might just not have waited long enough. Using a long enough timeout - * mitigates this but we cannot get rid of the fundamental "issue". - * - * @throws Exception - */ - @Test - public void testControllerNodeIPChanges() throws Exception { - class DummyHAListener implements IHAListener { - public Map<String, String> curControllerNodeIPs; - public Map<String, String> addedControllerNodeIPs; - public Map<String, String> removedControllerNodeIPs; - public int nCalled; - - public DummyHAListener() { - this.nCalled = 0; - } - - @Override - public synchronized void controllerNodeIPsChanged( - Map<String, String> curControllerNodeIPs, - Map<String, String> addedControllerNodeIPs, - Map<String, String> removedControllerNodeIPs) { - this.curControllerNodeIPs = curControllerNodeIPs; - this.addedControllerNodeIPs = addedControllerNodeIPs; - this.removedControllerNodeIPs = removedControllerNodeIPs; - this.nCalled++; - notifyAll(); - } - - public void do_assert(int nCalled, - Map<String, String> curControllerNodeIPs, - Map<String, String> addedControllerNodeIPs, - Map<String, String> removedControllerNodeIPs) { - assertEquals("nCalled is not as expected", nCalled, this.nCalled); - assertEquals("curControllerNodeIPs is not as expected", - curControllerNodeIPs, this.curControllerNodeIPs); - assertEquals("addedControllerNodeIPs is not as expected", - addedControllerNodeIPs, this.addedControllerNodeIPs); - assertEquals("removedControllerNodeIPs is not as expected", - removedControllerNodeIPs, this.removedControllerNodeIPs); - - } - - @Override - public String getName() { - return null; - } - - @Override - public boolean - isCallbackOrderingPrereq(HAListenerTypeMarker type, - String name) { - return false; - } - - @Override - public boolean - isCallbackOrderingPostreq(HAListenerTypeMarker type, - String name) { - return false; - } - - @Override - public void transitionToMaster() { - } - } - DummyHAListener listener = new DummyHAListener(); - HashMap<String,String> expectedCurMap = new HashMap<String, String>(); - HashMap<String,String> expectedAddedMap = new HashMap<String, String>(); - HashMap<String,String> expectedRemovedMap = new HashMap<String, String>(); - - controller.addHAListener(listener); - - synchronized(listener) { - // Insert a first entry - controller.getStorageSourceService() - .insertRow(Controller.CONTROLLER_INTERFACE_TABLE_NAME, - getFakeControllerIPRow("row1", "c1", "Ethernet", 0, "1.1.1.1")); - expectedCurMap.clear(); - expectedAddedMap.clear(); - expectedRemovedMap.clear(); - expectedCurMap.put("c1", "1.1.1.1"); - expectedAddedMap.put("c1", "1.1.1.1"); - controller.processUpdateQueueForTesting(); - listener.do_assert(1, expectedCurMap, expectedAddedMap, expectedRemovedMap); - - // Add an interface that we want to ignore. - controller.getStorageSourceService() - .insertRow(Controller.CONTROLLER_INTERFACE_TABLE_NAME, - getFakeControllerIPRow("row2", "c1", "Ethernet", 1, "1.1.1.2")); - // TODO: do a different check. This call will have to wait for the timeout - controller.processUpdateQueueForTesting(); - assertTrue("controllerNodeIPsChanged() should not have been called here", - listener.nCalled == 1); - - // Add another entry - controller.getStorageSourceService() - .insertRow(Controller.CONTROLLER_INTERFACE_TABLE_NAME, - getFakeControllerIPRow("row3", "c2", "Ethernet", 0, "2.2.2.2")); - expectedCurMap.clear(); - expectedAddedMap.clear(); - expectedRemovedMap.clear(); - expectedCurMap.put("c1", "1.1.1.1"); - expectedCurMap.put("c2", "2.2.2.2"); - expectedAddedMap.put("c2", "2.2.2.2"); - controller.processUpdateQueueForTesting(); - listener.do_assert(2, expectedCurMap, expectedAddedMap, expectedRemovedMap); - - - // Update an entry - controller.getStorageSourceService() - .updateRow(Controller.CONTROLLER_INTERFACE_TABLE_NAME, - "row3", getFakeControllerIPRow("row3", "c2", "Ethernet", 0, "2.2.2.3")); - expectedCurMap.clear(); - expectedAddedMap.clear(); - expectedRemovedMap.clear(); - expectedCurMap.put("c1", "1.1.1.1"); - expectedCurMap.put("c2", "2.2.2.3"); - expectedAddedMap.put("c2", "2.2.2.3"); - expectedRemovedMap.put("c2", "2.2.2.2"); - controller.processUpdateQueueForTesting(); - listener.do_assert(3, expectedCurMap, expectedAddedMap, expectedRemovedMap); - - // Delete an entry - controller.getStorageSourceService() - .deleteRow(Controller.CONTROLLER_INTERFACE_TABLE_NAME, "row3"); - expectedCurMap.clear(); - expectedAddedMap.clear(); - expectedRemovedMap.clear(); - expectedCurMap.put("c1", "1.1.1.1"); - expectedRemovedMap.put("c2", "2.2.2.3"); - controller.processUpdateQueueForTesting(); - listener.do_assert(4, expectedCurMap, expectedAddedMap, expectedRemovedMap); - } - } - - @Test - public void testGetControllerNodeIPs() { - HashMap<String,String> expectedCurMap = new HashMap<String, String>(); - - controller.getStorageSourceService() - .insertRow(Controller.CONTROLLER_INTERFACE_TABLE_NAME, - getFakeControllerIPRow("row1", "c1", "Ethernet", 0, "1.1.1.1")); - controller.getStorageSourceService() - .insertRow(Controller.CONTROLLER_INTERFACE_TABLE_NAME, - getFakeControllerIPRow("row2", "c1", "Ethernet", 1, "1.1.1.2")); - controller.getStorageSourceService() - .insertRow(Controller.CONTROLLER_INTERFACE_TABLE_NAME, - getFakeControllerIPRow("row3", "c2", "Ethernet", 0, "2.2.2.2")); - expectedCurMap.put("c1", "1.1.1.1"); - expectedCurMap.put("c2", "2.2.2.2"); - assertEquals("expectedControllerNodeIPs is not as expected", - expectedCurMap, controller.getControllerNodeIPs()); - // we don't care for updates. drain update queue - controller.processUpdateQueueForTesting(); - } - - - /** - * Test the driver registry: test the bind order - */ - @Test - public void testSwitchDriverRegistryBindOrder() { - IOFSwitchDriver driver1 = createMock(IOFSwitchDriver.class); - IOFSwitchDriver driver2 = createMock(IOFSwitchDriver.class); - IOFSwitchDriver driver3 = createMock(IOFSwitchDriver.class); - IOFSwitch returnedSwitch = null; - IOFSwitch mockSwitch = createMock(IOFSwitch.class); - controller.addOFSwitchDriver("", driver3); - controller.addOFSwitchDriver("test switch", driver1); - controller.addOFSwitchDriver("test", driver2); - - replay(driver1); - replay(driver2); - replay(driver3); - replay(mockSwitch); - - OFDescriptionStatistics desc = createOFDescriptionStatistics(); - desc.setManufacturerDescription("test switch"); - desc.setHardwareDescription("version 0.9"); - reset(driver1); - reset(driver2); - reset(driver3); - reset(mockSwitch); - mockSwitch.setSwitchProperties(desc); - expectLastCall().once(); - expect(driver1.getOFSwitchImpl(desc)).andReturn(mockSwitch).once(); - replay(driver1); - replay(driver2); - replay(driver3); - replay(mockSwitch); - returnedSwitch = controller.getOFSwitchInstance(desc); - assertSame(mockSwitch, returnedSwitch); - verify(driver1); - verify(driver2); - verify(driver3); - verify(mockSwitch); - - desc = createOFDescriptionStatistics(); - desc.setManufacturerDescription("testFooBar"); - desc.setHardwareDescription("version 0.9"); - reset(driver1); - reset(driver2); - reset(driver3); - reset(mockSwitch); - mockSwitch.setSwitchProperties(desc); - expectLastCall().once(); - expect(driver2.getOFSwitchImpl(desc)).andReturn(mockSwitch).once(); - replay(driver1); - replay(driver2); - replay(driver3); - replay(mockSwitch); - returnedSwitch = controller.getOFSwitchInstance(desc); - assertSame(mockSwitch, returnedSwitch); - verify(driver1); - verify(driver2); - verify(driver3); - verify(mockSwitch); - - desc = createOFDescriptionStatistics(); - desc.setManufacturerDescription("FooBar"); - desc.setHardwareDescription("version 0.9"); - reset(driver1); - reset(driver2); - reset(driver3); - reset(mockSwitch); - mockSwitch.setSwitchProperties(desc); - expectLastCall().once(); - expect(driver3.getOFSwitchImpl(desc)).andReturn(mockSwitch).once(); - replay(driver1); - replay(driver2); - replay(driver3); - replay(mockSwitch); - returnedSwitch = controller.getOFSwitchInstance(desc); - assertSame(mockSwitch, returnedSwitch); - verify(driver1); - verify(driver2); - verify(driver3); - verify(mockSwitch); - } - - /** - * Test SwitchDriverRegistry - * Test fallback to default if no switch driver is registered for a - * particular prefix - */ - @Test - public void testSwitchDriverRegistryNoDriver() { - IOFSwitchDriver driver = createMock(IOFSwitchDriver.class); - IOFSwitch returnedSwitch = null; - IOFSwitch mockSwitch = createMock(IOFSwitch.class); - controller.addOFSwitchDriver("test switch", driver); - - replay(driver); - replay(mockSwitch); - - OFDescriptionStatistics desc = createOFDescriptionStatistics(); - desc.setManufacturerDescription("test switch"); - desc.setHardwareDescription("version 0.9"); - reset(driver); - reset(mockSwitch); - mockSwitch.setSwitchProperties(desc); - expectLastCall().once(); - expect(driver.getOFSwitchImpl(desc)).andReturn(mockSwitch).once(); - replay(driver); - replay(mockSwitch); - returnedSwitch = controller.getOFSwitchInstance(desc); - assertSame(mockSwitch, returnedSwitch); - verify(driver); - verify(mockSwitch); - - - desc = createOFDescriptionStatistics(); - desc.setManufacturerDescription("Foo Bar test switch"); - desc.setHardwareDescription("version 0.9"); - reset(driver); - reset(mockSwitch); - replay(driver); - replay(mockSwitch); - returnedSwitch = controller.getOFSwitchInstance(desc); - assertNotNull(returnedSwitch); - assertTrue("Returned switch should be OFSwitchImpl", - returnedSwitch instanceof OFSwitchImpl); - assertEquals(desc, returnedSwitch.getDescriptionStatistics()); - verify(driver); - verify(mockSwitch); - } - - /** - * - */ - @Test - public void testDriverRegistryExceptions() { - IOFSwitchDriver driver = createMock(IOFSwitchDriver.class); - IOFSwitchDriver driver2 = createMock(IOFSwitchDriver.class); - replay(driver, driver2); // no calls expected on driver - - //--------------- - // Test exception handling when registering driver - try { - controller.addOFSwitchDriver("foobar", null); - fail("Expected NullPointerException not thrown"); - } catch (NullPointerException e) { - //expected - } - - try { - controller.addOFSwitchDriver(null, driver); - fail("Expected NullPointerException not thrown"); - } catch (NullPointerException e) { - //expected - } - - // test that we can register each prefix only once! - controller.addOFSwitchDriver("foobar", driver); - try { - controller.addOFSwitchDriver("foobar", driver); - fail("Expected IllegalStateException not thrown"); - } catch (IllegalStateException e) { - //expected - } - - try { - controller.addOFSwitchDriver("foobar", driver2); - fail("Expected IllegalStateException not thrown"); - } catch (IllegalStateException e) { - //expected - } - - OFDescriptionStatistics desc = createOFDescriptionStatistics(); - - desc.setDatapathDescription(null); - try { - controller.getOFSwitchInstance(desc); - fail("Expected NullPointerException not thrown"); - } catch (NullPointerException e) { - //expected - } - desc.setHardwareDescription(null); - try { - controller.getOFSwitchInstance(desc); - fail("Expected NullPointerException not thrown"); - } catch (NullPointerException e) { - //expected - } - desc.setManufacturerDescription(null); - try { - controller.getOFSwitchInstance(desc); - fail("Expected NullPointerException not thrown"); - } catch (NullPointerException e) { - //expected - } - desc.setSerialNumber(null); - try { - controller.getOFSwitchInstance(desc); - fail("Expected NullPointerException not thrown"); - } catch (NullPointerException e) { - //expected - } - desc.setSoftwareDescription(null); - try { - controller.getOFSwitchInstance(desc); - fail("Expected NullPointerException not thrown"); - } catch (NullPointerException e) { - //expected - } - verify(driver, driver2); - } - } diff --git a/src/test/java/net/floodlightcontroller/core/internal/MockOFConnection.java b/src/test/java/net/floodlightcontroller/core/internal/MockOFConnection.java new file mode 100644 index 0000000000000000000000000000000000000000..64631b78afd5cfe93e065ddac678717aec507579 --- /dev/null +++ b/src/test/java/net/floodlightcontroller/core/internal/MockOFConnection.java @@ -0,0 +1,217 @@ +package net.floodlightcontroller.core.internal; + +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import java.util.Date; +import net.floodlightcontroller.core.IOFConnectionBackend; +import org.projectfloodlight.openflow.protocol.OFFactory; +import org.projectfloodlight.openflow.protocol.OFMessage; +import org.projectfloodlight.openflow.protocol.OFRequest; +import org.projectfloodlight.openflow.protocol.OFStatsReply; +import org.projectfloodlight.openflow.protocol.OFStatsRequest; +import org.projectfloodlight.openflow.types.DatapathId; +import org.projectfloodlight.openflow.types.OFAuxId; + +import com.google.common.collect.Iterables; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.SettableFuture; + +public class MockOFConnection implements IOFConnectionBackend { + + private final DatapathId id; + private final OFAuxId auxId; + private Date connectedSince; + private boolean connected; + private SocketAddress localInetAddress, remoteInetAddress; + private OFFactory factory; + private final List<OFMessage> messages; + private final Map<Long, RequestAndFuture<?>>requests; + private IOFConnectionListener listener; + + public MockOFConnection(DatapathId id, OFAuxId auxId){ + this.id = id; + this.auxId = auxId; + + this.setDefaultAddresses(); + this.messages = new ArrayList<>(); + this.requests = new HashMap<>(); + } + + private void setDefaultAddresses() { + SocketAddress socketAddress = null; + try { + byte[] addressBytes = {1, 1, 1, (byte)(this.id.getLong()%255)}; + InetAddress inetAddress = InetAddress.getByAddress(addressBytes); + socketAddress = new InetSocketAddress(inetAddress, 7847); + } + catch (Exception e) { + throw new RuntimeException(e); + } + + this.remoteInetAddress = socketAddress; + + socketAddress = null; + try { + byte[] addressBytes = {127, 0, 0, 1}; + InetAddress inetAddress = InetAddress.getByAddress(addressBytes); + socketAddress = new InetSocketAddress(inetAddress, 7847); + } + catch (Exception e) { + throw new RuntimeException(e); + } + + this.localInetAddress = socketAddress; + } + + @Override + public void write(OFMessage m) { + messages.add(m); + } + + @Override + public void write(Iterable<OFMessage> msglist) { + Iterables.addAll(messages, msglist); + } + + static class RequestAndFuture<R extends OFMessage> { + final OFRequest<R> request; + final SettableFuture<R> replyFuture; + + public RequestAndFuture(OFRequest<R> request) { + this.request = request; + this.replyFuture = SettableFuture.create(); + } + + public OFRequest<R> getRequest() { + return request; + } + + public SettableFuture<R> getReplyFuture() { + return replyFuture; + } + + } + + @Override + public <R extends OFMessage> ListenableFuture<R> + writeRequest(OFRequest<R> request) { + RequestAndFuture<R> raf = new RequestAndFuture<>(request); + messages.add(request); + requests.put(request.getXid(), raf); + return raf.getReplyFuture(); + } + + @Override + public <REPLY extends OFStatsReply> ListenableFuture<List<REPLY>> + writeStatsRequest(OFStatsRequest<REPLY> request) { + return null; + } + + public void setConnectedSince(Date connectedSince) { + this.connectedSince = connectedSince; + } + + @Override + public Date getConnectedSince() { + return this.connectedSince; + } + + @Override + public void flush() { + // no op + } + + @Override + public DatapathId getDatapathId() { + return this.id; + } + + @Override + public OFAuxId getAuxId() { + return this.auxId; + } + + public void setRemoteInetAddress(SocketAddress address){ + this.remoteInetAddress = address; + } + + @Override + public SocketAddress getRemoteInetAddress() { + return this.remoteInetAddress; + } + + public void setLocalInetAddress(SocketAddress address){ + this.localInetAddress = address; + } + + @Override + public SocketAddress getLocalInetAddress() { + return this.localInetAddress; + } + + public void setOFFactory(OFFactory factory) { + this.factory = factory; + } + + @Override + public OFFactory getOFFactory() { + return this.factory; + } + + @Override + public void disconnect() { + this.connected = false; + } + + @Override + public void cancelAllPendingRequests() { + // no op + } + + @Override + public boolean isWritable() { + return true; + } + + @Override + public boolean isConnected() { + return this.connected; + } + + public void setConnected(boolean conection) { + this.connected = true; + } + + @Override + public void setListener(IOFConnectionListener listener) { + this.listener = listener; + } + + // for interacting with the action + public List<OFMessage> getMessages() { + return messages; + } + + public Map<Long, RequestAndFuture<?>> getRequests() { + return requests; + } + + public IOFConnectionListener getListener() { + return listener; + } + + public void clearMessages() { + this.messages.clear(); + this.requests.clear(); + } + + public OFMessage retrieveMessage() { + return this.messages.remove(0); + } +} diff --git a/src/test/java/net/floodlightcontroller/core/internal/MockOFSwitchImpl.java b/src/test/java/net/floodlightcontroller/core/internal/MockOFSwitchImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..846f4f7b82ad89a557f6b30aaf0b1ddacd6f1675 --- /dev/null +++ b/src/test/java/net/floodlightcontroller/core/internal/MockOFSwitchImpl.java @@ -0,0 +1,71 @@ +package net.floodlightcontroller.core.internal; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +import org.easymock.EasyMock; +import net.floodlightcontroller.core.OFSwitch; +import org.projectfloodlight.openflow.protocol.OFCapabilities; +import org.projectfloodlight.openflow.protocol.OFFactories; +import org.projectfloodlight.openflow.protocol.OFStatsReply; +import org.projectfloodlight.openflow.protocol.OFStatsRequest; +import org.projectfloodlight.openflow.protocol.OFStatsType; +import org.projectfloodlight.openflow.protocol.OFVersion; + +import com.google.common.util.concurrent.ListenableFuture; + +/** + * A sublcass of OFSwitchImpl that contains extra setters. + * This class can be used for testing. + * @author alexreimers + * + */ +public class MockOFSwitchImpl extends OFSwitch { + protected Map<OFStatsType, List<OFStatsReply>> statsMap; + + public MockOFSwitchImpl(MockOFConnection connection) { + super(connection, OFFactories.getFactory(OFVersion.OF_10), + EasyMock.createMock(IOFSwitchManager.class), connection.getDatapathId()); + statsMap = new HashMap<OFStatsType, List<OFStatsReply>>(); + } + + public void setBuffers(int buffers) { + this.buffers = buffers; + } + + public void setCapabilities(Set<OFCapabilities> cap) { + this.capabilities = cap; + } + + public void setAttributes(Map<Object, Object> attrs) { + this.attributes.putAll(attrs); + } + + + @SuppressWarnings("unchecked") + @Override + public <REPLY extends OFStatsReply> ListenableFuture<List<REPLY>> writeStatsRequest(OFStatsRequest<REPLY> request) { + ListenableFuture<List<REPLY>> ofStatsFuture = + EasyMock.createNiceMock(ListenableFuture.class); + + // We create a mock future and return info from the map + try { + OFStatsType statsType = request.getStatsType(); + List<REPLY> replies = (List<REPLY>) statsMap.get(statsType); + EasyMock.expect(ofStatsFuture.get(EasyMock.anyLong(), + EasyMock.anyObject(TimeUnit.class))).andReturn(replies).anyTimes(); + EasyMock.expect(ofStatsFuture.get()).andReturn(replies).anyTimes(); + EasyMock.replay(ofStatsFuture); + } catch (Exception e) { + throw new RuntimeException(e); + } + return ofStatsFuture; + } + + public void addStatsRequest(OFStatsType type, List<OFStatsReply> reply) { + statsMap.put(type, reply); + } +} diff --git a/src/test/java/net/floodlightcontroller/core/internal/OFChannelHandlerTest.java b/src/test/java/net/floodlightcontroller/core/internal/OFChannelHandlerTest.java deleted file mode 100644 index ed01868b0035eeae062f8f8f17afa2930a3c5ed2..0000000000000000000000000000000000000000 --- a/src/test/java/net/floodlightcontroller/core/internal/OFChannelHandlerTest.java +++ /dev/null @@ -1,1419 +0,0 @@ -package net.floodlightcontroller.core.internal; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Set; - -import net.floodlightcontroller.core.FloodlightContext; -import net.floodlightcontroller.core.IOFSwitch; -import net.floodlightcontroller.core.IFloodlightProviderService.Role; -import net.floodlightcontroller.core.IOFSwitch.PortChangeEvent; -import net.floodlightcontroller.core.IOFSwitch.PortChangeType; -import net.floodlightcontroller.core.ImmutablePort; -import net.floodlightcontroller.debugcounter.DebugCounter; -import net.floodlightcontroller.debugcounter.IDebugCounterService; -import net.floodlightcontroller.debugcounter.IDebugCounterService.CounterException; -import net.floodlightcontroller.storage.IResultSet; -import net.floodlightcontroller.storage.IStorageSourceService; -import net.floodlightcontroller.threadpool.IThreadPoolService; -import net.floodlightcontroller.util.OrderedCollection; -import net.floodlightcontroller.util.LinkedHashSetWrapper; - -import org.easymock.Capture; -import org.easymock.CaptureType; -import org.easymock.EasyMock; -import org.jboss.netty.channel.Channel; -import org.jboss.netty.channel.ChannelHandlerContext; -import org.jboss.netty.channel.ChannelPipeline; -import org.jboss.netty.channel.ChannelStateEvent; -import org.jboss.netty.channel.ExceptionEvent; -import org.jboss.netty.channel.MessageEvent; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.openflow.protocol.OFError; -import org.openflow.protocol.OFError.OFBadRequestCode; -import org.openflow.protocol.OFError.OFErrorType; -import org.openflow.protocol.OFFeaturesReply; -import org.openflow.protocol.OFGetConfigReply; -import org.openflow.protocol.OFMessage; -import org.openflow.protocol.OFPacketIn; -import org.openflow.protocol.OFPhysicalPort; -import org.openflow.protocol.OFPortStatus; -import org.openflow.protocol.OFPortStatus.OFPortReason; -import org.openflow.protocol.OFSetConfig; -import org.openflow.protocol.OFStatisticsReply; -import org.openflow.protocol.OFStatisticsRequest; -import org.openflow.protocol.OFType; -import org.openflow.protocol.OFVendor; -import org.openflow.protocol.factory.BasicFactory; -import org.openflow.protocol.statistics.OFDescriptionStatistics; -import org.openflow.protocol.statistics.OFStatisticsType; -import org.openflow.util.HexString; -import org.openflow.vendor.nicira.OFNiciraVendorData; -import org.openflow.vendor.nicira.OFRoleReplyVendorData; -import org.openflow.vendor.nicira.OFRoleRequestVendorData; - -import static org.easymock.EasyMock.*; - -import static org.junit.Assert.*; - - -public class OFChannelHandlerTest { - private static final short CORE_PRIORITY = 4242; - private static final short ACCESS_PRIORITY = 42; - private Controller controller; - private IThreadPoolService threadPool; - private IDebugCounterService debugCounterService; - private OFChannelHandler handler; - private Channel channel; - private ChannelHandlerContext ctx; - private MessageEvent messageEvent; - private ChannelStateEvent channelStateEvent; - private ChannelPipeline pipeline; - - - private Capture<ExceptionEvent> exceptionEventCapture; - private Capture<List<OFMessage>> writeCapture; - - private OFFeaturesReply featuresReply; - - private Set<Integer> seenXids = null; - private IStorageSourceService storageSource; - private IResultSet storageResultSet; - private IOFSwitch sw; - - - - @Before - public void setUpFeaturesReply() { - featuresReply = (OFFeaturesReply)BasicFactory.getInstance() - .getMessage(OFType.FEATURES_REPLY); - featuresReply.setDatapathId(0x42L); - featuresReply.setBuffers(1); - featuresReply.setTables((byte)1); - featuresReply.setCapabilities(3); - featuresReply.setActions(4); - List<OFPhysicalPort> ports = new ArrayList<OFPhysicalPort>(); - // A dummy port. - OFPhysicalPort p = new OFPhysicalPort(); - p.setName("Eth1"); - p.setPortNumber((short)1); - ports.add(p); - featuresReply.setPorts(ports); - } - - - @Before - public void setUp() throws Exception { - controller = createMock(Controller.class); - threadPool = createMock(IThreadPoolService.class); - ctx = createMock(ChannelHandlerContext.class); - channelStateEvent = createMock(ChannelStateEvent.class); - channel = createMock(Channel.class); - messageEvent = createMock(MessageEvent.class); - exceptionEventCapture = new Capture<ExceptionEvent>(CaptureType.ALL); - pipeline = createMock(ChannelPipeline.class); - writeCapture = new Capture<List<OFMessage>>(CaptureType.ALL); - sw = createMock(IOFSwitch.class); - seenXids = null; - - // TODO: should mock IDebugCounterService and make sure - // the expected counters are updated. - debugCounterService = new DebugCounter(); - Controller.Counters counters = - new Controller.Counters(); - counters.createCounters(debugCounterService); - expect(controller.getCounters()).andReturn(counters).anyTimes(); - replay(controller); - handler = new OFChannelHandler(controller); - verify(controller); - reset(controller); - - resetChannel(); - - // thread pool is usually not called, so start empty replay - replay(threadPool); - - // replay controller. Reset it if you need more specific behavior - replay(controller); - - // replay switch. Reset it if you need more specific behavior - replay(sw); - - // Mock ctx and channelStateEvent - expect(ctx.getChannel()).andReturn(channel).anyTimes(); - expect(channelStateEvent.getChannel()).andReturn(channel).anyTimes(); - replay(ctx, channelStateEvent); - - /* Setup an exception event capture on the channel. Right now - * we only expect exception events to be send up the channel. - * However, it's easy to extend to other events if we need it - */ - pipeline.sendUpstream(capture(exceptionEventCapture)); - expectLastCall().anyTimes(); - replay(pipeline); - } - - @After - public void tearDown() { - /* ensure no exception was thrown */ - if (exceptionEventCapture.hasCaptured()) { - Throwable ex = exceptionEventCapture.getValue().getCause(); - throw new AssertionError("Unexpected exception: " + - ex.getClass().getName() + "(" + ex + ")"); - } - assertFalse("Unexpected messages have been captured", - writeCapture.hasCaptured()); - // verify all mocks. - verify(channel); - verify(messageEvent); - verify(controller); - verify(threadPool); - verify(ctx); - verify(channelStateEvent); - verify(pipeline); - verify(sw); - } - - /** Reset the channel mock and set basic method call expectations */ - void resetChannel() { - reset(channel); - expect(channel.getPipeline()).andReturn(pipeline).anyTimes(); - expect(channel.getRemoteAddress()).andReturn(null).anyTimes(); - } - - - /** reset, setup, and replay the messageEvent mock for the given - * messages - */ - void setupMessageEvent(List<OFMessage> messages) { - reset(messageEvent); - expect(messageEvent.getMessage()).andReturn(messages).atLeastOnce(); - replay(messageEvent); - } - - - /** reset, setup, and replay the messageEvent mock for the given - * messages, mock controller send message to channel handler - * - * This method will reset, start replay on controller, and then verify - */ - void sendMessageToHandlerWithControllerReset(List<OFMessage> messages) - throws Exception { - verify(controller); - reset(controller); - - sendMessageToHandlerNoControllerReset(messages); - } - - /** reset, setup, and replay the messageEvent mock for the given - * messages, mock controller send message to channel handler - * - * This method will start replay on controller, and then verify - */ - void sendMessageToHandlerNoControllerReset(List<OFMessage> messages) - throws Exception { - setupMessageEvent(messages); - - // mock controller - controller.flushAll(); - expectLastCall().atLeastOnce(); - replay(controller); - handler.messageReceived(ctx, messageEvent); - verify(controller); - } - - /** - * Extract the list of OFMessages that was captured by the Channel.write() - * capture. Will check that something was actually captured first. We'll - * collapse the messages from multiple writes into a single list of - * OFMessages. - * Resets the channelWriteCapture. - */ - List<OFMessage> getMessagesFromCapture() { - List<OFMessage> msgs = new ArrayList<OFMessage>(); - - assertTrue("No write on channel was captured", - writeCapture.hasCaptured()); - List<List<OFMessage>> capturedVals = writeCapture.getValues(); - - for (List<OFMessage> oneWriteList: capturedVals) - msgs.addAll(oneWriteList); - writeCapture.reset(); - return msgs; - } - - - /** - * Verify that the given exception event capture (as returned by - * getAndInitExceptionCapture) has thrown an exception of the given - * expectedExceptionClass. - * Resets the capture - */ - void verifyExceptionCaptured( - Class<? extends Throwable> expectedExceptionClass) { - assertTrue("Excpected exception not thrown", - exceptionEventCapture.hasCaptured()); - Throwable caughtEx = exceptionEventCapture.getValue().getCause(); - assertEquals(expectedExceptionClass, caughtEx.getClass()); - exceptionEventCapture.reset(); - } - - /** make sure that the transaction ids in the given messages are - * not 0 and differ between each other. - * While it's not a defect per se if the xids are we want to ensure - * we use different ones for each message we send. - */ - void verifyUniqueXids(List<OFMessage> msgs) { - if (seenXids == null) - seenXids = new HashSet<Integer>(); - for (OFMessage m: msgs) { - int xid = m.getXid(); - assertTrue("Xid in messags is 0", xid != 0); - assertFalse("Xid " + xid + " has already been used", - seenXids.contains(xid)); - seenXids.add(xid); - } - } - - - @Test - public void testInitState() throws Exception { - // Message event needs to be list - expect(messageEvent.getMessage()).andReturn(null); - replay(channel, messageEvent); - handler.messageReceived(ctx, messageEvent); - verify(channel, messageEvent); - verifyExceptionCaptured(AssertionError.class); - - // Message event needs to be list *of OFMessages* - // TODO: messageReceived can throw exceptions that don't get send - // back into the channel (e.g., the ClassCastException below). - // Do we need to care? - /* - reset(channel, messageEvent); - List<String> listOfWrongType = Collections.singletonList("FooBar"); - expect(messageEvent.getMessage()).andReturn(listOfWrongType) - .atLeastOnce(); - replay(channel, messageEvent); - handler.messageReceived(ctx, messageEvent); - verify(channel, messageEvent); - verifyExceptionCaptured(ClassCastException.class); - */ - - // We don't expect to receive /any/ messages in init state since - // channelConnected moves us to a different state - OFMessage m = BasicFactory.getInstance().getMessage(OFType.HELLO); - sendMessageToHandlerWithControllerReset(Collections.singletonList(m)); - - verifyExceptionCaptured(SwitchStateException.class); - assertEquals(OFChannelHandler.ChannelState.INIT, - handler.getStateForTesting()); - } - - /* Move the channel from scratch to WAIT_HELLO state */ - @Test - public void moveToWaitHello() throws Exception { - resetChannel(); - channel.write(capture(writeCapture)); - expectLastCall().andReturn(null).once(); - replay(channel); - // replay unused mocks - replay(messageEvent); - - handler.channelConnected(ctx, channelStateEvent); - - List<OFMessage> msgs = getMessagesFromCapture(); - assertEquals(1, msgs.size()); - assertEquals(OFType.HELLO, msgs.get(0).getType()); - assertEquals(OFChannelHandler.ChannelState.WAIT_HELLO, - handler.getStateForTesting()); - verifyUniqueXids(msgs); - } - - /** Move the channel from scratch to WAIT_FEATURES_REPLY state - * Builds on moveToWaitHello() - * adds testing for WAIT_HELLO state - */ - @Test - public void moveToWaitFeaturesReply() throws Exception { - moveToWaitHello(); - resetChannel(); - channel.write(capture(writeCapture)); - expectLastCall().andReturn(null).atLeastOnce(); - replay(channel); - - OFMessage hello = BasicFactory.getInstance().getMessage(OFType.HELLO); - sendMessageToHandlerWithControllerReset(Collections.singletonList(hello)); - - List<OFMessage> msgs = getMessagesFromCapture(); - assertEquals(1, msgs.size()); - assertEquals(OFType.FEATURES_REQUEST, msgs.get(0).getType()); - verifyUniqueXids(msgs); - - assertEquals(OFChannelHandler.ChannelState.WAIT_FEATURES_REPLY, - handler.getStateForTesting()); - } - - - /** Move the channel from scratch to WAIT_CONFIG_REPLY state - * Builds on moveToWaitFeaturesReply - * adds testing for WAIT_FEATURES_REPLY state - */ - @Test - public void moveToWaitConfigReply() throws Exception { - moveToWaitFeaturesReply(); - resetChannel(); - channel.write(capture(writeCapture)); - expectLastCall().andReturn(null).atLeastOnce(); - replay(channel); - - sendMessageToHandlerWithControllerReset(Collections.<OFMessage>singletonList(featuresReply)); - - List<OFMessage> msgs = getMessagesFromCapture(); - assertEquals(3, msgs.size()); - assertEquals(OFType.SET_CONFIG, msgs.get(0).getType()); - OFSetConfig sc = (OFSetConfig)msgs.get(0); - assertEquals((short)0xffff, sc.getMissSendLength()); - assertEquals(OFType.BARRIER_REQUEST, msgs.get(1).getType()); - assertEquals(OFType.GET_CONFIG_REQUEST, msgs.get(2).getType()); - verifyUniqueXids(msgs); - assertEquals(OFChannelHandler.ChannelState.WAIT_CONFIG_REPLY, - handler.getStateForTesting()); - } - - /** Move the channel from scratch to WAIT_DESCRIPTION_STAT_REPLY state - * Builds on moveToWaitConfigReply() - * adds testing for WAIT_CONFIG_REPLY state - */ - @Test - public void moveToWaitDescriptionStatReply() throws Exception { - moveToWaitConfigReply(); - resetChannel(); - channel.write(capture(writeCapture)); - expectLastCall().andReturn(null).atLeastOnce(); - replay(channel); - - OFGetConfigReply cr = (OFGetConfigReply)BasicFactory.getInstance() - .getMessage(OFType.GET_CONFIG_REPLY); - cr.setMissSendLength((short)0xffff); - - sendMessageToHandlerWithControllerReset(Collections.<OFMessage>singletonList(cr)); - - List<OFMessage> msgs = getMessagesFromCapture(); - assertEquals(1, msgs.size()); - assertEquals(OFType.STATS_REQUEST, msgs.get(0).getType()); - OFStatisticsRequest sr = (OFStatisticsRequest)msgs.get(0); - assertEquals(OFStatisticsType.DESC, sr.getStatisticType()); - // no idea why an OFStatisticsRequest even /has/ a getStatistics() - // methods. It really shouldn't - assertNull(sr.getStatistics()); - verifyUniqueXids(msgs); - assertEquals(OFChannelHandler.ChannelState.WAIT_DESCRIPTION_STAT_REPLY, - handler.getStateForTesting()); - } - - - /** A helper bean that represents the config for a particular switch in - * the storage source. - */ - private class MockStorageSourceConfig { - // the dpid - public String dpid; - // true if the given dpid should be present in the storage source - // if false the storage source will return an empty result - public boolean isPresent; - // the value of isCoreSwitch - public boolean isCoreSwitch; - } - /** setup and replay a mock storage source and result set that - * contains the IsCoreSwitch setting - */ - private void setupMockStorageSource(MockStorageSourceConfig cfg) { - - storageSource = createMock(IStorageSourceService.class); - storageResultSet = createMock(IResultSet.class); - - Iterator<IResultSet> it = null; - - if (cfg.isPresent) { - storageResultSet.getBoolean(Controller.SWITCH_CONFIG_CORE_SWITCH); - expectLastCall().andReturn(cfg.isCoreSwitch).atLeastOnce(); - it = Collections.singletonList(storageResultSet).iterator(); - } else { - it = Collections.<IResultSet>emptyList().iterator(); - } - - storageResultSet.close(); - expectLastCall().atLeastOnce(); - expect(storageResultSet.iterator()).andReturn(it).atLeastOnce(); - storageSource.getRow(Controller.SWITCH_CONFIG_TABLE_NAME, cfg.dpid); - expectLastCall().andReturn(storageResultSet).atLeastOnce(); - replay(storageResultSet, storageSource); - } - - private void verifyStorageSource() { - verify(storageSource); - verify(storageResultSet); - } - - private static OFStatisticsReply createDescriptionStatsReply() { - OFStatisticsReply sr = (OFStatisticsReply)BasicFactory.getInstance() - .getMessage(OFType.STATS_REPLY); - sr.setStatisticType(OFStatisticsType.DESC); - OFDescriptionStatistics desc = new OFDescriptionStatistics(); - desc.setDatapathDescription("Datapath Description"); - desc.setHardwareDescription("Hardware Description"); - desc.setManufacturerDescription("Manufacturer Description"); - desc.setSerialNumber("Serial Number"); - desc.setSoftwareDescription("Software Description"); - sr.setStatistics(Collections.singletonList(desc)); - return sr; - } - - /** - * setup the expectations for the mock switch that are needed - * after the switch is instantiated in the WAIT_DESCRIPTION_STATS STATE - * Will reset the switch - * @throws CounterException - */ - private void setupSwitchForInstantiationWithReset(String dpid) - throws Exception { - reset(sw); - sw.setChannel(channel); - expectLastCall().once(); - sw.setFloodlightProvider(controller); - expectLastCall().once(); - sw.setThreadPoolService(threadPool); - expectLastCall().once(); - sw.setDebugCounterService(debugCounterService); - expectLastCall().once(); - sw.setFeaturesReply(featuresReply); - expectLastCall().once(); - sw.setConnected(true); - expectLastCall().once(); - sw.getStringId(); - expectLastCall().andReturn(dpid).atLeastOnce(); - sw.isWriteThrottleEnabled(); // used for log message only - expectLastCall().andReturn(false).anyTimes(); - sw.setAccessFlowPriority(ACCESS_PRIORITY); - expectLastCall().once(); - sw.setCoreFlowPriority(CORE_PRIORITY); - expectLastCall().once(); - } - - /** Move the channel from scratch to WAIT_INITIAL_ROLE state - * for a switch that does not have a sub-handshake - * Builds on moveToWaitDescriptionStatReply() - * adds testing for WAIT_DESCRIPTION_STAT_REPLY state - * @param storageSourceConfig paramterizes the contents of the storage - * source (for IS_CORE_SWITCH) - */ - public void doMoveToWaitInitialRole(MockStorageSourceConfig cfg) - throws Exception { - moveToWaitDescriptionStatReply(); - - // We do not expect a write to the channel for the role request. We add - // the channel to the controller and the controller would in turn - // call handler.sendRoleRequest(). Since we mock the controller so - // we won't see the handler.sendRoleRequest() call and therefore we - // won't see any calls on the channel. - resetChannel(); - replay(channel); - - // build the stats reply - OFStatisticsReply sr = createDescriptionStatsReply(); - OFDescriptionStatistics desc = - (OFDescriptionStatistics) sr.getFirstStatistics(); - - setupMessageEvent(Collections.<OFMessage>singletonList(sr)); - setupMockStorageSource(cfg); - - setupSwitchForInstantiationWithReset(cfg.dpid); - sw.startDriverHandshake(); - expectLastCall().once(); - sw.isDriverHandshakeComplete(); - expectLastCall().andReturn(true).once(); - - if (cfg.isPresent) - sw.setAttribute(IOFSwitch.SWITCH_IS_CORE_SWITCH, cfg.isCoreSwitch); - replay(sw); - - // mock controller - reset(controller); - expect(controller.getDebugCounter()).andReturn(debugCounterService) - .once(); - controller.flushAll(); - expectLastCall().once(); - expect(controller.getThreadPoolService()) - .andReturn(threadPool).once(); - expect(controller.getOFSwitchInstance(eq(desc))) - .andReturn(sw).once(); - expect(controller.getCoreFlowPriority()) - .andReturn(CORE_PRIORITY).once(); - expect(controller.getAccessFlowPriority()) - .andReturn(ACCESS_PRIORITY).once(); - controller.addSwitchChannelAndSendInitialRole(handler); - expectLastCall().once(); - expect(controller.getStorageSourceService()) - .andReturn(storageSource).atLeastOnce(); - replay(controller); - - // send the description stats reply - handler.messageReceived(ctx, messageEvent); - - assertEquals(OFChannelHandler.ChannelState.WAIT_INITIAL_ROLE, - handler.getStateForTesting()); - verifyStorageSource(); - } - - /** - * Move the channel from scratch to WAIT_INITIAL_ROLE state via - * WAIT_SWITCH_DRIVER_SUB_HANDSHAKE - * Does extensive testing for the WAIT_SWITCH_DRIVER_SUB_HANDSHAKE state - * - */ - @Test - public void testSwitchDriverSubHandshake() - throws Exception { - MockStorageSourceConfig cfg = new MockStorageSourceConfig(); - cfg.dpid = HexString.toHexString(featuresReply.getDatapathId()); - cfg.isPresent = false; - - moveToWaitDescriptionStatReply(); - - resetChannel(); - channel.write(capture(writeCapture)); - expectLastCall().andReturn(null).atLeastOnce(); - replay(channel); - - // build the stats reply - OFStatisticsReply sr = createDescriptionStatsReply(); - OFDescriptionStatistics desc = - (OFDescriptionStatistics) sr.getFirstStatistics(); - - setupMessageEvent(Collections.<OFMessage>singletonList(sr)); - setupMockStorageSource(cfg); - - // Start the sub-handshake. Switch will indicate that it's not - // complete yet - setupSwitchForInstantiationWithReset(cfg.dpid); - sw.startDriverHandshake(); - expectLastCall().once(); - sw.isDriverHandshakeComplete(); - expectLastCall().andReturn(false).once(); - - if (cfg.isPresent) - sw.setAttribute(IOFSwitch.SWITCH_IS_CORE_SWITCH, cfg.isCoreSwitch); - replay(sw); - - // mock controller - reset(controller); - expect(controller.getDebugCounter()).andReturn(debugCounterService) - .once(); - controller.flushAll(); - expectLastCall().once(); - expect(controller.getThreadPoolService()) - .andReturn(threadPool).once(); - expect(controller.getOFSwitchInstance(eq(desc))) - .andReturn(sw).once(); - expect(controller.getCoreFlowPriority()) - .andReturn(CORE_PRIORITY).once(); - expect(controller.getAccessFlowPriority()) - .andReturn(ACCESS_PRIORITY).once(); - expect(controller.getStorageSourceService()) - .andReturn(storageSource).atLeastOnce(); - replay(controller); - - // send the description stats reply - handler.messageReceived(ctx, messageEvent); - - assertEquals(OFChannelHandler.ChannelState.WAIT_SWITCH_DRIVER_SUB_HANDSHAKE, - handler.getStateForTesting()); - assertFalse("Unexpected message captured", writeCapture.hasCaptured()); - verifyStorageSource(); - verify(sw); - - - //------------------------------------------------- - // Send a message to the handler, it should be passed to the - // switch's sub-handshake handling. - OFMessage m = BasicFactory.getInstance().getMessage(OFType.HELLO); - resetToStrict(sw); - expect(sw.inputThrottled(anyObject(OFMessage.class))) - .andReturn(false).anyTimes(); - sw.processDriverHandshakeMessage(m); - expectLastCall().once(); - expect(sw.isDriverHandshakeComplete()).andReturn(false).once(); - replay(sw); - - sendMessageToHandlerWithControllerReset(Collections.singletonList(m)); - assertEquals(OFChannelHandler.ChannelState.WAIT_SWITCH_DRIVER_SUB_HANDSHAKE, - handler.getStateForTesting()); - assertFalse("Unexpected message captured", writeCapture.hasCaptured()); - verify(sw); - - //------------------------------------------------- - // Send a ECHO_REQUEST. This should be handled by the OFChannelHandler - // and *not* passed to switch sub-handshake - // TODO: should this be also passed to the switch handshake instead? - m = BasicFactory.getInstance().getMessage(OFType.ECHO_REQUEST); - m.setXid(0x042042); - - reset(sw); - expect(sw.inputThrottled(anyObject(OFMessage.class))) - .andReturn(false).anyTimes(); - replay(sw); - sendMessageToHandlerWithControllerReset(Collections.singletonList(m)); - assertEquals(OFChannelHandler.ChannelState.WAIT_SWITCH_DRIVER_SUB_HANDSHAKE, - handler.getStateForTesting()); - List<OFMessage> msgs = getMessagesFromCapture(); - assertEquals(1, msgs.size()); - assertEquals(OFType.ECHO_REPLY, msgs.get(0).getType()); - assertEquals(0x42042, msgs.get(0).getXid()); - verify(sw); - - - //------------------------------------------------- - //------------------------------------------------- - // Send a message to the handler, it should be passed to the - // switch's sub-handshake handling. After this message the - // sub-handshake will be complete - m = BasicFactory.getInstance().getMessage(OFType.FLOW_REMOVED); - resetToStrict(sw); - expect(sw.inputThrottled(anyObject(OFMessage.class))) - .andReturn(false).anyTimes(); - sw.processDriverHandshakeMessage(m); - expectLastCall().once(); - expect(sw.isDriverHandshakeComplete()).andReturn(true).once(); - replay(sw); - - verify(controller); - reset(controller); - controller.addSwitchChannelAndSendInitialRole(handler); - expectLastCall().once(); - sendMessageToHandlerNoControllerReset(Collections.singletonList(m)); - assertEquals(OFChannelHandler.ChannelState.WAIT_INITIAL_ROLE, - handler.getStateForTesting()); - assertFalse("Unexpected message captured", writeCapture.hasCaptured()); - verify(sw); - - //------------------------------------------------- - - //------------------------------------------------- - } - - @Test - /** Test WaitDescriptionReplyState. No config for switch in storage */ - public void testWaitDescriptionReplyState1() throws Exception { - MockStorageSourceConfig cfg = new MockStorageSourceConfig(); - cfg.dpid = HexString.toHexString(featuresReply.getDatapathId()); - cfg.isPresent = false; - doMoveToWaitInitialRole(cfg); - } - - @Test - /** Test WaitDescriptionReplyState. switch is core switch */ - public void testWaitDescriptionReplyState2() throws Exception { - MockStorageSourceConfig cfg = new MockStorageSourceConfig(); - cfg.dpid = HexString.toHexString(featuresReply.getDatapathId()); - cfg.isPresent = true; - cfg.isCoreSwitch = true; - doMoveToWaitInitialRole(cfg); - } - - @Test - /** Test WaitDescriptionReplyState. switch is NOT core switch */ - public void testWaitDescriptionReplyState3() throws Exception { - MockStorageSourceConfig cfg = new MockStorageSourceConfig(); - cfg.dpid = HexString.toHexString(featuresReply.getDatapathId()); - cfg.isPresent = true; - cfg.isCoreSwitch = true; - doMoveToWaitInitialRole(cfg); - } - - /** - * Helper - * Verify that the given OFMessage is a correct Nicira RoleRequest message - * for the given role using the given xid. - */ - private void verifyRoleRequest(OFMessage m, - int expectedXid, - Role expectedRole) { - assertEquals(OFType.VENDOR, m.getType()); - OFVendor vendorMsg = (OFVendor)m; - assertEquals(expectedXid, vendorMsg.getXid()); - assertEquals(OFNiciraVendorData.NX_VENDOR_ID, vendorMsg.getVendor()); - assertTrue("Vendor data is not an instance of OFRoleRequestVendorData" - + " its class is: " + vendorMsg.getVendorData().getClass().getName(), - vendorMsg.getVendorData() instanceof OFRoleRequestVendorData); - OFRoleRequestVendorData requestData = - (OFRoleRequestVendorData)vendorMsg.getVendorData(); - assertEquals(expectedRole.toNxRole(), requestData.getRole()); - } - - /** - * Setup the mock switch and write capture for a role request, set the - * role and verify mocks. - * @param supportsNxRole whether the switch supports role request messages - * to setup the attribute. This must be null (don't yet know if roles - * supported: send to check) or true. - * @param xid The xid to use in the role request - * @param role The role to send - * @throws IOException - */ - private void setupSwitchSendRoleRequestAndVerify(Boolean supportsNxRole, - int xid, - Role role) throws IOException { - assertTrue("This internal test helper method most not be called " + - "with supportsNxRole==false. Test setup broken", - supportsNxRole == null || supportsNxRole == true); - reset(sw); - expect(sw.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE)) - .andReturn(supportsNxRole).atLeastOnce(); - expect(sw.getNextTransactionId()).andReturn(xid).once(); - sw.write(capture(writeCapture), EasyMock.<FloodlightContext>anyObject()); - expectLastCall().anyTimes(); - replay(sw); - - handler.sendRoleRequest(role); - - List<OFMessage> msgs = getMessagesFromCapture(); - assertEquals(1, msgs.size()); - verifyRoleRequest(msgs.get(0), xid, role); - verify(sw); - } - - /** - * Setup the mock switch for a role change request where the switch - * does not support roles. - * - * Needs to verify and reset the controller since we need to set - * an expectation - */ - private void setupSwitchRoleChangeUnsupported(int xid, - Role role) { - boolean supportsNxRole = false; - reset(sw); - expect(sw.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE)) - .andReturn(supportsNxRole).atLeastOnce(); - // TODO: hmmm. While it's not incorrect that we set the attribute - // again it looks odd. Maybe change - sw.setAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE, supportsNxRole); - expectLastCall().anyTimes(); - sw.setHARole(role); - expectLastCall().once(); - if (role == Role.SLAVE) { - sw.disconnectOutputStream(); - expectLastCall().once(); - } else { - verify(controller); - reset(controller); - controller.switchActivated(sw); - replay(controller); - } - replay(sw); - - handler.sendRoleRequest(role); - - verify(sw); - } - - /** Return a Nicira RoleReply message for the given role */ - private OFMessage getRoleReply(int xid, Role role) { - OFVendor vm = (OFVendor)BasicFactory.getInstance() - .getMessage(OFType.VENDOR); - vm.setXid(xid); - vm.setVendor(OFNiciraVendorData.NX_VENDOR_ID); - OFRoleReplyVendorData replyData = new OFRoleReplyVendorData(); - replyData.setRole(role.toNxRole()); - vm.setVendorData(replyData); - return vm; - } - - /** Return an OFError of the given type with the given xid */ - private OFMessage getErrorMessage(OFErrorType type, - int i, - int xid) { - OFError e = (OFError) BasicFactory.getInstance() - .getMessage(OFType.ERROR); - e.setErrorType(type); - e.setErrorCode((short)i); - e.setXid(xid); - return e; - } - - - /** Move the channel from scratch to MASTER state - * Builds on doMoveToWaitInitialRole() - * adds testing for WAIT_INITAL_ROLE state - * - * This method tests only the simple case that the switch supports roles - * and transitions to MASTER - */ - @Test - public void testInitialMoveToMasterWithRole() throws Exception { - int xid = 42; - // first, move us to WAIT_INITIAL_ROLE_STATE - MockStorageSourceConfig cfg = new MockStorageSourceConfig(); - cfg.dpid = HexString.toHexString(featuresReply.getDatapathId()); - cfg.isPresent = false; - doMoveToWaitInitialRole(cfg); - assertEquals(OFChannelHandler.ChannelState.WAIT_INITIAL_ROLE, - handler.getStateForTesting()); - - // Set the role - setupSwitchSendRoleRequestAndVerify(null, xid, Role.MASTER); - assertEquals(OFChannelHandler.ChannelState.WAIT_INITIAL_ROLE, - handler.getStateForTesting()); - - // prepare mocks and inject the role reply message - reset(sw); - expect(sw.inputThrottled(anyObject(OFMessage.class))) - .andReturn(false).anyTimes(); - sw.setAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE, true); - expectLastCall().once(); - sw.setHARole(Role.MASTER); - expectLastCall().once(); - replay(sw); - - verify(controller); - reset(controller); - controller.switchActivated(sw); - expectLastCall().once(); - OFMessage reply = getRoleReply(xid, Role.MASTER); - // sendMessageToHandler will verify and rest controller mock - sendMessageToHandlerNoControllerReset(Collections.singletonList(reply)); - - assertEquals(OFChannelHandler.ChannelState.MASTER, - handler.getStateForTesting()); - } - - /** Move the channel from scratch to SLAVE state - * Builds on doMoveToWaitInitialRole() - * adds testing for WAIT_INITAL_ROLE state - * - * This method tests only the simple case that the switch supports roles - * and transitions to SLAVE - */ - @Test - public void testInitialMoveToSlaveWithRole() throws Exception { - int xid = 42; - // first, move us to WAIT_INITIAL_ROLE_STATE - MockStorageSourceConfig cfg = new MockStorageSourceConfig(); - cfg.dpid = HexString.toHexString(featuresReply.getDatapathId()); - cfg.isPresent = false; - doMoveToWaitInitialRole(cfg); - assertEquals(OFChannelHandler.ChannelState.WAIT_INITIAL_ROLE, - handler.getStateForTesting()); - - // Set the role - setupSwitchSendRoleRequestAndVerify(null, xid, Role.SLAVE); - assertEquals(OFChannelHandler.ChannelState.WAIT_INITIAL_ROLE, - handler.getStateForTesting()); - - // prepare mocks and inject the role reply message - reset(sw); - expect(sw.inputThrottled(anyObject(OFMessage.class))) - .andReturn(false).anyTimes(); - sw.setAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE, true); - expectLastCall().once(); - sw.setHARole(Role.SLAVE); - expectLastCall().once(); - replay(sw); - - verify(controller); - reset(controller); - controller.switchDeactivated(sw); - expectLastCall().once(); - OFMessage reply = getRoleReply(xid, Role.SLAVE); - // sendMessageToHandler will verify and rest controller mock - sendMessageToHandlerNoControllerReset(Collections.singletonList(reply)); - - assertEquals(OFChannelHandler.ChannelState.SLAVE, - handler.getStateForTesting()); - } - - /** Move the channel from scratch to MASTER state - * Builds on doMoveToWaitInitialRole() - * adds testing for WAIT_INITAL_ROLE state - * - * This method tests the case that the switch does NOT support roles. - * The channel handler still needs to send the initial request to find - * out that whether the switch supports roles. - */ - @Test - public void testInitialMoveToMasterNoRole() throws Exception { - int xid = 43; - // first, move us to WAIT_INITIAL_ROLE_STATE - MockStorageSourceConfig cfg = new MockStorageSourceConfig(); - cfg.dpid = HexString.toHexString(featuresReply.getDatapathId()); - cfg.isPresent = false; - doMoveToWaitInitialRole(cfg); - assertEquals(OFChannelHandler.ChannelState.WAIT_INITIAL_ROLE, - handler.getStateForTesting()); - - // Set the role - setupSwitchSendRoleRequestAndVerify(null, xid, Role.MASTER); - assertEquals(OFChannelHandler.ChannelState.WAIT_INITIAL_ROLE, - handler.getStateForTesting()); - - // prepare mocks and inject the role reply message - reset(sw); - expect(sw.inputThrottled(anyObject(OFMessage.class))) - .andReturn(false).anyTimes(); - sw.setAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE, false); - expectLastCall().once(); - sw.setHARole(Role.MASTER); - expectLastCall().once(); - replay(sw); - - // FIXME: shouldn't use ordinal(), but OFError is broken - - // Error with incorrect xid and type. Should be ignored. - OFMessage err = getErrorMessage(OFErrorType.OFPET_BAD_ACTION, - 0, - xid+1); - // sendMessageToHandler will verify and rest controller mock - sendMessageToHandlerWithControllerReset(Collections.singletonList(err)); - assertEquals(OFChannelHandler.ChannelState.WAIT_INITIAL_ROLE, - handler.getStateForTesting()); - - // Error with correct xid. Should trigger state transition - err = getErrorMessage(OFErrorType.OFPET_BAD_REQUEST, - OFError.OFBadRequestCode.OFPBRC_BAD_VENDOR.ordinal(), - xid); - verify(controller); - reset(controller); - controller.switchActivated(sw); - expectLastCall().once(); - // sendMessageToHandler will verify and rest controller mock - sendMessageToHandlerNoControllerReset(Collections.singletonList(err)); - - assertEquals(OFChannelHandler.ChannelState.MASTER, - handler.getStateForTesting()); - - } - - /** Move the channel from scratch to MASTER state - * Builds on doMoveToWaitInitialRole() - * adds testing for WAIT_INITAL_ROLE state - * - * We let the initial role request time out. Role support should be - * disabled but the switch should be activated. - */ - @Test - public void testInitialMoveToMasterTimeout() throws Exception { - int timeout = 50; - handler.useRoleChangerWithOtherTimeoutForTesting(timeout); - int xid = 4343; - - // first, move us to WAIT_INITIAL_ROLE_STATE - MockStorageSourceConfig cfg = new MockStorageSourceConfig(); - cfg.dpid = HexString.toHexString(featuresReply.getDatapathId()); - cfg.isPresent = false; - doMoveToWaitInitialRole(cfg); - assertEquals(OFChannelHandler.ChannelState.WAIT_INITIAL_ROLE, - handler.getStateForTesting()); - - // Set the role - setupSwitchSendRoleRequestAndVerify(null, xid, Role.MASTER); - assertEquals(OFChannelHandler.ChannelState.WAIT_INITIAL_ROLE, - handler.getStateForTesting()); - - // prepare mocks and inject the role reply message - reset(sw); - expect(sw.inputThrottled(anyObject(OFMessage.class))) - .andReturn(false).anyTimes(); - sw.setAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE, false); - expectLastCall().once(); - sw.setHARole(Role.MASTER); - expectLastCall().once(); - replay(sw); - - OFMessage m = BasicFactory.getInstance().getMessage(OFType.ECHO_REPLY); - - Thread.sleep(timeout+5); - - verify(controller); - reset(controller); - controller.switchActivated(sw); - expectLastCall().once(); - sendMessageToHandlerNoControllerReset(Collections.singletonList(m)); - - assertEquals(OFChannelHandler.ChannelState.MASTER, - handler.getStateForTesting()); - - } - - - /** Move the channel from scratch to SLAVE state - * Builds on doMoveToWaitInitialRole() - * adds testing for WAIT_INITAL_ROLE state - * - * This method tests the case that the switch does NOT support roles. - * The channel handler still needs to send the initial request to find - * out that whether the switch supports roles. - * - */ - @Test - public void testInitialMoveToSlaveNoRole() throws Exception { - int xid = 44; - // first, move us to WAIT_INITIAL_ROLE_STATE - MockStorageSourceConfig cfg = new MockStorageSourceConfig(); - cfg.dpid = HexString.toHexString(featuresReply.getDatapathId()); - cfg.isPresent = false; - doMoveToWaitInitialRole(cfg); - assertEquals(OFChannelHandler.ChannelState.WAIT_INITIAL_ROLE, - handler.getStateForTesting()); - - // Set the role - setupSwitchSendRoleRequestAndVerify(null, xid, Role.SLAVE); - assertEquals(OFChannelHandler.ChannelState.WAIT_INITIAL_ROLE, - handler.getStateForTesting()); - - // prepare mocks and inject the role reply message - reset(sw); - expect(sw.inputThrottled(anyObject(OFMessage.class))) - .andReturn(false).anyTimes(); - sw.setAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE, false); - expectLastCall().once(); - sw.setHARole(Role.SLAVE); - expectLastCall().once(); - sw.disconnectOutputStream(); // Make sure we disconnect - expectLastCall().once(); - replay(sw); - - - // FIXME: shouldn't use ordinal(), but OFError is broken - - // Error with incorrect xid and type. Should be ignored. - OFMessage err = getErrorMessage(OFErrorType.OFPET_BAD_ACTION, - 0, - xid+1); - // sendMessageToHandler will verify and rest controller mock - sendMessageToHandlerWithControllerReset(Collections.singletonList(err)); - assertEquals(OFChannelHandler.ChannelState.WAIT_INITIAL_ROLE, - handler.getStateForTesting()); - - // Error with correct xid. Should trigger state transition - err = getErrorMessage(OFErrorType.OFPET_BAD_REQUEST, - OFError.OFBadRequestCode.OFPBRC_BAD_VENDOR.ordinal(), - xid); - // sendMessageToHandler will verify and rest controller mock - sendMessageToHandlerWithControllerReset(Collections.singletonList(err)); - } - - - /** Move the channel from scratch to SLAVE state - * Builds on doMoveToWaitInitialRole() - * adds testing for WAIT_INITAL_ROLE state - * - * We let the initial role request time out. The switch should be - * disconnected - */ - @Test - public void testInitialMoveToSlaveTimeout() throws Exception { - int timeout = 50; - handler.useRoleChangerWithOtherTimeoutForTesting(timeout); - int xid = 4444; - - // first, move us to WAIT_INITIAL_ROLE_STATE - MockStorageSourceConfig cfg = new MockStorageSourceConfig(); - cfg.dpid = HexString.toHexString(featuresReply.getDatapathId()); - cfg.isPresent = false; - doMoveToWaitInitialRole(cfg); - assertEquals(OFChannelHandler.ChannelState.WAIT_INITIAL_ROLE, - handler.getStateForTesting()); - - // Set the role - setupSwitchSendRoleRequestAndVerify(null, xid, Role.SLAVE); - assertEquals(OFChannelHandler.ChannelState.WAIT_INITIAL_ROLE, - handler.getStateForTesting()); - - // prepare mocks and inject the role reply message - reset(sw); - expect(sw.inputThrottled(anyObject(OFMessage.class))) - .andReturn(false).anyTimes(); - sw.setAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE, false); - expectLastCall().once(); - sw.setHARole(Role.SLAVE); - expectLastCall().once(); - sw.disconnectOutputStream(); // Make sure we disconnect - expectLastCall().once(); - replay(sw); - - OFMessage m = BasicFactory.getInstance().getMessage(OFType.ECHO_REPLY); - - Thread.sleep(timeout+5); - - sendMessageToHandlerWithControllerReset(Collections.singletonList(m)); - } - - - /** Move channel from scratch to WAIT_INITIAL_STATE, then MASTER, - * then SLAVE for cases where the switch does not support roles. - * I.e., the final SLAVE transition should disconnect the switch. - */ - @Test - public void testNoRoleInitialToMasterToSlave() throws Exception { - int xid = 46; - // First, lets move the state to MASTER without role support - testInitialMoveToMasterNoRole(); - assertEquals(OFChannelHandler.ChannelState.MASTER, - handler.getStateForTesting()); - - // try to set master role again. should be a no-op - setupSwitchRoleChangeUnsupported(xid, Role.MASTER); - assertEquals(OFChannelHandler.ChannelState.MASTER, - handler.getStateForTesting()); - - - setupSwitchRoleChangeUnsupported(xid, Role.SLAVE); - assertEquals(OFChannelHandler.ChannelState.SLAVE, - handler.getStateForTesting()); - - } - - /** Move the channel to MASTER state - * Expects that the channel is in MASTER or SLAVE state. - * - */ - public void changeRoleToMasterWithRequest() throws Exception { - int xid = 4242; - - assertTrue("This method can only be called when handler is in " + - "MASTER or SLAVE role", handler.isHandshakeComplete()); - - // Set the role - setupSwitchSendRoleRequestAndVerify(true, xid, Role.MASTER); - - // prepare mocks and inject the role reply message - reset(sw); - expect(sw.inputThrottled(anyObject(OFMessage.class))) - .andReturn(false).anyTimes(); - sw.setAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE, true); - expectLastCall().once(); - sw.setHARole(Role.MASTER); - expectLastCall().once(); - replay(sw); - - verify(controller); - reset(controller); - controller.switchActivated(sw); - expectLastCall().once(); - OFMessage reply = getRoleReply(xid, Role.MASTER); - // sendMessageToHandler will verify and rest controller mock - sendMessageToHandlerNoControllerReset(Collections.singletonList(reply)); - - assertEquals(OFChannelHandler.ChannelState.MASTER, - handler.getStateForTesting()); - } - - /** Move the channel to SLAVE state - * Expects that the channel is in MASTER or SLAVE state. - * - */ - public void changeRoleToSlaveWithRequest() throws Exception { - int xid = 2323; - - assertTrue("This method can only be called when handler is in " + - "MASTER or SLAVE role", handler.isHandshakeComplete()); - - // Set the role - setupSwitchSendRoleRequestAndVerify(true, xid, Role.SLAVE); - - // prepare mocks and inject the role reply message - reset(sw); - expect(sw.inputThrottled(anyObject(OFMessage.class))) - .andReturn(false).anyTimes(); - sw.setAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE, true); - expectLastCall().once(); - sw.setHARole(Role.SLAVE); - expectLastCall().once(); - replay(sw); - - verify(controller); - reset(controller); - controller.switchDeactivated(sw); - expectLastCall().once(); - OFMessage reply = getRoleReply(xid, Role.SLAVE); - // sendMessageToHandler will verify and rest controller mock - sendMessageToHandlerNoControllerReset(Collections.singletonList(reply)); - - assertEquals(OFChannelHandler.ChannelState.SLAVE, - handler.getStateForTesting()); - } - - @Test - public void testMultiRoleChange1() throws Exception { - testInitialMoveToMasterWithRole(); - changeRoleToMasterWithRequest(); - changeRoleToSlaveWithRequest(); - changeRoleToSlaveWithRequest(); - changeRoleToMasterWithRequest(); - changeRoleToSlaveWithRequest(); - } - - @Test - public void testMultiRoleChange2() throws Exception { - testInitialMoveToSlaveWithRole(); - changeRoleToMasterWithRequest(); - changeRoleToSlaveWithRequest(); - changeRoleToSlaveWithRequest(); - changeRoleToMasterWithRequest(); - changeRoleToSlaveWithRequest(); - } - - /** Start from scratch and reply with an unexpected error to the role - * change request - * Builds on doMoveToWaitInitialRole() - * adds testing for WAIT_INITAL_ROLE state - */ - @Test - public void testInitialRoleChangeOtherError() throws Exception { - int xid = 4343; - // first, move us to WAIT_INITIAL_ROLE_STATE - MockStorageSourceConfig cfg = new MockStorageSourceConfig(); - cfg.dpid = HexString.toHexString(featuresReply.getDatapathId()); - cfg.isPresent = false; - doMoveToWaitInitialRole(cfg); - assertEquals(OFChannelHandler.ChannelState.WAIT_INITIAL_ROLE, - handler.getStateForTesting()); - - // Set the role - setupSwitchSendRoleRequestAndVerify(null, xid, Role.MASTER); - assertEquals(OFChannelHandler.ChannelState.WAIT_INITIAL_ROLE, - handler.getStateForTesting()); - - - // FIXME: shouldn't use ordinal(), but OFError is broken - - OFMessage err = getErrorMessage(OFErrorType.OFPET_BAD_ACTION, - 0, - xid); - verify(sw); - reset(sw); - expect(sw.inputThrottled(anyObject(OFMessage.class))) - .andReturn(false).anyTimes(); - replay(sw); - sendMessageToHandlerWithControllerReset(Collections.singletonList(err)); - - verifyExceptionCaptured(SwitchStateException.class); - } - - /** - * Test dispatch of messages while in MASTER role - */ - @Test - public void testMessageDispatchMaster() throws Exception { - testInitialMoveToMasterWithRole(); - - // Send packet in. expect dispatch - OFPacketIn pi = (OFPacketIn) - BasicFactory.getInstance().getMessage(OFType.PACKET_IN); - reset(controller); - controller.handleMessage(sw, pi, null); - expectLastCall().once(); - sendMessageToHandlerNoControllerReset( - Collections.<OFMessage>singletonList(pi)); - verify(controller); - // TODO: many more to go - } - - /** - * Test port status message handling while MASTER - * - */ - @Test - public void testPortStatusMessageMaster() throws Exception { - long dpid = featuresReply.getDatapathId(); - testInitialMoveToMasterWithRole(); - - OFPhysicalPort p = new OFPhysicalPort(); - p.setName("Port1"); - p.setPortNumber((short)1); - OFPortStatus ps = (OFPortStatus) - BasicFactory.getInstance().getMessage(OFType.PORT_STATUS); - ps.setDesc(p); - - // The events we expect sw.handlePortStatus to return - // We'll just use the same list for all valid OFPortReasons and add - // arbitrary events for arbitrary ports that are not necessarily - // related to the port status message. Our goal - // here is not to return the correct set of events but the make sure - // that a) sw.handlePortStatus is called - // b) the list of events sw.handlePortStatus returns is sent - // as IOFSwitchListener notifications. - OrderedCollection<PortChangeEvent> events = - new LinkedHashSetWrapper<PortChangeEvent>(); - ImmutablePort p1 = ImmutablePort.create("eth1", (short)1); - ImmutablePort p2 = ImmutablePort.create("eth2", (short)2); - ImmutablePort p3 = ImmutablePort.create("eth3", (short)3); - ImmutablePort p4 = ImmutablePort.create("eth4", (short)4); - ImmutablePort p5 = ImmutablePort.create("eth5", (short)5); - events.add(new PortChangeEvent(p1, PortChangeType.ADD)); - events.add(new PortChangeEvent(p2, PortChangeType.DELETE)); - events.add(new PortChangeEvent(p3, PortChangeType.UP)); - events.add(new PortChangeEvent(p4, PortChangeType.DOWN)); - events.add(new PortChangeEvent(p5, PortChangeType.OTHER_UPDATE)); - - - for (OFPortReason reason: OFPortReason.values()) { - ps.setReason(reason.getReasonCode()); - - reset(sw); - expect(sw.inputThrottled(anyObject(OFMessage.class))) - .andReturn(false).anyTimes(); - expect(sw.getId()).andReturn(dpid).anyTimes(); - - expect(sw.processOFPortStatus(ps)).andReturn(events).once(); - replay(sw); - - reset(controller); - controller.notifyPortChanged(sw, p1, PortChangeType.ADD); - controller.notifyPortChanged(sw, p2, PortChangeType.DELETE); - controller.notifyPortChanged(sw, p3, PortChangeType.UP); - controller.notifyPortChanged(sw, p4, PortChangeType.DOWN); - controller.notifyPortChanged(sw, p5, PortChangeType.OTHER_UPDATE); - sendMessageToHandlerNoControllerReset( - Collections.<OFMessage>singletonList(ps)); - verify(sw); - verify(controller); - } - } - - /** - * Test re-assert MASTER - * - */ - @Test - public void testReassertMaster() throws Exception { - testInitialMoveToMasterWithRole(); - - OFError err = (OFError) - BasicFactory.getInstance().getMessage(OFType.ERROR); - err.setXid(42); - err.setErrorType(OFErrorType.OFPET_BAD_REQUEST); - err.setErrorCode(OFBadRequestCode.OFPBRC_EPERM); - - reset(controller); - controller.reassertRole(handler, Role.MASTER); - expectLastCall().once(); - controller.handleMessage(sw, err, null); - expectLastCall().once(); - - sendMessageToHandlerNoControllerReset( - Collections.<OFMessage>singletonList(err)); - - verify(sw); - verify(controller); - } - - -} diff --git a/src/test/java/net/floodlightcontroller/core/internal/OFChannelHandlerVer10Test.java b/src/test/java/net/floodlightcontroller/core/internal/OFChannelHandlerVer10Test.java new file mode 100644 index 0000000000000000000000000000000000000000..038639f79ff45cd566c5f92c3c684d377cb2f49d --- /dev/null +++ b/src/test/java/net/floodlightcontroller/core/internal/OFChannelHandlerVer10Test.java @@ -0,0 +1,464 @@ +package net.floodlightcontroller.core.internal; + +import static org.easymock.EasyMock.capture; +import static org.easymock.EasyMock.createMock; +import static org.easymock.EasyMock.expect; +import static org.easymock.EasyMock.expectLastCall; +import static org.easymock.EasyMock.replay; +import static org.easymock.EasyMock.reset; +import static org.easymock.EasyMock.verify; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import java.net.InetSocketAddress; +import java.util.ArrayList; +import java.util.Collections; +import java.util.EnumSet; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.easymock.Capture; +import org.easymock.CaptureType; +import org.easymock.EasyMock; +import org.hamcrest.CoreMatchers; +import org.jboss.netty.channel.Channel; +import org.jboss.netty.channel.ChannelHandlerContext; +import org.jboss.netty.channel.ChannelPipeline; +import org.jboss.netty.channel.ChannelStateEvent; +import org.jboss.netty.channel.ExceptionEvent; +import org.jboss.netty.channel.MessageEvent; +import org.jboss.netty.util.HashedWheelTimer; +import org.jboss.netty.util.Timer; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import net.floodlightcontroller.core.IOFConnectionBackend; +import net.floodlightcontroller.core.OFConnectionCounters; +import net.floodlightcontroller.core.internal.OpenflowPipelineFactory.PipelineHandler; +import net.floodlightcontroller.core.internal.OpenflowPipelineFactory.PipelineHandshakeTimeout; +import net.floodlightcontroller.debugcounter.DebugCounterServiceImpl; +import net.floodlightcontroller.debugcounter.IDebugCounterService; +import org.projectfloodlight.openflow.protocol.OFActionType; +import org.projectfloodlight.openflow.protocol.OFBarrierReply; +import org.projectfloodlight.openflow.protocol.OFCapabilities; +import org.projectfloodlight.openflow.protocol.OFFactories; +import org.projectfloodlight.openflow.protocol.OFFactory; +import org.projectfloodlight.openflow.protocol.OFFeaturesReply; +import org.projectfloodlight.openflow.protocol.OFFlowRemoved; +import org.projectfloodlight.openflow.protocol.OFFlowStatsReply; +import org.projectfloodlight.openflow.protocol.OFGetConfigReply; +import org.projectfloodlight.openflow.protocol.OFMessage; +import org.projectfloodlight.openflow.protocol.OFNiciraControllerRole; +import org.projectfloodlight.openflow.protocol.OFNiciraControllerRoleReply; +import org.projectfloodlight.openflow.protocol.OFPacketIn; +import org.projectfloodlight.openflow.protocol.OFPacketInReason; +import org.projectfloodlight.openflow.protocol.OFPortDesc; +import org.projectfloodlight.openflow.protocol.OFPortReason; +import org.projectfloodlight.openflow.protocol.OFPortStatus; +import org.projectfloodlight.openflow.protocol.OFQueueGetConfigReply; +import org.projectfloodlight.openflow.protocol.OFType; +import org.projectfloodlight.openflow.protocol.OFVersion; +import org.projectfloodlight.openflow.types.DatapathId; +import org.projectfloodlight.openflow.types.OFPort; + +import com.google.common.collect.ImmutableList; + + +public class OFChannelHandlerVer10Test { + private IOFSwitchManager switchManager; + private IOFConnectionListener connectionListener; + private IDebugCounterService debugCounterService; + private OFChannelHandler handler; + private Channel channel; + private Timer timer; + private ChannelHandlerContext ctx; + private MessageEvent messageEvent; + private ChannelStateEvent channelStateEvent; + private ChannelPipeline pipeline; + // FIXME:LOJI: Currently only use OF 1.0 + private final OFFactory factory = OFFactories.getFactory(OFVersion.OF_10); + + private Capture<ExceptionEvent> exceptionEventCapture; + private Capture<List<OFMessage>> writeCapture; + + private OFPortDesc portDesc; + private OFFeaturesReply featuresReply; + + private Set<Long> seenXids = null; + private INewOFConnectionListener newConnectionListener; + private Capture<IOFConnectionBackend> newConnection; + private Capture<OFFeaturesReply> newFeaturesReply; + + @Before + public void setUpFeaturesReply() { + portDesc = factory.buildPortDesc() + .setName("Eth1") + .setPortNo(OFPort.of(1)) + .build(); + featuresReply = factory.buildFeaturesReply() + .setDatapathId(DatapathId.of(0x42L)) + .setNBuffers(1) + .setNTables((short)1) + .setCapabilities(EnumSet.<OFCapabilities>of(OFCapabilities.FLOW_STATS, OFCapabilities.TABLE_STATS)) + .setActions(EnumSet.<OFActionType>of(OFActionType.SET_VLAN_PCP)) + .setPorts(ImmutableList.<OFPortDesc>of(portDesc)) + .build(); + } + + + @Before + public void setUp() throws Exception { + switchManager = createMock(IOFSwitchManager.class); + connectionListener = createMock(IOFConnectionListener.class); + newConnectionListener = createMock(INewOFConnectionListener.class); + newConnection = new Capture<IOFConnectionBackend>(); + newFeaturesReply = new Capture<OFFeaturesReply>(); + + ctx = createMock(ChannelHandlerContext.class); + channelStateEvent = createMock(ChannelStateEvent.class); + channel = createMock(Channel.class); + timer = new HashedWheelTimer(); + messageEvent = createMock(MessageEvent.class); + exceptionEventCapture = new Capture<ExceptionEvent>(CaptureType.ALL); + pipeline = createMock(ChannelPipeline.class); + writeCapture = new Capture<List<OFMessage>>(CaptureType.ALL); + seenXids = null; + + // TODO: should mock IDebugCounterService and make sure + // the expected counters are updated. + debugCounterService = new DebugCounterServiceImpl(); + debugCounterService.registerModule(OFConnectionCounters.COUNTER_MODULE); + SwitchManagerCounters counters = + new SwitchManagerCounters(debugCounterService); + expect(switchManager.getCounters()).andReturn(counters).anyTimes(); + replay(switchManager); + handler = new OFChannelHandler(switchManager, newConnectionListener, + pipeline, debugCounterService, + timer); + verify(switchManager); + reset(switchManager); + + resetChannel(); + + // replay controller. Reset it if you need more specific behavior + replay(switchManager); + + // Mock ctx and channelStateEvent + expect(ctx.getChannel()).andReturn(channel).anyTimes(); + expect(channelStateEvent.getChannel()).andReturn(channel).anyTimes(); + replay(ctx, channelStateEvent); + + /* Setup an exception event capture on the channel. Right now + * we only expect exception events to be send up the channel. + * However, it's easy to extend to other events if we need it + */ + pipeline.sendUpstream(capture(exceptionEventCapture)); + expectLastCall().anyTimes(); + expect(pipeline.get(OFMessageDecoder.class)).andReturn(new OFMessageDecoder()).anyTimes(); + replay(pipeline); + } + + @After + public void tearDown() { + /* ensure no exception was thrown */ + if (exceptionEventCapture.hasCaptured()) { + Throwable ex = exceptionEventCapture.getValue().getCause(); + ex.printStackTrace(); + throw new AssertionError("Unexpected exception: " + + ex.getClass().getName() + "(" + ex + ")"); + } + assertFalse("Unexpected messages have been captured", + writeCapture.hasCaptured()); + // verify all mocks. + verify(channel); + verify(messageEvent); + verify(switchManager); + verify(ctx); + verify(channelStateEvent); + verify(pipeline); + } + + /** Reset the channel mock and set basic method call expectations */ + void resetChannel() { + reset(channel); + expect(channel.getPipeline()).andReturn(pipeline).anyTimes(); + expect(channel.getRemoteAddress()).andReturn(InetSocketAddress.createUnresolved("1.1.1.1", 80)).anyTimes(); + } + + + /** reset, setup, and replay the messageEvent mock for the given + * messages + */ + void setupMessageEvent(List<OFMessage> messages) { + reset(messageEvent); + expect(messageEvent.getMessage()).andReturn(messages).atLeastOnce(); + replay(messageEvent); + } + + + /** reset, setup, and replay the messageEvent mock for the given + * messages, mock controller send message to channel handler + * + * This method will reset, start replay on controller, and then verify + */ + void sendMessageToHandlerWithControllerReset(List<OFMessage> messages) + throws Exception { + + sendMessageToHandlerNoControllerReset(messages); + } + + /** reset, setup, and replay the messageEvent mock for the given + * messages, mock controller send message to channel handler + * + * This method will start replay on controller, and then verify + */ + void sendMessageToHandlerNoControllerReset(List<OFMessage> messages) + throws Exception { + setupMessageEvent(messages); + + handler.messageReceived(ctx, messageEvent); + } + + /** + * Extract the list of OFMessages that was captured by the Channel.write() + * capture. Will check that something was actually captured first. We'll + * collapse the messages from multiple writes into a single list of + * OFMessages. + * Resets the channelWriteCapture. + */ + List<OFMessage> getMessagesFromCapture() { + List<OFMessage> msgs = new ArrayList<OFMessage>(); + + assertTrue("No write on channel was captured", + writeCapture.hasCaptured()); + List<List<OFMessage>> capturedVals = writeCapture.getValues(); + + for (List<OFMessage> oneWriteList: capturedVals) + msgs.addAll(oneWriteList); + writeCapture.reset(); + return msgs; + } + + + /** + * Verify that the given exception event capture (as returned by + * getAndInitExceptionCapture) has thrown an exception of the given + * expectedExceptionClass. + * Resets the capture + */ + void verifyExceptionCaptured( + Class<? extends Throwable> expectedExceptionClass) { + assertTrue("Excpected exception not thrown", + exceptionEventCapture.hasCaptured()); + Throwable caughtEx = exceptionEventCapture.getValue().getCause(); + assertEquals(expectedExceptionClass, caughtEx.getClass()); + exceptionEventCapture.reset(); + } + + /** make sure that the transaction ids in the given messages are + * not 0 and differ between each other. + * While it's not a defect per se if the xids are we want to ensure + * we use different ones for each message we send. + */ + void verifyUniqueXids(List<OFMessage> msgs) { + if (seenXids == null) + seenXids = new HashSet<Long>(); + for (OFMessage m: msgs) { + long xid = m.getXid(); + assertTrue("Xid in messags is 0", xid != 0); + assertFalse("Xid " + xid + " has already been used", + seenXids.contains(xid)); + seenXids.add(xid); + } + } + + + @Test + public void testInitState() throws Exception { + // Message event needs to be list + expect(messageEvent.getMessage()).andReturn(null); + replay(channel, messageEvent); + handler.messageReceived(ctx, messageEvent); + verify(channel, messageEvent); + verifyExceptionCaptured(AssertionError.class); + + // We don't expect to receive /any/ messages in init state since + // channelConnected moves us to a different state + OFMessage m = factory.buildHello().build(); + sendMessageToHandlerWithControllerReset(ImmutableList.<OFMessage>of(m)); + + verifyExceptionCaptured(SwitchStateException.class); + assertThat(handler.getStateForTesting(), CoreMatchers.instanceOf(OFChannelHandler.InitState.class)); + } + + /* Move the channel from scratch to WAIT_HELLO state */ + @Test + public void moveToWaitHello() throws Exception { + + resetChannel(); + channel.write(capture(writeCapture)); + expectLastCall().andReturn(null).once(); + replay(channel); + // replay unused mocks + replay(messageEvent); + + handler.channelConnected(ctx, channelStateEvent); + + List<OFMessage> msgs = getMessagesFromCapture(); + assertEquals(1, msgs.size()); + assertEquals(OFType.HELLO, msgs.get(0).getType()); + assertThat(handler.getStateForTesting(), CoreMatchers.instanceOf(OFChannelHandler.WaitHelloState.class)); + verifyUniqueXids(msgs); + } + + /** Move the channel from scratch to WAIT_FEATURES_REPLY state + * Builds on moveToWaitHello() + * adds testing for WAIT_HELLO state + */ + @Test + public void moveToWaitFeaturesReply() throws Exception { + + moveToWaitHello(); + + resetChannel(); + channel.write(capture(writeCapture)); + expectLastCall().andReturn(null).atLeastOnce(); + replay(channel); + + OFMessage hello = factory.buildHello().build(); + sendMessageToHandlerWithControllerReset(ImmutableList.<OFMessage>of(hello)); + + List<OFMessage> msgs = getMessagesFromCapture(); + assertEquals(1, msgs.size()); + assertEquals(OFType.FEATURES_REQUEST, msgs.get(0).getType()); + verifyUniqueXids(msgs); + + assertThat(handler.getStateForTesting(), CoreMatchers.instanceOf(OFChannelHandler.WaitFeaturesReplyState.class)); + } + + + /** Move the channel from scratch to WAIT_FEATURES_REPLY state + * Builds on moveToWaitHello() + * adds testing for WAIT_HELLO state + */ + @Test + public void moveToComplete() throws Exception { + moveToWaitFeaturesReply(); + + reset(pipeline); + HandshakeTimeoutHandler newHandler = new HandshakeTimeoutHandler( + handler, + timer, + PipelineHandshakeTimeout.SWITCH); + + expect( + pipeline.replace(EasyMock.eq(PipelineHandler.CHANNEL_HANDSHAKE_TIMEOUT), + EasyMock.eq(PipelineHandler.SWITCH_HANDSHAKE_TIMEOUT), + EasyMock.anyObject(HandshakeTimeoutHandler.class))).andReturn(newHandler) + .once(); + + replay(pipeline); + + reset(newConnectionListener); + newConnectionListener.connectionOpened(capture(newConnection), capture(newFeaturesReply)); + expectLastCall().once(); + replay(newConnectionListener); + + sendMessageToHandlerWithControllerReset(Collections.<OFMessage>singletonList(featuresReply)); + + assertThat(handler.getStateForTesting(), CoreMatchers.instanceOf(OFChannelHandler.CompleteState.class)); + assertTrue("A connection has been created and set", handler.getConnectionForTesting() != null); + + + } + + /** + * Test dispatch of messages while in Complete state + */ + @Test + public void testMessageDispatchComplete() throws Exception { + moveToComplete(); + newConnection.getValue().setListener(connectionListener); + + resetChannel(); + channel.write(capture(writeCapture)); + expectLastCall().andReturn(null).atLeastOnce(); + replay(channel); + + // Send echo request. expect reply + OFMessage echoRequest = factory.buildEchoRequest().build(); + sendMessageToHandlerWithControllerReset(ImmutableList.<OFMessage>of(echoRequest)); + + List<OFMessage> msgs = getMessagesFromCapture(); + assertEquals(1, msgs.size()); + assertEquals(OFType.ECHO_REPLY, msgs.get(0).getType()); + + // Send barrier reply. expect dispatch + OFBarrierReply barrierReply = factory.buildBarrierReply() + .build(); + + resetAndExpectConnectionListener(barrierReply); + + + // Send packet in. expect dispatch + OFFlowRemoved flowRemoved = factory.buildFlowRemoved() + .build(); + + resetAndExpectConnectionListener(flowRemoved); + + // Send get config reply. expect dispatch + OFGetConfigReply getConfigReply = factory.buildGetConfigReply() + .build(); + + resetAndExpectConnectionListener(getConfigReply); + + // Send packet in. expect dispatch + OFPacketIn pi = factory.buildPacketIn() + .setReason(OFPacketInReason.NO_MATCH) + .build(); + + resetAndExpectConnectionListener(pi); + + // Send port status. expect dispatch + OFPortStatus portStatus = factory.buildPortStatus() + .setReason(OFPortReason.DELETE) + .setDesc(portDesc) + .build(); + + resetAndExpectConnectionListener(portStatus); + + // Send queue reply. expect dispatch + OFQueueGetConfigReply queueReply = factory.buildQueueGetConfigReply() + .build(); + + resetAndExpectConnectionListener(queueReply); + + // Send stat reply. expect dispatch + OFFlowStatsReply statReply = factory.buildFlowStatsReply() + .build(); + + resetAndExpectConnectionListener(statReply); + + // Send role reply. expect dispatch + OFNiciraControllerRoleReply roleReply = factory.buildNiciraControllerRoleReply() + .setRole(OFNiciraControllerRole.ROLE_MASTER) + .build(); + + resetAndExpectConnectionListener(roleReply); + + } + + public void resetAndExpectConnectionListener(OFMessage m) throws Exception{ + reset(connectionListener); + connectionListener.messageReceived(handler.getConnectionForTesting(), m); + expectLastCall().once(); + replay(connectionListener); + + sendMessageToHandlerWithControllerReset(Collections.<OFMessage>singletonList(m)); + + verify(connectionListener); + } +} diff --git a/src/test/java/net/floodlightcontroller/core/internal/OFChannelHandlerVer13Test.java b/src/test/java/net/floodlightcontroller/core/internal/OFChannelHandlerVer13Test.java new file mode 100644 index 0000000000000000000000000000000000000000..0703ef8c49a8ecdd052c59c96f176bf16a516ab8 --- /dev/null +++ b/src/test/java/net/floodlightcontroller/core/internal/OFChannelHandlerVer13Test.java @@ -0,0 +1,470 @@ +package net.floodlightcontroller.core.internal; + +import static org.easymock.EasyMock.capture; +import static org.easymock.EasyMock.createMock; +import static org.easymock.EasyMock.expect; +import static org.easymock.EasyMock.expectLastCall; +import static org.easymock.EasyMock.replay; +import static org.easymock.EasyMock.reset; +import static org.easymock.EasyMock.verify; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.EnumSet; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.easymock.Capture; +import org.easymock.CaptureType; +import org.easymock.EasyMock; +import org.hamcrest.CoreMatchers; +import org.jboss.netty.channel.Channel; +import org.jboss.netty.channel.ChannelHandlerContext; +import org.jboss.netty.channel.ChannelPipeline; +import org.jboss.netty.channel.ChannelStateEvent; +import org.jboss.netty.channel.ExceptionEvent; +import org.jboss.netty.channel.MessageEvent; +import org.jboss.netty.util.HashedWheelTimer; +import org.jboss.netty.util.Timer; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import net.floodlightcontroller.core.IOFConnectionBackend; +import net.floodlightcontroller.core.OFConnectionCounters; +import net.floodlightcontroller.core.internal.OpenflowPipelineFactory.PipelineHandler; +import net.floodlightcontroller.core.internal.OpenflowPipelineFactory.PipelineHandshakeTimeout; +import net.floodlightcontroller.debugcounter.DebugCounterServiceImpl; +import net.floodlightcontroller.debugcounter.IDebugCounterService; +import org.projectfloodlight.openflow.protocol.OFBarrierReply; +import org.projectfloodlight.openflow.protocol.OFBsnSetAuxCxnsReply; +import org.projectfloodlight.openflow.protocol.OFCapabilities; +import org.projectfloodlight.openflow.protocol.OFControllerRole; +import org.projectfloodlight.openflow.protocol.OFFactories; +import org.projectfloodlight.openflow.protocol.OFFactory; +import org.projectfloodlight.openflow.protocol.OFFeaturesReply; +import org.projectfloodlight.openflow.protocol.OFFlowRemoved; +import org.projectfloodlight.openflow.protocol.OFFlowStatsReply; +import org.projectfloodlight.openflow.protocol.OFGetConfigReply; +import org.projectfloodlight.openflow.protocol.OFMessage; +import org.projectfloodlight.openflow.protocol.OFPacketIn; +import org.projectfloodlight.openflow.protocol.OFPacketInReason; +import org.projectfloodlight.openflow.protocol.OFPortDesc; +import org.projectfloodlight.openflow.protocol.OFPortReason; +import org.projectfloodlight.openflow.protocol.OFPortStatus; +import org.projectfloodlight.openflow.protocol.OFQueueGetConfigReply; +import org.projectfloodlight.openflow.protocol.OFRoleReply; +import org.projectfloodlight.openflow.protocol.OFType; +import org.projectfloodlight.openflow.protocol.OFVersion; +import org.projectfloodlight.openflow.types.DatapathId; +import org.projectfloodlight.openflow.types.OFAuxId; +import org.projectfloodlight.openflow.types.OFPort; + +import com.google.common.collect.ImmutableList; + + +public class OFChannelHandlerVer13Test { + private static final DatapathId dpid = DatapathId.of(0x42L); + + private IOFSwitchManager switchManager; + private IOFConnectionListener connectionListener; + private INewOFConnectionListener newConnectionListener; + private IDebugCounterService debugCounterService; + private OFChannelHandler handler; + private Channel channel; + private Timer timer; + private ChannelHandlerContext ctx; + private MessageEvent messageEvent; + private ChannelStateEvent channelStateEvent; + private ChannelPipeline pipeline; + private final OFFactory factory = OFFactories.getFactory(OFVersion.OF_13); + + private Capture<ExceptionEvent> exceptionEventCapture; + private Capture<List<OFMessage>> writeCapture; + + private OFFeaturesReply featuresReply; + private OFPortDesc portDesc; + + private Set<Long> seenXids = null; + + private Capture<IOFConnectionBackend> newConnection; + + private Capture<OFFeaturesReply> newFeaturesReply; + + @Before + public void setUpFeaturesReply() { + portDesc = factory.buildPortDesc() + .setName("Eth1") + .setPortNo(OFPort.of(1)) + .build(); + featuresReply = factory.buildFeaturesReply() + .setDatapathId(dpid) + .setNBuffers(1) + .setNTables((short)1) + .setCapabilities(EnumSet.<OFCapabilities>of(OFCapabilities.FLOW_STATS, OFCapabilities.TABLE_STATS)) + .setAuxiliaryId(OFAuxId.MAIN) + .build(); + } + + @Before + public void setUp() throws Exception { + switchManager = createMock(IOFSwitchManager.class); + connectionListener = createMock(IOFConnectionListener.class); + newConnectionListener = createMock(INewOFConnectionListener.class); + newConnection = new Capture<IOFConnectionBackend>(); + newFeaturesReply = new Capture<OFFeaturesReply>(); + + ctx = createMock(ChannelHandlerContext.class); + channelStateEvent = createMock(ChannelStateEvent.class); + channel = createMock(Channel.class); + timer = new HashedWheelTimer(); + messageEvent = createMock(MessageEvent.class); + exceptionEventCapture = new Capture<ExceptionEvent>(CaptureType.ALL); + pipeline = createMock(ChannelPipeline.class); + writeCapture = new Capture<List<OFMessage>>(CaptureType.ALL); + seenXids = null; + + + // TODO: should mock IDebugCounterService and make sure + // the expected counters are updated. + debugCounterService = new DebugCounterServiceImpl(); + debugCounterService.registerModule(OFConnectionCounters.COUNTER_MODULE); + SwitchManagerCounters counters = + new SwitchManagerCounters(debugCounterService); + expect(switchManager.getCounters()).andReturn(counters).anyTimes(); + replay(switchManager); + handler = new OFChannelHandler(switchManager, newConnectionListener, + pipeline, debugCounterService, timer); + verify(switchManager); + reset(switchManager); + + resetChannel(); + + // replay controller. Reset it if you need more specific behavior + replay(switchManager); + + // Mock ctx and channelStateEvent + expect(ctx.getChannel()).andReturn(channel).anyTimes(); + expect(channelStateEvent.getChannel()).andReturn(channel).anyTimes(); + replay(ctx, channelStateEvent); + + /* Setup an exception event capture on the channel. Right now + * we only expect exception events to be send up the channel. + * However, it's easy to extend to other events if we need it + */ + pipeline.sendUpstream(capture(exceptionEventCapture)); + expectLastCall().anyTimes(); + expect(pipeline.get(OFMessageDecoder.class)).andReturn(new OFMessageDecoder()).anyTimes(); + replay(pipeline); + } + + @After + public void tearDown() { + /* ensure no exception was thrown */ + if (exceptionEventCapture.hasCaptured()) { + Throwable ex = exceptionEventCapture.getValue().getCause(); + ex.printStackTrace(); + throw new AssertionError("Unexpected exception: " + + ex.getClass().getName() + "(" + ex + ")"); + } + assertFalse("Unexpected messages have been captured", + writeCapture.hasCaptured()); + // verify all mocks. + verify(channel); + verify(messageEvent); + verify(switchManager); + verify(ctx); + verify(channelStateEvent); + verify(pipeline); + } + + /** Reset the channel mock and set basic method call expectations */ + void resetChannel() { + reset(channel); + expect(channel.getPipeline()).andReturn(pipeline).anyTimes(); + expect(channel.getRemoteAddress()).andReturn(null).anyTimes(); + } + + + /** reset, setup, and replay the messageEvent mock for the given + * messages + */ + void setupMessageEvent(List<OFMessage> messages) { + reset(messageEvent); + expect(messageEvent.getMessage()).andReturn(messages).atLeastOnce(); + replay(messageEvent); + } + + + /** reset, setup, and replay the messageEvent mock for the given + * messages, mock controller send message to channel handler + * + * This method will reset, start replay on controller, and then verify + */ + void sendMessageToHandlerWithControllerReset(List<OFMessage> messages) + throws Exception { + + sendMessageToHandlerNoControllerReset(messages); + } + + /** reset, setup, and replay the messageEvent mock for the given + * messages, mock controller send message to channel handler + * + * This method will start replay on controller, and then verify + */ + void sendMessageToHandlerNoControllerReset(List<OFMessage> messages) + throws Exception { + setupMessageEvent(messages); + + handler.messageReceived(ctx, messageEvent); + } + + /** + * Extract the list of OFMessages that was captured by the Channel.write() + * capture. Will check that something was actually captured first. We'll + * collapse the messages from multiple writes into a single list of + * OFMessages. + * Resets the channelWriteCapture. + */ + List<OFMessage> getMessagesFromCapture() { + List<OFMessage> msgs = new ArrayList<OFMessage>(); + + assertTrue("No write on channel was captured", + writeCapture.hasCaptured()); + List<List<OFMessage>> capturedVals = writeCapture.getValues(); + + for (List<OFMessage> oneWriteList: capturedVals) + msgs.addAll(oneWriteList); + writeCapture.reset(); + return msgs; + } + + + /** + * Verify that the given exception event capture (as returned by + * getAndInitExceptionCapture) has thrown an exception of the given + * expectedExceptionClass. + * Resets the capture + */ + void verifyExceptionCaptured( + Class<? extends Throwable> expectedExceptionClass) { + assertTrue("Excpected exception not thrown", + exceptionEventCapture.hasCaptured()); + Throwable caughtEx = exceptionEventCapture.getValue().getCause(); + assertEquals(expectedExceptionClass, caughtEx.getClass()); + exceptionEventCapture.reset(); + } + + /** make sure that the transaction ids in the given messages are + * not 0 and differ between each other. + * While it's not a defect per se if the xids are we want to ensure + * we use different ones for each message we send. + */ + void verifyUniqueXids(List<OFMessage> msgs) { + if (seenXids == null) + seenXids = new HashSet<Long>(); + for (OFMessage m: msgs) { + long xid = m.getXid(); + assertTrue("Xid in messags is 0", xid != 0); + assertFalse("Xid " + xid + " has already been used", + seenXids.contains(xid)); + seenXids.add(xid); + } + } + + + @Test + public void testInitState() throws Exception { + // Message event needs to be list + expect(messageEvent.getMessage()).andReturn(null); + replay(channel, messageEvent); + handler.messageReceived(ctx, messageEvent); + verify(channel, messageEvent); + verifyExceptionCaptured(AssertionError.class); + + // We don't expect to receive /any/ messages in init state since + // channelConnected moves us to a different state + OFMessage m = factory.buildHello().build(); + sendMessageToHandlerWithControllerReset(ImmutableList.<OFMessage>of(m)); + + verifyExceptionCaptured(SwitchStateException.class); + assertThat(handler.getStateForTesting(), CoreMatchers.instanceOf(OFChannelHandler.InitState.class)); + } + + /* Move the channel from scratch to WAIT_HELLO state */ + @Test + public void moveToWaitHello() throws Exception { + resetChannel(); + channel.write(capture(writeCapture)); + expectLastCall().andReturn(null).once(); + replay(channel); + // replay unused mocks + replay(messageEvent); + + handler.channelConnected(ctx, channelStateEvent); + + List<OFMessage> msgs = getMessagesFromCapture(); + assertEquals(1, msgs.size()); + assertEquals(OFType.HELLO, msgs.get(0).getType()); + assertThat(handler.getStateForTesting(), CoreMatchers.instanceOf(OFChannelHandler.WaitHelloState.class)); + verifyUniqueXids(msgs); + } + + /** Move the channel from scratch to WAIT_FEATURES_REPLY state + * Builds on moveToWaitHello() + * adds testing for WAIT_HELLO state + */ + @Test + public void moveToWaitFeaturesReply() throws Exception { + moveToWaitHello(); + resetChannel(); + channel.write(capture(writeCapture)); + expectLastCall().andReturn(null).atLeastOnce(); + replay(channel); + + OFMessage hello = factory.buildHello().build(); + sendMessageToHandlerWithControllerReset(ImmutableList.<OFMessage>of(hello)); + + List<OFMessage> msgs = getMessagesFromCapture(); + assertEquals(1, msgs.size()); + assertEquals(OFType.FEATURES_REQUEST, msgs.get(0).getType()); + verifyUniqueXids(msgs); + + assertThat(handler.getStateForTesting(), CoreMatchers.instanceOf(OFChannelHandler.WaitFeaturesReplyState.class)); + } + + + /** Move the channel from scratch to WAIT_FEATURES_REPLY state + * Builds on moveToWaitHello() + * adds testing for WAIT_HELLO state + */ + @Test + public void moveToComplete() throws Exception { + moveToWaitFeaturesReply(); + + reset(pipeline); + HandshakeTimeoutHandler newHandler = new HandshakeTimeoutHandler( + handler, + timer, + PipelineHandshakeTimeout.SWITCH); + + expect( + pipeline.replace(EasyMock.eq(PipelineHandler.CHANNEL_HANDSHAKE_TIMEOUT), + EasyMock.eq(PipelineHandler.SWITCH_HANDSHAKE_TIMEOUT), + EasyMock.anyObject(HandshakeTimeoutHandler.class))).andReturn(newHandler) + .once(); + + replay(pipeline); + + newConnectionListener.connectionOpened(capture(newConnection), capture(newFeaturesReply)); + expectLastCall().once(); + replay(newConnectionListener); + + sendMessageToHandlerWithControllerReset(Collections.<OFMessage>singletonList(featuresReply)); + + assertThat(handler.getStateForTesting(), CoreMatchers.instanceOf(OFChannelHandler.CompleteState.class)); + assertTrue("A connection has been created and set", handler.getConnectionForTesting() != null); + verify(newConnectionListener); + assertTrue(newConnection.hasCaptured()); + assertThat(newFeaturesReply.getValue(), equalTo(featuresReply)); + } + + /** + * Test dispatch of messages while in Complete state + */ + @Test + public void testMessageDispatchComplete() throws Exception { + moveToComplete(); + newConnection.getValue().setListener(connectionListener); + + resetChannel(); + channel.write(capture(writeCapture)); + expectLastCall().andReturn(null).atLeastOnce(); + replay(channel); + + // Send echo request. expect reply + OFMessage echoRequest = factory.buildEchoRequest().build(); + sendMessageToHandlerWithControllerReset(ImmutableList.<OFMessage>of(echoRequest)); + + List<OFMessage> msgs = getMessagesFromCapture(); + assertEquals(1, msgs.size()); + assertEquals(OFType.ECHO_REPLY, msgs.get(0).getType()); + + + // Send barrier reply. expect dispatch + OFBarrierReply barrierReply = factory.buildBarrierReply() + .build(); + + resetAndExpectConnectionListener(barrierReply); + + + // Send packet in. expect dispatch + OFFlowRemoved flowRemoved = factory.buildFlowRemoved() + .build(); + + resetAndExpectConnectionListener(flowRemoved); + + // Send get config reply. expect dispatch + OFGetConfigReply getConfigReply = factory.buildGetConfigReply() + .build(); + + resetAndExpectConnectionListener(getConfigReply); + + // Send packet in. expect dispatch + OFPacketIn pi = factory.buildPacketIn() + .setReason(OFPacketInReason.NO_MATCH) + .build(); + + resetAndExpectConnectionListener(pi); + + // Send port status. expect dispatch + OFPortStatus portStatus = factory.buildPortStatus() + .setReason(OFPortReason.DELETE) + .setDesc(portDesc) + .build(); + + resetAndExpectConnectionListener(portStatus); + + // Send queue reply. expect dispatch + OFQueueGetConfigReply queueReply = factory.buildQueueGetConfigReply() + .build(); + + resetAndExpectConnectionListener(queueReply); + + // Send stat reply. expect dispatch + OFFlowStatsReply statReply = factory.buildFlowStatsReply() + .build(); + + resetAndExpectConnectionListener(statReply); + + // Send role reply. expect dispatch + OFRoleReply roleReply = factory.buildRoleReply() + .setRole(OFControllerRole.ROLE_MASTER) + .build(); + + resetAndExpectConnectionListener(roleReply); + + // Send experimenter. expect dispatch + OFBsnSetAuxCxnsReply auxReply = factory.buildBsnSetAuxCxnsReply() + .build(); + + resetAndExpectConnectionListener(auxReply); + + } + + public void resetAndExpectConnectionListener(OFMessage m) throws Exception{ + reset(connectionListener); + connectionListener.messageReceived(handler.getConnectionForTesting(), m); + expectLastCall().once(); + replay(connectionListener); + + sendMessageToHandlerWithControllerReset(Collections.<OFMessage>singletonList(m)); + + verify(connectionListener); + } +} diff --git a/src/test/java/net/floodlightcontroller/core/internal/OFStatisticsFutureTest.java b/src/test/java/net/floodlightcontroller/core/internal/OFStatisticsFutureTest.java deleted file mode 100644 index 15dc9f401052962dde4f1df3ce3111599f87d1e4..0000000000000000000000000000000000000000 --- a/src/test/java/net/floodlightcontroller/core/internal/OFStatisticsFutureTest.java +++ /dev/null @@ -1,149 +0,0 @@ -package net.floodlightcontroller.core.internal; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; - -import net.floodlightcontroller.core.IOFSwitch; -import net.floodlightcontroller.core.test.MockThreadPoolService; - -import org.junit.Before; -import org.junit.Test; -import static org.junit.Assert.*; -import org.openflow.protocol.OFStatisticsReply; -import org.openflow.protocol.statistics.OFFlowStatisticsReply; -import org.openflow.protocol.statistics.OFStatistics; -import org.openflow.protocol.statistics.OFStatisticsType; - -import static org.easymock.EasyMock.*; - -public class OFStatisticsFutureTest { - private MockThreadPoolService tp; - - @Before - public void setUp() { - tp = new MockThreadPoolService(); - } - - private OFStatisticsReply getStatisticsReply(int transactionId, - int count, boolean moreReplies) { - OFStatisticsReply sr = new OFStatisticsReply(); - sr.setXid(transactionId); - sr.setStatisticType(OFStatisticsType.FLOW); - List<OFStatistics> statistics = new ArrayList<OFStatistics>(); - for (int i = 0; i < count; ++i) { - statistics.add(new OFFlowStatisticsReply()); - } - sr.setStatistics(statistics); - if (moreReplies) - sr.setFlags((short) 1); - return sr; - } - - public class FutureFetcher<E> implements Runnable { - public E value; - public Future<E> future; - - public FutureFetcher(Future<E> future) { - this.future = future; - } - - @Override - public void run() { - try { - value = future.get(); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - /** - * @return the value - */ - public E getValue() { - return value; - } - - /** - * @return the future - */ - public Future<E> getFuture() { - return future; - } - } - - /** - * - * @throws Exception - */ - @Test - public void testOFStatisticsFuture() throws Exception { - // Test for a single stats reply - IOFSwitch sw = createMock(IOFSwitch.class); - sw.cancelStatisticsReply(1); - OFStatisticsFuture sf = new OFStatisticsFuture(tp, sw, 1); - - replay(sw); - List<OFStatistics> stats; - FutureFetcher<List<OFStatistics>> ff = new FutureFetcher<List<OFStatistics>>(sf); - Thread t = new Thread(ff); - t.start(); - sf.deliverFuture(sw, getStatisticsReply(1, 10, false)); - - t.join(); - stats = ff.getValue(); - verify(sw); - assertEquals(10, stats.size()); - - // Test multiple stats replies - reset(sw); - sw.cancelStatisticsReply(1); - - sf = new OFStatisticsFuture(tp, sw, 1); - - replay(sw); - ff = new FutureFetcher<List<OFStatistics>>(sf); - t = new Thread(ff); - t.start(); - sf.deliverFuture(sw, getStatisticsReply(1, 10, true)); - sf.deliverFuture(sw, getStatisticsReply(1, 5, false)); - t.join(); - - stats = sf.get(); - verify(sw); - assertEquals(15, stats.size()); - - // Test cancellation - reset(sw); - sw.cancelStatisticsReply(1); - sf = new OFStatisticsFuture(tp, sw, 1); - - replay(sw); - ff = new FutureFetcher<List<OFStatistics>>(sf); - t = new Thread(ff); - t.start(); - sf.cancel(true); - t.join(); - - stats = sf.get(); - verify(sw); - assertEquals(0, stats.size()); - - // Test self timeout - reset(sw); - sw.cancelStatisticsReply(1); - sf = new OFStatisticsFuture(tp, sw, 1, 75, TimeUnit.MILLISECONDS); - - replay(sw); - ff = new FutureFetcher<List<OFStatistics>>(sf); - t = new Thread(ff); - t.start(); - t.join(2000); - - stats = sf.get(); - verify(sw); - assertEquals(0, stats.size()); - } - -} diff --git a/src/test/java/net/floodlightcontroller/core/internal/OFSwitchHandlerTestBase.java b/src/test/java/net/floodlightcontroller/core/internal/OFSwitchHandlerTestBase.java new file mode 100644 index 0000000000000000000000000000000000000000..f2b4bfcf82c591d363408ce694b72fe791701d47 --- /dev/null +++ b/src/test/java/net/floodlightcontroller/core/internal/OFSwitchHandlerTestBase.java @@ -0,0 +1,1102 @@ +package net.floodlightcontroller.core.internal; + +import static org.easymock.EasyMock.anyLong; +import static org.easymock.EasyMock.anyObject; +import static org.easymock.EasyMock.createMock; +import static org.easymock.EasyMock.expect; +import static org.easymock.EasyMock.expectLastCall; +import static org.easymock.EasyMock.replay; +import static org.easymock.EasyMock.reset; +import static org.easymock.EasyMock.resetToStrict; +import static org.easymock.EasyMock.verify; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.IOException; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import org.easymock.EasyMock; +import org.hamcrest.CoreMatchers; +import org.hamcrest.Matchers; +import org.jboss.netty.util.Timeout; +import org.jboss.netty.util.Timer; +import org.jboss.netty.util.TimerTask; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import net.floodlightcontroller.core.HARole; +import net.floodlightcontroller.core.IOFSwitch; +import net.floodlightcontroller.core.IOFSwitch.SwitchStatus; +import net.floodlightcontroller.core.IOFSwitchBackend; +import net.floodlightcontroller.core.PortChangeEvent; +import net.floodlightcontroller.core.PortChangeType; +import net.floodlightcontroller.core.internal.OFSwitchAppHandshakePlugin.PluginResultType; +import net.floodlightcontroller.core.internal.OFSwitchHandshakeHandler.QuarantineState; +import net.floodlightcontroller.core.internal.OFSwitchHandshakeHandler.WaitAppHandshakeState; +import net.floodlightcontroller.debugcounter.DebugCounterServiceImpl; +import net.floodlightcontroller.debugcounter.IDebugCounterService; +import org.projectfloodlight.openflow.protocol.OFBadActionCode; +import org.projectfloodlight.openflow.protocol.OFBadRequestCode; +import org.projectfloodlight.openflow.protocol.OFControllerRole; +import org.projectfloodlight.openflow.protocol.OFDescStatsReply; +import org.projectfloodlight.openflow.protocol.OFErrorMsg; +import org.projectfloodlight.openflow.protocol.OFFactory; +import org.projectfloodlight.openflow.protocol.OFFeaturesReply; +import org.projectfloodlight.openflow.protocol.OFGetConfigReply; +import org.projectfloodlight.openflow.protocol.OFMessage; +import org.projectfloodlight.openflow.protocol.OFPacketIn; +import org.projectfloodlight.openflow.protocol.OFPacketInReason; +import org.projectfloodlight.openflow.protocol.OFPortDesc; +import org.projectfloodlight.openflow.protocol.OFPortReason; +import org.projectfloodlight.openflow.protocol.OFPortStatus; +import org.projectfloodlight.openflow.protocol.OFSetConfig; +import org.projectfloodlight.openflow.protocol.OFStatsRequest; +import org.projectfloodlight.openflow.protocol.OFStatsType; +import org.projectfloodlight.openflow.protocol.OFType; +import org.projectfloodlight.openflow.protocol.match.Match; +import org.projectfloodlight.openflow.types.DatapathId; +import org.projectfloodlight.openflow.types.OFAuxId; +import org.projectfloodlight.openflow.types.OFPort; +import net.floodlightcontroller.util.LinkedHashSetWrapper; +import net.floodlightcontroller.util.OrderedCollection; + +import com.google.common.collect.ImmutableList; + + +public abstract class OFSwitchHandlerTestBase { + protected static final DatapathId dpid = DatapathId.of(0x42L); + + protected IOFSwitchManager switchManager; + protected RoleManager roleManager; + + private IDebugCounterService debugCounterService; + protected OFSwitchHandshakeHandler switchHandler; + protected MockOFConnection connection; + // Use a 1.0 factory for the 1.0 test + protected final OFFactory factory = getFactory(); + + protected OFFeaturesReply featuresReply; + protected List<IAppHandshakePluginFactory> plugins; + + private HashSet<Long> seenXids = null; + protected IOFSwitchBackend sw; + private Timer timer; + private TestHandshakePlugin handshakePlugin; + + private class TestHandshakePlugin extends OFSwitchAppHandshakePlugin { + protected TestHandshakePlugin(PluginResult defaultResult, int timeoutS) { + super(defaultResult, timeoutS); + } + + @Override + protected void processOFMessage(OFMessage m) { + } + + @Override + protected void enterPlugin() { + } + } + + @Before + public void setUpFeaturesReply() { + getFeaturesReply(); + this.featuresReply = getFeaturesReply(); + + // Plugin set + IAppHandshakePluginFactory factory = createMock(IAppHandshakePluginFactory.class); + PluginResult result = new PluginResult(PluginResultType.QUARANTINE, "test quarantine"); + handshakePlugin = new TestHandshakePlugin(result, 5); + expect(factory.createPlugin()).andReturn(handshakePlugin).anyTimes(); + replay(factory); + plugins = ImmutableList.of(factory); + } + + + @Before + public void setUp() throws Exception { + switchManager = createMock(IOFSwitchManager.class); + roleManager = createMock(RoleManager.class); + sw = createMock(IOFSwitchBackend.class); + timer = createMock(Timer.class); + expect(timer.newTimeout(anyObject(TimerTask.class), anyLong(), anyObject(TimeUnit.class))).andReturn(EasyMock.createNiceMock(Timeout.class)); + replay(timer); + seenXids = null; + + // TODO: should mock IDebugCounterService and make sure + // the expected counters are updated. + debugCounterService = new DebugCounterServiceImpl(); + SwitchManagerCounters counters = + new SwitchManagerCounters(debugCounterService); + expect(switchManager.getCounters()).andReturn(counters).anyTimes(); + replay(switchManager); + connection = new MockOFConnection(featuresReply.getDatapathId(), OFAuxId.MAIN); + switchHandler = new OFSwitchHandshakeHandler(connection, featuresReply, switchManager, roleManager, timer); + + // replay sw. Reset it if you need more specific behavior + replay(sw); + } + + + @After + public void tearDown() { + verifyAll(); + } + + private void verifyAll() { + assertThat("Unexpected messages have been captured", + connection.getMessages(), + Matchers.empty()); + // verify all mocks. + verify(sw); + } + + void verifyUniqueXids(OFMessage... msgs) { + verifyUniqueXids(Arrays.asList(msgs)); + } + + /** make sure that the transaction ids in the given messages are + * not 0 and differ between each other. + * While it's not a defect per se if the xids are we want to ensure + * we use different ones for each message we send. + */ + void verifyUniqueXids(List<OFMessage> msgs) { + if (seenXids == null) + seenXids = new HashSet<Long>(); + for (OFMessage m: msgs) { + long xid = m.getXid(); + assertTrue("Xid in messags is 0", xid != 0); + assertFalse("Xid " + xid + " has already been used", + seenXids.contains(xid)); + seenXids.add(xid); + } + } + + + /*************************** abstract phases / utilities to be filled in by the subclasses */ + + // Factory + messages + + /** @return the version-appropriate factory */ + public abstract OFFactory getFactory(); + + /** + * @return a version appropriate features reply (different in 1.3 because it + * doesn't have ports) + */ + abstract OFFeaturesReply getFeaturesReply(); + /** @return the class that's used for role requests/replies (OFNiciraRoleRequest vs. + * OFRequest) + */ + + /// Role differences + + abstract Class<?> getRoleRequestClass(); + /** Verify that the given OFMessage is a correct RoleRequest message + * for the given role using the given xid (for the version). + */ + public abstract void verifyRoleRequest(OFMessage m, + OFControllerRole expectedRole); + /** Return a RoleReply message for the given role */ + protected abstract OFMessage getRoleReply(long xid, OFControllerRole role); + + /// Difference in the handshake sequence + + /** OF1.3 has the PortDescStatsRequest, OF1.0 not */ + abstract void moveToPreConfigReply() throws Exception; + /** + * Move the channel from scratch to WaitAppHandshakeState + * Different for OF1.0 and OF1.3 because of GenTables. + * @throws Exception + */ + @Test + public abstract void moveToWaitAppHandshakeState() throws Exception; + + /** + * Move the channel from scratch to WaitSwitchDriverSubHandshake + * Different for OF1.0 and OF1.3 because of GenTables. + * @throws Exception + */ + @Test + public abstract void moveToWaitSwitchDriverSubHandshake() throws Exception; + + /** + * Move the channel from scratch to WaitInitialRole + * Different for OF1.0 and OF1.3 because of Controller Connections. + * @throws Exception + */ + @Test + public abstract void moveToWaitInitialRole() throws Exception; + + /*******************************************************************************************/ + + + /** Move the channel from scratch to INIT state + This occurs upon creation of the switch handler + */ + @Test + public void testInitState() throws Exception { + assertThat(connection.getListener(), notNullValue()); + assertThat(switchHandler.getStateForTesting(), CoreMatchers.instanceOf(OFSwitchHandshakeHandler.InitState.class)); + } + + + /** Move the channel from scratch to WAIT_CONFIG_REPLY state + * adds testing for beginHandshake() which moves the state from + * InitState to WaitConfigReply. + */ + @Test + public void moveToWaitConfigReply() throws Exception { + moveToPreConfigReply(); + + List<OFMessage> msgs = connection.getMessages(); + assertEquals(3, msgs.size()); + assertEquals(OFType.SET_CONFIG, msgs.get(0).getType()); + OFSetConfig sc = (OFSetConfig)msgs.get(0); + assertEquals(0xffff, sc.getMissSendLen()); + assertEquals(OFType.BARRIER_REQUEST, msgs.get(1).getType()); + assertEquals(OFType.GET_CONFIG_REQUEST, msgs.get(2).getType()); + verifyUniqueXids(msgs); + msgs.clear(); + assertThat(switchHandler.getStateForTesting(), CoreMatchers.instanceOf(OFSwitchHandshakeHandler.WaitConfigReplyState.class)); + verifyAll(); + } + + + + /** Move the channel from scratch to WAIT_DESCRIPTION_STAT_REPLY state + * Builds on moveToWaitConfigReply() + * adds testing for WAIT_CONFIG_REPLY state + */ + @Test + public void moveToWaitDescriptionStatReply() throws Exception { + moveToWaitConfigReply(); + + connection.clearMessages(); + OFGetConfigReply cr = factory.buildGetConfigReply() + .setMissSendLen(0xFFFF) + .build(); + + switchHandler.processOFMessage(cr); + + OFMessage msg = connection.retrieveMessage(); + assertEquals(OFType.STATS_REQUEST, msg.getType()); + OFStatsRequest<?> sr = (OFStatsRequest<?>)msg; + assertEquals(OFStatsType.DESC, sr.getStatsType()); + verifyUniqueXids(msg); + assertThat(switchHandler.getStateForTesting(), CoreMatchers.instanceOf(OFSwitchHandshakeHandler.WaitDescriptionStatReplyState.class)); + } + + protected OFDescStatsReply createDescriptionStatsReply() { + OFDescStatsReply statsReply = factory.buildDescStatsReply() + .setDpDesc("Datapath Description") + .setHwDesc("Hardware Description") + .setMfrDesc("Manufacturer Description") + .setSwDesc("Software Description") + .setSerialNum("Serial Number") + .build(); + return statsReply; + } + + /** + * setup the expectations for the mock switch that are needed + * after the switch is instantiated in the WAIT_DESCRIPTION_STATS STATE + * Will reset the switch + * @throws CounterException + */ + protected void setupSwitchForInstantiationWithReset() + throws Exception { + reset(sw); + sw.setFeaturesReply(featuresReply); + expectLastCall().once(); + } + + /** + * Tests a situation where a switch returns a QUARANTINE result. This means + * we should move the handshake handler to a quarantine state and also + * quarantine the switch in the controller. + * + * @throws Exception + */ + @Test + public void moveQuarantine() throws Exception { + moveToWaitAppHandshakeState(); + + reset(switchManager); + switchManager.switchStatusChanged(sw, SwitchStatus.HANDSHAKE, SwitchStatus.QUARANTINED); + expectLastCall().once(); + replay(switchManager); + + assertThat(switchHandler.getStateForTesting(), CoreMatchers.instanceOf(WaitAppHandshakeState.class)); + WaitAppHandshakeState state = (WaitAppHandshakeState) switchHandler.getStateForTesting(); + assertThat(state.getCurrentPlugin(), CoreMatchers.<OFSwitchAppHandshakePlugin>equalTo(handshakePlugin)); + + reset(sw); + expect(sw.getStatus()).andReturn(SwitchStatus.HANDSHAKE); + sw.setStatus(SwitchStatus.QUARANTINED); + expectLastCall().once(); + replay(sw); + + PluginResult result = new PluginResult(PluginResultType.QUARANTINE, "test quarantine"); + handshakePlugin.exitPlugin(result); + + assertThat(switchHandler.getStateForTesting(), + CoreMatchers.instanceOf(QuarantineState.class)); + verify(switchManager); + } + + /** + * Tests a situation where a plugin returns a DISCONNECT result. This means + * we should disconnect the connection and the state should not change. + * + * @throws Exception + */ + @Test + public void failedAppHandshake() throws Exception { + moveToWaitAppHandshakeState(); + + assertThat(switchHandler.getStateForTesting(), + CoreMatchers.instanceOf(WaitAppHandshakeState.class)); + + WaitAppHandshakeState state = (WaitAppHandshakeState) switchHandler.getStateForTesting(); + assertThat(state.getCurrentPlugin(), CoreMatchers.<OFSwitchAppHandshakePlugin>equalTo(handshakePlugin)); + + PluginResult result = new PluginResult(PluginResultType.DISCONNECT); + handshakePlugin.exitPlugin(result); + + assertThat(connection.isConnected(), equalTo(false)); + } + + + @Test + public void validAppHandshakePluginReason() throws Exception { + try{ + new PluginResult(PluginResultType.QUARANTINE,"This should not cause an exception"); + }catch(IllegalStateException e) { + fail("This should cause an illegal state exception"); + } + } + + @Test + public void invalidAppHandshakePluginReason() throws Exception { + try{ + new PluginResult(PluginResultType.CONTINUE,"This should cause an exception"); + fail("This should cause an illegal state exception"); + }catch(IllegalStateException e) { /* Expected */ } + + try{ + new PluginResult(PluginResultType.DISCONNECT,"This should cause an exception"); + fail("This should cause an illegal state exception"); + }catch(IllegalStateException e) { /* Expected */ } + } + + /** + * Move the channel from scratch to WAIT_INITIAL_ROLE state via + * WAIT_SWITCH_DRIVER_SUB_HANDSHAKE + * Does extensive testing for the WAIT_SWITCH_DRIVER_SUB_HANDSHAKE state + * + */ + @Test + public void testSwitchDriverSubHandshake() + throws Exception { + moveToWaitSwitchDriverSubHandshake(); + + //------------------------------------------------- + //------------------------------------------------- + // Send a message to the handler, it should be passed to the + // switch's sub-handshake handling. After this message the + // sub-handshake will be complete + // FIXME:LOJI: With Andi's fix for a default Match object we won't + // need to build/set this match object + + Match match = factory.buildMatch().build(); + OFMessage m = factory.buildFlowRemoved().setMatch(match).build(); + resetToStrict(sw); + sw.processDriverHandshakeMessage(m); + expectLastCall().once(); + expect(sw.isDriverHandshakeComplete()).andReturn(true).once(); + replay(sw); + + switchHandler.processOFMessage(m); + + assertThat(switchHandler.getStateForTesting(), + CoreMatchers.instanceOf(OFSwitchHandshakeHandler.WaitAppHandshakeState.class)); + assertThat("Unexpected message captured", connection.getMessages(), Matchers.empty()); + verify(sw); + } + + @Test + /** Test WaitDescriptionReplyState */ + public void testWaitDescriptionReplyState() throws Exception { + moveToWaitInitialRole(); + } + + /** + * Setup the mock switch and write capture for a role request, set the + * role and verify mocks. + * @param supportsNxRole whether the switch supports role request messages + * to setup the attribute. This must be null (don't yet know if roles + * supported: send to check) or true. + * @param role The role to send + * @throws IOException + */ + private long setupSwitchSendRoleRequestAndVerify(Boolean supportsNxRole, + OFControllerRole role) throws IOException { + assertTrue("This internal test helper method most not be called " + + "with supportsNxRole==false. Test setup broken", + supportsNxRole == null || supportsNxRole == true); + reset(sw); + expect(sw.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE)) + .andReturn(supportsNxRole).atLeastOnce(); + replay(sw); + + switchHandler.sendRoleRequest(role); + + OFMessage msg = connection.retrieveMessage(); + verifyRoleRequest(msg, role); + verify(sw); + return msg.getXid(); + } + + + /** + * Setup the mock switch for a role change request where the switch + * does not support roles. + * + * Needs to verify and reset the controller since we need to set + * an expectation + */ + private void setupSwitchRoleChangeUnsupported(int xid, + OFControllerRole role) { + SwitchStatus newStatus = role != OFControllerRole.ROLE_SLAVE ? SwitchStatus.MASTER : SwitchStatus.SLAVE; + boolean supportsNxRole = false; + verify(switchManager); + reset(sw, switchManager); + expect(sw.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE)) + .andReturn(supportsNxRole).atLeastOnce(); + // TODO: hmmm. While it's not incorrect that we set the attribute + // again it looks odd. Maybe change + sw.setAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE, supportsNxRole); + expectLastCall().anyTimes(); + sw.setControllerRole(role); + expectLastCall().once(); + + if (role == OFControllerRole.ROLE_SLAVE) { + sw.disconnect(); + expectLastCall().once(); + } else { + expect(sw.getStatus()).andReturn(SwitchStatus.HANDSHAKE).once(); + sw.setStatus(newStatus); + expectLastCall().once(); + switchManager.switchStatusChanged(sw, SwitchStatus.HANDSHAKE, newStatus); + } + replay(sw, switchManager); + + switchHandler.sendRoleRequest(role); + + verify(sw, switchManager); + } + + /** Return a bad request error message with the given xid/code */ + private OFMessage getBadRequestErrorMessage(OFBadRequestCode code, long xid) { + OFErrorMsg msg = factory.errorMsgs().buildBadRequestErrorMsg() + .setXid(xid) + .setCode(code) + .build(); + return msg; + } + + /** Return a bad action error message with the given xid/code */ + private OFMessage getBadActionErrorMessage(OFBadActionCode code, long xid) { + OFErrorMsg msg = factory.errorMsgs().buildBadActionErrorMsg() + .setXid(xid) + .setCode(code) + .build(); + return msg; + } + + + /** Move the channel from scratch to MASTER state + * Builds on doMoveToWaitInitialRole() + * adds testing for WAIT_INITAL_ROLE state + * + * This method tests only the simple case that the switch supports roles + * and transitions to MASTER + */ + @Test + public void testInitialMoveToMasterWithRole() throws Exception { + // first, move us to WAIT_INITIAL_ROLE_STATE + moveToWaitInitialRole(); + assertThat(switchHandler.getStateForTesting(), CoreMatchers.instanceOf(OFSwitchHandshakeHandler.WaitInitialRoleState.class)); + + // Set the role + long xid = setupSwitchSendRoleRequestAndVerify(null, OFControllerRole.ROLE_MASTER); + assertThat(switchHandler.getStateForTesting(), CoreMatchers.instanceOf(OFSwitchHandshakeHandler.WaitInitialRoleState.class)); + + // prepare mocks and inject the role reply message + reset(sw); + sw.setAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE, true); + expectLastCall().once(); + sw.setControllerRole(OFControllerRole.ROLE_MASTER); + expectLastCall().once(); + expect(sw.getStatus()).andReturn(SwitchStatus.HANDSHAKE).once(); + sw.setStatus(SwitchStatus.MASTER); + expectLastCall().once(); + replay(sw); + + reset(switchManager); + switchManager.switchStatusChanged(sw, SwitchStatus.HANDSHAKE, SwitchStatus.MASTER); + expectLastCall().once(); + replay(switchManager); + OFMessage reply = getRoleReply(xid, OFControllerRole.ROLE_MASTER); + + // sendMessageToHandler will verify and rest controller mock + switchHandler.processOFMessage(reply); + + assertThat(switchHandler.getStateForTesting(), CoreMatchers.instanceOf(OFSwitchHandshakeHandler.MasterState.class)); + } + + /** Move the channel from scratch to SLAVE state + * Builds on doMoveToWaitInitialRole() + * adds testing for WAIT_INITAL_ROLE state + * + * This method tests only the simple case that the switch supports roles + * and transitions to SLAVE + */ + @Test + public void testInitialMoveToSlaveWithRole() throws Exception { + + // first, move us to WAIT_INITIAL_ROLE_STATE + moveToWaitInitialRole(); + assertThat(switchHandler.getStateForTesting(), CoreMatchers.instanceOf(OFSwitchHandshakeHandler.WaitInitialRoleState.class)); + + // Set the role + long xid = setupSwitchSendRoleRequestAndVerify(null, OFControllerRole.ROLE_SLAVE); + assertThat(switchHandler.getStateForTesting(), CoreMatchers.instanceOf(OFSwitchHandshakeHandler.WaitInitialRoleState.class)); + + // prepare mocks and inject the role reply message + reset(sw); + sw.setAttribute(IOFSwitchBackend.SWITCH_SUPPORTS_NX_ROLE, true); + expectLastCall().once(); + sw.setControllerRole(OFControllerRole.ROLE_SLAVE); + expectLastCall().once(); + expect(sw.getStatus()).andReturn(SwitchStatus.HANDSHAKE).once(); + sw.setStatus(SwitchStatus.SLAVE); + expectLastCall().once(); + replay(sw); + + reset(switchManager); + switchManager.switchStatusChanged(sw, SwitchStatus.HANDSHAKE, SwitchStatus.SLAVE); + expectLastCall().once(); + replay(switchManager); + + OFMessage reply = getRoleReply(xid, OFControllerRole.ROLE_SLAVE); + + // sendMessageToHandler will verify and rest controller mock + switchHandler.processOFMessage(reply); + + assertThat(switchHandler.getStateForTesting(), CoreMatchers.instanceOf(OFSwitchHandshakeHandler.SlaveState.class)); + } + + /** Move the channel from scratch to MASTER state + * Builds on doMoveToWaitInitialRole() + * adds testing for WAIT_INITAL_ROLE state + * + * This method tests the case that the switch does NOT support roles. + * The channel handler still needs to send the initial request to find + * out that whether the switch supports roles. + */ + @Test + public void testInitialMoveToMasterNoRole() throws Exception { + // first, move us to WAIT_INITIAL_ROLE_STATE + moveToWaitInitialRole(); + assertThat(switchHandler.getStateForTesting(), CoreMatchers.instanceOf(OFSwitchHandshakeHandler.WaitInitialRoleState.class)); + + // Set the role + long xid = setupSwitchSendRoleRequestAndVerify(null, OFControllerRole.ROLE_MASTER); + assertThat(switchHandler.getStateForTesting(), CoreMatchers.instanceOf(OFSwitchHandshakeHandler.WaitInitialRoleState.class)); + + // prepare mocks and inject the role reply message + reset(sw); + sw.setAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE, false); + expectLastCall().once(); + sw.setControllerRole(OFControllerRole.ROLE_MASTER); + expectLastCall().once(); + expect(sw.getStatus()).andReturn(SwitchStatus.HANDSHAKE).once(); + sw.setStatus(SwitchStatus.MASTER); + expectLastCall().once(); + replay(sw); + + // FIXME: shouldn't use ordinal(), but OFError is broken + + // Error with incorrect xid and type. Should be ignored. + OFMessage err = getBadActionErrorMessage(OFBadActionCode.BAD_TYPE, xid+1); + // sendMessageToHandler will verify and rest controller mock + switchHandler.processOFMessage(err); + assertThat(switchHandler.getStateForTesting(), CoreMatchers.instanceOf(OFSwitchHandshakeHandler.WaitInitialRoleState.class)); + + // Error with correct xid. Should trigger state transition + err = getBadRequestErrorMessage(OFBadRequestCode.BAD_EXPERIMENTER, xid); + reset(switchManager); + switchManager.switchStatusChanged(sw, SwitchStatus.HANDSHAKE, SwitchStatus.MASTER); + expectLastCall().once(); + replay(switchManager); + // sendMessageToHandler will verify and rest controller mock + switchHandler.processOFMessage(err); + + assertThat(switchHandler.getStateForTesting(), CoreMatchers.instanceOf(OFSwitchHandshakeHandler.MasterState.class)); + } + + /** Move the channel from scratch to MASTER state + * Builds on doMoveToWaitInitialRole() + * adds testing for WAIT_INITAL_ROLE state + * + * We let the initial role request time out. Role support should be + * disabled but the switch should be activated. + */ + @Test + public void testInitialMoveToMasterTimeout() throws Exception { + int timeout = 50; + switchHandler.useRoleChangerWithOtherTimeoutForTesting(timeout); + + // first, move us to WAIT_INITIAL_ROLE_STATE + moveToWaitInitialRole(); + assertThat(switchHandler.getStateForTesting(), CoreMatchers.instanceOf(OFSwitchHandshakeHandler.WaitInitialRoleState.class)); + + // Set the role + setupSwitchSendRoleRequestAndVerify(null, OFControllerRole.ROLE_MASTER); + assertThat(switchHandler.getStateForTesting(), CoreMatchers.instanceOf(OFSwitchHandshakeHandler.WaitInitialRoleState.class)); + + // prepare mocks and inject the role reply message + reset(sw); + sw.setAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE, false); + expectLastCall().once(); + sw.setControllerRole(OFControllerRole.ROLE_MASTER); + expectLastCall().once(); + expect(sw.getStatus()).andReturn(SwitchStatus.HANDSHAKE).once(); + sw.setStatus(SwitchStatus.MASTER); + expectLastCall().once(); + replay(sw); + + OFMessage m = factory.buildBarrierReply().build(); + + Thread.sleep(timeout+5); + + reset(switchManager); + switchManager.switchStatusChanged(sw, SwitchStatus.HANDSHAKE, SwitchStatus.MASTER); + expectLastCall().once(); + replay(switchManager); + switchHandler.processOFMessage(m); + + assertThat(switchHandler.getStateForTesting(), CoreMatchers.instanceOf(OFSwitchHandshakeHandler.MasterState.class)); + + } + + + /** Move the channel from scratch to SLAVE state + * Builds on doMoveToWaitInitialRole() + * adds testing for WAIT_INITAL_ROLE state + * + * This method tests the case that the switch does NOT support roles. + * The channel handler still needs to send the initial request to find + * out that whether the switch supports roles. + * + */ + @Test + public void testInitialMoveToSlaveNoRole() throws Exception { + // first, move us to WAIT_INITIAL_ROLE_STATE + moveToWaitInitialRole(); + assertThat(switchHandler.getStateForTesting(), CoreMatchers.instanceOf(OFSwitchHandshakeHandler.WaitInitialRoleState.class)); + + // Set the role + long xid = setupSwitchSendRoleRequestAndVerify(null, OFControllerRole.ROLE_SLAVE); + assertThat(switchHandler.getStateForTesting(), CoreMatchers.instanceOf(OFSwitchHandshakeHandler.WaitInitialRoleState.class)); + + // prepare mocks and inject the role reply message + reset(sw); + sw.setAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE, false); + expectLastCall().once(); + sw.setControllerRole(OFControllerRole.ROLE_SLAVE); + expectLastCall().once(); + sw.disconnect(); // Make sure we disconnect + expectLastCall().once(); + replay(sw); + + + // FIXME: shouldn't use ordinal(), but OFError is broken + + // Error with incorrect xid and type. Should be ignored. + OFMessage err = getBadActionErrorMessage(OFBadActionCode.BAD_TYPE, xid+1); + + // sendMessageToHandler will verify and rest controller mock + switchHandler.processOFMessage(err); + assertThat(switchHandler.getStateForTesting(), CoreMatchers.instanceOf(OFSwitchHandshakeHandler.WaitInitialRoleState.class)); + + // Error with correct xid. Should trigger state transition + err = getBadRequestErrorMessage(OFBadRequestCode.BAD_EXPERIMENTER, xid); + // sendMessageToHandler will verify and rest controller mock + switchHandler.processOFMessage(err); + } + + + /** Move the channel from scratch to SLAVE state + * Builds on doMoveToWaitInitialRole() + * adds testing for WAIT_INITAL_ROLE state + * + * We let the initial role request time out. The switch should be + * disconnected + */ + @Test + public void testInitialMoveToSlaveTimeout() throws Exception { + int timeout = 50; + switchHandler.useRoleChangerWithOtherTimeoutForTesting(timeout); + + // first, move us to WAIT_INITIAL_ROLE_STATE + moveToWaitInitialRole(); + assertThat(switchHandler.getStateForTesting(), CoreMatchers.instanceOf(OFSwitchHandshakeHandler.WaitInitialRoleState.class)); + + // Set the role + setupSwitchSendRoleRequestAndVerify(null, OFControllerRole.ROLE_SLAVE); + assertThat(switchHandler.getStateForTesting(), CoreMatchers.instanceOf(OFSwitchHandshakeHandler.WaitInitialRoleState.class)); + + // prepare mocks and inject the role reply message + reset(sw); + sw.setAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE, false); + expectLastCall().once(); + sw.setControllerRole(OFControllerRole.ROLE_SLAVE); + expectLastCall().once(); + sw.disconnect(); // Make sure we disconnect + expectLastCall().once(); + replay(sw); + + // Apparently this can be any type of message for this test?! + OFMessage m = factory.buildBarrierReply().build(); + + Thread.sleep(timeout+5); + switchHandler.processOFMessage(m); + } + + + /** Move channel from scratch to WAIT_INITIAL_STATE, then MASTER, + * then SLAVE for cases where the switch does not support roles. + * I.e., the final SLAVE transition should disconnect the switch. + */ + @Test + public void testNoRoleInitialToMasterToSlave() throws Exception { + int xid = 46; + // First, lets move the state to MASTER without role support + testInitialMoveToMasterNoRole(); + assertThat(switchHandler.getStateForTesting(), + CoreMatchers.instanceOf(OFSwitchHandshakeHandler.MasterState.class)); + + assertThat("Unexpected messages have been captured", + connection.getMessages(), + Matchers.empty()); + + // try to set master role again. should be a no-op + setupSwitchRoleChangeUnsupported(xid, OFControllerRole.ROLE_MASTER); + assertThat(switchHandler.getStateForTesting(), CoreMatchers.instanceOf(OFSwitchHandshakeHandler.MasterState.class)); + + assertThat("Unexpected messages have been captured", + connection.getMessages(), + Matchers.empty()); + + setupSwitchRoleChangeUnsupported(xid, OFControllerRole.ROLE_SLAVE); + assertThat(connection.isConnected(), equalTo(false)); + + assertThat("Unexpected messages have been captured", + connection.getMessages(), + Matchers.empty()); + } + + /** Move the channel to MASTER state + * Expects that the channel is in MASTER or SLAVE state. + * + */ + public void changeRoleToMasterWithRequest() throws Exception { + assertTrue("This method can only be called when handler is in " + + "MASTER or SLAVE role", switchHandler.isHandshakeComplete()); + + // Set the role + long xid = setupSwitchSendRoleRequestAndVerify(true, OFControllerRole.ROLE_MASTER); + + // prepare mocks and inject the role reply message + reset(sw); + sw.setAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE, true); + expectLastCall().once(); + sw.setControllerRole(OFControllerRole.ROLE_MASTER); + expectLastCall().once(); + expect(sw.getStatus()).andReturn(SwitchStatus.HANDSHAKE).once(); + sw.setStatus(SwitchStatus.MASTER); + expectLastCall().once(); + replay(sw); + + reset(switchManager); + switchManager.switchStatusChanged(sw, SwitchStatus.HANDSHAKE, SwitchStatus.MASTER); + expectLastCall().once(); + replay(switchManager); + + OFMessage reply = getRoleReply(xid, OFControllerRole.ROLE_MASTER); + + switchHandler.processOFMessage(reply); + + assertThat(switchHandler.getStateForTesting(), CoreMatchers.instanceOf(OFSwitchHandshakeHandler.MasterState.class)); + } + + /** Move the channel to SLAVE state + * Expects that the channel is in MASTER or SLAVE state. + * + */ + public void changeRoleToSlaveWithRequest() throws Exception { + assertTrue("This method can only be called when handler is in " + + "MASTER or SLAVE role", switchHandler.isHandshakeComplete()); + + // Set the role + long xid = setupSwitchSendRoleRequestAndVerify(true, OFControllerRole.ROLE_SLAVE); + + // prepare mocks and inject the role reply message + reset(sw); + sw.setAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE, true); + expectLastCall().once(); + sw.setControllerRole(OFControllerRole.ROLE_SLAVE); + expectLastCall().once(); + expect(sw.getStatus()).andReturn(SwitchStatus.MASTER).once(); + sw.setStatus(SwitchStatus.SLAVE); + expectLastCall().once(); + replay(sw); + + reset(switchManager); + switchManager.switchStatusChanged(sw, SwitchStatus.MASTER, SwitchStatus.SLAVE); + expectLastCall().once(); + replay(switchManager); + + OFMessage reply = getRoleReply(xid, OFControllerRole.ROLE_SLAVE); + connection.getListener().messageReceived(connection, reply); + + assertThat(switchHandler.getStateForTesting(), CoreMatchers.instanceOf(OFSwitchHandshakeHandler.SlaveState.class)); + } + + @Test + public void testMultiRoleChange1() throws Exception { + testInitialMoveToMasterWithRole(); + changeRoleToMasterWithRequest(); + changeRoleToSlaveWithRequest(); + changeRoleToSlaveWithRequest(); + changeRoleToMasterWithRequest(); + changeRoleToSlaveWithRequest(); + } + + @Test + public void testMultiRoleChange2() throws Exception { + testInitialMoveToSlaveWithRole(); + changeRoleToMasterWithRequest(); + changeRoleToSlaveWithRequest(); + changeRoleToSlaveWithRequest(); + changeRoleToMasterWithRequest(); + changeRoleToSlaveWithRequest(); + } + + /** Start from scratch and reply with an unexpected error to the role + * change request + * Builds on doMoveToWaitInitialRole() + * adds testing for WAIT_INITAL_ROLE state + */ + @Test + public void testInitialRoleChangeOtherError() throws Exception { + // first, move us to WAIT_INITIAL_ROLE_STATE + moveToWaitInitialRole(); + assertThat(switchHandler.getStateForTesting(), CoreMatchers.instanceOf(OFSwitchHandshakeHandler.WaitInitialRoleState.class)); + + // Set the role + long xid = setupSwitchSendRoleRequestAndVerify(null, OFControllerRole.ROLE_MASTER); + assertThat(switchHandler.getStateForTesting(), CoreMatchers.instanceOf(OFSwitchHandshakeHandler.WaitInitialRoleState.class)); + + OFMessage err = getBadActionErrorMessage(OFBadActionCode.BAD_TYPE, xid); + + verifyExceptionCaptured(err, SwitchStateException.class); + } + + /** + * Test dispatch of messages while in MASTER role + */ + @Test + public void testMessageDispatchMaster() throws Exception { + testInitialMoveToMasterWithRole(); + + // Send packet in. expect dispatch + OFPacketIn pi = factory.buildPacketIn() + .setReason(OFPacketInReason.NO_MATCH) + .build(); + reset(switchManager); + switchManager.handleMessage(sw, pi, null); + expectLastCall().once(); + replay(switchManager); + switchHandler.processOFMessage(pi); + + // TODO: many more to go + } + + /** + * Test port status message handling while MASTER + * + */ + @Test + public void testPortStatusMessageMaster() throws Exception { + DatapathId dpid = featuresReply.getDatapathId(); + testInitialMoveToMasterWithRole(); + + OFPortDesc portDesc = factory.buildPortDesc() + .setName("Port1") + .setPortNo(OFPort.of(1)) + .build(); + OFPortStatus.Builder portStatusBuilder = factory.buildPortStatus() + .setDesc(portDesc); + + // The events we expect sw.handlePortStatus to return + // We'll just use the same list for all valid OFPortReasons and add + // arbitrary events for arbitrary ports that are not necessarily + // related to the port status message. Our goal + // here is not to return the correct set of events but the make sure + // that a) sw.handlePortStatus is called + // b) the list of events sw.handlePortStatus returns is sent + // as IOFSwitchListener notifications. + OrderedCollection<PortChangeEvent> events = + new LinkedHashSetWrapper<PortChangeEvent>(); + OFPortDesc.Builder pb = factory.buildPortDesc(); + OFPortDesc p1 = pb.setName("eth1").setPortNo(OFPort.of(1)).build(); + OFPortDesc p2 = pb.setName("eth2").setPortNo(OFPort.of(2)).build(); + OFPortDesc p3 = pb.setName("eth3").setPortNo(OFPort.of(3)).build(); + OFPortDesc p4 = pb.setName("eth4").setPortNo(OFPort.of(4)).build(); + OFPortDesc p5 = pb.setName("eth5").setPortNo(OFPort.of(5)).build(); + + + events.add(new PortChangeEvent(p1, PortChangeType.ADD)); + events.add(new PortChangeEvent(p2, PortChangeType.DELETE)); + events.add(new PortChangeEvent(p3, PortChangeType.UP)); + events.add(new PortChangeEvent(p4, PortChangeType.DOWN)); + events.add(new PortChangeEvent(p5, PortChangeType.OTHER_UPDATE)); + + + for (OFPortReason reason: OFPortReason.values()) { + OFPortStatus portStatus = portStatusBuilder.setReason(reason).build(); + + reset(sw); + expect(sw.getId()).andReturn(dpid).anyTimes(); + + expect(sw.processOFPortStatus(portStatus)).andReturn(events).once(); + replay(sw); + + reset(switchManager); + switchManager.notifyPortChanged(sw, p1, PortChangeType.ADD); + switchManager.notifyPortChanged(sw, p2, PortChangeType.DELETE); + switchManager.notifyPortChanged(sw, p3, PortChangeType.UP); + switchManager.notifyPortChanged(sw, p4, PortChangeType.DOWN); + switchManager.notifyPortChanged(sw, p5, PortChangeType.OTHER_UPDATE); + replay(switchManager); + + switchHandler.processOFMessage(portStatus); + + verify(sw); + } + } + + /** + * Test re-assert MASTER + * + */ + @Test + public void testReassertMaster() throws Exception { + testInitialMoveToMasterWithRole(); + + OFMessage err = getBadRequestErrorMessage(OFBadRequestCode.EPERM, 42); + + reset(roleManager); + roleManager.reassertRole(switchHandler, HARole.ACTIVE); + expectLastCall().once(); + replay(roleManager); + + reset(switchManager); + switchManager.handleMessage(sw, err, null); + expectLastCall().once(); + replay(switchManager); + + switchHandler.processOFMessage(err); + + verify(sw); + } + + /** + * Verify that the given exception event capture (as returned by + * getAndInitExceptionCapture) has thrown an exception of the given + * expectedExceptionClass. + * Resets the capture + * @param err + */ + void verifyExceptionCaptured( + OFMessage err, Class<? extends Throwable> expectedExceptionClass) { + + Throwable caughtEx = null; + // This should purposely cause an exception + try{ + switchHandler.processOFMessage(err); + } + catch(Exception e){ + // Capture the exception + caughtEx = e; + } + + assertThat(caughtEx, CoreMatchers.instanceOf(expectedExceptionClass)); + } + + /** + * Tests the connection closed functionality before the switch handshake is complete. + * Essentially when the switch handshake is only aware of the IOFConnection. + */ + @Test + public void testConnectionClosedBeforeHandshakeComplete() { + + // Test connection closed prior to being finished + reset(switchManager); + switchManager.handshakeDisconnected(dpid); + expectLastCall().once(); + replay(switchManager); + + switchHandler.connectionClosed(connection); + + verify(switchManager); + } + + /** + * Tests the connection closed functionality after the switch handshake is complete. + * Essentially when the switch handshake is aware of an IOFSwitch. + * @throws Exception + */ + @Test + public void testConnectionClosedAfterHandshakeComplete() throws Exception { + + testInitialMoveToMasterWithRole(); + // Test connection closed prior to being finished + reset(switchManager); + switchManager.handshakeDisconnected(dpid); + expectLastCall().once(); + switchManager.switchDisconnected(sw); + expectLastCall().once(); + replay(switchManager); + + reset(sw); + expect(sw.getStatus()).andReturn(SwitchStatus.DISCONNECTED).anyTimes(); + replay(sw); + + switchHandler.connectionClosed(connection); + + verify(switchManager); + verify(sw); + } +} diff --git a/src/test/java/net/floodlightcontroller/core/internal/OFSwitchHandshakeHandlerVer10Test.java b/src/test/java/net/floodlightcontroller/core/internal/OFSwitchHandshakeHandlerVer10Test.java new file mode 100644 index 0000000000000000000000000000000000000000..cefde88995600fc48eeda697301e801f89ff5dd7 --- /dev/null +++ b/src/test/java/net/floodlightcontroller/core/internal/OFSwitchHandshakeHandlerVer10Test.java @@ -0,0 +1,172 @@ +package net.floodlightcontroller.core.internal; + +import static org.easymock.EasyMock.anyObject; +import static org.easymock.EasyMock.eq; +import static org.easymock.EasyMock.expect; +import static org.easymock.EasyMock.expectLastCall; +import static org.easymock.EasyMock.replay; +import static org.easymock.EasyMock.reset; +import static org.easymock.EasyMock.verify; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.junit.Assert.assertThat; + +import java.util.EnumSet; + +import org.hamcrest.CoreMatchers; +import org.hamcrest.Matchers; +import org.junit.Test; +import net.floodlightcontroller.core.IOFSwitchBackend; +import net.floodlightcontroller.core.OFConnection; +import net.floodlightcontroller.core.SwitchDescription; +import net.floodlightcontroller.core.internal.OFSwitchAppHandshakePlugin.PluginResultType; +import net.floodlightcontroller.core.internal.OFSwitchHandshakeHandler.WaitAppHandshakeState; +import org.projectfloodlight.openflow.protocol.OFActionType; +import org.projectfloodlight.openflow.protocol.OFCapabilities; +import org.projectfloodlight.openflow.protocol.OFControllerRole; +import org.projectfloodlight.openflow.protocol.OFDescStatsReply; +import org.projectfloodlight.openflow.protocol.OFFactories; +import org.projectfloodlight.openflow.protocol.OFFactory; +import org.projectfloodlight.openflow.protocol.OFFeaturesReply; +import org.projectfloodlight.openflow.protocol.OFMessage; +import org.projectfloodlight.openflow.protocol.OFNiciraControllerRoleReply; +import org.projectfloodlight.openflow.protocol.OFNiciraControllerRoleRequest; +import org.projectfloodlight.openflow.protocol.OFPortDesc; +import org.projectfloodlight.openflow.protocol.OFPortDescStatsReply; +import org.projectfloodlight.openflow.protocol.OFType; +import org.projectfloodlight.openflow.protocol.OFVersion; +import org.projectfloodlight.openflow.types.DatapathId; +import org.projectfloodlight.openflow.types.OFPort; + +import com.google.common.collect.ImmutableList; + + +public class OFSwitchHandshakeHandlerVer10Test extends OFSwitchHandlerTestBase { + + @Override + public OFFactory getFactory() { + return OFFactories.getFactory(OFVersion.OF_10); + } + + @Override + OFFeaturesReply getFeaturesReply() { + OFPortDesc portDesc = factory.buildPortDesc() + .setName("Eth1") + .setPortNo(OFPort.of(1)) + .build(); + return factory.buildFeaturesReply() + .setDatapathId(dpid) + .setNBuffers(1) + .setNTables((short)1) + .setCapabilities(EnumSet.<OFCapabilities>of(OFCapabilities.FLOW_STATS, OFCapabilities.TABLE_STATS)) + .setActions(EnumSet.<OFActionType>of(OFActionType.SET_VLAN_PCP)) + .setPorts(ImmutableList.<OFPortDesc>of(portDesc)) + .build(); + } + + @Override + void moveToPreConfigReply() throws Exception { + testInitState(); + switchHandler.beginHandshake(); + } + + public void handleDescStatsAndCreateSwitch(boolean switchDriverComplete) throws Exception { + // build the stats reply + OFDescStatsReply sr = createDescriptionStatsReply(); + + reset(sw); + SwitchDescription switchDescription = new SwitchDescription(sr); + setupSwitchForInstantiationWithReset(); + sw.startDriverHandshake(); + expectLastCall().once(); + sw.isDriverHandshakeComplete(); + expectLastCall().andReturn(switchDriverComplete).once(); + + if(factory.getVersion().compareTo(OFVersion.OF_13) >= 0) { + sw.setPortDescStats(anyObject(OFPortDescStatsReply.class)); + expectLastCall().once(); + } + + replay(sw); + + reset(switchManager); + expect(switchManager.getHandshakePlugins()).andReturn(plugins).anyTimes(); + expect( + switchManager.getOFSwitchInstance(anyObject(OFConnection.class), + eq(switchDescription), + anyObject(OFFactory.class), + anyObject(DatapathId.class))).andReturn(sw).once(); + switchManager.switchAdded(sw); + expectLastCall().once(); + replay(switchManager); + + // send the description stats reply + switchHandler.processOFMessage(sr); + } + + @Test + @Override + public void moveToWaitAppHandshakeState() throws Exception { + moveToWaitDescriptionStatReply(); + handleDescStatsAndCreateSwitch(true); + assertThat(switchHandler.getStateForTesting(), + CoreMatchers.instanceOf(WaitAppHandshakeState.class)); + } + + @Override + Class<?> getRoleRequestClass() { + return OFNiciraControllerRoleRequest.class; + } + + @Override + public void verifyRoleRequest(OFMessage m, OFControllerRole expectedRole) { + assertThat(m.getType(), equalTo(OFType.EXPERIMENTER)); + OFNiciraControllerRoleRequest roleRequest = (OFNiciraControllerRoleRequest)m; + assertThat(roleRequest.getRole(), equalTo(NiciraRoleUtils.ofRoleToNiciraRole(expectedRole))); + } + + @Override + protected OFMessage getRoleReply(long xid, OFControllerRole role) { + OFNiciraControllerRoleReply roleReply = factory.buildNiciraControllerRoleReply() + .setXid(xid) + .setRole(NiciraRoleUtils.ofRoleToNiciraRole(role)) + .build(); + return roleReply; + } + + @Override + @Test + public void moveToWaitSwitchDriverSubHandshake() throws Exception { + moveToWaitDescriptionStatReply(); + handleDescStatsAndCreateSwitch(false); + + assertThat(switchHandler.getStateForTesting(), CoreMatchers.instanceOf(OFSwitchHandshakeHandler.WaitSwitchDriverSubHandshakeState.class)); + assertThat("Unexpected message captured", connection.getMessages(), Matchers.empty()); + verify(sw); + } + + @Override + @Test + public void moveToWaitInitialRole() + throws Exception { + moveToWaitAppHandshakeState(); + + assertThat(switchHandler.getStateForTesting(), + CoreMatchers.instanceOf(WaitAppHandshakeState.class)); + + reset(sw); + expect(sw.getAttribute(IOFSwitchBackend.SWITCH_SUPPORTS_NX_ROLE)).andReturn(true).anyTimes(); + replay(sw); + + reset(roleManager); + expect(roleManager.getOFControllerRole()).andReturn(OFControllerRole.ROLE_MASTER).anyTimes(); + replay(roleManager); + + WaitAppHandshakeState state = (WaitAppHandshakeState) switchHandler.getStateForTesting(); + PluginResult result = new PluginResult(PluginResultType.CONTINUE); + state.exitPlugin(result); + + assertThat(connection.retrieveMessage(), instanceOf(getRoleRequestClass())); + assertThat(switchHandler.getStateForTesting(), CoreMatchers.instanceOf(OFSwitchHandshakeHandler.WaitInitialRoleState.class)); + } +} diff --git a/src/test/java/net/floodlightcontroller/core/internal/OFSwitchHandshakeHandlerVer13Test.java b/src/test/java/net/floodlightcontroller/core/internal/OFSwitchHandshakeHandlerVer13Test.java new file mode 100644 index 0000000000000000000000000000000000000000..caf061b1d2fb1574710bdacc3a92319d3222991a --- /dev/null +++ b/src/test/java/net/floodlightcontroller/core/internal/OFSwitchHandshakeHandlerVer13Test.java @@ -0,0 +1,345 @@ +package net.floodlightcontroller.core.internal; + +import static org.easymock.EasyMock.anyObject; +import static org.easymock.EasyMock.capture; +import static org.easymock.EasyMock.eq; +import static org.easymock.EasyMock.expect; +import static org.easymock.EasyMock.expectLastCall; +import static org.easymock.EasyMock.replay; +import static org.easymock.EasyMock.reset; +import static org.easymock.EasyMock.verify; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.junit.Assert.assertThat; + +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; + +import org.easymock.Capture; +import org.hamcrest.CoreMatchers; +import org.hamcrest.Matchers; +import org.junit.Test; +import net.floodlightcontroller.core.IOFSwitchBackend; +import net.floodlightcontroller.core.OFConnection; +import net.floodlightcontroller.core.SwitchDescription; +import net.floodlightcontroller.core.internal.OFSwitchAppHandshakePlugin.PluginResultType; +import net.floodlightcontroller.core.internal.OFSwitchHandshakeHandler.WaitAppHandshakeState; +import net.floodlightcontroller.core.util.URIUtil; +import org.projectfloodlight.openflow.protocol.OFBsnControllerConnection; +import org.projectfloodlight.openflow.protocol.OFBsnControllerConnectionState; +import org.projectfloodlight.openflow.protocol.OFBsnControllerConnectionsReply; +import org.projectfloodlight.openflow.protocol.OFBsnControllerConnectionsRequest; +import org.projectfloodlight.openflow.protocol.OFBsnGentableDescStatsReply; +import org.projectfloodlight.openflow.protocol.OFBsnGentableDescStatsRequest; +import org.projectfloodlight.openflow.protocol.OFBsnSetAuxCxnsReply; +import org.projectfloodlight.openflow.protocol.OFBsnSetAuxCxnsRequest; +import org.projectfloodlight.openflow.protocol.OFCapabilities; +import org.projectfloodlight.openflow.protocol.OFControllerRole; +import org.projectfloodlight.openflow.protocol.OFDescStatsReply; +import org.projectfloodlight.openflow.protocol.OFFactories; +import org.projectfloodlight.openflow.protocol.OFFactory; +import org.projectfloodlight.openflow.protocol.OFFeaturesReply; +import org.projectfloodlight.openflow.protocol.OFMessage; +import org.projectfloodlight.openflow.protocol.OFPortDesc; +import org.projectfloodlight.openflow.protocol.OFPortDescStatsReply; +import org.projectfloodlight.openflow.protocol.OFPortDescStatsRequest; +import org.projectfloodlight.openflow.protocol.OFRoleReply; +import org.projectfloodlight.openflow.protocol.OFRoleRequest; +import org.projectfloodlight.openflow.protocol.OFVersion; +import org.projectfloodlight.openflow.types.DatapathId; +import org.projectfloodlight.openflow.types.GenTableId; +import org.projectfloodlight.openflow.types.OFAuxId; +import org.projectfloodlight.openflow.types.OFPort; + +import com.google.common.collect.ImmutableList; + + +public class OFSwitchHandshakeHandlerVer13Test extends OFSwitchHandlerTestBase { + + @Override + public OFFactory getFactory() { + return OFFactories.getFactory(OFVersion.OF_13); + } + + @Override + OFFeaturesReply getFeaturesReply() { + return factory.buildFeaturesReply() + .setDatapathId(dpid) + .setNBuffers(1) + .setNTables((short)1) + .setCapabilities(EnumSet.<OFCapabilities>of(OFCapabilities.FLOW_STATS, OFCapabilities.TABLE_STATS)) + .setAuxiliaryId(OFAuxId.MAIN) + .build(); + } + + OFPortDescStatsReply getPortDescStatsReply() { + OFPortDesc portDesc = factory.buildPortDesc() + .setName("Eth1") + .setPortNo(OFPort.of(1)) + .build(); + return factory.buildPortDescStatsReply() + .setEntries(ImmutableList.<OFPortDesc>of(portDesc)) + .build(); + } + + + /** Move the channel from scratch to WAIT_CONFIG_REPLY state + * Builds on moveToWaitFeaturesReply + * adds testing for WAIT_FEATURES_REPLY state + */ + @Test + public void moveToWaitPortDescStatsReply() throws Exception { + testInitState(); + + switchHandler.beginHandshake(); + + OFMessage msg = connection.retrieveMessage(); + assertThat(msg, CoreMatchers.instanceOf(OFPortDescStatsRequest.class)); + verifyUniqueXids(msg); + + assertThat(switchHandler.getStateForTesting(), CoreMatchers.instanceOf(OFSwitchHandshakeHandler.WaitPortDescStatsReplyState.class)); + } + + @Override + void moveToPreConfigReply() throws Exception { + moveToWaitPortDescStatsReply(); + switchHandler.processOFMessage(getPortDescStatsReply()); + } + + public void handleDescStatsAndCreateSwitch() throws Exception { + // build the stats reply + OFDescStatsReply sr = createDescriptionStatsReply(); + + reset(sw); + SwitchDescription switchDescription = new SwitchDescription(sr); + setupSwitchForInstantiationWithReset(); + sw.setPortDescStats(anyObject(OFPortDescStatsReply.class)); + expectLastCall().once(); + replay(sw); + + reset(switchManager); + expect(switchManager.getHandshakePlugins()).andReturn(plugins).anyTimes(); + expect( + switchManager.getOFSwitchInstance(anyObject(OFConnection.class), + eq(switchDescription), + anyObject(OFFactory.class), + anyObject(DatapathId.class))).andReturn(sw).once(); + expect(switchManager.getNumRequiredConnections()).andReturn(0); + switchManager.switchAdded(sw); + expectLastCall().once(); + replay(switchManager); + + // send the description stats reply + switchHandler.processOFMessage(sr); + + verify(sw, switchManager); + } + + /** Move the channel from scratch to WAIT_DESCRIPTION_STAT_REPLY state + * Builds on moveToWaitConfigReply() + * adds testing for WAIT_CONFIG_REPLY state + */ + @Test + public void moveToWaitOFAuxCxnsReply() throws Exception { + moveToWaitDescriptionStatReply(); + handleDescStatsAndCreateSwitch(); + + OFMessage msg = connection.retrieveMessage(); + assertThat(msg, CoreMatchers.instanceOf(OFBsnSetAuxCxnsRequest.class)); + verifyUniqueXids(msg); + + assertThat(switchHandler.getStateForTesting(), CoreMatchers.instanceOf(OFSwitchHandshakeHandler.WaitOFAuxCxnsReplyState.class)); + + } + + /** Move the channel from scratch to WAIT_DESCRIPTION_STAT_REPLY state + * Builds on moveToWaitConfigReply() + * adds testing for WAIT_CONFIG_REPLY state + */ + @Test + public void moveToWaitGenDescStatsReply() throws Exception { + moveToWaitOFAuxCxnsReply(); + + // build the stats reply + OFBsnSetAuxCxnsReply auxReply = factory.buildBsnSetAuxCxnsReply() + .setNumAux(0) + .setStatus(0) + .build(); + + // send the description stats reply + switchHandler.processOFMessage(auxReply); + OFMessage msg = connection.retrieveMessage(); + assertThat(msg, CoreMatchers.instanceOf(OFBsnGentableDescStatsRequest.class)); + verifyUniqueXids(msg); + + assertThat(switchHandler.getStateForTesting(), CoreMatchers.instanceOf(OFSwitchHandshakeHandler.WaitGentableDescStatsReplyState.class)); + + } + + + /** This makes sure the correct behavior occurs for an illegal OF Aux Reply status + */ + @Test + public void testOFAuxSwitchFail() throws Exception { + moveToWaitOFAuxCxnsReply(); + + // Build and OF Aux reply - status of non zero denotes failure on switch end + OFBsnSetAuxCxnsReply auxReply = factory.buildBsnSetAuxCxnsReply() + .setNumAux(0) + .setStatus(-1) + .build(); + + verifyExceptionCaptured(auxReply, OFAuxException.class); + } + + @Test + @Override + public void moveToWaitAppHandshakeState() throws Exception { + moveToWaitGenDescStatsReply(); + + handleGenDescStatsReplay(true); + + assertThat(switchHandler.getStateForTesting(), + CoreMatchers.instanceOf(WaitAppHandshakeState.class)); + } + + private void handleGenDescStatsReplay(boolean driverHandshakeComplete) { + reset(sw); + Capture<GenTableMap> capGenTable = new Capture<>(); + sw.setGenTableMap(capture(capGenTable)); + expectLastCall().anyTimes(); + sw.startDriverHandshake(); + expectLastCall().once(); + sw.isDriverHandshakeComplete(); + expectLastCall().andReturn(driverHandshakeComplete).once(); + expect(sw.getId()).andReturn(featuresReply.getDatapathId()).anyTimes(); + replay(sw); + + reset(switchManager); + expect(switchManager.getHandshakePlugins()).andReturn(plugins).anyTimes(); + replay(switchManager); + OFBsnGentableDescStatsReply reply = createGenDescStatsReply(); + + switchHandler.processOFMessage(reply); + } + + private OFBsnGentableDescStatsReply createGenDescStatsReply() { + OFBsnGentableDescStatsReply reply = factory.buildBsnGentableDescStatsReply() + .setEntries( + ImmutableList.of( + factory.buildBsnGentableDescStatsEntry() + .setTableId(GenTableId.of(0)) + .setName("dhcp") + .setBucketsSize(64) + .setMaxEntries(4096) + .build(), + factory.buildBsnGentableDescStatsEntry() + .setTableId(GenTableId.of(1)) + .setName("arp") + .setMaxEntries(8192) + .setBucketsSize(64) + .build() + )) + .build(); + return reply; + } + + @Override + Class<?> getRoleRequestClass() { + return OFRoleRequest.class; + } + + @Override + public void verifyRoleRequest(OFMessage m, OFControllerRole expectedRole) { + assertThat(m, CoreMatchers.instanceOf(OFRoleRequest.class)); + OFRoleRequest roleRequest = (OFRoleRequest) m; + assertThat(roleRequest.getRole(), equalTo(expectedRole)); + } + + @Override + protected OFMessage getRoleReply(long xid, OFControllerRole role) { + OFRoleReply roleReply = factory.buildRoleReply() + .setXid(xid) + .setRole(role) + .build(); + return roleReply; + } + + @Test + public void moveToWaitControllerCxnsReplyState() throws Exception { + moveToWaitAppHandshakeState(); + + assertThat(switchHandler.getStateForTesting(), + CoreMatchers.instanceOf(WaitAppHandshakeState.class)); + + + WaitAppHandshakeState state = (WaitAppHandshakeState) switchHandler.getStateForTesting(); + PluginResult result = new PluginResult(PluginResultType.CONTINUE); + state.exitPlugin(result); + + OFMessage msg = connection.retrieveMessage(); + assertThat(msg, CoreMatchers.instanceOf(OFBsnControllerConnectionsRequest.class)); + verifyUniqueXids(msg); + + assertThat(switchHandler.getStateForTesting(), CoreMatchers.instanceOf(OFSwitchHandshakeHandler.WaitControllerCxnsReplyState.class)); + } + + @Override + @Test + public void moveToWaitInitialRole() + throws Exception { + moveToWaitControllerCxnsReplyState(); + + assertThat(switchHandler.getStateForTesting(), + CoreMatchers.instanceOf(WaitControllerCxnsReplyState.class)); + + OFBsnControllerConnection cxn = factory.buildBsnControllerConnection() + .setAuxiliaryId(OFAuxId.MAIN) + .setRole(OFControllerRole.ROLE_MASTER) + .setState(OFBsnControllerConnectionState.BSN_CONTROLLER_CONNECTION_STATE_CONNECTED) + .setUri(URIUtil.createURI("1.2.3.4", 6653).toString()) + .build(); + + List<OFBsnControllerConnection> cxns = new ArrayList<OFBsnControllerConnection>(); + cxns.add(cxn); + + // build the controller connections reply + OFBsnControllerConnectionsReply cxnsReply = factory.buildBsnControllerConnectionsReply() + .setConnections(cxns) + .build(); + + reset(sw); + sw.updateControllerConnections(cxnsReply); + expectLastCall().once(); + expect(sw.getAttribute(IOFSwitchBackend.SWITCH_SUPPORTS_NX_ROLE)).andReturn(true).anyTimes(); + replay(sw); + + reset(roleManager); + expect(roleManager.getOFControllerRole()).andReturn(OFControllerRole.ROLE_MASTER).anyTimes(); + roleManager.notifyControllerConnectionUpdate(); + expectLastCall().once(); + replay(roleManager); + + // send the controller connections reply + switchHandler.processOFMessage(cxnsReply); + + // Expect wait initial role's enterState message to be written + OFMessage msg = connection.retrieveMessage(); + assertThat(msg, CoreMatchers.instanceOf(OFRoleRequest.class)); + verifyUniqueXids(msg); + + assertThat(switchHandler.getStateForTesting(), CoreMatchers.instanceOf(OFSwitchHandshakeHandler.WaitInitialRoleState.class)); + } + + @Override + @Test + public void moveToWaitSwitchDriverSubHandshake() throws Exception { + moveToWaitGenDescStatsReply(); + handleGenDescStatsReplay(false); + + assertThat(switchHandler.getStateForTesting(), CoreMatchers.instanceOf(OFSwitchHandshakeHandler.WaitSwitchDriverSubHandshakeState.class)); + assertThat("Unexpected message captured", connection.getMessages(), Matchers.empty()); + verify(sw); + } + +} diff --git a/src/test/java/net/floodlightcontroller/core/internal/OFSwitchImplTest.java b/src/test/java/net/floodlightcontroller/core/internal/OFSwitchImplTest.java deleted file mode 100644 index e59a9d060899e856b6564d48cbf8527c90316a8c..0000000000000000000000000000000000000000 --- a/src/test/java/net/floodlightcontroller/core/internal/OFSwitchImplTest.java +++ /dev/null @@ -1,82 +0,0 @@ -/** - * 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.core.internal; - -import net.floodlightcontroller.core.SwitchDriverSubHandshakeAlreadyStarted; -import net.floodlightcontroller.core.SwitchDriverSubHandshakeCompleted; -import net.floodlightcontroller.core.SwitchDriverSubHandshakeNotStarted; -import net.floodlightcontroller.core.IFloodlightProviderService.Role; - -import org.junit.Before; -import org.junit.Test; -import org.openflow.protocol.OFMessage; -import org.openflow.protocol.OFType; -import org.openflow.protocol.factory.BasicFactory; - -import static org.junit.Assert.*; - -public class OFSwitchImplTest { - protected OFSwitchImpl sw; - - - @Before - public void setUp() throws Exception { - sw = new OFSwitchImpl(); - } - - @Test - public void testSetHARoleReply() { - - sw.setHARole(Role.MASTER); - assertEquals(Role.MASTER, sw.getHARole()); - - sw.setHARole(Role.EQUAL); - assertEquals(Role.EQUAL, sw.getHARole()); - - sw.setHARole(Role.SLAVE); - assertEquals(Role.SLAVE, sw.getHARole()); - } - - @Test - public void testSubHandshake() { - OFMessage m = BasicFactory.getInstance().getMessage(OFType.VENDOR); - // test execptions before handshake is started - try { - sw.processDriverHandshakeMessage(m); - fail("expected exception not thrown"); - } catch (SwitchDriverSubHandshakeNotStarted e) { /* expected */ } - try { - sw.isDriverHandshakeComplete(); - fail("expected exception not thrown"); - } catch (SwitchDriverSubHandshakeNotStarted e) { /* expected */ } - - // start the handshake -- it should immediately complete - sw.startDriverHandshake(); - assertTrue("Handshake should be complete", - sw.isDriverHandshakeComplete()); - - // test exceptions after handshake is completed - try { - sw.processDriverHandshakeMessage(m); - fail("expected exception not thrown"); - } catch (SwitchDriverSubHandshakeCompleted e) { /* expected */ } - try { - sw.startDriverHandshake(); - fail("Expected exception not thrown"); - } catch (SwitchDriverSubHandshakeAlreadyStarted e) { /* expected */ } - } -} diff --git a/src/test/java/net/floodlightcontroller/core/internal/OFSwitchManagerTest.java b/src/test/java/net/floodlightcontroller/core/internal/OFSwitchManagerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..7e6a7389b7396e63546d7f9d907c973b4831ed9a --- /dev/null +++ b/src/test/java/net/floodlightcontroller/core/internal/OFSwitchManagerTest.java @@ -0,0 +1,840 @@ +/** +* Copyright 2011, Big Switch Networks, Inc. +* Originally created by David Erickson, Stanford University +* +* Licensed under the Apache License, Version 2.0 (the "License"); you may +* not use this file except in compliance with the License. You may obtain +* a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +* License for the specific language governing permissions and limitations +* under the License. +**/ + +package net.floodlightcontroller.core.internal; + +import static org.easymock.EasyMock.createMock; +import static org.easymock.EasyMock.createNiceMock; +import static org.easymock.EasyMock.createStrictMock; +import static org.easymock.EasyMock.expect; +import static org.easymock.EasyMock.expectLastCall; +import static org.easymock.EasyMock.replay; +import static org.easymock.EasyMock.reset; +import static org.easymock.EasyMock.verify; +import static org.easymock.EasyMock.anyObject; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.util.List; + +import org.jboss.netty.util.Timer; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import net.floodlightcontroller.core.HARole; +import net.floodlightcontroller.core.IFloodlightProviderService; +import net.floodlightcontroller.core.IOFSwitch; +import net.floodlightcontroller.core.IOFSwitch.SwitchStatus; +import net.floodlightcontroller.core.IOFSwitchBackend; +import net.floodlightcontroller.core.IOFSwitchDriver; +import net.floodlightcontroller.core.IOFSwitchListener; +import net.floodlightcontroller.core.IShutdownListener; +import net.floodlightcontroller.core.IShutdownService; +import net.floodlightcontroller.core.LogicalOFMessageCategory; +import net.floodlightcontroller.core.NullConnection; +import net.floodlightcontroller.core.OFSwitch; +import net.floodlightcontroller.core.PortChangeType; +import net.floodlightcontroller.core.SwitchDescription; +import net.floodlightcontroller.core.module.FloodlightModuleContext; +import net.floodlightcontroller.debugcounter.DebugCounterServiceImpl; +import net.floodlightcontroller.debugcounter.IDebugCounterService; +import net.floodlightcontroller.debugevent.DebugEventService; +import net.floodlightcontroller.debugevent.IDebugEventService; +import net.floodlightcontroller.restserver.IRestApiService; +import net.floodlightcontroller.restserver.RestApiServer; +import net.floodlightcontroller.storage.IStorageSourceService; +import net.floodlightcontroller.storage.memory.MemoryStorageSource; +import net.floodlightcontroller.threadpool.IThreadPoolService; +import net.floodlightcontroller.threadpool.ThreadPool; + +import org.projectfloodlight.openflow.protocol.OFFactories; +import org.projectfloodlight.openflow.protocol.OFFactory; +import org.projectfloodlight.openflow.protocol.OFFeaturesReply; +import org.projectfloodlight.openflow.protocol.OFPortDesc; +import org.projectfloodlight.openflow.protocol.OFPortFeatures; +import org.projectfloodlight.openflow.protocol.OFVersion; +import org.projectfloodlight.openflow.types.DatapathId; +import org.projectfloodlight.openflow.types.OFAuxId; +import org.projectfloodlight.openflow.types.OFPort; +import org.sdnplatform.sync.ISyncService; +import org.sdnplatform.sync.test.MockSyncService; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; + +/** + * + * @author David Erickson (daviderickson@cs.stanford.edu) + */ +public class OFSwitchManagerTest{ + private Controller controller; + private OFSwitchManager switchManager; + + // FIXME:LOJI: For now just work with OF 1.0 + private final OFFactory factory = OFFactories.getFactory(OFVersion.OF_10); + private static DatapathId DATAPATH_ID_0 = DatapathId.of(0); + private static DatapathId DATAPATH_ID_1 = DatapathId.of(1); + + @Before + public void setUp() throws Exception { + doSetUp(HARole.ACTIVE); + } + + public void doSetUp(HARole role) throws Exception { + FloodlightModuleContext fmc = new FloodlightModuleContext(); + + FloodlightProvider cm = new FloodlightProvider(); + fmc.addConfigParam(cm, "role", role.toString()); + controller = (Controller)cm.getServiceImpls().get(IFloodlightProviderService.class); + fmc.addService(IFloodlightProviderService.class, controller); + + MemoryStorageSource memstorage = new MemoryStorageSource(); + fmc.addService(IStorageSourceService.class, memstorage); + + RestApiServer restApi = new RestApiServer(); + fmc.addService(IRestApiService.class, restApi); + + ThreadPool threadPool = new ThreadPool(); + fmc.addService(IThreadPoolService.class, threadPool); + + // TODO: should mock IDebugCounterService and make sure + // the expected counters are updated. + DebugCounterServiceImpl debugCounterService = new DebugCounterServiceImpl(); + fmc.addService(IDebugCounterService.class, debugCounterService); + + DebugEventService debugEventService = new DebugEventService(); + fmc.addService(IDebugEventService.class, debugEventService); + + switchManager = new OFSwitchManager(); + fmc.addService(IOFSwitchService.class, switchManager); + + MockSyncService syncService = new MockSyncService(); + fmc.addService(ISyncService.class, syncService); + + IShutdownService shutdownService = createMock(IShutdownService.class); + shutdownService.registerShutdownListener(anyObject(IShutdownListener.class)); + expectLastCall().anyTimes(); + replay(shutdownService); + fmc.addService(IShutdownService.class, shutdownService); + verify(shutdownService); + + threadPool.init(fmc); + syncService.init(fmc); + switchManager.init(fmc); + debugCounterService.init(fmc); + debugEventService.init(fmc); + restApi.init(fmc); + cm.init(fmc); + + syncService.init(fmc); + switchManager.startUpBase(fmc); + debugCounterService.startUp(fmc); + debugEventService.startUp(fmc); + threadPool.startUp(fmc); + restApi.startUp(fmc); + cm.startUp(fmc); + } + + @After + public void tearDown(){ + + } + + public Controller getController() { + return controller; + } + + private static SwitchDescription createSwitchDescription() { + return new SwitchDescription(); + } + + private OFFeaturesReply createOFFeaturesReply(DatapathId datapathId) { + OFFeaturesReply fr = factory.buildFeaturesReply() + .setXid(0) + .setDatapathId(datapathId) + .setPorts(ImmutableList.<OFPortDesc>of()) + .build(); + return fr; + } + + + /** Set the mock expectations for sw when sw is passed to addSwitch + * The same expectations can be used when a new SwitchSyncRepresentation + * is created from the given mocked switch */ + protected void setupSwitchForAddSwitch(IOFSwitch sw, DatapathId datapathId, + SwitchDescription description, OFFeaturesReply featuresReply) { + String dpidString = datapathId.toString(); + if (description == null) { + description = createSwitchDescription(); + } + if (featuresReply == null) { + featuresReply = createOFFeaturesReply(datapathId); + } + List<OFPortDesc> ports = featuresReply.getPorts(); + + expect(sw.getOFFactory()).andReturn(OFFactories.getFactory(OFVersion.OF_10)).anyTimes(); + expect(sw.getStatus()).andReturn(SwitchStatus.MASTER).anyTimes(); + expect(sw.getId()).andReturn(datapathId).anyTimes(); + expect(sw.getStringId()).andReturn(dpidString).anyTimes(); + expect(sw.getSwitchDescription()).andReturn(description).anyTimes(); + expect(sw.getBuffers()) + .andReturn(featuresReply.getNBuffers()).anyTimes(); + expect(sw.getTables()) + .andReturn(featuresReply.getNTables()).anyTimes(); + expect(sw.getCapabilities()) + .andReturn(featuresReply.getCapabilities()).anyTimes(); + expect(sw.getActions()) + .andReturn(featuresReply.getActions()).anyTimes(); + expect(sw.getPorts()) + .andReturn(ports).anyTimes(); + expect(sw.attributeEquals(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE, true)) + .andReturn(false).anyTimes(); + expect(sw.getInetAddress()).andReturn(null).anyTimes(); + } + + @Test + /** + * Test switchActivated for a new switch, i.e., a switch that was not + * previously known to the controller cluser. We expect that all + * flow mods are cleared and we expect a switchAdded + */ + public void testNewSwitchActivated() throws Exception { + + IOFSwitchBackend sw = createMock(IOFSwitchBackend.class); + setupSwitchForAddSwitch(sw, DATAPATH_ID_0, null, null); + + // Ensure switch doesn't already exist + assertNull(switchManager.getSwitch(DATAPATH_ID_0)); + + // strict mock. Order of events matters! + IOFSwitchListener listener = createStrictMock(IOFSwitchListener.class); + listener.switchAdded(DATAPATH_ID_0); + expectLastCall().once(); + listener.switchActivated(DATAPATH_ID_0); + expectLastCall().once(); + replay(listener); + switchManager.addOFSwitchListener(listener); + replay(sw); + switchManager.switchAdded(sw); + switchManager.switchStatusChanged(sw, SwitchStatus.HANDSHAKE, SwitchStatus.MASTER); + verify(sw); + + assertEquals(sw, switchManager.getSwitch(DATAPATH_ID_0)); + controller.processUpdateQueueForTesting(); + verify(listener); + } + + /** + * Test switchActivated for a new switch while in slave: disconnect the switch + */ + @Test + public void testNewSwitchActivatedWhileSlave() throws Exception { + doSetUp(HARole.STANDBY); + IOFSwitchBackend sw = createMock(IOFSwitchBackend.class); + + IOFSwitchListener listener = createMock(IOFSwitchListener.class); + switchManager.addOFSwitchListener(listener); + + expect(sw.getId()).andReturn(DATAPATH_ID_0).anyTimes(); + expect(sw.getStatus()).andReturn(SwitchStatus.MASTER).anyTimes(); + sw.disconnect(); + expectLastCall().once(); + replay(sw, listener); // nothing recorded + switchManager.switchAdded(sw); + switchManager.switchStatusChanged(sw, SwitchStatus.HANDSHAKE, SwitchStatus.MASTER); + verify(sw); + controller.processUpdateQueueForTesting(); + verify(listener); + } + + + /** + * Create and activate a switch, either completely new or reconnected + * The mocked switch instance will be returned. It will be reset. + */ + private IOFSwitchBackend doActivateSwitchInt(DatapathId datapathId, + SwitchDescription description, + OFFeaturesReply featuresReply, + boolean clearFlows) + throws Exception { + + IOFSwitchBackend sw = createMock(IOFSwitchBackend.class); + if (featuresReply == null) { + featuresReply = createOFFeaturesReply(datapathId); + } + if (description == null) { + description = createSwitchDescription(); + } + setupSwitchForAddSwitch(sw, datapathId, description, featuresReply); + replay(sw); + switchManager.switchAdded(sw); + switchManager.switchStatusChanged(sw, SwitchStatus.HANDSHAKE, SwitchStatus.MASTER); + verify(sw); + assertEquals(sw, switchManager.getSwitch(datapathId)); + // drain updates and ignore + controller.processUpdateQueueForTesting(); + + reset(sw); + return sw; + } + + /** + * Create and activate a new switch with the given dpid, features reply + * and description. If description and/or features reply are null we'll + * allocate the default one + * The mocked switch instance will be returned. It wil be reset. + */ + private IOFSwitchBackend doActivateNewSwitch(DatapathId dpid, + SwitchDescription description, + OFFeaturesReply featuresReply) + throws Exception { + return doActivateSwitchInt(dpid, description, featuresReply, true); + } + + /** + * Remove a nonexisting switch. should be ignored + */ + @Test + public void testNonexistingSwitchDisconnected() throws Exception { + IOFSwitchBackend sw = createMock(IOFSwitchBackend.class); + expect(sw.getId()).andReturn(DATAPATH_ID_1).anyTimes(); + expect(sw.getStringId()).andReturn(DATAPATH_ID_1.toString()).anyTimes(); + IOFSwitchListener listener = createMock(IOFSwitchListener.class); + switchManager.addOFSwitchListener(listener); + replay(sw, listener); + switchManager.switchDisconnected(sw); + controller.processUpdateQueueForTesting(); + verify(sw, listener); + + assertNull(switchManager.getSwitch(DATAPATH_ID_1)); + } + + /** + * Try to remove a switch that's different from what's in the active + * switch map. Should be ignored + */ + @Test + public void testSwitchDisconnectedOther() throws Exception { + IOFSwitch origSw = doActivateNewSwitch(DATAPATH_ID_1, null, null); + // create a new mock switch + IOFSwitchBackend sw = createMock(IOFSwitchBackend.class); + expect(sw.getId()).andReturn(DATAPATH_ID_1).anyTimes(); + expect(sw.getStringId()).andReturn(DATAPATH_ID_1.toString()).anyTimes(); + IOFSwitchListener listener = createMock(IOFSwitchListener.class); + switchManager.addOFSwitchListener(listener); + replay(sw, listener); + switchManager.switchDisconnected(sw); + controller.processUpdateQueueForTesting(); + verify(sw, listener); + + expect(origSw.getStatus()).andReturn(SwitchStatus.MASTER).anyTimes(); + replay(origSw); + assertSame(origSw, switchManager.getSwitch(DATAPATH_ID_1)); + } + + + + /** + * Try to activate a switch that's already active (which can happen if + * two different switches have the same DPIP or if a switch reconnects + * while the old TCP connection is still alive + */ + @Test + public void testSwitchActivatedWithAlreadyActiveSwitch() throws Exception { + SwitchDescription oldDescription = new SwitchDescription( + "", "", "", "", "Ye Olde Switch"); + SwitchDescription newDescription = new SwitchDescription( + "", "", "", "", "The new Switch"); + OFFeaturesReply featuresReply = createOFFeaturesReply(DATAPATH_ID_0); + + + // Setup: add a switch to the controller + IOFSwitchBackend oldsw = createMock(IOFSwitchBackend.class); + setupSwitchForAddSwitch(oldsw, DATAPATH_ID_0, oldDescription, featuresReply); + replay(oldsw); + switchManager.switchAdded(oldsw); + switchManager.switchStatusChanged(oldsw, SwitchStatus.HANDSHAKE, SwitchStatus.MASTER); + verify(oldsw); + // drain the queue, we don't care what's in it + controller.processUpdateQueueForTesting(); + assertEquals(oldsw, switchManager.getSwitch(DATAPATH_ID_0)); + + // Now the actual test: add a new switch with the same dpid to + // the controller + reset(oldsw); + expect(oldsw.getId()).andReturn(DATAPATH_ID_0).anyTimes(); + oldsw.cancelAllPendingRequests(); + expectLastCall().once(); + oldsw.disconnect(); + expectLastCall().once(); + + + IOFSwitchBackend newsw = createMock(IOFSwitchBackend.class); + setupSwitchForAddSwitch(newsw, DATAPATH_ID_0, newDescription, featuresReply); + + // Strict mock. We need to get the removed notification before the + // add notification + IOFSwitchListener listener = createStrictMock(IOFSwitchListener.class); + listener.switchRemoved(DATAPATH_ID_0); + listener.switchAdded(DATAPATH_ID_0); + listener.switchActivated(DATAPATH_ID_0); + replay(listener); + switchManager.addOFSwitchListener(listener); + + + replay(newsw, oldsw); + switchManager.switchAdded(newsw); + switchManager.switchStatusChanged(newsw, SwitchStatus.HANDSHAKE, SwitchStatus.MASTER); + verify(newsw, oldsw); + + assertEquals(newsw, switchManager.getSwitch(DATAPATH_ID_0)); + controller.processUpdateQueueForTesting(); + verify(listener); + } + + + + /** + * Tests that you can't remove a switch from the map returned by + * getSwitches() (because getSwitches should return an unmodifiable + * map) + */ + @Test + public void testRemoveActiveSwitch() { + IOFSwitchBackend sw = createNiceMock(IOFSwitchBackend.class); + setupSwitchForAddSwitch(sw, DATAPATH_ID_1, null, null); + replay(sw); + switchManager.switchAdded(sw); + switchManager.switchStatusChanged(sw, SwitchStatus.HANDSHAKE, SwitchStatus.MASTER); + assertEquals(sw, switchManager.getSwitch(DATAPATH_ID_1)); + try { + switchManager.getAllSwitchMap().remove(DATAPATH_ID_1); + fail("Expected: UnsupportedOperationException"); + } catch(UnsupportedOperationException e) { + // expected + } + // we don't care for updates. drain queue. + controller.processUpdateQueueForTesting(); + } + + /** + * Tests that the switch manager should only return a switch to a getActiveSwitch + * call when the switch is visible/active. + */ + @Test + public void testGetActiveSwitch() { + MockOFConnection connection = new MockOFConnection(DATAPATH_ID_1, OFAuxId.MAIN); + IOFSwitchBackend sw = new MockOFSwitchImpl(connection); + sw.setStatus(SwitchStatus.HANDSHAKE); + + assertNull(switchManager.getActiveSwitch(DATAPATH_ID_1)); + switchManager.switchAdded(sw); + assertNull(switchManager.getActiveSwitch(DATAPATH_ID_1)); + sw.setStatus(SwitchStatus.MASTER); + assertEquals(sw, switchManager.getActiveSwitch(DATAPATH_ID_1)); + sw.setStatus(SwitchStatus.QUARANTINED); + assertNull(switchManager.getActiveSwitch(DATAPATH_ID_1)); + sw.setStatus(SwitchStatus.SLAVE); + assertEquals(sw, switchManager.getActiveSwitch(DATAPATH_ID_1)); + sw.setStatus(SwitchStatus.DISCONNECTED); + assertNull(switchManager.getActiveSwitch(DATAPATH_ID_1)); + // we don't care for updates. drain queue. + controller.processUpdateQueueForTesting(); + } + + /** + * Test that notifyPortChanged() results in an IOFSwitchListener + * update and that its arguments are passed through to + * the listener call + */ + @Test + public void testNotifySwitchPortChanged() throws Exception { + DatapathId dpid = DatapathId.of(42); + + OFPortDesc p1 = factory.buildPortDesc() + .setName("Port1") + .setPortNo(OFPort.of(1)) + .build(); + OFFeaturesReply fr1 = factory.buildFeaturesReply() + .setXid(0) + .setDatapathId(dpid) + .setPorts(ImmutableList.<OFPortDesc>of(p1)) + .build(); + + OFPortDesc p2 = factory.buildPortDesc() + .setName("Port1") + .setPortNo(OFPort.of(1)) + .setAdvertised(ImmutableSet.<OFPortFeatures>of(OFPortFeatures.PF_100MB_FD)) + .build(); + OFFeaturesReply fr2 = factory.buildFeaturesReply() + .setXid(0) + .setDatapathId(dpid) + .setPorts(ImmutableList.<OFPortDesc>of(p2)) + .build(); + + SwitchDescription desc = createSwitchDescription(); + + // activate switch + IOFSwitchBackend sw = doActivateNewSwitch(dpid, desc, fr1); + + IOFSwitchListener listener = createMock(IOFSwitchListener.class); + switchManager.addOFSwitchListener(listener); + // setup switch with the new, second features reply (and thus ports) + setupSwitchForAddSwitch(sw, dpid, desc, fr2); + listener.switchPortChanged(dpid, p2, + PortChangeType.OTHER_UPDATE); + expectLastCall().once(); + replay(listener); + replay(sw); + switchManager.notifyPortChanged(sw, p2, + PortChangeType.OTHER_UPDATE); + controller.processUpdateQueueForTesting(); + verify(listener); + verify(sw); + + } + + /** + * Test the driver registry: test the bind order + */ + @Test + public void testSwitchDriverRegistryBindOrder() { + IOFSwitchDriver driver1 = createMock(IOFSwitchDriver.class); + IOFSwitchDriver driver2 = createMock(IOFSwitchDriver.class); + IOFSwitchDriver driver3 = createMock(IOFSwitchDriver.class); + IOFSwitchBackend returnedSwitch = null; + IOFSwitchBackend mockSwitch = createMock(IOFSwitchBackend.class); + switchManager.addOFSwitchDriver("", driver3); + switchManager.addOFSwitchDriver("test switch", driver1); + switchManager.addOFSwitchDriver("test", driver2); + + replay(driver1); + replay(driver2); + replay(driver3); + replay(mockSwitch); + + SwitchDescription description = new SwitchDescription( + "test switch", "version 0.9", "", "", ""); + reset(driver1); + reset(driver2); + reset(driver3); + reset(mockSwitch); + mockSwitch.setSwitchProperties(description); + expectLastCall().once(); + OFFactory factory = OFFactories.getFactory(OFVersion.OF_10); + expect(driver1.getOFSwitchImpl(description, factory)).andReturn(mockSwitch).once(); + replay(driver1); + replay(driver2); + replay(driver3); + replay(mockSwitch); + returnedSwitch = switchManager.getOFSwitchInstance(new NullConnection(), description, factory, DatapathId.of(1)); + assertSame(mockSwitch, returnedSwitch); + verify(driver1); + verify(driver2); + verify(driver3); + verify(mockSwitch); + + description = new SwitchDescription( + "testFooBar", "version 0.9", "", "", ""); + reset(driver1); + reset(driver2); + reset(driver3); + reset(mockSwitch); + mockSwitch.setSwitchProperties(description); + expectLastCall().once(); + expect(driver2.getOFSwitchImpl(description, factory)).andReturn(mockSwitch).once(); + replay(driver1); + replay(driver2); + replay(driver3); + replay(mockSwitch); + returnedSwitch = switchManager.getOFSwitchInstance(new NullConnection(), description, + OFFactories.getFactory(OFVersion.OF_10), DatapathId.of(1)); + assertSame(mockSwitch, returnedSwitch); + verify(driver1); + verify(driver2); + verify(driver3); + verify(mockSwitch); + + description = new SwitchDescription( + "FooBar", "version 0.9", "", "", ""); + reset(driver1); + reset(driver2); + reset(driver3); + reset(mockSwitch); + mockSwitch.setSwitchProperties(description); + expectLastCall().once(); + expect(driver3.getOFSwitchImpl(description, factory)).andReturn(mockSwitch).once(); + replay(driver1); + replay(driver2); + replay(driver3); + replay(mockSwitch); + returnedSwitch = switchManager.getOFSwitchInstance(new NullConnection(), description, factory, DatapathId.of(1)); + assertSame(mockSwitch, returnedSwitch); + verify(driver1); + verify(driver2); + verify(driver3); + verify(mockSwitch); + } + + /** + * Test SwitchDriverRegistry + * Test fallback to default if no switch driver is registered for a + * particular prefix + */ + @Test + public void testSwitchDriverRegistryNoDriver() { + IOFSwitchDriver driver = createMock(IOFSwitchDriver.class); + IOFSwitch returnedSwitch = null; + IOFSwitchBackend mockSwitch = createMock(IOFSwitchBackend.class); + switchManager.addOFSwitchDriver("test switch", driver); + + replay(driver); + replay(mockSwitch); + + SwitchDescription desc = new SwitchDescription("test switch", "version 0.9", "", "", ""); + reset(driver); + reset(mockSwitch); + mockSwitch.setSwitchProperties(desc); + expectLastCall().once(); + expect(driver.getOFSwitchImpl(desc, factory)).andReturn(mockSwitch).once(); + replay(driver); + replay(mockSwitch); + returnedSwitch = switchManager.getOFSwitchInstance(new NullConnection(), desc, factory, DatapathId.of(1)); + assertSame(mockSwitch, returnedSwitch); + verify(driver); + verify(mockSwitch); + + desc = new SwitchDescription("Foo Bar test switch", "version 0.9", "", "", ""); + reset(driver); + reset(mockSwitch); + replay(driver); + replay(mockSwitch); + returnedSwitch = switchManager.getOFSwitchInstance(new NullConnection(), desc, + OFFactories.getFactory(OFVersion.OF_10), DatapathId.of(1)); + assertNotNull(returnedSwitch); + assertTrue("Returned switch should be OFSwitch", + returnedSwitch instanceof OFSwitch); + assertEquals(desc, returnedSwitch.getSwitchDescription()); + verify(driver); + verify(mockSwitch); + } + + /** + * + */ + @Test + public void testDriverRegistryExceptions() { + IOFSwitchDriver driver = createMock(IOFSwitchDriver.class); + IOFSwitchDriver driver2 = createMock(IOFSwitchDriver.class); + replay(driver, driver2); // no calls expected on driver + + //--------------- + // Test exception handling when registering driver + try { + switchManager.addOFSwitchDriver("foobar", null); + fail("Expected NullPointerException not thrown"); + } catch (NullPointerException e) { + //expected + } + + try { + switchManager.addOFSwitchDriver(null, driver); + fail("Expected NullPointerException not thrown"); + } catch (NullPointerException e) { + //expected + } + + // test that we can register each prefix only once! + switchManager.addOFSwitchDriver("foobar", driver); + try { + switchManager.addOFSwitchDriver("foobar", driver); + fail("Expected IllegalStateException not thrown"); + } catch (IllegalStateException e) { + //expected + } + + try { + switchManager.addOFSwitchDriver("foobar", driver2); + fail("Expected IllegalStateException not thrown"); + } catch (IllegalStateException e) { + //expected + } + + //OFDescStatsReply desc = createOFDescStatsReply(); + //desc.setDatapathDescription(null); + SwitchDescription description = new SwitchDescription(null, "", "", "", ""); + try { + switchManager.getOFSwitchInstance(null, description, + OFFactories.getFactory(OFVersion.OF_10), DatapathId.of(1)); + fail("Expected NullPointerException not thrown"); + } catch (NullPointerException e) { + //expected + } + description = new SwitchDescription("", null, "", "", ""); + try { + switchManager.getOFSwitchInstance(null, description, + OFFactories.getFactory(OFVersion.OF_10), DatapathId.of(1)); + fail("Expected NullPointerException not thrown"); + } catch (NullPointerException e) { + //expected + } + description = new SwitchDescription("", "", null, "", ""); + try { + switchManager.getOFSwitchInstance(null, description, + OFFactories.getFactory(OFVersion.OF_10), DatapathId.of(1)); + fail("Expected NullPointerException not thrown"); + } catch (NullPointerException e) { + //expected + } + description = new SwitchDescription("", "", "", null, ""); + try { + switchManager.getOFSwitchInstance(null, description, + OFFactories.getFactory(OFVersion.OF_10), DatapathId.of(1)); + fail("Expected NullPointerException not thrown"); + } catch (NullPointerException e) { + //expected + } + description = new SwitchDescription("", "", "", "", null); + try { + switchManager.getOFSwitchInstance(null, description, + OFFactories.getFactory(OFVersion.OF_10), DatapathId.of(1)); + fail("Expected NullPointerException not thrown"); + } catch (NullPointerException e) { + //expected + + } + verify(driver, driver2); + } + + @Test + public void testRegisterCategory() { + + // Must be in INIT state + Timer timer = createMock(Timer.class); + replay(timer); + switchManager = new OFSwitchManager(); + switchManager.loadLogicalCategories(); + assertTrue("Connections should be empty", switchManager.getNumRequiredConnections() == 0); + + // Add initial category + switchManager = new OFSwitchManager(); + LogicalOFMessageCategory category = new LogicalOFMessageCategory("aux1", 1); + switchManager.registerLogicalOFMessageCategory(category); + switchManager.loadLogicalCategories(); + + assertTrue("Required connections should be 1", switchManager.getNumRequiredConnections() == 1); + + // Multiple categories on the same auxId should produce one required connection + switchManager = new OFSwitchManager(); + switchManager.registerLogicalOFMessageCategory(new LogicalOFMessageCategory("aux1", 1)); + switchManager.registerLogicalOFMessageCategory(new LogicalOFMessageCategory("aux1-2", 1)); + switchManager.loadLogicalCategories(); + + assertTrue("Required connections should be 1", switchManager.getNumRequiredConnections() == 1); + + // Adding a category on a different aux ID should increase the required connection count + switchManager = new OFSwitchManager(); + switchManager.registerLogicalOFMessageCategory(new LogicalOFMessageCategory("aux1", 1)); + switchManager.registerLogicalOFMessageCategory(new LogicalOFMessageCategory("aux2", 2)); + switchManager.loadLogicalCategories(); + assertTrue("Required connections should be 2", switchManager.getNumRequiredConnections() == 2); + } + + @Test + public void testRegisterCategoryException() { + + switchManager = new OFSwitchManager(); + switchManager.loadLogicalCategories(); + LogicalOFMessageCategory category = new LogicalOFMessageCategory("test", 1); + + // Wrong State + try { + switchManager.registerLogicalOFMessageCategory(category); + fail("Expected Unsupported Operation Exception not thrown"); + } catch (UnsupportedOperationException e) { /* expected */ } + + switchManager = new OFSwitchManager(); + + // Categories must have category with auxid of 1 + LogicalOFMessageCategory bad = new LogicalOFMessageCategory("bad", 2); + switchManager.registerLogicalOFMessageCategory(bad); + + try{ + switchManager.loadLogicalCategories(); + fail("Expected exception not thrown"); + } catch (IllegalStateException e) { /* expected */} + + // Non contiguous category auxids of (1,3) + switchManager = new OFSwitchManager(); + switchManager.registerLogicalOFMessageCategory(category); + LogicalOFMessageCategory nonContiguous = new LogicalOFMessageCategory("bad", 3); + switchManager.registerLogicalOFMessageCategory(nonContiguous); + + try{ + switchManager.loadLogicalCategories(); + fail("Expected exception not thrown"); + } catch (IllegalStateException e) { /* expected */} + } + + @Test + public void testNewConnectionOpened() { + MockOFConnection connection = new MockOFConnection(DATAPATH_ID_1, OFAuxId.MAIN); + OFFeaturesReply featuresReply = createOFFeaturesReply(DATAPATH_ID_1); + + // Assert no switch handlers + assertTrue(switchManager.getSwitchHandshakeHandlers().isEmpty()); + switchManager.connectionOpened(connection, featuresReply); + // Ensure a + assertTrue(switchManager.getSwitchHandshakeHandlers().size() == 1); + assertTrue(switchManager.getSwitchHandshakeHandlers().get(0).getDpid().equals(DATAPATH_ID_1)); + } + + @Test + public void testDuplicateConnectionOpened() { + // Seed with 1 connection and handler + testNewConnectionOpened(); + + MockOFConnection connection = new MockOFConnection(DATAPATH_ID_1, OFAuxId.MAIN); + OFFeaturesReply featuresReply = createOFFeaturesReply(DATAPATH_ID_1); + + switchManager.connectionOpened(connection, featuresReply); + + // Ensure duplicate connections are + assertTrue(switchManager.getSwitchHandshakeHandlers().size() == 1); + assertTrue(switchManager.getSwitchHandshakeHandlers().get(0).getDpid().equals(DATAPATH_ID_1)); + } + + @Test + public void testHandshakeDisconnected() { + // Seed with 1 connection and handler + testNewConnectionOpened(); + + assertTrue(switchManager.getSwitchHandshakeHandlers().size() == 1); + // Disconnect wrong handshake + switchManager.handshakeDisconnected(DATAPATH_ID_0); + assertTrue(switchManager.getSwitchHandshakeHandlers().size() == 1); + // Disconnect correct handshake + switchManager.handshakeDisconnected(DATAPATH_ID_1); + assertTrue(switchManager.getSwitchHandshakeHandlers().size() == 0); + + } +} diff --git a/src/test/java/net/floodlightcontroller/core/internal/OFSwitchTest.java b/src/test/java/net/floodlightcontroller/core/internal/OFSwitchTest.java new file mode 100644 index 0000000000000000000000000000000000000000..b5ca01ed6884656e520c40ccd5cc284e88eae5f9 --- /dev/null +++ b/src/test/java/net/floodlightcontroller/core/internal/OFSwitchTest.java @@ -0,0 +1,163 @@ +/** + * 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.core.internal; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.net.InetSocketAddress; +import java.net.URI; +import java.util.ArrayList; +import java.util.List; + +import org.easymock.EasyMock; +import org.junit.Before; +import org.junit.Test; +import net.floodlightcontroller.core.IOFSwitchBackend; +import net.floodlightcontroller.core.OFSwitch; +import net.floodlightcontroller.core.SwitchDriverSubHandshakeAlreadyStarted; +import net.floodlightcontroller.core.SwitchDriverSubHandshakeCompleted; +import net.floodlightcontroller.core.SwitchDriverSubHandshakeNotStarted; +import net.floodlightcontroller.core.util.URIUtil; +import org.projectfloodlight.openflow.protocol.OFBsnControllerConnection; +import org.projectfloodlight.openflow.protocol.OFBsnControllerConnectionState; +import org.projectfloodlight.openflow.protocol.OFBsnControllerConnectionsReply; +import org.projectfloodlight.openflow.protocol.OFControllerRole; +import org.projectfloodlight.openflow.protocol.OFFactories; +import org.projectfloodlight.openflow.protocol.OFFactory; +import org.projectfloodlight.openflow.protocol.OFMessage; +import org.projectfloodlight.openflow.protocol.OFNiciraControllerRole; +import org.projectfloodlight.openflow.protocol.OFVersion; +import org.projectfloodlight.openflow.types.DatapathId; +import org.projectfloodlight.openflow.types.OFAuxId; + +public class OFSwitchTest { + protected OFSwitch sw; + protected OFFactory factory = OFFactories.getFactory(OFVersion.OF_13); + + @Before + public void setUp() throws Exception { + MockOFConnection mockConnection = new MockOFConnection(DatapathId.of(1), OFAuxId.MAIN); + sw = new OFSwitch(mockConnection, OFFactories.getFactory(OFVersion.OF_10), + EasyMock.createMock(IOFSwitchManager.class), DatapathId.of(1)); + } + + @Test + public void testSetHARoleReply() { + sw.setControllerRole(OFControllerRole.ROLE_MASTER); + assertEquals(OFControllerRole.ROLE_MASTER, sw.getControllerRole()); + + sw.setControllerRole(OFControllerRole.ROLE_EQUAL); + assertEquals(OFControllerRole.ROLE_EQUAL, sw.getControllerRole()); + + sw.setControllerRole(OFControllerRole.ROLE_SLAVE); + assertEquals(OFControllerRole.ROLE_SLAVE, sw.getControllerRole()); + } + + @Test + public void testSubHandshake() { + OFFactory factory = OFFactories.getFactory(OFVersion.OF_10); + OFMessage m = factory.buildNiciraControllerRoleReply() + .setXid(1) + .setRole(OFNiciraControllerRole.ROLE_MASTER) + .build(); + // test exceptions before handshake is started + try { + sw.processDriverHandshakeMessage(m); + fail("expected exception not thrown"); + } catch (SwitchDriverSubHandshakeNotStarted e) { /* expected */ } + try { + sw.isDriverHandshakeComplete(); + fail("expected exception not thrown"); + } catch (SwitchDriverSubHandshakeNotStarted e) { /* expected */ } + + // start the handshake -- it should immediately complete + sw.startDriverHandshake(); + assertTrue("Handshake should be complete", + sw.isDriverHandshakeComplete()); + + // test exceptions after handshake is completed + try { + sw.processDriverHandshakeMessage(m); + fail("expected exception not thrown"); + } catch (SwitchDriverSubHandshakeCompleted e) { /* expected */ } + try { + sw.startDriverHandshake(); + fail("Expected exception not thrown"); + } catch (SwitchDriverSubHandshakeAlreadyStarted e) { /* expected */ } + } + + /** + * Helper to load controller connection messages into a switch for testing. + * @param sw the switch to insert the message on + * @param role the role for the controller connection message + * @param state the state for the controller connection message + * @param uri the URI for the controller connection message + */ + public void updateControllerConnections(IOFSwitchBackend sw, OFControllerRole role1, OFBsnControllerConnectionState state1, String uri1 + , OFControllerRole role2, OFBsnControllerConnectionState state2, String uri2) { + OFBsnControllerConnection connection1 = factory.buildBsnControllerConnection() + .setAuxiliaryId(OFAuxId.MAIN) + .setRole(role1) + .setState(state1) + .setUri(uri1) + .build(); + + OFBsnControllerConnection connection2 = factory.buildBsnControllerConnection() + .setAuxiliaryId(OFAuxId.MAIN) + .setRole(role2) + .setState(state2) + .setUri(uri2) + .build(); + + List<OFBsnControllerConnection> connections = new ArrayList<OFBsnControllerConnection>(); + connections.add(connection1); + connections.add(connection2); + + OFBsnControllerConnectionsReply reply = factory.buildBsnControllerConnectionsReply() + .setConnections(connections) + .build(); + + sw.updateControllerConnections(reply); + } + + /** + * This test ensures that the switch accurately determined if another master + * exists in the cluster by examining the controller connections it has. + */ + @Test + public void testHasAnotherMaster() { + URI cokeUri = URIUtil.createURI("1.2.3.4", 6653); + InetSocketAddress address = (InetSocketAddress) sw.getConnection(OFAuxId.MAIN).getLocalInetAddress(); + URI pepsiUri = URIUtil.createURI(address.getHostName(), address.getPort()); + + updateControllerConnections(sw, OFControllerRole.ROLE_SLAVE, OFBsnControllerConnectionState.BSN_CONTROLLER_CONNECTION_STATE_CONNECTED, cokeUri.toString(), + OFControllerRole.ROLE_MASTER, OFBsnControllerConnectionState.BSN_CONTROLLER_CONNECTION_STATE_CONNECTED, pepsiUri.toString()); + + // From the perspective of pepsi, the cluster currently does NOT have another master controller + assertFalse(sw.hasAnotherMaster()); + + // Switch the controller connections so that pepsi is no longer master + updateControllerConnections(sw, OFControllerRole.ROLE_MASTER, OFBsnControllerConnectionState.BSN_CONTROLLER_CONNECTION_STATE_CONNECTED, cokeUri.toString(), + OFControllerRole.ROLE_SLAVE, OFBsnControllerConnectionState.BSN_CONTROLLER_CONNECTION_STATE_CONNECTED, pepsiUri.toString()); + + // From the perspective of pepsi, the cluster currently has another master controller + assertTrue(sw.hasAnotherMaster()); + } +} diff --git a/src/test/java/net/floodlightcontroller/core/internal/RoleManagerTest.java b/src/test/java/net/floodlightcontroller/core/internal/RoleManagerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..861574b29ce04bde74fbfb1e220f45e559ead3ca --- /dev/null +++ b/src/test/java/net/floodlightcontroller/core/internal/RoleManagerTest.java @@ -0,0 +1,236 @@ +/** +* Copyright 2011, Big Switch Networks, Inc. +* Originally created by David Erickson, Stanford University +* +* Licensed under the Apache License, Version 2.0 (the "License"); you may +* not use this file except in compliance with the License. You may obtain +* a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +* License for the specific language governing permissions and limitations +* under the License. +**/ + +package net.floodlightcontroller.core.internal; + +import static org.easymock.EasyMock.anyObject; +import static org.junit.Assert.*; + +import org.junit.Before; +import org.junit.Test; + +import net.floodlightcontroller.test.FloodlightTestCase; + +import static org.easymock.EasyMock.createMock; +import static org.easymock.EasyMock.expect; +import static org.easymock.EasyMock.expectLastCall; +import static org.easymock.EasyMock.replay; +import static org.easymock.EasyMock.reset; +import static org.easymock.EasyMock.verify; + +import java.util.HashMap; + +import org.junit.After; + +import net.floodlightcontroller.core.HARole; +import net.floodlightcontroller.core.IOFSwitch; +import net.floodlightcontroller.core.IOFSwitchBackend; +import net.floodlightcontroller.core.IShutdownService; +import net.floodlightcontroller.core.internal.Controller.IUpdate; +import net.floodlightcontroller.core.test.MockSwitchManager; +import net.floodlightcontroller.debugcounter.IDebugCounterService; +import net.floodlightcontroller.debugcounter.MockDebugCounterService; +import org.projectfloodlight.openflow.types.DatapathId; + +public class RoleManagerTest extends FloodlightTestCase { + private Controller controller; + private RoleManager roleManager; + + private static DatapathId DATAPATH_ID_1 = DatapathId.of(1); + + @Override + @Before + public void setUp() throws Exception { + doSetUp(HARole.ACTIVE); + } + + private void doSetUp(HARole role) { + controller = createMock(Controller.class); + + // Mock controller behavior + reset(controller); + IDebugCounterService counterService = new MockDebugCounterService(); + expect(controller.getDebugCounter()).andReturn(counterService).anyTimes(); + replay(controller); + + + IShutdownService shutdownService = createMock(IShutdownService.class); + roleManager = new RoleManager(controller, shutdownService , role, "test"); + + // Make sure the desired role is set + assertTrue(roleManager.getRole().equals(role)); + } + + @After + public void tearDown() { + verify(controller); + } + + @Test + public void testSetRoleStandbyToActive() throws Exception { + doSetUp(HARole.STANDBY); + + this.setRoleAndMockController(HARole.ACTIVE); + + assertTrue(roleManager.getRole() == HARole.ACTIVE); + + } + + @Test + public void testSetRoleActiveToStandby() throws Exception { + // Set by default + assertTrue(roleManager.getRole() == HARole.ACTIVE); + + this.setRoleAndMockController(HARole.STANDBY); + + assertTrue(roleManager.getRole() == HARole.STANDBY); + + } + + @Test + public void testSetRoleActiveToActive() throws Exception { + // Set by default + assertTrue(roleManager.getRole() == HARole.ACTIVE); + + this.setRoleAndMockController(HARole.ACTIVE); + + assertTrue(roleManager.getRole() == HARole.ACTIVE); + + } + + @Test + public void testSetRoleStandbyToStandby() throws Exception { + doSetUp(HARole.STANDBY); + + this.setRoleAndMockController(HARole.STANDBY); + + assertTrue(roleManager.getRole() == HARole.STANDBY); + + } + + /** + * Helper method that mocks up the controller and sets the supplied role + * @param role the desired role to pass to setRole + */ + private void setRoleAndMockController(HARole role) { + reset(controller); + controller.addUpdateToQueue(anyObject(IUpdate.class)); + expectLastCall().anyTimes(); + replay(controller); + + roleManager.setRole(role, "test"); + } + + + @Test + public void testNotifyFollower() throws Exception { + // Set by default + assertTrue(roleManager.getRole() == HARole.ACTIVE); + + reset(controller); + controller.addUpdateToQueue(anyObject(IUpdate.class)); + expectLastCall().anyTimes(); + replay(controller); + + // Test ACTIVE + roleManager.notify(); + + assertTrue(roleManager.getRole() == HARole.STANDBY); + + // Test STANDBY + roleManager.notify(); + + assertTrue(roleManager.getRole() == HARole.STANDBY); + + } + + @Test + public void testNotifyLeaderNoMaster() { + doSetUp(HARole.STANDBY); + + // Another master does NOT exist + setupSwitchesForNotifyLeader(false); + + roleManager.notify(); + + assertTrue(roleManager.getRole() == HARole.ACTIVE); + } + + @Test + public void testNotifyLeaderAnotherMaster() { + doSetUp(HARole.STANDBY); + + // Another master exists + setupSwitchesForNotifyLeader(true); + + roleManager.notify(); + + assertTrue(roleManager.getRole() == HARole.STANDBY); + } + + @Test + public void testNotifyLeaderSplitBrainProtection() throws Exception { + doSetUp(HARole.STANDBY); + + /* Split brain protection should not allow a controller to become ACTIVE + * if another ACTIVE controller exists in the cluster. + */ + setupSwitchesForNotifyLeader(true); + + roleManager.notify(); + + assertTrue(roleManager.getRole() == HARole.STANDBY); + + /* At this point if the leader in the split brain scenario goes down, + * the controller connections should be updated to reflect that and + * leader notification should succeed as no other ACTIVE controller exists. + */ + setupSwitchesForNotifyLeader(false); + + /* Since the roleManager has already been notified, the controller connections + * should prompt an update. + */ + roleManager.notifyControllerConnectionUpdate(); + + assertTrue(roleManager.getRole() == HARole.ACTIVE); + } + + /** + * Helper to setup switches to test NotifyLeader scenarios. + * @param hasAnotherMaster whether or not the switches should have another master + */ + public void setupSwitchesForNotifyLeader(boolean hasAnotherMaster) { + reset(controller); + // Setup switches with another master + MockSwitchManager switchManager = new MockSwitchManager(); + + IOFSwitchBackend sw1 = createMock(IOFSwitchBackend.class); + reset(sw1); + expect(sw1.hasAnotherMaster()).andReturn(hasAnotherMaster).anyTimes(); + replay(sw1); + + HashMap<DatapathId, IOFSwitch> switches = new HashMap<DatapathId, IOFSwitch> (); + switches.put(DATAPATH_ID_1, sw1); + switchManager.setSwitches(switches); + + expect(controller.getSwitchService()).andReturn(switchManager).once(); + controller.addUpdateToQueue(anyObject(IUpdate.class)); + expectLastCall().anyTimes(); + replay(controller); + + } +} \ No newline at end of file diff --git a/src/test/java/net/floodlightcontroller/core/module/FloodlightTestModuleLoader.java b/src/test/java/net/floodlightcontroller/core/module/FloodlightTestModuleLoader.java deleted file mode 100644 index b40715a66ea3e418b534cc8f5d91eff70d68d7b5..0000000000000000000000000000000000000000 --- a/src/test/java/net/floodlightcontroller/core/module/FloodlightTestModuleLoader.java +++ /dev/null @@ -1,198 +0,0 @@ -/** - * 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.core.module; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Iterator; - -import org.sdnplatform.sync.test.MockSyncService; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import net.floodlightcontroller.core.module.FloodlightModuleLoader; -import net.floodlightcontroller.core.module.IFloodlightModule; -import net.floodlightcontroller.core.test.MockFloodlightProvider; -import net.floodlightcontroller.core.test.MockThreadPoolService; -import net.floodlightcontroller.counter.NullCounterStore; -import net.floodlightcontroller.devicemanager.internal.DefaultEntityClassifier; -import net.floodlightcontroller.devicemanager.test.MockDeviceManager; -import net.floodlightcontroller.perfmon.NullPktInProcessingTime; -import net.floodlightcontroller.storage.memory.MemoryStorageSource; -import net.floodlightcontroller.topology.TopologyManager; - -public class FloodlightTestModuleLoader extends FloodlightModuleLoader { - protected static Logger log = LoggerFactory.getLogger(FloodlightTestModuleLoader.class); - - // List of default modules to use unless specified otherwise - public static final Class<? extends IFloodlightModule> DEFAULT_STORAGE_SOURCE = - MemoryStorageSource.class; - public static final Class<? extends IFloodlightModule> DEFAULT_FLOODLIGHT_PRPOVIDER = - MockFloodlightProvider.class; - public static final Class<? extends IFloodlightModule> DEFAULT_TOPOLOGY_PROVIDER = - TopologyManager.class; - public static final Class<? extends IFloodlightModule> DEFAULT_DEVICE_SERVICE = - MockDeviceManager.class; - public static final Class<? extends IFloodlightModule> DEFAULT_COUNTER_STORE = - NullCounterStore.class; - public static final Class<? extends IFloodlightModule> DEFAULT_THREADPOOL = - MockThreadPoolService.class; - public static final Class<? extends IFloodlightModule> DEFAULT_ENTITY_CLASSIFIER = - DefaultEntityClassifier.class; - public static final Class<? extends IFloodlightModule> DEFAULT_PERFMON = - NullPktInProcessingTime.class; - public static final Class<? extends IFloodlightModule> DEFAULT_SYNC_SERVICE = - MockSyncService.class; - - protected static final Collection<Class<? extends IFloodlightModule>> DEFAULT_MODULE_LIST; - - static { - DEFAULT_MODULE_LIST = new ArrayList<Class<? extends IFloodlightModule>>(); - DEFAULT_MODULE_LIST.add(DEFAULT_DEVICE_SERVICE); - DEFAULT_MODULE_LIST.add(DEFAULT_FLOODLIGHT_PRPOVIDER); - DEFAULT_MODULE_LIST.add(DEFAULT_STORAGE_SOURCE); - DEFAULT_MODULE_LIST.add(DEFAULT_TOPOLOGY_PROVIDER); - DEFAULT_MODULE_LIST.add(DEFAULT_COUNTER_STORE); - DEFAULT_MODULE_LIST.add(DEFAULT_THREADPOOL); - DEFAULT_MODULE_LIST.add(DEFAULT_ENTITY_CLASSIFIER); - DEFAULT_MODULE_LIST.add(DEFAULT_PERFMON); - DEFAULT_MODULE_LIST.add(DEFAULT_SYNC_SERVICE); - } - - protected IFloodlightModuleContext fmc; - - /** - * Adds default modules to the list of modules to load. This is done - * in order to avoid the module loader throwing errors about duplicate - * modules and neither one is specified by the user. - * @param userModules The list of user specified modules to add to. - */ - protected void addDefaultModules(Collection<Class<? extends IFloodlightModule>> userModules) { - Collection<Class<? extends IFloodlightModule>> defaultModules = - new ArrayList<Class<? extends IFloodlightModule>>(DEFAULT_MODULE_LIST.size()); - defaultModules.addAll(DEFAULT_MODULE_LIST); - - Iterator<Class<? extends IFloodlightModule>> modIter = userModules.iterator(); - while (modIter.hasNext()) { - Class<? extends IFloodlightModule> userMod = modIter.next(); - Iterator<Class<? extends IFloodlightModule>> dmIter = defaultModules.iterator(); - while (dmIter.hasNext()) { - Class<? extends IFloodlightModule> dmMod = dmIter.next(); - Collection<Class<? extends IFloodlightService>> userModServs; - Collection<Class<? extends IFloodlightService>> dmModServs; - try { - dmModServs = dmMod.newInstance().getModuleServices(); - userModServs = userMod.newInstance().getModuleServices(); - } catch (InstantiationException e) { - log.error(e.getMessage()); - break; - } catch (IllegalAccessException e) { - log.error(e.getMessage()); - break; - } - - // If either of these are null continue as they have no services - if (dmModServs == null || userModServs == null) continue; - - // If the user supplied modules has a service - // that is in the default module list we remove - // the default module from the list. - boolean shouldBreak = false; - Iterator<Class<? extends IFloodlightService>> userModServsIter - = userModServs.iterator(); - while (userModServsIter.hasNext()) { - Class<? extends IFloodlightService> userModServIntf = userModServsIter.next(); - Iterator<Class<? extends IFloodlightService>> dmModsServsIter - = dmModServs.iterator(); - while (dmModsServsIter.hasNext()) { - Class<? extends IFloodlightService> dmModServIntf - = dmModsServsIter.next(); - - if (dmModServIntf.getCanonicalName().equals( - userModServIntf.getCanonicalName())) { - logger.debug("Removing default module {} because it was " + - "overriden by an explicitly specified module", - dmModServIntf.getCanonicalName()); - dmIter.remove(); - shouldBreak = true; - break; - } - } - if (shouldBreak) break; - } - if (shouldBreak) break; - } - } - - // Append the remaining default modules to the user specified ones. - // This avoids the module loader throwing duplicate module errors. - userModules.addAll(defaultModules); - log.debug("Using module set " + userModules.toString()); - } - - /** - * Sets up all modules and their dependencies. - * @param modules The list of modules that the user wants to load. - * @param mockedServices The list of services that will be mocked. Any - * module that provides this service will not be loaded. - */ - public void setupModules(Collection<Class<? extends IFloodlightModule>> modules, - Collection<IFloodlightService> mockedServices) throws FloodlightModuleException { - addDefaultModules(modules); - Collection<String> modulesAsString = new ArrayList<String>(); - for (Class<? extends IFloodlightModule> m : modules) { - modulesAsString.add(m.getCanonicalName()); - } - - fmc = loadModulesFromList(modulesAsString, null, mockedServices); - } - - /** - * Gets the inited/started instance of a module from the context. - * @param ifl The name if the module to get, i.e. "LearningSwitch.class". - * @return The inited/started instance of the module. - */ - public IFloodlightModule getModuleByName(Class<? extends IFloodlightModule> ifl) { - Collection<IFloodlightModule> modules = fmc.getAllModules(); - for (IFloodlightModule m : modules) { - if (ifl.getCanonicalName().equals(m.getClass().getCanonicalName())) { - return m; - } - } - return null; - } - - /** - * Gets an inited/started instance of a service from the context. - * @param ifs The name of the service to get, i.e. "ITopologyService.class". - * @return The inited/started instance of the service from teh context. - */ - public IFloodlightService getModuleByService(Class<? extends IFloodlightService> ifs) { - Collection<IFloodlightModule> modules = fmc.getAllModules(); - for (IFloodlightModule m : modules) { - Collection<Class<? extends IFloodlightService>> mServs = m.getModuleServices(); - if (mServs == null) continue; - for (Class<? extends IFloodlightService> mServClass : mServs) { - if (mServClass.getCanonicalName().equals(ifs.getCanonicalName())) { - assert(m instanceof IFloodlightService); - return (IFloodlightService)m; - } - } - } - return null; - } -} diff --git a/src/test/java/net/floodlightcontroller/core/test/MockFloodlightProvider.java b/src/test/java/net/floodlightcontroller/core/test/MockFloodlightProvider.java index 92e082b740c276bee6de2e19c3288783a76e387d..f5f89350abb048234cd368bc773f45187ff98f05 100644 --- a/src/test/java/net/floodlightcontroller/core/test/MockFloodlightProvider.java +++ b/src/test/java/net/floodlightcontroller/core/test/MockFloodlightProvider.java @@ -17,45 +17,51 @@ package net.floodlightcontroller.core.test; -import java.lang.management.ManagementFactory; -import java.lang.management.RuntimeMXBean; +import static org.junit.Assert.fail; + import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; -import java.util.Set; -import java.util.HashSet; import java.util.Map; import java.util.Map.Entry; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +import org.jboss.netty.util.Timer; import net.floodlightcontroller.core.FloodlightContext; import net.floodlightcontroller.core.HAListenerTypeMarker; +import net.floodlightcontroller.core.HARole; import net.floodlightcontroller.core.IFloodlightProviderService; import net.floodlightcontroller.core.IHAListener; import net.floodlightcontroller.core.IInfoProvider; +import net.floodlightcontroller.core.IListener.Command; import net.floodlightcontroller.core.IOFMessageListener; import net.floodlightcontroller.core.IOFSwitch; -import net.floodlightcontroller.core.IOFSwitchDriver; -import net.floodlightcontroller.core.IOFSwitchListener; -import net.floodlightcontroller.core.IListener.Command; -import net.floodlightcontroller.core.IReadyForReconcileListener; import net.floodlightcontroller.core.RoleInfo; +import net.floodlightcontroller.core.internal.Controller.IUpdate; +import net.floodlightcontroller.core.internal.Controller.ModuleLoaderState; +import net.floodlightcontroller.core.internal.RoleManager; 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.ListenerDispatcher; + +import org.projectfloodlight.openflow.protocol.OFMessage; +import org.projectfloodlight.openflow.protocol.OFPacketIn; +import org.projectfloodlight.openflow.protocol.OFType; + import net.floodlightcontroller.packet.Ethernet; -import org.openflow.protocol.OFMessage; -import org.openflow.protocol.OFPacketIn; -import org.openflow.protocol.OFType; -import org.openflow.protocol.factory.BasicFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -64,26 +70,31 @@ import org.slf4j.LoggerFactory; * @author David Erickson (daviderickson@cs.stanford.edu) */ public class MockFloodlightProvider implements IFloodlightModule, IFloodlightProviderService { - protected static Logger log = LoggerFactory.getLogger(MockFloodlightProvider.class); + private final static Logger log = LoggerFactory.getLogger(MockFloodlightProvider.class); protected ConcurrentMap<OFType, ListenerDispatcher<OFType,IOFMessageListener>> listeners; - protected List<IOFSwitchListener> switchListeners; protected ListenerDispatcher<HAListenerTypeMarker, IHAListener> haListeners; - protected Map<Long, IOFSwitch> switches; - protected BasicFactory factory; - private Role role; + private HARole role; + private final String openFlowHostname = "127.0.0.1"; + private final int openFlowPort = 6653; + private final boolean useAsyncUpdates; + private volatile ExecutorService executorService; + private volatile Future<?> mostRecentUpdateFuture; /** * */ - public MockFloodlightProvider() { + public MockFloodlightProvider(boolean useAsyncUpdates) { listeners = new ConcurrentHashMap<OFType, ListenerDispatcher<OFType, IOFMessageListener>>(); - switches = new ConcurrentHashMap<Long, IOFSwitch>(); - switchListeners = new CopyOnWriteArrayList<IOFSwitchListener>(); haListeners = new ListenerDispatcher<HAListenerTypeMarker, IHAListener>(); - factory = BasicFactory.getInstance(); + role = null; + this.useAsyncUpdates = useAsyncUpdates; + } + + public MockFloodlightProvider() { + this(false); } @Override @@ -115,48 +126,17 @@ public class MockFloodlightProvider implements IFloodlightModule, IFloodlightPro public Map<OFType, List<IOFMessageListener>> getListeners() { Map<OFType, List<IOFMessageListener>> lers = new HashMap<OFType, List<IOFMessageListener>>(); - for(Entry<OFType, ListenerDispatcher<OFType, IOFMessageListener>> e : - listeners.entrySet()) { - lers.put(e.getKey(), e.getValue().getOrderedListeners()); - } - return Collections.unmodifiableMap(lers); + for(Entry<OFType, ListenerDispatcher<OFType, IOFMessageListener>> e : + listeners.entrySet()) { + lers.put(e.getKey(), e.getValue().getOrderedListeners()); + } + return Collections.unmodifiableMap(lers); } public void clearListeners() { this.listeners.clear(); } - @Override - public Map<Long,IOFSwitch> getAllSwitchMap() { - return Collections.unmodifiableMap(this.switches); - } - - @Override - public Set<Long> getAllSwitchDpids() { - // the contract for getAllSwitchDpids says the caller will own the - // returned set - return new HashSet<Long>(this.switches.keySet()); - } - - @Override - public IOFSwitch getSwitch(long dpid) { - return this.switches.get(dpid); - } - - public void setSwitches(Map<Long, IOFSwitch> switches) { - this.switches = switches; - } - - @Override - public void addOFSwitchListener(IOFSwitchListener listener) { - switchListeners.add(listener); - } - - @Override - public void removeOFSwitchListener(IOFSwitchListener listener) { - switchListeners.remove(listener); - } - public void dispatchMessage(IOFSwitch sw, OFMessage msg) { dispatchMessage(sw, msg, new FloodlightContext()); } @@ -169,7 +149,7 @@ public class MockFloodlightProvider implements IFloodlightModule, IFloodlightPro if (OFType.PACKET_IN.equals(msg.getType())) { OFPacketIn pi = (OFPacketIn)msg; Ethernet eth = new Ethernet(); - eth.deserialize(pi.getPacketData(), 0, pi.getPacketData().length); + eth.deserialize(pi.getData(), 0, pi.getData().length); IFloodlightProviderService.bcStore.put(bc, IFloodlightProviderService.CONTEXT_PI_PAYLOAD, eth); @@ -181,7 +161,8 @@ public class MockFloodlightProvider implements IFloodlightModule, IFloodlightPro } @Override - public void handleOutgoingMessage(IOFSwitch sw, OFMessage m, FloodlightContext bc) { + public void handleOutgoingMessage(IOFSwitch sw, OFMessage m) { + FloodlightContext bc = new FloodlightContext(); List<IOFMessageListener> msgListeners = null; if (listeners.containsKey(m.getType())) { msgListeners = listeners.get(m.getType()).getOrderedListeners(); @@ -198,42 +179,23 @@ public class MockFloodlightProvider implements IFloodlightModule, IFloodlightPro public void handleOutgoingMessages(IOFSwitch sw, List<OFMessage> msglist, FloodlightContext bc) { for (OFMessage m:msglist) { - handleOutgoingMessage(sw, m, bc); + handleOutgoingMessage(sw, m); } } - /** - * @return the switchListeners - */ - public List<IOFSwitchListener> getSwitchListeners() { - return switchListeners; - } - - @Override - public void terminate() { - } - - @Override - public boolean injectOfMessage(IOFSwitch sw, OFMessage msg) { - dispatchMessage(sw, msg); - return true; - } - - @Override - public boolean injectOfMessage(IOFSwitch sw, OFMessage msg, - FloodlightContext bContext) { - dispatchMessage(sw, msg, bContext); - return true; - } - - @Override - public BasicFactory getOFMessageFactory() { - return factory; - } - @Override public void run() { logListeners(); + if (useAsyncUpdates) + executorService = Executors.newSingleThreadExecutor(); + } + + public void shutdown() { + if (executorService != null) { + executorService.shutdownNow(); + executorService = null; + mostRecentUpdateFuture = null; + } } @Override @@ -262,34 +224,62 @@ public class MockFloodlightProvider implements IFloodlightModule, IFloodlightPro } @Override - public void init(FloodlightModuleContext context) - throws FloodlightModuleException { - // TODO Auto-generated method stub - + public void init(FloodlightModuleContext context) throws FloodlightModuleException { + // do nothing. } @Override public void startUp(FloodlightModuleContext context) { - // TODO Auto-generated method stub - + // do nothing. } @Override public void addInfoProvider(String type, IInfoProvider provider) { - // TODO Auto-generated method stub - + // do nothing. } @Override public void removeInfoProvider(String type, IInfoProvider provider) { - // TODO Auto-generated method stub - + // do nothing. } @Override public Map<String, Object> getControllerInfo(String type) { - // TODO Auto-generated method stub - return null; + // mock up something + Map<String, Object> summary = new HashMap<String, Object>(); + summary.put("test-summary-1", 2); + summary.put("test-summary-2", 5); + return summary; + } + + @Override + public void addUpdateToQueue(final IUpdate update) { + if (useAsyncUpdates) { + mostRecentUpdateFuture = executorService.submit(new Runnable() { + @Override + public void run() { + update.dispatch(); + } + }); + } else { + update.dispatch(); + } + } + + public void waitForUpdates(long timeout, TimeUnit unit) throws InterruptedException { + + long timeoutNanos = unit.toNanos(timeout); + long start = System.nanoTime(); + for (;;) { + Future<?> future = mostRecentUpdateFuture; + if ((future == null) || future.isDone()) + break; + Thread.sleep(100); + long now = System.nanoTime(); + if (now > start + timeoutNanos) { + fail("Timeout waiting for update tasks to complete"); + } + } } @Override @@ -303,7 +293,7 @@ public class MockFloodlightProvider implements IFloodlightModule, IFloodlightPro } @Override - public Role getRole() { + public HARole getRole() { /* DISABLE THIS CHECK FOR NOW. OTHER UNIT TESTS NEED TO BE UPDATED * FIRST if (this.role == null) @@ -315,7 +305,7 @@ public class MockFloodlightProvider implements IFloodlightModule, IFloodlightPro } @Override - public void setRole(Role role, String roleChangeDescription) { + public void setRole(HARole role, String roleChangeDescription) { this.role = role; } @@ -324,22 +314,25 @@ public class MockFloodlightProvider implements IFloodlightModule, IFloodlightPro * @param oldRole * @param newRole */ - public void transitionToMaster() { - for (IHAListener rl : haListeners.getOrderedListeners()) { - rl.transitionToMaster(); - } + public void transitionToActive() { + IUpdate update = new IUpdate() { + @Override + public void dispatch() { + for (IHAListener rl : haListeners.getOrderedListeners()) { + rl.transitionToActive(); + } + } + }; + addUpdateToQueue(update); } - @Override public Map<String, String> getControllerNodeIPs() { - // TODO Auto-generated method stub return null; } @Override public long getSystemStartTime() { - // TODO Auto-generated method stub return 0; } @@ -366,51 +359,67 @@ public class MockFloodlightProvider implements IFloodlightModule, IFloodlightPro } @Override - public void setAlwaysClearFlowsOnSwActivate(boolean value) { + public RoleInfo getRoleInfo() { // TODO Auto-generated method stub + return null; + } + + @Override + public Map<String, Long> getMemory() { + Map<String, Long> m = new HashMap<String, Long>(); + m.put("total", 1000000000L); + m.put("free", 20000000L); + return m; + } + @Override + public Long getUptime() { + return 1000000L; } @Override - public void addOFSwitchDriver(String desc, IOFSwitchDriver driver) { - // TODO Auto-generated method stub + public String getOFHostname() { + return openFlowHostname; + } + @Override + public int getOFPort() { + return openFlowPort; } @Override - public RoleInfo getRoleInfo() { - // TODO Auto-generated method stub - return null; + public void handleMessage(IOFSwitch sw, OFMessage m, + FloodlightContext bContext) { + // do nothing } @Override - public Map<String, Long> getMemory() { - Map<String, Long> m = new HashMap<String, Long>(); - Runtime runtime = Runtime.getRuntime(); - m.put("total", runtime.totalMemory()); - m.put("free", runtime.freeMemory()); - return m; + public Timer getTimer() { + return null; } @Override - public Long getUptime() { - RuntimeMXBean rb = ManagementFactory.getRuntimeMXBean(); - return rb.getUptime(); + public RoleManager getRoleManager() { + return null; } @Override - public void addReadyForReconcileListener(IReadyForReconcileListener l) { - // do nothing. + public ModuleLoaderState getModuleLoaderState() { + return null; } @Override - public void addSwitchEvent(long switchDPID, String reason, boolean flushNow) { - // TODO Auto-generated method stub + public String getControllerId() { + return null; } @Override public Set<String> getUplinkPortPrefixSet() { - // TODO Auto-generated method stub return null; } + + @Override + public int getWorkerThreads() { + return 0; + } } diff --git a/src/test/java/net/floodlightcontroller/core/test/MockSwitchManager.java b/src/test/java/net/floodlightcontroller/core/test/MockSwitchManager.java new file mode 100644 index 0000000000000000000000000000000000000000..2bd6d0c376585831fb8d37f273a0aa0ed2ae71c3 --- /dev/null +++ b/src/test/java/net/floodlightcontroller/core/test/MockSwitchManager.java @@ -0,0 +1,253 @@ +package net.floodlightcontroller.core.test; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import net.floodlightcontroller.core.FloodlightContext; +import net.floodlightcontroller.core.IOFConnectionBackend; +import net.floodlightcontroller.core.IOFSwitch; +import net.floodlightcontroller.core.IOFSwitch.SwitchStatus; +import net.floodlightcontroller.core.IOFSwitchBackend; +import net.floodlightcontroller.core.IOFSwitchDriver; +import net.floodlightcontroller.core.IOFSwitchListener; +import net.floodlightcontroller.core.LogicalOFMessageCategory; +import net.floodlightcontroller.core.PortChangeType; +import net.floodlightcontroller.core.SwitchDescription; +import net.floodlightcontroller.core.internal.IAppHandshakePluginFactory; +import net.floodlightcontroller.core.internal.IOFSwitchManager; +import net.floodlightcontroller.core.internal.IOFSwitchService; +import net.floodlightcontroller.core.internal.OFSwitchHandshakeHandler; +import net.floodlightcontroller.core.internal.SwitchManagerCounters; +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.rest.SwitchRepresentation; +import net.floodlightcontroller.debugcounter.DebugCounterServiceImpl; + +import org.projectfloodlight.openflow.protocol.OFFactory; +import org.projectfloodlight.openflow.protocol.OFMessage; +import org.projectfloodlight.openflow.protocol.OFPortDesc; +import org.projectfloodlight.openflow.types.DatapathId; + +import com.google.common.collect.ImmutableList; + +public class MockSwitchManager implements IFloodlightModule, IOFSwitchManager, IOFSwitchService { + + private Map<DatapathId, OFSwitchHandshakeHandler> switchHandlers; + private Map<DatapathId, IOFSwitch> switches; + private final SwitchManagerCounters counters; + //private final CopyOnWriteArrayList<IOFSwitchListener> switchListeners; + + public MockSwitchManager(){ + switchHandlers = new ConcurrentHashMap<DatapathId, OFSwitchHandshakeHandler>(); + switches = new ConcurrentHashMap<DatapathId, IOFSwitch>(); + counters = new SwitchManagerCounters(new DebugCounterServiceImpl()); + //switchListeners = new CopyOnWriteArrayList<IOFSwitchListener>(); + } + + @Override + public void switchAdded(IOFSwitchBackend sw) { + // do nothing + + } + + @Override + public void switchDisconnected(IOFSwitchBackend sw) { + // do nothing + + } + + @Override + public void handshakeDisconnected(DatapathId dpid) { + // do nothing + } + + @Override + public void notifyPortChanged(IOFSwitchBackend sw, OFPortDesc port, + PortChangeType type) { + // do nothing + + } + + @Override + public IOFSwitchBackend + getOFSwitchInstance(IOFConnectionBackend connection, + SwitchDescription description, + OFFactory factory, DatapathId datapathId) { + return null; + } + + @Override + public void handleMessage(IOFSwitchBackend sw, OFMessage m, + FloodlightContext bContext) { + // do nothing + + } + + public void setSwitchHandshakeHandlers(Map<DatapathId, OFSwitchHandshakeHandler> handlers) { + this.switchHandlers = handlers; + } + @Override + public ImmutableList<OFSwitchHandshakeHandler> + getSwitchHandshakeHandlers() { + return ImmutableList.copyOf(this.switchHandlers.values()); + } + + @Override + public void addOFSwitchDriver(String manufacturerDescriptionPrefix, + IOFSwitchDriver driver) { + // do nothing + + } + + @Override + public void switchStatusChanged(IOFSwitchBackend sw, + SwitchStatus oldStatus, + SwitchStatus newStatus) { + // do nothing + + } + + @Override + public int getNumRequiredConnections() { + return 0; + } + + @Override + public void addSwitchEvent(DatapathId switchDpid, String reason, + boolean flushNow) { + // do nothing + + } + + @Override + public List<IAppHandshakePluginFactory> getHandshakePlugins() { + return null; + } + + @Override + public SwitchManagerCounters getCounters() { + return this.counters; + } + + @Override + public boolean isCategoryRegistered(LogicalOFMessageCategory category) { + return false; + } + + public void setSwitches(Map<DatapathId, IOFSwitch> switches) { + this.switches = switches; + } + + @Override + public Map<DatapathId, IOFSwitch> getAllSwitchMap() { + return Collections.unmodifiableMap(switches); + } + + @Override + public IOFSwitch getSwitch(DatapathId dpid) { + return this.switches.get(dpid); + } + + @Override + public IOFSwitch getActiveSwitch(DatapathId dpid) { + IOFSwitch sw = this.switches.get(dpid); + if(sw != null && sw.getStatus().isVisible()) + return sw; + else + return null; + } + + @Override + public void addOFSwitchListener(IOFSwitchListener listener) { + // do nothing + } + + @Override + public void removeOFSwitchListener(IOFSwitchListener listener) { + // do nothing + } + + @Override + public void registerLogicalOFMessageCategory(LogicalOFMessageCategory category) { + // do nothing + } + + @Override + public void registerHandshakePlugin(IAppHandshakePluginFactory plugin) { + // do nothing + + } + + @Override + public Set<DatapathId> getAllSwitchDpids() { + return this.switches.keySet(); + } + + @Override + public Collection<Class<? extends IFloodlightService>> + getModuleServices() { + Collection<Class<? extends IFloodlightService>> services = + new ArrayList<Class<? extends IFloodlightService>>(1); + services.add(IOFSwitchService.class); + return services; + } + + @Override + public Map<Class<? extends IFloodlightService>, IFloodlightService> + getServiceImpls() { + Map<Class<? extends IFloodlightService>, + IFloodlightService> m = + new HashMap<Class<? extends IFloodlightService>, + IFloodlightService>(); + m.put(IOFSwitchService.class, this); + return m; + } + + @Override + public Collection<Class<? extends IFloodlightService>> + getModuleDependencies() { + return null; + } + + @Override + public void init(FloodlightModuleContext context) throws FloodlightModuleException { + // do nothing + } + + @Override + public void startUp(FloodlightModuleContext context) throws FloodlightModuleException { + // do nothing + } + + @Override + public List<SwitchRepresentation> getSwitchRepresentations() { + List<SwitchRepresentation> representations = new ArrayList<SwitchRepresentation>(); + + for(DatapathId dpid : this.switches.keySet()) { + SwitchRepresentation representation = getSwitchRepresentation(dpid); + if(representation != null) { + representations.add(representation); + } + } + return representations; + } + + @Override + public SwitchRepresentation getSwitchRepresentation(DatapathId dpid) { + IOFSwitch sw = this.switches.get(dpid); + OFSwitchHandshakeHandler handler = this.switchHandlers.get(dpid); + + if(sw != null && handler != null) { + return new SwitchRepresentation(sw, handler); + } + return null; + } +} diff --git a/src/test/java/net/floodlightcontroller/core/test/MockThreadPoolService.java b/src/test/java/net/floodlightcontroller/core/test/MockThreadPoolService.java index 2e59ba4c8f7706db536a985caa3577b7e7ab6908..42017f4a4d97054af0b778e4e6a263817a67c694 100644 --- a/src/test/java/net/floodlightcontroller/core/test/MockThreadPoolService.java +++ b/src/test/java/net/floodlightcontroller/core/test/MockThreadPoolService.java @@ -79,5 +79,4 @@ public class MockThreadPoolService implements IFloodlightModule, IThreadPoolServ public void startUp(FloodlightModuleContext context) { // no-op } - } diff --git a/src/test/java/net/floodlightcontroller/core/test/PacketFactory.java b/src/test/java/net/floodlightcontroller/core/test/PacketFactory.java index 07f8c3dd5295233dde8e5405816df12cc17bc031..a4779c1d3af0f30b520a3f195979ffeb4fe1b0d8 100644 --- a/src/test/java/net/floodlightcontroller/core/test/PacketFactory.java +++ b/src/test/java/net/floodlightcontroller/core/test/PacketFactory.java @@ -19,18 +19,20 @@ package net.floodlightcontroller.core.test; import java.util.ArrayList; import java.util.List; +import net.floodlightcontroller.core.IOFSwitch; + +import org.projectfloodlight.openflow.protocol.OFFactory; +import org.projectfloodlight.openflow.protocol.OFPacketIn; +import org.projectfloodlight.openflow.protocol.OFPacketInReason; +import org.projectfloodlight.openflow.types.IpProtocol; +import org.projectfloodlight.openflow.types.MacAddress; +import org.projectfloodlight.openflow.types.OFPort; + import net.floodlightcontroller.packet.DHCP; import net.floodlightcontroller.packet.DHCPOption; import net.floodlightcontroller.packet.Ethernet; import net.floodlightcontroller.packet.IPv4; import net.floodlightcontroller.packet.UDP; -import net.floodlightcontroller.util.MACAddress; - -import org.openflow.protocol.OFPacketIn; -import org.openflow.protocol.OFPacketOut; -import org.openflow.protocol.OFType; -import org.openflow.protocol.OFPacketIn.OFPacketInReason; -import org.openflow.protocol.factory.BasicFactory; /** * A class to that creates many types of L2/L3/L4 or OpenFlow packets. @@ -41,88 +43,88 @@ import org.openflow.protocol.factory.BasicFactory; public class PacketFactory { public static String broadcastMac = "ff:ff:ff:ff:ff:ff"; public static String broadcastIp = "255.255.255.255"; - protected static BasicFactory OFMessageFactory = BasicFactory.getInstance(); - + /** * Generates a DHCP request OFPacketIn. * @param hostMac The host MAC address of for the request. * @return An OFPacketIn that contains a DHCP request packet. */ - public static OFPacketIn DhcpDiscoveryRequestOFPacketIn(MACAddress hostMac) { + public static OFPacketIn DhcpDiscoveryRequestOFPacketIn(IOFSwitch sw, + MacAddress hostMac) { byte[] serializedPacket = DhcpDiscoveryRequestEthernet(hostMac).serialize(); - return (((OFPacketIn)OFMessageFactory - .getMessage(OFType.PACKET_IN)) - .setBufferId(OFPacketOut.BUFFER_ID_NONE) - .setInPort((short) 1) - .setPacketData(serializedPacket) + OFFactory factory = sw.getOFFactory(); + OFPacketIn packetIn = factory.buildPacketIn() + .setInPort(OFPort.of(1)) + .setData(serializedPacket) .setReason(OFPacketInReason.NO_MATCH) - .setTotalLength((short)serializedPacket.length)); + .build(); + return packetIn; } - + /** * Generates a DHCP request Ethernet frame. * @param hostMac The host MAC address of for the request. * @returnAn An Ethernet frame that contains a DHCP request packet. */ - public static Ethernet DhcpDiscoveryRequestEthernet(MACAddress hostMac) { + public static Ethernet DhcpDiscoveryRequestEthernet(MacAddress hostMac) { List<DHCPOption> optionList = new ArrayList<DHCPOption>(); - + byte[] requestValue = new byte[4]; requestValue[0] = requestValue[1] = requestValue[2] = requestValue[3] = 0; - DHCPOption requestOption = + DHCPOption requestOption = new DHCPOption() .setCode(DHCP.DHCPOptionCode.OptionCode_RequestedIP. getValue()) .setLength((byte)4) .setData(requestValue); - + byte[] msgTypeValue = new byte[1]; msgTypeValue[0] = 1; // DHCP request - DHCPOption msgTypeOption = + DHCPOption msgTypeOption = new DHCPOption() .setCode(DHCP.DHCPOptionCode.OptionCode_MessageType. getValue()) .setLength((byte)1) .setData(msgTypeValue); - + byte[] reqParamValue = new byte[4]; reqParamValue[0] = 1; // subnet mask reqParamValue[1] = 3; // Router reqParamValue[2] = 6; // Domain Name Server reqParamValue[3] = 42; // NTP Server - DHCPOption reqParamOption = + DHCPOption reqParamOption = new DHCPOption() .setCode(DHCP.DHCPOptionCode.OptionCode_RequestedParameters. getValue()) .setLength((byte)4) .setData(reqParamValue); - + byte[] clientIdValue = new byte[7]; clientIdValue[0] = 1; // Ethernet - System.arraycopy(hostMac.toBytes(), 0, + System.arraycopy(hostMac.getBytes(), 0, clientIdValue, 1, 6); - DHCPOption clientIdOption = + DHCPOption clientIdOption = new DHCPOption() .setCode(DHCP.DHCPOptionCode.OptionCode_ClientID. getValue()) .setLength((byte)7) .setData(clientIdValue); - - DHCPOption endOption = + + DHCPOption endOption = new DHCPOption() .setCode(DHCP.DHCPOptionCode.OptionCode_END. getValue()) .setLength((byte)0) .setData(null); - + optionList.add(requestOption); optionList.add(msgTypeOption); optionList.add(reqParamOption); optionList.add(clientIdOption); optionList.add(endOption); - + Ethernet requestPacket = new Ethernet(); - requestPacket.setSourceMACAddress(hostMac.toBytes()) + requestPacket.setSourceMACAddress(hostMac.getBytes()) .setDestinationMACAddress(broadcastMac) .setEtherType(Ethernet.TYPE_IPv4) .setPayload( @@ -133,7 +135,7 @@ public class PacketFactory { .setFlags((byte)0) .setFragmentOffset((short)0) .setTtl((byte)250) - .setProtocol(IPv4.PROTOCOL_UDP) + .setProtocol(IpProtocol.UDP) .setChecksum((short)0) .setSourceAddress(0) .setDestinationAddress(broadcastIp) @@ -155,9 +157,9 @@ public class PacketFactory { .setYourIPAddress(0) .setServerIPAddress(0) .setGatewayIPAddress(0) - .setClientHardwareAddress(hostMac.toBytes()) + .setClientHardwareAddress(hostMac.getBytes()) .setOptions(optionList)))); - + return requestPacket; } } diff --git a/src/test/java/net/floodlightcontroller/core/util/AppCookieTest.java b/src/test/java/net/floodlightcontroller/core/util/AppCookieTest.java index 23c15a44e8b4517ea71269d786aaefde2471fd4a..5e0f87538b0a7f850446a826420418cdaa67e352 100644 --- a/src/test/java/net/floodlightcontroller/core/util/AppCookieTest.java +++ b/src/test/java/net/floodlightcontroller/core/util/AppCookieTest.java @@ -20,6 +20,11 @@ package net.floodlightcontroller.core.util; import static org.junit.Assert.*; import org.junit.Test; +import net.floodlightcontroller.core.util.AppCookie; +import net.floodlightcontroller.core.util.AppIDInUseException; +import net.floodlightcontroller.core.util.AppIDNotRegisteredException; +import net.floodlightcontroller.core.util.InvalidAppIDValueException; +import org.projectfloodlight.openflow.types.U64; public class AppCookieTest { @@ -38,10 +43,10 @@ public class AppCookieTest { public void testAppCookie(){ int user = 0xF123F123; // MSB set int user2 = 0x42; // MSB cleared - long expectedCookie11 = 0xF4200000F123F123L; // app1, user1 - long expectedCookie21 = 0x74300000F123F123L; // app2, user1 - long expectedCookie12 = 0xF420000000000042L; // app1, user2 - long expectedCookie22 = 0x7430000000000042L; // app2, user2 + U64 expectedCookie11 = U64.of(0xF4200000F123F123L); // app1, user1 + U64 expectedCookie21 = U64.of(0x74300000F123F123L); // app2, user1 + U64 expectedCookie12 = U64.of(0xF420000000000042L); // app1, user2 + U64 expectedCookie22 = U64.of(0x7430000000000042L); // app2, user2 String name = "FooBar"; String name2 = "FooFooFoo"; @@ -54,7 +59,7 @@ public class AppCookieTest { AppCookie.registerApp(appId, name); - long cookie = AppCookie.makeCookie(appId, user); + U64 cookie = AppCookie.makeCookie(appId, user); assertEquals(expectedCookie11, cookie); assertEquals(appId, AppCookie.extractApp(cookie)); assertEquals(user, AppCookie.extractUser(cookie)); diff --git a/src/test/java/net/floodlightcontroller/core/util/MessageDispatcherTest.java b/src/test/java/net/floodlightcontroller/core/util/MessageDispatcherTest.java index 0829aa447f4d211b0e207c45a0527b7a825906b9..9ea34ce70933726c08a9fa3e695a70216b64b680 100644 --- a/src/test/java/net/floodlightcontroller/core/util/MessageDispatcherTest.java +++ b/src/test/java/net/floodlightcontroller/core/util/MessageDispatcherTest.java @@ -18,6 +18,12 @@ package net.floodlightcontroller.core.util; import static org.easymock.EasyMock.createNiceMock; +import static org.junit.Assert.*; + +import org.junit.Test; + +import net.floodlightcontroller.test.FloodlightTestCase; + import static org.easymock.EasyMock.expect; import static org.easymock.EasyMock.replay; import static org.easymock.EasyMock.verify; @@ -26,11 +32,9 @@ import java.util.ArrayList; import java.util.List; import java.util.Random; -import org.junit.Test; -import org.openflow.protocol.OFType; - +import org.projectfloodlight.openflow.protocol.OFType; import net.floodlightcontroller.core.IOFMessageListener; -import net.floodlightcontroller.test.FloodlightTestCase; +import net.floodlightcontroller.core.util.ListenerDispatcher; public class MessageDispatcherTest extends FloodlightTestCase { diff --git a/src/test/java/net/floodlightcontroller/core/util/SingletonTaskTest.java b/src/test/java/net/floodlightcontroller/core/util/SingletonTaskTest.java index fa04b741496d801dfb7c3fe417a61083acab3f93..70560cc312d347b7c1f6ae02e55b79e025d4570c 100644 --- a/src/test/java/net/floodlightcontroller/core/util/SingletonTaskTest.java +++ b/src/test/java/net/floodlightcontroller/core/util/SingletonTaskTest.java @@ -17,6 +17,8 @@ package net.floodlightcontroller.core.util; +import static org.junit.Assert.*; + import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; @@ -72,7 +74,6 @@ public class SingletonTaskTest extends FloodlightTestCase { time = System.nanoTime(); } }); - long start = System.nanoTime(); st1.reschedule(10, TimeUnit.MILLISECONDS); assertFalse("Check that task hasn't run yet", ran > 0); @@ -80,8 +81,6 @@ public class SingletonTaskTest extends FloodlightTestCase { ses.awaitTermination(5, TimeUnit.SECONDS); assertEquals("Check that task ran", 1, ran); - assertTrue("Check that time passed appropriately", - (time - start) >= TimeUnit.NANOSECONDS.convert(10, TimeUnit.MILLISECONDS)); } @Test @@ -99,7 +98,7 @@ public class SingletonTaskTest extends FloodlightTestCase { time = System.nanoTime(); } }); - long start = System.nanoTime(); + st1.reschedule(20, TimeUnit.MILLISECONDS); Thread.sleep(5); assertFalse("Check that task hasn't run yet", ran > 0); @@ -129,8 +128,6 @@ public class SingletonTaskTest extends FloodlightTestCase { ses.awaitTermination(5, TimeUnit.SECONDS); assertEquals("Check that task ran only once", 1, ran); - assertTrue("Check that time passed appropriately: " + (time - start), - (time - start) >= TimeUnit.NANOSECONDS.convert(55, TimeUnit.MILLISECONDS)); } @Test @@ -157,7 +154,6 @@ public class SingletonTaskTest extends FloodlightTestCase { } }); - long start = System.nanoTime(); st1.reschedule(5, TimeUnit.MILLISECONDS); Thread.sleep(20); assertEquals("Check that task started", 1, ran); @@ -175,13 +171,8 @@ public class SingletonTaskTest extends FloodlightTestCase { assertEquals("Check that task ran exactly twice", 2, ran); assertEquals("Check that task finished exactly twice", 2, finished); - assertTrue("Check that time passed appropriately: " + (time - start), - (time - start) >= TimeUnit.NANOSECONDS.convert(130, TimeUnit.MILLISECONDS)); - assertTrue("Check that time passed appropriately: " + (time - start), - (time - start) <= TimeUnit.NANOSECONDS.convert(160, TimeUnit.MILLISECONDS)); - ses.shutdown(); - ses.awaitTermination(5, TimeUnit.SECONDS); + ses.awaitTermination(15, TimeUnit.SECONDS); } @Test @@ -209,7 +200,6 @@ public class SingletonTaskTest extends FloodlightTestCase { } }); - long start = System.nanoTime(); st1.reschedule(5, TimeUnit.MILLISECONDS); Thread.sleep(20); assertEquals("Check that task started", 1, ran); @@ -227,11 +217,6 @@ public class SingletonTaskTest extends FloodlightTestCase { assertEquals("Check that task ran exactly twice", 2, ran); assertEquals("Check that task finished exactly twice", 2, finished); - assertTrue("Check that time passed appropriately: " + (time - start), - (time - start) >= TimeUnit.NANOSECONDS.convert(100, TimeUnit.MILLISECONDS)); - assertTrue("Check that time passed appropriately: " + (time - start), - (time - start) <= TimeUnit.NANOSECONDS.convert(125, TimeUnit.MILLISECONDS)); - ses.shutdown(); ses.awaitTermination(5, TimeUnit.SECONDS); } @@ -262,7 +247,6 @@ public class SingletonTaskTest extends FloodlightTestCase { } }); - long start = System.nanoTime(); st1.reschedule(0, null); Thread.sleep(20); assertEquals("Check that task started", 1, ran); @@ -280,11 +264,6 @@ public class SingletonTaskTest extends FloodlightTestCase { assertEquals("Check that task ran exactly twice", 2, ran); assertEquals("Check that task finished exactly twice", 2, finished); - assertTrue("Check that time passed appropriately: " + (time - start), - (time - start) >= TimeUnit.NANOSECONDS.convert(90, TimeUnit.MILLISECONDS)); - assertTrue("Check that time passed appropriately: " + (time - start), - (time - start) <= TimeUnit.NANOSECONDS.convert(130, TimeUnit.MILLISECONDS)); - ses.shutdown(); ses.awaitTermination(5, TimeUnit.SECONDS); } diff --git a/src/test/java/net/floodlightcontroller/devicemanager/internal/DeviceManagerImplTest.java b/src/test/java/net/floodlightcontroller/devicemanager/internal/DeviceManagerImplTest.java index ecd145f969176aafb8e7a0ede3dcbc87bd5fb731..43317cc8d84152149e0781378f014fb83fa3cb19 100644 --- a/src/test/java/net/floodlightcontroller/devicemanager/internal/DeviceManagerImplTest.java +++ b/src/test/java/net/floodlightcontroller/devicemanager/internal/DeviceManagerImplTest.java @@ -53,8 +53,8 @@ import net.floodlightcontroller.core.FloodlightContext; import net.floodlightcontroller.core.IFloodlightProviderService; import net.floodlightcontroller.core.IListener.Command; import net.floodlightcontroller.core.IOFSwitch; -import net.floodlightcontroller.core.IFloodlightProviderService.Role; -import net.floodlightcontroller.core.ImmutablePort; +import net.floodlightcontroller.core.internal.RoleManager; +import net.floodlightcontroller.core.HARole; import net.floodlightcontroller.core.module.FloodlightModuleContext; import net.floodlightcontroller.core.module.FloodlightModuleException; import net.floodlightcontroller.core.test.MockThreadPoolService; @@ -90,11 +90,12 @@ import net.floodlightcontroller.topology.ITopologyService; import org.easymock.EasyMock; import org.junit.Before; import org.junit.Test; -import org.openflow.protocol.OFPacketIn; -import org.openflow.protocol.OFPacketIn.OFPacketInReason; -import org.openflow.protocol.OFPort; -import org.openflow.protocol.OFType; -import org.openflow.util.HexString; +import org.projectfloodlight.openflow.protocol.OFPacketIn; +import org.projectfloodlight.openflow.protocol.OFPacketInReason; +import org.projectfloodlight.openflow.protocol.OFPortDesc; +import org.projectfloodlight.openflow.types.DatapathId; +import org.projectfloodlight.openflow.types.OFPort; +import org.projectfloodlight.openflow.protocol.OFType; import org.sdnplatform.sync.IClosableIterator; import org.sdnplatform.sync.IStoreClient; import org.sdnplatform.sync.ISyncService; @@ -121,14 +122,12 @@ public class DeviceManagerImplTest extends FloodlightTestCase { MemoryStorageSource storageSource; FlowReconcileManager flowReconcileMgr; - private IOFSwitch makeSwitchMock(long id) { + private IOFSwitch makeSwitchMock(DatapathId id) { IOFSwitch mockSwitch = createMock(IOFSwitch.class); - ImmutablePort port = ImmutablePort.create("p1", (short)1); + OFPort port = OFPort.of(1); expect(mockSwitch.getId()).andReturn(id).anyTimes(); - expect(mockSwitch.getStringId()) - .andReturn(HexString.toHexString(id, 6)).anyTimes(); - expect(mockSwitch.getPort(anyShort())) - .andReturn(port).anyTimes(); + expect(mockSwitch.getStringId()).andReturn(id.toString()).anyTimes(); + expect(mockSwitch.getPort(OFPort.of(anyShort())).getPortNo()).andReturn(port).anyTimes(); return mockSwitch; } @@ -141,16 +140,15 @@ public class DeviceManagerImplTest extends FloodlightTestCase { */ private ITopologyService makeMockTopologyAllPortsAp() { ITopologyService mockTopology = createMock(ITopologyService.class); - mockTopology.isAttachmentPointPort(anyLong(), anyShort()); + mockTopology.isAttachmentPointPort(DatapathId.of(anyLong()), OFPort.of(anyShort())); expectLastCall().andReturn(true).anyTimes(); - mockTopology.getL2DomainId(anyLong()); + mockTopology.getL2DomainId(DatapathId.of(anyLong())); expectLastCall().andReturn(1L).anyTimes(); - mockTopology.isBroadcastDomainPort(anyLong(), anyShort()); + mockTopology.isBroadcastDomainPort(DatapathId.of(anyLong()), OFPort.of(anyShort())); expectLastCall().andReturn(false).anyTimes(); - mockTopology.isConsistent(anyLong(), anyShort(), anyLong(), anyShort()); + mockTopology.isConsistent(DatapathId.of(anyLong()), OFPort.of(anyShort()), DatapathId.of(anyLong()), OFPort.of(anyShort())); expectLastCall().andReturn(false).anyTimes(); - mockTopology.isInSameBroadcastDomain(anyLong(), anyShort(), - anyLong(), anyShort()); + mockTopology.isInSameBroadcastDomain(DatapathId.of(anyLong()), OFPort.of(anyShort()), DatapathId.of(anyLong()), OFPort.of(anyShort())); expectLastCall().andReturn(false).anyTimes(); return mockTopology; } @@ -158,10 +156,10 @@ public class DeviceManagerImplTest extends FloodlightTestCase { @Override @Before public void setUp() throws Exception { - doSetUp(Role.MASTER); + doSetUp(HARole.ACTIVE); } - public void doSetUp(Role initialRole) throws Exception { + public void doSetUp(HARole initialRole) throws Exception { super.setUp(); this.syncService = new MockSyncService(); @@ -173,6 +171,7 @@ public class DeviceManagerImplTest extends FloodlightTestCase { fmc.addService(IThreadPoolService.class, tp); mockFloodlightProvider = getMockFloodlightProvider(); mockFloodlightProvider.setRole(initialRole, ""); + deviceManager = new DeviceManagerImpl(); flowReconcileMgr = new FlowReconcileManager(); @@ -209,15 +208,15 @@ public class DeviceManagerImplTest extends FloodlightTestCase { expectLastCall().anyTimes(); replay(topology); - IOFSwitch mockSwitch1 = makeSwitchMock(1L); - IOFSwitch mockSwitch10 = makeSwitchMock(10L); - IOFSwitch mockSwitch5 = makeSwitchMock(5L); - IOFSwitch mockSwitch50 = makeSwitchMock(50L); - Map<Long, IOFSwitch> switches = new HashMap<Long,IOFSwitch>(); - switches.put(1L, mockSwitch1); - switches.put(10L, mockSwitch10); - switches.put(5L, mockSwitch5); - switches.put(50L, mockSwitch50); + IOFSwitch mockSwitch1 = makeSwitchMock(DatapathId.of(1L)); + IOFSwitch mockSwitch10 = makeSwitchMock(DatapathId.of(10L)); + IOFSwitch mockSwitch5 = makeSwitchMock(DatapathId.of(5L)); + IOFSwitch mockSwitch50 = makeSwitchMock(DatapathId.of(50L)); + Map<DatapathId, IOFSwitch> switches = new HashMap<DatapathId, IOFSwitch>(); + switches.put(DatapathId.of(1L), mockSwitch1); + switches.put(DatapathId.of(10L), mockSwitch10); + switches.put(DatapathId.of(5L), mockSwitch5); + switches.put(DatapathId.of(50L), mockSwitch50); mockFloodlightProvider.setSwitches(switches); replay(mockSwitch1, mockSwitch5, mockSwitch10, mockSwitch50); diff --git a/src/test/java/net/floodlightcontroller/hub/HubTest.java b/src/test/java/net/floodlightcontroller/hub/HubTest.java index 22dfd855ffb94d0cbb0587b769ecb47eaefde552..ecef81215eb24b6ff2a2f1e625585a2184481817 100644 --- a/src/test/java/net/floodlightcontroller/hub/HubTest.java +++ b/src/test/java/net/floodlightcontroller/hub/HubTest.java @@ -21,10 +21,11 @@ import static org.easymock.EasyMock.createMock; import static org.easymock.EasyMock.replay; import static org.easymock.EasyMock.verify; import static org.easymock.EasyMock.capture; +import static org.junit.Assert.*; -import java.util.Arrays; +import java.util.ArrayList; +import java.util.List; -import net.floodlightcontroller.core.FloodlightContext; import net.floodlightcontroller.core.IOFMessageListener; import net.floodlightcontroller.core.IOFSwitch; import net.floodlightcontroller.core.test.MockFloodlightProvider; @@ -39,14 +40,18 @@ import org.easymock.Capture; import org.easymock.CaptureType; import org.junit.Before; import org.junit.Test; -import org.openflow.protocol.OFPacketIn; -import org.openflow.protocol.OFPacketIn.OFPacketInReason; -import org.openflow.protocol.OFMessage; -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.projectfloodlight.openflow.protocol.OFFactories; +import org.projectfloodlight.openflow.protocol.OFPacketIn; +import org.projectfloodlight.openflow.protocol.OFPacketInReason; +import org.projectfloodlight.openflow.protocol.OFMessage; +import org.projectfloodlight.openflow.protocol.OFPacketOut; +import org.projectfloodlight.openflow.protocol.OFType; +import org.projectfloodlight.openflow.protocol.OFVersion; +import org.projectfloodlight.openflow.protocol.action.OFAction; +import org.projectfloodlight.openflow.protocol.action.OFActionOutput; +import org.projectfloodlight.openflow.protocol.match.MatchField; +import org.projectfloodlight.openflow.types.OFBufferId; +import org.projectfloodlight.openflow.types.OFPort; /** * @@ -56,7 +61,7 @@ public class HubTest extends FloodlightTestCase { protected OFPacketIn packetIn; protected IPacket testPacket; protected byte[] testPacketSerialized; - private MockFloodlightProvider mockFloodlightProvider; + private MockFloodlightProvider mockFloodlightProvider; private Hub hub; @Before @@ -85,33 +90,36 @@ public class HubTest extends FloodlightTestCase { this.testPacketSerialized = testPacket.serialize(); // Build the PacketIn - this.packetIn = ((OFPacketIn) mockFloodlightProvider.getOFMessageFactory().getMessage(OFType.PACKET_IN)) - .setBufferId(-1) - .setInPort((short) 1) - .setPacketData(this.testPacketSerialized) + //TODO @Ryan should this just be OF_13 or include OF_10 too? + this.packetIn = (OFPacketIn) OFFactories.getFactory(OFVersion.OF_13).buildPacketIn() + .setBufferId(OFBufferId.NO_BUFFER) + .setMatch(OFFactories.getFactory(OFVersion.OF_13).buildMatch() + .setExact(MatchField.IN_PORT, OFPort.of(1)) + .build()) + .setData(this.testPacketSerialized) .setReason(OFPacketInReason.NO_MATCH) - .setTotalLength((short) this.testPacketSerialized.length); + .setTotalLen((short) this.testPacketSerialized.length).build(); } @Test public void testFloodNoBufferId() throws Exception { - // build our expected flooded packetOut - OFPacketOut po = ((OFPacketOut) mockFloodlightProvider.getOFMessageFactory().getMessage(OFType.PACKET_OUT)) - .setActions(Arrays.asList(new OFAction[] {new OFActionOutput().setPort(OFPort.OFPP_FLOOD.getValue())})) - .setActionsLength((short) OFActionOutput.MINIMUM_LENGTH) - .setBufferId(-1) - .setInPort((short) 1) - .setPacketData(this.testPacketSerialized); - po.setLengthU(OFPacketOut.MINIMUM_LENGTH + po.getActionsLengthU() - + this.testPacketSerialized.length); - // Mock up our expected behavior IOFSwitch mockSwitch = createMock(IOFSwitch.class); + // build our expected flooded packetOut + OFActionOutput ao = OFFactories.getFactory(OFVersion.OF_13).actions().buildOutput().setPort(OFPort.FLOOD).build(); + List<OFAction> al = new ArrayList<OFAction>(); + al.add(ao); + OFPacketOut po = OFFactories.getFactory(OFVersion.OF_13).buildPacketOut() + .setActions(al) + .setBufferId(OFBufferId.NO_BUFFER) + .setXid(1) + .setInPort(OFPort.of(1)) + .setData(this.testPacketSerialized).build(); + Capture<OFMessage> wc1 = new Capture<OFMessage>(CaptureType.ALL); - Capture<FloodlightContext> bc1 = new Capture<FloodlightContext>(CaptureType.ALL); - mockSwitch.write(capture(wc1), capture(bc1)); + mockSwitch.write(capture(wc1)); // Start recording the replay on the mocks replay(mockSwitch); @@ -126,28 +134,34 @@ public class HubTest extends FloodlightTestCase { assertTrue(wc1.hasCaptured()); OFMessage m = wc1.getValue(); + //TODO @Ryan the wc1 message has inport=ANY and the next xid + // Can this be asserted anymore with OF1.3? assertEquals(po, m); } @Test public void testFloodBufferId() throws Exception { MockFloodlightProvider mockFloodlightProvider = getMockFloodlightProvider(); - this.packetIn.setBufferId(10); - + this.packetIn = this.packetIn.createBuilder() + .setBufferId(OFBufferId.of(10)) + .setXid(1) + .build(); + + OFActionOutput ao = OFFactories.getFactory(OFVersion.OF_13).actions().buildOutput().setPort(OFPort.FLOOD).build(); + List<OFAction> al = new ArrayList<OFAction>(); + al.add(ao); // build our expected flooded packetOut - OFPacketOut po = ((OFPacketOut) mockFloodlightProvider.getOFMessageFactory().getMessage(OFType.PACKET_OUT)) - .setActions(Arrays.asList(new OFAction[] {new OFActionOutput().setPort(OFPort.OFPP_FLOOD.getValue())})) - .setActionsLength((short) OFActionOutput.MINIMUM_LENGTH) - .setBufferId(10) - .setInPort((short) 1); - po.setLengthU(OFPacketOut.MINIMUM_LENGTH + po.getActionsLengthU()); + OFPacketOut po = OFFactories.getFactory(OFVersion.OF_13).buildPacketOut() + .setActions(al) + .setXid(1) + .setBufferId(OFBufferId.of(10)) + .setInPort(OFPort.of(1)) + .build(); // Mock up our expected behavior IOFSwitch mockSwitch = createMock(IOFSwitch.class); - Capture<OFMessage> wc1 = new Capture<OFMessage>(CaptureType.ALL); - Capture<FloodlightContext> bc1 = new Capture<FloodlightContext>(CaptureType.ALL); - - mockSwitch.write(capture(wc1), capture(bc1)); + Capture<OFPacketOut> wc1 = new Capture<OFPacketOut>(CaptureType.ALL); + mockSwitch.write(capture(wc1)); // Start recording the replay on the mocks replay(mockSwitch); @@ -161,6 +175,9 @@ public class HubTest extends FloodlightTestCase { verify(mockSwitch); assertTrue(wc1.hasCaptured()); + //TODO @Ryan the wc1 message has inport=ANY, + // bufferid=NONE, and the next xid + // Can this be asserted anymore with OF1.3? OFMessage m = wc1.getValue(); assertEquals(po, m); } diff --git a/src/test/java/net/floodlightcontroller/storage/tests/StorageTest.java b/src/test/java/net/floodlightcontroller/storage/tests/StorageTest.java index d127181907f0ca7036132f039006fd89b5eb3653..99e2d4e44458d4f123b8d7ca7a7978ffe85ff3ce 100644 --- a/src/test/java/net/floodlightcontroller/storage/tests/StorageTest.java +++ b/src/test/java/net/floodlightcontroller/storage/tests/StorageTest.java @@ -30,6 +30,10 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; + +import static org.junit.Assert.*; +import org.junit.Test; + import net.floodlightcontroller.restserver.RestApiServer; import net.floodlightcontroller.storage.CompoundPredicate; import net.floodlightcontroller.storage.IStorageExceptionHandler; @@ -44,7 +48,6 @@ import net.floodlightcontroller.storage.RowOrdering; import net.floodlightcontroller.storage.nosql.NoSqlStorageSource; import net.floodlightcontroller.test.FloodlightTestCase; -import org.junit.Test; public abstract class StorageTest extends FloodlightTestCase { diff --git a/src/test/java/net/floodlightcontroller/test/FloodlightTestCase.java b/src/test/java/net/floodlightcontroller/test/FloodlightTestCase.java index 94496dc2266131b7499085087b29d58eaf785cfa..299f97a60fc34c8695550ec97110d5ad92a293f9 100644 --- a/src/test/java/net/floodlightcontroller/test/FloodlightTestCase.java +++ b/src/test/java/net/floodlightcontroller/test/FloodlightTestCase.java @@ -17,88 +17,71 @@ package net.floodlightcontroller.test; -import junit.framework.TestCase; +import org.junit.Before; import net.floodlightcontroller.core.FloodlightContext; import net.floodlightcontroller.core.IFloodlightProviderService; +import net.floodlightcontroller.core.IOFSwitch; import net.floodlightcontroller.core.test.MockFloodlightProvider; -import net.floodlightcontroller.devicemanager.IDevice; -import net.floodlightcontroller.devicemanager.IDeviceService; +import net.floodlightcontroller.core.test.MockSwitchManager; +import org.projectfloodlight.openflow.protocol.OFMessage; +import org.projectfloodlight.openflow.protocol.OFPacketIn; +import org.projectfloodlight.openflow.protocol.OFPortDesc; +import org.projectfloodlight.openflow.protocol.OFType; +import org.projectfloodlight.openflow.types.MacAddress; +import org.projectfloodlight.openflow.types.OFPort; import net.floodlightcontroller.packet.Ethernet; -import org.junit.Test; -import org.openflow.protocol.OFMessage; -import org.openflow.protocol.OFPacketIn; -import org.openflow.protocol.OFPhysicalPort; -import org.openflow.protocol.OFType; - /** * This class gets a handle on the application context which is used to * retrieve Spring beans from during tests * * @author David Erickson (daviderickson@cs.stanford.edu) */ -public class FloodlightTestCase extends TestCase { +public class FloodlightTestCase { protected MockFloodlightProvider mockFloodlightProvider; + protected MockSwitchManager mockSwitchManager; public MockFloodlightProvider getMockFloodlightProvider() { return mockFloodlightProvider; } - public void setMockFloodlightProvider(MockFloodlightProvider mockFloodlightProvider) { - this.mockFloodlightProvider = mockFloodlightProvider; + public MockSwitchManager getMockSwitchService() { + return mockSwitchManager; } - public FloodlightContext parseAndAnnotate(OFMessage m, - IDevice srcDevice, - IDevice dstDevice) { - FloodlightContext bc = new FloodlightContext(); - return parseAndAnnotate(bc, m, srcDevice, dstDevice); + public void setMockFloodlightProvider(MockFloodlightProvider mockFloodlightProvider) { + this.mockFloodlightProvider = mockFloodlightProvider; } public FloodlightContext parseAndAnnotate(OFMessage m) { - return parseAndAnnotate(m, null, null); + FloodlightContext bc = new FloodlightContext(); + return parseAndAnnotate(bc, m); } - public FloodlightContext parseAndAnnotate(FloodlightContext bc, - OFMessage m, - IDevice srcDevice, - IDevice dstDevice) { + public static FloodlightContext parseAndAnnotate(FloodlightContext bc, OFMessage m) { if (OFType.PACKET_IN.equals(m.getType())) { OFPacketIn pi = (OFPacketIn)m; Ethernet eth = new Ethernet(); - eth.deserialize(pi.getPacketData(), 0, pi.getPacketData().length); + eth.deserialize(pi.getData(), 0, pi.getData().length); IFloodlightProviderService.bcStore.put(bc, IFloodlightProviderService.CONTEXT_PI_PAYLOAD, eth); } - if (srcDevice != null) { - IDeviceService.fcStore.put(bc, - IDeviceService.CONTEXT_SRC_DEVICE, - srcDevice); - } - if (dstDevice != null) { - IDeviceService.fcStore.put(bc, - IDeviceService.CONTEXT_DST_DEVICE, - dstDevice); - } return bc; } - @Override + @Before public void setUp() throws Exception { mockFloodlightProvider = new MockFloodlightProvider(); + mockSwitchManager = new MockSwitchManager(); } - @Test - public void testSanity() throws Exception { - assertTrue(true); - } - - public static OFPhysicalPort createOFPhysicalPort(String name, int number) { - OFPhysicalPort p = new OFPhysicalPort(); - p.setHardwareAddress(new byte [] { 0, 0, 0, 0, 0, 0 }); - p.setPortNumber((short)number); - p.setName(name); - return p; + public static OFPortDesc createOFPortDesc(IOFSwitch sw, String name, int number) { + OFPortDesc portDesc = sw.getOFFactory().buildPortDesc() + .setHwAddr(MacAddress.NONE) + .setPortNo(OFPort.of(number)) + .setName(name) + .build(); + return portDesc; } } diff --git a/src/test/java/net/floodlightcontroller/util/FutureTestUtils.java b/src/test/java/net/floodlightcontroller/util/FutureTestUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..62dbe00f8c9c3d29186208bec52494414321ff40 --- /dev/null +++ b/src/test/java/net/floodlightcontroller/util/FutureTestUtils.java @@ -0,0 +1,30 @@ +package net.floodlightcontroller.util; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.junit.Assert.assertThat; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; + +import junit.framework.AssertionFailedError; + +public class FutureTestUtils { + + private FutureTestUtils() { } + + @SuppressWarnings("unchecked") + public static <T extends Exception> T assertFutureFailedWithException(Future<?> future, + Class<T> clazz) throws InterruptedException { + + assertThat("Future should be complete ", future.isDone(), equalTo(true)); + try { + future.get(); + throw new AssertionFailedError("Expected ExecutionExcepion"); + } catch(ExecutionException e) { + assertThat(e.getCause(), instanceOf(clazz)); + return (T) e.getCause(); + } + } + +} diff --git a/src/test/java/net/floodlightcontroller/util/OFMessageDamperMockSwitch.java b/src/test/java/net/floodlightcontroller/util/OFMessageDamperMockSwitch.java index c3cc59ed3c6cb5d50b2abbf047bd54d5a221c883..547371cc0ba2e3694c2c810d8dd928f5f12059c0 100644 --- a/src/test/java/net/floodlightcontroller/util/OFMessageDamperMockSwitch.java +++ b/src/test/java/net/floodlightcontroller/util/OFMessageDamperMockSwitch.java @@ -28,20 +28,19 @@ import java.util.concurrent.Future; import net.floodlightcontroller.core.FloodlightContext; import net.floodlightcontroller.core.IOFMessageListener; import net.floodlightcontroller.core.IOFSwitch; -import net.floodlightcontroller.core.IFloodlightProviderService.Role; -import net.floodlightcontroller.core.ImmutablePort; +import net.floodlightcontroller.core.HARole; import net.floodlightcontroller.core.internal.Controller; import net.floodlightcontroller.debugcounter.IDebugCounterService; import net.floodlightcontroller.threadpool.IThreadPoolService; import org.jboss.netty.channel.Channel; -import org.openflow.protocol.OFFeaturesReply; -import org.openflow.protocol.OFMessage; -import org.openflow.protocol.OFPortStatus; -import org.openflow.protocol.OFStatisticsReply; -import org.openflow.protocol.OFStatisticsRequest; -import org.openflow.protocol.statistics.OFDescriptionStatistics; -import org.openflow.protocol.statistics.OFStatistics; +import org.projectfloodlight.openflow.protocol.OFFeaturesReply; +import org.projectfloodlight.openflow.protocol.OFMessage; +import org.projectfloodlight.openflow.protocol.OFPortStatus; +import org.projectfloodlight.openflow.protocol.OFStatsReply; +import org.projectfloodlight.openflow.protocol.OFStatsRequest; +import org.projectfloodlight.openflow.protocol.OFDescStatsReply; +import org.projectfloodlight.openflow.protocol.OFDescStatsRequest; /** @@ -357,7 +356,7 @@ public class OFMessageDamperMockSwitch implements IOFSwitch { } @Override - public void setFloodlightProvider(Controller controller) { + public void setFloodlightProvider(OLD__Controller controller) { fail("Unexpected method call"); // TODO Auto-generated method stub diff --git a/src/test/java/org/openflow/protocol/BasicFactoryTest.java b/src/test/java/org/openflow/protocol/BasicFactoryTest.java deleted file mode 100644 index 350d4be39c3f1148c7a6c4191085bbe8bb9132a5..0000000000000000000000000000000000000000 --- a/src/test/java/org/openflow/protocol/BasicFactoryTest.java +++ /dev/null @@ -1,134 +0,0 @@ -/** -* Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior -* University -* -* Licensed under the Apache License, Version 2.0 (the "License"); you may -* not use this file except in compliance with the License. You may obtain -* a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -* License for the specific language governing permissions and limitations -* under the License. -**/ - -package org.openflow.protocol; - -import static org.junit.Assert.assertArrayEquals; - -import java.util.List; - -import junit.framework.TestCase; - -import org.jboss.netty.buffer.ChannelBuffer; -import org.jboss.netty.buffer.ChannelBuffers; -import org.openflow.protocol.action.MockVendorAction; -import org.openflow.protocol.action.MockVendorActionFactory; -import org.openflow.protocol.action.OFAction; -import org.openflow.protocol.action.OFActionVendorGeneric; -import org.openflow.protocol.factory.BasicFactory; -import org.openflow.protocol.factory.MessageParseException; -import org.openflow.protocol.factory.OFVendorActionRegistry; -import org.openflow.util.U16; - -public class BasicFactoryTest extends TestCase { - - public void testCreateAndParse() throws MessageParseException { - BasicFactory factory = BasicFactory.getInstance(); - OFMessage m = factory.getMessage(OFType.HELLO); - m.setVersion((byte) 1); - m.setType(OFType.ECHO_REQUEST); - m.setLength(U16.t(8)); - m.setXid(0xdeadbeef); - ChannelBuffer bb = ChannelBuffers.dynamicBuffer(); - ChannelBuffer bb2 = ChannelBuffers.dynamicBuffer(); - m.writeTo(bb); - bb2.writeBytes(bb, bb.readableBytes()-1); - TestCase.assertNull(factory.parseMessage(bb2)); - bb2.writeByte(bb.readByte()); - List<OFMessage> message = factory.parseMessage(bb2); - TestCase.assertNotNull(message); - TestCase.assertEquals(message.size(), 1); - TestCase.assertTrue(message.get(0).getType() == OFType.ECHO_REQUEST); - } - - public void testInvalidMsgParse() throws MessageParseException { - BasicFactory factory = BasicFactory.getInstance(); - OFMessage m = factory.getMessage(OFType.HELLO); - m.setVersion((byte) 1); - m.setType(OFType.ECHO_REQUEST); - m.setLength(U16.t(16)); - m.setXid(0xdeadbeef); - ChannelBuffer bb = ChannelBuffers.dynamicBuffer(); - m.writeTo(bb); - List<OFMessage> message = factory.parseMessage(bb); - TestCase.assertNull(message); - } - - public void testCurrouptedMsgParse() throws MessageParseException { - BasicFactory factory = BasicFactory.getInstance(); - OFMessage m = factory.getMessage(OFType.HELLO); - m.setVersion((byte) 1); - m.setType(OFType.ERROR); - m.setLength(U16.t(8)); - m.setXid(0xdeadbeef); - ChannelBuffer bb = ChannelBuffers.dynamicBuffer(); - m.writeTo(bb); - try { - factory.parseMessage(bb); - } - catch(Exception e) { - TestCase.assertEquals(MessageParseException.class, e.getClass()); - } - } - - public void testCustomVendorAction() throws MessageParseException { - BasicFactory factory = BasicFactory.getInstance(); - OFVendorActionRegistry.getInstance().register( - MockVendorAction.VENDOR_ID, new MockVendorActionFactory()); - - - byte[] deadBeefMessage = { - (byte) 0xff, (byte) 0xff, // action vendor - 0x00, 0x10, // length - (byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte)0xef, // deadbeaf - 0x01, 0x02, 0x03, 0x04, - 0x05, 0x06, 0x07, 0x08 // pad - }; - - ChannelBuffer buf = ChannelBuffers.copiedBuffer(deadBeefMessage); - - List<OFAction> actions = factory.parseActions(buf,deadBeefMessage.length); - assertEquals(1, actions.size()); - OFAction ofAction = actions.get(0); - assertTrue("Action should be MockVendorAction, but is "+ofAction.getClass(), ofAction instanceof MockVendorAction); - assertArrayEquals( new byte[] { 1,2,3,4,5,6,7,8}, ((MockVendorAction)ofAction).getMockData()); - - - } - - public void testGenericVendorAction() throws MessageParseException { - byte[] nonDeadBeefMessage = { - (byte) 0xff, (byte) 0xff, // action vendor - 0x00, 0x10, // length - (byte) 0x7e, (byte) 0xe7, (byte) 0xbe, (byte)0xef, // deadbeaf - 0x01, 0x02, 0x03, 0x04, - 0x05, 0x06, 0x07, 0x08 // pad - }; - - BasicFactory factory = BasicFactory.getInstance(); - OFVendorActionRegistry.getInstance().register( - MockVendorAction.VENDOR_ID, new MockVendorActionFactory()); - - ChannelBuffer buf = ChannelBuffers.copiedBuffer(nonDeadBeefMessage); - - List<OFAction> actions = factory.parseActions(buf,nonDeadBeefMessage.length); - assertEquals(1, actions.size()); - OFAction ofAction = actions.get(0); - assertTrue("Action should be OFActionVendorGeneric, but is "+ofAction.getClass(), ofAction instanceof OFActionVendorGeneric); - } - -} diff --git a/src/test/java/org/openflow/protocol/OFActionTypeTest.java b/src/test/java/org/openflow/protocol/OFActionTypeTest.java deleted file mode 100644 index ed8386cd745dd7db691e334d6bd64fec81e67745..0000000000000000000000000000000000000000 --- a/src/test/java/org/openflow/protocol/OFActionTypeTest.java +++ /dev/null @@ -1,37 +0,0 @@ -/** -* Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior -* University -* -* Licensed under the Apache License, Version 2.0 (the "License"); you may -* not use this file except in compliance with the License. You may obtain -* a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -* License for the specific language governing permissions and limitations -* under the License. -**/ - -package org.openflow.protocol; - - -import org.junit.Test; -import org.openflow.protocol.action.OFActionType; - -import junit.framework.TestCase; - - -public class OFActionTypeTest extends TestCase { - @Test - public void testMapping() throws Exception { - TestCase.assertEquals(OFActionType.OUTPUT, - OFActionType.valueOf((short) 0)); - TestCase.assertEquals(OFActionType.OPAQUE_ENQUEUE, - OFActionType.valueOf((short) 11)); - TestCase.assertEquals(OFActionType.VENDOR, - OFActionType.valueOf((short) 0xffff)); - } -} diff --git a/src/test/java/org/openflow/protocol/OFBarrierReplyTest.java b/src/test/java/org/openflow/protocol/OFBarrierReplyTest.java deleted file mode 100644 index 7e447bb071bf477448823d085db1d6a9e7e72701..0000000000000000000000000000000000000000 --- a/src/test/java/org/openflow/protocol/OFBarrierReplyTest.java +++ /dev/null @@ -1,36 +0,0 @@ -/** -* Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior -* University -* -* Licensed under the Apache License, Version 2.0 (the "License"); you may -* not use this file except in compliance with the License. You may obtain -* a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -* License for the specific language governing permissions and limitations -* under the License. -**/ - -package org.openflow.protocol; - -import junit.framework.TestCase; - -import org.jboss.netty.buffer.ChannelBuffer; -import org.jboss.netty.buffer.ChannelBuffers; -import org.openflow.util.OFTestCase; - -public class OFBarrierReplyTest extends OFTestCase { - public void testWriteRead() throws Exception { - OFBarrierReply msg = (OFBarrierReply) messageFactory - .getMessage(OFType.BARRIER_REPLY); - ChannelBuffer bb = ChannelBuffers.dynamicBuffer(); - bb.clear(); - msg.writeTo(bb); - msg.readFrom(bb); - TestCase.assertEquals(OFType.BARRIER_REPLY, msg.getType()); - } -} diff --git a/src/test/java/org/openflow/protocol/OFBarrierRequestTest.java b/src/test/java/org/openflow/protocol/OFBarrierRequestTest.java deleted file mode 100644 index 3aa9cb4a789f6ea2202878a6d92da898d76a4490..0000000000000000000000000000000000000000 --- a/src/test/java/org/openflow/protocol/OFBarrierRequestTest.java +++ /dev/null @@ -1,36 +0,0 @@ -/** -* Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior -* University -* -* Licensed under the Apache License, Version 2.0 (the "License"); you may -* not use this file except in compliance with the License. You may obtain -* a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -* License for the specific language governing permissions and limitations -* under the License. -**/ - -package org.openflow.protocol; - -import junit.framework.TestCase; - -import org.jboss.netty.buffer.ChannelBuffer; -import org.jboss.netty.buffer.ChannelBuffers; -import org.openflow.util.OFTestCase; - -public class OFBarrierRequestTest extends OFTestCase { - public void testWriteRead() throws Exception { - OFBarrierRequest msg = (OFBarrierRequest) messageFactory - .getMessage(OFType.BARRIER_REQUEST); - ChannelBuffer bb = ChannelBuffers.dynamicBuffer(); - bb.clear(); - msg.writeTo(bb); - msg.readFrom(bb); - TestCase.assertEquals(OFType.BARRIER_REQUEST, msg.getType()); - } -} diff --git a/src/test/java/org/openflow/protocol/OFErrorTest.java b/src/test/java/org/openflow/protocol/OFErrorTest.java deleted file mode 100644 index f4a57269826d81cf17b47b547e4b7588afeb873f..0000000000000000000000000000000000000000 --- a/src/test/java/org/openflow/protocol/OFErrorTest.java +++ /dev/null @@ -1,88 +0,0 @@ -/** -* Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior -* University -* -* Licensed under the Apache License, Version 2.0 (the "License"); you may -* not use this file except in compliance with the License. You may obtain -* a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -* License for the specific language governing permissions and limitations -* under the License. -**/ - -package org.openflow.protocol; - -import java.util.List; - -import junit.framework.TestCase; - -import org.jboss.netty.buffer.ChannelBuffer; -import org.jboss.netty.buffer.ChannelBuffers; -import org.openflow.protocol.OFError.OFErrorType; -import org.openflow.protocol.OFError.OFHelloFailedCode; -import org.openflow.protocol.factory.BasicFactory; -import org.openflow.protocol.factory.MessageParseException; -import org.openflow.protocol.factory.OFMessageFactory; -import org.openflow.util.OFTestCase; - -public class OFErrorTest extends OFTestCase { - public void testWriteRead() throws Exception { - OFError msg = (OFError) messageFactory.getMessage(OFType.ERROR); - msg.setMessageFactory(messageFactory); - msg.setErrorType(OFErrorType.OFPET_HELLO_FAILED.getValue()); - msg.setErrorCode((short) OFHelloFailedCode.OFPHFC_INCOMPATIBLE - .ordinal()); - ChannelBuffer bb = ChannelBuffers.dynamicBuffer(); - bb.clear(); - msg.writeTo(bb); - msg.readFrom(bb); - TestCase.assertEquals(OFErrorType.OFPET_HELLO_FAILED.getValue(), - msg.getErrorType()); - TestCase.assertEquals((short) OFHelloFailedCode.OFPHFC_INCOMPATIBLE - .ordinal(), msg.getErrorType()); - TestCase.assertNull(msg.getOffendingMsg()); - - msg.setOffendingMsg(new OFHello()); - bb.clear(); - msg.writeTo(bb); - msg.readFrom(bb); - TestCase.assertEquals(OFErrorType.OFPET_HELLO_FAILED.getValue(), - msg.getErrorType()); - TestCase.assertEquals((short) OFHelloFailedCode.OFPHFC_INCOMPATIBLE - .ordinal(), msg.getErrorType()); - TestCase.assertNotNull(msg.getOffendingMsg()); - TestCase.assertEquals(OFHello.MINIMUM_LENGTH, - msg.getOffendingMsg().length); - } - - public void testGarbageAtEnd() throws MessageParseException { - // This is a OFError msg (12 bytes), that encaps a OFVendor msg (24 - // bytes) - // AND some zeros at the end (40 bytes) for a total of 76 bytes - // THIS is what an NEC sends in reply to Nox's VENDOR request - byte[] oferrorRaw = { 0x01, 0x01, 0x00, 0x4c, 0x00, 0x00, 0x10, - (byte) 0xcc, 0x00, 0x01, 0x00, 0x01, 0x01, 0x04, 0x00, 0x18, - 0x00, 0x00, 0x10, (byte) 0xcc, 0x00, 0x00, 0x23, 0x20, 0x00, - 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00 }; - OFMessageFactory factory = BasicFactory.getInstance(); - ChannelBuffer oferrorBuf = - ChannelBuffers.wrappedBuffer(oferrorRaw); - List<OFMessage> msg = factory.parseMessage(oferrorBuf); - TestCase.assertNotNull(msg); - TestCase.assertEquals(msg.size(), 1); - TestCase.assertEquals(76, msg.get(0).getLengthU()); - ChannelBuffer out = ChannelBuffers.dynamicBuffer(); - msg.get(0).writeTo(out); - TestCase.assertEquals(76, out.readableBytes()); - } -} diff --git a/src/test/java/org/openflow/protocol/OFFeaturesReplyTest.java b/src/test/java/org/openflow/protocol/OFFeaturesReplyTest.java deleted file mode 100644 index 62e491a995a3f232be6027043144f6544d70e1d8..0000000000000000000000000000000000000000 --- a/src/test/java/org/openflow/protocol/OFFeaturesReplyTest.java +++ /dev/null @@ -1,62 +0,0 @@ -/** -* Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior -* University -* -* Licensed under the Apache License, Version 2.0 (the "License"); you may -* not use this file except in compliance with the License. You may obtain -* a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -* License for the specific language governing permissions and limitations -* under the License. -**/ - -package org.openflow.protocol; - - -import java.util.ArrayList; -import java.util.List; - -import junit.framework.TestCase; - -import org.jboss.netty.buffer.ChannelBuffer; -import org.jboss.netty.buffer.ChannelBuffers; -import org.openflow.util.OFTestCase; - - -public class OFFeaturesReplyTest extends OFTestCase { - public void testWriteRead() throws Exception { - OFFeaturesReply ofr = (OFFeaturesReply) messageFactory - .getMessage(OFType.FEATURES_REPLY); - List<OFPhysicalPort> ports = new ArrayList<OFPhysicalPort>(); - OFPhysicalPort port = new OFPhysicalPort(); - port.setHardwareAddress(new byte[6]); - port.setName("eth0"); - ports.add(port); - ofr.setPorts(ports); - ChannelBuffer bb = ChannelBuffers.dynamicBuffer(); - bb.clear(); - ofr.writeTo(bb); - ofr.readFrom(bb); - TestCase.assertEquals(1, ofr.getPorts().size()); - TestCase.assertEquals("eth0", ofr.getPorts().get(0).getName()); - - // test a 15 character name - ofr.getPorts().get(0).setName("012345678901234"); - bb.clear(); - ofr.writeTo(bb); - ofr.readFrom(bb); - TestCase.assertEquals("012345678901234", ofr.getPorts().get(0).getName()); - - // test a 16 character name getting truncated - ofr.getPorts().get(0).setName("0123456789012345"); - bb.clear(); - ofr.writeTo(bb); - ofr.readFrom(bb); - TestCase.assertEquals("012345678901234", ofr.getPorts().get(0).getName()); - } -} diff --git a/src/test/java/org/openflow/protocol/OFFlowRemovedTest.java b/src/test/java/org/openflow/protocol/OFFlowRemovedTest.java deleted file mode 100644 index 11746e72411a663170e7ebbf4a8571fb68f8b13f..0000000000000000000000000000000000000000 --- a/src/test/java/org/openflow/protocol/OFFlowRemovedTest.java +++ /dev/null @@ -1,43 +0,0 @@ -/** -* Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior -* University -* -* Licensed under the Apache License, Version 2.0 (the "License"); you may -* not use this file except in compliance with the License. You may obtain -* a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -* License for the specific language governing permissions and limitations -* under the License. -**/ - -package org.openflow.protocol; - -import junit.framework.TestCase; - -import org.jboss.netty.buffer.ChannelBuffer; -import org.jboss.netty.buffer.ChannelBuffers; -import org.openflow.protocol.OFFlowRemoved.OFFlowRemovedReason; -import org.openflow.util.OFTestCase; - -public class OFFlowRemovedTest extends OFTestCase { - public void testWriteRead() throws Exception { - OFFlowRemoved msg = (OFFlowRemoved) messageFactory - .getMessage(OFType.FLOW_REMOVED); - msg.setMatch(new OFMatch()); - byte[] hwAddr = new byte[6]; - msg.getMatch().setDataLayerDestination(hwAddr); - msg.getMatch().setDataLayerSource(hwAddr); - msg.setReason(OFFlowRemovedReason.OFPRR_DELETE); - ChannelBuffer bb = ChannelBuffers.dynamicBuffer(); - bb.clear(); - msg.writeTo(bb); - msg.readFrom(bb); - TestCase.assertEquals(OFType.FLOW_REMOVED, msg.getType()); - TestCase.assertEquals(OFFlowRemovedReason.OFPRR_DELETE, msg.getReason()); - } -} diff --git a/src/test/java/org/openflow/protocol/OFGetConfigReplyTest.java b/src/test/java/org/openflow/protocol/OFGetConfigReplyTest.java deleted file mode 100644 index c1f1f671a6767bb11dc3993c65fc21845d0820ab..0000000000000000000000000000000000000000 --- a/src/test/java/org/openflow/protocol/OFGetConfigReplyTest.java +++ /dev/null @@ -1,38 +0,0 @@ -/** -* Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior -* University -* -* Licensed under the Apache License, Version 2.0 (the "License"); you may -* not use this file except in compliance with the License. You may obtain -* a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -* License for the specific language governing permissions and limitations -* under the License. -**/ - -package org.openflow.protocol; - -import junit.framework.TestCase; - -import org.jboss.netty.buffer.ChannelBuffer; -import org.jboss.netty.buffer.ChannelBuffers; -import org.openflow.util.OFTestCase; - -public class OFGetConfigReplyTest extends OFTestCase { - public void testWriteRead() throws Exception { - OFSetConfig msg = (OFSetConfig) messageFactory - .getMessage(OFType.SET_CONFIG); - msg.setFlags((short) 1); - ChannelBuffer bb = ChannelBuffers.dynamicBuffer(); - bb.clear(); - msg.writeTo(bb); - msg.readFrom(bb); - TestCase.assertEquals(OFType.SET_CONFIG, msg.getType()); - TestCase.assertEquals((short)1, msg.getFlags()); - } -} diff --git a/src/test/java/org/openflow/protocol/OFGetConfigRequestTest.java b/src/test/java/org/openflow/protocol/OFGetConfigRequestTest.java deleted file mode 100644 index 94d9036e68dd1e0b6573148e068fb4380f9946ef..0000000000000000000000000000000000000000 --- a/src/test/java/org/openflow/protocol/OFGetConfigRequestTest.java +++ /dev/null @@ -1,36 +0,0 @@ -/** -* Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior -* University -* -* Licensed under the Apache License, Version 2.0 (the "License"); you may -* not use this file except in compliance with the License. You may obtain -* a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -* License for the specific language governing permissions and limitations -* under the License. -**/ - -package org.openflow.protocol; - -import junit.framework.TestCase; - -import org.jboss.netty.buffer.ChannelBuffer; -import org.jboss.netty.buffer.ChannelBuffers; -import org.openflow.util.OFTestCase; - -public class OFGetConfigRequestTest extends OFTestCase { - public void testWriteRead() throws Exception { - OFGetConfigRequest msg = (OFGetConfigRequest) messageFactory - .getMessage(OFType.GET_CONFIG_REQUEST); - ChannelBuffer bb = ChannelBuffers.dynamicBuffer(); - bb.clear(); - msg.writeTo(bb); - msg.readFrom(bb); - TestCase.assertEquals(OFType.GET_CONFIG_REQUEST, msg.getType()); - } -} diff --git a/src/test/java/org/openflow/protocol/OFMatchTest.java b/src/test/java/org/openflow/protocol/OFMatchTest.java deleted file mode 100644 index fd7863acc12d49e89054ced08111def1c5559b6d..0000000000000000000000000000000000000000 --- a/src/test/java/org/openflow/protocol/OFMatchTest.java +++ /dev/null @@ -1,92 +0,0 @@ -/** -* Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior -* University -* -* Licensed under the Apache License, Version 2.0 (the "License"); you may -* not use this file except in compliance with the License. You may obtain -* a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -* License for the specific language governing permissions and limitations -* under the License. -**/ - -package org.openflow.protocol; - -import junit.framework.TestCase; - -public class OFMatchTest extends TestCase { - public void testFromString() { - OFMatch correct = new OFMatch(); - OFMatch tester = new OFMatch(); - - // Various combinations of "all"/"any" - tester.fromString("OFMatch[]"); - // correct is already wildcarded - TestCase.assertEquals(correct, tester); - tester.fromString("all"); - TestCase.assertEquals(correct, tester); - tester.fromString("ANY"); - TestCase.assertEquals(correct, tester); - tester.fromString(""); - TestCase.assertEquals(correct, tester); - tester.fromString("[]"); - TestCase.assertEquals(correct, tester); - - // ip_src - correct.setWildcards(~OFMatch.OFPFW_NW_SRC_MASK); - correct.setNetworkSource(0x01010203); - tester.fromString("nw_src=1.1.2.3"); - TestCase.assertEquals(correct.getNetworkSourceMaskLen(), tester - .getNetworkSourceMaskLen()); - TestCase.assertEquals(correct, tester); - tester.fromString("IP_sRc=1.1.2.3"); - TestCase.assertEquals(correct.getNetworkSourceMaskLen(), tester - .getNetworkSourceMaskLen()); - TestCase.assertEquals(correct, tester); - - // 0xVlan - correct = new OFMatch(); - correct.setDataLayerVirtualLan((short)65535); - correct.setWildcards(~OFMatch.OFPFW_DL_VLAN); - tester = new OFMatch(); - tester.fromString("dl_vlan=0xffff"); - TestCase.assertEquals(correct, tester); - } - - public void testToString() { - OFMatch match = new OFMatch(); - match.fromString("nw_dst=3.4.5.6/8"); - TestCase.assertEquals(8, match.getNetworkDestinationMaskLen()); - String correct = "OFMatch[nw_dst=3.0.0.0/8]"; - String tester = match.toString(); - - TestCase.assertEquals(correct, tester); - tester = "OFMatch[dl_type=35020]"; - correct = "OFMatch[dl_type=0x88cc]"; - match = new OFMatch(); - match.fromString(tester); - TestCase.assertEquals(correct, match.toString()); - OFMatch match2 = new OFMatch(); - match2.fromString(correct); - TestCase.assertEquals(match, match2); - } - - public void testClone() { - OFMatch match1 = new OFMatch(); - OFMatch match2 = match1.clone(); - TestCase.assertEquals(match1, match2); - match2.setNetworkProtocol((byte) 4); - match2.setWildcards(match2.getWildcards() & ~OFMatch.OFPFW_NW_PROTO); - TestCase.assertNotSame(match1, match2); - } - - public void testIpToString() { - String test = OFMatch.ipToString(-1); - TestCase.assertEquals("255.255.255.255", test); - } -} diff --git a/src/test/java/org/openflow/protocol/OFMessageContextStoreTest.java b/src/test/java/org/openflow/protocol/OFMessageContextStoreTest.java deleted file mode 100644 index 60a9e732b05ba2d03ccbf1c4b9793634cafdeb8a..0000000000000000000000000000000000000000 --- a/src/test/java/org/openflow/protocol/OFMessageContextStoreTest.java +++ /dev/null @@ -1,31 +0,0 @@ -/** -* Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior -* University -* -* Licensed under the Apache License, Version 2.0 (the "License"); you may -* not use this file except in compliance with the License. You may obtain -* a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -* License for the specific language governing permissions and limitations -* under the License. -**/ - -package org.openflow.protocol; - -import junit.framework.TestCase; - -public class OFMessageContextStoreTest extends TestCase { - public void testStoreAndGet() { - OFMessage msg = new OFMessage(); - OFMessageContextStore<String> store = new OFMessageContextStore<String>(msg, this.getName()); - String key = "mykey"; - String value = "myvalue"; - store.put(key, value); - TestCase.assertEquals(value, store.get(key)); - } -} diff --git a/src/test/java/org/openflow/protocol/OFPacketOutTest.java b/src/test/java/org/openflow/protocol/OFPacketOutTest.java deleted file mode 100644 index 55b54552fe03b279f20f076dd8857994dded0e09..0000000000000000000000000000000000000000 --- a/src/test/java/org/openflow/protocol/OFPacketOutTest.java +++ /dev/null @@ -1,45 +0,0 @@ -package org.openflow.protocol; - -import org.junit.Test; - -public class OFPacketOutTest { - - @Test(expected = IllegalArgumentException.class) - public void testBothBufferIdAndPayloadSet() { - OFPacketOut packetOut = new OFPacketOut(); - packetOut.setBufferId(12); - packetOut.setPacketData(new byte[] { 1, 2, 3 }); - } - - @Test - public void testOnlyBufferIdSet() { - OFPacketOut packetOut = new OFPacketOut(); - packetOut.setBufferId(12); - packetOut.setPacketData(null); - packetOut.setPacketData(new byte[] {}); - packetOut.validate(); - } - - @Test(expected = IllegalStateException.class) - public void testNeitherBufferIdNorPayloadSet() { - OFPacketOut packetOut = new OFPacketOut(); - packetOut.setBufferId(OFPacketOut.BUFFER_ID_NONE); - packetOut.setPacketData(null); - packetOut.validate(); - } - - @Test(expected = IllegalStateException.class) - public void testNeitherBufferIdNorPayloadSet2() { - OFPacketOut packetOut = new OFPacketOut(); - packetOut.setBufferId(OFPacketOut.BUFFER_ID_NONE); - packetOut.setPacketData(new byte[] {}); - packetOut.validate(); - } - - @Test(expected = IllegalStateException.class) - public void testNeitherBufferIdNorPayloadSet3() { - OFPacketOut packetOut = new OFPacketOut(); - packetOut.validate(); - } - -} diff --git a/src/test/java/org/openflow/protocol/OFPortConfigTest.java b/src/test/java/org/openflow/protocol/OFPortConfigTest.java deleted file mode 100644 index 9b115ebc40027a15bfe2f1589d39855bc1051ba2..0000000000000000000000000000000000000000 --- a/src/test/java/org/openflow/protocol/OFPortConfigTest.java +++ /dev/null @@ -1,39 +0,0 @@ -/** -* Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior -* University -* -* Licensed under the Apache License, Version 2.0 (the "License"); you may -* not use this file except in compliance with the License. You may obtain -* a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -* License for the specific language governing permissions and limitations -* under the License. -**/ - -package org.openflow.protocol; - -import junit.framework.TestCase; - -import org.jboss.netty.buffer.ChannelBuffer; -import org.jboss.netty.buffer.ChannelBuffers; -import org.openflow.util.OFTestCase; - -public class OFPortConfigTest extends OFTestCase { - public void testWriteRead() throws Exception { - OFPortMod msg = (OFPortMod) messageFactory - .getMessage(OFType.PORT_MOD); - msg.setHardwareAddress(new byte[6]); - msg.portNumber = 1; - ChannelBuffer bb = ChannelBuffers.dynamicBuffer(); - bb.clear(); - msg.writeTo(bb); - msg.readFrom(bb); - TestCase.assertEquals(OFType.PORT_MOD, msg.getType()); - TestCase.assertEquals(1, msg.getPortNumber()); - } -} diff --git a/src/test/java/org/openflow/protocol/OFPortStatusTest.java b/src/test/java/org/openflow/protocol/OFPortStatusTest.java deleted file mode 100644 index 4fab64e03568194784f6b7eaf4518ece9a5017c0..0000000000000000000000000000000000000000 --- a/src/test/java/org/openflow/protocol/OFPortStatusTest.java +++ /dev/null @@ -1,44 +0,0 @@ -/** -* Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior -* University -* -* Licensed under the Apache License, Version 2.0 (the "License"); you may -* not use this file except in compliance with the License. You may obtain -* a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -* License for the specific language governing permissions and limitations -* under the License. -**/ - -package org.openflow.protocol; - -import junit.framework.TestCase; - -import org.jboss.netty.buffer.ChannelBuffer; -import org.jboss.netty.buffer.ChannelBuffers; -import org.openflow.protocol.OFPortStatus.OFPortReason; -import org.openflow.util.OFTestCase; - -public class OFPortStatusTest extends OFTestCase { - public void testWriteRead() throws Exception { - OFPortStatus msg = (OFPortStatus) messageFactory - .getMessage(OFType.PORT_STATUS); - msg.setDesc(new OFPhysicalPort()); - msg.getDesc().setHardwareAddress(new byte[6]); - msg.getDesc().setName("eth0"); - msg.setReason((byte) OFPortReason.OFPPR_ADD.ordinal()); - ChannelBuffer bb = ChannelBuffers.dynamicBuffer(); - bb.clear(); - msg.writeTo(bb); - msg.readFrom(bb); - TestCase.assertEquals(OFType.PORT_STATUS, msg.getType()); - TestCase.assertEquals((byte) OFPortReason.OFPPR_ADD.ordinal(), msg - .getReason()); - TestCase.assertNotNull(msg.getDesc()); - } -} diff --git a/src/test/java/org/openflow/protocol/OFSetConfigTest.java b/src/test/java/org/openflow/protocol/OFSetConfigTest.java deleted file mode 100644 index 2a9e86f7720fb44baf87acaf487d901a2218e026..0000000000000000000000000000000000000000 --- a/src/test/java/org/openflow/protocol/OFSetConfigTest.java +++ /dev/null @@ -1,38 +0,0 @@ -/** -* Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior -* University -* -* Licensed under the Apache License, Version 2.0 (the "License"); you may -* not use this file except in compliance with the License. You may obtain -* a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -* License for the specific language governing permissions and limitations -* under the License. -**/ - -package org.openflow.protocol; - -import junit.framework.TestCase; - -import org.jboss.netty.buffer.ChannelBuffer; -import org.jboss.netty.buffer.ChannelBuffers; -import org.openflow.util.OFTestCase; - -public class OFSetConfigTest extends OFTestCase { - public void testWriteRead() throws Exception { - OFGetConfigReply msg = (OFGetConfigReply) messageFactory - .getMessage(OFType.GET_CONFIG_REPLY); - msg.setFlags((short) 1); - ChannelBuffer bb = ChannelBuffers.dynamicBuffer(); - bb.clear(); - msg.writeTo(bb); - msg.readFrom(bb); - TestCase.assertEquals(OFType.GET_CONFIG_REPLY, msg.getType()); - TestCase.assertEquals((short)1, msg.getFlags()); - } -} diff --git a/src/test/java/org/openflow/protocol/OFStatisticsReplyTest.java b/src/test/java/org/openflow/protocol/OFStatisticsReplyTest.java deleted file mode 100644 index 1885cae7b3d4731ccae5bfbe4fdb295951b1a760..0000000000000000000000000000000000000000 --- a/src/test/java/org/openflow/protocol/OFStatisticsReplyTest.java +++ /dev/null @@ -1,77 +0,0 @@ -/** -* Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior -* University -* -* Licensed under the Apache License, Version 2.0 (the "License"); you may -* not use this file except in compliance with the License. You may obtain -* a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -* License for the specific language governing permissions and limitations -* under the License. -**/ - -package org.openflow.protocol; - -import java.util.List; - -import junit.framework.TestCase; - -import org.jboss.netty.buffer.ChannelBuffer; -import org.jboss.netty.buffer.ChannelBuffers; -import org.openflow.protocol.factory.BasicFactory; -import org.openflow.protocol.factory.OFMessageFactory; -import org.openflow.protocol.statistics.OFStatisticsType; -import org.openflow.util.OFTestCase; - -public class OFStatisticsReplyTest extends OFTestCase { - public void testOFFlowStatisticsReply() throws Exception { - byte[] packet = new byte[] { 0x01, 0x11, 0x01, 0x2c, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, (byte) 0xff, - (byte) 0xff, 0x00, 0x00, 0x08, 0x00, 0x00, 0x01, 0x00, 0x00, - 0x0a, 0x00, 0x00, 0x03, 0x0a, 0x00, 0x00, 0x02, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3a, (byte) 0xa6, - (byte) 0xa6, 0x00, (byte) 0xff, (byte) 0xff, 0x00, 0x05, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - (byte) 0xc4, 0x00, 0x00, 0x00, 0x08, 0x00, 0x01, 0x00, 0x00, - 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x02, (byte) 0xff, (byte) 0xff, 0x00, 0x00, 0x08, 0x06, - 0x00, 0x02, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x03, 0x0a, 0x00, - 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x3b, 0x2f, (byte) 0xfa, 0x40, (byte) 0xff, (byte) 0xff, 0x00, - 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x08, 0x00, 0x01, 0x00, - 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x03, (byte) 0xff, (byte) 0xff, 0x00, 0x62, 0x08, - 0x00, 0x00, 0x01, 0x62, 0x37, 0x0a, 0x00, 0x00, 0x02, 0x0a, - 0x00, 0x00, 0x03, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x3a, (byte) 0xc5, 0x2a, (byte) 0x80, (byte) 0xff, - (byte) 0xff, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, (byte) 0xc4, 0x00, 0x00, 0x00, - 0x08, 0x00, 0x02, 0x00, 0x00 }; - - OFMessageFactory factory = BasicFactory.getInstance(); - ChannelBuffer packetBuf = ChannelBuffers.wrappedBuffer(packet); - List<OFMessage> msg = factory.parseMessage(packetBuf); - TestCase.assertNotNull(msg); - TestCase.assertEquals(msg.size(), 1); - TestCase.assertTrue(msg.get(0) instanceof OFStatisticsReply); - OFStatisticsReply sr = (OFStatisticsReply) msg.get(0); - TestCase.assertEquals(OFStatisticsType.FLOW, sr.getStatisticType()); - TestCase.assertEquals(3, sr.getStatistics().size()); - } -} diff --git a/src/test/java/org/openflow/protocol/OFStatisticsRequestTest.java b/src/test/java/org/openflow/protocol/OFStatisticsRequestTest.java deleted file mode 100644 index fceb8955eb755a12bb03c68bd1102513874b06ab..0000000000000000000000000000000000000000 --- a/src/test/java/org/openflow/protocol/OFStatisticsRequestTest.java +++ /dev/null @@ -1,79 +0,0 @@ -/** -* Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior -* University -* -* Licensed under the Apache License, Version 2.0 (the "License"); you may -* not use this file except in compliance with the License. You may obtain -* a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -* License for the specific language governing permissions and limitations -* under the License. -**/ - -package org.openflow.protocol; - -import java.util.List; - -import junit.framework.TestCase; - -import org.jboss.netty.buffer.ChannelBuffer; -import org.jboss.netty.buffer.ChannelBuffers; -import org.openflow.protocol.factory.BasicFactory; -import org.openflow.protocol.factory.OFMessageFactory; -import org.openflow.protocol.statistics.OFFlowStatisticsRequest; -import org.openflow.protocol.statistics.OFStatisticsType; -import org.openflow.protocol.statistics.OFVendorStatistics; -import org.openflow.util.OFTestCase; - -public class OFStatisticsRequestTest extends OFTestCase { - public void testOFFlowStatisticsRequest() throws Exception { - byte[] packet = new byte[] { 0x01, 0x10, 0x00, 0x38, 0x00, 0x00, 0x00, - 0x16, 0x00, 0x01, 0x00, 0x00, (byte) 0xff, (byte) 0xff, - (byte) 0xff, (byte) 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - (byte) 0xff, 0x00, (byte) 0xff, (byte) 0xff }; - - OFMessageFactory factory = BasicFactory.getInstance(); - ChannelBuffer packetBuf = ChannelBuffers.wrappedBuffer(packet); - List<OFMessage> msg = factory.parseMessage(packetBuf); - TestCase.assertNotNull(msg); - TestCase.assertEquals(msg.size(), 1); - TestCase.assertTrue(msg.get(0) instanceof OFStatisticsRequest); - OFStatisticsRequest sr = (OFStatisticsRequest) msg.get(0); - TestCase.assertEquals(OFStatisticsType.FLOW, sr.getStatisticType()); - TestCase.assertEquals(1, sr.getStatistics().size()); - TestCase.assertTrue(sr.getStatistics().get(0) instanceof OFFlowStatisticsRequest); - } - - public void testOFStatisticsRequestVendor() throws Exception { - byte[] packet = new byte[] { 0x01, 0x10, 0x00, 0x50, 0x00, 0x00, 0x00, - 0x63, (byte) 0xff, (byte) 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x4c, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x20, - (byte) 0xe0, 0x00, 0x11, 0x00, 0x0c, 0x29, (byte) 0xc5, - (byte) 0x95, 0x57, 0x02, 0x25, 0x5c, (byte) 0xca, 0x00, 0x02, - (byte) 0xff, (byte) 0xff, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x50, 0x04, - 0x00, 0x00, 0x00, 0x00, (byte) 0xff, 0x00, 0x00, 0x00, - (byte) 0xff, (byte) 0xff, 0x4e, 0x20 }; - - OFMessageFactory factory = BasicFactory.getInstance(); - ChannelBuffer packetBuf = ChannelBuffers.wrappedBuffer(packet); - List<OFMessage> msg = factory.parseMessage(packetBuf); - TestCase.assertNotNull(msg); - TestCase.assertEquals(msg.size(), 1); - TestCase.assertTrue(msg.get(0) instanceof OFStatisticsRequest); - OFStatisticsRequest sr = (OFStatisticsRequest) msg.get(0); - TestCase.assertEquals(OFStatisticsType.VENDOR, sr.getStatisticType()); - TestCase.assertEquals(1, sr.getStatistics().size()); - TestCase.assertTrue(sr.getStatistics().get(0) instanceof OFVendorStatistics); - TestCase.assertEquals(68, ((OFVendorStatistics)sr.getStatistics().get(0)).getLength()); - } -} diff --git a/src/test/java/org/openflow/protocol/OFStatisticsTypeTest.java b/src/test/java/org/openflow/protocol/OFStatisticsTypeTest.java deleted file mode 100644 index d44ef7f8e4553f3556efb0bb52ad193578c07bbf..0000000000000000000000000000000000000000 --- a/src/test/java/org/openflow/protocol/OFStatisticsTypeTest.java +++ /dev/null @@ -1,37 +0,0 @@ -/** -* Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior -* University -* -* Licensed under the Apache License, Version 2.0 (the "License"); you may -* not use this file except in compliance with the License. You may obtain -* a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -* License for the specific language governing permissions and limitations -* under the License. -**/ - -package org.openflow.protocol; - - -import junit.framework.TestCase; - -import org.junit.Test; -import org.openflow.protocol.statistics.OFStatisticsType; - - -public class OFStatisticsTypeTest extends TestCase { - @Test - public void testMapping() throws Exception { - TestCase.assertEquals(OFStatisticsType.DESC, - OFStatisticsType.valueOf((short) 0, OFType.STATS_REQUEST)); - TestCase.assertEquals(OFStatisticsType.QUEUE, - OFStatisticsType.valueOf((short) 5, OFType.STATS_REQUEST)); - TestCase.assertEquals(OFStatisticsType.VENDOR, - OFStatisticsType.valueOf((short) 0xffff, OFType.STATS_REQUEST)); - } -} diff --git a/src/test/java/org/openflow/protocol/OFTypeTest.java b/src/test/java/org/openflow/protocol/OFTypeTest.java deleted file mode 100644 index c6bf0a347454064d99f0f4ec1064d17cca23da9a..0000000000000000000000000000000000000000 --- a/src/test/java/org/openflow/protocol/OFTypeTest.java +++ /dev/null @@ -1,39 +0,0 @@ -/** -* Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior -* University -* -* Licensed under the Apache License, Version 2.0 (the "License"); you may -* not use this file except in compliance with the License. You may obtain -* a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -* License for the specific language governing permissions and limitations -* under the License. -**/ - -package org.openflow.protocol; - - -import junit.framework.TestCase; - -import org.junit.Test; - - -public class OFTypeTest extends TestCase { - - public void testOFTypeCreate() throws Exception { - OFType foo = OFType.HELLO; - Class<? extends OFMessage> c = foo.toClass(); - TestCase.assertEquals(c, OFHello.class); - } - - @Test - public void testMapping() throws Exception { - TestCase.assertEquals(OFType.HELLO, OFType.valueOf((byte) 0)); - TestCase.assertEquals(OFType.BARRIER_REPLY, OFType.valueOf((byte) 19)); - } -} diff --git a/src/test/java/org/openflow/protocol/OFVendorTest.java b/src/test/java/org/openflow/protocol/OFVendorTest.java deleted file mode 100644 index c0385f4415824d1c79408e93ac31de4be40f3e52..0000000000000000000000000000000000000000 --- a/src/test/java/org/openflow/protocol/OFVendorTest.java +++ /dev/null @@ -1,226 +0,0 @@ -/** -* Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior -* University -* -* Licensed under the Apache License, Version 2.0 (the "License"); you may -* not use this file except in compliance with the License. You may obtain -* a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -* License for the specific language governing permissions and limitations -* under the License. -**/ - -package org.openflow.protocol; - -import java.util.Arrays; - -import junit.framework.TestCase; - -import org.jboss.netty.buffer.ChannelBuffer; -import org.jboss.netty.buffer.ChannelBuffers; -import org.openflow.protocol.factory.BasicFactory; -import org.openflow.protocol.vendor.OFByteArrayVendorData; -import org.openflow.protocol.vendor.OFBasicVendorDataType; -import org.openflow.protocol.vendor.OFBasicVendorId; -import org.openflow.protocol.vendor.OFVendorData; -import org.openflow.protocol.vendor.OFVendorId; -import org.openflow.util.OFTestCase; - -public class OFVendorTest extends OFTestCase { - - public static int ACME_VENDOR_ID = 0x00112233; - - static class AcmeVendorData implements OFVendorData { - protected int dataType; - - @Override - public int getLength() { - return 4; - } - - @Override - public void readFrom(ChannelBuffer data, int length) { - dataType = data.readInt(); - } - - @Override - public void writeTo(ChannelBuffer data) { - data.writeInt(dataType); - } - } - - static class AcmeVendorData1 extends AcmeVendorData { - public short flags; - public short value; - - public static int DATA_TYPE = 1; - - public AcmeVendorData1() { - } - - public AcmeVendorData1(short flags, short value) { - this.dataType = DATA_TYPE; - this.flags = flags; - this.value = value; - } - - public short getFlags() { - return flags; - } - - public short getValue() { - return value; - } - - @Override - public int getLength() { - return 8; - } - - @Override - public void readFrom(ChannelBuffer data, int length) { - super.readFrom(data, length); - flags = data.readShort(); - value = data.readShort(); - - } - @Override - public void writeTo(ChannelBuffer data) { - super.writeTo(data); - data.writeShort(flags); - data.writeShort(value); - } - - public static Instantiable<OFVendorData> getInstantiable() { - return new Instantiable<OFVendorData>() { - @Override - public OFVendorData instantiate() { - return new AcmeVendorData1(); - } - }; - } - } - - static class AcmeVendorData2 extends AcmeVendorData { - public int type; - public int subtype; - - public static int DATA_TYPE = 2; - - public AcmeVendorData2() { - } - - public AcmeVendorData2(int type, int subtype) { - this.dataType = DATA_TYPE; - this.type = type; - this.subtype = subtype; - } - - public int getType() { - return type; - } - - public int getSubtype() { - return subtype; - } - - @Override - public int getLength() { - return 12; - } - - @Override - public void readFrom(ChannelBuffer data, int length) { - super.readFrom(data, length); - type = data.readShort(); - subtype = data.readShort(); - - } - @Override - public void writeTo(ChannelBuffer data) { - super.writeTo(data); - data.writeShort(type); - data.writeShort(subtype); - } - - public static Instantiable<OFVendorData> getInstantiable() { - return new Instantiable<OFVendorData>() { - @Override - public OFVendorData instantiate() { - return new AcmeVendorData2(); - } - }; - } - } - - { - OFBasicVendorId acmeVendorId = new OFBasicVendorId(ACME_VENDOR_ID, 4); - OFVendorId.registerVendorId(acmeVendorId); - OFBasicVendorDataType acmeVendorData1 = new OFBasicVendorDataType( - AcmeVendorData1.DATA_TYPE, AcmeVendorData1.getInstantiable()); - acmeVendorId.registerVendorDataType(acmeVendorData1); - OFBasicVendorDataType acmeVendorData2 = new OFBasicVendorDataType( - AcmeVendorData2.DATA_TYPE, AcmeVendorData2.getInstantiable()); - acmeVendorId.registerVendorDataType(acmeVendorData2); - } - - private OFVendor makeVendorMessage(int vendor) { - OFVendor msg = (OFVendor) messageFactory.getMessage(OFType.VENDOR); - msg.setVendorDataFactory(BasicFactory.getInstance()); - msg.setVendor(vendor); - return msg; - } - - public void testWriteRead() throws Exception { - OFVendor msg = makeVendorMessage(1); - ChannelBuffer bb = ChannelBuffers.dynamicBuffer(); - bb.clear(); - msg.writeTo(bb); - msg.readFrom(bb); - TestCase.assertEquals(1, msg.getVendor()); - } - - public void testVendorData() throws Exception { - OFVendor msg = makeVendorMessage(ACME_VENDOR_ID); - OFVendorData vendorData = new AcmeVendorData1((short)11, (short)22); - msg.setVendorData(vendorData); - msg.setLengthU(OFVendor.MINIMUM_LENGTH + vendorData.getLength()); - ChannelBuffer bb = ChannelBuffers.dynamicBuffer(); - bb.clear(); - msg.writeTo(bb); - msg.readFrom(bb); - assertEquals(ACME_VENDOR_ID, msg.getVendor()); - AcmeVendorData1 vendorData1 = (AcmeVendorData1) msg.getVendorData(); - assertEquals(11, vendorData1.getFlags()); - assertEquals(22, vendorData1.getValue()); - - vendorData = new AcmeVendorData2(33, 44); - msg.setVendorData(vendorData); - msg.setLengthU(OFVendor.MINIMUM_LENGTH + vendorData.getLength()); - bb.clear(); - msg.writeTo(bb); - msg.readFrom(bb); - assertEquals(ACME_VENDOR_ID, msg.getVendor()); - AcmeVendorData2 vendorData2 = (AcmeVendorData2) msg.getVendorData(); - assertEquals(33, vendorData2.getType()); - assertEquals(44, vendorData2.getSubtype()); - - final int DUMMY_VENDOR_ID = 55; - msg.setVendor(DUMMY_VENDOR_ID); - byte[] genericVendorDataBytes = new byte[] {0x55, 0x66}; - vendorData = new OFByteArrayVendorData(genericVendorDataBytes); - msg.setVendorData(vendorData); - msg.setLengthU(OFVendor.MINIMUM_LENGTH + vendorData.getLength()); - bb.clear(); - msg.writeTo(bb); - msg.readFrom(bb); - assertEquals(DUMMY_VENDOR_ID, msg.getVendor()); - OFByteArrayVendorData genericVendorData = (OFByteArrayVendorData) msg.getVendorData(); - assertTrue(Arrays.equals(genericVendorDataBytes, genericVendorData.getBytes())); - } -} diff --git a/src/test/java/org/openflow/protocol/WildcardsTest.java b/src/test/java/org/openflow/protocol/WildcardsTest.java deleted file mode 100644 index 5bf8d12314a070bc301a8ffda6a36ad02ff7d6a0..0000000000000000000000000000000000000000 --- a/src/test/java/org/openflow/protocol/WildcardsTest.java +++ /dev/null @@ -1,162 +0,0 @@ -package org.openflow.protocol; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - -import java.util.EnumSet; - -import org.junit.Test; -import org.openflow.protocol.Wildcards.Flag; - -public class WildcardsTest { - - @Test - public void testBasic() { - int[] intMasks = { 0, 0x3820e0, OFMatch.OFPFW_ALL_SANITIZED }; - for (int i : intMasks) { - Wildcards w = Wildcards.of(i); - assertEquals(i, w.getInt()); - } - } - - @Test - public void testAllSanitize() { - Wildcards w = Wildcards.of(OFMatch.OFPFW_ALL); - assertEquals(OFMatch.OFPFW_ALL_SANITIZED, w.getInt()); - assertTrue(w.isFull()); - assertFalse(w.isExact()); - } - - @Test - public void testAll() { - Wildcards all = Wildcards.FULL; - assertTrue(all.isFull()); - assertFalse(all.isExact()); - assertEquals(0, all.getNwDstMask()); - assertEquals(0, all.getNwSrcMask()); - - // unsetting flags from NONE is a no-op - Wildcards stillAll = all.wildcard(Flag.IN_PORT); - assertTrue(stillAll.isFull()); - assertEquals(all, stillAll); - - // so is setting a >= 32 netmask - - stillAll = all.withNwSrcMask(0); - assertTrue(stillAll.isFull()); - assertEquals(all, stillAll); - - stillAll = all.withNwDstMask(0); - assertTrue(stillAll.isFull()); - assertEquals(all, stillAll); - } - - @Test - public void testNone() { - Wildcards none = Wildcards.EXACT; - assertTrue(none.isExact()); - assertEquals(32, none.getNwDstMask()); - assertEquals(32, none.getNwSrcMask()); - - // unsetting flags from NONE is a no-op - Wildcards stillNone = none.matchOn(Flag.IN_PORT); - assertTrue(stillNone.isExact()); - assertEquals(none, stillNone); - - // so is setting a >= 32 netmask - stillNone = none.withNwSrcMask(32); - assertTrue(stillNone.isExact()); - assertEquals(none, stillNone); - - stillNone = none.withNwDstMask(32); - assertTrue(stillNone.isExact()); - assertEquals(none, stillNone); - } - - @Test - public void testSetOneFlag() { - Wildcards none = Wildcards.EXACT; - assertTrue(none.isExact()); - assertFalse(none.isWildcarded(Flag.DL_SRC)); - Wildcards one = none.wildcard(Flag.DL_SRC); - assertFalse(one.isExact()); - assertTrue(one.isWildcarded(Flag.DL_SRC)); - assertEquals(OFMatch.OFPFW_DL_SRC, one.getInt()); - assertEquals(EnumSet.of(Flag.DL_SRC), one.getWildcardedFlags()); - } - - @Test - public void testSetTwoFlags() { - Wildcards none = Wildcards.EXACT; - - // set two flags - Wildcards two = none.wildcard(Flag.DL_SRC, Flag.DL_DST); - assertFalse(two.isExact()); - assertTrue(two.isWildcarded(Flag.DL_SRC)); - assertTrue(two.isWildcarded(Flag.DL_DST)); - assertEquals(OFMatch.OFPFW_DL_SRC | OFMatch.OFPFW_DL_DST, two.getInt()); - assertEquals(EnumSet.of(Flag.DL_SRC, Flag.DL_DST), two.getWildcardedFlags()); - - // unset dl_dst - Wildcards gone = two.matchOn(Flag.DL_DST); - assertFalse(gone.isExact()); - assertTrue(gone.isWildcarded(Flag.DL_SRC)); - assertFalse(gone.isWildcarded(Flag.DL_DST)); - assertEquals(OFMatch.OFPFW_DL_SRC, gone.getInt()); - assertEquals(EnumSet.of(Flag.DL_SRC), gone.getWildcardedFlags()); - } - - @Test - public void testSetNwSrc() { - Wildcards none = Wildcards.EXACT; - assertEquals(32, none.getNwSrcMask()); - - // unsetting flags from NONE is a no-op - Wildcards nwSet = none.withNwSrcMask(8); - assertFalse(nwSet.isExact()); - assertEquals(EnumSet.noneOf(Flag.class), nwSet.getWildcardedFlags()); - assertEquals(8, nwSet.getNwSrcMask()); - assertEquals((32 - 8) << OFMatch.OFPFW_NW_SRC_SHIFT, nwSet.getInt()); - } - - @Test - public void testSetNwDst() { - Wildcards none = Wildcards.EXACT; - assertEquals(32, none.getNwDstMask()); - - // unsetting flags from NONE is a no-op - Wildcards nwSet = none.withNwDstMask(8); - assertFalse(nwSet.isExact()); - assertEquals(EnumSet.noneOf(Flag.class), nwSet.getWildcardedFlags()); - assertEquals(8, nwSet.getNwDstMask()); - assertEquals((32 - 8) << OFMatch.OFPFW_NW_DST_SHIFT, nwSet.getInt()); - } - - @Test - public void testToString() { - String s = Wildcards.FULL.toString(); - assertNotNull(s); - assertTrue(s.length() > 0); - } - - @Test - public void testInvert() { - assertEquals(Wildcards.FULL, Wildcards.EXACT.inverted()); - - Wildcards some = Wildcards.of(Flag.DL_VLAN, Flag.DL_VLAN_PCP); - Wildcards inv = some.inverted(); - - for(Flag f : Flag.values()) { - boolean shouldBeSet = (f == Flag.DL_VLAN || f == Flag.DL_VLAN_PCP); - - assertEquals("Flag " + f + " " - + (shouldBeSet ? "should be set " : "should not be set"), - shouldBeSet, some.isWildcarded(f)); - assertEquals(!(f == Flag.DL_VLAN || f == Flag.DL_VLAN_PCP), inv.isWildcarded(f)); - } - assertEquals(0, inv.getNwDstMask()); - assertEquals(0, inv.getNwSrcMask()); - } -} diff --git a/src/test/java/org/openflow/protocol/action/MockVendorAction.java b/src/test/java/org/openflow/protocol/action/MockVendorAction.java deleted file mode 100644 index 49b69fb8181d4a6dd6959a40786d643fe05ead20..0000000000000000000000000000000000000000 --- a/src/test/java/org/openflow/protocol/action/MockVendorAction.java +++ /dev/null @@ -1,41 +0,0 @@ -package org.openflow.protocol.action; - -import org.jboss.netty.buffer.ChannelBuffer; - - -public class MockVendorAction extends OFActionVendor { - public static final int VENDOR_ID = 0xdeadbeef; - - private static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; - private byte[] mockData; - - public byte[] getMockData() { - return mockData; - } - - public void setMockData(byte[] mockData) { - this.mockData = mockData; - } - - @Override - public void readFrom(ChannelBuffer data) { - super.readFrom(data); - - int dataLength = getLength() - MINIMUM_LENGTH; - if(dataLength > 0) { - mockData = new byte[dataLength]; - data.readBytes(mockData); - } else { - mockData = EMPTY_BYTE_ARRAY; - } - - } - - @Override - public void writeTo(ChannelBuffer data) { - super.writeTo(data); - data.writeBytes(mockData); - } - - -} diff --git a/src/test/java/org/openflow/protocol/action/MockVendorActionFactory.java b/src/test/java/org/openflow/protocol/action/MockVendorActionFactory.java deleted file mode 100644 index bbc254ca0074cc9e418c5541f976d000074cf0b1..0000000000000000000000000000000000000000 --- a/src/test/java/org/openflow/protocol/action/MockVendorActionFactory.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.openflow.protocol.action; - -import org.jboss.netty.buffer.ChannelBuffer; -import org.openflow.protocol.factory.OFVendorActionFactory; - -public class MockVendorActionFactory implements OFVendorActionFactory { - - @Override - public OFActionVendor readFrom(ChannelBuffer data) { - MockVendorAction action = new MockVendorAction(); - action.readFrom(data); - return action; - } - -} diff --git a/src/test/java/org/openflow/protocol/action/OFVendorActionRegistryTest.java b/src/test/java/org/openflow/protocol/action/OFVendorActionRegistryTest.java deleted file mode 100644 index 31ad675d6a0a708c5a69a7ae3b0dcb21bc24e5c5..0000000000000000000000000000000000000000 --- a/src/test/java/org/openflow/protocol/action/OFVendorActionRegistryTest.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.openflow.protocol.action; - -import static org.junit.Assert.assertEquals; - -import org.junit.Test; -import org.openflow.protocol.factory.OFVendorActionRegistry; - -public class OFVendorActionRegistryTest { - - @Test - public void test() { - MockVendorActionFactory factory = new MockVendorActionFactory(); - OFVendorActionRegistry.getInstance().register(MockVendorAction.VENDOR_ID, factory); - assertEquals(factory, OFVendorActionRegistry.getInstance().get(MockVendorAction.VENDOR_ID)); - } - -} diff --git a/src/test/java/org/openflow/util/HexStringTest.java b/src/test/java/org/openflow/util/HexStringTest.java deleted file mode 100644 index a8f8ba4b89760e797255da01e3cecb162f0da41b..0000000000000000000000000000000000000000 --- a/src/test/java/org/openflow/util/HexStringTest.java +++ /dev/null @@ -1,88 +0,0 @@ -/** -* Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior -* University -* -* Licensed under the Apache License, Version 2.0 (the "License"); you may -* not use this file except in compliance with the License. You may obtain -* a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -* License for the specific language governing permissions and limitations -* under the License. -**/ - -package org.openflow.util; - -import org.junit.Test; - -import junit.framework.TestCase; - -/** - * Does hexstring conversion work? - * - * @author Rob Sherwood (rob.sherwood@stanford.edu) - * - */ - -public class HexStringTest extends TestCase { - - @Test - public void testMarshalling() throws Exception { - String dpidStr = "00:00:00:23:20:2d:16:71"; - long dpid = HexString.toLong(dpidStr); - String testStr = HexString.toHexString(dpid); - TestCase.assertEquals(dpidStr, testStr); - } - - @Test - public void testToLong() { - String dpidStr = "3e:1f:01:fc:72:8c:63:31"; - long valid = 0x3e1f01fc728c6331L; - long testLong = HexString.toLong(dpidStr); - TestCase.assertEquals(valid, testLong); - } - - @Test - public void testToLongMSB() { - String dpidStr = "ca:7c:5e:d1:64:7a:95:9b"; - long valid = -3856102927509056101L; - long testLong = HexString.toLong(dpidStr); - TestCase.assertEquals(valid, testLong); - } - - @Test - public void testToLongError() { - String dpidStr = "09:08:07:06:05:04:03:02:01"; - try { - HexString.toLong(dpidStr); - fail("HexString.toLong() should have thrown a NumberFormatException"); - } - catch (NumberFormatException expected) { - // do nothing - } - } - - @Test - public void testToStringBytes() { - byte[] dpid = { 0, 0, 0, 0, 0, 0, 0, -1 }; - String valid = "00:00:00:00:00:00:00:ff"; - String testString = HexString.toHexString(dpid); - TestCase.assertEquals(valid, testString); - } - - @Test - public void testFromHexStringError() { - String invalidStr = "00:00:00:00:00:00:ffff"; - try { - HexString.fromHexString(invalidStr); - fail("HexString.fromHexString() should have thrown a NumberFormatException"); - } - catch (NumberFormatException expected) { - // do nothing - } - } -} diff --git a/src/test/java/org/openflow/util/OFTestCase.java b/src/test/java/org/openflow/util/OFTestCase.java deleted file mode 100644 index 07bee2d589a1901ad1f77afc3a2ee262a48be429..0000000000000000000000000000000000000000 --- a/src/test/java/org/openflow/util/OFTestCase.java +++ /dev/null @@ -1,36 +0,0 @@ -/** -* Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior -* University -* -* Licensed under the Apache License, Version 2.0 (the "License"); you may -* not use this file except in compliance with the License. You may obtain -* a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -* License for the specific language governing permissions and limitations -* under the License. -**/ - -package org.openflow.util; - -import org.openflow.protocol.factory.BasicFactory; -import org.openflow.protocol.factory.OFMessageFactory; - -import junit.framework.TestCase; - -public class OFTestCase extends TestCase { - public OFMessageFactory messageFactory; - - @Override - protected void setUp() throws Exception { - super.setUp(); - messageFactory = BasicFactory.getInstance(); - } - - public void test() throws Exception { - } -} diff --git a/src/test/java/org/openflow/util/U16Test.java b/src/test/java/org/openflow/util/U16Test.java deleted file mode 100644 index ba87e7b122d5cdb101a6d5a74898a612cb57fcdd..0000000000000000000000000000000000000000 --- a/src/test/java/org/openflow/util/U16Test.java +++ /dev/null @@ -1,33 +0,0 @@ -/** -* Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior -* University -* -* Licensed under the Apache License, Version 2.0 (the "License"); you may -* not use this file except in compliance with the License. You may obtain -* a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -* License for the specific language governing permissions and limitations -* under the License. -**/ - -package org.openflow.util; - -import junit.framework.TestCase; - -public class U16Test extends TestCase { - /** - * Tests that we correctly translate unsigned values in and out of a short - * @throws Exception - */ - public void test() throws Exception { - int val = 0xffff; - TestCase.assertEquals((short)-1, U16.t(val)); - TestCase.assertEquals((short)32767, U16.t(0x7fff)); - TestCase.assertEquals(val, U16.f((short)-1)); - } -} diff --git a/src/test/java/org/openflow/util/U32Test.java b/src/test/java/org/openflow/util/U32Test.java deleted file mode 100644 index 223c1034e74cef66403f3c786a30f771a88b970b..0000000000000000000000000000000000000000 --- a/src/test/java/org/openflow/util/U32Test.java +++ /dev/null @@ -1,32 +0,0 @@ -/** -* Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior -* University -* -* Licensed under the Apache License, Version 2.0 (the "License"); you may -* not use this file except in compliance with the License. You may obtain -* a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -* License for the specific language governing permissions and limitations -* under the License. -**/ - -package org.openflow.util; - -import junit.framework.TestCase; - -public class U32Test extends TestCase { - /** - * Tests that we correctly translate unsigned values in and out of an int - * @throws Exception - */ - public void test() throws Exception { - long val = 0xffffffffL; - TestCase.assertEquals(-1, U32.t(val)); - TestCase.assertEquals(val, U32.f(-1)); - } -} diff --git a/src/test/java/org/openflow/util/U64Test.java b/src/test/java/org/openflow/util/U64Test.java deleted file mode 100644 index 0a97e302f78e84700a0407b5770766eea339ffd4..0000000000000000000000000000000000000000 --- a/src/test/java/org/openflow/util/U64Test.java +++ /dev/null @@ -1,34 +0,0 @@ -/** -* Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior -* University -* -* Licensed under the Apache License, Version 2.0 (the "License"); you may -* not use this file except in compliance with the License. You may obtain -* a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -* License for the specific language governing permissions and limitations -* under the License. -**/ - -package org.openflow.util; - -import java.math.BigInteger; - -import junit.framework.TestCase; - -public class U64Test extends TestCase { - /** - * Tests that we correctly translate unsigned values in and out of a long - * @throws Exception - */ - public void test() throws Exception { - BigInteger val = new BigInteger("ffffffffffffffff", 16); - TestCase.assertEquals(-1, U64.t(val)); - TestCase.assertEquals(val, U64.f(-1)); - } -} diff --git a/src/test/java/org/openflow/util/U8Test.java b/src/test/java/org/openflow/util/U8Test.java deleted file mode 100644 index 2c06c4db31f56699dbcac37d1f4296d1ad16342d..0000000000000000000000000000000000000000 --- a/src/test/java/org/openflow/util/U8Test.java +++ /dev/null @@ -1,32 +0,0 @@ -/** -* Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior -* University -* -* Licensed under the Apache License, Version 2.0 (the "License"); you may -* not use this file except in compliance with the License. You may obtain -* a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -* License for the specific language governing permissions and limitations -* under the License. -**/ - -package org.openflow.util; - -import junit.framework.TestCase; - -public class U8Test extends TestCase { - /** - * Tests that we correctly translate unsigned values in and out of a byte - * @throws Exception - */ - public void test() throws Exception { - short val = 0xff; - TestCase.assertEquals(-1, U8.t(val)); - TestCase.assertEquals(val, U8.f((byte)-1)); - } -} diff --git a/src/test/java/org/openflow/util/UnsignedTest.java b/src/test/java/org/openflow/util/UnsignedTest.java deleted file mode 100644 index 7cdf7391bccd390ca941338cf99563dbb9da229c..0000000000000000000000000000000000000000 --- a/src/test/java/org/openflow/util/UnsignedTest.java +++ /dev/null @@ -1,83 +0,0 @@ -/** -* Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior -* University -* -* Licensed under the Apache License, Version 2.0 (the "License"); you may -* not use this file except in compliance with the License. You may obtain -* a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -* License for the specific language governing permissions and limitations -* under the License. -**/ - -package org.openflow.util; - -import java.math.BigInteger; -import java.nio.ByteBuffer; - -import junit.framework.TestCase; - -public class UnsignedTest extends TestCase { - public static String ULONG_MAX = "18446744073709551615"; - - /** - * Tests that we correctly extract an unsigned long into a BigInteger - * @throws Exception - */ - public void testGetUnsignedLong() throws Exception { - ByteBuffer bb = ByteBuffer.allocate(8); - bb.put((byte)0xff).put((byte)0xff).put((byte)0xff).put((byte)0xff); - bb.put((byte)0xff).put((byte)0xff).put((byte)0xff).put((byte)0xff); - bb.position(0); - bb.limit(8); - BigInteger bi = Unsigned.getUnsignedLong(bb); - BigInteger uLongMax = new BigInteger(ULONG_MAX); - for (int i = 0; i < uLongMax.bitCount(); ++i) { - TestCase.assertTrue("Bit: " + i + " should be: " + uLongMax.testBit(i), - uLongMax.testBit(i) == bi.testBit(i)); - } - TestCase.assertEquals(ULONG_MAX, bi.toString()); - - bb = ByteBuffer.allocate(10); - bb.put((byte)0x00); - bb.put((byte)0xff).put((byte)0xff).put((byte)0xff).put((byte)0xff); - bb.put((byte)0xff).put((byte)0xff).put((byte)0xff).put((byte)0xff); - bb.put((byte)0x00); - bb.position(0); - bb.limit(10); - bi = Unsigned.getUnsignedLong(bb, 1); - uLongMax = new BigInteger(ULONG_MAX); - for (int i = 0; i < uLongMax.bitCount(); ++i) { - TestCase.assertTrue("Bit: " + i + " should be: " + uLongMax.testBit(i), - uLongMax.testBit(i) == bi.testBit(i)); - } - TestCase.assertEquals(ULONG_MAX, bi.toString()); - } - - /** - * Tests that we correctly put an unsigned long into a ByteBuffer - * @throws Exception - */ - public void testPutUnsignedLong() throws Exception { - ByteBuffer bb = ByteBuffer.allocate(8); - BigInteger uLongMax = new BigInteger(ULONG_MAX); - Unsigned.putUnsignedLong(bb, uLongMax); - for (int i = 0; i < 8; ++i) { - TestCase.assertTrue("Byte: " + i + " should be 0xff, was: " + bb.get(i), - (bb.get(i) & (short)0xff) == 0xff); - } - - bb = ByteBuffer.allocate(10); - Unsigned.putUnsignedLong(bb, uLongMax, 1); - int offset = 1; - for (int i = 0; i < 8; ++i) { - TestCase.assertTrue("Byte: " + i + " should be 0xff, was: " + - bb.get(offset+i), (bb.get(offset+i) & (short)0xff) == 0xff); - } - } -}