Import Floodlight v0.90
diff --git a/src/ext/floodlight/Makefile b/src/ext/floodlight/Makefile
new file mode 100644
index 0000000..63c4dd0
--- /dev/null
+++ b/src/ext/floodlight/Makefile
@@ -0,0 +1,36 @@
+# Because I am old and crotchety and my fingers can't stop from running
+# `make` commands
+
+.PHONY: docs doc all test tests count install clean
+
+all:
+ ant
+
+init:
+ ant init
+
+docs:
+ ant javadoc
+
+doc:
+ ant javadoc
+
+javadoc:
+ ant javadoc
+
+check: tests
+test: tests
+
+tests: all unit-tests
+
+unit-tests:
+ ant tests
+
+regression-tests:
+ make -C regress tests
+
+count:
+ @find src -name \*.java | xargs wc -l | sort -n
+
+clean:
+ ant clean
diff --git a/src/ext/floodlight/apps/circuitpusher/circuitpusher.py b/src/ext/floodlight/apps/circuitpusher/circuitpusher.py
new file mode 100755
index 0000000..68c5df5
--- /dev/null
+++ b/src/ext/floodlight/apps/circuitpusher/circuitpusher.py
@@ -0,0 +1,201 @@
+#! /usr/bin/python
+"""
+circuitpusher utilizes floodlight rest APIs to create a bidirectional circuit,
+i.e., permanent flow entry, on all switches in route between two devices based
+on IP addresses with specified priority.
+
+Notes:
+ 1. The circuit pusher currently only creates circuit with two IP end points
+ 2. Prior to sending restAPI requests to the circuit pusher, the specified end
+ points 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.
+ 3. The current supported command syntax format is:
+ a) circuitpusher.py --controller={IP}:{rest port} --type ip --src {IP} --dst {IP} --add --name {circuit-name}
+
+ adds a new circuit between src and dst devices Currently ip circuit is supported. ARP is automatically supported.
+
+ Currently a simple circuit record storage is provided in a text file circuits.json in the working directory.
+ The file is not protected and does not clean itself between controller restarts. The file is needed for correct operation
+ and the user should make sure deleting the file when floodlight controller is restarted.
+
+ b) circuitpusher.py --controller={IP}:{rest port} --delete --name {circuit-name}
+
+ deletes a created circuit (as recorded in circuits.json) using the previously given name
+
+@author kcwang
+"""
+
+import os
+import sys
+import subprocess
+import json
+import argparse
+import io
+import time
+
+# parse circuit options. Currently supports add and delete actions.
+# Syntax:
+# circuitpusher --controller {IP:REST_PORT} --add --name {CIRCUIT_NAME} --type ip --src {IP} --dst {IP}
+# circuitpusher --controller {IP:REST_PORT} --delete --name {CIRCUIT_NAME}
+
+parser = argparse.ArgumentParser(description='Circuit Pusher')
+parser.add_argument('--controller', dest='controllerRestIp', action='store', default='localhost:8080', help='controller IP:RESTport, e.g., localhost:8080 or A.B.C.D:8080')
+parser.add_argument('--add', dest='action', action='store_const', const='add', default='add', help='action: add, delete')
+parser.add_argument('--delete', dest='action', action='store_const', const='delete', default='add', help='action: add, delete')
+parser.add_argument('--type', dest='type', action='store', default='ip', help='valid types: ip')
+parser.add_argument('--src', dest='srcAddress', action='store', default='0.0.0.0', help='source address: if type=ip, A.B.C.D')
+parser.add_argument('--dst', dest='dstAddress', action='store', default='0.0.0.0', help='destination address: if type=ip, A.B.C.D')
+parser.add_argument('--name', dest='circuitName', action='store', default='circuit-1', help='name for circuit, e.g., circuit-1')
+
+args = parser.parse_args()
+print args
+
+controllerRestIp = args.controllerRestIp
+
+# first check if a local file exists, which needs to be updated after add/delete
+if os.path.exists('./circuits.json'):
+ circuitDb = open('./circuits.json','r')
+ lines = circuitDb.readlines()
+ circuitDb.close()
+else:
+ lines={}
+
+if args.action=='add':
+
+ circuitDb = open('./circuits.json','a')
+
+ for line in lines:
+ data = json.loads(line)
+ if data['name']==(args.circuitName):
+ print "Circuit %s exists already. Use new name to create." % args.circuitName
+ sys.exit()
+ else:
+ circuitExists = False
+
+ # retrieve source and destination device attachment points
+ # using DeviceManager rest API
+
+ command = "curl -s http://%s/wm/device/?ipv4=%s" % (args.controllerRestIp, args.srcAddress)
+ result = os.popen(command).read()
+ parsedResult = json.loads(result)
+ print command+"\n"
+ sourceSwitch = parsedResult[0]['attachmentPoint'][0]['switchDPID']
+ 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']
+ destPort = parsedResult[0]['attachmentPoint'][0]['port']
+
+ print "Creating circuit:"
+ print "from source device at switch %s port %s" % (sourceSwitch,sourcePort)
+ print "to destination device at switch %s port %s"% (destSwitch,destPort)
+
+ # retrieving route from source to destination
+ # using Routing rest API
+
+ command = "curl -s http://%s/wm/topology/route/%s/%s/%s/%s/json" % (controllerRestIp, sourceSwitch, sourcePort, destSwitch, destPort)
+
+ result = os.popen(command).read()
+ parsedResult = json.loads(result)
+
+ print command+"\n"
+ print result+"\n"
+
+ for i in range(len(parsedResult)):
+ if i % 2 == 0:
+ ap1Dpid = parsedResult[i]['switch']
+ ap1Port = parsedResult[i]['port']
+ print ap1Dpid, ap1Port
+
+ else:
+ ap2Dpid = parsedResult[i]['switch']
+ ap2Port = parsedResult[i]['port']
+ print ap2Dpid, ap2Port
+
+ # send one flow mod per pair of APs in route
+ # using StaticFlowPusher rest API
+
+ # IMPORTANT NOTE: current Floodlight StaticflowEntryPusher
+ # assumes all flow entries to have unique name across all switches
+ # this will most possibly be relaxed later, but for now we
+ # encode each flow entry's name with both switch dpid, user
+ # specified name, and flow type (f: forward, r: reverse, farp/rarp: arp)
+
+ command = "curl -s -d '{\"switch\": \"%s\", \"name\":\"%s\", \"src-ip\":\"%s\", \"dst-ip\":\"%s\", \"ether-type\":\"%s\", \"cookie\":\"0\", \"priority\":\"32768\", \"ingress-port\":\"%s\",\"active\":\"true\", \"actions\":\"output=%s\"}' http://%s/wm/staticflowentrypusher/json" % (ap1Dpid, ap1Dpid+"."+args.circuitName+".f", args.srcAddress, args.dstAddress, "0x800", ap1Port, ap2Port, controllerRestIp)
+ result = os.popen(command).read()
+ print command
+
+ command = "curl -s -d '{\"switch\": \"%s\", \"name\":\"%s\", \"ether-type\":\"%s\", \"cookie\":\"0\", \"priority\":\"32768\", \"ingress-port\":\"%s\",\"active\":\"true\", \"actions\":\"output=%s\"}' http://%s/wm/staticflowentrypusher/json" % (ap1Dpid, ap1Dpid+"."+args.circuitName+".farp", "0x806", ap1Port, ap2Port, controllerRestIp)
+ result = os.popen(command).read()
+ print command
+
+
+ command = "curl -s -d '{\"switch\": \"%s\", \"name\":\"%s\", \"src-ip\":\"%s\", \"dst-ip\":\"%s\", \"ether-type\":\"%s\", \"cookie\":\"0\", \"priority\":\"32768\", \"ingress-port\":\"%s\",\"active\":\"true\", \"actions\":\"output=%s\"}' http://%s/wm/staticflowentrypusher/json" % (ap1Dpid, ap1Dpid+"."+args.circuitName+".r", args.dstAddress, args.srcAddress, "0x800", ap2Port, ap1Port, controllerRestIp)
+ result = os.popen(command).read()
+ print command
+
+ command = "curl -s -d '{\"switch\": \"%s\", \"name\":\"%s\", \"ether-type\":\"%s\", \"cookie\":\"0\", \"priority\":\"32768\", \"ingress-port\":\"%s\",\"active\":\"true\", \"actions\":\"output=%s\"}' http://%s/wm/staticflowentrypusher/json" % (ap1Dpid, ap1Dpid+"."+args.circuitName+".rarp", "0x806", ap2Port, ap1Port, controllerRestIp)
+ result = os.popen(command).read()
+ print command
+
+ # store created circuit attributes in local ./circuits.json
+ datetime = time.asctime()
+ circuitParams = {'name':args.circuitName, 'Dpid':ap1Dpid, 'inPort':ap1Port, 'outPort':ap2Port, 'datetime':datetime}
+ str = json.dumps(circuitParams)
+ circuitDb.write(str+"\n")
+
+ # confirm successful circuit creation
+ # using controller rest API
+
+ command="curl -s http://%s/wm/core/switch/all/flow/json| python -mjson.tool" % (controllerRestIp)
+ result = os.popen(command).read()
+ print command + "\n" + result
+
+elif args.action=='delete':
+
+ circuitDb = open('./circuits.json','w')
+
+ # removing previously created flow from switches
+ # using StaticFlowPusher rest API
+ # currently, circuitpusher records created circuits in local file ./circuits.db
+ # with circuit name and list of switches
+
+ circuitExists = False
+
+ for line in lines:
+ data = json.loads(line)
+ if data['name']==(args.circuitName):
+ circuitExists = True
+
+ sw = data['Dpid']
+ print data, sw
+
+ command = "curl -X DELETE -d '{\"name\":\"%s\", \"switch\":\"%s\"}' http://%s/wm/staticflowentrypusher/json" % (sw+"."+args.circuitName+".f", sw, controllerRestIp)
+ result = os.popen(command).read()
+ print command, result
+
+ command = "curl -X DELETE -d '{\"name\":\"%s\", \"switch\":\"%s\"}' http://%s/wm/staticflowentrypusher/json" % (sw+"."+args.circuitName+".farp", sw, controllerRestIp)
+ result = os.popen(command).read()
+ print command, result
+
+ command = "curl -X DELETE -d '{\"name\":\"%s\", \"switch\":\"%s\"}' http://%s/wm/staticflowentrypusher/json" % (sw+"."+args.circuitName+".r", sw, controllerRestIp)
+ result = os.popen(command).read()
+ print command, result
+
+ command = "curl -X DELETE -d '{\"name\":\"%s\", \"switch\":\"%s\"}' http://%s/wm/staticflowentrypusher/json" % (sw+"."+args.circuitName+".rarp", sw, controllerRestIp)
+ result = os.popen(command).read()
+ print command, result
+
+ else:
+ circuitDb.write(line)
+
+ circuitDb.close()
+
+ if not circuitExists:
+ print "specified circuit does not exist"
+ sys.exit()
+
diff --git a/src/ext/floodlight/build.xml b/src/ext/floodlight/build.xml
new file mode 100644
index 0000000..b141553
--- /dev/null
+++ b/src/ext/floodlight/build.xml
@@ -0,0 +1,278 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+
+ <!--
+ Copyright 2011, Big Switch Networks, Inc.
+
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You 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.
+-->
+
+<!--
+ The build uses pregenerated Thrift code by default to reduce build
+ dependencies. To generate it locally run the gen-thrift target.
+ If you change the Thrift files be sure to also commit the updated
+ generated code.
+-->
+
+<project default="dist" name="Floodlight">
+ <property name="target" location="target"/>
+ <property name="build" location="${target}/bin"/>
+ <property name="build-test" location="${target}/bin-test"/>
+ <property name="build-coverage" location="${target}/bin-coverage"/>
+ <property name="test-output" location="${target}/test"/>
+ <property name="coverage-output" location="${target}/coverage"/>
+ <property name="source" location="src/main/java"/>
+ <property name="resources" location="src/main/resources/"/>
+ <property name="source-test" location="src/test/java"/>
+ <property name="python-src" location="src/main/python"/>
+ <property name="docs" location="${target}/docs"/>
+ <property name="main-class" value="net.floodlightcontroller.core.Main"/>
+ <property name="floodlight-jar" location="${target}/floodlight.jar"/>
+ <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="thrift.package" value="net/floodlightcontroller/packetstreamer/thrift"/>
+ <property name="ant.build.javac.source" value="1.6"/>
+ <property name="ant.build.javac.target" value="1.6"/>
+ <property name="lib" location="lib"/>
+
+ <patternset id="lib">
+ <include name="logback-classic-1.0.0.jar"/>
+ <include name="logback-core-1.0.0.jar"/>
+ <include name="jackson-core-asl-1.8.6.jar"/>
+ <include name="jackson-mapper-asl-1.8.6.jar"/>
+ <include name="slf4j-api-1.6.4.jar"/>
+ <include name="org.restlet-2.1-RC1.jar"/>
+ <include name="org.restlet.ext.jackson-2.1-RC1.jar"/>
+ <include name="org.restlet.ext.simple-2.1-RC1.jar"/>
+ <include name="org.restlet.ext.slf4j-2.1-RC1.jar"/>
+ <include name="simple-4.1.21.jar"/>
+ <include name="netty-3.2.6.Final.jar"/>
+ <include name="args4j-2.0.16.jar"/>
+ <include name="concurrentlinkedhashmap-lru-1.2.jar"/>
+ <include name="jython-2.5.2.jar"/>
+ <include name="libthrift-0.7.0.jar"/>
+ </patternset>
+
+ <path id="classpath">
+ <fileset dir="${lib}">
+ <patternset refid="lib"/>
+ </fileset>
+ </path>
+
+ <patternset id="lib-cobertura">
+ <include name="cobertura-1.9.4.1.jar"/>
+ <include name="asm-3.0.jar"/>
+ <include name="asm-tree-3.0.jar"/>
+ <include name="oro/jakarta-oro-2.0.8.jar"/>
+ <include name="log4j-1.2.9.jar"/>
+ </patternset>
+ <path id="classpath-cobertura">
+ <fileset dir="${lib}">
+ <patternset refid="lib-cobertura"/>
+ </fileset>
+ </path>
+
+ <patternset id="lib-test">
+ <include name="junit-4.8.2.jar"/>
+ <include name="org.easymock-3.1.jar"/>
+ <include name="objenesis-1.2.jar"/> <!-- required by easymock to mock classes -->
+ <include name="cglib-nodep-2.2.2.jar"/> <!-- required by easymock to mock classes -->
+ </patternset>
+ <path id="classpath-test">
+ <fileset dir="${lib}">
+ <patternset refid="lib-test"/>
+ <patternset refid="lib-cobertura"/>
+ <patternset refid="lib"/>
+ </fileset>
+ </path>
+
+ <target name="init">
+ <mkdir dir="${build}"/>
+ <mkdir dir="${build-test}"/>
+ <mkdir dir="${target}/lib"/>
+ <mkdir dir="${thrift.out.dir}"/>
+ <mkdir dir="${test-output}"/>
+ </target>
+
+ <target name="compile" depends="init">
+ <javac includeAntRuntime="false"
+ classpathref="classpath"
+ debug="true"
+ srcdir="${source}:${thrift.out.dir}"
+ destdir="${build}">
+ </javac>
+ </target>
+
+ <target name="compile-tests" depends="compile-test"/>
+ <target name="compile-test" depends="compile">
+ <fileset dir="${resources}"/>
+ <javac includeAntRuntime="false" debug="true"
+ srcdir="${source-test}"
+ classpath="${build}"
+ classpathref="classpath-test"
+ destdir="${build-test}"/>
+ </target>
+
+ <!-- Thrift build based on http://www.flester.com/blog/2009/04/26/using-thrift-from-ant -->
+ <fileset id="thrift.files" dir="${thrift.dir}">
+ <include name="**/*.thrift"/>
+ </fileset>
+
+ <target name="gen-thrift" depends="init">
+ <pathconvert property="thrift.file.list" refid="thrift.files"
+ pathsep=" " dirsep="/">
+ </pathconvert>
+ <echo message="Running thrift generator on ${thrift.file.list}"/>
+ <exec executable="thrift" dir="${basedir}" failonerror="true">
+ <arg line="--strict -v --gen java -o ${thrift.out.dir}/.. ${thrift.file.list}"/>
+ </exec>
+ <!-- Get rid of annoying warnings in thrift java: at annotations -->
+ <echo message="Adding @SuppressWarning annotations"/>
+ <replaceregexp byline="true">
+ <regexp pattern="^public "/>
+ <substitution expression='@SuppressWarnings("all") public '/>
+ <fileset id="thrift.output.files" dir="${thrift.out.dir}/..">
+ <include name="**/*.java"/>
+ </fileset>
+ </replaceregexp>
+ </target>
+
+ <target name="clean">
+ <delete dir="${target}"/>
+ </target>
+
+ <target name="run" depends="dist">
+ <java fork="true" jar="${floodlight-jar}" classpathref="classpath">
+ <jvmarg value="-server"/>
+ <jvmarg value="-Xms1024M"/>
+ <jvmarg value="-Xmx1024M"/>
+ </java>
+ </target>
+
+ <target name="tests" depends="test"/>
+ <target name="test" depends="compile-test">
+ <junit fork="true" forkmode="once"
+ failureproperty="junit.failure"
+ printsummary="on">
+ <sysproperty key="net.sourceforge.cobertura.datafile"
+ file="${target}/cobertura.ser" />
+ <classpath>
+ <pathelement location="${build-coverage}"/>
+ <pathelement location="${build}"/>
+ <pathelement location="${build-test}"/>
+ <pathelement location="${floodlight-jar}"/>
+ <path refid="classpath-test"/>
+ </classpath>
+ <formatter type="brief" usefile="true" />
+ <batchtest todir="${test-output}">
+ <fileset dir="${source-test}">
+ <exclude name="**/storage/tests/StorageTest.java"/>
+ <include name="**/*Test*.java"/>
+ <exclude name="**/core/test/**"/>
+ <exclude name="**/core/module/**"/>
+ </fileset>
+ </batchtest>
+ </junit>
+ <fail if="junit.failure" message="Unit test(s) failed. See reports!"/>
+ </target>
+
+ <taskdef classpathref="classpath-cobertura" resource="tasks.properties"/>
+ <target name="clean-instrument">
+ <delete file="${target}/cobertura.ser"/>
+ <delete dir="${build-coverage}"/>
+ </target>
+ <target name="instrument" depends="compile,compile-test,clean-instrument">
+ <cobertura-instrument datafile="${target}/cobertura.ser"
+ todir="${build-coverage}"
+ classpathref="classpath-cobertura">
+ <fileset dir="${build}">
+ <include name="**/*.class"/>
+ </fileset>
+ </cobertura-instrument>
+ </target>
+ <target name="coverage-report">
+ <cobertura-report format="html"
+ datafile="${target}/cobertura.ser"
+ destdir="${coverage-output}"
+ srcdir="${source}"/>
+ <cobertura-report format="xml"
+ datafile="${target}/cobertura.ser"
+ destdir="${coverage-output}"
+ srcdir="${source}"/>
+ </target>
+ <target name="coverage" depends="instrument,test,coverage-report"/>
+
+ <target name="dist" depends="compile,compile-test">
+ <jar destfile="${floodlight-jar}" filesetmanifest="mergewithoutmain">
+ <manifest>
+ <attribute name="Main-Class" value="${main-class}"/>
+ <attribute name="Class-Path" value="."/>
+ </manifest>
+ <fileset dir="${build}"/>
+ <fileset dir="${resources}"/>
+ <fileset dir="${python-src}">
+ <include name="**/*.py"/>
+ </fileset>
+ <zipgroupfileset dir="${lib}">
+ <patternset refid="lib"/>
+ </zipgroupfileset>
+ </jar>
+ <jar destfile="${floodlight-test-jar}" filesetmanifest="mergewithoutmain">
+ <manifest>
+ <attribute name="Class-Path" value="."/>
+ </manifest>
+ <fileset dir="${build-test}"/>
+ <fileset dir="${resources}"/>
+ <zipgroupfileset dir="${lib}">
+ <patternset refid="lib-test"/>
+ <patternset refid="lib-cobertura"/>
+ </zipgroupfileset>
+ </jar>
+ </target>
+
+ <target name="javadoc">
+ <javadoc access="protected"
+ author="true"
+ classpathref="classpath"
+ destdir="${docs}"
+ doctitle="Floodlight"
+ nodeprecated="false"
+ nodeprecatedlist="false"
+ noindex="false"
+ nonavbar="false"
+ notree="false"
+ source="1.6"
+ sourcepath="${source}"
+ splitindex="true"
+ use="true"
+ version="true"/>
+ </target>
+
+ <target name="eclipse" depends="init">
+ <pathconvert property="eclipse-lib">
+ <map from="${basedir}/" to=""/>
+ <fileset dir="${lib}">
+ <patternset refid="lib"/>
+ <patternset refid="lib-test"/>
+ </fileset>
+ </pathconvert>
+ <exec executable="${basedir}/setup-eclipse.sh">
+ <arg value="${main-class}"/>
+ <arg value="${eclipse-lib}"/>
+ </exec>
+ </target>
+
+</project>
diff --git a/src/ext/floodlight/example/README b/src/ext/floodlight/example/README
new file mode 100644
index 0000000..e3e8179
--- /dev/null
+++ b/src/ext/floodlight/example/README
@@ -0,0 +1,28 @@
+One of Floodlight's main goals is extensibility and flexibility.
+
+To prove that point, this directory includes a number of useful
+utilities as examples of what can do with this extensibility.
+
+UTILITIES:
+--------------------------
+
+graphDeps.py and graphTopo.py
+
+ Read the module dependencies (graphDeps.py) or the topology
+ from the REST API and output it in the 'dot' format used by the
+ popular graphviz (www.graphviz.org) package so that they can
+ be visualized.
+
+ Example usage:
+ ./graphTopo.py $hostname # generate .dot file
+ dot -Tpdf -o $hostname.pdf $hostname.dot # convert to PDF
+ open $hostname.pdf # open to view topology
+
+
+
+packetStreamerClientExample.py
+
+ Example client for the packet streamer server in floodlight.
+ Allows you to intercept packets from floodlight's packet_in
+ processing chain and read them.
+
diff --git a/src/ext/floodlight/example/cli.py b/src/ext/floodlight/example/cli.py
new file mode 100755
index 0000000..ca8b443
--- /dev/null
+++ b/src/ext/floodlight/example/cli.py
@@ -0,0 +1,115 @@
+#!/usr/bin/python
+
+import sys
+import argparse
+import json
+import httplib
+import urllib2
+
+class RestApi(object):
+
+ def __init__(self, server,port):
+ self.server = server
+ self.port = port
+
+ def get(self, path):
+ #ret = self.rest_call(path, {}, 'GET')
+ #return ret[2]
+ f = urllib2.urlopen('http://'+self.server+':'+str(self.port)+path)
+ ret = f.read()
+ return json.loads(ret)
+
+ def set(self, path, data):
+ ret = self.rest_call(path, data, 'POST')
+ return ret[0] == 200
+
+ def remove(self, objtype, data):
+ #ret = self.rest_call(data, 'DELETE')
+ return ret[0] == 200
+
+ def rest_call(self, path, data, action):
+ headers = {
+ 'Content-type': 'application/json',
+ 'Accept': 'application/json',
+ }
+ body = json.dumps(data)
+ conn = httplib.HTTPConnection(self.server, self.port)
+ conn.request(action, path, body, headers)
+ response = conn.getresponse()
+ ret = (response.status, response.reason, response.read())
+ conn.close()
+ print str(ret[2])
+ return ret
+
+
+
+usage_desc = """
+Command descriptions:
+
+ host [debug]
+ link [tunnellinks]
+ port <blocked | broadcast>
+ memory
+ switch
+ switchclusters
+ counter [DPID] <name>
+ switch_stats [DPID] <port | queue | flow | aggregate | desc | table | features | host>
+"""
+
+def lookupPath(cmd):
+ path = ''
+
+ numargs = len(args.otherargs)
+
+ if args.cmd == 'switch_stats':
+ if numargs == 1:
+ path = '/wm/core/switch/all/'+args.otherargs[0]+'/json'
+ elif numargs == 2:
+ path = '/wm/core/switch/'+args.otherargs[0]+'/'+args.otherargs[1]+'/json'
+ elif args.cmd == 'switch':
+ path = '/wm/core/controller/switches/json'
+ elif args.cmd == 'counter':
+ if numargs == 1:
+ path = '/wm/core/counter/'+args.otherargs[0]+'/json'
+ elif numargs == 2:
+ path = '/wm/core/counter/'+args.otherargs[0]+'/'+args.otherargs[1]+'/json'
+ elif args.cmd == 'memory':
+ path = '/wm/core/memory/json'
+ elif args.cmd == 'link':
+ if numargs == 0:
+ path = '/wm/topology/links/json'
+ elif numargs == 1:
+ path = '/wm/topology/'+args.otherargs[0]+'/json'
+ elif args.cmd == 'port' and numargs == 1:
+ if args.otherargs[0] == "blocked":
+ path = '/wm/topology/blockedports/json'
+ elif args.otherargs[0] == "broadcast":
+ path = '/wm/topology/broadcastdomainports/json'
+ elif args.cmd == 'switchclusters':
+ path = '/wm/topology/switchclusters/json'
+ elif args.cmd == 'host':
+ path = '/wm/device/'
+ if len(args.otherargs) == 1 and args.otherargs[0] == 'debug':
+ path = '/wm/device/debug'
+ else:
+ print usage_desc
+ path = ''
+ exit(0)
+ return path
+
+parser = argparse.ArgumentParser(description='process args', usage=usage_desc, epilog='foo bar help')
+parser.add_argument('--ip', default='localhost')
+parser.add_argument('--port', default=8080)
+parser.add_argument('cmd')
+parser.add_argument('otherargs', nargs='*')
+args = parser.parse_args()
+
+#print args
+
+rest = RestApi(args.ip, args.port)
+path = lookupPath(args.cmd)
+
+out = rest.get(path)
+print json.dumps(out,sort_keys=True, indent=4)
+print "Number of items: " + str(len(out))
+
diff --git a/src/ext/floodlight/example/graphDeps.py b/src/ext/floodlight/example/graphDeps.py
new file mode 100755
index 0000000..505d516
--- /dev/null
+++ b/src/ext/floodlight/example/graphDeps.py
@@ -0,0 +1,72 @@
+#!/usr/bin/python
+
+import urllib2
+import json
+import sys
+
+
+def simple_json_get(url):
+ return json.loads(urllib2.urlopen(url).read())
+
+
+def shorten(s):
+ return s.replace('net.floodlightcontroller','n.f'
+ ).replace('com.bigswitch','c.b')
+
+def usage(s):
+ sys.stderr.write("Usage:\ngrahDeps.py hostname [port]\n%s" % s)
+ sys.stderr.write("\n\n\n\n writes data to 'hostname.dot' for use with graphviz\n")
+ sys.exit(1)
+
+
+if __name__ == '__main__':
+
+ host='localhost'
+ port=8080
+
+ if len(sys.argv) == 1 or sys.argv[1] == '-h' or sys.argv[1] == '--help':
+ usage("need to specify hostname")
+
+ host = sys.argv[1]
+ if len(sys.argv) > 2:
+ port = int(sys.argv[2])
+
+ sys.stderr.write("Connecting to %s:%d ..." % (host,port))
+ URL="http://%s:%d/wm/core/module/loaded/json" % (host,port)
+
+ deps = simple_json_get(URL)
+ serviceMap = {}
+ nodeMap = {}
+ nodeCount = 0
+
+ sys.stderr.write("Writing to %s.dot ..." % (host))
+ f = open("%s.dot" % host, 'w')
+
+ f.write( "digraph Deps {\n")
+
+ for mod, info in deps.iteritems():
+ # sys.stderr.write("Discovered module %s\n" % mod)
+ nodeMap[mod] = "n%d" % nodeCount
+ nodeCount += 1
+ label = shorten(mod) + "\\n"
+ for service, serviceImpl in info['provides'].iteritems():
+ # sys.stderr.write(" Discovered service %s implemented with %s\n" % (service,serviceImpl))
+ label += "\\nService=%s" % shorten(service)
+ serviceMap[serviceImpl] = mod
+ f.write(" %s [ label=\"%s\", color=\"blue\"];\n" % (nodeMap[mod], label))
+
+ f.write("\n") # for readability
+
+ for mod, info in deps.iteritems():
+ for dep, serviceImpl in info['depends'].iteritems():
+ f.write(" %s -> %s [ label=\"%s\"];\n" % (
+ nodeMap[mod],
+ shorten(nodeMap[serviceMap[serviceImpl]]),
+ shorten(dep)))
+
+
+ f.write("}\n")
+ f.close();
+ sys.stderr.write("Now type\ndot -Tpdf -o %s.pdf %s.dot\n" % (
+ host, host))
+
diff --git a/src/ext/floodlight/example/graphTopo.py b/src/ext/floodlight/example/graphTopo.py
new file mode 100755
index 0000000..b89a763
--- /dev/null
+++ b/src/ext/floodlight/example/graphTopo.py
@@ -0,0 +1,77 @@
+#!/usr/bin/python
+
+import urllib2
+import json
+import sys
+
+
+def simple_json_get(url):
+ return json.loads(urllib2.urlopen(url).read())
+
+
+def shorten(s):
+ return s.replace('net.floodlightcontroller','n.f'
+ ).replace('com.bigswitch','c.b')
+
+def usage(s):
+ sys.stderr.write("Usage:\ngrahTopo.py hostname [port]\n%s" % s)
+ sys.stderr.write("\n\n\n\n writes data to 'hostname.dot' for use with graphviz\n")
+ sys.exit(1)
+
+
+if __name__ == '__main__':
+
+ host='localhost'
+ port=8080
+
+ if len(sys.argv) == 1 or sys.argv[1] == '-h' or sys.argv[1] == '--help':
+ usage("need to specify hostname")
+
+ host = sys.argv[1]
+ if len(sys.argv) > 2:
+ port = int(sys.argv[2])
+
+ sys.stderr.write("Connecting to %s:%d ..." % (host,port))
+ URL="http://%s:%d/wm/topology/links/json" % (host,port)
+
+ # {
+ # "dst-port": 2,
+ # "dst-switch": "00:00:00:00:00:00:00:0a",
+ # "src-port": 3,
+ # "src-switch": "00:00:00:00:00:00:00:0c"
+ # }
+
+ links = simple_json_get(URL)
+ nodeMap = {}
+
+ sys.stderr.write("Writing to %s.dot ..." % (host))
+ f = open("%s.dot" % host, 'w')
+
+ f.write( "digraph Deps {\n")
+
+ for link in links:
+ # sys.stderr.write("Discovered module %s\n" % mod)
+ if not link['dst-switch'] in nodeMap:
+ sw = link['dst-switch']
+ nodeMap[sw] = "n%d" % len(nodeMap)
+ f.write(" %s [ label=\"dpid=%s\", color=\"blue\"];\n" % (nodeMap[sw], sw))
+
+ if not link['src-switch'] in nodeMap:
+ sw = link['src-switch']
+ nodeMap[sw] = "n%d" % len(nodeMap)
+ f.write(" %s [ label=\"dpid=%s\", color=\"blue\"];\n" % (nodeMap[sw], sw))
+
+
+ f.write(" %s -> %s [ label=\"%s\"];\n" % (
+ nodeMap[link['dst-switch']],
+ nodeMap[link['src-switch']],
+ "src_port %d --> dst_port %d" % (link['src-port'],link['dst-port'])
+ )
+ )
+
+
+ f.write("}\n")
+ f.close();
+ sys.stderr.write("Now type\ndot -Tpdf -o %s.pdf %s.dot\n" % (
+ host, host))
+
diff --git a/src/ext/floodlight/example/packetStreamerClientExample.py b/src/ext/floodlight/example/packetStreamerClientExample.py
new file mode 100755
index 0000000..4510374
--- /dev/null
+++ b/src/ext/floodlight/example/packetStreamerClientExample.py
@@ -0,0 +1,138 @@
+#!/usr/bin/python
+
+import urllib2
+import json
+import re
+import sys
+
+from optparse import OptionParser
+
+sys.path.append('~/floodlight/target/gen-py')
+sys.path.append('~/floodlight/thrift/lib/py')
+
+from packetstreamer import PacketStreamer
+from packetstreamer.ttypes import *
+
+from thrift import Thrift
+from thrift.transport import TSocket
+from thrift.transport import TTransport
+from thrift.protocol import TBinaryProtocol
+
+SESSIONID = 'sessionId'
+usage = "usage: %prog [options]"
+parser = OptionParser(usage=usage, version="%prog 1.0")
+parser.add_option("-c", "--controller", dest="controller", metavar="CONTROLLER_IP",
+ default="127.0.0.1", help="controller's IP address")
+parser.add_option("-m", "--mac", dest="mac", metavar="HOST_MAC",
+ help="The host mac address to trace the OF packets")
+
+(options, args) = parser.parse_args()
+
+def validateIp(ip):
+ ipReg = ("(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)"
+ "\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)"
+ "\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)"
+ "\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)")
+ m = re.compile(ipReg).match(ip)
+ if m:
+ return True
+ else :
+ return False
+
+def validateMac(mac):
+ macReg = '([a-fA-F0-9]{2}:){5}[a-fA-F0-9]{2}' # same regex as above
+ m = re.compile(macReg).match(mac)
+ if m:
+ return True
+ else :
+ return False
+
+if not validateIp(options.controller):
+ parser.error("Invalid format for ip address.")
+
+if not options.mac:
+ parser.error("-m or --mac option is required.")
+
+if not validateMac(options.mac):
+ parser.error("Invalid format for mac address. Format: xx:xx:xx:xx:xx:xx")
+
+controller = options.controller
+host = options.mac
+
+url = 'http://%s:8080/wm/core/packettrace/json' % controller
+filter = {'mac':host, 'direction':'both', 'period':1000}
+post_data = json.dumps(filter)
+request = urllib2.Request(url, post_data, {'Content-Type':'application/json'})
+response_text = None
+
+def terminateTrace(sid):
+ global controller
+
+ filter = {SESSIONID:sid, 'period':-1}
+ post_data = json.dumps(filter)
+ url = 'http://%s:8080/wm/core/packettrace/json' % controller
+ request = urllib2.Request(url, post_data, {'Content-Type':'application/json'})
+ try:
+ response = urllib2.urlopen(request)
+ response_text = response.read()
+ except Exception, e:
+ # Floodlight may not be running, but we don't want that to be a fatal
+ # error, so we just ignore the exception in that case.
+ print "Exception:", e
+
+try:
+ response = urllib2.urlopen(request)
+ response_text = response.read()
+except Exception, e:
+ # Floodlight may not be running, but we don't want that to be a fatal
+ # error, so we just ignore the exception in that case.
+ print "Exception:", e
+ exit
+
+if not response_text:
+ print "Failed to start a packet trace session"
+ sys.exit()
+
+response_text = json.loads(response_text)
+
+sessionId = None
+if SESSIONID in response_text:
+ sessionId = response_text[SESSIONID]
+else:
+ print "Failed to start a packet trace session"
+ sys.exit()
+
+try:
+
+ # Make socket
+ transport = TSocket.TSocket('localhost', 9090)
+
+ # Buffering is critical. Raw sockets are very slow
+ transport = TTransport.TFramedTransport(transport)
+
+ # Wrap in a protocol
+ protocol = TBinaryProtocol.TBinaryProtocol(transport)
+
+ # Create a client to use the protocol encoder
+ client = PacketStreamer.Client(protocol)
+
+ # Connect!
+ transport.open()
+
+ while 1:
+ packets = client.getPackets(sessionId)
+ for packet in packets:
+ print "Packet: %s"% packet
+ if "FilterTimeout" in packet:
+ sys.exit()
+
+except Thrift.TException, e:
+ print '%s' % (e.message)
+ terminateTrace(sessionId)
+
+except KeyboardInterrupt, e:
+ terminateTrace(sessionId)
+
+# Close!
+transport.close()
+
diff --git a/src/ext/floodlight/floodlight.sh b/src/ext/floodlight/floodlight.sh
new file mode 100755
index 0000000..5bc8564
--- /dev/null
+++ b/src/ext/floodlight/floodlight.sh
@@ -0,0 +1,36 @@
+#!/bin/sh
+
+# Set paths
+FL_HOME=`dirname $0`
+FL_JAR="${FL_HOME}/target/floodlight.jar"
+FL_LOGBACK="${FL_HOME}/logback.xml"
+
+# Set JVM options
+JVM_OPTS=""
+JVM_OPTS="$JVM_OPTS -server -d64"
+JVM_OPTS="$JVM_OPTS -Xmx2g -Xms2g -Xmn800m"
+JVM_OPTS="$JVM_OPTS -XX:+UseParallelGC -XX:+AggressiveOpts -XX:+UseFastAccessorMethods"
+JVM_OPTS="$JVM_OPTS -XX:MaxInlineSize=8192 -XX:FreqInlineSize=8192"
+JVM_OPTS="$JVM_OPTS -XX:CompileThreshold=1500 -XX:PreBlockSpin=8"
+JVM_OPTS="$JVM_OPTS -Dpython.security.respectJavaAccessibility=false"
+
+# Create a logback file if required
+[ -f ${FL_LOGBACK} ] || cat <<EOF_LOGBACK >${FL_LOGBACK}
+<configuration scan="true">
+ <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+ <encoder>
+ <pattern>%level [%logger:%thread] %msg%n</pattern>
+ </encoder>
+ </appender>
+ <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"/>
+</configuration>
+EOF_LOGBACK
+
+echo "Starting floodlight server ..."
+java ${JVM_OPTS} -Dlogback.configurationFile=${FL_LOGBACK} -jar ${FL_JAR}
diff --git a/src/ext/floodlight/floodlight_style_settings.xml b/src/ext/floodlight/floodlight_style_settings.xml
new file mode 100644
index 0000000..51ff232
--- /dev/null
+++ b/src/ext/floodlight/floodlight_style_settings.xml
@@ -0,0 +1,291 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<profiles version="12">
+<profile kind="CodeFormatterProfile" name="Floodlight" version="12">
+<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.disabling_tag" value="@formatter:off"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_field" value="0"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.use_on_off_tags" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_ellipsis" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_multiple_fields" value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer" value="18"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_conditional_expression" value="82"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_binary_operator" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_array_initializer" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_package" value="1"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.continuation_indentation" value="2"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation" value="18"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk" value="1"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_binary_operator" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_package" value="0"/>
+<setting id="org.eclipse.jdt.core.compiler.source" value="1.7"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.format_line_comments" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.join_wrapped_lines" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call" value="18"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_member_type" value="1"/>
+<setting id="org.eclipse.jdt.core.formatter.align_type_members_on_columns" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation" value="18"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_unary_operator" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.indent_parameter_description" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.lineSplit" value="77"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration" value="0"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.indentation.size" value="4"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.enabling_tag" value="@formatter:on"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration" value="20"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_assignment" value="2"/>
+<setting id="org.eclipse.jdt.core.compiler.problem.assertIdentifier" value="error"/>
+<setting id="org.eclipse.jdt.core.formatter.tabulation.char" value="space"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_body" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_method" value="1"/>
+<setting id="org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration" value="18"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_method_declaration" value="18"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_switch" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.compiler.problem.enumIdentifier" value="error"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_ellipsis" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_method_declaration" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.compact_else_if" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_constant" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.indent_root_tags" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch" value="18"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.tabulation.size" value="4"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration" value="20"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_empty_lines" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block_in_case" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve" value="1"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression" value="18"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter" value="insert"/>
+<setting id="org.eclipse.jdt.core.compiler.compliance" value="1.7"/>
+<setting id="org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer" value="2"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression" value="18"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_unary_operator" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration" value="20"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_binary_expression" value="18"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode" value="enabled"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_label" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant" value="18"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.format_javadoc_comments" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.line_length" value="80"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_import_groups" value="1"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body" value="0"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.wrap_before_binary_operator" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations" value="1"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_block" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration" value="18"/>
+<setting id="org.eclipse.jdt.core.formatter.join_lines_in_comments" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_compact_if" value="18"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_imports" value="1"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.format_html" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration" value="18"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.format_source_code" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration" value="18"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer" value="insert"/>
+<setting id="org.eclipse.jdt.core.compiler.codegen.targetPlatform" value="1.7"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_resources_in_try" value="82"/>
+<setting id="org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation" value="82"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.format_header" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.format_block_comments" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_enum_constants" value="2"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_type_declaration" value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_imports" value="1"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header" value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for" value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments" value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column" value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line" value="false"/>
+</profile>
+</profiles>
diff --git a/src/ext/floodlight/logback.xml b/src/ext/floodlight/logback.xml
new file mode 100644
index 0000000..b85b463
--- /dev/null
+++ b/src/ext/floodlight/logback.xml
@@ -0,0 +1,14 @@
+<configuration scan="true">
+ <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+ <encoder>
+ <pattern>%level [%logger:%thread] %msg%n</pattern>
+ </encoder>
+ </appender>
+ <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="WARN"/>
+</configuration>
diff --git a/src/ext/floodlight/setup-eclipse.sh b/src/ext/floodlight/setup-eclipse.sh
new file mode 100755
index 0000000..a39dc62
--- /dev/null
+++ b/src/ext/floodlight/setup-eclipse.sh
@@ -0,0 +1,50 @@
+#!/bin/bash
+
+d=$(dirname $0)
+MAIN_CLASS=$1
+LIBRARIES=$2
+[ "${MAIN_CLASS}" ] || { echo "Run 'ant eclipse' to generate Eclipse project files"; exit 1; }
+
+
+cat >$d/.project <<EOF
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>floodlight</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ </natures>
+</projectDescription>
+EOF
+
+
+cat >$d/.classpath <<EOF
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="src" path="src/main/java" output="target/bin"/>
+ <classpathentry kind="src" path="src/main/resources"/>
+ <classpathentry kind="src" path="src/test/java" output="target/bin-test"/>
+ <classpathentry kind="src" path="lib/gen-java" output="target/bin"/>
+EOF
+(
+IFS=":"
+for l in ${LIBRARIES}; do
+cat >>$d/.classpath <<EOF
+ <classpathentry exported="true" kind="lib" path="$l"/>
+EOF
+done
+)
+cat >>$d/.classpath <<EOF
+ <classpathentry exported="true" kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+ <classpathentry kind="output" path="target/bin"/>
+</classpath>
+EOF
diff --git a/src/ext/floodlight/src/main/images/Floodlight Icons.sketch/Data b/src/ext/floodlight/src/main/images/Floodlight Icons.sketch/Data
new file mode 100644
index 0000000..0fbceab
--- /dev/null
+++ b/src/ext/floodlight/src/main/images/Floodlight Icons.sketch/Data
Binary files differ
diff --git a/src/ext/floodlight/src/main/images/Floodlight Icons.sketch/QuickLook/Preview.png b/src/ext/floodlight/src/main/images/Floodlight Icons.sketch/QuickLook/Preview.png
new file mode 100644
index 0000000..4a36e7c
--- /dev/null
+++ b/src/ext/floodlight/src/main/images/Floodlight Icons.sketch/QuickLook/Preview.png
Binary files differ
diff --git a/src/ext/floodlight/src/main/images/Floodlight Icons.sketch/QuickLook/Thumbnail.png b/src/ext/floodlight/src/main/images/Floodlight Icons.sketch/QuickLook/Thumbnail.png
new file mode 100644
index 0000000..50ebe22
--- /dev/null
+++ b/src/ext/floodlight/src/main/images/Floodlight Icons.sketch/QuickLook/Thumbnail.png
Binary files differ
diff --git a/src/ext/floodlight/src/main/images/README b/src/ext/floodlight/src/main/images/README
new file mode 100644
index 0000000..46da010
--- /dev/null
+++ b/src/ext/floodlight/src/main/images/README
@@ -0,0 +1 @@
+These icons were created with Sketch, available at http://www.bohemiancoding.com/sketch/
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/FloodlightContext.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/FloodlightContext.java
new file mode 100644
index 0000000..aa4fe6b
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/FloodlightContext.java
@@ -0,0 +1,35 @@
+/**
+* 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.concurrent.ConcurrentHashMap;
+
+/**
+ * This is a context object where floodlight listeners can register
+ * and later retrieve context information associated with an
+ * event
+ * @author readams
+ */
+public class FloodlightContext {
+ protected ConcurrentHashMap<String, Object> storage =
+ new ConcurrentHashMap<String, Object>();
+
+ public ConcurrentHashMap<String, Object> getStorage() {
+ return storage;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/FloodlightContextStore.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/FloodlightContextStore.java
new file mode 100644
index 0000000..5455284
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/FloodlightContextStore.java
@@ -0,0 +1,34 @@
+/**
+* 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;
+
+public class FloodlightContextStore<V> {
+
+ @SuppressWarnings("unchecked")
+ public V get(FloodlightContext bc, String key) {
+ return (V)bc.storage.get(key);
+ }
+
+ public void put(FloodlightContext bc, String key, V value) {
+ bc.storage.put(key, value);
+ }
+
+ public void remove(FloodlightContext bc, String key) {
+ bc.storage.remove(key);
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/FloodlightProvider.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/FloodlightProvider.java
new file mode 100644
index 0000000..347bf5b
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/FloodlightProvider.java
@@ -0,0 +1,74 @@
+package net.floodlightcontroller.core;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import net.floodlightcontroller.core.internal.Controller;
+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.perfmon.IPktInProcessingTimeService;
+import net.floodlightcontroller.restserver.IRestApiService;
+import net.floodlightcontroller.storage.IStorageSourceService;
+import net.floodlightcontroller.threadpool.IThreadPoolService;
+
+public class FloodlightProvider implements IFloodlightModule {
+ Controller controller;
+
+ @Override
+ public Collection<Class<? extends IFloodlightService>> getModuleServices() {
+ Collection<Class<? extends IFloodlightService>> services =
+ new ArrayList<Class<? extends IFloodlightService>>(1);
+ services.add(IFloodlightProviderService.class);
+ return services;
+ }
+
+ @Override
+ public Map<Class<? extends IFloodlightService>,
+ IFloodlightService> getServiceImpls() {
+ controller = new Controller();
+
+ Map<Class<? extends IFloodlightService>,
+ IFloodlightService> m =
+ new HashMap<Class<? extends IFloodlightService>,
+ IFloodlightService>();
+ m.put(IFloodlightProviderService.class, controller);
+ return m;
+ }
+
+ @Override
+ public Collection<Class<? extends IFloodlightService>> getModuleDependencies() {
+ Collection<Class<? extends IFloodlightService>> dependencies =
+ new ArrayList<Class<? extends IFloodlightService>>(4);
+ dependencies.add(IStorageSourceService.class);
+ dependencies.add(IPktInProcessingTimeService.class);
+ dependencies.add(IRestApiService.class);
+ dependencies.add(ICounterStoreService.class);
+ dependencies.add(IThreadPoolService.class);
+ return dependencies;
+ }
+
+ @Override
+ public void init(FloodlightModuleContext context) throws FloodlightModuleException {
+ controller.setStorageSourceService(
+ context.getServiceImpl(IStorageSourceService.class));
+ controller.setPktInProcessingService(
+ context.getServiceImpl(IPktInProcessingTimeService.class));
+ controller.setCounterStore(
+ context.getServiceImpl(ICounterStoreService.class));
+ controller.setRestApiService(
+ context.getServiceImpl(IRestApiService.class));
+ controller.setThreadPoolService(
+ context.getServiceImpl(IThreadPoolService.class));
+ controller.init(context.getConfigParams(this));
+ }
+
+ @Override
+ public void startUp(FloodlightModuleContext context) {
+ controller.startupComponents();
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/IFloodlightProviderService.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/IFloodlightProviderService.java
new file mode 100644
index 0000000..1e3ec6f
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/IFloodlightProviderService.java
@@ -0,0 +1,210 @@
+/**
+ * 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 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;
+
+/**
+ * The interface exposed by the core bundle that allows you to interact
+ * with connected switches.
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public interface IFloodlightProviderService extends IFloodlightService {
+
+ /**
+ * A value stored in the floodlight context containing a parsed packet
+ * representation of the payload of a packet-in message.
+ */
+ 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, MASTER, SLAVE };
+
+ /**
+ * A FloodlightContextStore object that can be used to retrieve the
+ * packet-in payload
+ */
+ public static final FloodlightContextStore<Ethernet> bcStore =
+ new FloodlightContextStore<Ethernet>();
+
+ /**
+ * Adds an OpenFlow message listener
+ * @param type The OFType the component wants to listen for
+ * @param listener The component that wants to listen for the message
+ */
+ public void addOFMessageListener(OFType type, IOFMessageListener listener);
+
+ /**
+ * Removes an OpenFlow message listener
+ * @param type The OFType the component no long wants to listen for
+ * @param listener The component that no longer wants to receive the message
+ */
+ public void removeOFMessageListener(OFType type, IOFMessageListener listener);
+
+ /**
+ * Return a non-modifiable list of all current listeners
+ * @return listeners
+ */
+ public Map<OFType, List<IOFMessageListener>> getListeners();
+
+ /**
+ * Returns an unmodifiable map of all actively connected OpenFlow switches. This doesn't
+ * contain switches that are connected but the controller's in the slave role.
+ * @return the set of actively connected switches
+ */
+ public Map<Long, IOFSwitch> getSwitches();
+
+ /**
+ * Get the current role of the controller
+ */
+ public Role getRole();
+
+ /**
+ * Get the current mapping of controller IDs to their IP addresses
+ * Returns a copy of the current mapping.
+ * @see IHAListener
+ */
+ public Map<String,String> getControllerNodeIPs();
+
+ /**
+ * Gets the ID of the controller
+ */
+ public String getControllerId();
+
+ /**
+ * Set the role of the controller
+ */
+ public void setRole(Role role);
+
+ /**
+ * Add a switch listener
+ * @param listener The module that wants to listen for events
+ */
+ public void addOFSwitchListener(IOFSwitchListener listener);
+
+ /**
+ * Remove a switch listener
+ * @param listener The The module that no longer wants to listen for events
+ */
+ public void removeOFSwitchListener(IOFSwitchListener listener);
+
+ /**
+ * Adds a listener for HA role events
+ * @param listener The module that wants to listen for events
+ */
+ public void addHAListener(IHAListener listener);
+
+ /**
+ * Removes a listener for HA role events
+ * @param listener The module that no longer wants to listen for events
+ */
+ public void removeHAListener(IHAListener listener);
+
+ /**
+ * 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
+ */
+ 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
+ * @return True if successfully re-injected, false otherwise
+ */
+ 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
+ */
+ public void handleOutgoingMessage(IOFSwitch sw, OFMessage m,
+ FloodlightContext bc);
+
+ /**
+ * Gets the BasicFactory
+ * @return an OpenFlow message factory
+ */
+ public BasicFactory getOFMessageFactory();
+
+ /**
+ * Run the main I/O loop of the Controller.
+ */
+ public void run();
+
+ /**
+ * Add an info provider of a particular type
+ * @param type
+ * @param provider
+ */
+ public void addInfoProvider(String type, IInfoProvider provider);
+
+ /**
+ * Remove an info provider of a particular type
+ * @param type
+ * @param provider
+ */
+ public void removeInfoProvider(String type, IInfoProvider provider);
+
+ /**
+ * Return information of a particular type (for rest services)
+ * @param type
+ * @return
+ */
+ 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 setAlwaysClearFlowsOnSwAdd(boolean value);
+
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/IHAListener.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/IHAListener.java
new file mode 100644
index 0000000..c76f46a
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/IHAListener.java
@@ -0,0 +1,30 @@
+package net.floodlightcontroller.core;
+
+import java.util.Map;
+
+import net.floodlightcontroller.core.IFloodlightProviderService.Role;
+
+public interface IHAListener {
+ /**
+ * Gets called when the controller changes role (i.e. Master -> Slave).
+ * Note that oldRole CAN be null.
+ * @param oldRole The controller's old role
+ * @param newRole The controller's new role
+ */
+ public void roleChanged(Role oldRole, Role newRole);
+
+ /**
+ * 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
+ */
+ public void controllerNodeIPsChanged(
+ Map<String, String> curControllerNodeIPs,
+ Map<String, String> addedControllerNodeIPs,
+ Map<String, String> removedControllerNodeIPs
+ );
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/IInfoProvider.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/IInfoProvider.java
new file mode 100644
index 0000000..8bfae0d
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/IInfoProvider.java
@@ -0,0 +1,34 @@
+/**
+* Copyright 2011, 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;
+
+import java.util.Map;
+
+/**
+ *
+ *
+ * @author Shudong Zhou
+ */
+public interface IInfoProvider {
+
+ /**
+ * Called when rest API requests information of a particular type
+ * @param type
+ * @return
+ */
+ public Map<String, Object> getInfo(String type);
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/IListener.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/IListener.java
new file mode 100644
index 0000000..1bd6560
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/IListener.java
@@ -0,0 +1,52 @@
+/**
+* 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;
+
+public interface IListener<T> {
+ public enum Command {
+ CONTINUE, STOP
+ }
+
+ /**
+ * The name assigned to this listener
+ * @return
+ */
+ public String getName();
+
+ /**
+ * Check if the module called name is a callback ordering prerequisite
+ * for this module. In other words, if this function returns true for
+ * the given name, then this message listener will be called after that
+ * message listener.
+ * @param type the message type to which this applies
+ * @param name the name of the module
+ * @return whether name is a prerequisite.
+ */
+ public boolean isCallbackOrderingPrereq(T type, String name);
+
+ /**
+ * Check if the module called name is a callback ordering post-requisite
+ * for this module. In other words, if this function returns true for
+ * the given name, then this message listener will be called before that
+ * message listener.
+ * @param type the message type to which this applies
+ * @param name the name of the module
+ * @return whether name is a post-requisite.
+ */
+ public boolean isCallbackOrderingPostreq(T type, String name);
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/IOFMessageFilterManagerService.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/IOFMessageFilterManagerService.java
new file mode 100644
index 0000000..36b5be3
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/IOFMessageFilterManagerService.java
@@ -0,0 +1,7 @@
+package net.floodlightcontroller.core;
+
+import net.floodlightcontroller.core.module.IFloodlightService;
+
+public interface IOFMessageFilterManagerService extends IFloodlightService {
+ // empty for now
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/IOFMessageListener.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/IOFMessageListener.java
new file mode 100644
index 0000000..00fdac1
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/IOFMessageListener.java
@@ -0,0 +1,38 @@
+/**
+* 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 org.openflow.protocol.OFMessage;
+import org.openflow.protocol.OFType;
+
+/**
+ *
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public interface IOFMessageListener extends IListener<OFType> {
+ /**
+ * This is the method Floodlight uses to call listeners with OpenFlow messages
+ * @param sw the OpenFlow switch that sent this message
+ * @param msg the message
+ * @param cntx a Floodlight message context object you can use to pass
+ * information between listeners
+ * @return the command to continue or stop the execution
+ */
+ public Command receive(IOFSwitch sw, OFMessage msg, FloodlightContext cntx);
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/IOFSwitch.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/IOFSwitch.java
new file mode 100644
index 0000000..d63624c
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/IOFSwitch.java
@@ -0,0 +1,379 @@
+/**
+* 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.io.IOException;
+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 org.jboss.netty.channel.Channel;
+import org.openflow.protocol.OFFeaturesReply;
+import org.openflow.protocol.OFMessage;
+import org.openflow.protocol.OFPhysicalPort;
+import org.openflow.protocol.OFStatisticsRequest;
+import org.openflow.protocol.statistics.OFDescriptionStatistics;
+import org.openflow.protocol.statistics.OFStatistics;
+
+/**
+ *
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public interface IOFSwitch {
+ // Attribute keys
+ public static final String SWITCH_DESCRIPTION_FUTURE = "DescriptionFuture";
+ public static final String SWITCH_DESCRIPTION_DATA = "DescriptionData";
+ 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";
+
+ /**
+ * Writes to the OFMessage to the output stream.
+ * 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;
+
+ /**
+ * Writes the list of messages to the output stream
+ * 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();
+
+ /**
+ * FIXME: remove getChannel(). All access to the channel should be through
+ * wrapper functions in IOFSwitch
+ * @return
+ */
+ public Channel getChannel();
+
+ /**
+ * Returns switch features from features Reply
+ * @return
+ */
+ public int getBuffers();
+
+ public int getActions();
+
+ public int getCapabilities();
+
+ public byte getTables();
+
+ /**
+ * Set the OFFeaturesReply message returned by the switch during initial
+ * handshake.
+ * @param featuresReply
+ */
+ public void setFeaturesReply(OFFeaturesReply featuresReply);
+
+ /**
+ * Set the SwitchProperties based on it's description
+ * @param description
+ */
+ public void setSwitchProperties(OFDescriptionStatistics description);
+
+ /**
+ * Get list of all enabled ports. This will typically be different from
+ * the list of ports in the OFFeaturesReply, since that one is a static
+ * snapshot of the ports at the time the switch connected to the controller
+ * whereas this port list also reflects the port status messages that have
+ * been received.
+ * @return Unmodifiable list of ports not backed by the underlying collection
+ */
+ public Collection<OFPhysicalPort> getEnabledPorts();
+
+ /**
+ * Get list of the port numbers of all enabled ports. This will typically
+ * be different from the list of ports in the OFFeaturesReply, since that
+ * one is a static snapshot of the ports at the time the switch connected
+ * to the controller whereas this port list also reflects the port status
+ * messages that have been received.
+ * @return Unmodifiable list of ports not backed by the underlying collection
+ */
+ public Collection<Short> getEnabledPortNumbers();
+
+ /**
+ * Retrieve the port object by the port number. The port object
+ * is the one that reflects the port status updates that have been
+ * received, not the one from the features reply.
+ * @param portNumber
+ * @return port object
+ */
+ public OFPhysicalPort getPort(short portNumber);
+
+ /**
+ * Retrieve the port object by the port name. The port object
+ * is the one that reflects the port status updates that have been
+ * received, not the one from the features reply.
+ * @param portName
+ * @return port object
+ */
+ public OFPhysicalPort 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.
+ * @param port
+ */
+ public void setPort(OFPhysicalPort port);
+
+ /**
+ * Delete a port for the switch. This is called by the core controller
+ * code in response to a OFPortStatus message. It should not typically be
+ * called by other floodlight applications.
+ * @param portNumber
+ */
+ public void deletePort(short portNumber);
+
+ /**
+ * Delete a port for the switch. This is called by the core controller
+ * code in response to a OFPortStatus message. It should not typically be
+ * called by other floodlight applications.
+ * @param portName
+ */
+ public void deletePort(String portName);
+
+ /**
+ * Get list of all ports. This will typically be different from
+ * the list of ports in the OFFeaturesReply, since that one is a static
+ * snapshot of the ports at the time the switch connected to the controller
+ * whereas this port list also reflects the port status messages that have
+ * been received.
+ * @return Unmodifiable list of ports
+ */
+ public Collection<OFPhysicalPort> getPorts();
+
+ /**
+ * @param portName
+ * @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 portName);
+
+ /**
+ * @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);
+
+ /**
+ * @param port
+ * @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(OFPhysicalPort port);
+
+ /**
+ * Get the datapathId of the switch
+ * @return
+ */
+ public long getId();
+
+ /**
+ * Get a string version of the ID for this switch
+ * @return
+ */
+ public String getStringId();
+
+ /**
+ * 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>> getStatistics(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> getFeaturesReplyFromSwitch()
+ 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 still connected;
+ * Only call while holding processMessageLock
+ * @return whether the switch is still disconnected
+ */
+ public boolean isConnected();
+
+ /**
+ * Set whether the switch is connected
+ * Only call while holding modifySwitchLock
+ * @param connected whether the switch is connected
+ */
+ public void setConnected(boolean connected);
+
+ /**
+ * Get the current role of the controller for the switch
+ * @return the role of the controller
+ */
+ public Role getRole();
+
+ /**
+ * Check if the controller is an active controller for the switch.
+ * The controller is active if its role is MASTER or EQUAL.
+ * @return whether the controller is active
+ */
+ public boolean isActive();
+
+ /**
+ * Deliver the statistics future reply
+ * @param reply the reply to deliver
+ */
+ public void deliverStatisticsReply(OFMessage 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();
+
+ /**
+ * Checks if a specific switch property exists for this switch
+ * @param name name of property
+ * @return value for name
+ */
+ boolean hasAttribute(String name);
+
+ /**
+ * Set properties for switch specific behavior
+ * @param name name of property
+ * @return value for name
+ */
+ Object getAttribute(String name);
+
+ /**
+ * Set properties for switch specific behavior
+ * @param name name of property
+ * @param value value for name
+ */
+ void setAttribute(String name, Object value);
+
+ /**
+ * Set properties for switch specific behavior
+ * @param name name of property
+ * @return current value for name or null (if not present)
+ */
+ 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
+ * @return
+ */
+ public Map<Short, Long> getPortBroadcastHits();
+
+ /**
+ * 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();
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/IOFSwitchFilter.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/IOFSwitchFilter.java
new file mode 100644
index 0000000..134ba98
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/IOFSwitchFilter.java
@@ -0,0 +1,38 @@
+/**
+* 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;
+
+/**
+ * Used in conjunction with {@link IOFMessageListener} to allow a listener to
+ * filter an incoming message based on the {@link IOFSwitch} it originated from.
+ * Implementations wanting to use this interface should implement both
+ * IOFMessageListener and IOFSwitchFilter.
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public interface IOFSwitchFilter {
+
+ /**
+ * The result of this method call determines whether the
+ * IOFMessageListener's receive method is called or not.
+ *
+ * @param sw switch to filter on
+ * @return true to receive the message, false to ignore
+ */
+ public boolean isInterested(IOFSwitch sw);
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/IOFSwitchListener.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/IOFSwitchListener.java
new file mode 100644
index 0000000..1bc258b
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/IOFSwitchListener.java
@@ -0,0 +1,51 @@
+/**
+* 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;
+
+/**
+ *
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public interface IOFSwitchListener {
+
+ /**
+ * Fired when a switch is connected to the controller, and has sent
+ * a features reply.
+ * @param sw
+ */
+ public void addedSwitch(IOFSwitch sw);
+
+ /**
+ * Fired when a switch is disconnected from the controller.
+ * @param sw
+ */
+ public void removedSwitch(IOFSwitch sw);
+
+ /**
+ * Fired when ports on a switch change (any change to the collection
+ * of OFPhysicalPorts and/or to a particular port)
+ */
+ public void switchPortChanged(Long switchId);
+
+ /**
+ * The name assigned to this listener
+ * @return
+ */
+ public String getName();
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/Main.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/Main.java
new file mode 100644
index 0000000..91b317a
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/Main.java
@@ -0,0 +1,49 @@
+package net.floodlightcontroller.core;
+
+import org.kohsuke.args4j.CmdLineException;
+import org.kohsuke.args4j.CmdLineParser;
+
+import net.floodlightcontroller.core.internal.CmdLineSettings;
+import net.floodlightcontroller.core.module.FloodlightModuleException;
+import net.floodlightcontroller.core.module.FloodlightModuleLoader;
+import net.floodlightcontroller.core.module.IFloodlightModuleContext;
+import net.floodlightcontroller.restserver.IRestApiService;
+
+/**
+ * Host for the Floodlight main method
+ * @author alexreimers
+ */
+public class Main {
+
+ /**
+ * 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();
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/OFMessageFilterManager.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/OFMessageFilterManager.java
new file mode 100644
index 0000000..391c002
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/OFMessageFilterManager.java
@@ -0,0 +1,529 @@
+/**
+ * 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 TTransport transport = null;
+ protected static 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("%d", 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.
+ }
+
+ 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 {
+ transport = new TFramedTransport(new TSocket("localhost",
+ serverPort));
+ transport.open();
+
+ TProtocol protocol = new TBinaryProtocol(transport);
+ packetClient = new PacketStreamer.Client(protocol);
+
+ log.debug("Have a connection to packetstreamer server " +
+ "localhost:{}", serverPort);
+ 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/ext/floodlight/src/main/java/net/floodlightcontroller/core/annotations/LogMessageCategory.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/annotations/LogMessageCategory.java
new file mode 100644
index 0000000..e9abf02
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/annotations/LogMessageCategory.java
@@ -0,0 +1,34 @@
+/**
+* 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.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation used to set the category for log messages for a class
+ * @author readams
+ */
+@Target({ElementType.TYPE, ElementType.METHOD})
+public @interface LogMessageCategory {
+ /**
+ * The category for the log messages for this class
+ * @return
+ */
+ String value() default "Core";
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/annotations/LogMessageDoc.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/annotations/LogMessageDoc.java
new file mode 100644
index 0000000..80af1a7
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/annotations/LogMessageDoc.java
@@ -0,0 +1,71 @@
+/**
+* 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.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation used to document log messages. This can be used to generate
+ * documentation on syslog output.
+ * @author readams
+ */
+@Target({ElementType.TYPE, ElementType.METHOD})
+public @interface LogMessageDoc {
+ public static final String NO_ACTION = "No action is required.";
+ public static final String UNKNOWN_ERROR = "An unknown error occured";
+ public static final String GENERIC_ACTION =
+ "Examine the returned error or exception and take " +
+ "appropriate action.";
+ public static final String CHECK_SWITCH =
+ "Check the health of the indicated switch. " +
+ "Test and troubleshoot IP connectivity.";
+ public static final String CHECK_CONTROLLER =
+ "Verify controller system health, CPU usage, and memory. " +
+ "Rebooting the controller node may help if the controller " +
+ "node is in a distressed state.";
+ public static final String REPORT_CONTROLLER_BUG =
+ "This is likely a defect in the controller. Please report this " +
+ "issue. Restarting the controller or switch may help to " +
+ "alleviate.";
+ public static final String REPORT_SWITCH_BUG =
+ "This is likely a defect in the switch. Please report this " +
+ "issue. Restarting the controller or switch may help to " +
+ "alleviate.";
+
+ /**
+ * The log level for the log message
+ * @return the log level as a tring
+ */
+ String level() default "INFO";
+ /**
+ * The message that will be printed
+ * @return the message
+ */
+ String message() default UNKNOWN_ERROR;
+ /**
+ * An explanation of the meaning of the log message
+ * @return the explanation
+ */
+ String explanation() default UNKNOWN_ERROR;
+ /**
+ * The recommendated action associated with the log message
+ * @return the recommendation
+ */
+ String recommendation() default NO_ACTION;
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/annotations/LogMessageDocs.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/annotations/LogMessageDocs.java
new file mode 100644
index 0000000..663baf0
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/annotations/LogMessageDocs.java
@@ -0,0 +1,36 @@
+/**
+* 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.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation used to document log messages. This can be used to generate
+ * documentation on syslog output. This version allows multiple log messages
+ * to be documentated on an interface.
+ * @author readams
+ */
+@Target({ElementType.TYPE, ElementType.METHOD})
+public @interface LogMessageDocs {
+ /**
+ * A list of {@link LogMessageDoc} elements
+ * @return the list of log message doc
+ */
+ LogMessageDoc[] value();
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/CmdLineSettings.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/CmdLineSettings.java
new file mode 100644
index 0000000..7641a7c
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/CmdLineSettings.java
@@ -0,0 +1,17 @@
+package net.floodlightcontroller.core.internal;
+
+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";
+
+ @Option(name="-cf", aliases="--configFile", metaVar="FILE", usage="Floodlight configuration file")
+ private String configFile = DEFAULT_CONFIG_FILE;
+
+ public String getModuleFile() {
+ return configFile;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/Controller.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/Controller.java
new file mode 100644
index 0000000..90eff6f
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/Controller.java
@@ -0,0 +1,2239 @@
+/**
+* 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.io.FileInputStream;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.util.ArrayList;
+import java.nio.channels.ClosedChannelException;
+import java.util.Collection;
+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.Future;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import net.floodlightcontroller.core.FloodlightContext;
+import net.floodlightcontroller.core.IFloodlightProviderService;
+import net.floodlightcontroller.core.IHAListener;
+import net.floodlightcontroller.core.IInfoProvider;
+import net.floodlightcontroller.core.IOFMessageListener;
+import net.floodlightcontroller.core.IListener.Command;
+import net.floodlightcontroller.core.IOFSwitch;
+import net.floodlightcontroller.core.IOFSwitchFilter;
+import net.floodlightcontroller.core.IOFSwitchListener;
+import net.floodlightcontroller.core.annotations.LogMessageDoc;
+import net.floodlightcontroller.core.annotations.LogMessageDocs;
+import net.floodlightcontroller.core.internal.OFChannelState.HandshakeState;
+import net.floodlightcontroller.core.util.ListenerDispatcher;
+import net.floodlightcontroller.core.web.CoreWebRoutable;
+import net.floodlightcontroller.counter.ICounterStoreService;
+import net.floodlightcontroller.packet.Ethernet;
+import net.floodlightcontroller.perfmon.IPktInProcessingTimeService;
+import net.floodlightcontroller.restserver.IRestApiService;
+import net.floodlightcontroller.storage.IResultSet;
+import net.floodlightcontroller.storage.IStorageSourceListener;
+import net.floodlightcontroller.storage.IStorageSourceService;
+import net.floodlightcontroller.storage.OperatorPredicate;
+import net.floodlightcontroller.storage.StorageException;
+import net.floodlightcontroller.threadpool.IThreadPoolService;
+
+import org.jboss.netty.bootstrap.ServerBootstrap;
+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.channel.ChannelPipelineFactory;
+import org.jboss.netty.channel.ChannelStateEvent;
+import org.jboss.netty.channel.ChannelUpstreamHandler;
+import org.jboss.netty.channel.Channels;
+import org.jboss.netty.channel.ExceptionEvent;
+import org.jboss.netty.channel.MessageEvent;
+import org.jboss.netty.channel.group.ChannelGroup;
+import org.jboss.netty.channel.group.DefaultChannelGroup;
+import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
+import org.jboss.netty.handler.timeout.IdleStateAwareChannelUpstreamHandler;
+import org.jboss.netty.handler.timeout.IdleStateEvent;
+import org.jboss.netty.handler.timeout.ReadTimeoutException;
+import org.openflow.protocol.OFEchoReply;
+import org.openflow.protocol.OFError;
+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.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.OFStatisticsRequest;
+import org.openflow.protocol.OFSwitchConfig;
+import org.openflow.protocol.OFType;
+import org.openflow.protocol.OFVendor;
+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.protocol.vendor.OFBasicVendorDataType;
+import org.openflow.protocol.vendor.OFBasicVendorId;
+import org.openflow.protocol.vendor.OFVendorId;
+import org.openflow.util.HexString;
+import org.openflow.util.U16;
+import org.openflow.util.U32;
+import org.openflow.vendor.nicira.OFNiciraVendorData;
+import org.openflow.vendor.nicira.OFRoleReplyVendorData;
+import org.openflow.vendor.nicira.OFRoleRequestVendorData;
+import org.openflow.vendor.nicira.OFRoleVendorData;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * The main controller class. Handles all setup and network listeners
+ */
+public class Controller implements IFloodlightProviderService,
+ IStorageSourceListener {
+
+ protected static Logger log = LoggerFactory.getLogger(Controller.class);
+
+ private static final String ERROR_DATABASE =
+ "The controller could not communicate with the system database.";
+
+ protected BasicFactory factory;
+ protected ConcurrentMap<OFType,
+ ListenerDispatcher<OFType,IOFMessageListener>>
+ messageListeners;
+ // The activeSwitches map contains only those switches that are actively
+ // being controlled by us -- it doesn't contain switches that are
+ // in the slave role
+ protected ConcurrentHashMap<Long, IOFSwitch> activeSwitches;
+ // connectedSwitches contains all connected switches, including ones where
+ // we're a slave controller. We need to keep track of them so that we can
+ // send role request messages to switches when our role changes to master
+ // We add a switch to this set after it successfully completes the
+ // handshake. Access to this Set needs to be synchronized with roleChanger
+ protected HashSet<OFSwitchImpl> connectedSwitches;
+
+ // 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 Set<IHAListener> haListeners;
+ protected Map<String, List<IInfoProvider>> providerMap;
+ protected BlockingQueue<IUpdate> updates;
+
+ // Module dependencies
+ protected IRestApiService restApi;
+ protected ICounterStoreService counterStore = null;
+ protected IStorageSourceService storageSource;
+ protected IPktInProcessingTimeService pktinProcTime;
+ protected IThreadPoolService threadPool;
+
+ // Configuration options
+ protected int openFlowPort = 6633;
+ protected int workerThreads = 0;
+ // The id for this controller node. Should be unique for each controller
+ // node in a controller cluster.
+ protected String controllerId = "localhost";
+
+ // The current role of the controller.
+ // If the controller isn't configured to support roles, then this is null.
+ protected Role role;
+ // A helper that handles sending and timeout handling for role requests
+ protected RoleChanger roleChanger;
+
+ // Start time of the controller
+ protected long systemStartTime;
+
+ // Flag to always flush flow table on switch reconnect (HA or otherwise)
+ protected boolean alwaysClearFlowsOnSwAdd = false;
+
+ // Storage table names
+ protected static final String CONTROLLER_TABLE_NAME = "controller_controller";
+ protected static final String CONTROLLER_ID = "id";
+
+ protected static final String SWITCH_TABLE_NAME = "controller_switch";
+ protected static final String SWITCH_DATAPATH_ID = "dpid";
+ protected static final String SWITCH_SOCKET_ADDRESS = "socket_address";
+ protected static final String SWITCH_IP = "ip";
+ protected static final String SWITCH_CONTROLLER_ID = "controller_id";
+ protected static final String SWITCH_ACTIVE = "active";
+ protected static final String SWITCH_CONNECTED_SINCE = "connected_since";
+ protected static final String SWITCH_CAPABILITIES = "capabilities";
+ protected static final String SWITCH_BUFFERS = "buffers";
+ protected static final String SWITCH_TABLES = "tables";
+ protected static final String SWITCH_ACTIONS = "actions";
+
+ protected static final String SWITCH_CONFIG_TABLE_NAME = "controller_switchconfig";
+ protected static final String SWITCH_CONFIG_CORE_SWITCH = "core_switch";
+
+ protected static final String PORT_TABLE_NAME = "controller_port";
+ protected static final String PORT_ID = "id";
+ protected static final String PORT_SWITCH = "switch_id";
+ protected static final String PORT_NUMBER = "number";
+ protected static final String PORT_HARDWARE_ADDRESS = "hardware_address";
+ protected static final String PORT_NAME = "name";
+ protected static final String PORT_CONFIG = "config";
+ protected static final String PORT_STATE = "state";
+ protected static final String PORT_CURRENT_FEATURES = "current_features";
+ protected static final String PORT_ADVERTISED_FEATURES = "advertised_features";
+ protected static final String PORT_SUPPORTED_FEATURES = "supported_features";
+ protected static final String PORT_PEER_FEATURES = "peer_features";
+
+ protected static final String CONTROLLER_INTERFACE_TABLE_NAME = "controller_controllerinterface";
+ protected static final String CONTROLLER_INTERFACE_ID = "id";
+ protected static final String CONTROLLER_INTERFACE_CONTROLLER_ID = "controller_id";
+ protected static final String CONTROLLER_INTERFACE_TYPE = "type";
+ protected static final String CONTROLLER_INTERFACE_NUMBER = "number";
+ protected static final String CONTROLLER_INTERFACE_DISCOVERED_IP = "discovered_ip";
+
+
+
+ // Perf. related configuration
+ protected static final int SEND_BUFFER_SIZE = 4 * 1024 * 1024;
+ protected static final int BATCH_MAX_SIZE = 100;
+ protected static final boolean ALWAYS_DECODE_ETH = true;
+
+ /**
+ * Updates handled by the main loop
+ */
+ protected interface IUpdate {
+ /**
+ * Calls the appropriate listeners
+ */
+ public void dispatch();
+ }
+ public enum SwitchUpdateType {
+ ADDED,
+ REMOVED,
+ PORTCHANGED
+ }
+ /**
+ * Update message indicating a switch was added or removed
+ */
+ protected class SwitchUpdate implements IUpdate {
+ public IOFSwitch sw;
+ public SwitchUpdateType switchUpdateType;
+ public SwitchUpdate(IOFSwitch sw, SwitchUpdateType switchUpdateType) {
+ this.sw = sw;
+ this.switchUpdateType = switchUpdateType;
+ }
+ public void dispatch() {
+ if (log.isTraceEnabled()) {
+ log.trace("Dispatching switch update {} {}",
+ sw, switchUpdateType);
+ }
+ if (switchListeners != null) {
+ for (IOFSwitchListener listener : switchListeners) {
+ switch(switchUpdateType) {
+ case ADDED:
+ listener.addedSwitch(sw);
+ break;
+ case REMOVED:
+ listener.removedSwitch(sw);
+ break;
+ case PORTCHANGED:
+ listener.switchPortChanged(sw.getId());
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Update message indicating controller's role has changed
+ */
+ protected class HARoleUpdate implements IUpdate {
+ public Role oldRole;
+ public Role newRole;
+ public HARoleUpdate(Role newRole, Role oldRole) {
+ this.oldRole = oldRole;
+ this.newRole = newRole;
+ }
+ public void dispatch() {
+ // Make sure that old and new roles are different.
+ if (oldRole == newRole) {
+ if (log.isTraceEnabled()) {
+ log.trace("HA role update ignored as the old and " +
+ "new roles are the same. newRole = {}" +
+ "oldRole = {}", newRole, oldRole);
+ }
+ return;
+ }
+ if (log.isTraceEnabled()) {
+ log.trace("Dispatching HA Role update newRole = {}, oldRole = {}",
+ newRole, oldRole);
+ }
+ if (haListeners != null) {
+ for (IHAListener listener : haListeners) {
+ listener.roleChanged(oldRole, newRole);
+ }
+ }
+ }
+ }
+
+ /**
+ * Update message indicating
+ * IPs of controllers in controller cluster have changed.
+ */
+ protected class HAControllerNodeIPUpdate implements IUpdate {
+ public Map<String,String> curControllerNodeIPs;
+ public Map<String,String> addedControllerNodeIPs;
+ public Map<String,String> removedControllerNodeIPs;
+ public HAControllerNodeIPUpdate(
+ HashMap<String,String> curControllerNodeIPs,
+ HashMap<String,String> addedControllerNodeIPs,
+ HashMap<String,String> removedControllerNodeIPs) {
+ this.curControllerNodeIPs = curControllerNodeIPs;
+ this.addedControllerNodeIPs = addedControllerNodeIPs;
+ this.removedControllerNodeIPs = removedControllerNodeIPs;
+ }
+ public void dispatch() {
+ if (log.isTraceEnabled()) {
+ log.trace("Dispatching HA Controller Node IP update "
+ + "curIPs = {}, addedIPs = {}, removedIPs = {}",
+ new Object[] { curControllerNodeIPs, addedControllerNodeIPs,
+ removedControllerNodeIPs }
+ );
+ }
+ if (haListeners != null) {
+ for (IHAListener listener: haListeners) {
+ listener.controllerNodeIPsChanged(curControllerNodeIPs,
+ addedControllerNodeIPs, removedControllerNodeIPs);
+ }
+ }
+ }
+ }
+
+ // ***************
+ // Getters/Setters
+ // ***************
+
+ public void setStorageSourceService(IStorageSourceService storageSource) {
+ this.storageSource = storageSource;
+ }
+
+ public void setCounterStore(ICounterStoreService counterStore) {
+ this.counterStore = counterStore;
+ }
+
+ public void setPktInProcessingService(IPktInProcessingTimeService pits) {
+ this.pktinProcTime = pits;
+ }
+
+ public void setRestApiService(IRestApiService restApi) {
+ this.restApi = restApi;
+ }
+
+ public void setThreadPoolService(IThreadPoolService tp) {
+ this.threadPool = tp;
+ }
+
+ @Override
+ public Role getRole() {
+ synchronized(roleChanger) {
+ return role;
+ }
+ }
+
+ @Override
+ public void setRole(Role role) {
+ if (role == null) throw new NullPointerException("Role can not be null.");
+ if (role == Role.MASTER && this.role == Role.SLAVE) {
+ // Reset db state to Inactive for all switches.
+ updateAllInactiveSwitchInfo();
+ }
+
+ // Need to synchronize to ensure a reliable ordering on role request
+ // messages send and to ensure the list of connected switches is stable
+ // RoleChanger will handle the actual sending of the message and
+ // timeout handling
+ // @see RoleChanger
+ synchronized(roleChanger) {
+ if (role.equals(this.role)) {
+ log.debug("Ignoring role change: role is already {}", role);
+ return;
+ }
+
+ Role oldRole = this.role;
+ this.role = role;
+
+ log.debug("Submitting role change request to role {}", role);
+ roleChanger.submitRequest(connectedSwitches, role);
+
+ // Enqueue an update for our listeners.
+ try {
+ this.updates.put(new HARoleUpdate(role, oldRole));
+ } catch (InterruptedException e) {
+ log.error("Failure adding update to queue", e);
+ }
+ }
+ }
+
+
+
+ // **********************
+ // ChannelUpstreamHandler
+ // **********************
+
+ /**
+ * Return a new channel handler for processing a switch connections
+ * @param state The channel state object for the connection
+ * @return the new channel handler
+ */
+ protected ChannelUpstreamHandler getChannelHandler(OFChannelState state) {
+ return new OFChannelHandler(state);
+ }
+
+ /**
+ * Channel handler deals with the switch connection and dispatches
+ * switch messages to the appropriate locations.
+ * @author readams
+ */
+ protected class OFChannelHandler
+ extends IdleStateAwareChannelUpstreamHandler {
+ protected OFSwitchImpl sw;
+ protected OFChannelState state;
+
+ public OFChannelHandler(OFChannelState state) {
+ this.state = state;
+ }
+
+ @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.info("New switch connection from {}",
+ e.getChannel().getRemoteAddress());
+
+ sw = new OFSwitchImpl();
+ sw.setChannel(e.getChannel());
+ sw.setFloodlightProvider(Controller.this);
+ sw.setThreadPoolService(threadPool);
+
+ List<OFMessage> msglist = new ArrayList<OFMessage>(1);
+ msglist.add(factory.getMessage(OFType.HELLO));
+ e.getChannel().write(msglist);
+
+ }
+
+ @Override
+ @LogMessageDoc(message="Disconnected switch {switch information}",
+ explanation="The specified switch has disconnected.")
+ public void channelDisconnected(ChannelHandlerContext ctx,
+ ChannelStateEvent e) throws Exception {
+ if (sw != null && state.hsState == HandshakeState.READY) {
+ if (activeSwitches.containsKey(sw.getId())) {
+ // It's safe to call removeSwitch even though the map might
+ // not contain this particular switch but another with the
+ // same DPID
+ removeSwitch(sw);
+ }
+ synchronized(roleChanger) {
+ connectedSwitches.remove(sw);
+ }
+ sw.setConnected(false);
+ }
+ log.info("Disconnected switch {}", sw);
+ }
+
+ @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=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", sw);
+ ctx.getChannel().close();
+ } else if (e.getCause() instanceof HandshakeTimeoutException) {
+ log.error("Disconnecting switch {}: failed to complete handshake",
+ sw);
+ ctx.getChannel().close();
+ } else if (e.getCause() instanceof ClosedChannelException) {
+ //log.warn("Channel for sw {} already closed", sw);
+ } else if (e.getCause() instanceof IOException) {
+ log.error("Disconnecting switch {} due to IO Error: {}",
+ sw, e.getCause().getMessage());
+ ctx.getChannel().close();
+ } else if (e.getCause() instanceof SwitchStateException) {
+ log.error("Disconnecting switch {} due to switch state error: {}",
+ sw, e.getCause().getMessage());
+ ctx.getChannel().close();
+ } else if (e.getCause() instanceof MessageParseException) {
+ log.error("Disconnecting switch " + sw +
+ " due to message parse failure",
+ e.getCause());
+ ctx.getChannel().close();
+ } else if (e.getCause() instanceof StorageException) {
+ log.error("Terminating controller due to storage exception",
+ e.getCause());
+ terminate();
+ } else if (e.getCause() instanceof RejectedExecutionException) {
+ log.warn("Could not process message: queue full");
+ } else {
+ log.error("Error while processing message from switch " + sw,
+ e.getCause());
+ ctx.getChannel().close();
+ }
+ }
+
+ @Override
+ public void channelIdle(ChannelHandlerContext ctx, IdleStateEvent e)
+ throws Exception {
+ List<OFMessage> msglist = new ArrayList<OFMessage>(1);
+ msglist.add(factory.getMessage(OFType.ECHO_REQUEST));
+ e.getChannel().write(msglist);
+ }
+
+ @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 {
+ 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);
+ }
+ }
+
+ // Flush all flow-mods/packet-out generated from this "train"
+ OFSwitchImpl.flush_all();
+ }
+ }
+
+ /**
+ * Process the request for the switch description
+ */
+ @LogMessageDoc(level="ERROR",
+ message="Exception in reading description " +
+ " during handshake {exception}",
+ explanation="Could not process the switch description string",
+ recommendation=LogMessageDoc.CHECK_SWITCH)
+ void processSwitchDescReply() {
+ try {
+ // Read description, if it has been updated
+ @SuppressWarnings("unchecked")
+ Future<List<OFStatistics>> desc_future =
+ (Future<List<OFStatistics>>)sw.
+ getAttribute(IOFSwitch.SWITCH_DESCRIPTION_FUTURE);
+ List<OFStatistics> values =
+ desc_future.get(0, TimeUnit.MILLISECONDS);
+ if (values != null) {
+ OFDescriptionStatistics description =
+ new OFDescriptionStatistics();
+ ChannelBuffer data =
+ ChannelBuffers.buffer(description.getLength());
+ for (OFStatistics f : values) {
+ f.writeTo(data);
+ description.readFrom(data);
+ break; // SHOULD be a list of length 1
+ }
+ sw.setAttribute(IOFSwitch.SWITCH_DESCRIPTION_DATA,
+ description);
+ sw.setSwitchProperties(description);
+ data = null;
+
+ // At this time, also set other switch properties from storage
+ boolean is_core_switch = false;
+ IResultSet resultSet = null;
+ try {
+ String swid = sw.getStringId();
+ resultSet =
+ storageSource.getRow(SWITCH_CONFIG_TABLE_NAME, swid);
+ 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();
+ if (row.containsKey(SWITCH_CONFIG_CORE_SWITCH)) {
+ if (log.isDebugEnabled()) {
+ log.debug("Reading SWITCH_IS_CORE_SWITCH " +
+ "config for switch={}, is-core={}",
+ sw, row.get(SWITCH_CONFIG_CORE_SWITCH));
+ }
+ String ics =
+ (String)row.get(SWITCH_CONFIG_CORE_SWITCH);
+ is_core_switch = ics.equals("true");
+ }
+ }
+ }
+ finally {
+ if (resultSet != null)
+ resultSet.close();
+ }
+ if (is_core_switch) {
+ sw.setAttribute(IOFSwitch.SWITCH_IS_CORE_SWITCH,
+ new Boolean(true));
+ }
+ }
+ sw.removeAttribute(IOFSwitch.SWITCH_DESCRIPTION_FUTURE);
+ state.hasDescription = true;
+ checkSwitchReady();
+ }
+ catch (InterruptedException ex) {
+ // Ignore
+ }
+ catch (TimeoutException ex) {
+ // Ignore
+ } catch (Exception ex) {
+ log.error("Exception in reading description " +
+ " during handshake", ex);
+ }
+ }
+
+ /**
+ * Send initial switch setup information that we need before adding
+ * the switch
+ * @throws IOException
+ */
+ void sendHelloConfiguration() throws IOException {
+ // Send initial Features Request
+ sw.write(factory.getMessage(OFType.FEATURES_REQUEST), null);
+ }
+
+ /**
+ * Send the configuration requests we can only do after we have
+ * the features reply
+ * @throws IOException
+ */
+ void sendFeatureReplyConfiguration() throws IOException {
+ // Ensure we receive the full packet via PacketIn
+ OFSetConfig config = (OFSetConfig) factory
+ .getMessage(OFType.SET_CONFIG);
+ config.setMissSendLength((short) 0xffff)
+ .setLengthU(OFSwitchConfig.MINIMUM_LENGTH);
+ sw.write(config, null);
+ sw.write(factory.getMessage(OFType.GET_CONFIG_REQUEST),
+ null);
+
+ // Get Description to set switch-specific flags
+ OFStatisticsRequest req = new OFStatisticsRequest();
+ req.setStatisticType(OFStatisticsType.DESC);
+ req.setLengthU(req.getLengthU());
+ Future<List<OFStatistics>> dfuture =
+ sw.getStatistics(req);
+ sw.setAttribute(IOFSwitch.SWITCH_DESCRIPTION_FUTURE,
+ dfuture);
+
+ }
+
+ protected void checkSwitchReady() {
+ if (state.hsState == HandshakeState.FEATURES_REPLY &&
+ state.hasDescription && state.hasGetConfigReply) {
+
+ state.hsState = HandshakeState.READY;
+
+ synchronized(roleChanger) {
+ // We need to keep track of all of the switches that are connected
+ // to the controller, in any role, so that we can later send the
+ // role request messages when the controller role changes.
+ // We need to be synchronized while doing this: we must not
+ // send a another role request to the connectedSwitches until
+ // we were able to add this new switch to connectedSwitches
+ // *and* send the current role to the new switch.
+ connectedSwitches.add(sw);
+
+ if (role != null) {
+ // Send a role request if role support is enabled for the controller
+ // This is a probe that we'll use to determine if the switch
+ // actually supports the role request message. If it does we'll
+ // get back a role reply message. If it doesn't we'll get back an
+ // OFError message.
+ // If role is MASTER we will promote switch to active
+ // list when we receive the switch's role reply messages
+ log.debug("This controller's role is {}, " +
+ "sending initial role request msg to {}",
+ role, sw);
+ Collection<OFSwitchImpl> swList = new ArrayList<OFSwitchImpl>(1);
+ swList.add(sw);
+ roleChanger.submitRequest(swList, role);
+ }
+ else {
+ // Role supported not enabled on controller (for now)
+ // automatically promote switch to active state.
+ log.debug("This controller's role is null, " +
+ "not sending role request msg to {}",
+ role, sw);
+ // Need to clear FlowMods before we add the switch
+ // and dispatch updates otherwise we have a race condition.
+ sw.clearAllFlowMods();
+ addSwitch(sw);
+ state.firstRoleReplyReceived = true;
+ }
+ }
+ }
+ }
+
+ /* Handle a role reply message we received from the switch. Since
+ * netty serializes message dispatch we don't need to synchronize
+ * against other receive operations from the same switch, so no need
+ * to synchronize addSwitch(), removeSwitch() operations from the same
+ * connection.
+ * FIXME: However, when a switch with the same DPID connects we do
+ * need some synchronization. However, handling switches with same
+ * DPID needs to be revisited anyways (get rid of r/w-lock and synchronous
+ * removedSwitch notification):1
+ *
+ */
+ @LogMessageDoc(level="ERROR",
+ message="Invalid role value in role reply message",
+ explanation="Was unable to set the HA role (master or slave) " +
+ "for the controller.",
+ recommendation=LogMessageDoc.CHECK_CONTROLLER)
+ protected void handleRoleReplyMessage(OFVendor vendorMessage,
+ OFRoleReplyVendorData roleReplyVendorData) {
+ // Map from the role code in the message to our role enum
+ int nxRole = roleReplyVendorData.getRole();
+ Role role = null;
+ switch (nxRole) {
+ case OFRoleVendorData.NX_ROLE_OTHER:
+ role = Role.EQUAL;
+ break;
+ case OFRoleVendorData.NX_ROLE_MASTER:
+ role = Role.MASTER;
+ break;
+ case OFRoleVendorData.NX_ROLE_SLAVE:
+ role = Role.SLAVE;
+ break;
+ default:
+ log.error("Invalid role value in role reply message");
+ sw.getChannel().close();
+ return;
+ }
+
+ log.debug("Handling role reply for role {} from {}. " +
+ "Controller's role is {} ",
+ new Object[] { role, sw, Controller.this.role}
+ );
+
+ sw.deliverRoleReply(vendorMessage.getXid(), role);
+
+ boolean isActive = activeSwitches.containsKey(sw.getId());
+ if (!isActive && sw.isActive()) {
+ // Transition from SLAVE to MASTER.
+
+ if (!state.firstRoleReplyReceived ||
+ getAlwaysClearFlowsOnSwAdd()) {
+ // This is the first role-reply message we receive from
+ // this switch or roles were disabled when the switch
+ // connected:
+ // Delete all pre-existing flows for new connections to
+ // the master
+ //
+ // FIXME: Need to think more about what the test should
+ // be for when we flush the flow-table? For example,
+ // if all the controllers are temporarily in the backup
+ // role (e.g. right after a failure of the master
+ // controller) at the point the switch connects, then
+ // all of the controllers will initially connect as
+ // backup controllers and not flush the flow-table.
+ // Then when one of them is promoted to master following
+ // the master controller election the flow-table
+ // will still not be flushed because that's treated as
+ // a failover event where we don't want to flush the
+ // flow-table. The end result would be that the flow
+ // table for a newly connected switch is never
+ // flushed. Not sure how to handle that case though...
+ sw.clearAllFlowMods();
+ log.debug("First role reply from master switch {}, " +
+ "clear FlowTable to active switch list",
+ HexString.toHexString(sw.getId()));
+ }
+
+ // Some switches don't seem to update us with port
+ // status messages while in slave role.
+ readSwitchPortStateFromStorage(sw);
+
+ // Only add the switch to the active switch list if
+ // we're not in the slave role. Note that if the role
+ // attribute is null, then that means that the switch
+ // doesn't support the role request messages, so in that
+ // case we're effectively in the EQUAL role and the
+ // switch should be included in the active switch list.
+ addSwitch(sw);
+ log.debug("Added master switch {} to active switch list",
+ HexString.toHexString(sw.getId()));
+
+ }
+ else if (isActive && !sw.isActive()) {
+ // Transition from MASTER to SLAVE: remove switch
+ // from active switch list.
+ log.debug("Removed slave switch {} from active switch" +
+ " list", HexString.toHexString(sw.getId()));
+ removeSwitch(sw);
+ }
+
+ // Indicate that we have received a role reply message.
+ state.firstRoleReplyReceived = true;
+ }
+
+ protected boolean handleVendorMessage(OFVendor vendorMessage) {
+ boolean shouldHandleMessage = false;
+ int vendor = vendorMessage.getVendor();
+ switch (vendor) {
+ case OFNiciraVendorData.NX_VENDOR_ID:
+ OFNiciraVendorData niciraVendorData =
+ (OFNiciraVendorData)vendorMessage.getVendorData();
+ int dataType = niciraVendorData.getDataType();
+ switch (dataType) {
+ case OFRoleReplyVendorData.NXT_ROLE_REPLY:
+ OFRoleReplyVendorData roleReplyVendorData =
+ (OFRoleReplyVendorData) niciraVendorData;
+ handleRoleReplyMessage(vendorMessage,
+ roleReplyVendorData);
+ break;
+ default:
+ log.warn("Unhandled Nicira VENDOR message; " +
+ "data type = {}", dataType);
+ break;
+ }
+ break;
+ default:
+ log.warn("Unhandled VENDOR message; vendor id = {}", vendor);
+ break;
+ }
+
+ return shouldHandleMessage;
+ }
+
+ /**
+ * Dispatch an Openflow message from a switch to the appropriate
+ * handler.
+ * @param m The message to process
+ * @throws IOException
+ * @throws SwitchStateException
+ */
+ @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"),
+ @LogMessageDoc(level="WARN",
+ message="Received ERROR from sw {switch} that "
+ +"indicates roles are not supported "
+ +"but we have received a valid "
+ +"role reply earlier",
+ explanation="The switch sent a confusing message to the" +
+ "controller")
+ })
+ protected void processOFMessage(OFMessage m)
+ throws IOException, SwitchStateException {
+ boolean shouldHandleMessage = false;
+
+ switch (m.getType()) {
+ case HELLO:
+ if (log.isTraceEnabled())
+ log.trace("HELLO from {}", sw);
+
+ if (state.hsState.equals(HandshakeState.START)) {
+ state.hsState = HandshakeState.HELLO;
+ sendHelloConfiguration();
+ } else {
+ throw new SwitchStateException("Unexpected HELLO from "
+ + sw);
+ }
+ break;
+ case ECHO_REQUEST:
+ OFEchoReply reply =
+ (OFEchoReply) factory.getMessage(OFType.ECHO_REPLY);
+ reply.setXid(m.getXid());
+ sw.write(reply, null);
+ break;
+ case ECHO_REPLY:
+ break;
+ case FEATURES_REPLY:
+ if (log.isTraceEnabled())
+ log.trace("Features Reply from {}", sw);
+
+ sw.setFeaturesReply((OFFeaturesReply) m);
+ if (state.hsState.equals(HandshakeState.HELLO)) {
+ sendFeatureReplyConfiguration();
+ state.hsState = HandshakeState.FEATURES_REPLY;
+ // uncomment to enable "dumb" switches like cbench
+ // state.hsState = HandshakeState.READY;
+ // addSwitch(sw);
+ } else {
+ // return results to rest api caller
+ sw.deliverOFFeaturesReply(m);
+ // update database */
+ updateActiveSwitchInfo(sw);
+ }
+ break;
+ case GET_CONFIG_REPLY:
+ if (log.isTraceEnabled())
+ log.trace("Get config reply from {}", sw);
+
+ if (!state.hsState.equals(HandshakeState.FEATURES_REPLY)) {
+ String em = "Unexpected GET_CONFIG_REPLY from " + sw;
+ throw new SwitchStateException(em);
+ }
+ OFGetConfigReply cr = (OFGetConfigReply) m;
+ if (cr.getMissSendLength() == (short)0xffff) {
+ log.trace("Config Reply from {} confirms " +
+ "miss length set to 0xffff", sw);
+ } else {
+ log.warn("Config Reply from {} has " +
+ "miss length set to {}",
+ sw, cr.getMissSendLength() & 0xffff);
+ }
+ state.hasGetConfigReply = true;
+ checkSwitchReady();
+ break;
+ case VENDOR:
+ shouldHandleMessage = handleVendorMessage((OFVendor)m);
+ break;
+ case ERROR:
+ // TODO: we need better error handling. Especially for
+ // request/reply style message (stats, roles) we should have
+ // a unified way to lookup the xid in the error message.
+ // This will probable involve rewriting the way we handle
+ // request/reply style messages.
+ OFError error = (OFError) m;
+ boolean shouldLogError = true;
+ // TODO: should we check that firstRoleReplyReceived is false,
+ // i.e., check only whether the first request fails?
+ if (sw.checkFirstPendingRoleRequestXid(error.getXid())) {
+ boolean isBadVendorError =
+ (error.getErrorType() == OFError.OFErrorType.
+ OFPET_BAD_REQUEST.getValue());
+ // We expect to receive a bad vendor error when
+ // we're connected to a switch that doesn't support
+ // the Nicira vendor extensions (i.e. not OVS or
+ // derived from OVS). By protocol, it should also be
+ // BAD_VENDOR, but too many switch implementations
+ // get it wrong and we can already check the xid()
+ // so we can ignore the type with confidence that this
+ // is not a spurious error
+ shouldLogError = !isBadVendorError;
+ if (isBadVendorError) {
+ if (state.firstRoleReplyReceived && (role != null)) {
+ log.warn("Received ERROR from sw {} that "
+ +"indicates roles are not supported "
+ +"but we have received a valid "
+ +"role reply earlier", sw);
+ }
+ state.firstRoleReplyReceived = true;
+ sw.deliverRoleRequestNotSupported(error.getXid());
+ synchronized(roleChanger) {
+ if (sw.role == null && Controller.this.role==Role.SLAVE) {
+ // the switch doesn't understand role request
+ // messages and the current controller role is
+ // slave. We need to disconnect the switch.
+ // @see RoleChanger for rationale
+ sw.getChannel().close();
+ }
+ else if (sw.role == null) {
+ // Controller's role is master: add to
+ // active
+ // TODO: check if clearing flow table is
+ // right choice here.
+ // Need to clear FlowMods before we add the switch
+ // and dispatch updates otherwise we have a race condition.
+ // TODO: switch update is async. Won't we still have a potential
+ // race condition?
+ sw.clearAllFlowMods();
+ addSwitch(sw);
+ }
+ }
+ }
+ else {
+ // TODO: Is this the right thing to do if we receive
+ // some other error besides a bad vendor 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.
+ sw.getChannel().close();
+ }
+ }
+ // Once we support OF 1.2, we'd add code to handle it here.
+ //if (error.getXid() == state.ofRoleRequestXid) {
+ //}
+ if (shouldLogError)
+ logError(sw, error);
+ break;
+ case STATS_REPLY:
+ if (state.hsState.ordinal() <
+ HandshakeState.FEATURES_REPLY.ordinal()) {
+ String em = "Unexpected STATS_REPLY from " + sw;
+ throw new SwitchStateException(em);
+ }
+ sw.deliverStatisticsReply(m);
+ if (sw.hasAttribute(IOFSwitch.SWITCH_DESCRIPTION_FUTURE)) {
+ processSwitchDescReply();
+ }
+ break;
+ case PORT_STATUS:
+ // We want to update our port state info even if we're in
+ // the slave role, but we only want to update storage if
+ // we're the master (or equal).
+ boolean updateStorage = state.hsState.
+ equals(HandshakeState.READY) &&
+ (sw.getRole() != Role.SLAVE);
+ handlePortStatusMessage(sw, (OFPortStatus)m, updateStorage);
+ shouldHandleMessage = true;
+ break;
+
+ default:
+ shouldHandleMessage = true;
+ break;
+ }
+
+ if (shouldHandleMessage) {
+ sw.getListenerReadLock().lock();
+ try {
+ if (sw.isConnected()) {
+ if (!state.hsState.equals(HandshakeState.READY)) {
+ log.debug("Ignoring message type {} received " +
+ "from switch {} before switch is " +
+ "fully configured.", m.getType(), sw);
+ }
+ // Check if the controller is in the slave role for the
+ // switch. If it is, then don't dispatch the message to
+ // the listeners.
+ // TODO: Should we dispatch messages that we expect to
+ // receive when we're in the slave role, e.g. port
+ // status messages? Since we're "hiding" switches from
+ // the listeners when we're in the slave role, then it
+ // seems a little weird to dispatch port status messages
+ // to them. On the other hand there might be special
+ // modules that care about all of the connected switches
+ // and would like to receive port status notifications.
+ else if (sw.getRole() == Role.SLAVE) {
+ // Don't log message if it's a port status message
+ // since we expect to receive those from the switch
+ // and don't want to emit spurious messages.
+ if (m.getType() != OFType.PORT_STATUS) {
+ log.debug("Ignoring message type {} received " +
+ "from switch {} while in the slave role.",
+ m.getType(), sw);
+ }
+ } else {
+ handleMessage(sw, m, null);
+ }
+ }
+ }
+ finally {
+ sw.getListenerReadLock().unlock();
+ }
+ }
+ }
+ }
+
+ // ****************
+ // Message handlers
+ // ****************
+
+ protected void handlePortStatusMessage(IOFSwitch sw,
+ OFPortStatus m,
+ boolean updateStorage) {
+ short portNumber = m.getDesc().getPortNumber();
+ OFPhysicalPort port = m.getDesc();
+ if (m.getReason() == (byte)OFPortReason.OFPPR_MODIFY.ordinal()) {
+ sw.setPort(port);
+ if (updateStorage)
+ updatePortInfo(sw, port);
+ log.debug("Port #{} modified for {}", portNumber, sw);
+ } else if (m.getReason() == (byte)OFPortReason.OFPPR_ADD.ordinal()) {
+ sw.setPort(port);
+ if (updateStorage)
+ updatePortInfo(sw, port);
+ log.debug("Port #{} added for {}", portNumber, sw);
+ } else if (m.getReason() ==
+ (byte)OFPortReason.OFPPR_DELETE.ordinal()) {
+ sw.deletePort(portNumber);
+ if (updateStorage)
+ removePortInfo(sw, portNumber);
+ log.debug("Port #{} deleted for {}", portNumber, sw);
+ }
+ SwitchUpdate update = new SwitchUpdate(sw, SwitchUpdateType.PORTCHANGED);
+ try {
+ this.updates.put(update);
+ } catch (InterruptedException e) {
+ log.error("Failure adding update to queue", e);
+ }
+ }
+
+ /**
+ * flcontext_cache - Keep a thread local stack of contexts
+ */
+ protected static final ThreadLocal<Stack<FloodlightContext>> flcontext_cache =
+ new ThreadLocal <Stack<FloodlightContext>> () {
+ @Override
+ protected Stack<FloodlightContext> initialValue() {
+ return new Stack<FloodlightContext>();
+ }
+ };
+
+ /**
+ * flcontext_alloc - pop a context off the stack, if required create a new one
+ * @return FloodlightContext
+ */
+ protected static FloodlightContext flcontext_alloc() {
+ FloodlightContext flcontext = null;
+
+ if (flcontext_cache.get().empty()) {
+ flcontext = new FloodlightContext();
+ }
+ else {
+ flcontext = flcontext_cache.get().pop();
+ }
+
+ return flcontext;
+ }
+
+ /**
+ * flcontext_free - Free the context to the current thread
+ * @param flcontext
+ */
+ protected void flcontext_free(FloodlightContext flcontext) {
+ flcontext.getStorage().clear();
+ flcontext_cache.get().push(flcontext);
+ }
+
+ /**
+ * Handle replies to certain OFMessages, and pass others off to listeners
+ * @param sw The switch for the message
+ * @param m The message
+ * @param bContext The floodlight context. If null then floodlight context would
+ * be allocated in this function
+ * @throws IOException
+ */
+ @LogMessageDocs({
+ @LogMessageDoc(level="ERROR",
+ message="Ignoring PacketIn (Xid = {xid}) because the data" +
+ " field is empty.",
+ explanation="The switch sent an improperly-formatted PacketIn" +
+ " message",
+ recommendation=LogMessageDoc.CHECK_SWITCH),
+ @LogMessageDoc(level="WARN",
+ message="Unhandled OF Message: {} from {}",
+ explanation="The switch sent a message not handled by " +
+ "the controller")
+ })
+ protected void handleMessage(IOFSwitch sw, OFMessage m,
+ FloodlightContext bContext)
+ throws IOException {
+ Ethernet eth = null;
+
+ switch (m.getType()) {
+ case PACKET_IN:
+ OFPacketIn pi = (OFPacketIn)m;
+
+ if (pi.getPacketData().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.updatePacketInCounters(sw, m, eth);
+ }
+ // fall through to default case...
+
+ default:
+
+ List<IOFMessageListener> listeners = null;
+ if (messageListeners.containsKey(m.getType())) {
+ listeners = messageListeners.get(m.getType()).
+ getOrderedListeners();
+ }
+
+ FloodlightContext bc = null;
+ if (listeners != null) {
+ // Check if floodlight context is passed from the calling
+ // function, if so use that floodlight context, otherwise
+ // allocate one
+ if (bContext == null) {
+ bc = flcontext_alloc();
+ } else {
+ bc = bContext;
+ }
+ if (eth != null) {
+ IFloodlightProviderService.bcStore.put(bc,
+ IFloodlightProviderService.CONTEXT_PI_PAYLOAD,
+ eth);
+ }
+
+ // Get the starting time (overall and per-component) of
+ // the processing chain for this packet if performance
+ // monitoring is turned on
+ pktinProcTime.bootstrap(listeners);
+ pktinProcTime.recordStartTimePktIn();
+ Command cmd;
+ for (IOFMessageListener listener : listeners) {
+ if (listener instanceof IOFSwitchFilter) {
+ if (!((IOFSwitchFilter)listener).isInterested(sw)) {
+ continue;
+ }
+ }
+
+ pktinProcTime.recordStartTimeComp(listener);
+ cmd = listener.receive(sw, m, bc);
+ pktinProcTime.recordEndTimeComp(listener);
+
+ if (Command.STOP.equals(cmd)) {
+ break;
+ }
+ }
+ pktinProcTime.recordEndTimePktIn(sw, m, bc);
+ } else {
+ log.warn("Unhandled OF Message: {} from {}", m, sw);
+ }
+
+ if ((bContext == null) && (bc != null)) flcontext_free(bc);
+ }
+ }
+
+ /**
+ * 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}",
+ 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(IOFSwitch sw, OFError error) {
+ int etint = 0xffff & error.getErrorType();
+ if (etint < 0 || etint >= OFErrorType.values().length) {
+ log.error("Unknown error code {} from sw {}", etint, sw);
+ }
+ OFErrorType et = OFErrorType.values()[etint];
+ switch (et) {
+ case OFPET_HELLO_FAILED:
+ OFHelloFailedCode hfc =
+ OFHelloFailedCode.values()[0xffff & error.getErrorCode()];
+ log.error("Error {} {} from {}", new Object[] {et, hfc, sw});
+ break;
+ case OFPET_BAD_REQUEST:
+ OFBadRequestCode brc =
+ OFBadRequestCode.values()[0xffff & error.getErrorCode()];
+ log.error("Error {} {} from {}", new Object[] {et, brc, sw});
+ break;
+ case OFPET_BAD_ACTION:
+ OFBadActionCode bac =
+ OFBadActionCode.values()[0xffff & error.getErrorCode()];
+ log.error("Error {} {} from {}", new Object[] {et, bac, sw});
+ break;
+ case OFPET_FLOW_MOD_FAILED:
+ OFFlowModFailedCode fmfc =
+ OFFlowModFailedCode.values()[0xffff & error.getErrorCode()];
+ log.error("Error {} {} from {}", new Object[] {et, fmfc, sw});
+ break;
+ case OFPET_PORT_MOD_FAILED:
+ OFPortModFailedCode pmfc =
+ OFPortModFailedCode.values()[0xffff & error.getErrorCode()];
+ log.error("Error {} {} from {}", new Object[] {et, pmfc, sw});
+ break;
+ case OFPET_QUEUE_OP_FAILED:
+ OFQueueOpFailedCode qofc =
+ OFQueueOpFailedCode.values()[0xffff & error.getErrorCode()];
+ log.error("Error {} {} from {}", new Object[] {et, qofc, sw});
+ break;
+ default:
+ break;
+ }
+ }
+
+ /**
+ * Add a switch to the active switch list and call the switch listeners.
+ * This happens either when a switch first connects (and the controller is
+ * not in the slave role) or when the role of the controller changes from
+ * slave to master.
+ * @param sw the switch that has been added
+ */
+ // TODO: need to rethink locking and the synchronous switch update.
+ // We can / should also handle duplicate DPIDs in connectedSwitches
+ @LogMessageDoc(level="ERROR",
+ 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."
+ )
+ protected void addSwitch(IOFSwitch sw) {
+ // TODO: is it safe to modify the HashMap without holding
+ // the old switch's lock?
+ OFSwitchImpl oldSw = (OFSwitchImpl) this.activeSwitches.put(sw.getId(), sw);
+ if (sw == oldSw) {
+ // Note == for object equality, not .equals for value
+ log.info("New add switch for pre-existing switch {}", sw);
+ return;
+ }
+
+ if (oldSw != null) {
+ oldSw.getListenerWriteLock().lock();
+ try {
+ log.error("New switch added {} for already-added switch {}",
+ sw, oldSw);
+ // Set the connected flag to false to suppress calling
+ // the listeners for this switch in processOFMessage
+ oldSw.setConnected(false);
+
+ oldSw.cancelAllStatisticsReplies();
+
+ updateInactiveSwitchInfo(oldSw);
+
+ // we need to clean out old switch state definitively
+ // before adding the new switch
+ // FIXME: It seems not completely kosher to call the
+ // switch listeners here. I thought one of the points of
+ // having the asynchronous switch update mechanism was so
+ // the addedSwitch and removedSwitch were always called
+ // from a single thread to simplify concurrency issues
+ // for the listener.
+ if (switchListeners != null) {
+ for (IOFSwitchListener listener : switchListeners) {
+ listener.removedSwitch(oldSw);
+ }
+ }
+ // will eventually trigger a removeSwitch(), which will cause
+ // a "Not removing Switch ... already removed debug message.
+ // TODO: Figure out a way to handle this that avoids the
+ // spurious debug message.
+ oldSw.getChannel().close();
+ }
+ finally {
+ oldSw.getListenerWriteLock().unlock();
+ }
+ }
+
+ updateActiveSwitchInfo(sw);
+ SwitchUpdate update = new SwitchUpdate(sw, SwitchUpdateType.ADDED);
+ try {
+ this.updates.put(update);
+ } catch (InterruptedException e) {
+ log.error("Failure adding update to queue", e);
+ }
+ }
+
+ /**
+ * Remove a switch from the active switch list and call the switch listeners.
+ * This happens either when the switch is disconnected or when the
+ * controller's role for the switch changes from master to slave.
+ * @param sw the switch that has been removed
+ */
+ protected void removeSwitch(IOFSwitch sw) {
+ // No need to acquire the listener lock, since
+ // this method is only called after netty has processed all
+ // pending messages
+ log.debug("removeSwitch: {}", sw);
+ if (!this.activeSwitches.remove(sw.getId(), sw) || !sw.isConnected()) {
+ log.debug("Not removing switch {}; already removed", sw);
+ return;
+ }
+ // 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();
+
+ // FIXME: I think there's a race condition if we call updateInactiveSwitchInfo
+ // here if role support is enabled. In that case if the switch is being
+ // removed because we've been switched to being in the slave role, then I think
+ // it's possible that the new master may have already been promoted to master
+ // and written out the active switch state to storage. If we now execute
+ // updateInactiveSwitchInfo we may wipe out all of the state that was
+ // written out by the new master. Maybe need to revisit how we handle all
+ // of the switch state that's written to storage.
+
+ updateInactiveSwitchInfo(sw);
+ SwitchUpdate update = new SwitchUpdate(sw, SwitchUpdateType.REMOVED);
+ try {
+ this.updates.put(update);
+ } catch (InterruptedException e) {
+ log.error("Failure adding update to queue", e);
+ }
+ }
+
+ // ***************
+ // IFloodlightProvider
+ // ***************
+
+ @Override
+ public synchronized void addOFMessageListener(OFType type,
+ IOFMessageListener listener) {
+ ListenerDispatcher<OFType, IOFMessageListener> ldd =
+ messageListeners.get(type);
+ if (ldd == null) {
+ ldd = new ListenerDispatcher<OFType, IOFMessageListener>();
+ messageListeners.put(type, ldd);
+ }
+ ldd.addListener(type, listener);
+ }
+
+ @Override
+ public synchronized void removeOFMessageListener(OFType type,
+ IOFMessageListener listener) {
+ ListenerDispatcher<OFType, IOFMessageListener> ldd =
+ messageListeners.get(type);
+ if (ldd != null) {
+ ldd.removeListener(listener);
+ }
+ }
+
+ private void logListeners() {
+ for (Map.Entry<OFType,
+ ListenerDispatcher<OFType,
+ IOFMessageListener>> entry
+ : messageListeners.entrySet()) {
+
+ OFType type = entry.getKey();
+ ListenerDispatcher<OFType, IOFMessageListener> ldd =
+ entry.getValue();
+
+ StringBuffer sb = new StringBuffer();
+ sb.append("OFListeners for ");
+ sb.append(type);
+ sb.append(": ");
+ for (IOFMessageListener l : ldd.getOrderedListeners()) {
+ sb.append(l.getName());
+ sb.append(",");
+ }
+ log.debug(sb.toString());
+ }
+ }
+
+ public void removeOFMessageListeners(OFType type) {
+ messageListeners.remove(type);
+ }
+
+ @Override
+ public Map<Long, IOFSwitch> getSwitches() {
+ return Collections.unmodifiableMap(this.activeSwitches);
+ }
+
+ @Override
+ public void addOFSwitchListener(IOFSwitchListener listener) {
+ this.switchListeners.add(listener);
+ }
+
+ @Override
+ public void removeOFSwitchListener(IOFSwitchListener listener) {
+ this.switchListeners.remove(listener);
+ }
+
+ @Override
+ 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()) {
+ lers.put(e.getKey(), e.getValue().getOrderedListeners());
+ }
+ return Collections.unmodifiableMap(lers);
+ }
+
+ @Override
+ @LogMessageDocs({
+ @LogMessageDoc(message="Failed to inject OFMessage {message} onto " +
+ "a null switch",
+ explanation="Failed to process a message because the switch " +
+ " is no longer connected."),
+ @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) {
+ log.info("Failed to inject OFMessage {} onto a null switch", msg);
+ return false;
+ }
+
+ // FIXME: Do we need to be able to inject messages to switches
+ // where we're the slave controller (i.e. they're connected but
+ // not active)?
+ // FIXME: Don't we need synchronization logic here so we're holding
+ // the listener read lock when we call handleMessage? After some
+ // discussions it sounds like the right thing to do here would be to
+ // inject the message as a netty upstream channel event so it goes
+ // through the normal netty event processing, including being
+ // handled
+ if (!activeSwitches.containsKey(sw.getId())) return false;
+
+ try {
+ // Pass Floodlight context to the handleMessages()
+ handleMessage(sw, msg, bc);
+ } catch (IOException e) {
+ log.error("Error reinjecting OFMessage on switch {}",
+ HexString.toHexString(sw.getId()));
+ 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) {
+ if (log.isTraceEnabled()) {
+ String str = OFMessage.getDataAsString(sw, m, bc);
+ log.trace("{}", str);
+ }
+
+ List<IOFMessageListener> listeners = null;
+ if (messageListeners.containsKey(m.getType())) {
+ listeners =
+ messageListeners.get(m.getType()).getOrderedListeners();
+ }
+
+ if (listeners != null) {
+ for (IOFMessageListener listener : listeners) {
+ if (listener instanceof IOFSwitchFilter) {
+ if (!((IOFSwitchFilter)listener).isInterested(sw)) {
+ continue;
+ }
+ }
+ if (Command.STOP.equals(listener.receive(sw, m, bc))) {
+ break;
+ }
+ }
+ }
+ }
+
+ @Override
+ public BasicFactory getOFMessageFactory() {
+ return factory;
+ }
+
+ @Override
+ public String getControllerId() {
+ return controllerId;
+ }
+
+ // **************
+ // Initialization
+ // **************
+
+ protected void updateAllInactiveSwitchInfo() {
+ if (role == Role.SLAVE) {
+ return;
+ }
+ String controllerId = getControllerId();
+ String[] switchColumns = { SWITCH_DATAPATH_ID,
+ SWITCH_CONTROLLER_ID,
+ SWITCH_ACTIVE };
+ String[] portColumns = { PORT_ID, PORT_SWITCH };
+ IResultSet switchResultSet = null;
+ try {
+ OperatorPredicate op =
+ new OperatorPredicate(SWITCH_CONTROLLER_ID,
+ OperatorPredicate.Operator.EQ,
+ controllerId);
+ switchResultSet =
+ storageSource.executeQuery(SWITCH_TABLE_NAME,
+ switchColumns,
+ op, null);
+ while (switchResultSet.next()) {
+ IResultSet portResultSet = null;
+ try {
+ String datapathId =
+ switchResultSet.getString(SWITCH_DATAPATH_ID);
+ switchResultSet.setBoolean(SWITCH_ACTIVE, Boolean.FALSE);
+ op = new OperatorPredicate(PORT_SWITCH,
+ OperatorPredicate.Operator.EQ,
+ datapathId);
+ portResultSet =
+ storageSource.executeQuery(PORT_TABLE_NAME,
+ portColumns,
+ op, null);
+ while (portResultSet.next()) {
+ portResultSet.deleteRow();
+ }
+ portResultSet.save();
+ }
+ finally {
+ if (portResultSet != null)
+ portResultSet.close();
+ }
+ }
+ switchResultSet.save();
+ }
+ finally {
+ if (switchResultSet != null)
+ switchResultSet.close();
+ }
+ }
+
+ protected void updateControllerInfo() {
+ updateAllInactiveSwitchInfo();
+
+ // Write out the controller info to the storage source
+ Map<String, Object> controllerInfo = new HashMap<String, Object>();
+ String id = getControllerId();
+ controllerInfo.put(CONTROLLER_ID, id);
+ storageSource.updateRow(CONTROLLER_TABLE_NAME, controllerInfo);
+ }
+
+ protected void updateActiveSwitchInfo(IOFSwitch sw) {
+ if (role == Role.SLAVE) {
+ return;
+ }
+ // Obtain the row info for the switch
+ Map<String, Object> switchInfo = new HashMap<String, Object>();
+ String datapathIdString = sw.getStringId();
+ switchInfo.put(SWITCH_DATAPATH_ID, datapathIdString);
+ String controllerId = getControllerId();
+ switchInfo.put(SWITCH_CONTROLLER_ID, controllerId);
+ Date connectedSince = sw.getConnectedSince();
+ switchInfo.put(SWITCH_CONNECTED_SINCE, connectedSince);
+ Channel channel = sw.getChannel();
+ SocketAddress socketAddress = channel.getRemoteAddress();
+ if (socketAddress != null) {
+ String socketAddressString = socketAddress.toString();
+ switchInfo.put(SWITCH_SOCKET_ADDRESS, socketAddressString);
+ if (socketAddress instanceof InetSocketAddress) {
+ InetSocketAddress inetSocketAddress =
+ (InetSocketAddress)socketAddress;
+ InetAddress inetAddress = inetSocketAddress.getAddress();
+ String ip = inetAddress.getHostAddress();
+ switchInfo.put(SWITCH_IP, ip);
+ }
+ }
+
+ // Write out the switch features info
+ long capabilities = U32.f(sw.getCapabilities());
+ switchInfo.put(SWITCH_CAPABILITIES, capabilities);
+ long buffers = U32.f(sw.getBuffers());
+ switchInfo.put(SWITCH_BUFFERS, buffers);
+ long tables = U32.f(sw.getTables());
+ switchInfo.put(SWITCH_TABLES, tables);
+ long actions = U32.f(sw.getActions());
+ switchInfo.put(SWITCH_ACTIONS, actions);
+ switchInfo.put(SWITCH_ACTIVE, Boolean.TRUE);
+
+ // Update the switch
+ storageSource.updateRowAsync(SWITCH_TABLE_NAME, switchInfo);
+
+ // Update the ports
+ for (OFPhysicalPort port: sw.getPorts()) {
+ updatePortInfo(sw, port);
+ }
+ }
+
+ protected void updateInactiveSwitchInfo(IOFSwitch sw) {
+ if (role == Role.SLAVE) {
+ return;
+ }
+ log.debug("Update DB with inactiveSW {}", sw);
+ // Update the controller info in the storage source to be inactive
+ Map<String, Object> switchInfo = new HashMap<String, Object>();
+ String datapathIdString = sw.getStringId();
+ switchInfo.put(SWITCH_DATAPATH_ID, datapathIdString);
+ //switchInfo.put(SWITCH_CONNECTED_SINCE, null);
+ switchInfo.put(SWITCH_ACTIVE, Boolean.FALSE);
+ storageSource.updateRowAsync(SWITCH_TABLE_NAME, switchInfo);
+ }
+
+ protected void updatePortInfo(IOFSwitch sw, OFPhysicalPort port) {
+ if (role == Role.SLAVE) {
+ return;
+ }
+ String datapathIdString = sw.getStringId();
+ Map<String, Object> portInfo = new HashMap<String, Object>();
+ int portNumber = U16.f(port.getPortNumber());
+ String id = datapathIdString + "|" + portNumber;
+ portInfo.put(PORT_ID, id);
+ portInfo.put(PORT_SWITCH, datapathIdString);
+ portInfo.put(PORT_NUMBER, portNumber);
+ byte[] hardwareAddress = port.getHardwareAddress();
+ String hardwareAddressString = HexString.toHexString(hardwareAddress);
+ portInfo.put(PORT_HARDWARE_ADDRESS, hardwareAddressString);
+ String name = port.getName();
+ portInfo.put(PORT_NAME, name);
+ long config = U32.f(port.getConfig());
+ portInfo.put(PORT_CONFIG, config);
+ long state = U32.f(port.getState());
+ portInfo.put(PORT_STATE, state);
+ long currentFeatures = U32.f(port.getCurrentFeatures());
+ portInfo.put(PORT_CURRENT_FEATURES, currentFeatures);
+ long advertisedFeatures = U32.f(port.getAdvertisedFeatures());
+ portInfo.put(PORT_ADVERTISED_FEATURES, advertisedFeatures);
+ long supportedFeatures = U32.f(port.getSupportedFeatures());
+ portInfo.put(PORT_SUPPORTED_FEATURES, supportedFeatures);
+ long peerFeatures = U32.f(port.getPeerFeatures());
+ portInfo.put(PORT_PEER_FEATURES, peerFeatures);
+ storageSource.updateRowAsync(PORT_TABLE_NAME, portInfo);
+ }
+
+ /**
+ * Read switch port data from storage and write it into a switch object
+ * @param sw the switch to update
+ */
+ protected void readSwitchPortStateFromStorage(OFSwitchImpl sw) {
+ OperatorPredicate op =
+ new OperatorPredicate(PORT_SWITCH,
+ OperatorPredicate.Operator.EQ,
+ sw.getStringId());
+ IResultSet portResultSet =
+ storageSource.executeQuery(PORT_TABLE_NAME,
+ null, op, null);
+ //Map<Short, OFPhysicalPort> oldports =
+ // new HashMap<Short, OFPhysicalPort>();
+ //oldports.putAll(sw.getPorts());
+
+ while (portResultSet.next()) {
+ try {
+ OFPhysicalPort p = new OFPhysicalPort();
+ p.setPortNumber((short)portResultSet.getInt(PORT_NUMBER));
+ p.setName(portResultSet.getString(PORT_NAME));
+ p.setConfig((int)portResultSet.getLong(PORT_CONFIG));
+ p.setState((int)portResultSet.getLong(PORT_STATE));
+ String portMac = portResultSet.getString(PORT_HARDWARE_ADDRESS);
+ p.setHardwareAddress(HexString.fromHexString(portMac));
+ p.setCurrentFeatures((int)portResultSet.
+ getLong(PORT_CURRENT_FEATURES));
+ p.setAdvertisedFeatures((int)portResultSet.
+ getLong(PORT_ADVERTISED_FEATURES));
+ p.setSupportedFeatures((int)portResultSet.
+ getLong(PORT_SUPPORTED_FEATURES));
+ p.setPeerFeatures((int)portResultSet.
+ getLong(PORT_PEER_FEATURES));
+ //oldports.remove(Short.valueOf(p.getPortNumber()));
+ sw.setPort(p);
+ } catch (NullPointerException e) {
+ // ignore
+ }
+ }
+ SwitchUpdate update = new SwitchUpdate(sw, SwitchUpdateType.PORTCHANGED);
+ try {
+ this.updates.put(update);
+ } catch (InterruptedException e) {
+ log.error("Failure adding update to queue", e);
+ }
+ }
+
+ protected void removePortInfo(IOFSwitch sw, short portNumber) {
+ if (role == Role.SLAVE) {
+ return;
+ }
+ String datapathIdString = sw.getStringId();
+ String id = datapathIdString + "|" + portNumber;
+ storageSource.deleteRowAsync(PORT_TABLE_NAME, id);
+ }
+
+ /**
+ * Sets the initial role based on properties in the config params.
+ * It looks for two different properties.
+ * If the "role" property is specified then the value should be
+ * either "EQUAL", "MASTER", or "SLAVE" and the role of the
+ * controller is set to the specified value. If the "role" property
+ * is not specified then it looks next for the "role.path" property.
+ * In this case the value should be the path to a property file in
+ * the file system that contains a property called "floodlight.role"
+ * which can be one of the values listed above for the "role" property.
+ * The idea behind the "role.path" mechanism is that you have some
+ * separate heartbeat and master controller election algorithm that
+ * determines the role of the controller. When a role transition happens,
+ * it updates the current role in the file specified by the "role.path"
+ * file. Then if floodlight restarts for some reason it can get the
+ * correct current role of the controller from the file.
+ * @param configParams The config params for the FloodlightProvider service
+ * @return A valid role if role information is specified in the
+ * config params, otherwise null
+ */
+ @LogMessageDocs({
+ @LogMessageDoc(message="Controller role set to {role}",
+ explanation="Setting the initial HA role to "),
+ @LogMessageDoc(level="ERROR",
+ message="Invalid current role value: {role}",
+ explanation="An invalid HA role value was read from the " +
+ "properties file",
+ recommendation=LogMessageDoc.CHECK_CONTROLLER)
+ })
+ protected Role getInitialRole(Map<String, String> configParams) {
+ Role role = null;
+ 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);
+ }
+ catch (IllegalArgumentException exc) {
+ log.error("Invalid current role value: {}", roleString);
+ }
+ }
+
+ log.info("Controller role set to {}", role);
+
+ return role;
+ }
+
+ /**
+ * Tell controller that we're ready to accept switches loop
+ * @throws IOException
+ */
+ @LogMessageDocs({
+ @LogMessageDoc(message="Listening for switch connections on {address}",
+ explanation="The controller is ready and listening for new" +
+ " switch connections"),
+ @LogMessageDoc(message="Storage exception in controller " +
+ "updates loop; terminating process",
+ explanation=ERROR_DATABASE,
+ recommendation=LogMessageDoc.CHECK_CONTROLLER),
+ @LogMessageDoc(level="ERROR",
+ message="Exception in controller updates loop",
+ explanation="Failed to dispatch controller event",
+ recommendation=LogMessageDoc.GENERIC_ACTION)
+ })
+ public void run() {
+ 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 = new InetSocketAddress(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();
+ update.dispatch();
+ } catch (InterruptedException e) {
+ return;
+ } catch (StorageException e) {
+ log.error("Storage exception in controller " +
+ "updates loop; terminating process", e);
+ return;
+ } 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));
+ }
+ }
+
+ public 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) {
+ this.workerThreads = Integer.parseInt(threads);
+ }
+ log.debug("Number of worker threads set to {}", this.workerThreads);
+ String controllerId = configParams.get("controllerid");
+ if (controllerId != null) {
+ this.controllerId = controllerId;
+ }
+ log.debug("ControllerId set to {}", this.controllerId);
+ }
+
+ private void initVendorMessages() {
+ // Configure openflowj to be able to parse the role request/reply
+ // vendor messages.
+ OFBasicVendorId niciraVendorId = new OFBasicVendorId(
+ OFNiciraVendorData.NX_VENDOR_ID, 4);
+ OFVendorId.registerVendorId(niciraVendorId);
+ OFBasicVendorDataType roleRequestVendorData =
+ new OFBasicVendorDataType(
+ OFRoleRequestVendorData.NXT_ROLE_REQUEST,
+ OFRoleRequestVendorData.getInstantiable());
+ niciraVendorId.registerVendorDataType(roleRequestVendorData);
+ OFBasicVendorDataType roleReplyVendorData =
+ new OFBasicVendorDataType(
+ OFRoleReplyVendorData.NXT_ROLE_REPLY,
+ OFRoleReplyVendorData.getInstantiable());
+ niciraVendorId.registerVendorDataType(roleReplyVendorData);
+ }
+
+ /**
+ * Initialize internal data structures
+ */
+ public void init(Map<String, String> configParams) {
+ // 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>();
+ this.haListeners = new CopyOnWriteArraySet<IHAListener>();
+ this.activeSwitches = new ConcurrentHashMap<Long, IOFSwitch>();
+ this.connectedSwitches = new HashSet<OFSwitchImpl>();
+ this.controllerNodeIPsCache = new HashMap<String, String>();
+ this.updates = new LinkedBlockingQueue<IUpdate>();
+ this.factory = new BasicFactory();
+ this.providerMap = new HashMap<String, List<IInfoProvider>>();
+ setConfigParams(configParams);
+ this.role = getInitialRole(configParams);
+ this.roleChanger = new RoleChanger();
+ initVendorMessages();
+ this.systemStartTime = System.currentTimeMillis();
+ }
+
+ /**
+ * Startup all of the controller's components
+ */
+ @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() {
+ // Create the table names we use
+ storageSource.createTable(CONTROLLER_TABLE_NAME, null);
+ storageSource.createTable(SWITCH_TABLE_NAME, null);
+ storageSource.createTable(PORT_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.setTablePrimaryKeyName(SWITCH_TABLE_NAME,
+ SWITCH_DATAPATH_ID);
+ storageSource.setTablePrimaryKeyName(PORT_TABLE_NAME, PORT_ID);
+ storageSource.setTablePrimaryKeyName(CONTROLLER_INTERFACE_TABLE_NAME,
+ CONTROLLER_INTERFACE_ID);
+ storageSource.addListener(CONTROLLER_INTERFACE_TABLE_NAME, this);
+
+ while (true) {
+ try {
+ updateControllerInfo();
+ break;
+ }
+ catch (StorageException e) {
+ log.info("Waiting for storage source");
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e1) {
+ }
+ }
+ }
+
+ // Add our REST API
+ restApi.addRestletRoutable(new CoreWebRoutable());
+ }
+
+ @Override
+ public void addInfoProvider(String type, IInfoProvider provider) {
+ if (!providerMap.containsKey(type)) {
+ providerMap.put(type, new ArrayList<IInfoProvider>());
+ }
+ providerMap.get(type).add(provider);
+ }
+
+ @Override
+ public void removeInfoProvider(String type, IInfoProvider provider) {
+ if (!providerMap.containsKey(type)) {
+ log.debug("Provider type {} doesn't exist.", type);
+ return;
+ }
+
+ providerMap.get(type).remove(provider);
+ }
+
+ public Map<String, Object> getControllerInfo(String type) {
+ if (!providerMap.containsKey(type)) return null;
+
+ Map<String, Object> result = new LinkedHashMap<String, Object>();
+ for (IInfoProvider provider : providerMap.get(type)) {
+ result.putAll(provider.getInfo(type));
+ }
+
+ return result;
+ }
+
+ @Override
+ public void addHAListener(IHAListener listener) {
+ this.haListeners.add(listener);
+ }
+
+ @Override
+ public void removeHAListener(IHAListener listener) {
+ this.haListeners.remove(listener);
+ }
+
+
+ /**
+ * Handle changes to the controller nodes IPs and dispatch update.
+ */
+ @SuppressWarnings("unchecked")
+ protected void handleControllerNodeIPChanges() {
+ HashMap<String,String> curControllerNodeIPs = new HashMap<String,String>();
+ HashMap<String,String> addedControllerNodeIPs = new HashMap<String,String>();
+ HashMap<String,String> removedControllerNodeIPs =new HashMap<String,String>();
+ String[] colNames = { CONTROLLER_INTERFACE_CONTROLLER_ID,
+ CONTROLLER_INTERFACE_TYPE,
+ CONTROLLER_INTERFACE_NUMBER,
+ CONTROLLER_INTERFACE_DISCOVERED_IP };
+ synchronized(controllerNodeIPsCache) {
+ // We currently assume that interface Ethernet0 is the relevant
+ // controller interface. Might change.
+ // We could (should?) implement this using
+ // 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,
+ colNames,null, null);
+ while (res.next()) {
+ if (res.getString(CONTROLLER_INTERFACE_TYPE).equals("Ethernet") &&
+ res.getInt(CONTROLLER_INTERFACE_NUMBER) == 0) {
+ String controllerID = res.getString(CONTROLLER_INTERFACE_CONTROLLER_ID);
+ String discoveredIP = res.getString(CONTROLLER_INTERFACE_DISCOVERED_IP);
+ String curIP = controllerNodeIPsCache.get(controllerID);
+
+ curControllerNodeIPs.put(controllerID, discoveredIP);
+ if (curIP == null) {
+ // new controller node IP
+ addedControllerNodeIPs.put(controllerID, discoveredIP);
+ }
+ else if (curIP != discoveredIP) {
+ // IP changed
+ removedControllerNodeIPs.put(controllerID, curIP);
+ addedControllerNodeIPs.put(controllerID, discoveredIP);
+ }
+ }
+ }
+ // Now figure out if rows have been deleted. We can't use the
+ // rowKeys from rowsDeleted directly, since the tables primary
+ // key is a compound that we can't disassemble
+ Set<String> curEntries = curControllerNodeIPs.keySet();
+ Set<String> removedEntries = controllerNodeIPsCache.keySet();
+ removedEntries.removeAll(curEntries);
+ for (String removedControllerID : removedEntries)
+ removedControllerNodeIPs.put(removedControllerID, controllerNodeIPsCache.get(removedControllerID));
+ controllerNodeIPsCache = (HashMap<String, String>) curControllerNodeIPs.clone();
+ HAControllerNodeIPUpdate update = new HAControllerNodeIPUpdate(
+ curControllerNodeIPs, addedControllerNodeIPs,
+ removedControllerNodeIPs);
+ if (!removedControllerNodeIPs.isEmpty() || !addedControllerNodeIPs.isEmpty()) {
+ try {
+ this.updates.put(update);
+ } catch (InterruptedException e) {
+ log.error("Failure adding update to queue", e);
+ }
+ }
+ }
+ }
+
+ @Override
+ public Map<String, String> getControllerNodeIPs() {
+ // We return a copy of the mapping so we can guarantee that
+ // the mapping return is the same as one that will be (or was)
+ // dispatched to IHAListeners
+ HashMap<String,String> retval = new HashMap<String,String>();
+ synchronized(controllerNodeIPsCache) {
+ retval.putAll(controllerNodeIPsCache);
+ }
+ return retval;
+ }
+
+ @Override
+ public void rowsModified(String tableName, Set<Object> rowKeys) {
+ if (tableName.equals(CONTROLLER_INTERFACE_TABLE_NAME)) {
+ handleControllerNodeIPChanges();
+ }
+
+ }
+
+ @Override
+ public void rowsDeleted(String tableName, Set<Object> rowKeys) {
+ if (tableName.equals(CONTROLLER_INTERFACE_TABLE_NAME)) {
+ handleControllerNodeIPChanges();
+ }
+ }
+
+ @Override
+ public long getSystemStartTime() {
+ return (this.systemStartTime);
+ }
+
+ @Override
+ public void setAlwaysClearFlowsOnSwAdd(boolean value) {
+ this.alwaysClearFlowsOnSwAdd = value;
+ }
+
+ public boolean getAlwaysClearFlowsOnSwAdd() {
+ return this.alwaysClearFlowsOnSwAdd;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/HandshakeTimeoutException.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/HandshakeTimeoutException.java
new file mode 100644
index 0000000..421ec1a
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/HandshakeTimeoutException.java
@@ -0,0 +1,29 @@
+/**
+* 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;
+
+/**
+ * Exception is thrown when the handshake fails to complete
+ * before a specified time
+ * @author readams
+ */
+public class HandshakeTimeoutException extends Exception {
+
+ private static final long serialVersionUID = 6859880268940337312L;
+
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/HandshakeTimeoutHandler.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/HandshakeTimeoutHandler.java
new file mode 100644
index 0000000..6d3335f
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/HandshakeTimeoutHandler.java
@@ -0,0 +1,101 @@
+/**
+* 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.TimeUnit;
+
+import net.floodlightcontroller.core.internal.OFChannelState.HandshakeState;
+
+import org.jboss.netty.channel.ChannelHandlerContext;
+import org.jboss.netty.channel.ChannelStateEvent;
+import org.jboss.netty.channel.Channels;
+import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
+import org.jboss.netty.util.ExternalResourceReleasable;
+import org.jboss.netty.util.Timeout;
+import org.jboss.netty.util.Timer;
+import org.jboss.netty.util.TimerTask;
+
+/**
+ * Trigger a timeout if a switch fails to complete handshake soon enough
+ */
+public class HandshakeTimeoutHandler
+ extends SimpleChannelUpstreamHandler
+ implements ExternalResourceReleasable {
+ static final HandshakeTimeoutException EXCEPTION =
+ new HandshakeTimeoutException();
+
+ final OFChannelState state;
+ final Timer timer;
+ final long timeoutNanos;
+ volatile Timeout timeout;
+
+ public HandshakeTimeoutHandler(OFChannelState state, Timer timer,
+ long timeoutSeconds) {
+ super();
+ this.state = state;
+ 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),
+ timeoutNanos, TimeUnit.NANOSECONDS);
+ }
+ ctx.sendUpstream(e);
+ }
+
+ @Override
+ public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e)
+ throws Exception {
+ if (timeout != null) {
+ timeout.cancel();
+ timeout = null;
+ }
+ }
+
+ @Override
+ public void releaseExternalResources() {
+ timer.stop();
+ }
+
+ private final class HandshakeTimeoutTask implements TimerTask {
+
+ private final ChannelHandlerContext ctx;
+
+ HandshakeTimeoutTask(ChannelHandlerContext ctx) {
+ this.ctx = ctx;
+ }
+
+ @Override
+ public void run(Timeout timeout) throws Exception {
+ if (timeout.isCancelled()) {
+ return;
+ }
+
+ if (!ctx.getChannel().isOpen()) {
+ return;
+ }
+ if (!state.hsState.equals(HandshakeState.READY))
+ Channels.fireExceptionCaught(ctx, EXCEPTION);
+ }
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/IOFSwitchFeatures.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/IOFSwitchFeatures.java
new file mode 100644
index 0000000..ddbb0ef
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/IOFSwitchFeatures.java
@@ -0,0 +1,9 @@
+package net.floodlightcontroller.core.internal;
+
+import org.openflow.protocol.statistics.OFDescriptionStatistics;
+
+import net.floodlightcontroller.core.IOFSwitch;
+
+public interface IOFSwitchFeatures {
+ public void setFromDescription(IOFSwitch sw, OFDescriptionStatistics description);
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/OFChannelState.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/OFChannelState.java
new file mode 100644
index 0000000..ad5a377
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/OFChannelState.java
@@ -0,0 +1,64 @@
+/**
+* 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;
+
+/**
+ * Wrapper class to hold state for the OpenFlow switch connection
+ * @author readams
+ */
+class OFChannelState {
+
+ /**
+ * State for handling the switch handshake
+ */
+ protected enum HandshakeState {
+ /**
+ * Beginning state
+ */
+ START,
+
+ /**
+ * Received HELLO from switch
+ */
+ HELLO,
+
+ /**
+ * We've received the features reply
+ * Waiting for Config and Description reply
+ */
+ FEATURES_REPLY,
+
+ /**
+ * Switch is ready for processing messages
+ */
+ READY
+
+ }
+
+ protected volatile HandshakeState hsState = HandshakeState.START;
+ protected boolean hasGetConfigReply = false;
+ protected boolean hasDescription = false;
+
+ // The firstRoleReplyRecevied flag indicates if we have received the
+ // first role reply message on this connection (in response to the
+ // role request sent after the handshake). If role support is disabled
+ // on the controller we also set this flag to true.
+ // The flag is used to decide if the flow table should be wiped
+ // @see Controller.handleRoleReplyMessage()
+ protected boolean firstRoleReplyReceived = false;
+}
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/OFFeaturesReplyFuture.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/OFFeaturesReplyFuture.java
new file mode 100644
index 0000000..eca67bd
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/OFFeaturesReplyFuture.java
@@ -0,0 +1,72 @@
+/**
+ * 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/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/OFMessageDecoder.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/OFMessageDecoder.java
new file mode 100644
index 0000000..295e967
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/OFMessageDecoder.java
@@ -0,0 +1,60 @@
+/**
+* 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 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;
+
+/**
+ * Decode an openflow message from a Channel, for use in a netty
+ * pipeline
+ * @author readams
+ */
+public class OFMessageDecoder extends FrameDecoder {
+
+ OFMessageFactory factory = new BasicFactory();
+
+ @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
+ return null;
+ }
+
+ List<OFMessage> message = factory.parseMessage(buffer);
+ return message;
+ }
+
+ @Override
+ protected Object decodeLast(ChannelHandlerContext ctx, Channel channel,
+ ChannelBuffer buffer) throws Exception {
+ // This is not strictly needed atthis time. It is used to detect
+ // connection reset detection from netty (for debug)
+ return null;
+ }
+
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/OFMessageEncoder.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/OFMessageEncoder.java
new file mode 100644
index 0000000..6be5f9a
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/OFMessageEncoder.java
@@ -0,0 +1,56 @@
+/**
+* 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 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;
+
+/**
+ * Encode an openflow message for output into a ChannelBuffer, for use in a
+ * netty pipeline
+ * @author readams
+ */
+public class OFMessageEncoder extends OneToOneEncoder {
+
+ @Override
+ protected Object encode(ChannelHandlerContext ctx, Channel channel,
+ Object msg) throws Exception {
+ if (!( msg instanceof List))
+ return msg;
+
+ @SuppressWarnings("unchecked")
+ List<OFMessage> msglist = (List<OFMessage>)msg;
+ int size = 0;
+ for (OFMessage ofm : msglist) {
+ size += ofm.getLengthU();
+ }
+
+ ChannelBuffer buf = ChannelBuffers.buffer(size);;
+ for (OFMessage ofm : msglist) {
+ ofm.writeTo(buf);
+ }
+ return buf;
+ }
+
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/OFMessageFuture.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/OFMessageFuture.java
new file mode 100644
index 0000000..f01e179
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/OFMessageFuture.java
@@ -0,0 +1,170 @@
+/**
+* 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;
+ }
+
+
+ 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/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/OFStatisticsFuture.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/OFStatisticsFuture.java
new file mode 100644
index 0000000..4d3f733
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/OFStatisticsFuture.java
@@ -0,0 +1,80 @@
+/**
+* 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/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/OFSwitchImpl.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/OFSwitchImpl.java
new file mode 100644
index 0000000..dff00fd
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/OFSwitchImpl.java
@@ -0,0 +1,857 @@
+/**
+* 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 java.io.IOException;
+import java.net.SocketAddress;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.LinkedList;
+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.locks.Lock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+import net.floodlightcontroller.core.FloodlightContext;
+import net.floodlightcontroller.core.IFloodlightProviderService;
+import net.floodlightcontroller.core.IOFMessageListener;
+import net.floodlightcontroller.core.IFloodlightProviderService.Role;
+import net.floodlightcontroller.core.IOFSwitch;
+import net.floodlightcontroller.core.annotations.LogMessageDoc;
+import net.floodlightcontroller.core.annotations.LogMessageDocs;
+import net.floodlightcontroller.core.web.serializers.DPIDSerializer;
+import net.floodlightcontroller.threadpool.IThreadPoolService;
+import net.floodlightcontroller.util.TimedCache;
+
+import org.codehaus.jackson.annotate.JsonIgnore;
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.codehaus.jackson.map.annotate.JsonSerialize;
+import org.codehaus.jackson.map.ser.ToStringSerializer;
+import org.jboss.netty.channel.Channel;
+import org.openflow.protocol.OFFeaturesReply;
+import org.openflow.protocol.OFFeaturesRequest;
+import org.openflow.protocol.OFFlowMod;
+import org.openflow.protocol.OFMatch;
+import org.openflow.protocol.OFMessage;
+import org.openflow.protocol.OFPhysicalPort;
+import org.openflow.protocol.OFPort;
+import org.openflow.protocol.OFType;
+import org.openflow.protocol.OFVendor;
+import org.openflow.protocol.OFPhysicalPort.OFPortConfig;
+import org.openflow.protocol.OFPhysicalPort.OFPortState;
+import org.openflow.protocol.OFStatisticsRequest;
+import org.openflow.protocol.statistics.OFDescriptionStatistics;
+import org.openflow.protocol.statistics.OFStatistics;
+import org.openflow.util.HexString;
+import org.openflow.util.U16;
+import org.openflow.vendor.nicira.OFNiciraVendorData;
+import org.openflow.vendor.nicira.OFRoleRequestVendorData;
+import org.openflow.vendor.nicira.OFRoleVendorData;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This is the internal representation of an openflow switch.
+ */
+public class OFSwitchImpl 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 Logger log = LoggerFactory.getLogger(OFSwitchImpl.class);
+
+ private static final String HA_CHECK_SWITCH =
+ "Check the health of the indicated switch. If the problem " +
+ "persists or occurs repeatedly, it likely indicates a defect " +
+ "in the switch HA implementation.";
+
+ protected ConcurrentMap<Object, Object> attributes;
+ protected IFloodlightProviderService floodlightProvider;
+ protected IThreadPoolService threadPool;
+ protected Date connectedSince;
+ protected String stringId;
+ protected Channel channel;
+ protected AtomicInteger transactionIdSource;
+ // Lock to protect modification of the port maps. We only need to
+ // synchronize on modifications. For read operations we are fine since
+ // we rely on ConcurrentMaps which works for our use case.
+ private Object portLock;
+ // Map port numbers to the appropriate OFPhysicalPort
+ protected ConcurrentHashMap<Short, OFPhysicalPort> portsByNumber;
+ // Map port names to the appropriate OFPhyiscalPort
+ // XXX: The OF spec doesn't specify if port names need to be unique but
+ // according it's always the case in practice.
+ protected ConcurrentHashMap<String, OFPhysicalPort> portsByName;
+ protected Map<Integer,OFStatisticsFuture> statsFutureMap;
+ protected Map<Integer, IOFMessageListener> iofMsgListenersMap;
+ protected Map<Integer,OFFeaturesReplyFuture> featuresFutureMap;
+ protected boolean connected;
+ protected Role role;
+ protected TimedCache<Long> timedCache;
+ protected ReentrantReadWriteLock listenerLock;
+ protected ConcurrentMap<Short, Long> portBroadcastCacheHitMap;
+ /**
+ * When sending a role request message, the role request is added
+ * to this queue. If a role reply is received this queue is checked to
+ * verify that the reply matches the expected reply. We require in order
+ * delivery of replies. That's why we use a Queue.
+ * The RoleChanger uses a timeout to ensure we receive a timely reply.
+ *
+ * Need to synchronize on this instance if a request is sent, received,
+ * checked.
+ */
+ protected LinkedList<PendingRoleRequestEntry> pendingRoleRequests;
+
+ /* Switch features from initial featuresReply */
+ protected int capabilities;
+ protected int buffers;
+ protected int actions;
+ protected byte tables;
+ protected long datapathId;
+
+ public static IOFSwitchFeatures switchFeatures;
+ protected static final ThreadLocal<Map<OFSwitchImpl,List<OFMessage>>> local_msg_buffer =
+ new ThreadLocal<Map<OFSwitchImpl,List<OFMessage>>>() {
+ @Override
+ protected Map<OFSwitchImpl,List<OFMessage>> initialValue() {
+ return new HashMap<OFSwitchImpl,List<OFMessage>>();
+ }
+ };
+
+ // for managing our map sizes
+ protected static final int MAX_MACS_PER_SWITCH = 1000;
+
+ protected static class PendingRoleRequestEntry {
+ protected int xid;
+ protected Role role;
+ // cookie is used to identify the role "generation". roleChanger uses
+ protected long cookie;
+ public PendingRoleRequestEntry(int xid, Role role, long cookie) {
+ this.xid = xid;
+ this.role = role;
+ this.cookie = cookie;
+ }
+ }
+
+ public OFSwitchImpl() {
+ this.stringId = null;
+ this.attributes = new ConcurrentHashMap<Object, Object>();
+ this.connectedSince = new Date();
+ this.transactionIdSource = new AtomicInteger();
+ this.portLock = new Object();
+ this.portsByNumber = new ConcurrentHashMap<Short, OFPhysicalPort>();
+ this.portsByName = new ConcurrentHashMap<String, OFPhysicalPort>();
+ this.connected = true;
+ 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.listenerLock = new ReentrantReadWriteLock();
+ this.portBroadcastCacheHitMap = new ConcurrentHashMap<Short, Long>();
+ this.pendingRoleRequests = new LinkedList<OFSwitchImpl.PendingRoleRequestEntry>();
+
+ // Defaults properties for an ideal switch
+ this.setAttribute(PROP_FASTWILDCARDS, OFMatch.OFPFW_ALL);
+ this.setAttribute(PROP_SUPPORTS_OFPP_FLOOD, new Boolean(true));
+ this.setAttribute(PROP_SUPPORTS_OFPP_TABLE, new Boolean(true));
+ }
+
+
+ @Override
+ public Object getAttribute(String name) {
+ if (this.attributes.containsKey(name)) {
+ return this.attributes.get(name);
+ }
+ return null;
+ }
+
+ @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 Channel getChannel() {
+ return this.channel;
+ }
+
+ @JsonIgnore
+ public void setChannel(Channel channel) {
+ this.channel = channel;
+ }
+
+ @Override
+ public void write(OFMessage m, FloodlightContext bc) throws IOException {
+ Map<OFSwitchImpl,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) throws IOException {
+ 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);
+ }
+
+ public void write(List<OFMessage> msglist) throws IOException {
+ this.channel.write(msglist);
+ }
+
+ @Override
+ public void disconnectOutputStream() {
+ channel.close();
+ }
+
+ @Override
+ @JsonIgnore
+ public void setFeaturesReply(OFFeaturesReply featuresReply) {
+ synchronized(portLock) {
+ if (stringId == null) {
+ /* ports are updated via port status message, so we
+ * only fill in ports on initial connection.
+ */
+ for (OFPhysicalPort port : featuresReply.getPorts()) {
+ setPort(port);
+ }
+ }
+ this.datapathId = featuresReply.getDatapathId();
+ this.capabilities = featuresReply.getCapabilities();
+ this.buffers = featuresReply.getBuffers();
+ this.actions = featuresReply.getActions();
+ this.tables = featuresReply.getTables();
+ this.stringId = HexString.toHexString(this.datapathId);
+ }
+ }
+
+ @Override
+ @JsonIgnore
+ public Collection<OFPhysicalPort> getEnabledPorts() {
+ List<OFPhysicalPort> result = new ArrayList<OFPhysicalPort>();
+ for (OFPhysicalPort port : portsByNumber.values()) {
+ if (portEnabled(port)) {
+ result.add(port);
+ }
+ }
+ return result;
+ }
+
+ @Override
+ @JsonIgnore
+ public Collection<Short> getEnabledPortNumbers() {
+ List<Short> result = new ArrayList<Short>();
+ for (OFPhysicalPort port : portsByNumber.values()) {
+ if (portEnabled(port)) {
+ result.add(port.getPortNumber());
+ }
+ }
+ return result;
+ }
+
+ @Override
+ public OFPhysicalPort getPort(short portNumber) {
+ return portsByNumber.get(portNumber);
+ }
+
+ @Override
+ public OFPhysicalPort getPort(String portName) {
+ return portsByName.get(portName);
+ }
+
+ @Override
+ @JsonIgnore
+ public void setPort(OFPhysicalPort port) {
+ synchronized(portLock) {
+ portsByNumber.put(port.getPortNumber(), port);
+ portsByName.put(port.getName(), port);
+ }
+ }
+
+ @Override
+ @JsonProperty("ports")
+ public Collection<OFPhysicalPort> getPorts() {
+ return Collections.unmodifiableCollection(portsByNumber.values());
+ }
+
+ @Override
+ public void deletePort(short portNumber) {
+ synchronized(portLock) {
+ portsByName.remove(portsByNumber.get(portNumber).getName());
+ portsByNumber.remove(portNumber);
+ }
+ }
+
+ @Override
+ public void deletePort(String portName) {
+ synchronized(portLock) {
+ portsByNumber.remove(portsByName.get(portName).getPortNumber());
+ portsByName.remove(portName);
+ }
+ }
+
+ @Override
+ public boolean portEnabled(short portNumber) {
+ if (portsByNumber.get(portNumber) == null) return false;
+ return portEnabled(portsByNumber.get(portNumber));
+ }
+
+ @Override
+ public boolean portEnabled(String portName) {
+ if (portsByName.get(portName) == null) return false;
+ return portEnabled(portsByName.get(portName));
+ }
+
+ @Override
+ public boolean portEnabled(OFPhysicalPort port) {
+ if (port == null)
+ return false;
+ if ((port.getConfig() & OFPortConfig.OFPPC_PORT_DOWN.getValue()) > 0)
+ return false;
+ if ((port.getState() & OFPortState.OFPPS_LINK_DOWN.getValue()) > 0)
+ return false;
+ // Port STP state doesn't work with multiple VLANs, so ignore it for now
+ //if ((port.getState() & OFPortState.OFPPS_STP_MASK.getValue()) == OFPortState.OFPPS_STP_BLOCK.getValue())
+ // return false;
+ return true;
+ }
+
+ @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() {
+ return "OFSwitchImpl [" + channel.getRemoteAddress() + " 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.channel.write(msglist);
+ return;
+ }
+
+ @Override
+ public Future<List<OFStatistics>> getStatistics(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.channel.write(msglist);
+ return future;
+ }
+
+ @Override
+ public void deliverStatisticsReply(OFMessage 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);
+ }
+ }
+
+ @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;
+ }
+
+ @JsonIgnore
+ public void setThreadPoolService(IThreadPoolService tp) {
+ this.threadPool = tp;
+ }
+
+ @JsonIgnore
+ @Override
+ public synchronized boolean isConnected() {
+ return connected;
+ }
+
+ @Override
+ @JsonIgnore
+ public synchronized void setConnected(boolean connected) {
+ this.connected = connected;
+ }
+
+ @Override
+ public Role getRole() {
+ return role;
+ }
+
+ @JsonIgnore
+ @Override
+ public boolean isActive() {
+ return (role != Role.SLAVE);
+ }
+
+ @Override
+ @JsonIgnore
+ public void setSwitchProperties(OFDescriptionStatistics description) {
+ if (switchFeatures != null) {
+ switchFeatures.setFromDescription(this, description);
+ }
+ }
+
+ @Override
+ @LogMessageDoc(level="ERROR",
+ message="Failed to clear all flows on switch {switch}",
+ explanation="An I/O error occured while trying to clear " +
+ "flows on the switch.",
+ recommendation=LogMessageDoc.CHECK_SWITCH)
+ public void clearAllFlowMods() {
+ // Delete all pre-existing flows
+ 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));
+ try {
+ List<OFMessage> msglist = new ArrayList<OFMessage>(1);
+ msglist.add(fm);
+ channel.write(msglist);
+ } catch (Exception e) {
+ log.error("Failed to clear all flows on switch " + this, e);
+ }
+ }
+
+ @Override
+ public boolean updateBroadcastCache(Long entry, Short port) {
+ if (timedCache.update(entry)) {
+ Long count = portBroadcastCacheHitMap.putIfAbsent(port, new Long(1));
+ if (count != null) {
+ count++;
+ }
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ @JsonIgnore
+ public Map<Short, Long> getPortBroadcastHits() {
+ return this.portBroadcastCacheHitMap;
+ }
+
+
+ @Override
+ public void flush() {
+ Map<OFSwitchImpl,List<OFMessage>> msg_buffer_map = local_msg_buffer.get();
+ List<OFMessage> msglist = msg_buffer_map.get(this);
+ if ((msglist != null) && (msglist.size() > 0)) {
+ try {
+ this.write(msglist);
+ } catch (IOException e) {
+ // TODO: log exception
+ e.printStackTrace();
+ }
+ msglist.clear();
+ }
+ }
+
+ public static void flush_all() {
+ Map<OFSwitchImpl,List<OFMessage>> msg_buffer_map = local_msg_buffer.get();
+ for (OFSwitchImpl sw : msg_buffer_map.keySet()) {
+ sw.flush();
+ }
+ }
+
+ /**
+ * Return a read lock that must be held while calling the listeners for
+ * messages from the switch. Holding the read lock prevents the active
+ * switch list from being modified out from under the listeners.
+ * @return
+ */
+ @JsonIgnore
+ public Lock getListenerReadLock() {
+ return listenerLock.readLock();
+ }
+
+ /**
+ * Return a write lock that must be held when the controllers modifies the
+ * list of active switches. This is to ensure that the active switch list
+ * doesn't change out from under the listeners as they are handling a
+ * message from the switch.
+ * @return
+ */
+ @JsonIgnore
+ public Lock getListenerWriteLock() {
+ return listenerLock.writeLock();
+ }
+
+ /**
+ * Get the IP Address for the switch
+ * @return the inet address
+ */
+ @JsonSerialize(using=ToStringSerializer.class)
+ public SocketAddress getInetAddress() {
+ return channel.getRemoteAddress();
+ }
+
+ /**
+ * Send NX role request message to the switch requesting the specified role.
+ *
+ * This method should ONLY be called by @see RoleChanger.submitRequest().
+ *
+ * After sending the request add it to the queue of pending request. We
+ * use the queue to later verify that we indeed receive the correct reply.
+ * @param sw switch to send the role request message to
+ * @param role role to request
+ * @param cookie an opaque value that will be stored in the pending queue so
+ * RoleChanger can check for timeouts.
+ * @return transaction id of the role request message that was sent
+ */
+ protected int sendNxRoleRequest(Role role, long cookie)
+ throws IOException {
+ synchronized(pendingRoleRequests) {
+ // Convert the role enum to the appropriate integer constant used
+ // in the NX role request message
+ int nxRole = 0;
+ switch (role) {
+ case EQUAL:
+ nxRole = OFRoleVendorData.NX_ROLE_OTHER;
+ break;
+ case MASTER:
+ nxRole = OFRoleVendorData.NX_ROLE_MASTER;
+ break;
+ case SLAVE:
+ nxRole = OFRoleVendorData.NX_ROLE_SLAVE;
+ break;
+ default:
+ log.error("Invalid Role specified for switch {}."
+ + " Disconnecting.", this);
+ // TODO: should throw an error
+ return 0;
+ }
+
+ // Construct the role request message
+ OFVendor roleRequest = (OFVendor)floodlightProvider.
+ getOFMessageFactory().getMessage(OFType.VENDOR);
+ int xid = this.getNextTransactionId();
+ 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
+ List<OFMessage> msglist = new ArrayList<OFMessage>(1);
+ msglist.add(roleRequest);
+ // FIXME: should this use this.write() in order for messages to
+ // be processed by handleOutgoingMessage()
+ this.channel.write(msglist);
+
+ pendingRoleRequests.add(new PendingRoleRequestEntry(xid, role, cookie));
+ return xid;
+ }
+ }
+
+ /**
+ * Deliver a RoleReply message to this switch. Checks if the reply
+ * message matches the expected reply (head of the pending request queue).
+ * We require in-order delivery of replies. If there's any deviation from
+ * our expectations we disconnect the switch.
+ *
+ * We must not check the received role against the controller's current
+ * role because there's no synchronization but that's fine @see RoleChanger
+ *
+ * Will be called by the OFChannelHandler's receive loop
+ *
+ * @param xid Xid of the reply message
+ * @param role The Role in the the reply message
+ */
+ @LogMessageDocs({
+ @LogMessageDoc(level="ERROR",
+ message="Switch {switch}: received unexpected role reply for " +
+ "Role {role}" +
+ " Disconnecting switch",
+ explanation="The switch sent an unexpected HA role reply",
+ recommendation=HA_CHECK_SWITCH),
+ @LogMessageDoc(level="ERROR",
+ message="Switch {switch}: expected role reply with " +
+ "Xid {xid}, got {xid}. Disconnecting switch",
+ explanation="The switch sent an unexpected HA role reply",
+ recommendation=HA_CHECK_SWITCH),
+ @LogMessageDoc(level="ERROR",
+ message="Switch {switch}: expected role reply with " +
+ "Role {role}, got {role}. Disconnecting switch",
+ explanation="The switch sent an unexpected HA role reply",
+ recommendation=HA_CHECK_SWITCH)
+ })
+ protected void deliverRoleReply(int xid, Role role) {
+ synchronized(pendingRoleRequests) {
+ PendingRoleRequestEntry head = pendingRoleRequests.poll();
+ if (head == null) {
+ // Maybe don't disconnect if the role reply we received is
+ // for the same role we are already in.
+ log.error("Switch {}: received unexpected role reply for Role {}" +
+ " Disconnecting switch", this, role );
+ this.channel.close();
+ }
+ else if (head.xid != xid) {
+ // check xid before role!!
+ log.error("Switch {}: expected role reply with " +
+ "Xid {}, got {}. Disconnecting switch",
+ new Object[] { this, head.xid, xid } );
+ this.channel.close();
+ }
+ else if (head.role != role) {
+ log.error("Switch {}: expected role reply with " +
+ "Role {}, got {}. Disconnecting switch",
+ new Object[] { this, head.role, role } );
+ this.channel.close();
+ }
+ else {
+ log.debug("Received role reply message from {}, setting role to {}",
+ this, role);
+ if (this.role == null && getAttribute(SWITCH_SUPPORTS_NX_ROLE) == null) {
+ // The first role reply we received. Set the attribute
+ // that the switch supports roles
+ setAttribute(SWITCH_SUPPORTS_NX_ROLE, true);
+ }
+ this.role = role;
+ }
+ }
+ }
+
+ /**
+ * Checks whether the given xid matches the xid of the first pending
+ * role request.
+ * @param xid
+ * @return
+ */
+ protected boolean checkFirstPendingRoleRequestXid (int xid) {
+ synchronized(pendingRoleRequests) {
+ PendingRoleRequestEntry head = pendingRoleRequests.peek();
+ if (head == null)
+ return false;
+ else
+ return head.xid == xid;
+ }
+ }
+
+ /**
+ * Checks whether the given request cookie matches the cookie of the first
+ * pending request
+ * @param cookie
+ * @return
+ */
+ protected boolean checkFirstPendingRoleRequestCookie(long cookie) {
+ synchronized(pendingRoleRequests) {
+ PendingRoleRequestEntry head = pendingRoleRequests.peek();
+ if (head == null)
+ return false;
+ else
+ return head.cookie == cookie;
+ }
+ }
+
+ /**
+ * Called if we receive a vendor error message indicating that roles
+ * are not supported by the switch. If the xid matches the first pending
+ * one, we'll mark the switch as not supporting roles and remove the head.
+ * Otherwise we ignore it.
+ * @param xid
+ */
+ protected void deliverRoleRequestNotSupported(int xid) {
+ synchronized(pendingRoleRequests) {
+ PendingRoleRequestEntry head = pendingRoleRequests.poll();
+ this.role = null;
+ if (head!=null && head.xid == xid) {
+ setAttribute(SWITCH_SUPPORTS_NX_ROLE, false);
+ }
+ else {
+ this.channel.close();
+ }
+ }
+ }
+
+ @Override
+ public Future<OFFeaturesReply> getFeaturesReplyFromSwitch()
+ throws IOException {
+ OFMessage request = new OFFeaturesRequest();
+ 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.channel.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;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/OpenflowPipelineFactory.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/OpenflowPipelineFactory.java
new file mode 100644
index 0000000..5fb5c34
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/OpenflowPipelineFactory.java
@@ -0,0 +1,71 @@
+/**
+* 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.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.HashedWheelTimer;
+import org.jboss.netty.util.Timer;
+
+/**
+ * Creates a ChannelPipeline for a server-side openflow channel
+ * @author readams
+ */
+public class OpenflowPipelineFactory implements ChannelPipelineFactory {
+
+ protected Controller controller;
+ protected ThreadPoolExecutor pipelineExecutor;
+ protected Timer timer;
+ protected IdleStateHandler idleHandler;
+ protected ReadTimeoutHandler readTimeoutHandler;
+
+ public OpenflowPipelineFactory(Controller controller,
+ ThreadPoolExecutor pipelineExecutor) {
+ super();
+ this.controller = controller;
+ this.pipelineExecutor = pipelineExecutor;
+ this.timer = new HashedWheelTimer();
+ this.idleHandler = new IdleStateHandler(timer, 20, 25, 0);
+ this.readTimeoutHandler = new ReadTimeoutHandler(timer, 30);
+ }
+
+ @Override
+ public ChannelPipeline getPipeline() throws Exception {
+ OFChannelState state = new OFChannelState();
+
+ 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(state, timer, 15));
+ if (pipelineExecutor != null)
+ pipeline.addLast("pipelineExecutor",
+ new ExecutionHandler(pipelineExecutor));
+ pipeline.addLast("handler", controller.getChannelHandler(state));
+ return pipeline;
+ }
+
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/RoleChanger.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/RoleChanger.java
new file mode 100644
index 0000000..6378136
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/RoleChanger.java
@@ -0,0 +1,321 @@
+package net.floodlightcontroller.core.internal;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.concurrent.DelayQueue;
+import java.util.concurrent.Delayed;
+import java.util.concurrent.TimeUnit;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import net.floodlightcontroller.core.IFloodlightProviderService.Role;
+import net.floodlightcontroller.core.IOFSwitch;
+import net.floodlightcontroller.core.annotations.LogMessageDoc;
+
+/**
+ * This class handles sending of RoleRequest messages to all connected switches.
+ *
+ * Handling Role Requests is tricky. Roles are hard state on the switch and
+ * we can't query it so we need to make sure that we have consistent states
+ * on the switches. Whenever we send a role request to the set of connected
+ * switches we need to make sure that we've sent the request to all of them
+ * before we process the next change request. If a new switch connects, we
+ * need to send it the current role and need to make sure that the current
+ * role doesn't change while we are doing it. We achieve this by synchronizing
+ * all these actions on Controller.roleChanger
+ * On the receive side: we need to make sure that we receive a reply for each
+ * request we send and that the reply is consistent with the request we sent.
+ * We'd also like to send the role request to the switch asynchronously in a
+ * separate thread so we don't block the REST API or other callers.
+ *
+ * There are potential ways to relax these synchronization requirements:
+ * - "Generation ID" for each role request. However, this would be most useful
+ * if it were global for the whole cluster
+ * - Regularly resend the controller's current role. Don't know whether this
+ * might have adverse effects on the switch.
+ *
+ * Caveats:
+ * - No way to know if another controller (not in our controller cluster)
+ * sends MASTER requests to connected switches. Then we would drop to
+ * slave role without knowing it. Could regularly resend the current role.
+ * Ideally the switch would notify us if it demoted us. What happens if
+ * the other controller also regularly resends the same role request?
+ * Or if the health check determines that
+ * a controller is dead but the controller is still talking to switches (maybe
+ * just its health check failed) and resending the master role request....
+ * We could try to detect if a switch demoted us to slave even if we think
+ * we are master (error messages on packet outs, e.g., when sending LLDPs)
+ *
+ *
+ * The general model of Role Request handling is as follows:
+ *
+ * - All role request messages are handled by this class. Class Controller
+ * submits a role change request and the request gets queued. submitRequest
+ * takes a Collection of switches to which to send the request. We make a copy
+ * of this list.
+ * - A thread takes these change requests from the queue and sends them to
+ * all the switches (using our copy of the switch list).
+ * - The OFSwitchImpl sends the request over the wire and puts the request
+ * into a queue of pending request (storing xid and role). We start a timeout
+ * to make sure we eventually receive a reply from the switch. We use a single
+ * timeout for each request submitted using submitRequest()
+ * - After the timeout triggers we go over the list of switches again and
+ * check that a response has been received (by checking the head of the
+ * OFSwitchImpl's queue of pending requests)
+ * - We handle requests and timeouts in the same thread. We use a priority queue
+ * to schedule them so we are guaranteed that they are processed in
+ * the same order as they are submitted. If a request times out we drop
+ * the connection to this switch.
+ * - Since we decouple submission of role change requests and actually sending
+ * them we cannot check a received role reply against the controller's current
+ * role because the controller's current role could have changed again.
+ * - Receiving Role Reply messages is handled by OFChannelHandler and
+ * OFSwitchImpl directly. The OFSwitchImpl checks if the received request
+ * is as expected (xid and role match the head of the pending queue in
+ * OFSwitchImpl). If so
+ * the switch updates its role. Otherwise the connection is dropped. If this
+ * is the first reply, the SWITCH_SUPPORTS_NX_ROLE attribute is set.
+ * Next, we call addSwitch(), removeSwitch() to update the list of active
+ * switches if appropriate.
+ * - If we receive an Error indicating that roles are not supported by the
+ * switch, we set the SWITCH_SUPPORTS_NX_ROLE to false. We keep the
+ * switch connection alive while in MASTER and EQUAL role.
+ * (TODO: is this the right behavior for EQUAL??). If the role changes to
+ * SLAVE the switch connection is dropped (remember: only if the switch
+ * doesn't support role requests)
+ * The expected behavior is that the switch will probably try to reconnect
+ * repeatedly (with some sort of exponential backoff), but after a while
+ * will give-up and move on to the next controller-IP configured on the
+ * switch. This is the serial failover mechanism from OpenFlow spec v1.0.
+ *
+ * New switch connection:
+ * - Switch handshake is done without sending any role request messages.
+ * - After handshake completes, switch is added to the list of connected switches
+ * and we send the first role request message if role
+ * requests are enabled. If roles are disabled automatically promote switch to
+ * active switch list and clear FlowTable.
+ * - When we receive the first reply we proceed as above. In addition, if
+ * the role request is for MASTER we wipe the flow table. We do not wipe
+ * the flow table if the switch connected while role supported was disabled
+ * on the controller.
+ *
+ */
+public class RoleChanger {
+ // FIXME: Upon closer inspection DelayQueue seems to be somewhat broken.
+ // We are required to implement a compareTo based on getDelay() and
+ // getDelay() must return the remaining delay, thus it needs to use the
+ // current time. So x1.compareTo(x1) can never return 0 as some time
+ // will have passed between evaluating both getDelays(). This is even worse
+ // if the thread happens to be preempted between calling the getDelay()
+ // For the time being we enforce a small delay between subsequent
+ // role request messages and hope that's long enough to not screw up
+ // ordering. In the long run we might want to use two threads and two queues
+ // (one for requests, one for timeouts)
+ // Sigh.
+ protected DelayQueue<RoleChangeTask> pendingTasks;
+ protected long lastSubmitTime;
+ protected Thread workerThread;
+ protected long timeout;
+ protected static long DEFAULT_TIMEOUT = 15L*1000*1000*1000L; // 15s
+ protected static Logger log = LoggerFactory.getLogger(RoleChanger.class);
+ /**
+ * A queued task to be handled by the Role changer thread.
+ */
+ protected static class RoleChangeTask implements Delayed {
+ protected enum Type {
+ /** This is a request. Dispatch the role update to switches */
+ REQUEST,
+ /** This is a timeout task. Check if all switches have
+ correctly replied to the previously dispatched role request */
+ TIMEOUT
+ }
+ // The set of switches to work on
+ public Collection<OFSwitchImpl> switches;
+ public Role role;
+ public Type type;
+ // the time when the task should run as nanoTime()
+ public long deadline;
+ public RoleChangeTask(Collection<OFSwitchImpl> switches, Role role, long deadline) {
+ this.switches = switches;
+ this.role = role;
+ this.type = Type.REQUEST;
+ this.deadline = deadline;
+ }
+ @Override
+ public int compareTo(Delayed o) {
+ Long timeRemaining = getDelay(TimeUnit.NANOSECONDS);
+ return timeRemaining.compareTo(o.getDelay(TimeUnit.NANOSECONDS));
+ }
+ @Override
+ public long getDelay(TimeUnit tu) {
+ long timeRemaining = deadline - System.nanoTime();
+ return tu.convert(timeRemaining, TimeUnit.NANOSECONDS);
+ }
+ }
+
+ @LogMessageDoc(level="ERROR",
+ message="RoleRequestWorker task had an uncaught exception.",
+ explanation="An unknown occured while processing an HA " +
+ "role change event.",
+ recommendation=LogMessageDoc.GENERIC_ACTION)
+ protected class RoleRequestWorker extends Thread {
+ @Override
+ public void run() {
+ RoleChangeTask t;
+ boolean interrupted = false;
+ log.trace("RoleRequestWorker thread started");
+ try {
+ while (true) {
+ try {
+ t = pendingTasks.take();
+ } catch (InterruptedException e) {
+ // see http://www.ibm.com/developerworks/java/library/j-jtp05236/index.html
+ interrupted = true;
+ continue;
+ }
+ if (t.type == RoleChangeTask.Type.REQUEST) {
+ sendRoleRequest(t.switches, t.role, t.deadline);
+ // Queue the timeout
+ t.type = RoleChangeTask.Type.TIMEOUT;
+ t.deadline += timeout;
+ pendingTasks.put(t);
+ }
+ else {
+ verifyRoleReplyReceived(t.switches, t.deadline);
+ }
+ }
+ }
+ catch (Exception e) {
+ // Should never get here
+ log.error("RoleRequestWorker task had an uncaught exception. ",
+ e);
+ }
+ finally {
+ // Be nice in case we earlier caught InterruptedExecution
+ if (interrupted)
+ Thread.currentThread().interrupt();
+ }
+ } // end loop
+ }
+
+ public RoleChanger() {
+ this.pendingTasks = new DelayQueue<RoleChangeTask>();
+ this.workerThread = new Thread(new RoleRequestWorker());
+ this.timeout = DEFAULT_TIMEOUT;
+ this.workerThread.start();
+ }
+
+
+ public synchronized void submitRequest(Collection<OFSwitchImpl> switches, Role role) {
+ long deadline = System.nanoTime();
+ // Grrr. stupid DelayQueue. Make sre we have at least 10ms between
+ // role request messages.
+ if (deadline - lastSubmitTime < 10 * 1000*1000)
+ deadline = lastSubmitTime + 10 * 1000*1000;
+ // make a copy of the list
+ ArrayList<OFSwitchImpl> switches_copy = new ArrayList<OFSwitchImpl>(switches);
+ RoleChangeTask req = new RoleChangeTask(switches_copy, role, deadline);
+ pendingTasks.put(req);
+ lastSubmitTime = deadline;
+ }
+
+ /**
+ * Send a role request message to switches. This checks the capabilities
+ * of the switch for understanding role request messaging. Currently we only
+ * support the OVS-style role request message, but once the controller
+ * supports OF 1.2, this function will also handle sending out the
+ * OF 1.2-style role request message.
+ * @param switches the collection of switches to send the request too
+ * @param role the role to request
+ */
+ @LogMessageDoc(level="WARN",
+ message="Failed to send role request message " +
+ "to switch {switch}: {message}. Disconnecting",
+ explanation="An I/O error occurred while attempting to change " +
+ "the switch HA role.",
+ recommendation=LogMessageDoc.CHECK_SWITCH)
+ protected void sendRoleRequest(Collection<OFSwitchImpl> switches,
+ Role role, long cookie) {
+ // There are three cases to consider:
+ //
+ // 1) If the controller role at the point the switch connected was
+ // null/disabled, then we never sent the role request probe to the
+ // switch and therefore never set the SWITCH_SUPPORTS_NX_ROLE
+ // attribute for the switch, so supportsNxRole is null. In that
+ // case since we're now enabling role support for the controller
+ // we should send out the role request probe/update to the switch.
+ //
+ // 2) If supportsNxRole == Boolean.TRUE then that means we've already
+ // sent the role request probe to the switch and it replied with
+ // a role reply message, so we know it supports role request
+ // messages. Now we're changing the role and we want to send
+ // it another role request message to inform it of the new role
+ // for the controller.
+ //
+ // 3) If supportsNxRole == Boolean.FALSE, then that means we sent the
+ // role request probe to the switch but it responded with an error
+ // indicating that it didn't understand the role request message.
+ // In that case we don't want to send it another role request that
+ // it (still) doesn't understand. But if the new role of the
+ // controller is SLAVE, then we don't want the switch to remain
+ // connected to this controller. It might support the older serial
+ // failover model for HA support, so we want to terminate the
+ // connection and get it to initiate a connection with another
+ // controller in its list of controllers. Eventually (hopefully, if
+ // things are configured correctly) it will walk down its list of
+ // controllers and connect to the current master controller.
+ Iterator<OFSwitchImpl> iter = switches.iterator();
+ while(iter.hasNext()) {
+ OFSwitchImpl sw = iter.next();
+ try {
+ Boolean supportsNxRole = (Boolean)
+ sw.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE);
+ if ((supportsNxRole == null) || supportsNxRole) {
+ // Handle cases #1 and #2
+ sw.sendNxRoleRequest(role, cookie);
+ } else {
+ // Handle case #3
+ if (role == Role.SLAVE) {
+ log.debug("Disconnecting switch {} that doesn't support " +
+ "role request messages from a controller that went to SLAVE mode");
+ // Closing the channel should result in a call to
+ // channelDisconnect which updates all state
+ sw.getChannel().close();
+ iter.remove();
+ }
+ }
+ } catch (IOException e) {
+ log.warn("Failed to send role request message " +
+ "to switch {}: {}. Disconnecting",
+ sw, e);
+ sw.getChannel().close();
+ iter.remove();
+ }
+ }
+ }
+
+ /**
+ * Verify that switches have received a role reply message we sent earlier
+ * @param switches the collection of switches to send the request too
+ * @param cookie the cookie of the request
+ */
+ @LogMessageDoc(level="WARN",
+ message="Timeout while waiting for role reply from switch {switch}."
+ + " Disconnecting",
+ explanation="Timed out waiting for the switch to respond to " +
+ "a request to change the HA role.",
+ recommendation=LogMessageDoc.CHECK_SWITCH)
+ protected void verifyRoleReplyReceived(Collection<OFSwitchImpl> switches,
+ long cookie) {
+ for (OFSwitchImpl sw: switches) {
+ if (sw.checkFirstPendingRoleRequestCookie(cookie)) {
+ sw.getChannel().close();
+ log.warn("Timeout while waiting for role reply from switch {}."
+ + " Disconnecting", sw);
+ }
+ }
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/SwitchStateException.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/SwitchStateException.java
new file mode 100644
index 0000000..d2a928e
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/internal/SwitchStateException.java
@@ -0,0 +1,43 @@
+/**
+* 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;
+
+/**
+ *
+ */
+public class SwitchStateException extends Exception {
+
+ private static final long serialVersionUID = 9153954512470002631L;
+
+ public SwitchStateException() {
+ super();
+ }
+
+ public SwitchStateException(String arg0, Throwable arg1) {
+ super(arg0, arg1);
+ }
+
+ public SwitchStateException(String arg0) {
+ super(arg0);
+ }
+
+ public SwitchStateException(Throwable arg0) {
+ super(arg0);
+ }
+
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/module/FloodlightModuleContext.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/module/FloodlightModuleContext.java
new file mode 100644
index 0000000..0cbae32
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/module/FloodlightModuleContext.java
@@ -0,0 +1,104 @@
+package net.floodlightcontroller.core.module;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * The service registry for an IFloodlightProvider.
+ * @author alexreimers
+ */
+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;
+
+ /**
+ * Creates the ModuleContext for use with this IFloodlightProvider.
+ * This will be used as a module registry for all IFloodlightModule(s).
+ */
+ public FloodlightModuleContext() {
+ serviceMap =
+ new HashMap<Class<? extends IFloodlightService>,
+ IFloodlightService>();
+ configParams =
+ new HashMap<Class<? extends IFloodlightModule>,
+ Map<String, String>>();
+ }
+
+ /**
+ * 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);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public <T extends IFloodlightService> T getServiceImpl(Class<T> service) {
+ IFloodlightService s = serviceMap.get(service);
+ return (T)s;
+ }
+
+ @Override
+ public Collection<Class<? extends IFloodlightService>> getAllServices() {
+ return serviceMap.keySet();
+ }
+
+ @Override
+ public Collection<IFloodlightModule> getAllModules() {
+ return moduleSet;
+ }
+
+ public void setModuleSet(Collection<IFloodlightModule> modSet) {
+ this.moduleSet = modSet;
+ }
+
+ /**
+ * Gets the configuration parameter map for a module
+ * @param module The module to get the configuration map for, usually yourself
+ * @return A map containing all the configuration parameters for the module, may be empty
+ */
+ @Override
+ public Map<String, String> getConfigParams(IFloodlightModule module) {
+ Map<String, String> retMap = configParams.get(module.getClass());
+ if (retMap == null) {
+ // Return an empty map if none exists so the module does not
+ // need to null check the map
+ retMap = new HashMap<String, String>();
+ configParams.put(module.getClass(), retMap);
+ }
+
+ // also add any configuration parameters for superclasses, but
+ // only if more specific configuration does not override it
+ for (Class<? extends IFloodlightModule> c : configParams.keySet()) {
+ if (c.isInstance(module)) {
+ for (Map.Entry<String, String> ent : configParams.get(c).entrySet()) {
+ if (!retMap.containsKey(ent.getKey())) {
+ retMap.put(ent.getKey(), ent.getValue());
+ }
+ }
+ }
+ }
+
+ return retMap;
+ }
+
+ /**
+ * Adds a configuration parameter for a module
+ * @param mod The fully qualified module name to add the parameter to
+ * @param key The configuration parameter key
+ * @param value The configuration parameter value
+ */
+ public void addConfigParam(IFloodlightModule mod, String key, String value) {
+ Map<String, String> moduleParams = configParams.get(mod.getClass());
+ if (moduleParams == null) {
+ moduleParams = new HashMap<String, String>();
+ configParams.put(mod.getClass(), moduleParams);
+ }
+ moduleParams.put(key, value);
+ }
+ }
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/module/FloodlightModuleException.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/module/FloodlightModuleException.java
new file mode 100644
index 0000000..20ccc86
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/module/FloodlightModuleException.java
@@ -0,0 +1,9 @@
+package net.floodlightcontroller.core.module;
+
+public class FloodlightModuleException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ public FloodlightModuleException(String error) {
+ super(error);
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/module/FloodlightModuleLoader.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/module/FloodlightModuleLoader.java
new file mode 100644
index 0000000..45fe997
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/module/FloodlightModuleLoader.java
@@ -0,0 +1,444 @@
+package net.floodlightcontroller.core.module;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+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 org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Finds all Floodlight modules in the class path and loads/starts them.
+ * @author alexreimers
+ *
+ */
+public class FloodlightModuleLoader {
+ protected static Logger logger =
+ LoggerFactory.getLogger(FloodlightModuleLoader.class);
+
+ protected static Map<Class<? extends IFloodlightService>,
+ Collection<IFloodlightModule>> serviceMap;
+ protected static Map<IFloodlightModule,
+ Collection<Class<? extends
+ IFloodlightService>>> moduleServiceMap;
+ protected static Map<String, IFloodlightModule> moduleNameMap;
+ protected static Object lock = new Object();
+
+ protected FloodlightModuleContext floodlightModuleContext;
+
+ public static final String COMPILED_CONF_FILE =
+ "floodlightdefault.properties";
+ public static final String FLOODLIGHT_MODULES_KEY =
+ "floodlight.modules";
+
+ public FloodlightModuleLoader() {
+ floodlightModuleContext = new FloodlightModuleContext();
+ }
+
+ /**
+ * 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.
+ */
+ 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.debug("Could not find module");
+ //moduleIter.remove();
+ continue;
+ }
+ //}
+ //for (IFloodlightModule m : moduleLoader) {
+ 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) {
+ 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);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Loads the modules from a specified configuration file.
+ * @param fName The configuration file path
+ * @return An IFloodlightModuleContext with all the modules to be started
+ * @throws FloodlightModuleException
+ */
+ @LogMessageDocs({
+ @LogMessageDoc(level="INFO",
+ message="Loading modules from file {file name}",
+ explanation="The controller is initializing its module " +
+ "configuration from the specified properties file"),
+ @LogMessageDoc(level="INFO",
+ message="Loading default modules",
+ explanation="The controller is initializing its module " +
+ "configuration to the default configuration"),
+ @LogMessageDoc(level="ERROR",
+ message="Could not load module configuration file",
+ explanation="The controller failed to read the " +
+ "module configuration file",
+ recommendation="Verify that the module configuration is " +
+ "present. " + LogMessageDoc.CHECK_CONTROLLER),
+ @LogMessageDoc(level="ERROR",
+ message="Could not load default modules",
+ explanation="The controller failed to read the default " +
+ "module configuration",
+ recommendation=LogMessageDoc.CHECK_CONTROLLER)
+ })
+ public IFloodlightModuleContext loadModulesFromConfig(String fName)
+ throws FloodlightModuleException {
+ Properties prop = new Properties();
+
+ File f = new File(fName);
+ if (f.isFile()) {
+ logger.info("Loading modules from file {}", fName);
+ try {
+ prop.load(new FileInputStream(fName));
+ } catch (Exception e) {
+ logger.error("Could not load module configuration file", e);
+ System.exit(1);
+ }
+ } else {
+ logger.info("Loading default modules");
+ InputStream is = this.getClass().getClassLoader().
+ getResourceAsStream(COMPILED_CONF_FILE);
+ try {
+ prop.load(is);
+ } catch (IOException e) {
+ logger.error("Could not load default modules", e);
+ System.exit(1);
+ }
+ }
+
+ String moduleList = prop.getProperty(FLOODLIGHT_MODULES_KEY)
+ .replaceAll("\\s", "");
+ Collection<String> configMods = new ArrayList<String>();
+ configMods.addAll(Arrays.asList(moduleList.split(",")));
+ return loadModulesFromList(configMods, prop);
+ }
+
+ /**
+ * 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.
+ * @return The ModuleContext containing all the loaded modules
+ * @throws FloodlightModuleException
+ */
+ protected IFloodlightModuleContext loadModulesFromList(Collection<String> configMods, Properties prop,
+ Collection<IFloodlightService> ignoreList) 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>();
+ 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 provies 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;
+ }
+ //}
+ }
+ }
+ }
+
+ // 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;
+ for (IFloodlightModule moduleDep : mods) {
+ if (configMods.contains(moduleDep.getClass().getCanonicalName())) {
+ // Module will be loaded, we can continue
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ String duplicateMods = "";
+ for (IFloodlightModule mod : mods) {
+ duplicateMods += mod.getClass().getCanonicalName() + ", ";
+ }
+ throw new FloodlightModuleException("ERROR! Found more " +
+ "than one (" + mods.size() + ") IFloodlightModules that provides " +
+ "service " + c.toString() +
+ ". Please specify one of the following modules in the config: " +
+ duplicateMods);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ floodlightModuleContext.setModuleSet(moduleSet);
+ parseConfigParameters(prop);
+ initModules(moduleSet);
+ startupModules(moduleSet);
+
+ return floodlightModuleContext;
+ }
+
+ /**
+ * 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 {
+ return loadModulesFromList(configMods, prop, null);
+ }
+
+ /**
+ * 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 module the module to add
+ */
+ protected void addModule(Map<Class<? extends IFloodlightService>,
+ IFloodlightModule> moduleMap,
+ Collection<IFloodlightModule> moduleSet,
+ 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);
+ }
+ }
+
+ /**
+ * Allocate service implementations and then init all the modules
+ * @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)
+ throws FloodlightModuleException {
+ for (IFloodlightModule module : moduleSet) {
+ // Get the module's service instance(s)
+ Map<Class<? extends IFloodlightService>,
+ IFloodlightService> simpls = module.getServiceImpls();
+
+ // add its services to the context
+ if (simpls != null) {
+ for (Entry<Class<? extends IFloodlightService>,
+ IFloodlightService> s : simpls.entrySet()) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Setting " + s.getValue() +
+ " as provider for " +
+ s.getKey().getCanonicalName());
+ }
+ if (floodlightModuleContext.getServiceImpl(s.getKey()) == null) {
+ floodlightModuleContext.addService(s.getKey(),
+ s.getValue());
+ } else {
+ throw new FloodlightModuleException("Cannot set "
+ + s.getValue()
+ + " as the provider for "
+ + s.getKey().getCanonicalName()
+ + " because "
+ + floodlightModuleContext.getServiceImpl(s.getKey())
+ + " already provides it");
+ }
+ }
+ }
+ }
+
+ for (IFloodlightModule module : moduleSet) {
+ // init the module
+ if (logger.isDebugEnabled()) {
+ logger.debug("Initializing " +
+ module.getClass().getCanonicalName());
+ }
+ module.init(floodlightModuleContext);
+ }
+ }
+
+ /**
+ * Call each loaded module's startup method
+ * @param moduleSet the module set to start up
+ */
+ protected void startupModules(Collection<IFloodlightModule> moduleSet) {
+ for (IFloodlightModule m : moduleSet) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Starting " + m.getClass().getCanonicalName());
+ }
+ m.startUp(floodlightModuleContext);
+ }
+ }
+
+ /**
+ * Parses configuration parameters for each module
+ * @param prop The properties file to use
+ */
+ @LogMessageDoc(level="WARN",
+ message="Module {module} not found or loaded. " +
+ "Not adding configuration option {key} = {value}",
+ explanation="Ignoring a configuration parameter for a " +
+ "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);
+ String configKey = key.substring(lastPeriod + 1);
+ // Check to see if it's overridden on the command line
+ String systemKey = System.getProperty(key);
+ if (systemKey != null) {
+ configValue = systemKey;
+ } else {
+ configValue = prop.getProperty(key);
+ }
+
+ IFloodlightModule mod = moduleNameMap.get(moduleName);
+ if (mod == null) {
+ logger.warn("Module {} not found or loaded. " +
+ "Not adding configuration option {} = {}",
+ new Object[]{moduleName, configKey, configValue});
+ } else {
+ floodlightModuleContext.addConfigParam(mod, configKey, configValue);
+ }
+ }
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/module/IFloodlightModule.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/module/IFloodlightModule.java
new file mode 100644
index 0000000..f8b196b
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/module/IFloodlightModule.java
@@ -0,0 +1,74 @@
+package net.floodlightcontroller.core.module;
+
+import java.util.Collection;
+import java.util.Map;
+
+
+/**
+ * Defines an interface for loadable Floodlight modules.
+ *
+ * At a high level, these functions are called in the following order:
+ * <ol>
+ * <li> getServices() : what services does this module provide
+ * <li> getDependencies() : list the dependencies
+ * <li> init() : internal initializations (don't touch other modules)
+ * <li> startUp() : external initializations (<em>do</em> touch other modules)
+ * </ol>
+ *
+ * @author alexreimers
+ */
+public interface IFloodlightModule {
+
+ /**
+ * Return the list of interfaces that this module implements.
+ * All interfaces must inherit IFloodlightService
+ * @return
+ */
+
+ public Collection<Class<? extends IFloodlightService>> getModuleServices();
+
+ /**
+ * Instantiate (as needed) and return objects that implement each
+ * of the services exported by this module. The map returned maps
+ * the implemented service to the object. The object could be the
+ * 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();
+
+ /**
+ * Get a list of Modules that this module depends on. The module system
+ * will ensure that each these dependencies is resolved before the
+ * subsequent calls to init().
+ * @return The Collection of IFloodlightServices that this module depends
+ * on.
+ */
+
+ public Collection<Class<? extends IFloodlightService>> getModuleDependencies();
+
+ /**
+ * This is a hook for each module to do its <em>internal</em> initialization,
+ * e.g., call setService(context.getService("Service"))
+ *
+ * All module dependencies are resolved when this is called, but not every module
+ * is initialized.
+ *
+ * @param context
+ * @throws FloodlightModuleException
+ */
+
+ void init(FloodlightModuleContext context) throws FloodlightModuleException;
+
+ /**
+ * This is a hook for each module to do its <em>external</em> initializations,
+ * e.g., register for callbacks or query for state in other modules
+ *
+ * It is expected that this function will not block and that modules that want
+ * non-event driven CPU will spawn their own threads.
+ *
+ * @param context
+ */
+
+ void startUp(FloodlightModuleContext context);
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/module/IFloodlightModuleContext.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/module/IFloodlightModuleContext.java
new file mode 100644
index 0000000..2c058a7
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/module/IFloodlightModuleContext.java
@@ -0,0 +1,35 @@
+package net.floodlightcontroller.core.module;
+
+import java.util.Collection;
+import java.util.Map;
+
+
+public interface IFloodlightModuleContext {
+ /**
+ * Retrieves a casted version of a module from the registry.
+ * @param name The IFloodlightService object type
+ * @return The IFloodlightService
+ * @throws FloodlightModuleException If the module was not found
+ * or a ClassCastException was encountered.
+ */
+ public <T extends IFloodlightService> T getServiceImpl(Class<T> service);
+
+ /**
+ * Returns all loaded services
+ * @return A collection of service classes that have been loaded
+ */
+ 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
+ * @return A key, value map of the configuration options
+ */
+ public Map<String, String> getConfigParams(IFloodlightModule module);
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/module/IFloodlightService.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/module/IFloodlightService.java
new file mode 100644
index 0000000..5974b3a
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/module/IFloodlightService.java
@@ -0,0 +1,11 @@
+package net.floodlightcontroller.core.module;
+
+/**
+ * This is the base interface for any IFloodlightModule package that provides
+ * a service.
+ * @author alexreimers
+ *
+ */
+public abstract interface IFloodlightService {
+ // This space is intentionally left blank....don't touch it
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/module/ModuleLoaderResource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/module/ModuleLoaderResource.java
new file mode 100644
index 0000000..a73a17f
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/module/ModuleLoaderResource.java
@@ -0,0 +1,104 @@
+package net.floodlightcontroller.core.module;
+
+import java.util.Collection;
+import java.util.HashMap;
+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;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Returns list of modules loaded by Floodlight.
+ * @author Rob Sherwood
+ */
+public class ModuleLoaderResource extends ServerResource {
+ protected static Logger log =
+ LoggerFactory.getLogger(ModuleLoaderResource.class);
+
+ /**
+ * Retrieves information about loaded modules.
+ * @return Information about loaded modules.
+ */
+ @Get("json")
+ public Map<String, Object> retrieve() {
+ return retrieveInternal(false);
+ }
+
+ /**
+ * Retrieves all modules and their dependencies available
+ * to Floodlight.
+ * @param loadedOnly Whether to return all modules available or only the ones loaded.
+ * @return Information about modules available or loaded.
+ */
+ public Map<String, Object> retrieveInternal(boolean loadedOnly) {
+ Map<String, Object> model = new HashMap<String, Object>();
+
+ Set<String> loadedModules = new HashSet<String>();
+ for (Object val : getContext().getAttributes().values()) {
+ if ((val instanceof IFloodlightModule) || (val instanceof IFloodlightService)) {
+ String serviceImpl = val.getClass().getCanonicalName();
+ loadedModules.add(serviceImpl);
+ // log.debug("Tracking serviceImpl " + serviceImpl);
+ }
+ }
+
+ for (String moduleName :
+ FloodlightModuleLoader.moduleNameMap.keySet() ) {
+ Map<String,Object> moduleInfo = new HashMap<String, Object>();
+
+ IFloodlightModule module =
+ FloodlightModuleLoader.moduleNameMap.get(
+ moduleName);
+
+ Collection<Class<? extends IFloodlightService>> deps =
+ module.getModuleDependencies();
+ if ( deps == null)
+ deps = new HashSet<Class<? extends IFloodlightService>>();
+ Map<String,Object> depsMap = new HashMap<String, Object> ();
+ for (Class<? extends IFloodlightService> service : deps) {
+ Object serviceImpl = getContext().getAttributes().get(service.getCanonicalName());
+ if (serviceImpl != null)
+ depsMap.put(service.getCanonicalName(), serviceImpl.getClass().getCanonicalName());
+ else
+ depsMap.put(service.getCanonicalName(), "<unresolved>");
+
+ }
+ moduleInfo.put("depends", depsMap);
+
+ Collection<Class<? extends IFloodlightService>> provides =
+ module.getModuleServices();
+ if ( provides == null)
+ provides = new HashSet<Class<? extends IFloodlightService>>();
+ Map<String,Object> providesMap = new HashMap<String,Object>();
+ for (Class<? extends IFloodlightService> service : provides) {
+ providesMap.put(service.getCanonicalName(), module.getServiceImpls().get(service).getClass().getCanonicalName());
+ }
+ moduleInfo.put("provides", providesMap);
+
+ moduleInfo.put("loaded", false); // not loaded, by default
+
+ // check if this module is loaded directly
+ if (loadedModules.contains(module.getClass().getCanonicalName())) {
+ moduleInfo.put("loaded", true);
+ } else {
+ // if not, then maybe one of the services it exports is loaded
+ for (Class<? extends IFloodlightService> service : provides) {
+ String modString = module.getServiceImpls().get(service).getClass().getCanonicalName();
+ if (loadedModules.contains(modString))
+ moduleInfo.put("loaded", true);
+ /* else
+ log.debug("ServiceImpl not loaded " + modString); */
+ }
+ }
+
+ if ((Boolean)moduleInfo.get("loaded")|| !loadedOnly )
+ model.put(moduleName, moduleInfo);
+ }
+ return model;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/types/MacVlanPair.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/types/MacVlanPair.java
new file mode 100644
index 0000000..7a44f1d
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/types/MacVlanPair.java
@@ -0,0 +1,44 @@
+/**
+* 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.types;
+
+public class MacVlanPair {
+ public Long mac;
+ public Short vlan;
+ public MacVlanPair(Long mac, Short vlan) {
+ this.mac = mac;
+ this.vlan = vlan;
+ }
+
+ public long getMac() {
+ return mac.longValue();
+ }
+
+ public short getVlan() {
+ return vlan.shortValue();
+ }
+
+ public boolean equals(Object o) {
+ return (o instanceof MacVlanPair) && (mac.equals(((MacVlanPair) o).mac))
+ && (vlan.equals(((MacVlanPair) o).vlan));
+ }
+
+ public int hashCode() {
+ return mac.hashCode() ^ vlan.hashCode();
+ }
+}
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/types/SwitchMessagePair.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/types/SwitchMessagePair.java
new file mode 100644
index 0000000..0e91bc9
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/types/SwitchMessagePair.java
@@ -0,0 +1,40 @@
+/**
+* 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.types;
+
+import org.openflow.protocol.OFMessage;
+
+import net.floodlightcontroller.core.IOFSwitch;
+
+public class SwitchMessagePair {
+ private final IOFSwitch sw;
+ private final OFMessage msg;
+
+ public SwitchMessagePair(IOFSwitch sw, OFMessage msg) {
+ this.sw = sw;
+ this.msg = msg;
+ }
+
+ public IOFSwitch getSwitch() {
+ return this.sw;
+ }
+
+ public OFMessage getMessage() {
+ return this.msg;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/util/AppCookie.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/util/AppCookie.java
new file mode 100644
index 0000000..210823e
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/util/AppCookie.java
@@ -0,0 +1,54 @@
+/**
+* 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.util;
+
+/***
+ * FIXME Need a system for registering/binding applications to a unique ID
+ *
+ * @author capveg
+ *
+ */
+
+public class AppCookie {
+ static final int APP_ID_BITS = 12;
+ static final int APP_ID_SHIFT = (64 - APP_ID_BITS);
+ // we have bits 13-31 unused here ... that's ok!
+ static final int USER_BITS = 32;
+ static final int USER_SHIFT = 0;
+
+
+ /**
+ * Encapsulate an application ID and a user block of stuff into a cookie
+ *
+ * @param application An ID to identify the application
+ * @param user Some application specific data
+ * @return a cookie for use in OFFlowMod.setCookie()
+ */
+
+ static public long makeCookie(int application, int user) {
+ return ((application & ((1L << APP_ID_BITS) - 1)) << APP_ID_SHIFT) | user;
+ }
+
+ static public int extractApp(long cookie) {
+ return (int)((cookie>> APP_ID_SHIFT) & ((1L << APP_ID_BITS) - 1));
+ }
+
+ static public int extractUser(long cookie) {
+ return (int)((cookie>> USER_SHIFT) & ((1L << USER_BITS) - 1));
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/util/ListenerDispatcher.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/util/ListenerDispatcher.java
new file mode 100644
index 0000000..58b543c
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/util/ListenerDispatcher.java
@@ -0,0 +1,136 @@
+/**
+* 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.util;
+
+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;
+
+/**
+ * Maintain lists of listeners ordered by dependency.
+ *
+ * @author readams
+ *
+ */
+public class ListenerDispatcher<U, T extends IListener<U>> {
+ protected static Logger logger = LoggerFactory.getLogger(ListenerDispatcher.class);
+ List<T> listeners = null;
+
+ private void visit(List<T> newlisteners, U type, HashSet<T> visited,
+ List<T> ordering, T listener) {
+ if (!visited.contains(listener)) {
+ visited.add(listener);
+
+ for (T i : newlisteners) {
+ if (ispre(type, i, listener)) {
+ visit(newlisteners, type, visited, ordering, i);
+ }
+ }
+ ordering.add(listener);
+ }
+ }
+
+ private boolean ispre(U type, T l1, T l2) {
+ return (l2.isCallbackOrderingPrereq(type, l1.getName()) ||
+ l1.isCallbackOrderingPostreq(type, l2.getName()));
+ }
+
+ /**
+ * Add a listener to the list of listeners
+ * @param listener
+ */
+ @LogMessageDoc(level="ERROR",
+ message="No listener dependency solution: " +
+ "No listeners without incoming dependencies",
+ explanation="The set of listeners installed " +
+ "have dependencies with no solution",
+ recommendation="Install a different set of listeners " +
+ "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)
+ newlisteners.addAll(listeners);
+
+ newlisteners.add(listener);
+ // Find nodes without outgoing edges
+ List<T> terminals = new ArrayList<T>();
+ for (T i : newlisteners) {
+ boolean isterm = true;
+ for (T j : newlisteners) {
+ if (ispre(type, i, j)) {
+ isterm = false;
+ break;
+ }
+ }
+ if (isterm) {
+ terminals.add(i);
+ }
+ }
+
+ if (terminals.size() == 0) {
+ logger.error("No listener dependency solution: " +
+ "No listeners without incoming dependencies");
+ listeners = newlisteners;
+ return;
+ }
+
+ // visit depth-first traversing in the opposite order from
+ // the dependencies. Note we will not generally detect cycles
+ HashSet<T> visited = new HashSet<T>();
+ List<T> ordering = new ArrayList<T>();
+ for (T term : terminals) {
+ visit(newlisteners, type, visited, ordering, term);
+ }
+ listeners = ordering;
+ }
+
+ /**
+ * Remove the given listener
+ * @param listener the listener to remove
+ */
+ public void removeListener(T listener) {
+ if (listeners != null) {
+ List<T> newlisteners = new ArrayList<T>();
+ newlisteners.addAll(listeners);
+ newlisteners.remove(listener);
+ listeners = newlisteners;
+ }
+ }
+
+ /**
+ * Clear all listeners
+ */
+ public void clearListeners() {
+ listeners = new ArrayList<T>();
+ }
+
+ /**
+ * Get the ordered list of listeners ordered by dependencies
+ * @return
+ */
+ public List<T> getOrderedListeners() {
+ return listeners;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/util/MutableInteger.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/util/MutableInteger.java
new file mode 100644
index 0000000..0f070fa
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/util/MutableInteger.java
@@ -0,0 +1,55 @@
+/**
+* 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.util;
+
+public class MutableInteger extends Number {
+ private static final long serialVersionUID = 1L;
+ int mutableInt;
+
+ public MutableInteger(int value) {
+ this.mutableInt = value;
+ }
+
+ public void setValue(int value) {
+ this.mutableInt = value;
+ }
+
+ @Override
+ public double doubleValue() {
+ return (double) mutableInt;
+ }
+
+ @Override
+ public float floatValue() {
+ // TODO Auto-generated method stub
+ return (float) mutableInt;
+ }
+
+ @Override
+ public int intValue() {
+ // TODO Auto-generated method stub
+ return mutableInt;
+ }
+
+ @Override
+ public long longValue() {
+ // TODO Auto-generated method stub
+ return (long) mutableInt;
+ }
+
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/util/SingletonTask.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/util/SingletonTask.java
new file mode 100644
index 0000000..07729e5
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/util/SingletonTask.java
@@ -0,0 +1,162 @@
+/**
+* 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.util;
+
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+import net.floodlightcontroller.core.annotations.LogMessageDoc;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This allows you to represent a task that should be queued for future execution
+ * but where you only want the task to complete once in response to some sequence
+ * of events. For example, if you get a change notification and want to reload state,
+ * you only want to reload the state once, at the end, and don't want to queue
+ * an update for every notification that might come in.
+ *
+ * The semantics are as follows:
+ * * If the task hasn't begun yet, do not queue a new task
+ * * 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 class SingletonTaskContext {
+ protected boolean taskShouldRun = false;
+ protected boolean taskRunning = false;
+
+ protected SingletonTaskWorker waitingTask = null;
+ }
+
+ protected static class SingletonTaskWorker implements Runnable {
+ SingletonTask parent;
+ boolean canceled = false;
+ long nextschedule = 0;
+
+ public SingletonTaskWorker(SingletonTask parent) {
+ super();
+ this.parent = parent;
+ }
+
+ @Override
+ @LogMessageDoc(level="ERROR",
+ message="Exception while executing task",
+ recommendation=LogMessageDoc.GENERIC_ACTION)
+ public void run() {
+ synchronized (parent.context) {
+ if (canceled || !parent.context.taskShouldRun)
+ return;
+
+ parent.context.taskRunning = true;
+ parent.context.taskShouldRun = false;
+ }
+
+ try {
+ parent.task.run();
+ } catch (Exception e) {
+ logger.error("Exception while executing task", e);
+ }
+
+ synchronized (parent.context) {
+ parent.context.taskRunning = false;
+
+ if (parent.context.taskShouldRun) {
+ long now = System.nanoTime();
+ if ((nextschedule <= 0 || (nextschedule - now) <= 0)) {
+ parent.ses.execute(this);
+ } else {
+ parent.ses.schedule(this,
+ nextschedule-now,
+ TimeUnit.NANOSECONDS);
+ }
+ }
+ }
+ }
+ }
+
+ protected SingletonTaskContext context = new SingletonTaskContext();
+ protected Runnable task;
+ protected ScheduledExecutorService ses;
+
+
+ /**
+ * Construct a new SingletonTask for the given runnable. The context
+ * is used to manage the state of the task execution and can be shared
+ * by more than one instance of the runnable.
+ * @param context
+ * @param Task
+ */
+ public SingletonTask(ScheduledExecutorService ses,
+ Runnable task) {
+ super();
+ this.task = task;
+ this.ses = ses;
+ }
+
+ /**
+ * Schedule the task to run if there's not already a task scheduled
+ * If there is such a task waiting that has not already started, it
+ * cancel that task and reschedule it to run at the given time. If the
+ * task is already started, it will cause the task to be rescheduled once
+ * it completes to run after delay from the time of reschedule.
+ *
+ * @param delay the delay in scheduling
+ * @param unit the timeunit of the delay
+ */
+ public void reschedule(long delay, TimeUnit unit) {
+ boolean needQueue = true;
+ SingletonTaskWorker stw = null;
+
+ synchronized (context) {
+ if (context.taskRunning || context.taskShouldRun) {
+ if (context.taskRunning) {
+ // schedule to restart at the right time
+ if (delay > 0) {
+ long now = System.nanoTime();
+ long then =
+ now + TimeUnit.NANOSECONDS.convert(delay, unit);
+ context.waitingTask.nextschedule = then;
+ } else {
+ context.waitingTask.nextschedule = 0;
+ }
+ needQueue = false;
+ } else {
+ // cancel and requeue
+ context.waitingTask.canceled = true;
+ context.waitingTask = null;
+ }
+ }
+
+ context.taskShouldRun = true;
+
+ if (needQueue) {
+ stw = context.waitingTask = new SingletonTaskWorker(this);
+ }
+ }
+
+ if (needQueue) {
+ if (delay <= 0)
+ ses.execute(stw);
+ else
+ ses.schedule(stw, delay, unit);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/AllSwitchStatisticsResource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/AllSwitchStatisticsResource.java
new file mode 100644
index 0000000..d012fc8
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/AllSwitchStatisticsResource.java
@@ -0,0 +1,174 @@
+/**
+* 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.web;
+
+import java.lang.Thread.State;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import net.floodlightcontroller.core.IFloodlightProviderService;
+import net.floodlightcontroller.core.types.MacVlanPair;
+
+import org.openflow.protocol.OFFeaturesReply;
+import org.openflow.protocol.statistics.OFStatistics;
+import org.openflow.protocol.statistics.OFStatisticsType;
+import org.openflow.util.HexString;
+import org.restlet.resource.Get;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Return switch statistics information for all switches
+ * @author readams
+ */
+public class AllSwitchStatisticsResource extends SwitchResourceBase {
+ protected static Logger log =
+ LoggerFactory.getLogger(AllSwitchStatisticsResource.class);
+
+ @Get("json")
+ public Map<String, Object> retrieve() {
+ String statType = (String) getRequestAttributes().get("statType");
+ return retrieveInternal(statType);
+ }
+
+ public Map<String, Object> retrieveInternal(String statType) {
+ HashMap<String, Object> model = new HashMap<String, Object>();
+
+ OFStatisticsType type = null;
+ REQUESTTYPE rType = null;
+
+ if (statType.equals("port")) {
+ type = OFStatisticsType.PORT;
+ rType = REQUESTTYPE.OFSTATS;
+ } else if (statType.equals("queue")) {
+ type = OFStatisticsType.QUEUE;
+ rType = REQUESTTYPE.OFSTATS;
+ } else if (statType.equals("flow")) {
+ type = OFStatisticsType.FLOW;
+ rType = REQUESTTYPE.OFSTATS;
+ } else if (statType.equals("aggregate")) {
+ type = OFStatisticsType.AGGREGATE;
+ rType = REQUESTTYPE.OFSTATS;
+ } else if (statType.equals("desc")) {
+ type = OFStatisticsType.DESC;
+ rType = REQUESTTYPE.OFSTATS;
+ } else if (statType.equals("table")) {
+ type = OFStatisticsType.TABLE;
+ rType = REQUESTTYPE.OFSTATS;
+ } else if (statType.equals("features")) {
+ rType = REQUESTTYPE.OFFEATURES;
+ } else {
+ return model;
+ }
+
+ IFloodlightProviderService floodlightProvider =
+ (IFloodlightProviderService)getContext().getAttributes().
+ get(IFloodlightProviderService.class.getCanonicalName());
+ Long[] switchDpids = floodlightProvider.getSwitches().keySet().toArray(new Long[0]);
+ List<GetConcurrentStatsThread> activeThreads = new ArrayList<GetConcurrentStatsThread>(switchDpids.length);
+ List<GetConcurrentStatsThread> pendingRemovalThreads = new ArrayList<GetConcurrentStatsThread>();
+ GetConcurrentStatsThread t;
+ for (Long l : switchDpids) {
+ t = new GetConcurrentStatsThread(l, rType, type);
+ activeThreads.add(t);
+ t.start();
+ }
+
+ // Join all the threads after the timeout. Set a hard timeout
+ // of 12 seconds for the threads to finish. If the thread has not
+ // finished the switch has not replied yet and therefore we won't
+ // add the switch's stats to the reply.
+ for (int iSleepCycles = 0; iSleepCycles < 12; iSleepCycles++) {
+ for (GetConcurrentStatsThread curThread : activeThreads) {
+ if (curThread.getState() == State.TERMINATED) {
+ if (rType == REQUESTTYPE.OFSTATS) {
+ model.put(HexString.toHexString(curThread.getSwitchId()), curThread.getStatisticsReply());
+ } else if (rType == REQUESTTYPE.OFFEATURES) {
+ model.put(HexString.toHexString(curThread.getSwitchId()), curThread.getFeaturesReply());
+ }
+ pendingRemovalThreads.add(curThread);
+ }
+ }
+
+ // remove the threads that have completed the queries to the switches
+ for (GetConcurrentStatsThread curThread : pendingRemovalThreads) {
+ activeThreads.remove(curThread);
+ }
+ // clear the list so we don't try to double remove them
+ pendingRemovalThreads.clear();
+
+ // if we are done finish early so we don't always get the worst case
+ if (activeThreads.isEmpty()) {
+ break;
+ }
+
+ // sleep for 1 s here
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ log.error("Interrupted while waiting for statistics", e);
+ }
+ }
+
+ return model;
+ }
+
+ protected class GetConcurrentStatsThread extends Thread {
+ private List<OFStatistics> switchReply;
+ private long switchId;
+ private OFStatisticsType statType;
+ private REQUESTTYPE requestType;
+ private OFFeaturesReply featuresReply;
+ private Map<MacVlanPair, Short> switchTable;
+
+ public GetConcurrentStatsThread(long switchId, REQUESTTYPE requestType, OFStatisticsType statType) {
+ this.switchId = switchId;
+ this.requestType = requestType;
+ this.statType = statType;
+ this.switchReply = null;
+ this.featuresReply = null;
+ this.switchTable = null;
+ }
+
+ public List<OFStatistics> getStatisticsReply() {
+ return switchReply;
+ }
+
+ public OFFeaturesReply getFeaturesReply() {
+ return featuresReply;
+ }
+
+ public Map<MacVlanPair, Short> getSwitchTable() {
+ return switchTable;
+ }
+
+ public long getSwitchId() {
+ return switchId;
+ }
+
+ public void run() {
+ if ((requestType == REQUESTTYPE.OFSTATS) && (statType != null)) {
+ switchReply = getSwitchStatistics(switchId, statType);
+ } else if (requestType == REQUESTTYPE.OFFEATURES) {
+ featuresReply = getSwitchFeaturesReply(switchId);
+ }
+ }
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/ControllerMemoryResource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/ControllerMemoryResource.java
new file mode 100644
index 0000000..bcb2bd1
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/ControllerMemoryResource.java
@@ -0,0 +1,39 @@
+/**
+* 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.web;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.restlet.resource.Get;
+import org.restlet.resource.ServerResource;
+
+/**
+ * Retrieve floodlight memory state
+ * @author readams
+ */
+public class ControllerMemoryResource extends ServerResource {
+ @Get("json")
+ public Map<String, Object> retrieve() {
+ HashMap<String, Object> model = new HashMap<String, Object>();
+ Runtime runtime = Runtime.getRuntime();
+ model.put("total", new Long(runtime.totalMemory()));
+ model.put("free", new Long(runtime.freeMemory()));
+ return model;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/ControllerRoleResource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/ControllerRoleResource.java
new file mode 100644
index 0000000..652058e
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/ControllerRoleResource.java
@@ -0,0 +1,57 @@
+package net.floodlightcontroller.core.web;
+
+import org.restlet.data.Status;
+import org.restlet.resource.ServerResource;
+
+import net.floodlightcontroller.core.IFloodlightProviderService;
+import net.floodlightcontroller.core.IFloodlightProviderService.Role;
+import net.floodlightcontroller.core.annotations.LogMessageDoc;
+
+import org.restlet.resource.Get;
+import org.restlet.resource.Post;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ControllerRoleResource extends ServerResource {
+
+ protected static Logger log = LoggerFactory.getLogger(ControllerRoleResource.class);
+
+ @Get("json")
+ public RoleInfo getRole() {
+ IFloodlightProviderService floodlightProvider =
+ (IFloodlightProviderService)getContext().getAttributes().
+ get(IFloodlightProviderService.class.getCanonicalName());
+ return new RoleInfo(floodlightProvider.getRole());
+ }
+
+ @Post("json")
+ @LogMessageDoc(level="WARN",
+ message="Invalid role value specified in REST API to " +
+ "set controller role",
+ explanation="An HA role change request was malformed.",
+ recommendation=LogMessageDoc.CHECK_CONTROLLER)
+ public void setRole(RoleInfo roleInfo) {
+ //Role role = Role.lookupRole(roleInfo.getRole());
+ Role role = null;
+ try {
+ role = Role.valueOf(roleInfo.getRole().toUpperCase());
+ }
+ catch (IllegalArgumentException e) {
+ // The role value in the REST call didn't match a valid
+ // role name, so just leave the role as null and handle
+ // the error below.
+ }
+ if (role == null) {
+ log.warn ("Invalid role value specified in REST API to " +
+ "set controller role");
+ setStatus(Status.CLIENT_ERROR_BAD_REQUEST, "Invalid role value");
+ return;
+ }
+
+ IFloodlightProviderService floodlightProvider =
+ (IFloodlightProviderService)getContext().getAttributes().
+ get(IFloodlightProviderService.class.getCanonicalName());
+
+ floodlightProvider.setRole(role);
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/ControllerSummaryResource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/ControllerSummaryResource.java
new file mode 100644
index 0000000..20fbf85
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/ControllerSummaryResource.java
@@ -0,0 +1,40 @@
+/**
+* Copyright 2012, Big Switch Networks, Inc.
+* Originally created by Shudong Zhou, Big Switch Networks
+*
+* 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.web;
+
+import java.util.Map;
+
+import org.restlet.resource.Get;
+import org.restlet.resource.ServerResource;
+
+import net.floodlightcontroller.core.IFloodlightProviderService;
+
+/**
+ * Get summary counters registered by all modules
+ * @author shudongz
+ */
+public class ControllerSummaryResource extends ServerResource {
+ @Get("json")
+ public Map<String, Object> retrieve() {
+ IFloodlightProviderService floodlightProvider =
+ (IFloodlightProviderService)getContext().getAttributes().
+ get(IFloodlightProviderService.class.getCanonicalName());
+ return floodlightProvider.getControllerInfo("summary");
+ }
+
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/ControllerSwitchesResource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/ControllerSwitchesResource.java
new file mode 100644
index 0000000..454f566
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/ControllerSwitchesResource.java
@@ -0,0 +1,81 @@
+/**
+* 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.web;
+
+import java.util.Collections;
+import java.util.Iterator;
+
+import net.floodlightcontroller.core.IFloodlightProviderService;
+import net.floodlightcontroller.core.IOFSwitch;
+import net.floodlightcontroller.util.FilterIterator;
+
+import org.openflow.util.HexString;
+import org.restlet.data.Form;
+import org.restlet.data.Status;
+import org.restlet.resource.Get;
+import org.restlet.resource.ServerResource;
+
+/**
+ * Get a list of switches connected to the controller
+ * @author readams
+ */
+public class ControllerSwitchesResource extends ServerResource {
+ public static final String DPID_ERROR =
+ "Invalid Switch DPID: must be a 64-bit quantity, expressed in " +
+ "hex as AA:BB:CC:DD:EE:FF:00:11";
+
+ @Get("json")
+ public Iterator<IOFSwitch> retrieve() {
+ IFloodlightProviderService floodlightProvider =
+ (IFloodlightProviderService)getContext().getAttributes().
+ get(IFloodlightProviderService.class.getCanonicalName());
+
+ Long switchDPID = null;
+
+ Form form = getQuery();
+ String dpid = form.getFirstValue("dpid", true);
+ if (dpid != null) {
+ try {
+ switchDPID = HexString.toLong(dpid);
+ } catch (Exception e) {
+ setStatus(Status.CLIENT_ERROR_BAD_REQUEST, DPID_ERROR);
+ return null;
+ }
+ }
+ if (switchDPID != null) {
+ IOFSwitch sw =
+ floodlightProvider.getSwitches().get(switchDPID);
+ if (sw != null)
+ return Collections.singleton(sw).iterator();
+ return Collections.<IOFSwitch>emptySet().iterator();
+ }
+ final String dpidStartsWith =
+ form.getFirstValue("dpid__startswith", true);
+ Iterator<IOFSwitch> switer =
+ floodlightProvider.getSwitches().values().iterator();
+ if (dpidStartsWith != null) {
+ return new FilterIterator<IOFSwitch>(switer) {
+ @Override
+ protected boolean matches(IOFSwitch value) {
+ return value.getStringId().startsWith(dpidStartsWith);
+ }
+ };
+ }
+ return switer;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/CoreWebRoutable.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/CoreWebRoutable.java
new file mode 100644
index 0000000..45ef6e9
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/CoreWebRoutable.java
@@ -0,0 +1,65 @@
+/**
+* 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.web;
+
+import net.floodlightcontroller.core.module.ModuleLoaderResource;
+import net.floodlightcontroller.restserver.RestletRoutable;
+
+import org.restlet.Context;
+import org.restlet.Restlet;
+import org.restlet.routing.Router;
+
+/**
+ * Creates a router to handle all the core web URIs
+ * @author readams
+ */
+public class CoreWebRoutable implements RestletRoutable {
+ @Override
+ public String basePath() {
+ return "/wm/core";
+ }
+
+ @Override
+ public Restlet getRestlet(Context context) {
+ Router router = new Router(context);
+ router.attach("/module/all/json", ModuleLoaderResource.class);
+ router.attach("/module/loaded/json", LoadedModuleLoaderResource.class);
+ router.attach("/switch/{switchId}/role/json", SwitchRoleResource.class);
+ router.attach("/switch/all/{statType}/json", AllSwitchStatisticsResource.class);
+ router.attach("/switch/{switchId}/{statType}/json", SwitchStatisticsResource.class);
+ router.attach("/controller/switches/json", ControllerSwitchesResource.class);
+ router.attach("/counter/{counterTitle}/json", CounterResource.class);
+ router.attach("/counter/{switchId}/{counterName}/json", SwitchCounterResource.class);
+ router.attach("/counter/categories/{switchId}/{counterName}/{layer}/json", SwitchCounterCategoriesResource.class);
+ router.attach("/memory/json", ControllerMemoryResource.class);
+ router.attach("/packettrace/json", PacketTraceResource.class);
+ // Get the last {count} events from the event histories
+ router.attach("/event-history/topology-switch/{count}/json",
+ EventHistoryTopologySwitchResource.class);
+ router.attach("/event-history/topology-link/{count}/json",
+ EventHistoryTopologyLinkResource.class);
+ router.attach("/event-history/topology-cluster/{count}/json",
+ EventHistoryTopologyClusterResource.class);
+ router.attach("/storage/tables/json", StorageSourceTablesResource.class);
+ router.attach("/controller/summary/json", ControllerSummaryResource.class);
+ router.attach("/role/json", ControllerRoleResource.class);
+ router.attach("/health/json", HealthCheckResource.class);
+ router.attach("/system/uptime/json", SystemUptimeResource.class);
+ return router;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/CounterResource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/CounterResource.java
new file mode 100644
index 0000000..fb680d7
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/CounterResource.java
@@ -0,0 +1,70 @@
+/**
+* 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.web;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import net.floodlightcontroller.counter.CounterValue;
+import net.floodlightcontroller.counter.ICounter;
+
+import org.restlet.resource.Get;
+
+public class CounterResource extends CounterResourceBase {
+ @Get("json")
+ public Map<String, Object> retrieve() {
+ String counterTitle =
+ (String) getRequestAttributes().get("counterTitle");
+ Map<String, Object> model = new HashMap<String,Object>();
+ CounterValue v;
+ if (counterTitle.equalsIgnoreCase("all")) {
+ Map<String, ICounter> counters = this.counterStore.getAll();
+ if (counters != null) {
+ Iterator<Map.Entry<String, ICounter>> it =
+ counters.entrySet().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());
+ }
+ }
+ }
+ } else {
+ ICounter counter = this.counterStore.getCounter(counterTitle);
+ if (counter != null) {
+ v = counter.getCounterValue();
+ } else {
+ v = new CounterValue(CounterValue.CounterType.LONG);
+ }
+
+ if (CounterValue.CounterType.LONG == v.getType()) {
+ model.put(counterTitle, v.getLong());
+ } else if (v.getType() == CounterValue.CounterType.DOUBLE) {
+ model.put(counterTitle, v.getDouble());
+ }
+ }
+ return model;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/CounterResourceBase.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/CounterResourceBase.java
new file mode 100644
index 0000000..70e90ed
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/CounterResourceBase.java
@@ -0,0 +1,35 @@
+/**
+* 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.web;
+
+import net.floodlightcontroller.counter.ICounterStoreService;
+
+import org.restlet.resource.ResourceException;
+import org.restlet.resource.ServerResource;
+
+public class CounterResourceBase extends ServerResource {
+ protected ICounterStoreService counterStore;
+
+ @Override
+ protected void doInit() throws ResourceException {
+ super.doInit();
+ counterStore =
+ (ICounterStoreService)getContext().getAttributes().
+ get(ICounterStoreService.class.getCanonicalName());
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/EventHistoryTopologyClusterResource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/EventHistoryTopologyClusterResource.java
new file mode 100644
index 0000000..1be942c
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/EventHistoryTopologyClusterResource.java
@@ -0,0 +1,45 @@
+package net.floodlightcontroller.core.web;
+
+import net.floodlightcontroller.linkdiscovery.ILinkDiscoveryService;
+import net.floodlightcontroller.linkdiscovery.internal.EventHistoryTopologyCluster;
+import net.floodlightcontroller.linkdiscovery.internal.LinkDiscoveryManager;
+import net.floodlightcontroller.util.EventHistory;
+
+import org.restlet.resource.Get;
+import org.restlet.resource.ServerResource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @author subrata
+ *
+ */
+public class EventHistoryTopologyClusterResource extends ServerResource {
+ // TODO - Move this to the LinkDiscovery rest API
+ protected static Logger log =
+ LoggerFactory.getLogger(EventHistoryTopologyClusterResource.class);
+
+ @Get("json")
+ public EventHistory<EventHistoryTopologyCluster> handleEvHistReq() {
+
+ // Get the event history count. Last <count> events would be returned
+ String evHistCount = (String)getRequestAttributes().get("count");
+ int count = EventHistory.EV_HISTORY_DEFAULT_SIZE;
+ try {
+ count = Integer.parseInt(evHistCount);
+ }
+ catch(NumberFormatException nFE) {
+ // Invalid input for event count - use default value
+ }
+
+ LinkDiscoveryManager topoManager =
+ (LinkDiscoveryManager)getContext().getAttributes().
+ get(ILinkDiscoveryService.class.getCanonicalName());
+ if (topoManager != null) {
+ return new EventHistory<EventHistoryTopologyCluster>(
+ topoManager.evHistTopologyCluster, count);
+ }
+
+ return null;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/EventHistoryTopologyLinkResource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/EventHistoryTopologyLinkResource.java
new file mode 100644
index 0000000..4a21070
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/EventHistoryTopologyLinkResource.java
@@ -0,0 +1,45 @@
+package net.floodlightcontroller.core.web;
+
+import net.floodlightcontroller.linkdiscovery.ILinkDiscoveryService;
+import net.floodlightcontroller.linkdiscovery.internal.EventHistoryTopologyLink;
+import net.floodlightcontroller.linkdiscovery.internal.LinkDiscoveryManager;
+import net.floodlightcontroller.util.EventHistory;
+
+import org.restlet.resource.Get;
+import org.restlet.resource.ServerResource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @author subrata
+ *
+ */
+public class EventHistoryTopologyLinkResource extends ServerResource {
+ // TODO - Move this to the DeviceManager Rest API
+ protected static Logger log =
+ LoggerFactory.getLogger(EventHistoryTopologyLinkResource.class);
+
+ @Get("json")
+ public EventHistory<EventHistoryTopologyLink> handleEvHistReq() {
+
+ // Get the event history count. Last <count> events would be returned
+ String evHistCount = (String)getRequestAttributes().get("count");
+ int count = EventHistory.EV_HISTORY_DEFAULT_SIZE;
+ try {
+ count = Integer.parseInt(evHistCount);
+ }
+ catch(NumberFormatException nFE) {
+ // Invalid input for event count - use default value
+ }
+
+ LinkDiscoveryManager linkDiscoveryManager =
+ (LinkDiscoveryManager)getContext().getAttributes().
+ get(ILinkDiscoveryService.class.getCanonicalName());
+ if (linkDiscoveryManager != null) {
+ return new EventHistory<EventHistoryTopologyLink>(
+ linkDiscoveryManager.evHistTopologyLink, count);
+ }
+
+ return null;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/EventHistoryTopologySwitchResource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/EventHistoryTopologySwitchResource.java
new file mode 100644
index 0000000..1c95e2c
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/EventHistoryTopologySwitchResource.java
@@ -0,0 +1,37 @@
+package net.floodlightcontroller.core.web;
+
+import net.floodlightcontroller.linkdiscovery.ILinkDiscoveryService;
+import net.floodlightcontroller.linkdiscovery.internal.EventHistoryTopologySwitch;
+import net.floodlightcontroller.linkdiscovery.internal.LinkDiscoveryManager;
+import net.floodlightcontroller.util.EventHistory;
+
+import org.restlet.resource.Get;
+import org.restlet.resource.ServerResource;
+
+/**
+ * @author subrata
+ *
+ */
+public class EventHistoryTopologySwitchResource extends ServerResource {
+
+ @Get("json")
+ public EventHistory<EventHistoryTopologySwitch> handleEvHistReq() {
+
+ // Get the event history count. Last <count> events would be returned
+ String evHistCount = (String)getRequestAttributes().get("count");
+ int count = EventHistory.EV_HISTORY_DEFAULT_SIZE;
+ try {
+ count = Integer.parseInt(evHistCount);
+ }
+ catch(NumberFormatException nFE) {
+ // Invalid input for event count - use default value
+ }
+
+ LinkDiscoveryManager topoManager =
+ (LinkDiscoveryManager)getContext().getAttributes().
+ get(ILinkDiscoveryService.class.getCanonicalName());
+
+ return new EventHistory<EventHistoryTopologySwitch>(
+ topoManager.evHistTopologySwitch, count);
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/HealthCheckResource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/HealthCheckResource.java
new file mode 100644
index 0000000..12ee545
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/HealthCheckResource.java
@@ -0,0 +1,36 @@
+package net.floodlightcontroller.core.web;
+
+import org.restlet.resource.Get;
+import org.restlet.resource.ServerResource;
+
+public class HealthCheckResource extends ServerResource {
+
+ public static class HealthCheckInfo {
+
+ protected boolean healthy;
+
+ public HealthCheckInfo() {
+ this.healthy = true;
+ }
+
+ public boolean isHealthy() {
+ return healthy;
+ }
+
+ public void setHealthy(boolean healthy) {
+ this.healthy = healthy;
+ }
+ }
+
+ @Get("json")
+ public HealthCheckInfo healthCheck() {
+ // Currently this is the simplest possible health check -- basically
+ // just that the controller is still running and able to respond to
+ // REST calls.
+ // Eventually this should be more sophisticated and do things
+ // like monitoring internal data structures of the controller
+ // (e.g. async storage queue length).
+ return new HealthCheckInfo();
+ }
+
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/LoadedModuleLoaderResource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/LoadedModuleLoaderResource.java
new file mode 100644
index 0000000..38367c3
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/LoadedModuleLoaderResource.java
@@ -0,0 +1,19 @@
+package net.floodlightcontroller.core.web;
+
+import java.util.Map;
+
+import org.restlet.resource.Get;
+
+import net.floodlightcontroller.core.module.ModuleLoaderResource;
+
+public class LoadedModuleLoaderResource extends ModuleLoaderResource {
+ /**
+ * Retrieves information about all modules available
+ * to Floodlight.
+ * @return Information about all modules available.
+ */
+ @Get("json")
+ public Map<String, Object> retrieve() {
+ return retrieveInternal(true);
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/PacketTraceResource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/PacketTraceResource.java
new file mode 100644
index 0000000..85da942
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/PacketTraceResource.java
@@ -0,0 +1,118 @@
+package net.floodlightcontroller.core.web;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.restlet.data.Status;
+import org.restlet.resource.Post;
+import org.restlet.resource.ServerResource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import net.floodlightcontroller.core.OFMessageFilterManager;
+
+public class PacketTraceResource extends ServerResource {
+ protected static Logger log = LoggerFactory.getLogger(PacketTraceResource.class);
+
+ public static class FilterParameters {
+
+ protected String sessionId = null;
+ protected String mac = null;
+ protected Integer period = null;
+ protected String direction = null;
+ protected String output = null;
+
+ public String getSessionId() {
+ return sessionId;
+ }
+ public void setSessionId(String sessionId) {
+ this.sessionId = sessionId;
+ }
+ public String getMac() {
+ return mac;
+ }
+ public void setMac(String mac) {
+ this.mac = mac;
+ }
+ public Integer getPeriod() {
+ return period;
+ }
+ public void setPeriod(Integer period) {
+ this.period = period;
+ }
+ public String getDirection() {
+ return direction;
+ }
+ public void setDirection(String direction) {
+ this.direction = direction;
+ }
+ public String getOutput() {
+ return output;
+ }
+ public void setOutput(String output) {
+ this.output = output;
+ }
+
+ public String toString() {
+ return "SessionID: " + sessionId +
+ "\tmac" + mac +
+ "\tperiod" + period +
+ "\tdirection" + direction +
+ "\toutput" + output;
+ }
+ }
+
+ public static class PacketTraceOutput {
+ protected String sessionId = null;
+
+ public String getSessionId() {
+ return sessionId;
+ }
+
+ public void setSessionId(String sessionId) {
+ this.sessionId = sessionId;
+ }
+ }
+
+ @Post("json")
+ public PacketTraceOutput packettrace(FilterParameters fp) {
+
+ ConcurrentHashMap <String,String> filter = new ConcurrentHashMap<String,String> ();
+ String sid = null;
+ PacketTraceOutput output = new PacketTraceOutput();
+ OFMessageFilterManager manager =
+ (OFMessageFilterManager)getContext()
+ .getAttributes().
+ get(OFMessageFilterManager.class.getCanonicalName());
+
+ if (manager == null) {
+ sid = null;
+ setStatus(Status.SERVER_ERROR_SERVICE_UNAVAILABLE);
+ }
+
+ if (fp.getSessionId() != null) {
+ filter.put("sessionId", fp.getSessionId());
+ }
+ if (fp.getMac() != null) {
+ filter.put("mac", fp.getMac());
+ }
+ if (fp.getDirection() != null) {
+ filter.put("direction", fp.getDirection());
+ }
+
+ if (filter.isEmpty()) {
+ setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
+ } else {
+ if (log.isDebugEnabled()) {
+ log.debug ("Call setupFilter: sid:{} filter:{}, period:{}",
+ new Object[] {fp.getSessionId(), filter,
+ fp.getPeriod()*1000});
+ }
+ sid = manager.setupFilter(fp.getSessionId(), filter,
+ fp.getPeriod()*1000);
+ output.setSessionId(sid);
+ setStatus(Status.SUCCESS_OK);
+ }
+
+ return output;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/RoleInfo.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/RoleInfo.java
new file mode 100644
index 0000000..e600ea0
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/RoleInfo.java
@@ -0,0 +1,26 @@
+package net.floodlightcontroller.core.web;
+
+import net.floodlightcontroller.core.IFloodlightProviderService.Role;
+
+public class RoleInfo {
+ protected String role;
+
+ public RoleInfo() {
+ }
+
+ public RoleInfo(String role) {
+ setRole(role);
+ }
+
+ public RoleInfo(Role role) {
+ this.role = (role != null) ? role.name() : "DISABLED";
+ }
+
+ public String getRole() {
+ return role;
+ }
+
+ public void setRole(String role) {
+ this.role = role;
+ }
+}
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/StorageSourceTablesResource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/StorageSourceTablesResource.java
new file mode 100644
index 0000000..51f514f
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/StorageSourceTablesResource.java
@@ -0,0 +1,18 @@
+package net.floodlightcontroller.core.web;
+
+import java.util.Set;
+
+import net.floodlightcontroller.storage.IStorageSourceService;
+
+import org.restlet.resource.Get;
+import org.restlet.resource.ServerResource;
+
+public class StorageSourceTablesResource extends ServerResource {
+ @Get("json")
+ public Set<String> retrieve() {
+ IStorageSourceService storageSource = (IStorageSourceService)getContext().
+ getAttributes().get(IStorageSourceService.class.getCanonicalName());
+ Set<String> allTableNames = storageSource.getAllTableNames();
+ return allTableNames;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/SwitchCounterCategoriesResource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/SwitchCounterCategoriesResource.java
new file mode 100644
index 0000000..f14d706
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/SwitchCounterCategoriesResource.java
@@ -0,0 +1,87 @@
+/**
+* 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.web;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.openflow.util.HexString;
+import org.restlet.resource.Get;
+
+import net.floodlightcontroller.core.IFloodlightProviderService;
+import net.floodlightcontroller.counter.CounterStore.NetworkLayer;
+import net.floodlightcontroller.counter.ICounterStoreService;
+
+/**
+ * Get the counter categories for a particular switch
+ * @author readams
+ */
+public class SwitchCounterCategoriesResource extends CounterResourceBase {
+ @Get("json")
+ public Map<String, Object> retrieve() {
+ IFloodlightProviderService floodlightProvider =
+ (IFloodlightProviderService)getContext().getAttributes().
+ get(IFloodlightProviderService.class.getCanonicalName());
+ HashMap<String,Object> model = new HashMap<String,Object>();
+
+ String switchID = (String) getRequestAttributes().get("switchId");
+ String counterName = (String) getRequestAttributes().get("counterName");
+ String layer = (String) getRequestAttributes().get("layer");
+
+ Long[] switchDpids;
+ if (switchID.equalsIgnoreCase("all")) {
+ switchDpids = floodlightProvider.getSwitches().keySet().toArray(new Long[0]);
+ for (Long dpid : switchDpids) {
+ switchID = HexString.toHexString(dpid);
+
+ getOneSwitchCounterCategoriesJson(model, switchID, counterName, layer);
+ }
+ } else {
+ getOneSwitchCounterCategoriesJson(model, switchID, counterName, layer);
+ }
+
+ return model;
+ }
+
+ protected void getOneSwitchCounterCategoriesJson(Map<String, Object> model,
+ String switchID,
+ String counterName,
+ String layer) {
+ String fullCounterName = "";
+ NetworkLayer nl = NetworkLayer.L3;
+
+ try {
+ counterName = URLDecoder.decode(counterName, "UTF-8");
+ layer = URLDecoder.decode(layer, "UTF-8");
+ fullCounterName = switchID + ICounterStoreService.TitleDelimitor + counterName;
+ } catch (UnsupportedEncodingException e) {
+ //Just leave counterTitle undecoded if there is an issue - fail silently
+ }
+
+ if (layer.compareToIgnoreCase("4") == 0) {
+ nl = NetworkLayer.L4;
+ }
+ List<String> categories = this.counterStore.getAllCategories(fullCounterName, nl);
+ if (categories != null) {
+ model.put(fullCounterName + "." + layer, categories);
+ }
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/SwitchCounterResource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/SwitchCounterResource.java
new file mode 100644
index 0000000..188836d
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/SwitchCounterResource.java
@@ -0,0 +1,83 @@
+/**
+* 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.web;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.openflow.util.HexString;
+import org.restlet.resource.Get;
+
+import net.floodlightcontroller.core.IFloodlightProviderService;
+import net.floodlightcontroller.counter.ICounter;
+import net.floodlightcontroller.counter.ICounterStoreService;
+
+/**
+ * Get counters for a particular switch
+ * @author readams
+ */
+public class SwitchCounterResource extends CounterResourceBase {
+ @Get("json")
+ public Map<String, Object> retrieve() {
+ IFloodlightProviderService floodlightProvider =
+ (IFloodlightProviderService)getContext().getAttributes().
+ get(IFloodlightProviderService.class.getCanonicalName());
+ HashMap<String,Object> model = new HashMap<String,Object>();
+
+ String switchID = (String) getRequestAttributes().get("switchId");
+ String counterName = (String) getRequestAttributes().get("counterName");
+
+ Long[] switchDpids;
+ if (switchID.equalsIgnoreCase("all")) {
+ switchDpids = floodlightProvider.getSwitches().keySet().toArray(new Long[0]);
+ getOneSwitchCounterJson(model, ICounterStoreService.CONTROLLER_NAME, counterName);
+ for (Long dpid : switchDpids) {
+ switchID = HexString.toHexString(dpid);
+
+ getOneSwitchCounterJson(model, switchID, counterName);
+ }
+ } else {
+ getOneSwitchCounterJson(model, switchID, counterName);
+ }
+ return model;
+ }
+
+ protected void getOneSwitchCounterJson(Map<String, Object> model,
+ String switchID, String counterName) {
+ String fullCounterName = "";
+
+ try {
+ counterName = URLDecoder.decode(counterName, "UTF-8");
+ fullCounterName =
+ switchID + ICounterStoreService.TitleDelimitor + counterName;
+ } catch (UnsupportedEncodingException e) {
+ //Just leave counterTitle undecoded if there is an issue - fail silently
+ }
+
+ ICounter counter = this.counterStore.getCounter(fullCounterName);
+ Map<String, Long> sample = new HashMap<String, Long> ();
+ if (counter != null) {
+ sample.put(counter.getCounterDate().toString(),
+ counter.getCounterValue().getLong());
+ model.put(switchID, sample);
+ }
+ }
+
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/SwitchResourceBase.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/SwitchResourceBase.java
new file mode 100644
index 0000000..d810024
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/SwitchResourceBase.java
@@ -0,0 +1,157 @@
+/**
+* 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.web;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.Future;
+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 org.restlet.resource.ResourceException;
+import org.restlet.resource.ServerResource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Base class for server resources related to switches
+ * @author readams
+ *
+ */
+public class SwitchResourceBase extends ServerResource {
+ protected static Logger log = LoggerFactory.getLogger(SwitchResourceBase.class);
+
+ public enum REQUESTTYPE {
+ OFSTATS,
+ OFFEATURES
+ }
+
+ @Override
+ protected void doInit() throws ResourceException {
+ super.doInit();
+
+ }
+
+ @LogMessageDoc(level="ERROR",
+ message="Failure retrieving statistics from switch {switch}",
+ explanation="An error occurred while retrieving statistics" +
+ "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.getSwitches().get(switchId);
+ Future<List<OFStatistics>> future;
+ List<OFStatistics> 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((short)OFPort.OFPP_NONE.getValue());
+ req.setStatistics(Collections.singletonList((OFStatistics)specificReq));
+ requestLength += specificReq.getLength();
+ } else if (statType == OFStatisticsType.QUEUE) {
+ OFQueueStatisticsRequest specificReq = new OFQueueStatisticsRequest();
+ specificReq.setPortNumber((short)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) {
+ // pass - nothing todo besides set the type above
+ }
+ req.setLengthU(requestLength);
+ try {
+ future = sw.getStatistics(req);
+ values = future.get(10, TimeUnit.SECONDS);
+ } catch (Exception e) {
+ log.error("Failure retrieving statistics from switch " + sw, e);
+ }
+ }
+ return values;
+ }
+
+ protected List<OFStatistics> getSwitchStatistics(String switchId, OFStatisticsType statType) {
+ return getSwitchStatistics(HexString.toLong(switchId), statType);
+ }
+
+ protected OFFeaturesReply getSwitchFeaturesReply(long switchId) {
+ IFloodlightProviderService floodlightProvider =
+ (IFloodlightProviderService)getContext().getAttributes().
+ get(IFloodlightProviderService.class.getCanonicalName());
+
+ IOFSwitch sw = floodlightProvider.getSwitches().get(switchId);
+ Future<OFFeaturesReply> future;
+ OFFeaturesReply featuresReply = null;
+ if (sw != null) {
+ try {
+ future = sw.getFeaturesReplyFromSwitch();
+ featuresReply = future.get(10, TimeUnit.SECONDS);
+ } catch (Exception e) {
+ log.error("Failure getting features reply from switch" + sw, e);
+ }
+ }
+
+ return featuresReply;
+ }
+
+ protected OFFeaturesReply getSwitchFeaturesReply(String switchId) {
+ return getSwitchFeaturesReply(HexString.toLong(switchId));
+ }
+
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/SwitchRoleResource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/SwitchRoleResource.java
new file mode 100644
index 0000000..0d73f93
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/SwitchRoleResource.java
@@ -0,0 +1,46 @@
+package net.floodlightcontroller.core.web;
+
+import java.util.HashMap;
+
+import org.openflow.util.HexString;
+import org.restlet.resource.ServerResource;
+
+import net.floodlightcontroller.core.IFloodlightProviderService;
+import net.floodlightcontroller.core.IOFSwitch;
+
+import org.restlet.resource.Get;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class SwitchRoleResource extends ServerResource {
+
+ protected static Logger log = LoggerFactory.getLogger(SwitchRoleResource.class);
+
+ @Get("json")
+ public Object getRole() {
+ IFloodlightProviderService floodlightProvider =
+ (IFloodlightProviderService)getContext().getAttributes().
+ get(IFloodlightProviderService.class.getCanonicalName());
+
+ String switchId = (String) getRequestAttributes().get("switchId");
+
+ RoleInfo roleInfo;
+
+ if (switchId.equalsIgnoreCase("all")) {
+ HashMap<String,RoleInfo> model = new HashMap<String,RoleInfo>();
+ for (IOFSwitch sw: floodlightProvider.getSwitches().values()) {
+ switchId = sw.getStringId();
+ roleInfo = new RoleInfo(sw.getRole());
+ model.put(switchId, roleInfo);
+ }
+ return model;
+ }
+
+ Long dpid = HexString.toLong(switchId);
+ IOFSwitch sw = floodlightProvider.getSwitches().get(dpid);
+ if (sw == null)
+ return null;
+ roleInfo = new RoleInfo(sw.getRole());
+ return roleInfo;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/SwitchStatisticsResource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/SwitchStatisticsResource.java
new file mode 100644
index 0000000..57771f7
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/SwitchStatisticsResource.java
@@ -0,0 +1,63 @@
+/**
+* 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.web;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.openflow.protocol.statistics.OFStatisticsType;
+import org.restlet.resource.Get;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Return switch statistics information for specific switches
+ * @author readams
+ */
+public class SwitchStatisticsResource extends SwitchResourceBase {
+ protected static Logger log =
+ LoggerFactory.getLogger(SwitchStatisticsResource.class);
+
+ @Get("json")
+ public Map<String, Object> retrieve() {
+ HashMap<String,Object> result = new HashMap<String,Object>();
+ Object values = null;
+
+ String switchId = (String) getRequestAttributes().get("switchId");
+ String statType = (String) getRequestAttributes().get("statType");
+
+ if (statType.equals("port")) {
+ values = getSwitchStatistics(switchId, OFStatisticsType.PORT);
+ } else if (statType.equals("queue")) {
+ values = getSwitchStatistics(switchId, OFStatisticsType.QUEUE);
+ } else if (statType.equals("flow")) {
+ values = getSwitchStatistics(switchId, OFStatisticsType.FLOW);
+ } else if (statType.equals("aggregate")) {
+ values = getSwitchStatistics(switchId, OFStatisticsType.AGGREGATE);
+ } else if (statType.equals("desc")) {
+ values = getSwitchStatistics(switchId, OFStatisticsType.DESC);
+ } else if (statType.equals("table")) {
+ values = getSwitchStatistics(switchId, OFStatisticsType.TABLE);
+ } else if (statType.equals("features")) {
+ values = getSwitchFeaturesReply(switchId);
+ }
+
+ result.put(switchId, values);
+ return result;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/SystemUptimeResource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/SystemUptimeResource.java
new file mode 100644
index 0000000..fe4b967
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/SystemUptimeResource.java
@@ -0,0 +1,31 @@
+package net.floodlightcontroller.core.web;
+
+import net.floodlightcontroller.core.IFloodlightProviderService;
+import org.restlet.resource.Get;
+import org.restlet.resource.ServerResource;
+
+
+
+public class SystemUptimeResource extends ServerResource {
+
+ public class UptimeRest {
+ long systemUptimeMsec;
+
+ public long getSystemUptimeMsec() {
+ return systemUptimeMsec;
+ }
+ }
+
+ @Get("json")
+ public UptimeRest retrieve() {
+ IFloodlightProviderService floodlightProvider =
+ (IFloodlightProviderService)getContext().getAttributes().
+ get(IFloodlightProviderService.class.getCanonicalName());
+
+ UptimeRest uptime = new UptimeRest();
+ uptime.systemUptimeMsec =
+ System.currentTimeMillis() - floodlightProvider.getSystemStartTime();
+
+ return (uptime);
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/serializers/ByteArrayMACSerializer.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/serializers/ByteArrayMACSerializer.java
new file mode 100644
index 0000000..66c33f5
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/serializers/ByteArrayMACSerializer.java
@@ -0,0 +1,40 @@
+/**
+* Copyright 2011,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.web.serializers;
+
+import java.io.IOException;
+
+import org.codehaus.jackson.JsonGenerator;
+import org.codehaus.jackson.JsonProcessingException;
+import org.codehaus.jackson.map.JsonSerializer;
+import org.codehaus.jackson.map.SerializerProvider;
+import org.openflow.util.HexString;
+
+/**
+ * Serialize a MAC as colon-separated hexadecimal
+ */
+public class ByteArrayMACSerializer extends JsonSerializer<byte[]> {
+
+ @Override
+ public void serialize(byte[] mac, JsonGenerator jGen,
+ SerializerProvider serializer)
+ throws IOException, JsonProcessingException {
+ jGen.writeString(HexString.toHexString(mac));
+ }
+
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/serializers/DPIDSerializer.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/serializers/DPIDSerializer.java
new file mode 100644
index 0000000..e74cc01
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/serializers/DPIDSerializer.java
@@ -0,0 +1,40 @@
+/**
+* Copyright 2011,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.web.serializers;
+
+import java.io.IOException;
+
+import org.codehaus.jackson.JsonGenerator;
+import org.codehaus.jackson.JsonProcessingException;
+import org.codehaus.jackson.map.JsonSerializer;
+import org.codehaus.jackson.map.SerializerProvider;
+import org.openflow.util.HexString;
+
+/**
+ * Serialize a DPID as colon-separated hexadecimal
+ */
+public class DPIDSerializer extends JsonSerializer<Long> {
+
+ @Override
+ public void serialize(Long dpid, JsonGenerator jGen,
+ SerializerProvider serializer)
+ throws IOException, JsonProcessingException {
+ jGen.writeString(HexString.toHexString(dpid, 8));
+ }
+
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/serializers/IPv4Serializer.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/serializers/IPv4Serializer.java
new file mode 100644
index 0000000..f4a5877
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/serializers/IPv4Serializer.java
@@ -0,0 +1,41 @@
+/**
+* Copyright 2011,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.web.serializers;
+
+import java.io.IOException;
+
+import net.floodlightcontroller.packet.IPv4;
+
+import org.codehaus.jackson.JsonGenerator;
+import org.codehaus.jackson.JsonProcessingException;
+import org.codehaus.jackson.map.JsonSerializer;
+import org.codehaus.jackson.map.SerializerProvider;
+
+/**
+ * Serialize an integer as an IPv4 Address in dotted decimal format
+ */
+public class IPv4Serializer extends JsonSerializer<Integer> {
+
+ @Override
+ public void serialize(Integer i, JsonGenerator jGen,
+ SerializerProvider serializer)
+ throws IOException, JsonProcessingException {
+ jGen.writeString(IPv4.fromIPv4Address(i));
+ }
+
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/serializers/MACSerializer.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/serializers/MACSerializer.java
new file mode 100644
index 0000000..a7c9fb7
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/serializers/MACSerializer.java
@@ -0,0 +1,40 @@
+/**
+* Copyright 2011,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.web.serializers;
+
+import java.io.IOException;
+
+import org.codehaus.jackson.JsonGenerator;
+import org.codehaus.jackson.JsonProcessingException;
+import org.codehaus.jackson.map.JsonSerializer;
+import org.codehaus.jackson.map.SerializerProvider;
+import org.openflow.util.HexString;
+
+/**
+ * Serialize a MAC as colon-separated hexadecimal
+ */
+public class MACSerializer extends JsonSerializer<Long> {
+
+ @Override
+ public void serialize(Long dpid, JsonGenerator jGen,
+ SerializerProvider serializer)
+ throws IOException, JsonProcessingException {
+ jGen.writeString(HexString.toHexString(dpid, 6));
+ }
+
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/serializers/UShortSerializer.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/serializers/UShortSerializer.java
new file mode 100644
index 0000000..c125c76
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/core/web/serializers/UShortSerializer.java
@@ -0,0 +1,40 @@
+/**
+* Copyright 2011,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.web.serializers;
+
+import java.io.IOException;
+
+import org.codehaus.jackson.JsonGenerator;
+import org.codehaus.jackson.JsonProcessingException;
+import org.codehaus.jackson.map.JsonSerializer;
+import org.codehaus.jackson.map.SerializerProvider;
+
+/**
+ * Serialize a short value as an unsigned short
+ */
+public class UShortSerializer extends JsonSerializer<Short> {
+
+ @Override
+ public void serialize(Short s, JsonGenerator jGen,
+ SerializerProvider serializer) throws IOException,
+ JsonProcessingException {
+ if (s == null) jGen.writeNull();
+ else jGen.writeNumber(s.shortValue() & 0xffff);
+ }
+
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/counter/ConcurrentCounter.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/counter/ConcurrentCounter.java
new file mode 100644
index 0000000..cdec1e0
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/counter/ConcurrentCounter.java
@@ -0,0 +1,205 @@
+/**
+* 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.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Queue;
+import java.util.Set;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+import net.floodlightcontroller.counter.CounterValue.CounterType;
+
+
+/**
+ * This module needs to be updated with CounterValue.
+ *
+ * This is a crumby attempt at a highly concurrent implementation of the Counter interface.
+ *
+ * (Help! Help! Someone please re-write me! This will almost certainly break at high loads.)
+ *
+ * The gist is that this class, ConcurrentCounter, keeps an internal highly transient buffer that is occasionally flushed
+ * in to a set of CountBuffers (circular buffers) which store a longer term historical view of the count values at different
+ * moments in time.
+ *
+ * This Counter implementation may be a bit over-engineered... The goal here was to present an implementation that is very
+ * predictable with respect to memory and CPU time and, at the same time, present a very fast increment() method. The reasoning
+ * here is that this will be a go-to class when it comes to debugging, particularly in high-load situations where logging
+ * may introduce so much variability to the system that it foils the results.
+ *
+ * @author kyle
+ *
+ */
+public class ConcurrentCounter implements ICounter {
+
+ protected static final Map<DateSpan, Integer> MAX_HISTORY = new HashMap<DateSpan, Integer>();
+ static {
+ MAX_HISTORY.put(DateSpan.REALTIME, new Integer(1));
+ MAX_HISTORY.put(DateSpan.SECONDS, new Integer(120));
+ MAX_HISTORY.put(DateSpan.MINUTES, new Integer(60));
+ MAX_HISTORY.put(DateSpan.HOURS, new Integer(48));
+ MAX_HISTORY.put(DateSpan.DAYS, new Integer(60));
+ MAX_HISTORY.put(DateSpan.WEEKS, new Integer(2));
+ }
+
+ protected static Set<ConcurrentCounter> liveCounters;
+
+ static {
+ liveCounters = Collections.newSetFromMap(new ConcurrentHashMap<ConcurrentCounter, Boolean>()); //nifty way to get concurrent hash set
+ //Set a background thread to flush any liveCounters every 100 milliseconds
+ Executors.newScheduledThreadPool(1).scheduleAtFixedRate(new Runnable() {
+ public void run() {
+ for(ConcurrentCounter c : liveCounters) {
+ c.flush();
+ }
+ }}, 100, 100, TimeUnit.MILLISECONDS);
+ }
+
+ /**
+ * Very simple data structure to store off a single count entry at a single point in time
+ * @author kyle
+ *
+ */
+ protected static final class CountAtom {
+ protected Date date;
+ protected Long delta;
+
+ protected CountAtom(Date date, Long delta) {
+ this.date = date;
+ this.delta = delta;
+ }
+
+ public String toString() {
+ return "[" + this.date + ": " + this.delta + "]";
+ }
+ }
+
+
+ protected Queue<CountAtom> unprocessedCountBuffer;
+ protected Map<DateSpan, CountBuffer> counts;
+ protected Date startDate;
+
+ /**
+ * Factory method to create a new counter instance. (Design note -
+ * use a factory pattern here as it may be necessary to hook in other
+ * registrations around counter objects as they are created.)
+ *
+ * @param startDate
+ * @return
+ */
+ public static ICounter createCounter(Date startDate) {
+ ConcurrentCounter cc = new ConcurrentCounter(startDate);
+ ConcurrentCounter.liveCounters.add(cc);
+ return cc;
+
+ }
+
+ /**
+ * Protected constructor - use createCounter factory method instead
+ * @param startDate
+ */
+ protected ConcurrentCounter(Date startDate) {
+ init(startDate);
+ }
+
+ protected void init(Date startDate) {
+ this.startDate = startDate;
+ this.unprocessedCountBuffer = new ConcurrentLinkedQueue<CountAtom>();
+ this.counts = new HashMap<DateSpan, CountBuffer>();
+
+ for(DateSpan ds : DateSpan.values()) {
+ CountBuffer cb = new CountBuffer(startDate, ds, MAX_HISTORY.get(ds));
+ counts.put(ds, cb);
+ }
+ }
+ /**
+ * This is the key method that has to be both fast and very thread-safe.
+ */
+ @Override
+ public void increment() {
+ this.increment(new Date(), (long)1);
+ }
+
+ @Override
+ public void increment(Date d, long delta) {
+ this.unprocessedCountBuffer.add(new CountAtom(d, delta));
+ }
+
+ @Override
+ public void setCounter(Date d, CounterValue value) {
+ // To be done later
+ }
+
+ /**
+ * Reset the value.
+ */
+ @Override
+ public void reset(Date startDate) {
+ init(startDate);
+ }
+
+ /**
+ * Flushes values out of the internal buffer and in to structures
+ * that can be fetched with a call to snapshot()
+ */
+ public synchronized void flush() {
+ for(CountAtom c = this.unprocessedCountBuffer.poll(); c != null; c = this.unprocessedCountBuffer.poll()) {
+ for(DateSpan ds : DateSpan.values()) {
+ CountBuffer cb = counts.get(ds);
+ cb.increment(c.date, c.delta);
+ }
+ }
+ }
+
+ @Override
+ public CounterValue getCounterValue() {
+ // To be done later
+ //CountSeries cs = counts.get(DateSpan.REALTIME).snapshot();
+ //return cs.getSeries()[0];
+ return new CounterValue(CounterType.LONG);
+ }
+
+ @Override
+ public Date getCounterDate() {
+ // To be done later
+ //CountSeries cs = counts.get(DateSpan.REALTIME).snapshot();
+ //return cs.getSeries()[0];
+ return new Date();
+ }
+
+ @Override
+ /**
+ * This method returns a disconnected copy of the underlying CountSeries corresponding to dateSpan.
+ */
+ public CountSeries snapshot(DateSpan dateSpan) {
+ flush();
+ CountSeries cs = counts.get(dateSpan).snapshot();
+ return cs;
+ }
+
+
+
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/counter/CountBuffer.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/counter/CountBuffer.java
new file mode 100644
index 0000000..fa45862
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/counter/CountBuffer.java
@@ -0,0 +1,125 @@
+/**
+* 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;
+
+import net.floodlightcontroller.counter.ICounter.DateSpan;
+
+
+/**
+ * Implements a circular buffer to store the last x time-based counter values. This is pretty crumby
+ * implementation, basically wrapping everything with synchronized blocks, in order to ensure that threads
+ * which will be updating the series don't result in a thread which is reading the series getting stuck with
+ * a start date which does not correspond to the count values in getSeries.
+ *
+ * This could probably use a re-think...
+ *
+ * @author kyle
+ *
+ */
+public class CountBuffer {
+ protected long[] counterValues;
+ protected Date startDate;
+ protected DateSpan dateSpan;
+ protected int currentIndex;
+ protected int seriesLength;
+
+
+ public CountBuffer(Date startDate, DateSpan dateSpan, int seriesLength) {
+ this.seriesLength = seriesLength;
+ this.counterValues = new long[seriesLength];
+ this.dateSpan = dateSpan;
+
+ this.startDate = startDate;
+ this.currentIndex = 0;
+ }
+
+ /**
+ * Increment the count associated with Date d, forgetting some of the older count values if necessary to ensure
+ * that the total span of time covered by this series corresponds to DateSpan * seriesLength (circular buffer).
+ *
+ * Note - fails silently if the Date falls prior to the start of the tracked count values.
+ *
+ * Note - this should be a reasonably fast method, though it will have to block if there is another thread reading the
+ * series at the same time.
+ *
+ * @param d
+ * @param delta
+ */
+ public synchronized void increment(Date d, long delta) {
+
+ long dsMillis = CountSeries.dateSpanToMilliseconds(this.dateSpan);
+ Date endDate = new Date(startDate.getTime() + seriesLength * dsMillis - 1);
+
+ if(d.getTime() < startDate.getTime()) {
+ return; //silently fail rather than insert a count at a time older than the history buffer we're keeping
+ }
+ else if (d.getTime() >= startDate.getTime() && d.getTime() <= endDate.getTime()) {
+ int index = (int) (( d.getTime() - startDate.getTime() ) / dsMillis); // java rounds down on long/long
+ int modIndex = (index + currentIndex) % seriesLength;
+ long currentValue = counterValues[modIndex];
+ counterValues[modIndex] = currentValue + delta;
+ }
+ else if (d.getTime() > endDate.getTime()) {
+ //Initialize new buckets
+ int newBuckets = (int)((d.getTime() - endDate.getTime()) / dsMillis) + 1; // java rounds down on long/long
+ for(int i = 0; i < newBuckets; i++) {
+ int modIndex = (i + currentIndex) % seriesLength;
+ counterValues[modIndex] = 0;
+ }
+ //Update internal vars
+ this.startDate = new Date(startDate.getTime() + dsMillis * newBuckets);
+ this.currentIndex = (currentIndex + newBuckets) % this.seriesLength;
+
+ //Call again (date should be in the range this time)
+ this.increment(d, delta);
+ }
+ }
+
+ /**
+ * Relatively slow method, expected to be called primarily from UI rather than from in-packet-path.
+ *
+ * @return the count values associated with each time interval starting with startDate and demarc'ed by dateSpan
+ */
+ public long[] getSeries() { //synchronized here should lock on 'this', implying that it shares the lock with increment
+ long[] ret = new long[this.seriesLength];
+ for(int i = 0; i < this.seriesLength; i++) {
+ int modIndex = (currentIndex + i) % this.seriesLength;
+ ret[i] = this.counterValues[modIndex];
+ }
+ return ret;
+ }
+
+
+ /**
+ * Returns an immutable count series that represents a snapshot of this
+ * series at a specific moment in time.
+ * @return
+ */
+ public synchronized CountSeries snapshot() {
+ long[] cvs = new long[this.seriesLength];
+ for(int i = 0; i < this.seriesLength; i++) {
+ int modIndex = (this.currentIndex + i) % this.seriesLength;
+ cvs[i] = this.counterValues[modIndex];
+ }
+
+ return new CountSeries(this.startDate, this.dateSpan, cvs);
+ }
+
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/counter/CountSeries.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/counter/CountSeries.java
new file mode 100644
index 0000000..e8a547a
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/counter/CountSeries.java
@@ -0,0 +1,88 @@
+/**
+* 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.Arrays;
+import java.util.Date;
+
+import net.floodlightcontroller.counter.ICounter.DateSpan;
+
+/**
+ * Simple immutable class to store a series of historic counter values
+ *
+ * This could probably use a re-think...
+ *
+ * @author kyle
+ *
+ */
+public class CountSeries {
+ protected long[] counterValues;
+ protected Date startDate;
+ protected DateSpan dateSpan;
+
+ public CountSeries(Date startDate, DateSpan dateSpan, long[] counterValues) {
+ this.counterValues = counterValues.clone();
+ this.dateSpan = dateSpan;
+ this.startDate = startDate;
+ }
+
+
+ public long[] getSeries() { //synchronized here should lock on 'this', implying that it shares the lock with increment
+ return this.counterValues.clone();
+ }
+
+ /**
+ * Returns the startDate of this series. The first long in getSeries represents the sum of deltas from increment calls with dates
+ * that correspond to >= startDate and < startDate + DateSpan.
+ * @return
+ */
+ public Date getStartDate() {//synchronized here should lock on 'this', implying that it shares the lock with increment
+ return this.startDate;
+ }
+
+ public String toString() {
+ String ret = "{start: " + this.startDate + ", span: " + this.dateSpan + ", series: " + Arrays.toString(getSeries()) + "}";
+ return ret;
+ }
+
+ /**
+ * Return a long that is the number of milliseconds in a ds (second/minute/hour/day/week). (Utility method.)
+ *
+ * @param ds
+ * @return
+ */
+ public static final long dateSpanToMilliseconds(DateSpan ds) {
+ long delta = 1;
+ switch(ds) {
+ case WEEKS:
+ delta *= 7;
+ case DAYS:
+ delta *= 24;
+ case HOURS:
+ delta *= 60;
+ case MINUTES:
+ delta *= 60;
+ case SECONDS:
+ delta *= 1000;
+ default:
+ break;
+ }
+ return delta;
+ }
+
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/counter/CounterStore.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/counter/CounterStore.java
new file mode 100644
index 0000000..26d1302
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/counter/CounterStore.java
@@ -0,0 +1,461 @@
+/**
+ * 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.
+ **/
+
+/**
+ * Implements a very simple central store for system counters
+ */
+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 java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+import javax.annotation.PostConstruct;
+
+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;
+
+
+/**
+ * @author kyle
+ *
+ */
+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;
+ }
+
+ /**
+ * A map of counterName --> Counter
+ */
+ protected ConcurrentHashMap<String, CounterEntry> nameToCEIndex =
+ new ConcurrentHashMap<String, CounterEntry>();
+
+ protected ICounter heartbeatCounter;
+ protected ICounter randomCounter;
+
+ /**
+ * Counter Categories grouped by network layers
+ * NetworkLayer -> CounterToCategories
+ */
+ protected static Map<NetworkLayer, Map<String, List<String>>> layeredCategories =
+ new ConcurrentHashMap<NetworkLayer, Map<String, List<String>>> ();
+
+ public void updatePacketInCounters(IOFSwitch sw, OFMessage m, Ethernet eth) {
+ OFPacketIn packet = (OFPacketIn)m;
+
+ // Make sure there is data
+ if (packet.getPacketData().length <= 0) return;
+
+ /* Extract the etherType and protocol field for IPv4 packet.
+ */
+ String etherType = String.format("%04x", eth.getEtherType());
+
+ /*
+ * Valid EtherType must be greater than or equal to 0x0600
+ * It is V1 Ethernet Frame if EtherType < 0x0600
+ */
+ if (eth.getEtherType() < 0x0600) {
+ etherType = "0599";
+ }
+
+ if (TypeAliases.l3TypeAliasMap != null &&
+ TypeAliases.l3TypeAliasMap.containsKey(etherType)) {
+ etherType = TypeAliases.l3TypeAliasMap.get(etherType);
+ } else {
+ etherType = "L3_" + etherType;
+ }
+ String switchIdHex = sw.getStringId();
+
+ String packetName = m.getType().toClass().getName();
+ packetName = packetName.substring(packetName.lastIndexOf('.')+1);
+
+ // Construct controller counter for the packet_in
+ String controllerCounterName =
+ CounterStore.createCounterName(CONTROLLER_NAME,
+ -1,
+ packetName);
+
+ String controllerL3CategoryCounterName =
+ CounterStore.createCounterName(CONTROLLER_NAME,
+ -1,
+ packetName,
+ etherType,
+ NetworkLayer.L3);
+
+ String l2Type = null;
+ if (eth.isBroadcast()) {
+ l2Type = BROADCAST;
+ } else if (eth.isMulticast()) {
+ l2Type = MULTICAST;
+ } else {
+ l2Type = UNICAST;
+ }
+
+ // Construct both port and switch L3 counter for the packet_in
+ String controllerL2CategoryCounterName = CounterStore.createCounterName(CONTROLLER_NAME,
+ -1,
+ packetName,
+ l2Type,
+ NetworkLayer.L2);
+ String switchL2CategoryCounterName = CounterStore.createCounterName(switchIdHex,
+ -1,
+ packetName,
+ l2Type,
+ NetworkLayer.L2);
+ String portL2CategoryCounterName = CounterStore.createCounterName(switchIdHex,
+ packet.getInPort(),
+ packetName,
+ l2Type,
+ NetworkLayer.L2);
+
+ // Construct both port and switch L3 counter for the packet_in
+ String portCounterName =
+ CounterStore.createCounterName(switchIdHex,
+ packet.getInPort(),
+ packetName);
+ String switchCounterName =
+ CounterStore.createCounterName(switchIdHex,
+ -1,
+ packetName);
+
+ String portL3CategoryCounterName =
+ CounterStore.createCounterName(switchIdHex,
+ packet.getInPort(),
+ packetName,
+ etherType,
+ NetworkLayer.L3);
+ String switchL3CategoryCounterName =
+ CounterStore.createCounterName(switchIdHex,
+ -1,
+ packetName,
+ etherType,
+ NetworkLayer.L3);
+
+ // Controller counters
+ ICounter controllerCounter = getCounter(controllerCounterName);
+ if (controllerCounter == null) {
+ controllerCounter = createCounter(controllerCounterName,
+ CounterType.LONG);
+ }
+ controllerCounter.increment();
+ ICounter portCounter = getCounter(portCounterName);
+ if (portCounter == null) {
+ portCounter = createCounter(portCounterName,
+ CounterType.LONG);
+ }
+ portCounter.increment();
+ ICounter switchCounter = getCounter(switchCounterName);
+ if (switchCounter == null) {
+ switchCounter = createCounter(switchCounterName,
+ CounterType.LONG);
+ }
+ switchCounter.increment();
+
+ // L2 counters
+ ICounter controllerL2Counter = getCounter(controllerL2CategoryCounterName);
+ if (controllerL2Counter == null) {
+ controllerL2Counter = createCounter(controllerL2CategoryCounterName,
+ CounterType.LONG);
+ }
+ controllerL2Counter.increment();
+ ICounter switchL2Counter = getCounter(switchL2CategoryCounterName);
+ if (switchL2Counter == null) {
+ switchL2Counter = createCounter(switchL2CategoryCounterName,
+ CounterType.LONG);
+ }
+ switchL2Counter.increment();
+ ICounter portL2Counter = getCounter(portL2CategoryCounterName);
+ if (portL2Counter == null) {
+ portL2Counter = createCounter(portL2CategoryCounterName,
+ CounterType.LONG);
+ }
+ portL2Counter.increment();
+
+ // L3 counters
+ ICounter controllerL3Counter = getCounter(controllerL3CategoryCounterName);
+ if (controllerL3Counter == null) {
+ controllerL3Counter = createCounter(controllerL3CategoryCounterName,
+ CounterType.LONG);
+ }
+ controllerL3Counter.increment();
+ ICounter portL3Counter = getCounter(portL3CategoryCounterName);
+ if (portL3Counter == null) {
+ portL3Counter = createCounter(portL3CategoryCounterName,
+ CounterType.LONG);
+ }
+ portL3Counter.increment();
+ ICounter switchL3Counter = getCounter(switchL3CategoryCounterName);
+ if (switchL3Counter == null) {
+ switchL3Counter = createCounter(switchL3CategoryCounterName,
+ CounterType.LONG);
+ }
+ switchL3Counter.increment();
+
+ // L4 counters
+ if (etherType.compareTo(CounterStore.L3ET_IPV4) == 0) {
+ IPv4 ipV4 = (IPv4)eth.getPayload();
+ String l4Type = String.format("%02x", ipV4.getProtocol());
+ if (TypeAliases.l4TypeAliasMap != null &&
+ TypeAliases.l4TypeAliasMap.containsKey(l4Type)) {
+ l4Type = TypeAliases.l4TypeAliasMap.get(l4Type);
+ } else {
+ l4Type = "L4_" + l4Type;
+ }
+ String controllerL4CategoryCounterName =
+ CounterStore.createCounterName(CONTROLLER_NAME,
+ -1,
+ packetName,
+ l4Type,
+ NetworkLayer.L4);
+ String portL4CategoryCounterName =
+ CounterStore.createCounterName(switchIdHex,
+ packet.getInPort(),
+ packetName,
+ l4Type,
+ NetworkLayer.L4);
+ String switchL4CategoryCounterName =
+ CounterStore.createCounterName(switchIdHex,
+ -1,
+ packetName,
+ l4Type,
+ NetworkLayer.L4);
+ ICounter controllerL4Counter = getCounter(controllerL4CategoryCounterName);
+ if (controllerL4Counter == null) {
+ controllerL4Counter = createCounter(controllerL4CategoryCounterName,
+ CounterType.LONG);
+ }
+ controllerL4Counter.increment();
+ ICounter portL4Counter = getCounter(portL4CategoryCounterName);
+ if (portL4Counter == null) {
+ portL4Counter = createCounter(portL4CategoryCounterName,
+ CounterType.LONG);
+ }
+ portL4Counter.increment();
+ ICounter switchL4Counter = getCounter(switchL4CategoryCounterName);
+ if (switchL4Counter == null) {
+ switchL4Counter = createCounter(switchL4CategoryCounterName,
+ CounterType.LONG);
+ }
+ switchL4Counter.increment();
+ }
+ }
+
+ /**
+ * This method can only be used to update packetOut and flowmod counters
+ *
+ * @param sw
+ * @param ofMsg
+ */
+ public void updatePktOutFMCounterStore(IOFSwitch sw, OFMessage ofMsg) {
+ String packetName = ofMsg.getType().toClass().getName();
+ packetName = packetName.substring(packetName.lastIndexOf('.')+1);
+ // flowmod is per switch and controller. portid = -1
+ String controllerFMCounterName = CounterStore.createCounterName(CONTROLLER_NAME, -1, packetName);
+ ICounter counter = getCounter(controllerFMCounterName);
+ if (counter == null) {
+ counter = createCounter(controllerFMCounterName, CounterValue.CounterType.LONG);
+ }
+ counter.increment();
+
+ String switchFMCounterName = CounterStore.createCounterName(sw.getStringId(), -1, packetName);
+ counter = getCounter(switchFMCounterName);
+ if (counter == null) {
+ counter = createCounter(switchFMCounterName, CounterValue.CounterType.LONG);
+ }
+ counter.increment();
+ }
+
+
+ /**
+ * 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;
+ }
+ }
+
+ /**
+ * 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
+ */
+ public 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;
+ }
+
+ @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;
+ }
+
+ @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;
+ }
+
+ /**
+ * Post construction init method to kick off the health check and random (test) counter threads
+ */
+ @PostConstruct
+ public void startUp() {
+ this.heartbeatCounter = this.createCounter("CounterStore heartbeat", CounterValue.CounterType.LONG);
+ this.randomCounter = this.createCounter("CounterStore random", CounterValue.CounterType.LONG);
+ //Set a background thread to flush any liveCounters every 100 milliseconds
+ Executors.newScheduledThreadPool(1).scheduleAtFixedRate(new Runnable() {
+ public void run() {
+ heartbeatCounter.increment();
+ randomCounter.increment(new Date(), (long) (Math.random() * 100)); //TODO - pull this in to random timing
+ }}, 100, 100, TimeUnit.MILLISECONDS);
+ }
+
+ @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 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/ext/floodlight/src/main/java/net/floodlightcontroller/counter/CounterValue.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/counter/CounterValue.java
new file mode 100644
index 0000000..1852d5c
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/counter/CounterValue.java
@@ -0,0 +1,102 @@
+/**
+* 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/ext/floodlight/src/main/java/net/floodlightcontroller/counter/ICounter.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/counter/ICounter.java
new file mode 100644
index 0000000..625bebd
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/counter/ICounter.java
@@ -0,0 +1,80 @@
+/**
+* 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 testing - no performance guarantees
+ */
+ 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);
+
+ /**
+ * Returns a CountSeries that is a snapshot of the counter's values for the given dateSpan. (Further changes
+ * to this counter won't be reflected in the CountSeries that comes back.)
+ *
+ * @param dateSpan
+ * @return
+ */
+ public CountSeries snapshot(DateSpan dateSpan);
+
+
+ public static enum DateSpan {
+ REALTIME,
+ SECONDS,
+ MINUTES,
+ HOURS,
+ DAYS,
+ WEEKS
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/counter/ICounterStoreService.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/counter/ICounterStoreService.java
new file mode 100644
index 0000000..c89eee0
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/counter/ICounterStoreService.java
@@ -0,0 +1,71 @@
+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 updatePacketInCounters(IOFSwitch sw, OFMessage m, Ethernet eth);
+
+ /**
+ * This method can only be used to update packetOut and flowmod counters
+ *
+ * @param sw
+ * @param ofMsg
+ */
+ public void updatePktOutFMCounterStore(IOFSwitch sw, OFMessage ofMsg);
+
+ /**
+ * 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/ext/floodlight/src/main/java/net/floodlightcontroller/counter/NullCounterStore.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/counter/NullCounterStore.java
new file mode 100644
index 0000000..fed8c1e
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/counter/NullCounterStore.java
@@ -0,0 +1,104 @@
+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 updatePacketInCounters(IOFSwitch sw, OFMessage m, Ethernet eth) {
+ // no-op
+ }
+
+ @Override
+ public void updatePktOutFMCounterStore(IOFSwitch sw, OFMessage ofMsg) {
+ // 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/ext/floodlight/src/main/java/net/floodlightcontroller/counter/SimpleCounter.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/counter/SimpleCounter.java
new file mode 100644
index 0000000..01a0428
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/counter/SimpleCounter.java
@@ -0,0 +1,137 @@
+/**
+* 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(), (long)1);
+ }
+
+ @Override
+ synchronized public void increment(Date d, long delta) {
+ this.samplingTime = d;
+ this.counter.increment(delta);
+ }
+
+ 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());
+ }
+
+ @Override
+ /**
+ * This method only returns the real-time value.
+ */
+ synchronized public CountSeries snapshot(DateSpan dateSpan) {
+ long[] values = new long[1];
+ values[0] = this.counter.getLong();
+ return new CountSeries(this.samplingTime, DateSpan.DAYS, values);
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/counter/TypeAliases.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/counter/TypeAliases.java
new file mode 100644
index 0000000..0d7e2b5
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/counter/TypeAliases.java
@@ -0,0 +1,190 @@
+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/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/IDevice.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/IDevice.java
new file mode 100644
index 0000000..95969f8
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/IDevice.java
@@ -0,0 +1,98 @@
+/**
+* Copyright 2011,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.devicemanager;
+
+import java.util.Date;
+
+
+/**
+ * Represents an independent device on the network. A device consists of a
+ * set of entities, and all the information known about a given device comes
+ * only from merging all associated entities for that device.
+ * @author readams
+ */
+public interface IDevice {
+ /**
+ * Get the primary key for this device.
+ * @return the primary key
+ */
+ public Long getDeviceKey();
+
+ /**
+ * Get the MAC address of the device as a Long value.
+ * @return the MAC address for the device
+ */
+ public long getMACAddress();
+
+ /**
+ * Get the MAC address of the device as a String value.
+ * @return the MAC address for the device
+ */
+ public String getMACAddressString();
+
+ /**
+ * Get all unique VLAN IDs for the device. If the device has untagged
+ * entities, then the value -1 will be returned.
+ * @return an array containing all unique VLAN IDs for the device.
+ */
+ public Short[] getVlanId();
+
+ /**
+ * Get all unique IPv4 addresses associated with the device.
+ * @return an array containing the unique IPv4 addresses for the device.
+ */
+ public Integer[] getIPv4Addresses();
+
+ /**
+ * Get all unique attachment points associated with the device. This will
+ * not include any blocked attachment points.
+ * @return an array containing all unique attachment points for the device
+ */
+ public SwitchPort[] getAttachmentPoints();
+
+ /**
+ * Get all unique attachment points associated with the device.
+ * @param includeError whether to include blocked attachment points.
+ * Blocked attachment points should not be used for forwarding, but
+ * could be useful to show to a user
+ * @return an array containing all unique attachment points for the device
+ */
+ public SwitchPort[] getAttachmentPoints(boolean includeError);
+
+ /**
+ * Returns all unique VLAN IDs for the device that were observed on
+ * the given switch port
+ * @param swp the switch port to query
+ * @return an array containing the unique VLAN IDs
+ */
+ public Short[] getSwitchPortVlanIds(SwitchPort swp);
+
+ /**
+ * Get the most recent timestamp for this device
+ * @return the last seen timestamp
+ */
+ public Date getLastSeen();
+
+ /**
+ * Get the entity class for the device.
+ * @return the entity class
+ * @see IEntityClassifierService
+ */
+ public IEntityClass getEntityClass();
+
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/IDeviceListener.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/IDeviceListener.java
new file mode 100644
index 0000000..3c3d599
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/IDeviceListener.java
@@ -0,0 +1,61 @@
+/**
+* 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.devicemanager;
+
+/**
+ * Implementors of this interface can receive updates from DeviceManager about
+ * the state of devices under its control.
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public interface IDeviceListener {
+ /**
+ * Called when a new Device is found
+ * @param device the device that changed
+ */
+ public void deviceAdded(IDevice device);
+
+ /**
+ * Called when a Device is removed, this typically occurs when the port the
+ * Device is attached to goes down, or the switch it is attached to is
+ * removed.
+ * @param device the device that changed
+ */
+ public void deviceRemoved(IDevice device);
+
+ /**
+ * Called when a Device has moved to a new location on the network. Note
+ * that either the switch or the port or both has changed.
+ *
+ * @param device the device that changed
+ */
+ public void deviceMoved(IDevice device);
+
+ /**
+ * Called when a network address has been added or remove from a device
+ *
+ * @param device the device that changed
+ */
+ public void deviceIPV4AddrChanged(IDevice device);
+
+ /**
+ * Called when a VLAN tag for the device has been added or removed
+ * @param device the device that changed
+ */
+ public void deviceVlanChanged(IDevice device);
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/IDeviceService.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/IDeviceService.java
new file mode 100755
index 0000000..ad29a94
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/IDeviceService.java
@@ -0,0 +1,202 @@
+/**
+* Copyright 2011,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.devicemanager;
+
+import java.util.Collection;
+import java.util.EnumSet;
+import java.util.Iterator;
+
+import net.floodlightcontroller.core.FloodlightContextStore;
+import net.floodlightcontroller.core.module.IFloodlightService;
+
+/**
+ * Device manager allows interacting with devices on the network. Note
+ * that under normal circumstances, {@link Device} objects should be retrieved
+ * from the {@link FloodlightContext} rather than from {@link IDeviceManager}.
+ */
+public interface IDeviceService extends IFloodlightService {
+ /**
+ * Fields used in devices for indexes and querying
+ * @see IDeviceService#addIndex
+ */
+ enum DeviceField {
+ MAC, IPV4, VLAN, SWITCH, PORT
+ }
+
+ /**
+ * The source device for the current packet-in, if applicable.
+ */
+ public static final String CONTEXT_SRC_DEVICE =
+ "net.floodlightcontroller.devicemanager.srcDevice";
+
+ /**
+ * The destination device for the current packet-in, if applicable.
+ */
+ public static final String CONTEXT_DST_DEVICE =
+ "net.floodlightcontroller.devicemanager.dstDevice";
+
+ /**
+ * A FloodlightContextStore object that can be used to interact with the
+ * FloodlightContext information created by BVS manager.
+ */
+ public static final FloodlightContextStore<IDevice> fcStore =
+ new FloodlightContextStore<IDevice>();
+
+ /**
+ * Get the device with the given device key.
+ *
+ * @param deviceKey the key to search for
+ * @return the device associated with the key, or null if no such device
+ * @see IDevice#getDeviceKey()
+ */
+ public IDevice getDevice(Long deviceKey);
+
+ /**
+ * Search for a device exactly matching the provided device fields. This
+ * is the same lookup process that is used for packet_in processing and
+ * device learning. Thus, findDevice() can be used to match flow entries
+ * from switches to devices.
+ * Only the key fields as defined by the {@link IEntityClassifierService} will
+ * be important in this search. All key fields MUST be supplied.
+ *
+ *{@link queryDevices()} might be more appropriate!
+ *
+ * @param macAddress The MAC address
+ * @param vlan the VLAN. Null means no VLAN and is valid even if VLAN is a
+ * key field.
+ * @param ipv4Address the ipv4 address
+ * @param switchDPID the switch DPID
+ * @param switchPort the switch port
+ * @return an {@link IDevice} or null if no device is found.
+ * @see IDeviceManager#setEntityClassifier(IEntityClassifierService)
+ * @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)
+ throws IllegalArgumentException;
+
+ /**
+ * 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 source the source device. The returned destination will be
+ * in the same entity class as the source.
+ * @param macAddress The MAC address for the destination
+ * @param vlan the VLAN if available
+ * @param ipv4Address The IP address if available.
+ * @return an {@link IDevice} or null if no device is found.
+ * @see IDeviceService#findDevice(long, Short, Integer, Long,
+ * Integer)
+ * @throws IllegalArgumentException if not all key fields of the
+ * source's {@link IEntityClass} are specified.
+ */
+ public IDevice findDestDevice(IDevice source,
+ long macAddress, Short vlan,
+ Integer ipv4Address)
+ throws IllegalArgumentException;
+
+ /**
+ * Get an unmodifiable collection view over all devices currently known.
+ * @return the collection of all devices
+ */
+ public Collection<? extends IDevice> getAllDevices();
+
+ /**
+ * Create an index over a set of fields. This allows efficient lookup
+ * of devices when querying using the indexed set of specified fields.
+ * The index must be registered before any device learning takes place,
+ * or it may be incomplete. It's OK if this is called multiple times with
+ * the same fields; only one index will be created for each unique set of
+ * fields.
+ *
+ * @param perClass set to true if the index should be maintained for each
+ * entity class separately.
+ * @param keyFields the set of fields on which to index
+ */
+ public void addIndex(boolean perClass,
+ EnumSet<DeviceField> keyFields);
+
+ /**
+ * Find devices that match the provided query. Any fields that are
+ * null will not be included in the query. If there is an index for
+ * the query, then it will be performed efficiently using the index.
+ * Otherwise, there will be a full scan of the device list.
+ *
+ * @param macAddress The MAC address
+ * @param vlan the VLAN
+ * @param ipv4Address the ipv4 address
+ * @param switchDPID the switch DPID
+ * @param switchPort the switch port
+ * @return an iterator over a set of devices matching the query
+ * @see IDeviceService#queryClassDevices(IEntityClass, Long,
+ * Short, Integer, Long, Integer)
+ */
+ public Iterator<? extends IDevice> queryDevices(Long macAddress,
+ Short vlan,
+ Integer ipv4Address,
+ Long switchDPID,
+ Integer switchPort);
+
+ /**
+ * Find devices that match the provided query. Only the index for
+ * the class of the specified reference device will be searched.
+ * Any fields that are null will not be included in the query. If
+ * there is an index for the query, then it will be performed
+ * efficiently using the index. Otherwise, there will be a full scan
+ * of the device list.
+ *
+ * @param reference The reference device to refer to when finding
+ * entity classes.
+ * @param macAddress The MAC address
+ * @param vlan the VLAN
+ * @param ipv4Address the ipv4 address
+ * @param switchDPID the switch DPID
+ * @param switchPort the switch port
+ * @return an iterator over a set of devices matching the query
+ * @see IDeviceService#queryClassDevices(Long,
+ * Short, Integer, Long, Integer)
+ */
+ public Iterator<? extends IDevice> queryClassDevices(IDevice reference,
+ Long macAddress,
+ Short vlan,
+ Integer ipv4Address,
+ Long switchDPID,
+ Integer switchPort);
+
+ /**
+ * Adds a listener to listen for IDeviceManagerServices notifications
+ *
+ * @param listener The listener that wants the notifications
+ */
+ public void addListener(IDeviceListener listener);
+
+ /**
+ * Specify points in the network where attachment points are not to
+ * be learned.
+ * @param sw
+ * @param port
+ */
+ public void addSuppressAPs(long swId, short port);
+
+ public void removeSuppressAPs(long swId, short port);
+
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/IEntityClass.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/IEntityClass.java
new file mode 100644
index 0000000..bb077f1
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/IEntityClass.java
@@ -0,0 +1,59 @@
+/**
+* Copyright 2011,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.devicemanager;
+
+import java.util.EnumSet;
+
+import net.floodlightcontroller.devicemanager.IDeviceService.DeviceField;
+import net.floodlightcontroller.devicemanager.internal.Device;
+
+/**
+ * Entities within an entity class are grouped into {@link Device} objects
+ * based on the {@link IEntityClass}, and the key fields specified by the entity
+ * class. A set of entities are considered to be the same device if and only
+ * if they belong to the same entity class and they match on all key fields
+ * for that entity class. A field is effectively wildcarded by not including
+ * it in the list of key fields returned by {@link IEntityClassifierService} and/or
+ * {@link IEntityClass}.
+ *
+ * Note that if you're not using static objects, you'll need to override
+ * {@link Object#equals(Object)} and {@link Object#hashCode()}.
+ *
+ * @author readams
+ *
+ */
+public interface IEntityClass {
+ /**
+ * Return the set of key fields for this entity class. Entities
+ * belonging to this class that differ in fields not included in
+ * this collection will be considered the same device. The key
+ * fields for an entity class must not change unless associated
+ * with a flush of that entity class.
+ *
+ * @return a set containing the fields that should not
+ * be wildcarded. May be null to indicate that all fields are key fields.
+ */
+ EnumSet<DeviceField> getKeyFields();
+
+ /**
+ * Returns a user-friendly, unique name for this EntityClass
+ * @return the name of the entity class
+ */
+ String getName();
+}
+
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/IEntityClassListener.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/IEntityClassListener.java
new file mode 100644
index 0000000..6029af1
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/IEntityClassListener.java
@@ -0,0 +1,35 @@
+/**
+* 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.devicemanager;
+
+import java.util.Set;
+
+/**
+ * Implementors of this interface can receive updates from DeviceManager about
+ * the changes entity Classes.
+ *
+ * @author Ananth Suryanarayana (Ananth.Suryanarayana@bigswitch.com)
+ */
+public interface IEntityClassListener {
+
+ /**
+ * Process entity classes change event.
+ * @param entityClassNames Set of entity classes changed
+ */
+ public void entityClassChanged(Set<String> entityClassNames);
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/IEntityClassifierService.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/IEntityClassifierService.java
new file mode 100644
index 0000000..2569a7d
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/IEntityClassifierService.java
@@ -0,0 +1,108 @@
+/**
+* Copyright 2011,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.devicemanager;
+
+import java.util.Collection;
+import java.util.EnumSet;
+
+import net.floodlightcontroller.core.module.IFloodlightService;
+import net.floodlightcontroller.devicemanager.IDeviceService.DeviceField;
+import net.floodlightcontroller.devicemanager.internal.Entity;
+
+/**
+ * A component that wishes to participate in entity classification needs to
+ * implement the IEntityClassifier interface, and register with the Device
+ * Manager as an entity classifier. An entity is classified by the classifier
+ * into an {@link IEntityClass}
+ *
+ * @author readams
+ */
+public interface IEntityClassifierService extends IFloodlightService {
+ /**
+ * Classify the given entity into an IEntityClass. It is important
+ * that the key fields returned by {@link IEntityClassifierService#getKeyFields()}
+ * be sufficient for classifying entities. That is, if two entities are
+ * identical except for a field that is not a key field, they must be
+ * assigned the same class. Furthermore, entity classification must be
+ * transitive: For all entities x, y, z, if x and y belong to a class c, and
+ * y and z belong class c, then x and z must belong to class c.
+ *
+ * @param entity the entity to classify
+ * @return the IEntityClass resulting from the classification.
+ * @see IEntityClassifierService#getKeyFields()
+ */
+ IEntityClass classifyEntity(Entity entity);
+
+ /**
+ * Return the most general list of fields that should be used as key
+ * fields. If devices differ in any fields not listed here, they can
+ * never be considered a different device by any {@link IEntityClass}
+ * returned by {@link IEntityClassifierService#classifyEntity}. The key fields
+ * for an entity classifier must not change unless associated with a
+ * flush of all entity state. The list of key fields must be the union
+ * of all key fields that could be returned by
+ * {@link IEntityClass#getKeyFields()}.
+ *
+ * @return a set containing the fields that should not be
+ * wildcarded. May be null to indicate that all fields are key fields.
+ * @see {@link IEntityClass#getKeyFields()}
+ * @see {@link IEntityClassifierService#classifyEntity}
+ */
+ EnumSet<DeviceField> getKeyFields();
+
+ /**
+ * Reclassify the given entity into a class. When reclassifying entities,
+ * it can be helpful to take into account the current classification either
+ * as an optimization or to allow flushing any cached state tied to the key
+ * for that device. The entity will be assigned to a new device with a new
+ * object if the entity class returned is different from the entity class for
+ * curDevice.
+ *
+ * <p>Note that you must take steps to ensure you always return classes
+ * in some consistent ordering.
+
+ * @param curDevice the device currently associated with the entity
+ * @param entity the entity to reclassify
+ * @return the IEntityClass resulting from the classification
+ */
+ IEntityClass reclassifyEntity(IDevice curDevice,
+ Entity entity);
+
+ /**
+ * Once reclassification is complete for a device, this method will be
+ * called. If any entities within the device changed their classification,
+ * it will split into one or more new devices for each of the entities. If
+ * two devices are merged because of a reclassification, then this will be
+ * called on each of the devices, with the same device in the newDevices
+ * collection.
+ *
+ * @param oldDevice the original device object
+ * @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);
+
+ /**
+ * Adds a listener to listen for IEntityClassifierServices notifications
+ *
+ * @param listener The listener that wants the notifications
+ */
+ public void addListener(IEntityClassListener listener);
+}
+
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/SwitchPort.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/SwitchPort.java
new file mode 100644
index 0000000..7426163
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/SwitchPort.java
@@ -0,0 +1,136 @@
+/**
+* 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.devicemanager;
+
+import net.floodlightcontroller.core.web.serializers.DPIDSerializer;
+
+import org.codehaus.jackson.map.annotate.JsonSerialize;
+import org.codehaus.jackson.map.ser.ToStringSerializer;
+
+/**
+ * A simple switch DPID/port pair
+ * @author readams
+ *
+ */
+public class SwitchPort {
+ @JsonSerialize(using=ToStringSerializer.class)
+ public enum ErrorStatus {
+ DUPLICATE_DEVICE("duplicate-device");
+
+ private String value;
+ ErrorStatus(String v) {
+ value = v;
+ }
+
+ @Override
+ public String toString() {
+ return value;
+ }
+
+ public static ErrorStatus fromString(String str) {
+ for (ErrorStatus m : ErrorStatus.values()) {
+ if (m.value.equals(str)) {
+ return m;
+ }
+ }
+ return null;
+ }
+ }
+
+ protected long switchDPID;
+ protected int port;
+ ErrorStatus errorStatus;
+
+ /**
+ * Simple constructor
+ * @param switchDPID the dpid
+ * @param port the port
+ * @param errorStatus any error status for the switch port
+ */
+ public SwitchPort(long switchDPID, int port, ErrorStatus errorStatus) {
+ super();
+ this.switchDPID = switchDPID;
+ this.port = port;
+ this.errorStatus = errorStatus;
+ }
+
+ /**
+ * Simple constructor
+ * @param switchDPID the dpid
+ * @param port the port
+ */
+ public SwitchPort(long switchDPID, int port) {
+ super();
+ this.switchDPID = switchDPID;
+ this.port = port;
+ this.errorStatus = null;
+ }
+
+ // ***************
+ // Getters/Setters
+ // ***************
+
+ @JsonSerialize(using=DPIDSerializer.class)
+ public long getSwitchDPID() {
+ return switchDPID;
+ }
+
+ public int getPort() {
+ return port;
+ }
+
+ public ErrorStatus getErrorStatus() {
+ return errorStatus;
+ }
+
+ // ******
+ // Object
+ // ******
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result
+ + ((errorStatus == null)
+ ? 0
+ : errorStatus.hashCode());
+ result = prime * result + port;
+ result = prime * result + (int) (switchDPID ^ (switchDPID >>> 32));
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+ if (obj == null) return false;
+ if (getClass() != obj.getClass()) return false;
+ SwitchPort other = (SwitchPort) obj;
+ if (errorStatus != other.errorStatus) return false;
+ if (port != other.port) return false;
+ if (switchDPID != other.switchDPID) return false;
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "SwitchPort [switchDPID=" + switchDPID + ", port=" + port
+ + ", errorStatus=" + errorStatus + "]";
+ }
+
+}
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/internal/AttachmentPoint.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/internal/AttachmentPoint.java
new file mode 100644
index 0000000..a08a3a5
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/internal/AttachmentPoint.java
@@ -0,0 +1,124 @@
+/**
+ * Copyright 2011,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.
+ **/
+
+/**
+ * @author Srini
+ */
+
+package net.floodlightcontroller.devicemanager.internal;
+
+public class AttachmentPoint {
+ long sw;
+ short port;
+ long activeSince;
+ long lastSeen;
+
+ // Timeout for moving attachment points from OF/broadcast
+ // domain to another.
+ public static final long INACTIVITY_INTERVAL = 30000; // 30 seconds
+ public static final long EXTERNAL_TO_EXTERNAL_TIMEOUT = 5000; // 5 seconds
+ 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) {
+ this.sw = sw;
+ this.port = port;
+ this.activeSince = activeSince;
+ this.lastSeen = lastSeen;
+ }
+
+ public AttachmentPoint(long sw, short port, long lastSeen) {
+ this.sw = sw;
+ this.port = port;
+ this.lastSeen = lastSeen;
+ this.activeSince = lastSeen;
+ }
+
+ public AttachmentPoint(AttachmentPoint ap) {
+ this.sw = ap.sw;
+ this.port = ap.port;
+ this.activeSince = ap.activeSince;
+ this.lastSeen = ap.lastSeen;
+ }
+
+ public long getSw() {
+ return sw;
+ }
+ public void setSw(long sw) {
+ this.sw = sw;
+ }
+ public short getPort() {
+ return port;
+ }
+ public void setPort(short port) {
+ this.port = port;
+ }
+ public long getActiveSince() {
+ return activeSince;
+ }
+ public void setActiveSince(long activeSince) {
+ this.activeSince = activeSince;
+ }
+ public long getLastSeen() {
+ return lastSeen;
+ }
+ public void setLastSeen(long lastSeen) {
+ if (this.lastSeen + INACTIVITY_INTERVAL < lastSeen)
+ this.activeSince = lastSeen;
+ if (this.lastSeen < lastSeen)
+ this.lastSeen = lastSeen;
+ }
+
+ /**
+ * Hash is generated using only switch and port
+ */
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + port;
+ result = prime * result + (int) (sw ^ (sw >>> 32));
+ return result;
+ }
+
+ /**
+ * Compares only the switch and port
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ AttachmentPoint other = (AttachmentPoint) obj;
+ if (port != other.port)
+ return false;
+ if (sw != other.sw)
+ return false;
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "AttachmentPoint [sw=" + sw + ", port=" + port
+ + ", activeSince=" + activeSince + ", lastSeen=" + lastSeen
+ + "]";
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/internal/DefaultEntityClassifier.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/internal/DefaultEntityClassifier.java
new file mode 100644
index 0000000..faed0d4
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/internal/DefaultEntityClassifier.java
@@ -0,0 +1,138 @@
+/**
+* Copyright 2011,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.devicemanager.internal;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.Map;
+
+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.devicemanager.IDevice;
+import net.floodlightcontroller.devicemanager.IDeviceService;
+import net.floodlightcontroller.devicemanager.IDeviceService.DeviceField;
+import net.floodlightcontroller.devicemanager.IEntityClass;
+import net.floodlightcontroller.devicemanager.IEntityClassListener;
+import net.floodlightcontroller.devicemanager.IEntityClassifierService;
+
+/**
+ * This is a default entity classifier that simply classifies all
+ * entities into a fixed entity class, with key fields of MAC and VLAN.
+ * @author readams
+ */
+public class DefaultEntityClassifier implements
+ IEntityClassifierService,
+ IFloodlightModule
+{
+ /**
+ * A default fixed entity class
+ */
+ protected static class DefaultEntityClass implements IEntityClass {
+ String name;
+
+ public DefaultEntityClass(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public EnumSet<IDeviceService.DeviceField> getKeyFields() {
+ return keyFields;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+ }
+
+ protected static EnumSet<DeviceField> keyFields;
+ static {
+ keyFields = EnumSet.of(DeviceField.MAC, DeviceField.VLAN);
+ }
+ protected static DefaultEntityClass entityClass =
+ new DefaultEntityClass("DefaultEntityClass");
+
+ @Override
+ public IEntityClass classifyEntity(Entity entity) {
+ return entityClass;
+ }
+
+ @Override
+ public IEntityClass reclassifyEntity(IDevice curDevice,
+ Entity entity) {
+ return entityClass;
+ }
+
+ @Override
+ public void deviceUpdate(IDevice oldDevice,
+ Collection<? extends IDevice> newDevices) {
+ // no-op
+ }
+
+ @Override
+ public EnumSet<DeviceField> getKeyFields() {
+ return keyFields;
+ }
+
+ @Override
+ public Collection<Class<? extends IFloodlightService>> getModuleServices() {
+ Collection<Class<? extends IFloodlightService>> l =
+ new ArrayList<Class<? extends IFloodlightService>>();
+ l.add(IEntityClassifierService.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(IEntityClassifierService.class, this);
+ return m;
+ }
+
+ @Override
+ public Collection<Class<? extends IFloodlightService>>
+ getModuleDependencies() {
+ // No dependencies
+ return null;
+ }
+
+ @Override
+ public void init(FloodlightModuleContext context)
+ throws FloodlightModuleException {
+ // no-op
+ }
+
+ @Override
+ public void startUp(FloodlightModuleContext context) {
+ // no-op
+ }
+
+ @Override
+ public void addListener(IEntityClassListener listener) {
+ // no-op
+
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/internal/Device.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/internal/Device.java
new file mode 100755
index 0000000..645125e
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/internal/Device.java
@@ -0,0 +1,725 @@
+/**
+ * Copyright 2011,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.devicemanager.internal;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeSet;
+
+import org.codehaus.jackson.map.annotate.JsonSerialize;
+import org.openflow.util.HexString;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import net.floodlightcontroller.devicemanager.IDeviceService.DeviceField;
+import net.floodlightcontroller.devicemanager.web.DeviceSerializer;
+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;
+
+/**
+ * Concrete implementation of {@link IDevice}
+ * @author readams
+ */
+@JsonSerialize(using=DeviceSerializer.class)
+public class Device implements IDevice {
+ protected static Logger log =
+ LoggerFactory.getLogger(Device.class);
+
+ protected Long deviceKey;
+ protected DeviceManagerImpl deviceManager;
+
+ protected Entity[] entities;
+ protected IEntityClass entityClass;
+
+ protected String macAddressString;
+
+ /**
+ * These are the old attachment points for the device that were
+ * valid no more than INACTIVITY_TIME ago.
+ */
+ protected List<AttachmentPoint> oldAPs;
+ /**
+ * The current attachment points for the device.
+ */
+ protected List<AttachmentPoint> attachmentPoints;
+ // ************
+ // Constructors
+ // ************
+
+ /**
+ * Create a device from an entities
+ * @param deviceManager the device manager for this device
+ * @param deviceKey the unique identifier for this device object
+ * @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) {
+ this.deviceManager = deviceManager;
+ this.deviceKey = deviceKey;
+ this.entities = new Entity[] {entity};
+ this.macAddressString =
+ HexString.toHexString(entity.getMacAddress(), 6);
+ this.entityClass = entityClass;
+ Arrays.sort(this.entities);
+
+ this.oldAPs = null;
+ this.attachmentPoints = null;
+
+ if (entity.getSwitchDPID() != null &&
+ entity.getSwitchPort() != null){
+ long sw = entity.getSwitchDPID();
+ short port = entity.getSwitchPort().shortValue();
+
+ if (deviceManager.isValidAttachmentPoint(sw, port)) {
+ AttachmentPoint ap;
+ ap = new AttachmentPoint(sw, port,
+entity.getLastSeenTimestamp().getTime());
+
+ this.attachmentPoints = new ArrayList<AttachmentPoint>();
+ this.attachmentPoints.add(ap);
+ }
+ }
+ }
+
+ /**
+ * Create a device from a set of entities
+ * @param deviceManager the device manager for this device
+ * @param deviceKey the unique identifier for this device object
+ * @param entities the initial entities for the device
+ * @param entityClass the entity class associated with the entities
+ */
+ public Device(DeviceManagerImpl deviceManager,
+ Long deviceKey,
+ Collection<AttachmentPoint> oldAPs,
+ Collection<AttachmentPoint> attachmentPoints,
+ Collection<Entity> entities,
+ IEntityClass entityClass) {
+ this.deviceManager = deviceManager;
+ this.deviceKey = deviceKey;
+ this.entities = entities.toArray(new Entity[entities.size()]);
+ this.oldAPs = null;
+ this.attachmentPoints = null;
+ if (oldAPs != null) {
+ this.oldAPs =
+ new ArrayList<AttachmentPoint>(oldAPs);
+ }
+ if (attachmentPoints != null) {
+ this.attachmentPoints =
+ new ArrayList<AttachmentPoint>(attachmentPoints);
+ }
+ this.macAddressString =
+ HexString.toHexString(this.entities[0].getMacAddress(), 6);
+ this.entityClass = entityClass;
+ Arrays.sort(this.entities);
+ }
+
+ /**
+ * Construct a new device consisting of the entities from the old device
+ * plus an additional entity
+ * @param device the old device object
+ * @param newEntity the entity to add. newEntity must be have the same
+ * entity class as device
+ */
+ public Device(Device device,
+ Entity newEntity) {
+ this.deviceManager = device.deviceManager;
+ this.deviceKey = device.deviceKey;
+ this.entities = Arrays.<Entity>copyOf(device.entities,
+ device.entities.length + 1);
+ this.entities[this.entities.length - 1] = newEntity;
+ Arrays.sort(this.entities);
+ this.oldAPs = null;
+ if (device.oldAPs != null) {
+ this.oldAPs =
+ new ArrayList<AttachmentPoint>(device.oldAPs);
+ }
+ this.attachmentPoints = null;
+ if (device.attachmentPoints != null) {
+ this.attachmentPoints =
+ new ArrayList<AttachmentPoint>(device.attachmentPoints);
+ }
+
+ this.macAddressString =
+ HexString.toHexString(this.entities[0].getMacAddress(), 6);
+
+ this.entityClass = device.entityClass;
+ }
+
+ /**
+ * Given a list of attachment points (apList), the procedure would return
+ * a map of attachment points for each L2 domain. L2 domain id is the key.
+ * @param apList
+ * @return
+ */
+ private Map<Long, AttachmentPoint> getAPMap(List<AttachmentPoint> apList) {
+
+ if (apList == null) return null;
+ ITopologyService topology = deviceManager.topology;
+
+ // Get the old attachment points and sort them.
+ List<AttachmentPoint>oldAP = new ArrayList<AttachmentPoint>();
+ if (apList != null) oldAP.addAll(apList);
+
+ // Remove invalid attachment points before sorting.
+ List<AttachmentPoint>tempAP =
+ new ArrayList<AttachmentPoint>();
+ for(AttachmentPoint ap: oldAP) {
+ if (deviceManager.isValidAttachmentPoint(ap.getSw(), ap.getPort())){
+ tempAP.add(ap);
+ }
+ }
+ oldAP = tempAP;
+
+ Collections.sort(oldAP, deviceManager.apComparator);
+
+ // Map of attachment point by L2 domain Id.
+ Map<Long, AttachmentPoint> apMap = new HashMap<Long, 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()))
+ continue;
+
+ long id = topology.getL2DomainId(ap.getSw());
+ apMap.put(id, ap);
+ }
+
+ if (apMap.isEmpty()) return null;
+ return apMap;
+ }
+
+ /**
+ * Remove all attachment points that are older than INACTIVITY_INTERVAL
+ * from the list.
+ * @param apList
+ * @return
+ */
+ private boolean removeExpiredAttachmentPoints(List<AttachmentPoint>apList) {
+
+ List<AttachmentPoint> expiredAPs = new ArrayList<AttachmentPoint>();
+
+ if (apList == null) return false;
+
+ for(AttachmentPoint ap: apList) {
+ if (ap.getLastSeen() + AttachmentPoint.INACTIVITY_INTERVAL <
+ System.currentTimeMillis())
+ expiredAPs.add(ap);
+ }
+ if (expiredAPs.size() > 0) {
+ apList.removeAll(expiredAPs);
+ return true;
+ } else return false;
+ }
+
+ /**
+ * Get a list of duplicate attachment points, given a list of old attachment
+ * points and one attachment point per L2 domain. Given a true attachment
+ * point in the L2 domain, say trueAP, another attachment point in the
+ * same L2 domain, say ap, is duplicate if:
+ * 1. ap is inconsistent with trueAP, and
+ * 2. active time of ap is after that of trueAP; and
+ * 3. last seen time of ap is within the last INACTIVITY_INTERVAL
+ * @param oldAPList
+ * @param apMap
+ * @return
+ */
+ List<AttachmentPoint> getDuplicateAttachmentPoints(List<AttachmentPoint>oldAPList,
+ Map<Long, AttachmentPoint>apMap) {
+ ITopologyService topology = deviceManager.topology;
+ List<AttachmentPoint> dupAPs = new ArrayList<AttachmentPoint>();
+ long timeThreshold = System.currentTimeMillis() -
+ AttachmentPoint.INACTIVITY_INTERVAL;
+
+ if (oldAPList == null || apMap == null)
+ return dupAPs;
+
+ for(AttachmentPoint ap: oldAPList) {
+ long 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;
+ if (!c && active && last) {
+ dupAPs.add(ap);
+ }
+ }
+
+ return dupAPs;
+ }
+
+ /**
+ * Update the known attachment points. This method is called whenever
+ * topology changes. The method returns true if there's any change to
+ * the list of attachment points -- which indicates a possible device
+ * move.
+ * @return
+ */
+ protected boolean updateAttachmentPoint() {
+ boolean moved = false;
+
+ if (attachmentPoints == null || attachmentPoints.isEmpty())
+ return false;
+
+ List<AttachmentPoint> apList = new ArrayList<AttachmentPoint>();
+ if (attachmentPoints != null) apList.addAll(attachmentPoints);
+ Map<Long, AttachmentPoint> newMap = getAPMap(apList);
+ if (newMap == null || newMap.size() != apList.size()) {
+ moved = true;
+ }
+
+ // Prepare the new attachment point list.
+ if (moved) {
+ List<AttachmentPoint> newAPList =
+ new ArrayList<AttachmentPoint>();
+ if (newMap != null) newAPList.addAll(newMap.values());
+ this.attachmentPoints = newAPList;
+ }
+
+ // Set the oldAPs to null.
+ this.oldAPs = null;
+ return moved;
+ }
+
+ /**
+ * Update the list of attachment points given that a new packet-in
+ * was seen from (sw, port) at time (lastSeen). The return value is true
+ * if there was any change to the list of attachment points for the device
+ * -- which indicates a device move.
+ * @param sw
+ * @param port
+ * @param lastSeen
+ * @return
+ */
+ protected boolean updateAttachmentPoint(long sw, short port, long lastSeen){
+ ITopologyService topology = deviceManager.topology;
+ List<AttachmentPoint> oldAPList;
+ List<AttachmentPoint> apList;
+ boolean oldAPFlag = false;
+
+ if (!deviceManager.isValidAttachmentPoint(sw, port)) return false;
+ AttachmentPoint newAP = new AttachmentPoint(sw, port, lastSeen);
+
+ //Copy the oldAP and ap list.
+ apList = new ArrayList<AttachmentPoint>();
+ if (attachmentPoints != null) apList.addAll(attachmentPoints);
+ oldAPList = new ArrayList<AttachmentPoint>();
+ if (oldAPs != null) oldAPList.addAll(oldAPs);
+
+ // if the sw, port is in old AP, remove it from there
+ // and update the lastSeen in that object.
+ if (oldAPList.contains(newAP)) {
+ int index = oldAPList.indexOf(newAP);
+ newAP = oldAPList.remove(index);
+ newAP.setLastSeen(lastSeen);
+ this.oldAPs = oldAPList;
+ oldAPFlag = true;
+ }
+
+ // newAP now contains the new attachment point.
+
+ // Get the APMap is null or empty.
+ Map<Long, AttachmentPoint> apMap = getAPMap(apList);
+ if (apMap == null || apMap.isEmpty()) {
+ apList.add(newAP);
+ attachmentPoints = apList;
+ return true;
+ }
+
+ long id = topology.getL2DomainId(sw);
+ AttachmentPoint oldAP = apMap.get(id);
+
+ if (oldAP == null) // No attachment on this L2 domain.
+ {
+ apList = new ArrayList<AttachmentPoint>();
+ apList.addAll(apMap.values());
+ apList.add(newAP);
+ this.attachmentPoints = apList;
+ return true; // new AP found on an L2 island.
+ }
+
+ // There is already a known attachment point on the same L2 island.
+ // 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) {
+ oldAP.setLastSeen(newAP.lastSeen);
+ }
+ this.attachmentPoints =
+ new ArrayList<AttachmentPoint>(apMap.values());
+ return false; // nothing to do here.
+ }
+
+ int x = deviceManager.apComparator.compare(oldAP, newAP);
+ if (x < 0) {
+ // newAP replaces oldAP.
+ apMap.put(id, newAP);
+ this.attachmentPoints =
+ new ArrayList<AttachmentPoint>(apMap.values());
+
+ oldAPList = new ArrayList<AttachmentPoint>();
+ if (oldAPs != null) oldAPList.addAll(oldAPs);
+ oldAPList.add(oldAP);
+ this.oldAPs = oldAPList;
+ if (!topology.isInSameBroadcastDomain(oldAP.getSw(), oldAP.getPort(),
+ newAP.getSw(), newAP.getPort()))
+ return true; // attachment point changed.
+ } else if (oldAPFlag) {
+ // retain oldAP as is. Put the newAP in oldAPs for flagging
+ // possible duplicates.
+ oldAPList = new ArrayList<AttachmentPoint>();
+ if (oldAPs != null) oldAPList.addAll(oldAPs);
+ // Add ot oldAPList only if it was picked up from the oldAPList
+ oldAPList.add(newAP);
+ this.oldAPs = oldAPList;
+ }
+ return false;
+ }
+
+ /**
+ * Delete (sw,port) from the list of list of attachment points
+ * and oldAPs.
+ * @param sw
+ * @param port
+ * @return
+ */
+ public boolean deleteAttachmentPoint(long sw, short port) {
+ AttachmentPoint ap = new AttachmentPoint(sw, port, 0);
+
+ if (this.oldAPs != null) {
+ ArrayList<AttachmentPoint> apList = new ArrayList<AttachmentPoint>();
+ apList.addAll(this.oldAPs);
+ int index = apList.indexOf(ap);
+ if (index > 0) {
+ apList.remove(index);
+ this.oldAPs = apList;
+ }
+ }
+
+ if (this.attachmentPoints != null) {
+ ArrayList<AttachmentPoint> apList = new ArrayList<AttachmentPoint>();
+ apList.addAll(this.attachmentPoints);
+ int index = apList.indexOf(ap);
+ if (index > 0) {
+ apList.remove(index);
+ this.attachmentPoints = apList;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public boolean deleteAttachmentPoint(long sw) {
+ boolean deletedFlag;
+ ArrayList<AttachmentPoint> apList;
+ ArrayList<AttachmentPoint> modifiedList;
+
+ // Delete the APs on switch sw in oldAPs.
+ deletedFlag = false;
+ apList = new ArrayList<AttachmentPoint>();
+ if (this.oldAPs != null)
+ apList.addAll(this.oldAPs);
+ modifiedList = new ArrayList<AttachmentPoint>();
+
+ for(AttachmentPoint ap: apList) {
+ if (ap.getSw() == sw) {
+ deletedFlag = true;
+ } else {
+ modifiedList.add(ap);
+ }
+ }
+
+ if (deletedFlag) {
+ this.oldAPs = modifiedList;
+ }
+
+ // Delete the APs on switch sw in attachmentPoints.
+ deletedFlag = false;
+ apList = new ArrayList<AttachmentPoint>();
+ if (this.attachmentPoints != null)
+ apList.addAll(this.attachmentPoints);
+ modifiedList = new ArrayList<AttachmentPoint>();
+
+ for(AttachmentPoint ap: apList) {
+ if (ap.getSw() == sw) {
+ deletedFlag = true;
+ } else {
+ modifiedList.add(ap);
+ }
+ }
+
+ if (deletedFlag) {
+ this.attachmentPoints = modifiedList;
+ return true;
+ }
+
+ return false;
+ }
+
+
+ @Override
+ public SwitchPort[] getAttachmentPoints() {
+ return getAttachmentPoints(false);
+ }
+
+ @Override
+ public SwitchPort[] getAttachmentPoints(boolean includeError) {
+ List<SwitchPort> sp = new ArrayList<SwitchPort>();
+ SwitchPort [] returnSwitchPorts = new SwitchPort[] {};
+ if (attachmentPoints == null) return returnSwitchPorts;
+ if (attachmentPoints.isEmpty()) return returnSwitchPorts;
+
+
+ // copy ap list.
+ List<AttachmentPoint> apList;
+ apList = new ArrayList<AttachmentPoint>();
+ if (attachmentPoints != null) apList.addAll(attachmentPoints);
+ // get AP map.
+ Map<Long, AttachmentPoint> apMap = getAPMap(apList);
+
+ if (apMap != null) {
+ for(AttachmentPoint ap: apMap.values()) {
+ SwitchPort swport = new SwitchPort(ap.getSw(),
+ ap.getPort());
+ sp.add(swport);
+ }
+ }
+
+ if (!includeError)
+ return sp.toArray(new SwitchPort[sp.size()]);
+
+ List<AttachmentPoint> oldAPList;
+ oldAPList = new ArrayList<AttachmentPoint>();
+
+ if (oldAPs != null) oldAPList.addAll(oldAPs);
+
+ if (removeExpiredAttachmentPoints(oldAPList))
+ this.oldAPs = oldAPList;
+
+ List<AttachmentPoint> dupList;
+ dupList = this.getDuplicateAttachmentPoints(oldAPList, apMap);
+ if (dupList != null) {
+ for(AttachmentPoint ap: dupList) {
+ SwitchPort swport = new SwitchPort(ap.getSw(),
+ ap.getPort(),
+ ErrorStatus.DUPLICATE_DEVICE);
+ sp.add(swport);
+ }
+ }
+ return sp.toArray(new SwitchPort[sp.size()]);
+ }
+
+ // *******
+ // IDevice
+ // *******
+
+ @Override
+ public Long getDeviceKey() {
+ return deviceKey;
+ }
+
+ @Override
+ public long getMACAddress() {
+ // we assume only one MAC per device for now.
+ return entities[0].getMacAddress();
+ }
+
+ @Override
+ public String getMACAddressString() {
+ return macAddressString;
+ }
+
+ @Override
+ public Short[] getVlanId() {
+ if (entities.length == 1) {
+ if (entities[0].getVlan() != null) {
+ return new Short[]{ entities[0].getVlan() };
+ } else {
+ return new Short[] { Short.valueOf((short)-1) };
+ }
+ }
+
+ TreeSet<Short> vals = new TreeSet<Short>();
+ for (Entity e : entities) {
+ if (e.getVlan() == null)
+ vals.add((short)-1);
+ else
+ vals.add(e.getVlan());
+ }
+ return vals.toArray(new Short[vals.size()]);
+ }
+
+ static final EnumSet<DeviceField> ipv4Fields = EnumSet.of(DeviceField.IPV4);
+
+ @Override
+ public Integer[] 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>();
+ for (Entity e : entities) {
+ if (e.getIpv4Address() == null) continue;
+
+ // We have an IP address only if among the devices within the class
+ // we have the most recent entity with that IP.
+ boolean validIP = true;
+ Iterator<Device> devices =
+ deviceManager.queryClassByEntity(entityClass, ipv4Fields, e);
+ while (devices.hasNext()) {
+ Device d = devices.next();
+ if (deviceKey.equals(d.getDeviceKey()))
+ continue;
+ for (Entity se : d.entities) {
+ if (se.getIpv4Address() != null &&
+ se.getIpv4Address().equals(e.getIpv4Address()) &&
+ se.getLastSeenTimestamp() != null &&
+ 0 < se.getLastSeenTimestamp().
+ compareTo(e.getLastSeenTimestamp())) {
+ validIP = false;
+ break;
+ }
+ }
+ if (!validIP)
+ break;
+ }
+
+ if (validIP)
+ vals.add(e.getIpv4Address());
+ }
+
+ return vals.toArray(new Integer[vals.size()]);
+ }
+
+ @Override
+ public Short[] getSwitchPortVlanIds(SwitchPort swp) {
+ TreeSet<Short> vals = new TreeSet<Short>();
+ for (Entity e : entities) {
+ if (e.switchDPID == swp.getSwitchDPID()
+ && e.switchPort == swp.getPort()) {
+ if (e.getVlan() == null)
+ vals.add(Ethernet.VLAN_UNTAGGED);
+ else
+ vals.add(e.getVlan());
+ }
+ }
+ return vals.toArray(new Short[vals.size()]);
+ }
+
+ @Override
+ public Date getLastSeen() {
+ Date d = null;
+ for (int i = 0; i < entities.length; i++) {
+ if (d == null ||
+ entities[i].getLastSeenTimestamp().compareTo(d) > 0)
+ d = entities[i].getLastSeenTimestamp();
+ }
+ return d;
+ }
+
+ // ***************
+ // Getters/Setters
+ // ***************
+
+ @Override
+ public IEntityClass getEntityClass() {
+ return entityClass;
+ }
+
+ public Entity[] getEntities() {
+ return entities;
+ }
+
+ // ***************
+ // Utility Methods
+ // ***************
+
+ /**
+ * Check whether the device contains the specified entity
+ * @param entity the entity to search for
+ * @return the index of the entity, or <0 if not found
+ */
+ protected int entityIndex(Entity entity) {
+ return Arrays.binarySearch(entities, entity);
+ }
+
+ // ******
+ // Object
+ // ******
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + Arrays.hashCode(entities);
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+ if (obj == null) return false;
+ if (getClass() != obj.getClass()) return false;
+ Device other = (Device) obj;
+ if (!deviceKey.equals(other.deviceKey)) return false;
+ if (!Arrays.equals(entities, other.entities)) return false;
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("Device [deviceKey=");
+ builder.append(deviceKey);
+ builder.append(", entityClass=");
+ builder.append(entityClass.getName());
+ builder.append(", MAC=");
+ builder.append(macAddressString);
+ builder.append(", IPs=[");
+ boolean isFirst = true;
+ for (Integer ip: getIPv4Addresses()) {
+ if (!isFirst)
+ builder.append(", ");
+ isFirst = false;
+ builder.append(IPv4.fromIPv4Address(ip));
+ }
+ builder.append("], APs=");
+ builder.append(Arrays.toString(getAttachmentPoints(true)));
+ builder.append("]");
+ return builder.toString();
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceIndex.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceIndex.java
new file mode 100644
index 0000000..0d8ea75
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceIndex.java
@@ -0,0 +1,116 @@
+/**
+* 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.devicemanager.internal;
+
+import java.util.Collection;
+import java.util.EnumSet;
+import java.util.Iterator;
+
+import net.floodlightcontroller.devicemanager.IDeviceService.DeviceField;
+
+/**
+ * An index that maps key fields of an entity to device keys
+ */
+public abstract class DeviceIndex {
+ /**
+ * The key fields for this index
+ */
+ protected EnumSet<DeviceField> keyFields;
+
+ /**
+ * Construct a new device index using the provided key fields
+ * @param keyFields the key fields to use
+ */
+ public DeviceIndex(EnumSet<DeviceField> keyFields) {
+ super();
+ this.keyFields = keyFields;
+ }
+
+ /**
+ * Find all device keys in the index that match the given entity
+ * on all the key fields for this index
+ * @param e the entity to search for
+ * @return an iterator over device keys
+ */
+ public abstract Iterator<Long> queryByEntity(Entity entity);
+
+ /**
+ * Get all device keys in the index. If certain devices exist
+ * multiple times, then these devices may be returned multiple times
+ * @return an iterator over device keys
+ */
+ public abstract Iterator<Long> getAll();
+
+ /**
+ * Attempt to update an index with the entities in the provided
+ * {@link Device}. If the update fails because of a 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.
+ */
+ public abstract boolean updateIndex(Device device, Long deviceKey);
+
+ /**
+ * Add a mapping from the given entity to the given device key. This
+ * update will not fail because of a concurrent update
+ * @param device the device to update
+ * @param deviceKey the device key for the device
+ */
+ public abstract void updateIndex(Entity entity, Long deviceKey);
+
+ /**
+ * Remove the entry for the given entity
+ * @param entity the entity to remove
+ */
+ public abstract void removeEntity(Entity entity);
+
+ /**
+ * Remove the given device key from the index for the given entity
+ * @param entity the entity to search for
+ * @param deviceKey the key to remove
+ */
+ public abstract void removeEntity(Entity entity, Long deviceKey);
+
+ /**
+ * Remove the give device from the index only if this the collection
+ * of others does not contain an entity that is identical on all the key
+ * fields for this index.
+ * @param entity the entity to search for
+ * @param deviceKey the key to remove
+ * @param others the others against which to check
+ */
+ public void removeEntityIfNeeded(Entity entity, Long deviceKey,
+ Collection<Entity> others) {
+ IndexedEntity ie = new IndexedEntity(keyFields, entity);
+ for (Entity o : others) {
+ IndexedEntity oio = new IndexedEntity(keyFields, o);
+ if (oio.equals(ie)) return;
+ }
+
+ Iterator<Long> keyiter = this.queryByEntity(entity);
+ while (keyiter.hasNext()) {
+ Long key = keyiter.next();
+ if (key.equals(deviceKey)) {
+ removeEntity(entity, deviceKey);
+ break;
+ }
+ }
+ }
+
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceIndexInterator.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceIndexInterator.java
new file mode 100644
index 0000000..2015bbe
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceIndexInterator.java
@@ -0,0 +1,59 @@
+/**
+* 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.devicemanager.internal;
+
+import java.util.Iterator;
+
+/**
+ * An iterator for handling device index queries
+ */
+public class DeviceIndexInterator implements Iterator<Device> {
+ private DeviceManagerImpl deviceManager;
+ private Iterator<Long> subIterator;
+
+ /**
+ * Construct a new device index iterator referring to a device manager
+ * instance and an iterator over device keys
+ *
+ * @param deviceManager the device manager
+ * @param subIterator an iterator over device keys
+ */
+ public DeviceIndexInterator(DeviceManagerImpl deviceManager,
+ Iterator<Long> subIterator) {
+ super();
+ this.deviceManager = deviceManager;
+ this.subIterator = subIterator;
+ }
+
+ @Override
+ public boolean hasNext() {
+ return subIterator.hasNext();
+ }
+
+ @Override
+ public Device next() {
+ Long next = subIterator.next();
+ return deviceManager.deviceMap.get(next);
+ }
+
+ @Override
+ public void remove() {
+ subIterator.remove();
+ }
+
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceIterator.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceIterator.java
new file mode 100644
index 0000000..2cbea66
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceIterator.java
@@ -0,0 +1,117 @@
+/**
+* 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.devicemanager.internal;
+
+import java.util.Arrays;
+import java.util.Iterator;
+
+import net.floodlightcontroller.devicemanager.IEntityClass;
+import net.floodlightcontroller.devicemanager.SwitchPort;
+import net.floodlightcontroller.util.FilterIterator;
+
+/**
+ * An iterator for handling device queries
+ */
+public class DeviceIterator extends FilterIterator<Device> {
+ private IEntityClass[] entityClasses;
+
+ private Long macAddress;
+ private Short vlan;
+ private Integer ipv4Address;
+ private Long switchDPID;
+ private Integer switchPort;
+
+ /**
+ * Construct a new device iterator over the key fields
+ * @param subIterator an iterator over the full data structure to scan
+ * @param entityClasses the entity classes to search for
+ * @param macAddress The MAC address
+ * @param vlan the VLAN
+ * @param ipv4Address the ipv4 address
+ * @param switchDPID the switch DPID
+ * @param switchPort the switch port
+ */
+ public DeviceIterator(Iterator<Device> subIterator,
+ IEntityClass[] entityClasses,
+ Long macAddress,
+ Short vlan,
+ Integer ipv4Address,
+ Long switchDPID,
+ Integer switchPort) {
+ super(subIterator);
+ this.entityClasses = entityClasses;
+ this.subIterator = subIterator;
+ this.macAddress = macAddress;
+ this.vlan = vlan;
+ this.ipv4Address = ipv4Address;
+ this.switchDPID = switchDPID;
+ this.switchPort = switchPort;
+ }
+
+ @Override
+ protected boolean matches(Device value) {
+ boolean match;
+ if (entityClasses != null) {
+ IEntityClass clazz = value.getEntityClass();
+ if (clazz == null) return false;
+
+ match = false;
+ for (IEntityClass entityClass : entityClasses) {
+ if (clazz.equals(entityClass)) {
+ match = true;
+ break;
+ }
+ }
+ if (!match) return false;
+ }
+ if (macAddress != null) {
+ if (macAddress.longValue() != value.getMACAddress())
+ return false;
+ }
+ if (vlan != null) {
+ Short[] vlans = value.getVlanId();
+ if (Arrays.binarySearch(vlans, vlan) < 0)
+ return false;
+ }
+ if (ipv4Address != null) {
+ Integer[] ipv4Addresses = value.getIPv4Addresses();
+ if (Arrays.binarySearch(ipv4Addresses, ipv4Address) < 0)
+ return false;
+ }
+ if (switchDPID != null || switchPort != null) {
+ SwitchPort[] sps = value.getAttachmentPoints();
+ if (sps == null) return false;
+
+ match = false;
+ for (SwitchPort sp : sps) {
+ if (switchDPID != null) {
+ if (switchDPID.longValue() != sp.getSwitchDPID())
+ return false;
+ }
+ if (switchPort != null) {
+ if (switchPort.intValue() != sp.getPort())
+ return false;
+ }
+ match = true;
+ break;
+ }
+ if (!match) return false;
+ }
+ return true;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceManagerImpl.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceManagerImpl.java
new file mode 100755
index 0000000..feccdc4
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceManagerImpl.java
@@ -0,0 +1,1717 @@
+/**
+ * Copyright 2011,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.devicemanager.internal;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+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.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.Queue;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+import net.floodlightcontroller.core.FloodlightContext;
+import net.floodlightcontroller.core.IFloodlightProviderService;
+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.module.FloodlightModuleContext;
+import net.floodlightcontroller.core.module.IFloodlightModule;
+import net.floodlightcontroller.core.module.IFloodlightService;
+import net.floodlightcontroller.core.util.SingletonTask;
+import net.floodlightcontroller.devicemanager.IDevice;
+import net.floodlightcontroller.devicemanager.IDeviceService;
+import net.floodlightcontroller.devicemanager.IEntityClass;
+import net.floodlightcontroller.devicemanager.IEntityClassListener;
+import net.floodlightcontroller.devicemanager.IEntityClassifierService;
+import net.floodlightcontroller.devicemanager.IDeviceListener;
+import net.floodlightcontroller.devicemanager.SwitchPort;
+import net.floodlightcontroller.devicemanager.web.DeviceRoutable;
+import net.floodlightcontroller.flowcache.IFlowReconcileListener;
+import net.floodlightcontroller.flowcache.IFlowReconcileService;
+import net.floodlightcontroller.flowcache.OFMatchReconcile;
+import net.floodlightcontroller.linkdiscovery.ILinkDiscovery.LDUpdate;
+import net.floodlightcontroller.packet.ARP;
+import net.floodlightcontroller.packet.DHCP;
+import net.floodlightcontroller.packet.Ethernet;
+import net.floodlightcontroller.packet.IPv4;
+import net.floodlightcontroller.packet.UDP;
+import net.floodlightcontroller.restserver.IRestApiService;
+import net.floodlightcontroller.storage.IStorageSourceService;
+import net.floodlightcontroller.threadpool.IThreadPoolService;
+import net.floodlightcontroller.topology.ITopologyListener;
+import net.floodlightcontroller.topology.ITopologyService;
+import net.floodlightcontroller.util.MultiIterator;
+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.OFType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * DeviceManager creates Devices based upon MAC addresses seen in the network.
+ * It tracks any network addresses mapped to the Device, and its location
+ * within the network.
+ * @author readams
+ */
+public class DeviceManagerImpl implements
+IDeviceService, IOFMessageListener, ITopologyListener,
+IFloodlightModule, IEntityClassListener,
+IFlowReconcileListener, IInfoProvider, IHAListener {
+ 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;
+
+ /**
+ * 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 long deviceKeyCounter = 0;
+
+ /**
+ * Lock for incrementing the device key counter
+ */
+ protected Object deviceKeyLock = new Object();
+
+ /**
+ * 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
+ */
+ protected Set<IDeviceListener> deviceListeners;
+
+ /**
+ * A device update event to be dispatched
+ */
+ protected static class DeviceUpdate {
+ public enum Change {
+ ADD, DELETE, CHANGE;
+ }
+
+ /**
+ * The affected device
+ */
+ protected IDevice 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(IDevice 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;
+
+ // 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;
+
+ // *********************
+ // 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 findDestDevice(IDevice source, 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 (source == null ||
+ !allKeyFieldsPresent(e, source.getEntityClass().getKeyFields())) {
+ throw new IllegalArgumentException("Not all key fields and/or "
+ + " no source device specified. Required fields: " +
+ entityClassifier.getKeyFields());
+ }
+ return findDestByEntity(source, 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(IDevice reference,
+ Long macAddress,
+ Short vlan,
+ Integer ipv4Address,
+ Long switchDPID,
+ Integer switchPort) {
+ IEntityClass entityClass = reference.getEntityClass();
+ 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.add(listener);
+ }
+
+ // *************
+ // 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 "devicemanager";
+ }
+
+ @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:
+ 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) {
+ // Extract source entity information
+ Entity srcEntity =
+ getEntityFromFlowMod(ofm.ofmWithSwDpid, true);
+ if (srcEntity == null)
+ return Command.STOP;
+
+ // Find the device by source entity
+ Device srcDevice = findDeviceByEntity(srcEntity);
+ if (srcDevice == null)
+ 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, dstEntity);
+ if (dstDevice != null)
+ fcStore.put(ofm.cntx, CONTEXT_DST_DEVICE, dstDevice);
+ }
+ 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);
+ return l;
+ }
+
+ @Override
+ public void init(FloodlightModuleContext fmc) {
+ this.perClassIndices =
+ new HashSet<EnumSet<DeviceField>>();
+ addIndex(true, EnumSet.of(DeviceField.IPV4));
+
+ this.deviceListeners = new HashSet<IDeviceListener>();
+ this.suppressAPs =
+ Collections.synchronizedSet(new HashSet<SwitchPort>());
+
+ 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.entityClassifier = fmc.getServiceImpl(IEntityClassifierService.class);
+ }
+
+ @Override
+ public void startUp(FloodlightModuleContext fmc) {
+ 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);
+ if (topology != null)
+ topology.addListener(this);
+ flowReconcileMgr.addFlowReconcileListener(this);
+ entityClassifier.addListener(this);
+
+ Runnable ecr = new Runnable() {
+ @Override
+ public void run() {
+ cleanupEntities();
+ entityCleanupTask.reschedule(ENTITY_CLEANUP_INTERVAL,
+ TimeUnit.SECONDS);
+ }
+ };
+ ScheduledExecutorService ses = threadPool.getScheduledExecutor();
+ entityCleanupTask = new SingletonTask(ses, ecr);
+ entityCleanupTask.reschedule(ENTITY_CLEANUP_INTERVAL,
+ TimeUnit.SECONDS);
+
+ if (restApi != null) {
+ restApi.addRestletRoutable(new DeviceRoutable());
+ } else {
+ logger.debug("Could not instantiate REST API");
+ }
+ }
+
+ // ***************
+ // IHAListener
+ // ***************
+
+ @Override
+ public void roleChanged(Role oldRole, Role newRole) {
+ switch(newRole) {
+ case SLAVE:
+ logger.debug("Resetting device state because of role change");
+ startUp(null);
+ break;
+ default:
+ break;
+ }
+ }
+
+ @Override
+ public void controllerNodeIPsChanged(
+ Map<String, String> curControllerNodeIPs,
+ Map<String, String> addedControllerNodeIPs,
+ Map<String, String> removedControllerNodeIPs) {
+ // no-op
+ }
+
+ // ****************
+ // 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)
+ return Command.STOP;
+
+ // Learn/lookup device information
+ Device srcDevice = learnDeviceByEntity(srcEntity);
+ if (srcDevice == null)
+ 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.
+ Entity dstEntity = getDestEntityFromPacket(eth);
+ Device dstDevice = null;
+ if (dstEntity != null) {
+ dstDevice =
+ findDestByEntity(srcDevice, dstEntity);
+ if (dstDevice != null)
+ fcStore.put(cntx, CONTEXT_DST_DEVICE, dstDevice);
+ }
+
+ if (logger.isTraceEnabled()) {
+ logger.trace("Received PI: {} on switch {}, port {} *** eth={}" +
+ " *** srcDev={} *** dstDev={} *** ",
+ new Object[] { pi, sw.getStringId(), pi.getInPort(), eth,
+ srcDevice, dstDevice });
+ }
+ return Command.CONTINUE;
+ }
+
+ /**
+ * 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 IP address from packet if the packet is either an ARP
+ * or a DHCP packet
+ * @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());
+ }
+ } else if (eth.getPayload() instanceof IPv4) {
+ IPv4 ipv4 = (IPv4) eth.getPayload();
+ if (ipv4.getPayload() instanceof UDP) {
+ UDP udp = (UDP)ipv4.getPayload();
+ if (udp.getPayload() instanceof DHCP) {
+ DHCP dhcp = (DHCP)udp.getPayload();
+ if (dhcp.getOpCode() == DHCP.OPCODE_REPLY) {
+ return ipv4.getSourceAddress();
+ }
+ }
+ }
+ }
+ 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;
+
+ 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());
+ }
+
+ /**
+ * 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;
+
+ 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();
+ }
+
+ boolean learnap = true;
+ if (swDpid == null ||
+ inPort == null ||
+ !isValidAttachmentPoint(swDpid, inPort)) {
+ // If this is an internal port or we otherwise don't want
+ // to learn on these ports. In the future, we should
+ // handle this case by labeling flows with something that
+ // will give us the entity class. For now, we'll do our
+ // best assuming attachment point information isn't used
+ // 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 source the source device. 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(IDevice source,
+ 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(source.getEntityClass());
+ 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
+ return null;
+ }
+ 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)
+ throw new IllegalStateException("Corrupted device index");
+ } else {
+ // If the secondary index does not contain the entity,
+ // create a new Device object containing the entity, and
+ // generate a new device ID. However, we first check if
+ // the entity is allowed (e.g., for spoofing protection)
+ if (!isEntityAllowed(entity, entityClass)) {
+ logger.info("PacketIn is not allowed {} {}",
+ entityClass.getName(), entity);
+ return null;
+ }
+ synchronized (deviceKeyLock) {
+ deviceKey = Long.valueOf(deviceKeyCounter++);
+ }
+ device = allocateDevice(deviceKey, entity, entityClass);
+ if (logger.isDebugEnabled()) {
+ logger.debug("New device created: {} deviceKey={}, entity={}",
+ new Object[]{device, deviceKey, entity});
+ }
+
+ // 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);
+
+ // generate new device update
+ deviceUpdates =
+ updateUpdates(deviceUpdates,
+ new DeviceUpdate(device, ADD, null));
+
+ break;
+ }
+
+ if (!isEntityAllowed(entity, device.getEntityClass())) {
+ logger.info("PacketIn is not allowed {} {}",
+ device.getEntityClass().getName(), entity);
+ return null;
+ }
+ int entityindex = -1;
+ if ((entityindex = device.entityIndex(entity)) >= 0) {
+ // update timestamp on the found entity
+ Date lastSeen = entity.getLastSeenTimestamp();
+ if (lastSeen == null) lastSeen = new Date();
+ device.entities[entityindex].setLastSeenTimestamp(lastSeen);
+ if (device.entities[entityindex].getSwitchDPID() != null &&
+ device.entities[entityindex].getSwitchPort() != null) {
+ long sw = device.entities[entityindex].getSwitchDPID();
+ short port = device.entities[entityindex].getSwitchPort().shortValue();
+
+ boolean moved =
+ device.updateAttachmentPoint(sw,
+ port,
+ lastSeen.getTime());
+
+ if (moved) {
+ 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 NOT updated: " +
+ "attachment points {}," +
+ "entities {}", device.attachmentPoints,
+ device.entities);
+ }
+ }
+ }
+ break;
+ } else {
+ boolean moved = false;
+ Device newDevice = allocateDevice(device, entity);
+ if (entity.getSwitchDPID() != null && entity.getSwitchPort() != null) {
+ moved = newDevice.updateAttachmentPoint(entity.getSwitchDPID(),
+ entity.getSwitchPort().shortValue(),
+ entity.getLastSeenTimestamp().getTime());
+ }
+
+ // generate updates
+ EnumSet<DeviceField> changedFields =
+ findChangedFields(device, entity);
+ if (changedFields.size() > 0)
+ deviceUpdates =
+ updateUpdates(deviceUpdates,
+ new DeviceUpdate(newDevice, CHANGE,
+ changedFields));
+
+ // 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);
+
+ if (moved) {
+ sendDeviceMovedNotification(device);
+ if (logger.isDebugEnabled()) {
+ logger.debug("Device moved: attachment points {}," +
+ "entities {}", device.attachmentPoints,
+ device.entities);
+ }
+ } else {
+ if (logger.isDebugEnabled()) {
+ logger.debug("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);
+
+
+ // generate new device update
+ deviceUpdates =
+ updateUpdates(deviceUpdates,
+ new DeviceUpdate(dev, DELETE, null));
+ }
+ }
+
+ processUpdates(deviceUpdates);
+
+ 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);
+ }
+ for (IDeviceListener listener : deviceListeners) {
+ 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);
+ }
+ }
+
+ // *********************
+ // 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);
+ }
+ }
+
+ /**
+ * Clean up expired entities/devices
+ */
+ protected void cleanupEntities () {
+
+ 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;
+ }
+
+ for (Entity e : toRemove) {
+ removeEntity(e, d.getEntityClass(), d.deviceKey, toKeep);
+ }
+
+ if (toKeep.size() > 0) {
+ Device newDevice = allocateDevice(d.getDeviceKey(),
+ d.oldAPs,
+ d.attachmentPoints,
+ toKeep,
+ d.entityClass);
+
+ EnumSet<DeviceField> changedFields =
+ EnumSet.noneOf(DeviceField.class);
+ for (Entity e : toRemove) {
+ changedFields.addAll(findChangedFields(newDevice, e));
+ }
+ if (changedFields.size() > 0)
+ deviceUpdates.add(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;
+ }
+ } else {
+ deviceUpdates.add(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;
+ }
+ processUpdates(deviceUpdates);
+ break;
+ }
+ }
+ }
+
+ protected void removeEntity(Entity removed,
+ IEntityClass entityClass,
+ Long deviceKey,
+ Collection<Entity> others) {
+ 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) {
+ 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,
+ List<AttachmentPoint> aps,
+ List<AttachmentPoint> trueAPs,
+ Collection<Entity> entities,
+ IEntityClass entityClass) {
+ return new Device(this, deviceKey, aps, trueAPs, entities, entityClass);
+ }
+
+ protected Device allocateDevice(Device device,
+ Entity entity) {
+ return new Device(device, entity);
+ }
+
+ 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(),newAPs, null,
+ entities, device.getEntityClass());
+ d.updateAttachmentPoint();
+ return d;
+ }
+
+ @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));
+ }
+
+ /**
+ * Topology listener method.
+ */
+ @Override
+ public void topologyChanged() {
+ Iterator<Device> diter = deviceMap.values().iterator();
+ List<LDUpdate> updateList = topology.getLastLinkUpdates();
+ 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);
+ }
+ }
+ }
+
+ /**
+ * Send update notifications to listeners
+ * @param updates the updates to process.
+ */
+ protected void sendDeviceMovedNotification(Device d) {
+ for (IDeviceListener listener : deviceListeners) {
+ listener.deviceMoved(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;
+ }
+
+ 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);
+ }
+ return true;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceMultiIndex.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceMultiIndex.java
new file mode 100644
index 0000000..c6aa980
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceMultiIndex.java
@@ -0,0 +1,108 @@
+/**
+* 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.devicemanager.internal;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import net.floodlightcontroller.devicemanager.IDeviceService.DeviceField;
+import net.floodlightcontroller.util.IterableIterator;
+
+/**
+ * An index that maps key fields of an entity to device keys, with multiple
+ * device keys allowed per entity
+ */
+public class DeviceMultiIndex extends DeviceIndex {
+ /**
+ * The index
+ */
+ private ConcurrentHashMap<IndexedEntity, Collection<Long>> index;
+
+ /**
+ * @param keyFields
+ */
+ public DeviceMultiIndex(EnumSet<DeviceField> keyFields) {
+ super(keyFields);
+ index = new ConcurrentHashMap<IndexedEntity, Collection<Long>>();
+ }
+
+ // ***********
+ // DeviceIndex
+ // ***********
+
+ @Override
+ public Iterator<Long> queryByEntity(Entity entity) {
+ IndexedEntity ie = new IndexedEntity(keyFields, entity);
+ Collection<Long> devices = index.get(ie);
+ if (devices != null)
+ return devices.iterator();
+
+ return Collections.<Long>emptySet().iterator();
+ }
+
+ @Override
+ public Iterator<Long> getAll() {
+ Iterator<Collection<Long>> iter = index.values().iterator();
+ return new IterableIterator<Long>(iter);
+ }
+
+ @Override
+ public boolean updateIndex(Device device, Long deviceKey) {
+ for (Entity e : device.entities) {
+ updateIndex(e, deviceKey);
+ }
+ return true;
+ }
+
+ @Override
+ public void updateIndex(Entity entity, Long deviceKey) {
+ Collection<Long> devices = null;
+
+ IndexedEntity ie = new IndexedEntity(keyFields, entity);
+ if (!ie.hasNonNullKeys()) return;
+
+ devices = index.get(ie);
+ if (devices == null) {
+ Map<Long,Boolean> chm = new ConcurrentHashMap<Long,Boolean>();
+ devices = Collections.newSetFromMap(chm);
+ Collection<Long> r = index.putIfAbsent(ie, devices);
+ if (r != null)
+ devices = r;
+ }
+
+ devices.add(deviceKey);
+ }
+
+ @Override
+ public void removeEntity(Entity entity) {
+ IndexedEntity ie = new IndexedEntity(keyFields, entity);
+ index.remove(ie);
+ }
+
+ @Override
+ public void removeEntity(Entity entity, Long deviceKey) {
+ IndexedEntity ie = new IndexedEntity(keyFields, entity);
+ Collection<Long> devices = index.get(ie);
+ if (devices != null)
+ devices.remove(deviceKey);
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceUniqueIndex.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceUniqueIndex.java
new file mode 100644
index 0000000..4f2d3f8
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceUniqueIndex.java
@@ -0,0 +1,116 @@
+/**
+* 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.devicemanager.internal;
+
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.Iterator;
+import java.util.concurrent.ConcurrentHashMap;
+
+import net.floodlightcontroller.devicemanager.IDeviceService.DeviceField;
+
+/**
+ * An index that maps key fields of an entity uniquely to a device key
+ */
+public class DeviceUniqueIndex extends DeviceIndex {
+ /**
+ * The index
+ */
+ private ConcurrentHashMap<IndexedEntity, Long> index;
+
+ /**
+ * Construct a new device index using the provided key fields
+ * @param keyFields the key fields to use
+ */
+ public DeviceUniqueIndex(EnumSet<DeviceField> keyFields) {
+ super(keyFields);
+ index = new ConcurrentHashMap<IndexedEntity, Long>();
+ }
+
+ // ***********
+ // DeviceIndex
+ // ***********
+
+ @Override
+ public Iterator<Long> queryByEntity(Entity entity) {
+ final Long deviceKey = findByEntity(entity);
+ if (deviceKey != null)
+ return Collections.<Long>singleton(deviceKey).iterator();
+
+ return Collections.<Long>emptySet().iterator();
+ }
+
+ @Override
+ public Iterator<Long> getAll() {
+ return index.values().iterator();
+ }
+
+ @Override
+ public boolean updateIndex(Device device, Long deviceKey) {
+ for (Entity e : device.entities) {
+ IndexedEntity ie = new IndexedEntity(keyFields, e);
+ if (!ie.hasNonNullKeys()) continue;
+
+ Long ret = index.putIfAbsent(ie, deviceKey);
+ if (ret != null && !ret.equals(deviceKey)) {
+ // If the return value is non-null, then fail the insert
+ // (this implies that a device using this entity has
+ // already been created in another thread).
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public void updateIndex(Entity entity, Long deviceKey) {
+ IndexedEntity ie = new IndexedEntity(keyFields, entity);
+ if (!ie.hasNonNullKeys()) return;
+ index.put(ie, deviceKey);
+ }
+
+ @Override
+ public void removeEntity(Entity entity) {
+ IndexedEntity ie = new IndexedEntity(keyFields, entity);
+ index.remove(ie);
+ }
+
+ @Override
+ public void removeEntity(Entity entity, Long deviceKey) {
+ IndexedEntity ie = new IndexedEntity(keyFields, entity);
+ index.remove(ie, deviceKey);
+ }
+
+ // **************
+ // Public Methods
+ // **************
+
+ /**
+ * Look up a {@link Device} based on the provided {@link Entity}.
+ * @param entity the entity to search for
+ * @return The key for the {@link Device} object if found
+ */
+ public Long findByEntity(Entity entity) {
+ IndexedEntity ie = new IndexedEntity(keyFields, entity);
+ Long deviceKey = index.get(ie);
+ if (deviceKey == null)
+ return null;
+ return deviceKey;
+ }
+
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/internal/Entity.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/internal/Entity.java
new file mode 100644
index 0000000..36c5471
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/internal/Entity.java
@@ -0,0 +1,279 @@
+/**
+* Copyright 2011,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.devicemanager.internal;
+
+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 org.codehaus.jackson.map.annotate.JsonSerialize;
+import org.openflow.util.HexString;
+
+/**
+ * An entity on the network is a visible trace of a device that corresponds
+ * to a packet received from a particular interface on the edge of a network,
+ * with a particular VLAN tag, and a particular MAC address, along with any
+ * other packet characteristics we might want to consider as helpful for
+ * disambiguating devices.
+ *
+ * Entities are the most basic element of devices; devices consist of one or
+ * more entities. Entities are immutable once created, except for the last
+ * seen timestamp.
+ *
+ * @author readams
+ *
+ */
+public class Entity implements Comparable<Entity> {
+ /**
+ * Timeout for computing {@link Entity#activeSince}.
+ * @see {@link Entity#activeSince}
+ */
+ protected static int ACTIVITY_TIMEOUT = 30000;
+
+ /**
+ * The MAC address associated with this entity
+ */
+ protected long 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;
+
+ /**
+ * The VLAN tag on this entity, or null if untagged
+ */
+ protected Short vlan;
+
+ /**
+ * The DPID of the switch for the ingress point for this entity,
+ * or null if not present
+ */
+ protected Long switchDPID;
+
+ /**
+ * The port number of the switch for the ingress point for this entity,
+ * or null if not present
+ */
+ protected Integer switchPort;
+
+ /**
+ * The last time we observed this entity on the network
+ */
+ protected Date lastSeenTimestamp;
+
+ /**
+ * The time between {@link Entity#activeSince} and
+ * {@link Entity#lastSeenTimestamp} is a period of activity for this
+ * entity where it was observed repeatedly. If, when the entity is
+ * observed, the is longer ago than the activity timeout,
+ * {@link Entity#lastSeenTimestamp} and {@link Entity#activeSince} will
+ * be set to the current time.
+ */
+ protected Date activeSince;
+
+ private int hashCode = 0;
+
+ // ************
+ // Constructors
+ // ************
+
+ /**
+ * Create a new entity
+ *
+ * @param macAddress
+ * @param vlan
+ * @param ipv4Address
+ * @param switchDPID
+ * @param switchPort
+ * @param lastSeenTimestamp
+ */
+ public Entity(long macAddress, Short vlan,
+ Integer ipv4Address, Long switchDPID, Integer switchPort,
+ Date lastSeenTimestamp) {
+ this.macAddress = macAddress;
+ this.ipv4Address = ipv4Address;
+ this.vlan = vlan;
+ this.switchDPID = switchDPID;
+ this.switchPort = switchPort;
+ this.lastSeenTimestamp = lastSeenTimestamp;
+ this.activeSince = lastSeenTimestamp;
+ }
+
+ // ***************
+ // Getters/Setters
+ // ***************
+
+ @JsonSerialize(using=MACSerializer.class)
+ public long getMacAddress() {
+ return macAddress;
+ }
+
+ @JsonSerialize(using=IPv4Serializer.class)
+ public Integer getIpv4Address() {
+ return ipv4Address;
+ }
+
+ public Short getVlan() {
+ return vlan;
+ }
+
+ @JsonSerialize(using=DPIDSerializer.class)
+ public Long getSwitchDPID() {
+ return switchDPID;
+ }
+
+ public Integer getSwitchPort() {
+ return switchPort;
+ }
+
+ public Date getLastSeenTimestamp() {
+ return lastSeenTimestamp;
+ }
+
+ /**
+ * Set the last seen timestamp and also update {@link Entity#activeSince}
+ * if appropriate
+ * @param lastSeenTimestamp the new last seen timestamp
+ * @see {@link Entity#activeSince}
+ */
+ public void setLastSeenTimestamp(Date lastSeenTimestamp) {
+ if (activeSince == null ||
+ (activeSince.getTime() + ACTIVITY_TIMEOUT) <
+ lastSeenTimestamp.getTime())
+ this.activeSince = lastSeenTimestamp;
+ this.lastSeenTimestamp = lastSeenTimestamp;
+ }
+
+ public Date getActiveSince() {
+ return activeSince;
+ }
+
+ public void setActiveSince(Date activeSince) {
+ this.activeSince = activeSince;
+ }
+
+ @Override
+ public int hashCode() {
+ if (hashCode != 0) return hashCode;
+ final int prime = 31;
+ hashCode = 1;
+ hashCode = prime * hashCode
+ + ((ipv4Address == null) ? 0 : ipv4Address.hashCode());
+ hashCode = prime * hashCode + (int) (macAddress ^ (macAddress >>> 32));
+ hashCode = prime * hashCode
+ + ((switchDPID == null) ? 0 : switchDPID.hashCode());
+ hashCode = prime * hashCode
+ + ((switchPort == null) ? 0 : switchPort.hashCode());
+ hashCode = prime * hashCode + ((vlan == null) ? 0 : vlan.hashCode());
+ return hashCode;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+ if (obj == null) return false;
+ if (getClass() != obj.getClass()) return false;
+ Entity other = (Entity) obj;
+ if (hashCode() != other.hashCode()) return false;
+ if (ipv4Address == null) {
+ if (other.ipv4Address != null) return false;
+ } else if (!ipv4Address.equals(other.ipv4Address)) return false;
+ if (macAddress != other.macAddress) return false;
+ if (switchDPID == null) {
+ if (other.switchDPID != null) return false;
+ } else if (!switchDPID.equals(other.switchDPID)) return false;
+ if (switchPort == null) {
+ if (other.switchPort != null) return false;
+ } else if (!switchPort.equals(other.switchPort)) return false;
+ if (vlan == null) {
+ if (other.vlan != null) return false;
+ } else if (!vlan.equals(other.vlan)) return false;
+ return true;
+ }
+
+
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("Entity [macAddress=");
+ builder.append(HexString.toHexString(macAddress, 6));
+ builder.append(", ipv4Address=");
+ builder.append(IPv4.fromIPv4Address(ipv4Address==null ?
+ 0 : ipv4Address.intValue()));
+ builder.append(", vlan=");
+ builder.append(vlan);
+ builder.append(", switchDPID=");
+ builder.append(switchDPID);
+ builder.append(", switchPort=");
+ builder.append(switchPort);
+ builder.append(", lastSeenTimestamp=");
+ builder.append(lastSeenTimestamp == null? "null" : lastSeenTimestamp.getTime());
+ builder.append(", activeSince=");
+ builder.append(activeSince == null? "null" : activeSince.getTime());
+ builder.append("]");
+ return builder.toString();
+ }
+
+ @Override
+ public int compareTo(Entity o) {
+ if (macAddress < o.macAddress) return -1;
+ if (macAddress > o.macAddress) return 1;
+
+ int r;
+ if (switchDPID == null)
+ r = o.switchDPID == null ? 0 : -1;
+ else if (o.switchDPID == null)
+ r = 1;
+ else
+ r = switchDPID.compareTo(o.switchDPID);
+ if (r != 0) return r;
+
+ if (switchPort == null)
+ r = o.switchPort == null ? 0 : -1;
+ else if (o.switchPort == null)
+ r = 1;
+ else
+ r = switchPort.compareTo(o.switchPort);
+ if (r != 0) return r;
+
+ if (ipv4Address == null)
+ r = o.ipv4Address == null ? 0 : -1;
+ else if (o.ipv4Address == null)
+ r = 1;
+ else
+ r = ipv4Address.compareTo(o.ipv4Address);
+ if (r != 0) return r;
+
+ if (vlan == null)
+ r = o.vlan == null ? 0 : -1;
+ else if (o.vlan == null)
+ r = 1;
+ else
+ r = vlan.compareTo(o.vlan);
+ if (r != 0) return r;
+
+ return 0;
+ }
+
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/internal/IndexedEntity.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/internal/IndexedEntity.java
new file mode 100644
index 0000000..3e0829d
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/internal/IndexedEntity.java
@@ -0,0 +1,155 @@
+package net.floodlightcontroller.devicemanager.internal;
+
+import java.util.EnumSet;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import net.floodlightcontroller.devicemanager.IDeviceService;
+import net.floodlightcontroller.devicemanager.IDeviceService.DeviceField;
+
+
+/**
+ * This is a thin wrapper around {@link Entity} that allows overriding
+ * the behavior of {@link Object#hashCode()} and {@link Object#equals(Object)}
+ * so that the keying behavior in a hash map can be changed dynamically
+ * @author readams
+ */
+public class IndexedEntity {
+ protected EnumSet<DeviceField> keyFields;
+ protected Entity entity;
+ private int hashCode = 0;
+ protected static Logger logger =
+ LoggerFactory.getLogger(IndexedEntity.class);
+ /**
+ * Create a new {@link IndexedEntity} for the given {@link Entity} using
+ * the provided key fields.
+ * @param keyFields The key fields that will be used for computing
+ * {@link IndexedEntity#hashCode()} and {@link IndexedEntity#equals(Object)}
+ * @param entity the entity to wrap
+ */
+ public IndexedEntity(EnumSet<DeviceField> keyFields, Entity entity) {
+ super();
+ this.keyFields = keyFields;
+ this.entity = entity;
+ }
+
+ /**
+ * Check whether this entity has non-null values in any of its key fields
+ * @return true if any key fields have a non-null value
+ */
+ public boolean hasNonNullKeys() {
+ for (DeviceField f : keyFields) {
+ switch (f) {
+ case MAC:
+ return true;
+ case IPV4:
+ if (entity.ipv4Address != null) return true;
+ break;
+ case SWITCH:
+ if (entity.switchDPID != null) return true;
+ break;
+ case PORT:
+ if (entity.switchPort != null) return true;
+ break;
+ case VLAN:
+ if (entity.vlan != null) return true;
+ break;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+
+ if (hashCode != 0) {
+ return hashCode;
+ }
+
+ final int prime = 31;
+ hashCode = 1;
+ for (DeviceField f : keyFields) {
+ switch (f) {
+ case MAC:
+ hashCode = prime * hashCode
+ + (int) (entity.macAddress ^
+ (entity.macAddress >>> 32));
+ break;
+ case IPV4:
+ hashCode = prime * hashCode
+ + ((entity.ipv4Address == null)
+ ? 0
+ : entity.ipv4Address.hashCode());
+ break;
+ case SWITCH:
+ hashCode = prime * hashCode
+ + ((entity.switchDPID == null)
+ ? 0
+ : entity.switchDPID.hashCode());
+ break;
+ case PORT:
+ hashCode = prime * hashCode
+ + ((entity.switchPort == null)
+ ? 0
+ : entity.switchPort.hashCode());
+ break;
+ case VLAN:
+ hashCode = prime * hashCode
+ + ((entity.vlan == null)
+ ? 0
+ : entity.vlan.hashCode());
+ break;
+ }
+ }
+ return hashCode;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+ if (obj == null) return false;
+ if (getClass() != obj.getClass()) return false;
+ IndexedEntity other = (IndexedEntity) obj;
+
+ if (!keyFields.equals(other.keyFields))
+ return false;
+
+ for (IDeviceService.DeviceField f : keyFields) {
+ switch (f) {
+ case MAC:
+ if (entity.macAddress != 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;
+ break;
+ case SWITCH:
+ if (entity.switchDPID == null) {
+ if (other.entity.switchDPID != null) 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;
+ break;
+ case VLAN:
+ if (entity.vlan == null) {
+ if (other.entity.vlan != null) return false;
+ } else if (!entity.vlan.
+ equals(other.entity.vlan)) return false;
+ break;
+ }
+ }
+
+ return true;
+ }
+
+
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/web/AbstractDeviceResource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/web/AbstractDeviceResource.java
new file mode 100644
index 0000000..58e79e4
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/web/AbstractDeviceResource.java
@@ -0,0 +1,197 @@
+/**
+* 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.devicemanager.web;
+
+import java.util.Iterator;
+
+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.restlet.data.Form;
+import org.restlet.data.Status;
+import org.restlet.resource.ServerResource;
+
+/**
+ * Resource for querying and displaying devices that exist in the system
+ */
+public abstract class AbstractDeviceResource extends ServerResource {
+ public static final String MAC_ERROR =
+ "Invalid MAC address: must be a 48-bit quantity, " +
+ "expressed in hex as AA:BB:CC:DD:EE:FF";
+ public static final String VLAN_ERROR =
+ "Invalid VLAN: must be an integer in the range 0-4095";
+ public static final String IPV4_ERROR =
+ "Invalid IPv4 address: must be in dotted decimal format, " +
+ "234.0.59.1";
+ public static final String DPID_ERROR =
+ "Invalid Switch DPID: must be a 64-bit quantity, expressed in " +
+ "hex as AA:BB:CC:DD:EE:FF:00:11";
+ public static final String PORT_ERROR =
+ "Invalid Port: must be a positive integer";
+
+ public Iterator<? extends IDevice> getDevices() {
+ IDeviceService deviceManager =
+ (IDeviceService)getContext().getAttributes().
+ get(IDeviceService.class.getCanonicalName());
+
+ Long macAddress = null;
+ Short vlan = null;
+ Integer ipv4Address = null;
+ Long switchDPID = null;
+ Integer switchPort = null;
+
+ Form form = getQuery();
+ String macAddrStr = form.getFirstValue("mac", true);
+ String vlanStr = form.getFirstValue("vlan", true);
+ String ipv4Str = form.getFirstValue("ipv4", true);
+ String dpid = form.getFirstValue("dpid", true);
+ String port = form.getFirstValue("port", true);
+
+ if (macAddrStr != null) {
+ try {
+ macAddress = HexString.toLong(macAddrStr);
+ } catch (Exception e) {
+ setStatus(Status.CLIENT_ERROR_BAD_REQUEST, MAC_ERROR);
+ return null;
+ }
+ }
+ if (vlanStr != null) {
+ try {
+ vlan = Short.parseShort(vlanStr);
+ if (vlan > 4095 || vlan < 0) {
+ setStatus(Status.CLIENT_ERROR_BAD_REQUEST, VLAN_ERROR);
+ return null;
+ }
+ } catch (Exception e) {
+ setStatus(Status.CLIENT_ERROR_BAD_REQUEST, VLAN_ERROR);
+ return null;
+ }
+ }
+ if (ipv4Str != null) {
+ try {
+ ipv4Address = IPv4.toIPv4Address(ipv4Str);
+ } catch (Exception e) {
+ setStatus(Status.CLIENT_ERROR_BAD_REQUEST, IPV4_ERROR);
+ return null;
+ }
+ }
+ if (dpid != null) {
+ try {
+ switchDPID = HexString.toLong(dpid);
+ } catch (Exception e) {
+ setStatus(Status.CLIENT_ERROR_BAD_REQUEST, DPID_ERROR);
+ return null;
+ }
+ }
+ if (port != null) {
+ try {
+ switchPort = Integer.parseInt(port);
+ if (switchPort < 0) {
+ setStatus(Status.CLIENT_ERROR_BAD_REQUEST, PORT_ERROR);
+ return null;
+ }
+ } catch (Exception e) {
+ setStatus(Status.CLIENT_ERROR_BAD_REQUEST, PORT_ERROR);
+ return null;
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ Iterator<Device> diter = (Iterator<Device>)
+ deviceManager.queryDevices(macAddress,
+ vlan,
+ ipv4Address,
+ switchDPID,
+ switchPort);
+
+ final String macStartsWith =
+ form.getFirstValue("mac__startswith", true);
+ final String vlanStartsWith =
+ form.getFirstValue("vlan__startswith", true);
+ final String ipv4StartsWith =
+ form.getFirstValue("ipv4__startswith", true);
+ final String dpidStartsWith =
+ form.getFirstValue("dpid__startswith", true);
+ final String portStartsWith =
+ form.getFirstValue("port__startswith", true);
+
+ return new FilterIterator<Device>(diter) {
+ @Override
+ protected boolean matches(Device value) {
+ if (macStartsWith != null) {
+ if (!value.getMACAddressString().startsWith(macStartsWith))
+ return false;
+ }
+ if (vlanStartsWith != null) {
+ boolean match = false;
+ for (Short v : value.getVlanId()) {
+ if (v != null &&
+ v.toString().startsWith(vlanStartsWith)) {
+ match = true;
+ break;
+ }
+ }
+ if (!match) return false;
+ }
+ if (ipv4StartsWith != null) {
+ boolean match = false;
+ for (Integer v : value.getIPv4Addresses()) {
+ String str = IPv4.fromIPv4Address(v);
+ if (v != null &&
+ str.startsWith(ipv4StartsWith)) {
+ match = true;
+ break;
+ }
+ }
+ if (!match) return false;
+ }
+ if (dpidStartsWith != null) {
+ boolean match = false;
+ for (SwitchPort v : value.getAttachmentPoints(true)) {
+ String str =
+ HexString.toHexString(v.getSwitchDPID(), 8);
+ if (v != null &&
+ str.startsWith(dpidStartsWith)) {
+ match = true;
+ break;
+ }
+ }
+ if (!match) return false;
+ }
+ if (portStartsWith != null) {
+ boolean match = false;
+ for (SwitchPort v : value.getAttachmentPoints(true)) {
+ String str = Integer.toString(v.getPort());
+ if (v != null &&
+ str.startsWith(portStartsWith)) {
+ match = true;
+ break;
+ }
+ }
+ if (!match) return false;
+ }
+ return true;
+ }
+ };
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/web/DeviceEntityResource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/web/DeviceEntityResource.java
new file mode 100644
index 0000000..2783a26
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/web/DeviceEntityResource.java
@@ -0,0 +1,55 @@
+/**
+* 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.devicemanager.web;
+
+import java.util.Iterator;
+
+import net.floodlightcontroller.devicemanager.IDevice;
+import net.floodlightcontroller.devicemanager.internal.Device;
+import net.floodlightcontroller.devicemanager.internal.Entity;
+
+import org.restlet.resource.Get;
+
+/**
+ * Resource for querying and displaying internal debug information on
+ * network entities associated with devices
+ */
+public class DeviceEntityResource extends AbstractDeviceResource {
+ @Get("json")
+ public Iterator<Entity[]> getDeviceEntities() {
+ final Iterator<? extends IDevice> devices = super.getDevices();
+ return new Iterator<Entity[]>() {
+
+ @Override
+ public boolean hasNext() {
+ return devices.hasNext();
+ }
+
+ @Override
+ public Entity[] next() {
+ Device d = (Device)devices.next();
+ return d.getEntities();
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ };
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/web/DeviceResource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/web/DeviceResource.java
new file mode 100644
index 0000000..c479af0
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/web/DeviceResource.java
@@ -0,0 +1,33 @@
+/**
+* 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.devicemanager.web;
+
+import java.util.Iterator;
+
+import net.floodlightcontroller.devicemanager.IDevice;
+import org.restlet.resource.Get;
+
+/**
+ * Resource for querying and displaying devices that exist in the system
+ */
+public class DeviceResource extends AbstractDeviceResource {
+ @Get("json")
+ public Iterator<? extends IDevice> getDevices() {
+ return super.getDevices();
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/web/DeviceRoutable.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/web/DeviceRoutable.java
new file mode 100644
index 0000000..9a76505
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/web/DeviceRoutable.java
@@ -0,0 +1,44 @@
+/**
+* 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.devicemanager.web;
+
+import org.restlet.Context;
+import org.restlet.Restlet;
+import org.restlet.routing.Router;
+
+import net.floodlightcontroller.restserver.RestletRoutable;
+
+/**
+ * Routable for device rest api
+ */
+public class DeviceRoutable implements RestletRoutable {
+
+ @Override
+ public String basePath() {
+ return "/wm/device";
+ }
+
+ @Override
+ public Restlet getRestlet(Context context) {
+ Router router = new Router(context);
+ router.attach("/", DeviceResource.class);
+ router.attach("/debug", DeviceEntityResource.class);
+ return router;
+ }
+
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/web/DeviceSerializer.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/web/DeviceSerializer.java
new file mode 100644
index 0000000..66bdaef
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/devicemanager/web/DeviceSerializer.java
@@ -0,0 +1,70 @@
+/**
+* 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.devicemanager.web;
+
+import java.io.IOException;
+
+import net.floodlightcontroller.devicemanager.SwitchPort;
+import net.floodlightcontroller.devicemanager.internal.Device;
+import net.floodlightcontroller.packet.IPv4;
+
+import org.codehaus.jackson.JsonGenerator;
+import org.codehaus.jackson.JsonProcessingException;
+import org.codehaus.jackson.map.JsonSerializer;
+import org.codehaus.jackson.map.SerializerProvider;
+import org.openflow.util.HexString;
+
+/**
+ * Serialize a device object
+ */
+public class DeviceSerializer extends JsonSerializer<Device> {
+
+ @Override
+ public void serialize(Device device, JsonGenerator jGen,
+ SerializerProvider serializer) throws IOException,
+ JsonProcessingException {
+ jGen.writeStartObject();
+
+ jGen.writeStringField("entityClass", device.getEntityClass().getName());
+
+ jGen.writeArrayFieldStart("mac");
+ jGen.writeString(HexString.toHexString(device.getMACAddress(), 6));
+ jGen.writeEndArray();
+
+ jGen.writeArrayFieldStart("ipv4");
+ for (Integer ip : device.getIPv4Addresses())
+ jGen.writeString(IPv4.fromIPv4Address(ip));
+ jGen.writeEndArray();
+
+ jGen.writeArrayFieldStart("vlan");
+ for (Short vlan : device.getVlanId())
+ if (vlan >= 0)
+ jGen.writeNumber(vlan);
+ jGen.writeEndArray();
+ jGen.writeArrayFieldStart("attachmentPoint");
+ for (SwitchPort ap : device.getAttachmentPoints(true)) {
+ serializer.defaultSerializeValue(ap, jGen);
+ }
+ jGen.writeEndArray();
+
+ jGen.writeNumberField("lastSeen", device.getLastSeen().getTime());
+
+ jGen.writeEndObject();
+ }
+
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/firewall/Firewall.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/firewall/Firewall.java
new file mode 100644
index 0000000..3f8ff6c
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/firewall/Firewall.java
@@ -0,0 +1,667 @@
+package net.floodlightcontroller.firewall;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+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 net.floodlightcontroller.core.FloodlightContext;
+import net.floodlightcontroller.core.IOFMessageListener;
+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.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;
+import net.floodlightcontroller.routing.IRoutingDecision;
+import net.floodlightcontroller.routing.RoutingDecision;
+import net.floodlightcontroller.storage.IResultSet;
+import net.floodlightcontroller.storage.IStorageSourceService;
+import net.floodlightcontroller.storage.StorageException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Stateless firewall implemented as a Google Summer of Code project.
+ * Configuration done through REST API
+ *
+ * @author Amer Tahir
+ * @edited KC Wang
+ */
+public class Firewall implements IFirewallService, IOFMessageListener,
+ IFloodlightModule {
+
+ // service modules needed
+ protected IFloodlightProviderService floodlightProvider;
+ protected IStorageSourceService storageSource;
+ protected IRestApiService restApi;
+ protected static Logger logger;
+
+ protected List<FirewallRule> rules; // protected by synchronized
+ protected boolean enabled;
+ protected int subnet_mask = IPv4.toIPv4Address("255.255.255.0");
+
+ // constant strings for storage/parsing
+ public static final String TABLE_NAME = "controller_firewallrules";
+ public static final String COLUMN_RULEID = "ruleid";
+ public static final String COLUMN_DPID = "dpid";
+ 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_TYPE = "dl_type";
+ public static final String COLUMN_NW_SRC_PREFIX = "nw_src_prefix";
+ public static final String COLUMN_NW_SRC_MASKBITS = "nw_src_maskbits";
+ public static final String COLUMN_NW_DST_PREFIX = "nw_dst_prefix";
+ public static final String COLUMN_NW_DST_MASKBITS = "nw_dst_maskbits";
+ public static final String COLUMN_NW_PROTO = "nw_proto";
+ 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_PRIORITY = "priority";
+ public static final String COLUMN_ACTION = "action";
+ public static String ColumnNames[] = { COLUMN_RULEID, COLUMN_DPID,
+ COLUMN_IN_PORT, COLUMN_DL_SRC, COLUMN_DL_DST, COLUMN_DL_TYPE,
+ COLUMN_NW_SRC_PREFIX, COLUMN_NW_SRC_MASKBITS, COLUMN_NW_DST_PREFIX,
+ COLUMN_NW_DST_MASKBITS, COLUMN_NW_PROTO, COLUMN_TP_SRC,
+ COLUMN_TP_DST, COLUMN_WILDCARD_DPID, COLUMN_WILDCARD_IN_PORT,
+ COLUMN_WILDCARD_DL_SRC, COLUMN_WILDCARD_DL_DST,
+ COLUMN_WILDCARD_DL_TYPE, COLUMN_WILDCARD_NW_SRC,
+ COLUMN_WILDCARD_NW_DST, COLUMN_WILDCARD_NW_PROTO, COLUMN_PRIORITY,
+ COLUMN_ACTION };
+
+ @Override
+ public String getName() {
+ return "firewall";
+ }
+
+ @Override
+ public boolean isCallbackOrderingPrereq(OFType type, String name) {
+ // no prereq
+ return false;
+ }
+
+ @Override
+ public boolean isCallbackOrderingPostreq(OFType type, String name) {
+ return (type.equals(OFType.PACKET_IN) && name.equals("forwarding"));
+ }
+
+ @Override
+ public Collection<Class<? extends IFloodlightService>> getModuleServices() {
+ Collection<Class<? extends IFloodlightService>> l = new ArrayList<Class<? extends IFloodlightService>>();
+ l.add(IFirewallService.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(IFirewallService.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;
+ }
+
+ /**
+ * Reads the rules from the storage and creates a sorted arraylist of
+ * FirewallRule from them.
+ *
+ * Similar to getStorageRules(), which only reads contents for REST GET and
+ * does no parsing, checking, nor putting into FirewallRule objects
+ *
+ * @return the sorted arraylist of FirewallRule instances (rules from
+ * storage)
+ */
+ protected ArrayList<FirewallRule> readRulesFromStorage() {
+ ArrayList<FirewallRule> l = new ArrayList<FirewallRule>();
+
+ try {
+ Map<String, Object> row;
+
+ // (..., null, null) for no predicate, no ordering
+ 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);
+ return l;
+ }
+ try {
+ r.ruleid = Integer
+ .parseInt((String) row.get(COLUMN_RULEID));
+ r.dpid = Long.parseLong((String) row.get(COLUMN_DPID));
+
+ for (String key : row.keySet()) {
+ if (row.get(key) == null)
+ continue;
+ 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)) {
+ 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())
+ r.action = FirewallRule.FirewallAction.ALLOW;
+ else {
+ r.action = null;
+ logger.error("action not recognized");
+ }
+ }
+ }
+ } catch (ClassCastException e) {
+ logger.error(
+ "skipping rule {} with bad data : "
+ + e.getMessage(), r.ruleid);
+ }
+ if (r.action != null)
+ l.add(r);
+ }
+ } catch (StorageException e) {
+ logger.error("failed to access storage: {}", e.getMessage());
+ // if the table doesn't exist, then wait to populate later via
+ // setStorageSource()
+ }
+
+ // now, sort the list based on priorities
+ Collections.sort(l);
+
+ 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);
+ rules = new ArrayList<FirewallRule>();
+ logger = LoggerFactory.getLogger(Firewall.class);
+
+ // start disabled
+ enabled = false;
+ }
+
+ @Override
+ public void startUp(FloodlightModuleContext context) {
+ // register REST interface
+ restApi.addRestletRoutable(new FirewallWebRoutable());
+
+ // always place firewall in pipeline at bootup
+ floodlightProvider.addOFMessageListener(OFType.PACKET_IN, this);
+
+ // storage, create table and read rules
+ storageSource.createTable(TABLE_NAME, null);
+ storageSource.setTablePrimaryKeyName(TABLE_NAME, COLUMN_RULEID);
+ synchronized (rules) {
+ this.rules = readRulesFromStorage();
+ }
+ }
+
+ @Override
+ public Command receive(IOFSwitch sw, OFMessage msg, FloodlightContext cntx) {
+ 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);
+ }
+ break;
+ default:
+ break;
+ }
+
+ return Command.CONTINUE;
+ }
+
+ @Override
+ public void enableFirewall(boolean enabled) {
+ logger.info("Setting firewall to {}", enabled);
+ this.enabled = enabled;
+ }
+
+ @Override
+ public List<FirewallRule> getRules() {
+ return this.rules;
+ }
+
+ // Only used to serve REST GET
+ // Similar to readRulesFromStorage(), which actually checks and stores
+ // record into FirewallRule list
+ @Override
+ public List<Map<String, Object>> getStorageRules() {
+ 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);
+ for (Iterator<IResultSet> it = resultSet.iterator(); it.hasNext();) {
+ l.add(it.next().getRow());
+ }
+ } catch (StorageException e) {
+ logger.error("failed to access storage: {}", e.getMessage());
+ // if the table doesn't exist, then wait to populate later via
+ // setStorageSource()
+ }
+ return l;
+ }
+
+ @Override
+ public String getSubnetMask() {
+ return IPv4.fromIPv4Address(this.subnet_mask);
+ }
+
+ @Override
+ public void setSubnetMask(String newMask) {
+ if (newMask.trim().isEmpty())
+ return;
+ this.subnet_mask = IPv4.toIPv4Address(newMask.trim());
+ }
+
+ @Override
+ public synchronized void addRule(FirewallRule rule) {
+
+ // generate random ruleid for each newly created rule
+ // may want to return to caller if useful
+ // may want to check conflict
+ rule.ruleid = rule.genID();
+
+ int i = 0;
+ // locate the position of the new rule in the sorted arraylist
+ for (i = 0; i < this.rules.size(); i++) {
+ if (this.rules.get(i).priority >= rule.priority)
+ break;
+ }
+ // now, add rule to the list
+ if (i <= this.rules.size()) {
+ this.rules.add(i, rule);
+ } else {
+ this.rules.add(rule);
+ }
+ // 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_PRIORITY, Integer.toString(rule.priority));
+ entry.put(COLUMN_ACTION, Integer.toString(rule.action.ordinal()));
+ storageSource.insertRow(TABLE_NAME, entry);
+ }
+
+ @Override
+ public synchronized void deleteRule(int ruleid) {
+ Iterator<FirewallRule> iter = this.rules.iterator();
+ while (iter.hasNext()) {
+ FirewallRule r = iter.next();
+ if (r.ruleid == ruleid) {
+ // found the rule, now remove it
+ iter.remove();
+ break;
+ }
+ }
+ // delete from database
+ storageSource.deleteRow(TABLE_NAME, Integer.toString(ruleid));
+ }
+
+ /**
+ * Iterates over the firewall rules and tries to match them with the
+ * incoming packet (flow). Uses the FirewallRule class's matchWithFlow
+ * method to perform matching. It maintains a pair of wildcards (allow and
+ * deny) which are assigned later to the firewall's decision, where 'allow'
+ * wildcards are applied if the matched rule turns out to be an ALLOW rule
+ * and 'deny' wildcards are applied otherwise. Wildcards are applied to
+ * firewall decision to optimize flows in the switch, ensuring least number
+ * of flows per firewall rule. So, if a particular field is not "ANY" (i.e.
+ * not wildcarded) in a higher priority rule, then if a lower priority rule
+ * matches the packet and wildcards it, it can't be wildcarded in the
+ * switch's flow entry, because otherwise some packets matching the higher
+ * priority rule might escape the firewall. The reason for keeping different
+ * two different wildcards is that if a field is not wildcarded in a higher
+ * priority allow rule, the same field shouldn't be wildcarded for packets
+ * matching the lower priority deny rule (non-wildcarded fields in higher
+ * priority rules override the wildcarding of those fields in lower priority
+ * rules of the opposite type). So, to ensure that wildcards are
+ * appropriately set for different types of rules (allow vs. deny), separate
+ * wildcards are maintained. Iteration is performed on the sorted list of
+ * rules (sorted in decreasing order of priority).
+ *
+ * @param sw
+ * the switch instance
+ * @param pi
+ * the incoming packet data structure
+ * @param cntx
+ * the floodlight context
+ * @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) {
+ FirewallRule matched_rule = null;
+ Ethernet eth = IFloodlightProviderService.bcStore.get(cntx,
+ IFloodlightProviderService.CONTEXT_PI_PAYLOAD);
+ WildcardsPair wildcards = new WildcardsPair();
+
+ synchronized (rules) {
+ Iterator<FirewallRule> iter = this.rules.iterator();
+ FirewallRule rule = null;
+ // iterate through list to find a matching firewall rule
+ while (iter.hasNext()) {
+ // get next rule from list
+ rule = iter.next();
+
+ // check if rule matches
+ if (rule.matchesFlow(sw.getId(), pi.getInPort(), eth, wildcards) == true) {
+ matched_rule = rule;
+ break;
+ }
+ }
+ }
+
+ // 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;
+ } else {
+ ret.wildcards = wildcards.allow;
+ }
+ return ret;
+ }
+
+ /**
+ * Checks whether an IP address is a broadcast address or not (determines
+ * using subnet mask)
+ *
+ * @param IPAddress
+ * the IP address to check
+ * @return true if it is a broadcast address, false otherwise
+ */
+ protected boolean IPIsBroadcast(int IPAddress) {
+ // inverted subnet mask
+ int inv_subnet_mask = ~this.subnet_mask;
+ 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);
+
+ // 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.getEtherType() == Ethernet.TYPE_IPv4
+ && this.IPIsBroadcast(((IPv4) eth.getPayload())
+ .getDestinationAddress()) == false) {
+ allowBroadcast = false;
+ }
+ if (allowBroadcast == true) {
+ 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),
+ 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),
+ IRoutingDecision.RoutingAction.DROP);
+ decision.addToContext(cntx);
+ }
+ return Command.CONTINUE;
+ }
+ /*
+ * ARP response (unicast) can be let through without filtering through
+ * rules by uncommenting the code below
+ */
+ /*
+ * else if (eth.getEtherType() == Ethernet.TYPE_ARP) {
+ * logger.info("allowing ARP traffic"); decision = new
+ * FirewallDecision(IRoutingDecision.RoutingAction.FORWARD_OR_FLOOD);
+ * decision.addToContext(cntx); return Command.CONTINUE; }
+ */
+
+ // check if we have a matching rule for this packet/flow
+ // and no decision is taken 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);
+ 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);
+ }
+ }
+ } else {
+ 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.addToContext(cntx);
+ if (logger.isTraceEnabled())
+ logger.trace("Allow rule={} match for PacketIn={}", rule,
+ pi);
+ }
+ }
+
+ return Command.CONTINUE;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/firewall/FirewallResource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/firewall/FirewallResource.java
new file mode 100644
index 0000000..1f4d71a
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/firewall/FirewallResource.java
@@ -0,0 +1,125 @@
+package net.floodlightcontroller.firewall;
+
+import java.io.IOException;
+
+import org.codehaus.jackson.JsonParseException;
+import org.codehaus.jackson.JsonParser;
+import org.codehaus.jackson.JsonToken;
+import org.codehaus.jackson.map.MappingJsonFactory;
+import org.restlet.resource.Post;
+import org.restlet.resource.Get;
+import org.restlet.resource.ServerResource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class FirewallResource extends ServerResource {
+ protected static Logger log = LoggerFactory.getLogger(FirewallResource.class);
+
+ @Get("json")
+ public Object handleRequest() {
+ IFirewallService firewall =
+ (IFirewallService)getContext().getAttributes().
+ get(IFirewallService.class.getCanonicalName());
+
+ String op = (String) getRequestAttributes().get("op");
+
+ // REST API check status
+ if (op.equalsIgnoreCase("status")) {
+ if (firewall.isEnabled())
+ return "{\"result\" : \"firewall enabled\"}";
+ else
+ return "{\"result\" : \"firewall disabled\"}";
+ }
+
+ // REST API enable firewall
+ if (op.equalsIgnoreCase("enable")) {
+ firewall.enableFirewall(true);
+ return "{\"status\" : \"success\", \"details\" : \"firewall running\"}";
+ }
+
+ // REST API disable firewall
+ if (op.equalsIgnoreCase("disable")) {
+ firewall.enableFirewall(false);
+ return "{\"status\" : \"success\", \"details\" : \"firewall stopped\"}";
+ }
+
+ // REST API retrieving rules from storage
+ // currently equivalent to /wm/firewall/rules/json
+ if (op.equalsIgnoreCase("storageRules")) {
+ return firewall.getStorageRules();
+ }
+
+ // REST API set local subnet mask -- this only makes sense for one subnet
+ // will remove later
+ if (op.equalsIgnoreCase("subnet-mask")) {
+ return firewall.getSubnetMask();
+ }
+
+ // no known options found
+ return "{\"status\" : \"failure\", \"details\" : \"invalid operation\"}";
+ }
+
+ /**
+ * Allows setting of subnet mask
+ * @param fmJson The Subnet Mask in JSON format.
+ * @return A string status message
+ */
+ @Post
+ public String handlePost(String fmJson) {
+ IFirewallService firewall =
+ (IFirewallService)getContext().getAttributes().
+ get(IFirewallService.class.getCanonicalName());
+
+ String newMask;
+ try {
+ newMask = jsonExtractSubnetMask(fmJson);
+ } catch (IOException e) {
+ log.error("Error parsing new subnet mask: " + fmJson, e);
+ e.printStackTrace();
+ return "{\"status\" : \"Error! Could not parse new subnet mask, see log for details.\"}";
+ }
+ firewall.setSubnetMask(newMask);
+ return ("{\"status\" : \"subnet mask set\"}");
+ }
+
+ /**
+ * Extracts subnet mask from a JSON string
+ * @param fmJson The JSON formatted string
+ * @return The subnet mask
+ * @throws IOException If there was an error parsing the JSON
+ */
+ public static String jsonExtractSubnetMask(String fmJson) throws IOException {
+ String subnet_mask = "";
+ 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 == "subnet-mask") {
+ subnet_mask = jp.getText();
+ break;
+ }
+ }
+
+ return subnet_mask;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/firewall/FirewallRule.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/firewall/FirewallRule.java
new file mode 100644
index 0000000..d9b2612
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/firewall/FirewallRule.java
@@ -0,0 +1,392 @@
+package net.floodlightcontroller.firewall;
+
+import org.openflow.protocol.OFMatch;
+
+import net.floodlightcontroller.packet.Ethernet;
+import net.floodlightcontroller.packet.IPacket;
+import net.floodlightcontroller.packet.IPv4;
+import net.floodlightcontroller.packet.TCP;
+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 int priority = 0;
+
+ public FirewallAction action;
+
+ public enum FirewallAction {
+ /*
+ * DENY: Deny rule
+ * ALLOW: Allow rule
+ */
+ DENY, ALLOW
+ }
+
+ 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.priority = 0;
+ this.action = FirewallAction.ALLOW;
+ this.ruleid = 0;
+ }
+
+ /**
+ * Generates a unique ID for the instance
+ *
+ * @return int representing the unique id
+ */
+ public int genID() {
+ int uid = this.hashCode();
+ if (uid < 0) {
+ uid = Math.abs(uid);
+ uid = uid * 15551;
+ }
+ return uid;
+ }
+
+ /**
+ * Comparison method for Collections.sort method
+ *
+ * @param rule
+ * the rule to compare with
+ * @return number representing the result of comparison 0 if equal negative
+ * if less than 'rule' greater than zero if greater priority rule
+ * than 'rule'
+ */
+ @Override
+ public int compareTo(FirewallRule rule) {
+ return this.priority - rule.priority;
+ }
+
+ /**
+ * Determines if this instance matches an existing rule instance
+ *
+ * @param r
+ * : the FirewallRule instance to compare with
+ * @return boolean: true if a match is found
+ **/
+ 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)) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Matches this rule to a given flow - incoming packet
+ *
+ * @param switchDpid
+ * the Id of the connected switch
+ * @param inPort
+ * 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
+ * module that is used by the Firewall module's matchWithRule
+ * method to derive wildcards 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) {
+ IPacket pkt = packet.getPayload();
+
+ // dl_type type
+ IPv4 pkt_ip = null;
+
+ // nw_proto types
+ TCP pkt_tcp = null;
+ UDP pkt_udp = null;
+
+ // tp_src and tp_dst (tp port numbers)
+ short pkt_tp_src = 0;
+ short pkt_tp_dst = 0;
+
+ // switchID matches?
+ if (wildcard_dpid == false && dpid != switchDpid)
+ return false;
+
+ // in_port matches?
+ if (wildcard_in_port == false && in_port != inPort)
+ return false;
+ if (action == FirewallRule.FirewallAction.DENY) {
+ wildcards.drop &= ~OFMatch.OFPFW_IN_PORT;
+ } else {
+ wildcards.allow &= ~OFMatch.OFPFW_IN_PORT;
+ }
+
+ // mac address (src and dst) match?
+ if (wildcard_dl_src == false
+ && dl_src != packet.getSourceMAC().toLong())
+ return false;
+ if (action == FirewallRule.FirewallAction.DENY) {
+ wildcards.drop &= ~OFMatch.OFPFW_DL_SRC;
+ } else {
+ wildcards.allow &= ~OFMatch.OFPFW_DL_SRC;
+ }
+
+ if (wildcard_dl_dst == false
+ && dl_dst != packet.getDestinationMAC().toLong())
+ return false;
+ if (action == FirewallRule.FirewallAction.DENY) {
+ wildcards.drop &= ~OFMatch.OFPFW_DL_DST;
+ } else {
+ wildcards.allow &= ~OFMatch.OFPFW_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)
+ return false;
+ else {
+ if (action == FirewallRule.FirewallAction.DENY) {
+ wildcards.drop &= ~OFMatch.OFPFW_DL_TYPE;
+ } else {
+ wildcards.allow &= ~OFMatch.OFPFW_DL_TYPE;
+ }
+ }
+ } else if (dl_type == Ethernet.TYPE_IPv4) {
+ if (packet.getEtherType() != Ethernet.TYPE_IPv4)
+ return false;
+ else {
+ if (action == FirewallRule.FirewallAction.DENY) {
+ wildcards.drop &= ~OFMatch.OFPFW_NW_PROTO;
+ } else {
+ wildcards.allow &= ~OFMatch.OFPFW_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)
+ return false;
+ if (action == FirewallRule.FirewallAction.DENY) {
+ wildcards.drop &= ~OFMatch.OFPFW_NW_SRC_ALL;
+ wildcards.drop |= (nw_src_maskbits << OFMatch.OFPFW_NW_SRC_SHIFT);
+ } else {
+ wildcards.allow &= ~OFMatch.OFPFW_NW_SRC_ALL;
+ wildcards.allow |= (nw_src_maskbits << OFMatch.OFPFW_NW_SRC_SHIFT);
+ }
+
+ if (wildcard_nw_dst == false
+ && this.matchIPAddress(nw_dst_prefix,
+ nw_dst_maskbits,
+ 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);
+ } else {
+ wildcards.allow &= ~OFMatch.OFPFW_NW_DST_ALL;
+ wildcards.allow |= (nw_dst_maskbits << OFMatch.OFPFW_NW_DST_SHIFT);
+ }
+
+ // nw_proto check
+ if (wildcard_nw_proto == false) {
+ if (nw_proto == IPv4.PROTOCOL_TCP) {
+ if (pkt_ip.getProtocol() != IPv4.PROTOCOL_TCP)
+ return false;
+ 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)
+ return false;
+ 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)
+ return false;
+ else {
+ // nothing more needed for ICMP
+ }
+ }
+ if (action == FirewallRule.FirewallAction.DENY) {
+ wildcards.drop &= ~OFMatch.OFPFW_NW_PROTO;
+ } else {
+ wildcards.allow &= ~OFMatch.OFPFW_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)
+ return false;
+ if (action == FirewallRule.FirewallAction.DENY) {
+ wildcards.drop &= ~OFMatch.OFPFW_TP_SRC;
+ } else {
+ wildcards.allow &= ~OFMatch.OFPFW_TP_SRC;
+ }
+
+ // does the destination port match?
+ if (tp_dst != 0 && tp_dst != pkt_tp_dst)
+ return false;
+ if (action == FirewallRule.FirewallAction.DENY) {
+ wildcards.drop &= ~OFMatch.OFPFW_TP_DST;
+ } else {
+ wildcards.allow &= ~OFMatch.OFPFW_TP_DST;
+ }
+ }
+ }
+
+ }
+ } else {
+ // non-IP packet - not supported - report no match
+ return false;
+ }
+ }
+ if (action == FirewallRule.FirewallAction.DENY) {
+ wildcards.drop &= ~OFMatch.OFPFW_DL_TYPE;
+ } else {
+ wildcards.allow &= ~OFMatch.OFPFW_DL_TYPE;
+ }
+
+ // all applicable checks passed
+ return true;
+ }
+
+ /**
+ * Determines if rule's CIDR address matches IP address of the packet
+ *
+ * @param rulePrefix
+ * prefix part of the CIDR address
+ * @param ruleBits
+ * the size of mask of the CIDR address
+ * @param packetAddress
+ * the IP address of the incoming packet to match with
+ * @return true if CIDR address matches the packet's IP address, false
+ * otherwise
+ */
+ protected boolean matchIPAddress(int rulePrefix, int ruleBits,
+ int packetAddress) {
+ boolean matched = true;
+
+ int rule_iprng = 32 - ruleBits;
+ int rule_ipint = rulePrefix;
+ int pkt_ipint = packetAddress;
+ // 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
+ // wildcarded
+ rule_ipint = rule_ipint >> rule_iprng;
+ pkt_ipint = pkt_ipint >> rule_iprng;
+ // now left shift to return to normal range, except that the
+ // rule_iprng number of LSB
+ // are now zeroed
+ rule_ipint = rule_ipint << rule_iprng;
+ pkt_ipint = pkt_ipint << rule_iprng;
+ }
+ // check if we have a match
+ if (rule_ipint != pkt_ipint)
+ matched = false;
+
+ return matched;
+ }
+
+ @Override
+ 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 + 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();
+ return result;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/firewall/FirewallRulesResource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/firewall/FirewallRulesResource.java
new file mode 100644
index 0000000..7a31d38
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/firewall/FirewallRulesResource.java
@@ -0,0 +1,292 @@
+package net.floodlightcontroller.firewall;
+
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.List;
+
+import org.codehaus.jackson.JsonParseException;
+import org.codehaus.jackson.JsonParser;
+import org.codehaus.jackson.JsonToken;
+import org.codehaus.jackson.map.MappingJsonFactory;
+import org.openflow.util.HexString;
+import org.restlet.resource.Delete;
+import org.restlet.resource.Post;
+import org.restlet.resource.Get;
+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 {
+ protected static Logger log = LoggerFactory.getLogger(FirewallRulesResource.class);
+
+ @Get("json")
+ public Object handleRequest() {
+ IFirewallService firewall =
+ (IFirewallService)getContext().getAttributes().
+ get(IFirewallService.class.getCanonicalName());
+
+ return firewall.getRules();
+ }
+
+ /**
+ * Takes a Firewall Rule string in JSON format and parses it into
+ * our firewall rule data structure, then adds it to the firewall.
+ * @param fmJson The Firewall rule entry in JSON format.
+ * @return A string status message
+ */
+ @Post
+ public String store(String fmJson) {
+ IFirewallService firewall =
+ (IFirewallService)getContext().getAttributes().
+ get(IFirewallService.class.getCanonicalName());
+
+ FirewallRule rule;
+ try {
+ rule = jsonToFirewallRule(fmJson);
+ } catch (IOException e) {
+ log.error("Error parsing firewall rule: " + fmJson, e);
+ e.printStackTrace();
+ return "{\"status\" : \"Error! Could not parse firewall rule, see log for details.\"}";
+ }
+ String status = null;
+ if (checkRuleExists(rule, firewall.getRules())) {
+ status = "Error! A similar firewall rule already exists.";
+ log.error(status);
+ } else {
+ // add rule to firewall
+ firewall.addRule(rule);
+ status = "Rule added";
+ }
+ return ("{\"status\" : \"" + status + "\"}");
+ }
+
+ /**
+ * Takes a Firewall Rule string in JSON format and parses it into
+ * our firewall rule data structure, then deletes it from the firewall.
+ * @param fmJson The Firewall rule entry in JSON format.
+ * @return A string status message
+ */
+
+ @Delete
+ public String remove(String fmJson) {
+ IFirewallService firewall =
+ (IFirewallService)getContext().getAttributes().
+ get(IFirewallService.class.getCanonicalName());
+
+ FirewallRule rule;
+ try {
+ rule = jsonToFirewallRule(fmJson);
+ } catch (IOException e) {
+ log.error("Error parsing firewall rule: " + fmJson, e);
+ e.printStackTrace();
+ return "{\"status\" : \"Error! Could not parse firewall rule, see log for details.\"}";
+ }
+ String status = null;
+ boolean exists = false;
+ Iterator<FirewallRule> iter = firewall.getRules().iterator();
+ while (iter.hasNext()) {
+ FirewallRule r = iter.next();
+ if (r.ruleid == rule.ruleid) {
+ exists = true;
+ break;
+ }
+ }
+ if (!exists) {
+ status = "Error! Can't delete, a rule with this ID doesn't exist.";
+ log.error(status);
+ } else {
+ // delete rule from firewall
+ firewall.deleteRule(rule.ruleid);
+ status = "Rule deleted";
+ }
+ return ("{\"status\" : \"" + status + "\"}");
+ }
+
+ /**
+ * Turns a JSON formatted Firewall Rule string into a FirewallRule instance
+ * @param fmJson The JSON formatted static firewall rule
+ * @return The FirewallRule instance
+ * @throws IOException If there was an error parsing the JSON
+ */
+
+ public static FirewallRule jsonToFirewallRule(String fmJson) throws IOException {
+ FirewallRule rule = new FirewallRule();
+ 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;
+
+ String tmp;
+
+ // This is currently only applicable for remove(). In store(), ruleid takes a random number
+ if (n == "ruleid") {
+ rule.ruleid = Integer.parseInt((String)jp.getText());
+ }
+
+ // This assumes user having dpid info for involved switches
+ else if (n == "switchid") {
+ tmp = jp.getText();
+ if (tmp.equalsIgnoreCase("-1") == false) {
+ // user inputs hex format dpid
+ rule.dpid = HexString.toLong(tmp);
+ rule.wildcard_dpid = false;
+ }
+ }
+
+ else if (n == "src-inport") {
+ rule.in_port = Short.parseShort(jp.getText());
+ rule.wildcard_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));
+ }
+ }
+
+ 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));
+ }
+ }
+
+ else if (n == "dl-type") {
+ tmp = jp.getText();
+ if (tmp.equalsIgnoreCase("ARP")) {
+ rule.wildcard_dl_type = false;
+ rule.dl_type = Ethernet.TYPE_ARP;
+ }
+ }
+
+ 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];
+ }
+ }
+
+ 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];
+ }
+ }
+
+ 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;
+ } 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;
+ } 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;
+ }
+ }
+
+ else if (n == "tp-src") {
+ rule.wildcard_tp_src = false;
+ rule.tp_src = Short.parseShort(jp.getText());
+ }
+
+ else if (n == "tp-dst") {
+ rule.wildcard_tp_dst = false;
+ rule.tp_dst = Short.parseShort(jp.getText());
+ }
+
+ else if (n == "priority") {
+ rule.priority = Integer.parseInt(jp.getText());
+ }
+
+ else if (n == "action") {
+ if (jp.getText().equalsIgnoreCase("allow") == true) {
+ rule.action = FirewallRule.FirewallAction.ALLOW;
+ } else if (jp.getText().equalsIgnoreCase("deny") == true) {
+ rule.action = FirewallRule.FirewallAction.DENY;
+ }
+ }
+ }
+
+ return rule;
+ }
+
+ public static int[] IPCIDRToPrefixBits(String cidr) {
+ int ret[] = new int[2];
+
+ // as IP can also be a prefix rather than an absolute address
+ // split it over "/" to get the bit range
+ String[] parts = cidr.split("/");
+ String cidr_prefix = parts[0].trim();
+ int cidr_bits = 0;
+ if (parts.length == 2) {
+ try {
+ cidr_bits = Integer.parseInt(parts[1].trim());
+ } catch (Exception exp) {
+ cidr_bits = 32;
+ }
+ }
+ ret[0] = IPv4.toIPv4Address(cidr_prefix);
+ ret[1] = cidr_bits;
+
+ return ret;
+ }
+
+ public static boolean checkRuleExists(FirewallRule rule, List<FirewallRule> rules) {
+ Iterator<FirewallRule> iter = rules.iterator();
+ while (iter.hasNext()) {
+ FirewallRule r = iter.next();
+
+ // check if we find a similar rule
+ if (rule.isSameAs(r)) {
+ return true;
+ }
+ }
+
+ // no rule matched, so it doesn't exist in the rules
+ return false;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/firewall/FirewallWebRoutable.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/firewall/FirewallWebRoutable.java
new file mode 100644
index 0000000..3a9beab
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/firewall/FirewallWebRoutable.java
@@ -0,0 +1,26 @@
+package net.floodlightcontroller.firewall;
+
+import net.floodlightcontroller.restserver.RestletRoutable;
+import org.restlet.Context;
+import org.restlet.routing.Router;
+
+public class FirewallWebRoutable implements RestletRoutable {
+ /**
+ * Create the Restlet router and bind to the proper resources.
+ */
+ @Override
+ public Router getRestlet(Context context) {
+ Router router = new Router(context);
+ router.attach("/module/{op}/json", FirewallResource.class);
+ router.attach("/rules/json", FirewallRulesResource.class);
+ return router;
+ }
+
+ /**
+ * Set the base path for the Firewall
+ */
+ @Override
+ public String basePath() {
+ return "/wm/firewall";
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/firewall/IFirewallService.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/firewall/IFirewallService.java
new file mode 100644
index 0000000..ae9d89f
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/firewall/IFirewallService.java
@@ -0,0 +1,56 @@
+package net.floodlightcontroller.firewall;
+
+import java.util.List;
+import java.util.Map;
+
+import net.floodlightcontroller.core.module.IFloodlightService;
+
+public interface IFirewallService extends IFloodlightService {
+
+ /**
+ * Enables/disables the firewall.
+ * @param enable Whether to enable or disable the firewall.
+ */
+ public void enableFirewall(boolean enable);
+
+ /**
+ * Returns operational status of the firewall
+ * @return boolean enabled;
+ */
+ public boolean isEnabled();
+
+ /**
+ * Returns all of the firewall rules
+ * @return List of all rules
+ */
+ public List<FirewallRule> getRules();
+
+ /**
+ * Returns the subnet mask
+ * @return subnet mask
+ */
+ public String getSubnetMask();
+
+ /**
+ * Sets the subnet mask
+ * @param newMask The new subnet mask
+ */
+ public void setSubnetMask(String newMask);
+
+ /**
+ * Returns all of the firewall rules in storage
+ * for debugging and unit-testing purposes
+ * @return List of all rules in storage
+ */
+ public List<Map<String, Object>> getStorageRules();
+
+ /**
+ * Adds a new Firewall rule
+ */
+ public void addRule(FirewallRule rule);
+
+ /**
+ * Deletes a Firewall rule
+ */
+ public void deleteRule(int ruleid);
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/firewall/RuleWildcardsPair.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/firewall/RuleWildcardsPair.java
new file mode 100644
index 0000000..3fab409
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/firewall/RuleWildcardsPair.java
@@ -0,0 +1,8 @@
+package net.floodlightcontroller.firewall;
+
+import org.openflow.protocol.OFMatch;
+
+public class RuleWildcardsPair {
+ public FirewallRule rule;
+ public int wildcards = OFMatch.OFPFW_ALL;
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/firewall/WildcardsPair.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/firewall/WildcardsPair.java
new file mode 100644
index 0000000..2e5f123
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/firewall/WildcardsPair.java
@@ -0,0 +1,8 @@
+package net.floodlightcontroller.firewall;
+
+import org.openflow.protocol.OFMatch;
+
+public class WildcardsPair {
+ public int allow = OFMatch.OFPFW_ALL;
+ public int drop = OFMatch.OFPFW_ALL;
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/flowcache/FCQueryObj.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/flowcache/FCQueryObj.java
new file mode 100644
index 0000000..cce3401
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/flowcache/FCQueryObj.java
@@ -0,0 +1,117 @@
+package net.floodlightcontroller.flowcache;
+
+import java.util.Arrays;
+
+import net.floodlightcontroller.devicemanager.IDevice;
+import net.floodlightcontroller.flowcache.IFlowCacheService.FCQueryEvType;
+
+
+/**
+ * The Class FCQueryObj.
+ */
+public class FCQueryObj {
+
+ /** The caller of the flow cache query. */
+ public IFlowQueryHandler fcQueryHandler;
+ /** The application instance name. */
+ public String applInstName;
+ /** The vlan Id. */
+ public Short[] vlans;
+ /** The destination device. */
+ public IDevice dstDevice;
+ /** The source device. */
+ public IDevice srcDevice;
+ /** The caller name */
+ public String callerName;
+ /** Event type that triggered this flow query submission */
+ public FCQueryEvType evType;
+ /** The caller opaque data. Returned unchanged in the query response
+ * via the callback. The type of this object could be different for
+ * different callers */
+ public Object callerOpaqueObj;
+
+ /**
+ * Instantiates a new flow cache query object
+ */
+ public FCQueryObj(IFlowQueryHandler fcQueryHandler,
+ String applInstName,
+ Short vlan,
+ IDevice srcDevice,
+ IDevice dstDevice,
+ String callerName,
+ FCQueryEvType evType,
+ Object callerOpaqueObj) {
+ this.fcQueryHandler = fcQueryHandler;
+ this.applInstName = applInstName;
+ this.srcDevice = srcDevice;
+ this.dstDevice = dstDevice;
+ this.callerName = callerName;
+ this.evType = evType;
+ this.callerOpaqueObj = callerOpaqueObj;
+
+ if (vlan != null) {
+ this.vlans = new Short[] { vlan };
+ } else {
+ if (srcDevice != null) {
+ this.vlans = srcDevice.getVlanId();
+ } else if (dstDevice != null) {
+ this.vlans = dstDevice.getVlanId();
+ }
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "FCQueryObj [fcQueryCaller=" + fcQueryHandler
+ + ", applInstName="
+ + applInstName + ", vlans=" + Arrays.toString(vlans)
+ + ", dstDevice=" + dstDevice + ", srcDevice="
+ + srcDevice + ", callerName=" + callerName + ", evType="
+ + evType + ", callerOpaqueObj=" + callerOpaqueObj + "]";
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ FCQueryObj other = (FCQueryObj) obj;
+ if (applInstName == null) {
+ if (other.applInstName != null)
+ return false;
+ } else if (!applInstName.equals(other.applInstName))
+ return false;
+ if (callerName == null) {
+ if (other.callerName != null)
+ return false;
+ } else if (!callerName.equals(other.callerName))
+ return false;
+ if (callerOpaqueObj == null) {
+ if (other.callerOpaqueObj != null)
+ return false;
+ } else if (!callerOpaqueObj.equals(other.callerOpaqueObj))
+ return false;
+ if (dstDevice == null) {
+ if (other.dstDevice != null)
+ return false;
+ } else if (!dstDevice.equals(other.dstDevice))
+ return false;
+ if (evType != other.evType)
+ return false;
+ if (fcQueryHandler != other.fcQueryHandler)
+ return false;
+ if (srcDevice == null) {
+ if (other.srcDevice != null)
+ return false;
+ } else if (!srcDevice.equals(other.srcDevice))
+ return false;
+ if (!Arrays.equals(vlans, other.vlans))
+ return false;
+ return true;
+ }
+
+
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/flowcache/FlowCacheQueryResp.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/flowcache/FlowCacheQueryResp.java
new file mode 100644
index 0000000..b01aedf
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/flowcache/FlowCacheQueryResp.java
@@ -0,0 +1,54 @@
+package net.floodlightcontroller.flowcache;
+
+import java.util.ArrayList;
+
+/**
+ * Object to return flows in response to a query message to BigFlowCache.
+ * This object is passed in the flowQueryRespHandler() callback.
+ */
+public class FlowCacheQueryResp {
+
+ /** query object provided by the caller, returned unchanged. */
+ public FCQueryObj queryObj;
+ /**
+ * Set to true if more flows could be returned for this query in
+ * additional callbacks. Set of false in the last callback for the
+ * query.
+ */
+ public boolean moreFlag;
+
+ /**
+ * Set to true if the response has been sent to handler
+ */
+ public boolean hasSent;
+
+ /**
+ * The flow list. If there are large number of flows to be returned
+ * then they may be returned in multiple callbacks.
+ */
+ public ArrayList<QRFlowCacheObj> qrFlowCacheObjList;
+
+ /**
+ * Instantiates a new big flow cache query response.
+ *
+ * @param query the flow cache query object as given by the caller of
+ * flow cache submit query API.
+ */
+ public FlowCacheQueryResp(FCQueryObj query) {
+ qrFlowCacheObjList = new ArrayList<QRFlowCacheObj>();
+ queryObj = query;
+ moreFlag = false;
+ hasSent = false;
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ String s = queryObj.toString() + "; moreFlasg=" + moreFlag +
+ "; hasSent=" + hasSent;
+ s += "; FlowCount=" + Integer.toString(qrFlowCacheObjList.size());
+ return s;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/flowcache/FlowReconcileManager.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/flowcache/FlowReconcileManager.java
new file mode 100644
index 0000000..d5d323d
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/flowcache/FlowReconcileManager.java
@@ -0,0 +1,440 @@
+package net.floodlightcontroller.flowcache;
+
+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.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+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.devicemanager.IDevice;
+import net.floodlightcontroller.flowcache.IFlowCacheService.FCQueryEvType;
+import net.floodlightcontroller.flowcache.IFlowReconcileListener;
+import net.floodlightcontroller.flowcache.OFMatchReconcile;
+import net.floodlightcontroller.threadpool.IThreadPoolService;
+
+import org.openflow.protocol.OFType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class FlowReconcileManager
+ implements IFloodlightModule, IFlowReconcileService {
+
+ /** The logger. */
+ private static Logger logger =
+ LoggerFactory.getLogger(FlowReconcileManager.class);
+
+ /** Reference to dependent modules */
+ protected IThreadPoolService threadPool;
+ protected ICounterStoreService counterStore;
+
+ /**
+ * 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;
+
+ /** A FIFO queue to keep all outstanding flows for reconciliation */
+ Queue<OFMatchReconcile> flowQueue;
+
+ /** Asynchronous task to feed the flowReconcile pipeline */
+ protected SingletonTask flowReconcileTask;
+
+ String controllerPktInCounterName;
+ protected SimpleCounter lastPacketInCounter;
+
+ protected static int MAX_SYSTEM_LOAD_PER_SECOND = 50000;
+ /** a minimum flow reconcile rate so that it won't stave */
+ protected static int MIN_FLOW_RECONCILE_PER_SECOND = 1000;
+
+ /** once per second */
+ protected static int FLOW_RECONCILE_DELAY_MILLISEC = 10;
+ protected Date lastReconcileTime;
+
+ /** Config to enable or disable flowReconcile */
+ protected static final String EnableConfigKey = "enable";
+ protected boolean flowReconcileEnabled;
+
+ public int flowReconcileThreadRunCount;
+
+ @Override
+ public synchronized void addFlowReconcileListener(
+ IFlowReconcileListener listener) {
+ flowReconcileListeners.addListener(OFType.FLOW_MOD, listener);
+
+ if (logger.isTraceEnabled()) {
+ StringBuffer sb = new StringBuffer();
+ sb.append("FlowMod listeners: ");
+ for (IFlowReconcileListener l :
+ flowReconcileListeners.getOrderedListeners()) {
+ sb.append(l.getName());
+ sb.append(",");
+ }
+ logger.trace(sb.toString());
+ }
+ }
+
+ @Override
+ public synchronized void removeFlowReconcileListener(
+ IFlowReconcileListener listener) {
+ flowReconcileListeners.removeListener(listener);
+ }
+
+ @Override
+ public synchronized void clearFlowReconcileListeners() {
+ flowReconcileListeners.clearListeners();
+ }
+
+ /**
+ * Add to-be-reconciled flow to the queue.
+ *
+ * @param ofmRcIn the ofm rc in
+ */
+ public void reconcileFlow(OFMatchReconcile ofmRcIn) {
+ if (ofmRcIn == null) return;
+
+ // Make a copy before putting on the queue.
+ OFMatchReconcile myOfmRc = new OFMatchReconcile(ofmRcIn);
+
+ flowQueue.add(myOfmRc);
+
+ Date currTime = new Date();
+ long delay = 0;
+
+ /** schedule reconcile task immidiately if it has been more than 1 sec
+ * since the last run. Otherwise, schedule the reconcile task in
+ * DELAY_MILLISEC.
+ */
+ if (currTime.after(new Date(lastReconcileTime.getTime() + 1000))) {
+ delay = 0;
+ } else {
+ delay = FLOW_RECONCILE_DELAY_MILLISEC;
+ }
+ flowReconcileTask.reschedule(delay, TimeUnit.MILLISECONDS);
+
+ if (logger.isTraceEnabled()) {
+ logger.trace("Reconciling flow: {}, total: {}",
+ myOfmRc.toString(), flowQueue.size());
+ }
+ }
+
+ @Override
+ public void updateFlowForDestinationDevice(IDevice device,
+ IFlowQueryHandler handler,
+ FCQueryEvType fcEvType) {
+ // NO-OP
+ }
+
+ @Override
+ public void updateFlowForSourceDevice(IDevice device,
+ IFlowQueryHandler handler,
+ FCQueryEvType fcEvType) {
+ // NO-OP
+ }
+
+ @Override
+ public void flowQueryGenericHandler(FlowCacheQueryResp flowResp) {
+ if (flowResp.queryObj.evType != FCQueryEvType.GET) {
+ OFMatchReconcile ofmRc = new OFMatchReconcile();;
+ /* Re-provision these flows */
+ for (QRFlowCacheObj entry : flowResp.qrFlowCacheObjList) {
+ /* reconcile the flows in entry */
+ entry.toOFMatchReconcile(ofmRc,
+ flowResp.queryObj.applInstName,
+ OFMatchReconcile.ReconcileAction.UPDATE_PATH);
+ reconcileFlow(ofmRc);
+ }
+ }
+ return;
+ }
+
+ // IFloodlightModule
+
+ @Override
+ public Collection<Class<? extends IFloodlightService>> getModuleServices() {
+ Collection<Class<? extends IFloodlightService>> l =
+ new ArrayList<Class<? extends IFloodlightService>>();
+ l.add(IFlowReconcileService.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(IFlowReconcileService.class, this);
+ return m;
+ }
+
+ @Override
+ public Collection<Class<? extends IFloodlightService>>
+ getModuleDependencies() {
+ Collection<Class<? extends IFloodlightService>> l =
+ new ArrayList<Class<? extends IFloodlightService>>();
+ l.add(IThreadPoolService.class);
+ l.add(ICounterStoreService.class);
+ return null;
+ }
+
+ @Override
+ public void init(FloodlightModuleContext context)
+ throws FloodlightModuleException {
+ threadPool = context.getServiceImpl(IThreadPoolService.class);
+ counterStore = context.getServiceImpl(ICounterStoreService.class);
+
+ flowQueue = new ConcurrentLinkedQueue<OFMatchReconcile>();
+ flowReconcileListeners =
+ new ListenerDispatcher<OFType, IFlowReconcileListener>();
+
+ Map<String, String> configParam = context.getConfigParams(this);
+ String enableValue = configParam.get(EnableConfigKey);
+ // Set flowReconcile default to true
+ flowReconcileEnabled = true;
+ if (enableValue != null &&
+ enableValue.equalsIgnoreCase("false")) {
+ flowReconcileEnabled = false;
+ }
+
+ flowReconcileThreadRunCount = 0;
+ lastReconcileTime = new Date(0);
+ logger.debug("FlowReconcile is {}", flowReconcileEnabled);
+ }
+
+ @Override
+ public void startUp(FloodlightModuleContext context) {
+ // thread to do flow reconcile
+ ScheduledExecutorService ses = threadPool.getScheduledExecutor();
+ flowReconcileTask = new SingletonTask(ses, new Runnable() {
+ @Override
+ public void run() {
+ try {
+ if (doReconcile()) {
+ flowReconcileTask.reschedule(
+ FLOW_RECONCILE_DELAY_MILLISEC,
+ TimeUnit.MILLISECONDS);
+ }
+ } catch (Exception e) {
+ logger.warn("Exception in doReconcile(): {}",
+ e.getMessage());
+ e.printStackTrace();
+ }
+ }
+ });
+
+ String packetInName = OFType.PACKET_IN.toClass().getName();
+ packetInName = packetInName.substring(packetInName.lastIndexOf('.')+1);
+
+ // Construct controller counter for the packet_in
+ controllerPktInCounterName =
+ CounterStore.createCounterName(ICounterStoreService.CONTROLLER_NAME,
+ -1,
+ packetInName);
+ }
+
+ /**
+ * Feed the flows into the flow reconciliation pipeline.
+ * @return true if more flows to be reconciled
+ * false if no more flows to be reconciled.
+ */
+ protected boolean doReconcile() {
+ if (!flowReconcileEnabled) {
+ return false;
+ }
+
+ // Record the execution time.
+ lastReconcileTime = new Date();
+
+ ArrayList<OFMatchReconcile> ofmRcList =
+ new ArrayList<OFMatchReconcile>();
+
+ // Get the maximum number of flows that can be reconciled.
+ int reconcileCapacity = getCurrentCapacity();
+ if (logger.isTraceEnabled()) {
+ logger.trace("Reconcile capacity {} flows", reconcileCapacity);
+ }
+ while (!flowQueue.isEmpty() && reconcileCapacity > 0) {
+ OFMatchReconcile ofmRc = flowQueue.poll();
+ reconcileCapacity--;
+ if (ofmRc != null) {
+ ofmRcList.add(ofmRc);
+ if (logger.isTraceEnabled()) {
+ logger.trace("Add flow {} to be the reconcileList", ofmRc.cookie);
+ }
+ } else {
+ break;
+ }
+ }
+
+ // Run the flow through all the flow reconcile listeners
+ IFlowReconcileListener.Command retCmd;
+ if (ofmRcList.size() > 0) {
+ List<IFlowReconcileListener> listeners =
+ flowReconcileListeners.getOrderedListeners();
+ if (listeners == null) {
+ if (logger.isTraceEnabled()) {
+ logger.trace("No flowReconcile listener");
+ }
+ return false;
+ }
+
+ for (IFlowReconcileListener flowReconciler :
+ flowReconcileListeners.getOrderedListeners()) {
+ if (logger.isTraceEnabled()) {
+ logger.trace("Reconciling flow: call listener {}",
+ flowReconciler.getName());
+ }
+ retCmd = flowReconciler.reconcileFlows(ofmRcList);
+ if (retCmd == IFlowReconcileListener.Command.STOP) {
+ break;
+ }
+ }
+ flowReconcileThreadRunCount++;
+ } else {
+ if (logger.isTraceEnabled()) {
+ logger.trace("No flow to be reconciled.");
+ }
+ }
+
+ // Return true if there are more flows to be reconciled
+ if (flowQueue.isEmpty()) {
+ return false;
+ } else {
+ if (logger.isTraceEnabled()) {
+ logger.trace("{} more flows to be reconciled.",
+ flowQueue.size());
+ }
+ return true;
+ }
+ }
+
+ /**
+ * Compute the maximum number of flows to be reconciled.
+ *
+ * It computes the packetIn increment from the counter values in
+ * the counter store;
+ * Then computes the rate based on the elapsed time
+ * from the last query;
+ * Then compute the max flow reconcile rate by subtracting the packetIn
+ * rate from the hard-coded max system rate.
+ * If the system rate is reached or less than MIN_FLOW_RECONCILE_PER_SECOND,
+ * set the maximum flow reconcile rate to the MIN_FLOW_RECONCILE_PER_SECOND
+ * to prevent starvation.
+ * Then convert the rate to an absolute number for the
+ * FLOW_RECONCILE_PERIOD.
+ * @return
+ */
+ protected int getCurrentCapacity() {
+ ICounter pktInCounter =
+ counterStore.getCounter(controllerPktInCounterName);
+ int minFlows = MIN_FLOW_RECONCILE_PER_SECOND *
+ FLOW_RECONCILE_DELAY_MILLISEC / 1000;
+
+ // 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);
+ 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);
+ return minFlows;
+ }
+
+ int pktInRate = getPktInRate(pktInCounter, new Date());
+
+ // Update the last packetInCounter
+ lastPacketInCounter = (SimpleCounter)
+ SimpleCounter.createCounter(pktInCounter);
+ int capacity = minFlows;
+ if ((pktInRate + MIN_FLOW_RECONCILE_PER_SECOND) <=
+ MAX_SYSTEM_LOAD_PER_SECOND) {
+ capacity = (MAX_SYSTEM_LOAD_PER_SECOND - pktInRate)
+ * FLOW_RECONCILE_DELAY_MILLISEC / 1000;
+ }
+
+ if (logger.isTraceEnabled()) {
+ logger.trace("Capacity is {}", capacity);
+ }
+ return capacity;
+ }
+
+ protected int getPktInRate(ICounter newCnt, Date currentTime) {
+ if (newCnt == null ||
+ newCnt.getCounterDate() == null ||
+ newCnt.getCounterValue() == null) {
+ return 0;
+ }
+
+ // Somehow the system time is messed up. return max packetIn rate
+ // to reduce the system load.
+ if (newCnt.getCounterDate().before(
+ lastPacketInCounter.getCounterDate())) {
+ logger.debug("Time is going backward. new {}, old {}",
+ newCnt.getCounterDate(),
+ lastPacketInCounter.getCounterDate());
+ return MAX_SYSTEM_LOAD_PER_SECOND;
+ }
+
+ long elapsedTimeInSecond = (currentTime.getTime() -
+ lastPacketInCounter.getCounterDate().getTime()) / 1000;
+ if (elapsedTimeInSecond == 0) {
+ // This should never happen. Check to avoid division by zero.
+ return 0;
+ }
+
+ long diff = 0;
+ switch (newCnt.getCounterValue().getType()) {
+ case LONG:
+ long newLong = newCnt.getCounterValue().getLong();
+ long oldLong = lastPacketInCounter.getCounterValue().getLong();
+ if (newLong < oldLong) {
+ // Roll over event
+ diff = Long.MAX_VALUE - oldLong + newLong;
+ } else {
+ diff = newLong - oldLong;
+ }
+ break;
+
+ case DOUBLE:
+ double newDouble = newCnt.getCounterValue().getDouble();
+ double oldDouble = lastPacketInCounter.getCounterValue().getDouble();
+ if (newDouble < oldDouble) {
+ // Roll over event
+ diff = (long)(Double.MAX_VALUE - oldDouble + newDouble);
+ } else {
+ diff = (long)(newDouble - oldDouble);
+ }
+ break;
+ }
+
+ return (int)(diff/elapsedTimeInSecond);
+ }
+}
+
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/flowcache/IFlowCacheService.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/flowcache/IFlowCacheService.java
new file mode 100644
index 0000000..8e44ed3
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/flowcache/IFlowCacheService.java
@@ -0,0 +1,185 @@
+package net.floodlightcontroller.flowcache;
+
+import org.openflow.protocol.OFMatchWithSwDpid;
+
+import net.floodlightcontroller.core.FloodlightContext;
+import net.floodlightcontroller.core.FloodlightContextStore;
+import net.floodlightcontroller.core.IOFSwitch;
+import net.floodlightcontroller.devicemanager.SwitchPort;
+import net.floodlightcontroller.core.module.IFloodlightService;
+
+/**
+ * The Interface IFlowCache.
+ * <p>
+ * public interface APIs to Big Switch Flow-Cache Service. Flow-Cache maintains
+ * the network-level flows that are currently deployed in the underlying
+ * network. The flow cache can be queried using various filters by using the
+ * corresponding APIs.
+ *
+ * @author subrata
+ *
+ */
+public interface IFlowCacheService extends IFloodlightService {
+
+ public static final String FLOWCACHE_APP_NAME =
+ "net.floodlightcontroller.flowcache.appName";
+ public static final String FLOWCACHE_APP_INSTANCE_NAME =
+ "net.floodlightcontroller.flowcache.appInstanceName";
+
+ /**
+ * The flow cache query event type indicating the event that triggered the
+ * query. The callerOpaqueObj can be keyed based on this event type
+ */
+ public static enum FCQueryEvType {
+ /** The GET query. Flows need not be reconciled for this query type */
+ GET,
+ /** A new App was added. */
+ APP_ADDED,
+ /** An App was deleted. */
+ APP_DELETED,
+ /** Interface rule of an app was modified */
+ APP_INTERFACE_RULE_CHANGED,
+ /** Some App configuration was changed */
+ APP_CONFIG_CHANGED,
+ /** An ACL was added */
+ ACL_ADDED,
+ /** An ACL was deleted */
+ ACL_DELETED,
+ /** An ACL rule was added */
+ ACL_RULE_ADDED,
+ /** An ACL rule was deleted */
+ ACL_RULE_DELETED,
+ /** ACL configuration was changed */
+ ACL_CONFIG_CHANGED,
+ /** device had moved to a different port in the network */
+ DEVICE_MOVED,
+ /** device's property had changed, such as tag assignment */
+ DEVICE_PROPERTY_CHANGED,
+ /** Link down */
+ LINK_DOWN,
+ /** Periodic scan of switch flow table */
+ PERIODIC_SCAN,
+ }
+
+ /**
+ * A FloodlightContextStore object that can be used to interact with the
+ * FloodlightContext information about flowCache.
+ */
+ public static final FloodlightContextStore<String> fcStore =
+ new FloodlightContextStore<String>();
+
+ /**
+ * Submit a flow cache query with query parameters specified in FCQueryObj
+ * object. The query object can be created using one of the newFCQueryObj
+ * helper functions in IFlowCache interface.
+ * <p>
+ * The queried flows are returned via the flowQueryRespHandler() callback
+ * that the caller must implement. The caller can match the query with
+ * the response using unique callerOpaqueData which remains unchanged
+ * in the request and response callback.
+ *
+ * @see com.bigswitch.floodlight.flowcache#flowQueryRespHandler
+ * @param query the flow cache query object as input
+ *
+ */
+ public void submitFlowCacheQuery(FCQueryObj query);
+
+ /**
+ * Deactivates all flows in the flow cache for which the source switch
+ * matches the given switchDpid. Note that the flows are NOT deleted
+ * from the cache.
+ *
+ * @param switchDpid Data-path identifier of the source switch
+ */
+ public void deactivateFlowCacheBySwitch(long switchDpid);
+
+ /**
+ * Deletes all flows in the flow cache for which the source switch
+ * matches the given switchDpid.
+ *
+ * @param switchDpid Data-path identifier of the source switch
+ */
+ public void deleteFlowCacheBySwitch(long switchDpid);
+
+ /**
+ * Add a flow to the flow-cache - called when a flow-mod is about to be
+ * written to a set of switches. If it returns false then it should not
+ * be written to the switches. If it returns true then the cookie returned
+ * should be used for the flow mod sent to the switches.
+ *
+ * @param appInstName Application instance name
+ * @param ofm openflow match object
+ * @param cookie openflow-mod cookie
+ * @param swPort SwitchPort object
+ * @param priority openflow match priority
+ * @param action action taken on the matched packets (PERMIT or DENY)
+ * @return true: flow should be written to the switch(es)
+ * false: flow should not be written to the switch(es). false is
+ * returned, for example, when the flow was recently
+ * written to the flow-cache and hence it is dampened to
+ * avoid frequent writes of the same flow to the switches
+ * This case can typically arise for the flows written at the
+ * internal ports as they are heavily wild-carded.
+ */
+ public boolean addFlow(String appInstName, OFMatchWithSwDpid ofm,
+ Long cookie, long srcSwDpid,
+ short inPort, short priority, byte action);
+
+ /**
+ * Add a flow to the flow-cache - called when a flow-mod is about to be
+ * written to a set of switches. If it returns false then it should not
+ * be written to the switches. If it returns true then the cookie returned
+ * should be used for the flow mod sent to the switches.
+ *
+ * @param cntx the cntx
+ * @param ofm the ofm
+ * @param cookie the cookie
+ * @param swPort the sw port
+ * @param priority the priority
+ * @param action the action
+ * @return true: flow should be written to the switch(es)
+ * false: flow should not be written to the switch(es). false is
+ * returned, for example, when the flow was recently
+ * written to the flow-cache and hence it is dampened to
+ * avoid frequent writes of the same flow to the switches
+ * This case can typically arise for the flows written at the
+ * internal ports as they are heavily wild-carded.
+ */
+ public boolean addFlow(FloodlightContext cntx, OFMatchWithSwDpid ofm,
+ Long cookie, SwitchPort swPort,
+ short priority, byte action);
+
+ /**
+ * Move the specified flow from its current application instance to a
+ * different application instance. This API can be used when a flow moves
+ * to a different application instance when the application instance
+ * configuration changes or when a device moves to a different part in
+ * the network that belongs to a different application instance.
+ * <p>
+ * Note that, if the flow was not found in the current application
+ * instance then the flow is not moved to the new application instance.
+ *
+ * @param ofMRc the object containing the flow match and new application
+ * instance name.
+ * @return true is the flow was found in the flow cache in the current
+ * application instance; false if the flow was not found in the flow-cache
+ * in the current application instance.
+ */
+ public boolean moveFlowToDifferentApplInstName(OFMatchReconcile ofMRc);
+
+ /**
+ * Delete all flow from the specified switch
+ * @param sw
+ */
+ public void deleteAllFlowsAtASourceSwitch(IOFSwitch sw);
+
+ /**
+ * Post a request to update flowcache from a switch.
+ * This is an asynchronous operation.
+ * It queries the switch for stats and updates the flowcache asynchronously
+ * with the response.
+ * @param swDpid
+ * @param delay_ms
+ */
+ public void querySwitchFlowTable(long swDpid);
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/flowcache/IFlowQueryHandler.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/flowcache/IFlowQueryHandler.java
new file mode 100644
index 0000000..5d1b1a9
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/flowcache/IFlowQueryHandler.java
@@ -0,0 +1,21 @@
+package net.floodlightcontroller.flowcache;
+
+public interface IFlowQueryHandler {
+ /**
+ * This callback function is called in response to a flow query request
+ * submitted to the flow cache service. The module handling this callback
+ * can be different from the one that submitted the query. In the flow
+ * query object used for submitting the flow query, the identity of the
+ * callback handler is passed. When flow cache service has all or some
+ * of the flows that needs to be returned then this callback is called
+ * for the appropriate module. The respone contains a boolean more flag
+ * that indicates if there are additional flows that may be returned
+ * via additional callback calls.
+ *
+ * @param resp the response object containing the original flow query
+ * object, partial or complete list of flows that we queried and some
+ * metadata such as the more flag described aboce.
+ *
+ */
+ public void flowQueryRespHandler(FlowCacheQueryResp resp);
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/flowcache/IFlowReconcileListener.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/flowcache/IFlowReconcileListener.java
new file mode 100644
index 0000000..f1100ed
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/flowcache/IFlowReconcileListener.java
@@ -0,0 +1,40 @@
+package net.floodlightcontroller.flowcache;
+
+import java.util.ArrayList;
+
+import net.floodlightcontroller.core.IListener;
+import org.openflow.protocol.OFType;
+
+/**
+ * The Interface IFlowReconciler.
+ *
+ * @author subrata
+ */
+public interface IFlowReconcileListener extends IListener<OFType> {
+ /**
+ * Given an input OFMatch, this method applies the policy of the reconciler
+ * and returns a the same input OFMatch structure modified. Additional
+ * OFMatches, if needed, are returned in OFMatch-list. All the OFMatches
+ * are assumed to have "PERMIT" action.
+ *
+ * @param ofmRcList input flow matches, to be updated to be consistent with
+ * the policies of this reconciler
+ * Additional OFMatch-es can be added to the "list" as
+ * needed.
+ * For example after a new ACL application, one flow-match
+ * may result in multiple flow-matches
+ * The method must also update the ReconcileAction
+ * member in ofmRcList entries to indicate if the
+ * flow needs to be modified, deleted or left unchanged
+ * OR of a new entry is to be added after flow
+ * reconciliation
+ *
+ *
+ * @return Command.CONTINUE if the OFMatch should be sent to the
+ * next flow reconciler.
+ * Command.STOP if the OFMatch shouldn't be processed
+ * further. In this case the no reconciled flow-mods would
+ * be programmed
+ */
+ public Command reconcileFlows(ArrayList<OFMatchReconcile> ofmRcList);
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/flowcache/IFlowReconcileService.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/flowcache/IFlowReconcileService.java
new file mode 100644
index 0000000..f48c4e0
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/flowcache/IFlowReconcileService.java
@@ -0,0 +1,75 @@
+/**
+ * Provides Flow Reconcile service to other modules that need to reconcile
+ * flows.
+ */
+package net.floodlightcontroller.flowcache;
+
+import net.floodlightcontroller.core.module.IFloodlightService;
+import net.floodlightcontroller.devicemanager.IDevice;
+import net.floodlightcontroller.flowcache.IFlowCacheService.FCQueryEvType;
+
+public interface IFlowReconcileService extends IFloodlightService {
+ /**
+ * Add a flow reconcile listener
+ * @param listener The module that can reconcile flows
+ */
+ public void addFlowReconcileListener(IFlowReconcileListener listener);
+
+ /**
+ * Remove a flow reconcile listener
+ * @param listener The module that no longer reconcile flows
+ */
+ public void removeFlowReconcileListener(IFlowReconcileListener listener);
+
+ /**
+ * Remove all flow reconcile listeners
+ */
+ public void clearFlowReconcileListeners();
+
+ /**
+ * Reconcile flow. Returns false if no modified flow-mod need to be
+ * programmed if cluster ID is providced then pnly flows in the given
+ * cluster are reprogrammed
+ *
+ * @param ofmRcIn the ofm rc in
+ */
+ public void reconcileFlow(OFMatchReconcile ofmRcIn);
+
+ /**
+ * Updates the flows to a device after the device moved to a new location
+ * <p>
+ * Queries the flow-cache to get all the flows destined to the given device.
+ * Reconciles each of these flows by potentially reprogramming them to its
+ * new attachment point
+ *
+ * @param device device that has moved
+ * @param handler handler to process the flows
+ * @param fcEvType Event type that triggered the update
+ *
+ */
+ public void updateFlowForDestinationDevice(IDevice device,
+ IFlowQueryHandler handler,
+ FCQueryEvType fcEvType);
+
+ /**
+ * Updates the flows from a device
+ * <p>
+ * Queries the flow-cache to get all the flows source from the given device.
+ * Reconciles each of these flows by potentially reprogramming them to its
+ * new attachment point
+ *
+ * @param device device where the flow originates
+ * @param handler handler to process the flows
+ * @param fcEvType Event type that triggered the update
+ *
+ */
+ public void updateFlowForSourceDevice(IDevice device,
+ IFlowQueryHandler handler,
+ FCQueryEvType fcEvType);
+
+ /**
+ * Generic flow query handler to insert FlowMods into the reconcile pipeline.
+ * @param flowResp
+ */
+ public void flowQueryGenericHandler(FlowCacheQueryResp flowResp);
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/flowcache/OFMatchReconcile.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/flowcache/OFMatchReconcile.java
new file mode 100644
index 0000000..68831f4
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/flowcache/OFMatchReconcile.java
@@ -0,0 +1,84 @@
+package net.floodlightcontroller.flowcache;
+
+import net.floodlightcontroller.core.FloodlightContext;
+import org.openflow.protocol.OFMatchWithSwDpid;
+
+/**
+ * OFMatchReconcile class to indicate result of a flow-reconciliation.
+ */
+public class OFMatchReconcile {
+
+ /**
+ * The enum ReconcileAction. Specifies the result of reconciliation of a
+ * flow.
+ */
+ public enum ReconcileAction {
+
+ /** Delete the flow-mod from the switch */
+ DROP,
+ /** Leave the flow-mod as-is. */
+ NO_CHANGE,
+ /** Program this new flow mod. */
+ NEW_ENTRY,
+ /**
+ * Reprogram the flow mod as the path of the flow might have changed,
+ * for example when a host is moved or when a link goes down. */
+ UPDATE_PATH,
+ /* Flow is now in a different BVS */
+ APP_INSTANCE_CHANGED,
+ /* Delete the flow-mod - used to delete, for example, drop flow-mods
+ * when the source and destination are in the same BVS after a
+ * configuration change */
+ DELETE
+ }
+
+ /** The open flow match after reconciliation. */
+ public OFMatchWithSwDpid ofmWithSwDpid;
+ /** flow mod. priority */
+ public short priority;
+ /** Action of this flow-mod PERMIT or DENY */
+ public byte action;
+ /** flow mod. cookie */
+ public long cookie;
+ /** The application instance name. */
+ public String appInstName;
+ /**
+ * The new application instance name. This is null unless the flow
+ * has moved to a different BVS due to BVS config change or device
+ * move to a different switch port etc.*/
+ public String newAppInstName;
+ /** The reconcile action. */
+ public ReconcileAction rcAction;
+
+ // The context for the reconcile action
+ public FloodlightContext cntx;
+
+ /**
+ * Instantiates a new oF match reconcile object.
+ */
+ public OFMatchReconcile() {
+ ofmWithSwDpid = new OFMatchWithSwDpid();
+ rcAction = ReconcileAction.NO_CHANGE;
+ cntx = new FloodlightContext();
+ }
+
+ public OFMatchReconcile(OFMatchReconcile copy) {
+ ofmWithSwDpid =
+ new OFMatchWithSwDpid(copy.ofmWithSwDpid.getOfMatch(),
+ copy.ofmWithSwDpid.getSwitchDataPathId());
+ priority = copy.priority;
+ action = copy.action;
+ cookie = copy.cookie;
+ appInstName = copy.appInstName;
+ newAppInstName = copy.newAppInstName;
+ rcAction = copy.rcAction;
+ cntx = new FloodlightContext();
+ }
+
+ @Override
+ public String toString() {
+ return "OFMatchReconcile [" + ofmWithSwDpid + " priority=" + priority + " action=" + action +
+ " cookie=" + cookie + " appInstName=" + appInstName + " newAppInstName=" + newAppInstName +
+ " ReconcileAction=" + rcAction + "]";
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/flowcache/PendingSwRespKey.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/flowcache/PendingSwRespKey.java
new file mode 100644
index 0000000..767ce94
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/flowcache/PendingSwRespKey.java
@@ -0,0 +1,42 @@
+package net.floodlightcontroller.flowcache;
+
+public class PendingSwRespKey {
+ long swDpid;
+ int transId;
+
+ public PendingSwRespKey(long swDpid, int transId) {
+ this.swDpid = swDpid;
+ this.transId = transId;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 97;
+ Long dpid = swDpid;
+ Integer tid = transId;
+ return (tid.hashCode()*prime + dpid.hashCode());
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (!(obj instanceof PendingSwRespKey)) {
+ return false;
+ }
+ PendingSwRespKey other = (PendingSwRespKey) obj;
+ if ((swDpid != other.swDpid) || (transId != other.transId)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return Long.toHexString(swDpid)+","+Integer.toString(transId);
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/flowcache/PendingSwitchResp.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/flowcache/PendingSwitchResp.java
new file mode 100644
index 0000000..d6f264f
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/flowcache/PendingSwitchResp.java
@@ -0,0 +1,24 @@
+package net.floodlightcontroller.flowcache;
+
+import net.floodlightcontroller.flowcache.IFlowCacheService.FCQueryEvType;
+
+/**
+ * The Class PendingSwitchResp. This object is used to track the pending
+ * responses to switch flow table queries.
+ */
+public class PendingSwitchResp {
+ protected FCQueryEvType evType;
+
+ public PendingSwitchResp(
+ FCQueryEvType evType) {
+ this.evType = evType;
+ }
+
+ public FCQueryEvType getEvType() {
+ return evType;
+ }
+
+ public void setEvType(FCQueryEvType evType) {
+ this.evType = evType;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/flowcache/QRFlowCacheObj.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/flowcache/QRFlowCacheObj.java
new file mode 100644
index 0000000..5121f8b
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/flowcache/QRFlowCacheObj.java
@@ -0,0 +1,67 @@
+package net.floodlightcontroller.flowcache;
+
+
+import org.openflow.protocol.OFMatchWithSwDpid;
+
+/**
+ * Used in BigFlowCacheQueryResp as query result.
+ * Used to return one flow when queried by one of the big flow cache APIs.
+ * One of these QRFlowCacheObj is returned for each combination of
+ * priority and action.
+ *
+ * @author subrata
+ */
+public class QRFlowCacheObj {
+
+ /** The open flow match object. */
+ public OFMatchWithSwDpid ofmWithSwDpid;
+ /** The flow-mod priority. */
+ public short priority;
+ /** flow-mod cookie */
+ public long cookie;
+ /** The action - PERMIT or DENY. */
+ public byte action;
+ /** The reserved byte to align with 8 bytes. */
+ public byte reserved;
+
+ /**
+ * Instantiates a new flow cache query object.
+ *
+ * @param priority the priority
+ * @param action the action
+ */
+ public QRFlowCacheObj(short priority, byte action, long cookie) {
+ ofmWithSwDpid = new OFMatchWithSwDpid();
+ this.action = action;
+ this.priority = priority;
+ this.cookie = cookie;
+ }
+
+ /**
+ * Populate a given OFMatchReconcile object from the values of this
+ * class.
+ *
+ * @param ofmRc the given OFMatchReconcile object
+ * @param appInstName the application instance name
+ * @param rcAction the reconcile action
+ */
+ public void toOFMatchReconcile(OFMatchReconcile ofmRc,
+ String appInstName, OFMatchReconcile.ReconcileAction rcAction) {
+ ofmRc.ofmWithSwDpid = ofmWithSwDpid; // not copying
+ ofmRc.appInstName = appInstName;
+ ofmRc.rcAction = rcAction;
+ ofmRc.priority = priority;
+ ofmRc.cookie = cookie;
+ ofmRc.action = action;
+ }
+
+ @Override
+ public String toString() {
+ String str = "ofmWithSwDpid: " + this.ofmWithSwDpid.toString() + " ";
+ str += "priority: " + this.priority + " ";
+ str += "cookie: " + this.cookie + " ";
+ str += "action: " + this.action + " ";
+ str += "reserved: " + this.reserved + " ";
+ return str;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/forwarding/Forwarding.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/forwarding/Forwarding.java
new file mode 100644
index 0000000..3fc7ae9
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/forwarding/Forwarding.java
@@ -0,0 +1,453 @@
+/**
+* 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;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import net.floodlightcontroller.core.FloodlightContext;
+import net.floodlightcontroller.core.IFloodlightProviderService;
+import net.floodlightcontroller.core.IOFSwitch;
+import net.floodlightcontroller.devicemanager.IDevice;
+import net.floodlightcontroller.devicemanager.IDeviceService;
+import net.floodlightcontroller.devicemanager.SwitchPort;
+import net.floodlightcontroller.core.annotations.LogMessageCategory;
+import net.floodlightcontroller.core.annotations.LogMessageDoc;
+import net.floodlightcontroller.core.annotations.LogMessageDocs;
+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.packet.Ethernet;
+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.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];
+ Long srcCluster =
+ topology.getL2DomainId(srcDap.getSwitchDPID());
+ Long dstCluster =
+ topology.getL2DomainId(dstDap.getSwitchDPID());
+
+ int srcVsDest = srcCluster.compareTo(dstCluster);
+ if (srcVsDest == 0) {
+ if (!srcDap.equals(dstDap) &&
+ (srcCluster != null) &&
+ (dstCluster != null)) {
+ Route route =
+ routingEngine.getRoute(srcDap.getSwitchDPID(),
+ (short)srcDap.getPort(),
+ dstDap.getSwitchDPID(),
+ (short)dstDap.getPort());
+ 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(pi.getBufferId());
+ po.setInPort(pi.getInPort());
+ if (pi.getBufferId() == OFPacketOut.BUFFER_ID_NONE) {
+ 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);
+
+ // read our config options
+ Map<String, String> configOptions = context.getConfigParams(this);
+ try {
+ String idleTimeout = configOptions.get("idletimeout");
+ if (idleTimeout != null) {
+ FLOWMOD_DEFAULT_IDLE_TIMEOUT = Short.parseShort(idleTimeout);
+ }
+ } catch (NumberFormatException e) {
+ log.warn("Error parsing flow idle timeout, " +
+ "using default of {} seconds",
+ FLOWMOD_DEFAULT_IDLE_TIMEOUT);
+ }
+ try {
+ String hardTimeout = configOptions.get("hardtimeout");
+ if (hardTimeout != null) {
+ FLOWMOD_DEFAULT_HARD_TIMEOUT = Short.parseShort(hardTimeout);
+ }
+ } catch (NumberFormatException e) {
+ log.warn("Error parsing flow hard timeout, " +
+ "using default of {} seconds",
+ FLOWMOD_DEFAULT_HARD_TIMEOUT);
+ }
+ log.debug("FlowMod idle timeout set to {} seconds",
+ FLOWMOD_DEFAULT_IDLE_TIMEOUT);
+ log.debug("FlowMod hard timeout set to {} seconds",
+ FLOWMOD_DEFAULT_HARD_TIMEOUT);
+ }
+
+ @Override
+ public void startUp(FloodlightModuleContext context) {
+ super.startUp();
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/hub/Hub.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/hub/Hub.java
new file mode 100644
index 0000000..3618351
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/hub/Hub.java
@@ -0,0 +1,144 @@
+/**
+* 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.hub;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+
+import net.floodlightcontroller.core.FloodlightContext;
+import net.floodlightcontroller.core.IFloodlightProviderService;
+import net.floodlightcontroller.core.IOFMessageListener;
+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 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;
+
+/**
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu) - 04/04/10
+ */
+public class Hub implements IFloodlightModule, IOFMessageListener {
+ protected static Logger log = LoggerFactory.getLogger(Hub.class);
+
+ protected IFloodlightProviderService floodlightProvider;
+
+ /**
+ * @param floodlightProvider the floodlightProvider to set
+ */
+ public void setFloodlightProvider(IFloodlightProviderService floodlightProvider) {
+ this.floodlightProvider = floodlightProvider;
+ }
+
+ @Override
+ public String getName() {
+ return Hub.class.getPackage().getName();
+ }
+
+ 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());
+
+ // set actions
+ OFActionOutput action = new OFActionOutput()
+ .setPort((short) 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() == 0xffffffff) {
+ 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);
+ }
+
+ return Command.CONTINUE;
+ }
+
+ @Override
+ public boolean isCallbackOrderingPrereq(OFType type, String name) {
+ return false;
+ }
+
+ @Override
+ public boolean isCallbackOrderingPostreq(OFType type, String name) {
+ return false;
+ }
+
+ // IFloodlightModule
+
+ @Override
+ public Collection<Class<? extends IFloodlightService>> getModuleServices() {
+ // We don't provide any services, return null
+ return null;
+ }
+
+ @Override
+ public Map<Class<? extends IFloodlightService>, IFloodlightService>
+ getServiceImpls() {
+ // We don't provide any services, return null
+ return null;
+ }
+
+ @Override
+ public Collection<Class<? extends IFloodlightService>>
+ getModuleDependencies() {
+ Collection<Class<? extends IFloodlightService>> l =
+ new ArrayList<Class<? extends IFloodlightService>>();
+ l.add(IFloodlightProviderService.class);
+ return l;
+ }
+
+ @Override
+ public void init(FloodlightModuleContext context)
+ throws FloodlightModuleException {
+ floodlightProvider =
+ context.getServiceImpl(IFloodlightProviderService.class);
+ }
+
+ @Override
+ public void startUp(FloodlightModuleContext context) {
+ floodlightProvider.addOFMessageListener(OFType.PACKET_IN, this);
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/jython/JythonDebugInterface.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/jython/JythonDebugInterface.java
new file mode 100644
index 0000000..19a97b5
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/jython/JythonDebugInterface.java
@@ -0,0 +1,68 @@
+package net.floodlightcontroller.jython;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+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;
+
+public class JythonDebugInterface implements IFloodlightModule {
+ protected static Logger log = LoggerFactory.getLogger(JythonDebugInterface.class);
+ protected JythonServer debug_server;
+ protected static int JYTHON_PORT = 6655;
+
+ @Override
+ public Collection<Class<? extends IFloodlightService>> getModuleServices() {
+ // We don't export services
+ return null;
+ }
+
+ @Override
+ public Map<Class<? extends IFloodlightService>, IFloodlightService>
+ getServiceImpls() {
+ // We don't export services
+ return null;
+ }
+
+ @Override
+ public Collection<Class<? extends IFloodlightService>>
+ getModuleDependencies() {
+ // We don't have any dependencies
+ return null;
+ }
+
+ @Override
+ public void init(FloodlightModuleContext context)
+ throws FloodlightModuleException {
+ // no-op
+ }
+
+ @Override
+ public void startUp(FloodlightModuleContext context) {
+ Map<String, Object> locals = new HashMap<String, Object>();
+ // add all existing module references to the debug server
+ for (Class<? extends IFloodlightService> s : context.getAllServices()) {
+ // Put only the last part of the name
+ String[] bits = s.getCanonicalName().split("\\.");
+ String name = bits[bits.length-1];
+ locals.put(name, context.getServiceImpl(s));
+ }
+
+ // read our config options
+ Map<String, String> configOptions = context.getConfigParams(this);
+ int port = JYTHON_PORT;
+ String portNum = configOptions.get("port");
+ if (portNum != null) {
+ port = Integer.parseInt(portNum);
+ }
+
+ JythonServer debug_server = new JythonServer(port, locals);
+ debug_server.start();
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/jython/JythonServer.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/jython/JythonServer.java
new file mode 100644
index 0000000..fc35b15
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/jython/JythonServer.java
@@ -0,0 +1,63 @@
+package net.floodlightcontroller.jython;
+
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.python.util.PythonInterpreter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This class starts a thread that runs a jython interpreter that
+ * can be used for debug (or even development).
+ *
+ * @author mandeepdhami
+ *
+ */
+public class JythonServer extends Thread {
+ protected static Logger log = LoggerFactory.getLogger(JythonServer.class);
+
+ int port;
+ Map<String, Object> locals;
+
+ /**
+ * @param port_ Port to use for jython server
+ * @param locals_ Locals to add to the interpreters top level name space
+ */
+ public JythonServer(int port_, Map<String, Object> locals_) {
+ this.port = port_ ;
+ this.locals = locals_;
+ if (this.locals == null) {
+ this.locals = new HashMap<String, Object>();
+ }
+ this.locals.put("log", JythonServer.log);
+ this.setName("debugserver");
+ }
+
+ /**
+ * The main thread for this class invoked by Thread.run()
+ *
+ * @see java.lang.Thread#run()
+ */
+ public void run() {
+ PythonInterpreter p = new PythonInterpreter();
+ for (String name : this.locals.keySet()) {
+ p.set(name, this.locals.get(name));
+ }
+
+ URL jarUrl = JythonServer.class.getProtectionDomain().getCodeSource().getLocation();
+ String jarPath = jarUrl.getPath();
+ if (jarUrl.getProtocol().equals("file")) {
+ // If URL is of type file, assume that we are in dev env and set path to python dir.
+ // else use the jar file as is
+ jarPath = jarPath + "../../src/main/python/";
+ }
+
+ p.exec("import sys");
+ p.exec("sys.path.append('" + jarPath + "')");
+ p.exec("from debugserver import run_server");
+ p.exec("run_server(" + this.port + ", '0.0.0.0', locals())");
+ }
+
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/learningswitch/ILearningSwitchService.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/learningswitch/ILearningSwitchService.java
new file mode 100644
index 0000000..71f6625
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/learningswitch/ILearningSwitchService.java
@@ -0,0 +1,15 @@
+package net.floodlightcontroller.learningswitch;
+
+import java.util.Map;
+
+import net.floodlightcontroller.core.IOFSwitch;
+import net.floodlightcontroller.core.module.IFloodlightService;
+import net.floodlightcontroller.core.types.MacVlanPair;
+
+public interface ILearningSwitchService extends IFloodlightService {
+ /**
+ * Returns the LearningSwitch's learned host table
+ * @return The learned host table
+ */
+ public Map<IOFSwitch, Map<MacVlanPair,Short>> getTable();
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/learningswitch/LearningSwitch.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/learningswitch/LearningSwitch.java
new file mode 100644
index 0000000..005708d
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/learningswitch/LearningSwitch.java
@@ -0,0 +1,508 @@
+/**
+* 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.
+**/
+
+/**
+ * Floodlight
+ * A BSD licensed, Java based OpenFlow controller
+ *
+ * Floodlight is a Java based OpenFlow controller originally written by David Erickson at Stanford
+ * University. It is available under the BSD license.
+ *
+ * For documentation, forums, issue tracking and more visit:
+ *
+ * http://www.openflowhub.org/display/Floodlight/Floodlight+Home
+ **/
+
+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.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import net.floodlightcontroller.core.FloodlightContext;
+import net.floodlightcontroller.core.IFloodlightProviderService;
+import net.floodlightcontroller.core.IOFMessageListener;
+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.core.types.MacVlanPair;
+import net.floodlightcontroller.counter.ICounterStoreService;
+import net.floodlightcontroller.packet.Ethernet;
+import net.floodlightcontroller.restserver.IRestApiService;
+
+import org.openflow.protocol.OFError;
+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.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class LearningSwitch
+ implements IFloodlightModule, ILearningSwitchService, IOFMessageListener {
+ protected static Logger log = LoggerFactory.getLogger(LearningSwitch.class);
+
+ // Module dependencies
+ protected IFloodlightProviderService floodlightProvider;
+ protected ICounterStoreService counterStore;
+ protected IRestApiService restApi;
+
+ // Stores the learned state for each switch
+ protected Map<IOFSwitch, Map<MacVlanPair,Short>> macVlanToSwitchPortMap;
+
+ // flow-mod - for use in the cookie
+ public static final int LEARNING_SWITCH_APP_ID = 1;
+ // LOOK! This should probably go in some class that encapsulates
+ // the app cookie management
+ public static final int APP_ID_BITS = 12;
+ public static final int APP_ID_SHIFT = (64 - APP_ID_BITS);
+ public static final long LEARNING_SWITCH_COOKIE = (long) (LEARNING_SWITCH_APP_ID & ((1 << APP_ID_BITS) - 1)) << APP_ID_SHIFT;
+
+ // more flow-mod defaults
+ protected static final short IDLE_TIMEOUT_DEFAULT = 5;
+ protected static final short HARD_TIMEOUT_DEFAULT = 0;
+ protected static final short PRIORITY_DEFAULT = 100;
+
+ // for managing our map sizes
+ protected static final int MAX_MACS_PER_SWITCH = 1000;
+
+ // normally, setup reverse flow as well. Disable only for using cbench for comparison with NOX etc.
+ protected static final boolean LEARNING_SWITCH_REVERSE_FLOW = true;
+
+ /**
+ * @param floodlightProvider the floodlightProvider to set
+ */
+ public void setFloodlightProvider(IFloodlightProviderService floodlightProvider) {
+ this.floodlightProvider = floodlightProvider;
+ }
+
+ @Override
+ public String getName() {
+ return "learningswitch";
+ }
+
+ /**
+ * Adds a host to the MAC/VLAN->SwitchPort mapping
+ * @param sw The switch to add the mapping to
+ * @param mac The MAC address of the host to add
+ * @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);
+
+ if (vlan == (short) 0xffff) {
+ // 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;
+ }
+
+ 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));
+ macVlanToSwitchPortMap.put(sw, swMap);
+ }
+ swMap.put(new MacVlanPair(mac, vlan), portVal);
+ }
+
+ /**
+ * Removes a host from the MAC/VLAN->SwitchPort mapping
+ * @param sw The switch to remove the mapping from
+ * @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;
+ }
+ Map<MacVlanPair,Short> swMap = macVlanToSwitchPortMap.get(sw);
+ if (swMap != null)
+ swMap.remove(new MacVlanPair(mac, vlan));
+ }
+
+ /**
+ * Get the port that a MAC/VLAN pair is associated with
+ * @param sw The switch to get the mapping from
+ * @param mac The MAC address to get
+ * @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;
+ }
+ Map<MacVlanPair,Short> swMap = macVlanToSwitchPortMap.get(sw);
+ if (swMap != null)
+ return swMap.get(new MacVlanPair(mac, vlan));
+
+ // if none found
+ return null;
+ }
+
+ /**
+ * Clears the MAC/VLAN -> SwitchPort map for all switches
+ */
+ public void clearLearnedTable() {
+ macVlanToSwitchPortMap.clear();
+ }
+
+ /**
+ * Clears the MAC/VLAN -> SwitchPort map for a single switch
+ * @param sw The switch to clear the mapping for
+ */
+ public void clearLearnedTable(IOFSwitch sw) {
+ Map<MacVlanPair, Short> swMap = macVlanToSwitchPortMap.get(sw);
+ if (swMap != null)
+ swMap.clear();
+ }
+
+ @Override
+ public synchronized Map<IOFSwitch, Map<MacVlanPair,Short>> getTable() {
+ return macVlanToSwitchPortMap;
+ }
+
+ /**
+ * Writes a OFFlowMod to a switch.
+ * @param sw The switch tow rite the flowmod to.
+ * @param command The FlowMod actions (add, delete, etc).
+ * @param bufferId The buffer ID if the switch has buffered the packet.
+ * @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) {
+ // from openflow 1.0 spec - need to set these on a struct ofp_flow_mod:
+ // struct ofp_flow_mod {
+ // struct ofp_header header;
+ // struct ofp_match match; /* Fields to match */
+ // uint64_t cookie; /* Opaque controller-issued identifier. */
+ //
+ // /* Flow actions. */
+ // uint16_t command; /* One of OFPFC_*. */
+ // uint16_t idle_timeout; /* Idle time before discarding (seconds). */
+ // uint16_t hard_timeout; /* Max time before discarding (seconds). */
+ // uint16_t priority; /* Priority level of flow entry. */
+ // uint32_t buffer_id; /* Buffered packet to apply to (or -1).
+ // Not meaningful for OFPFC_DELETE*. */
+ // uint16_t out_port; /* For OFPFC_DELETE* commands, require
+ // matching entries to include this as an
+ // output port. A value of OFPP_NONE
+ // indicates no restriction. */
+ // uint16_t flags; /* One of OFPFF_*. */
+ // struct ofp_action_header actions[0]; /* The action length is inferred
+ // from the length field in the
+ // 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.IDLE_TIMEOUT_DEFAULT);
+ flowMod.setHardTimeout(LearningSwitch.HARD_TIMEOUT_DEFAULT);
+ flowMod.setPriority(LearningSwitch.PRIORITY_DEFAULT);
+ 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
+
+ // set the ofp_action_header/out actions:
+ // from the openflow 1.0 spec: need to set these on a struct ofp_action_output:
+ // uint16_t type; /* OFPAT_OUTPUT. */
+ // uint16_t len; /* Length is 8. */
+ // uint16_t port; /* Output port. */
+ // 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));
+
+ if (log.isTraceEnabled()) {
+ log.trace("{} {} flow mod {}",
+ new Object[]{ sw, (command == OFFlowMod.OFPFC_DELETE) ? "deleting" : "adding", flowMod });
+ }
+
+ counterStore.updatePktOutFMCounterStore(sw, flowMod);
+
+ // and write it out
+ try {
+ sw.write(flowMod, null);
+ } catch (IOException e) {
+ log.error("Failed to write {} to switch {}", new Object[]{ flowMod, sw }, e);
+ }
+ }
+
+ /**
+ * Writes an OFPacketOut message to a switch.
+ * @param sw The switch to write the PacketOut to.
+ * @param packetInMessage The corresponding PacketIn.
+ * @param egressPort The switchport to output the PacketOut.
+ */
+ private void writePacketOutForPacketIn(IOFSwitch sw,
+ OFPacketIn packetInMessage,
+ short 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). */
+ // uint16_t actions_len; /* Size of action array in bytes. */
+ // struct ofp_action_header actions[0]; /* Actions. */
+ /* uint8_t data[0]; */ /* Packet data. The length is inferred
+ 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
+
+ // 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;
+
+ // set actions
+ List<OFAction> actions = new ArrayList<OFAction>(1);
+ actions.add(new OFActionOutput(egressPort, (short) 0));
+ packetOutMessage.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;
+ }
+
+ // finally, set the total length
+ packetOutMessage.setLength(packetOutLength);
+
+ // and write it out
+ try {
+ counterStore.updatePktOutFMCounterStore(sw, packetOutMessage);
+ sw.write(packetOutMessage, null);
+ } catch (IOException e) {
+ log.error("Failed to write {} to switch {}: {}", new Object[]{ packetOutMessage, sw, e });
+ }
+ }
+
+ /**
+ * Processes a OFPacketIn message. If the switch has learned the MAC/VLAN to port mapping
+ * for the pair it will write a FlowMod for. If the mapping has not been learned the
+ * we will flood the packet.
+ * @param sw
+ * @param pi
+ * @param cntx
+ * @return
+ */
+ 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) {
+ 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) });
+ }
+ return Command.STOP;
+ }
+ if ((sourceMac & 0x010000000000L) == 0) {
+ // If source MAC is a unicast address, learn the port for this MAC/VLAN
+ this.addToPortMap(sw, sourceMac, vlan, pi.getInPort());
+ }
+
+ // Now output flow-mod and/or packet
+ Short outPort = getFromPortMap(sw, destMac, vlan);
+ 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()) {
+ 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 });
+ } 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
+ // add a flow table entry with source and destination MACs reversed, and
+ // input and output ports reversed. When either entry expires due to idle
+ // timeout, remove the other one. This ensures that if a device moves to
+ // a different port, a constant stream of packets headed to the device at
+ // 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);
+ this.writeFlowMod(sw, OFFlowMod.OFPFC_ADD, pi.getBufferId(), match, 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());
+ }
+ }
+ return Command.CONTINUE;
+ }
+
+ /**
+ * Processes a flow removed message. We will delete the learned MAC/VLAN mapping from
+ * the switch's table.
+ * @param sw The switch that sent the flow removed message.
+ * @param flowRemovedMessage The flow removed message.
+ * @return Whether to continue processing this message or stop.
+ */
+ private Command processFlowRemovedMessage(IOFSwitch sw, OFFlowRemoved flowRemovedMessage) {
+ if (flowRemovedMessage.getCookie() != LearningSwitch.LEARNING_SWITCH_COOKIE) {
+ return Command.CONTINUE;
+ }
+ if (log.isTraceEnabled()) {
+ log.trace("{} flow entry removed {}", sw, flowRemovedMessage);
+ }
+ OFMatch 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());
+
+ // 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());
+ return Command.CONTINUE;
+ }
+
+ // IOFMessageListener
+
+ @Override
+ public Command receive(IOFSwitch sw, OFMessage msg, FloodlightContext cntx) {
+ switch (msg.getType()) {
+ case PACKET_IN:
+ return this.processPacketInMessage(sw, (OFPacketIn) msg, cntx);
+ case FLOW_REMOVED:
+ return this.processFlowRemovedMessage(sw, (OFFlowRemoved) msg);
+ case ERROR:
+ log.info("received an error {} from switch {}", (OFError) msg, sw);
+ return Command.CONTINUE;
+ default:
+ break;
+ }
+ log.error("received an unexpected message {} from switch {}", msg, sw);
+ return Command.CONTINUE;
+ }
+
+ @Override
+ public boolean isCallbackOrderingPrereq(OFType type, String name) {
+ return false;
+ }
+
+ @Override
+ public boolean isCallbackOrderingPostreq(OFType type, String name) {
+ return false;
+ }
+
+ // IFloodlightModule
+
+ @Override
+ public Collection<Class<? extends IFloodlightService>> getModuleServices() {
+ Collection<Class<? extends IFloodlightService>> l =
+ new ArrayList<Class<? extends IFloodlightService>>();
+ l.add(ILearningSwitchService.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(ILearningSwitchService.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(ICounterStoreService.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);
+ }
+
+ @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());
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/learningswitch/LearningSwitchTable.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/learningswitch/LearningSwitchTable.java
new file mode 100644
index 0000000..19f8bf5
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/learningswitch/LearningSwitchTable.java
@@ -0,0 +1,69 @@
+package net.floodlightcontroller.learningswitch;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+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.types.MacVlanPair;
+
+import org.openflow.util.HexString;
+import org.restlet.data.Status;
+import org.restlet.resource.Get;
+import org.restlet.resource.ServerResource;
+import org.slf4j.Logger;
+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) {
+ Map<String, Object> entry = new HashMap<String, Object>();
+ entry.put("mac", HexString.toHexString(key.mac));
+ entry.put("vlan", key.vlan);
+ entry.put("port", port);
+ return entry;
+ }
+
+ protected List<Map<String, Object>> getOneSwitchTable(Map<MacVlanPair, Short> switchMap) {
+ List<Map<String, Object>> switchTable = new ArrayList<Map<String, Object>>();
+ for (Entry<MacVlanPair, Short> entry : switchMap.entrySet()) {
+ switchTable.add(formatTableEntry(entry.getKey(), entry.getValue()));
+ }
+ return switchTable;
+ }
+
+ @Get("json")
+ public Map<String, List<Map<String, Object>>> getSwitchTableJson() {
+ ILearningSwitchService lsp =
+ (ILearningSwitchService)getContext().getAttributes().
+ get(ILearningSwitchService.class.getCanonicalName());
+
+ Map<IOFSwitch, Map<MacVlanPair,Short>> 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)));
+ }
+ } else {
+ try {
+ IFloodlightProviderService floodlightProvider =
+ (IFloodlightProviderService)getContext().getAttributes().
+ get(IFloodlightProviderService.class.getCanonicalName());
+ long dpid = HexString.toLong(switchId);
+ IOFSwitch sw = floodlightProvider.getSwitches().get(dpid);
+ allSwitchTableJson.put(HexString.toHexString(sw.getId()), getOneSwitchTable(table.get(sw)));
+ } catch (NumberFormatException e) {
+ log.error("Could not decode switch ID = " + switchId);
+ setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
+ }
+ }
+
+ return allSwitchTableJson;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/learningswitch/LearningSwitchWebRoutable.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/learningswitch/LearningSwitchWebRoutable.java
new file mode 100644
index 0000000..76c30cb
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/learningswitch/LearningSwitchWebRoutable.java
@@ -0,0 +1,22 @@
+package net.floodlightcontroller.learningswitch;
+
+import org.restlet.Context;
+import org.restlet.Restlet;
+import org.restlet.routing.Router;
+
+import net.floodlightcontroller.restserver.RestletRoutable;
+
+public class LearningSwitchWebRoutable implements RestletRoutable {
+
+ @Override
+ public Restlet getRestlet(Context context) {
+ Router router = new Router(context);
+ router.attach("/table/{switch}/json", LearningSwitchTable.class);
+ return router;
+ }
+
+ @Override
+ public String basePath() {
+ return "/wm/learningswitch";
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/linkdiscovery/ILinkDiscovery.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/linkdiscovery/ILinkDiscovery.java
new file mode 100644
index 0000000..f172f63
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/linkdiscovery/ILinkDiscovery.java
@@ -0,0 +1,162 @@
+package net.floodlightcontroller.linkdiscovery;
+
+import org.codehaus.jackson.map.annotate.JsonSerialize;
+import org.codehaus.jackson.map.ser.ToStringSerializer;
+import org.openflow.util.HexString;
+
+public interface ILinkDiscovery {
+
+ @JsonSerialize(using=ToStringSerializer.class)
+ public enum UpdateOperation {
+ LINK_UPDATED("Link Updated"),
+ LINK_REMOVED("Link Removed"),
+ SWITCH_UPDATED("Switch Updated"),
+ SWITCH_REMOVED("Switch Removed"),
+ PORT_UP("Port Up"),
+ PORT_DOWN("Port Down");
+
+ private String value;
+ UpdateOperation(String v) {
+ value = v;
+ }
+
+ @Override
+ public String toString() {
+ return value;
+ }
+ }
+
+ public class LDUpdate {
+ protected long src;
+ protected short srcPort;
+ protected long dst;
+ protected short dstPort;
+ protected SwitchType srcType;
+ protected LinkType type;
+ protected UpdateOperation operation;
+
+ public LDUpdate(long src, short srcPort,
+ long dst, short dstPort,
+ ILinkDiscovery.LinkType type,
+ UpdateOperation operation) {
+ this.src = src;
+ this.srcPort = srcPort;
+ this.dst = dst;
+ this.dstPort = dstPort;
+ this.type = type;
+ this.operation = operation;
+ }
+
+ public LDUpdate(LDUpdate old) {
+ this.src = old.src;
+ this.srcPort = old.srcPort;
+ this.dst = old.dst;
+ this.dstPort = old.dstPort;
+ this.srcType = old.srcType;
+ this.type = old.type;
+ this.operation = old.operation;
+ }
+
+ // For updtedSwitch(sw)
+ public LDUpdate(long switchId, SwitchType stype, UpdateOperation oper ){
+ this.operation = oper;
+ this.src = switchId;
+ this.srcType = stype;
+ }
+
+ // For port up or port down.
+ public LDUpdate(long sw, short port, UpdateOperation operation) {
+ this.src = sw;
+ this.srcPort = port;
+ this.operation = operation;
+ }
+
+ public long getSrc() {
+ return src;
+ }
+
+ public short getSrcPort() {
+ return srcPort;
+ }
+
+ public long getDst() {
+ return dst;
+ }
+
+ public short getDstPort() {
+ return dstPort;
+ }
+
+ public SwitchType getSrcType() {
+ return srcType;
+ }
+
+ public LinkType getType() {
+ return type;
+ }
+
+ public UpdateOperation getOperation() {
+ return operation;
+ }
+
+ public void setOperation(UpdateOperation operation) {
+ this.operation = operation;
+ }
+
+ @Override
+ public String toString() {
+ switch (operation) {
+ case LINK_REMOVED:
+ case LINK_UPDATED:
+ return "LDUpdate [operation=" + operation +
+ ", src=" + HexString.toHexString(src)
+ + ", srcPort=" + srcPort
+ + ", dst=" + HexString.toHexString(dst)
+ + ", dstPort=" + dstPort
+ + ", type=" + type + "]";
+ case PORT_DOWN:
+ case PORT_UP:
+ return "LDUpdate [operation=" + operation +
+ ", src=" + HexString.toHexString(src)
+ + ", srcPort=" + srcPort + "]";
+ case SWITCH_REMOVED:
+ case SWITCH_UPDATED:
+ return "LDUpdate [operation=" + operation +
+ ", src=" + HexString.toHexString(src) + "]";
+ default:
+ return "LDUpdate: Unknown update.";
+ }
+ }
+ }
+
+ public enum SwitchType {
+ BASIC_SWITCH, CORE_SWITCH
+ };
+
+ public enum LinkType {
+ INVALID_LINK {
+ @Override
+ public String toString() {
+ return "invalid";
+ }
+ },
+ DIRECT_LINK{
+ @Override
+ public String toString() {
+ return "internal";
+ }
+ },
+ MULTIHOP_LINK {
+ @Override
+ public String toString() {
+ return "external";
+ }
+ },
+ TUNNEL {
+ @Override
+ public String toString() {
+ return "tunnel";
+ }
+ }
+ };
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/linkdiscovery/ILinkDiscoveryListener.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/linkdiscovery/ILinkDiscoveryListener.java
new file mode 100644
index 0000000..35779a2
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/linkdiscovery/ILinkDiscoveryListener.java
@@ -0,0 +1,23 @@
+/**
+ * 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.linkdiscovery;
+
+public interface ILinkDiscoveryListener extends ILinkDiscovery{
+
+ public void linkDiscoveryUpdate(LDUpdate update);
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/linkdiscovery/ILinkDiscoveryService.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/linkdiscovery/ILinkDiscoveryService.java
new file mode 100644
index 0000000..4145592
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/linkdiscovery/ILinkDiscoveryService.java
@@ -0,0 +1,84 @@
+/**
+* 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.linkdiscovery;
+
+import java.util.Map;
+import java.util.Set;
+
+import net.floodlightcontroller.core.module.IFloodlightService;
+import net.floodlightcontroller.routing.Link;
+import net.floodlightcontroller.topology.NodePortTuple;
+
+
+public interface ILinkDiscoveryService extends IFloodlightService {
+ /**
+ * Retrieves a map of all known link connections between OpenFlow switches
+ * and the associated info (valid time, port states) for the link.
+ */
+ public Map<Link, LinkInfo> getLinks();
+
+ /**
+ * Returns link type of a given link
+ * @param info
+ * @return
+ */
+ public ILinkDiscovery.LinkType getLinkType(Link lt, LinkInfo info);
+
+ /**
+ * Returns an unmodifiable map from switch id to a set of all links with it
+ * as an endpoint.
+ */
+ public Map<Long, Set<Link>> getSwitchLinks();
+
+ /**
+ * Adds a listener to listen for ILinkDiscoveryService messages
+ * @param listener The listener that wants the notifications
+ */
+ public void addListener(ILinkDiscoveryListener listener);
+
+ /**
+ * Retrieves a set of all switch ports on which lldps are suppressed.
+ */
+ public Set<NodePortTuple> getSuppressLLDPsInfo();
+
+ /**
+ * Adds a switch port to suppress lldp set
+ */
+ public void AddToSuppressLLDPs(long sw, short port);
+
+ /**
+ * Removes a switch port from suppress lldp set
+ */
+ public void RemoveFromSuppressLLDPs(long sw, short port);
+
+ /**
+ * Get the set of quarantined ports on a switch
+ */
+ public Set<Short> getQuarantinedPorts(long sw);
+
+ /**
+ * Get the status of auto port fast feature.
+ */
+ public boolean isAutoPortFastFeature();
+
+ /**
+ * Set the state for auto port fast feature.
+ * @param autoPortFastFeature
+ */
+ public void setAutoPortFastFeature(boolean autoPortFastFeature);
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/linkdiscovery/LinkInfo.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/linkdiscovery/LinkInfo.java
new file mode 100644
index 0000000..9c0dd1a
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/linkdiscovery/LinkInfo.java
@@ -0,0 +1,182 @@
+/**
+* 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.linkdiscovery;
+
+import net.floodlightcontroller.linkdiscovery.ILinkDiscovery.LinkType;
+
+import org.openflow.protocol.OFPhysicalPort.OFPortState;
+
+public class LinkInfo {
+
+ public LinkInfo(Long firstSeenTime,
+ Long lastLldpReceivedTime,
+ Long lastBddpReceivedTime,
+ int srcPortState,
+ int dstPortState) {
+ super();
+ this.srcPortState = srcPortState;
+ this.dstPortState = dstPortState;
+ this.firstSeenTime = firstSeenTime;
+ this.lastLldpReceivedTime = lastLldpReceivedTime;
+ this.lastBddpReceivedTime = lastBddpReceivedTime;
+ }
+
+ protected Integer srcPortState;
+ protected Integer dstPortState;
+ protected Long firstSeenTime;
+ protected Long lastLldpReceivedTime; /* Standard LLLDP received time */
+ protected Long 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
+ * maintained in the port list in IOFSwitch (i.e. the one returned
+ * from getPort), except that during a port status message the
+ * IOFSwitch port state will already have been updated with the
+ * new port state, so topology needs to keep its own copy so that
+ * it can determine if the port state has changed and therefore
+ * requires the new state to be written to storage.
+ */
+
+
+
+ public boolean linkStpBlocked() {
+ return ((srcPortState & OFPortState.OFPPS_STP_MASK.getValue()) == OFPortState.OFPPS_STP_BLOCK.getValue()) ||
+ ((dstPortState & OFPortState.OFPPS_STP_MASK.getValue()) == OFPortState.OFPPS_STP_BLOCK.getValue());
+ }
+
+ public Long getFirstSeenTime() {
+ return firstSeenTime;
+ }
+
+ public void setFirstSeenTime(Long firstSeenTime) {
+ this.firstSeenTime = firstSeenTime;
+ }
+
+ public Long getUnicastValidTime() {
+ return lastLldpReceivedTime;
+ }
+
+ public void setUnicastValidTime(Long unicastValidTime) {
+ this.lastLldpReceivedTime = unicastValidTime;
+ }
+
+ public Long getMulticastValidTime() {
+ return lastBddpReceivedTime;
+ }
+
+ public void setMulticastValidTime(Long multicastValidTime) {
+ this.lastBddpReceivedTime = multicastValidTime;
+ }
+
+ public Integer getSrcPortState() {
+ return srcPortState;
+ }
+
+ public void setSrcPortState(Integer srcPortState) {
+ this.srcPortState = srcPortState;
+ }
+
+ public Integer getDstPortState() {
+ return dstPortState;
+ }
+
+ public void setDstPortState(int dstPortState) {
+ this.dstPortState = dstPortState;
+ }
+
+ public LinkType getLinkType() {
+ if (lastLldpReceivedTime != null) {
+ return LinkType.DIRECT_LINK;
+ } else if (lastBddpReceivedTime != null) {
+ return LinkType.MULTIHOP_LINK;
+ }
+ return LinkType.INVALID_LINK;
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ final int prime = 5557;
+ int result = 1;
+ result = prime * result + ((firstSeenTime == null) ? 0 : firstSeenTime.hashCode());
+ result = prime * result + ((lastLldpReceivedTime == null) ? 0 : lastLldpReceivedTime.hashCode());
+ result = prime * result + ((lastBddpReceivedTime == null) ? 0 : lastBddpReceivedTime.hashCode());
+ result = prime * result + ((srcPortState == null) ? 0 : srcPortState.hashCode());
+ result = prime * result + ((dstPortState == null) ? 0 : dstPortState.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 (!(obj instanceof LinkInfo))
+ return false;
+ LinkInfo other = (LinkInfo) obj;
+
+ if (firstSeenTime == null) {
+ if (other.firstSeenTime != null)
+ return false;
+ } else if (!firstSeenTime.equals(other.firstSeenTime))
+ return false;
+
+ if (lastLldpReceivedTime == null) {
+ if (other.lastLldpReceivedTime != null)
+ return false;
+ } else if (!lastLldpReceivedTime.equals(other.lastLldpReceivedTime))
+ return false;
+
+ if (lastBddpReceivedTime == null) {
+ if (other.lastBddpReceivedTime != null)
+ return false;
+ } else if (!lastBddpReceivedTime.equals(other.lastBddpReceivedTime))
+ return false;
+
+ if (srcPortState == null) {
+ if (other.srcPortState != null)
+ return false;
+ } else if (!srcPortState.equals(other.srcPortState))
+ return false;
+
+ if (dstPortState == null) {
+ if (other.dstPortState != null)
+ return false;
+ } else if (!dstPortState.equals(other.dstPortState))
+ return false;
+
+ return true;
+ }
+
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return "LinkInfo [unicastValidTime=" + ((lastLldpReceivedTime == null) ? "null" : lastLldpReceivedTime)
+ + ", multicastValidTime=" + ((lastBddpReceivedTime == null) ? "null" : lastBddpReceivedTime)
+ + ", srcPortState=" + ((srcPortState == null) ? "null" : srcPortState)
+ + ", dstPortState=" + ((dstPortState == null) ? "null" : srcPortState)
+ + "]";
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/linkdiscovery/internal/EventHistoryTopologyCluster.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/linkdiscovery/internal/EventHistoryTopologyCluster.java
new file mode 100644
index 0000000..d57e987
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/linkdiscovery/internal/EventHistoryTopologyCluster.java
@@ -0,0 +1,43 @@
+package net.floodlightcontroller.linkdiscovery.internal;
+
+import net.floodlightcontroller.core.web.serializers.DPIDSerializer;
+
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.codehaus.jackson.map.annotate.JsonSerialize;
+
+/***
+ * Topology Cluster merge/split event history related classes and members
+ * @author subrata
+ *
+ */
+public class EventHistoryTopologyCluster {
+ // The following fields are not stored as String to save memory
+ // They should be converted to appropriate human-readable strings by
+ // the front end (e.g. in cli in Python)
+ public long dpid;
+ public long clusterIdOld; // Switch with dpid moved from cluster x to y
+ public long clusterIdNew;
+ public String reason;
+
+ @JsonProperty("Switch")
+ @JsonSerialize(using=DPIDSerializer.class)
+ public long getDpid() {
+ return dpid;
+ }
+ @JsonProperty("OldClusterId")
+ @JsonSerialize(using=DPIDSerializer.class)
+ public long getClusterIdOld() {
+ return clusterIdOld;
+ }
+ @JsonProperty("NewClusterId")
+ @JsonSerialize(using=DPIDSerializer.class)
+ public long getClusterIdNew() {
+ return clusterIdNew;
+ }
+ @JsonProperty("Reason")
+ public String getReason() {
+ return reason;
+ }
+
+
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/linkdiscovery/internal/EventHistoryTopologyLink.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/linkdiscovery/internal/EventHistoryTopologyLink.java
new file mode 100644
index 0000000..98796ed
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/linkdiscovery/internal/EventHistoryTopologyLink.java
@@ -0,0 +1,62 @@
+package net.floodlightcontroller.linkdiscovery.internal;
+
+import net.floodlightcontroller.core.web.serializers.DPIDSerializer;
+
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.codehaus.jackson.map.annotate.JsonSerialize;
+
+/***
+ * Topology link up/down event history related classes and members
+ * @author subrata
+ *
+ */
+public class EventHistoryTopologyLink {
+ // The following fields are not stored as String to save memory
+ // They should be converted to appropriate human-readable strings by
+ // the front end (e.g. in cli in Python)
+ public long srcSwDpid;
+ public long dstSwDpid;
+ public int srcPortState;
+ public int dstPortState;
+ public int srcSwport;
+ public int dstSwport;
+ public String linkType;
+ public String reason;
+
+ @JsonProperty("Source-Switch")
+ @JsonSerialize(using=DPIDSerializer.class)
+ public long getSrcSwDpid() {
+ return srcSwDpid;
+ }
+ @JsonProperty("Dest-Switch")
+ @JsonSerialize(using=DPIDSerializer.class)
+ public long getDstSwDpid() {
+ return dstSwDpid;
+ }
+ @JsonProperty("SrcPortState")
+ public int getSrcPortState() {
+ return srcPortState;
+ }
+ @JsonProperty("DstPortState")
+ public int getDstPortState() {
+ return dstPortState;
+ }
+ @JsonProperty("SrcPort")
+ public int getSrcSwport() {
+ return srcSwport;
+ }
+ @JsonProperty("DstPort")
+ public int getDstSwport() {
+ return dstSwport;
+ }
+ @JsonProperty("LinkType")
+ public String getLinkType() {
+ return linkType;
+ }
+ @JsonProperty("Reason")
+ public String getReason() {
+ return reason;
+ }
+
+
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/linkdiscovery/internal/EventHistoryTopologySwitch.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/linkdiscovery/internal/EventHistoryTopologySwitch.java
new file mode 100644
index 0000000..001942b
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/linkdiscovery/internal/EventHistoryTopologySwitch.java
@@ -0,0 +1,43 @@
+package net.floodlightcontroller.linkdiscovery.internal;
+
+import net.floodlightcontroller.core.web.serializers.DPIDSerializer;
+import net.floodlightcontroller.core.web.serializers.IPv4Serializer;
+
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.codehaus.jackson.map.annotate.JsonSerialize;
+
+/***
+ * Topology Switch event history related classes and members
+ * @author subrata
+ *
+ */
+public class EventHistoryTopologySwitch {
+ // The following fields are not stored as String to save memory
+ // They should be converted to appropriate human-readable strings by
+ // the front end (e.g. in cli in Python)
+ public long dpid;
+ public int ipv4Addr;
+ public int l4Port;
+ public String reason;
+
+ @JsonProperty("Switch")
+ @JsonSerialize(using=DPIDSerializer.class)
+ public long getDpid() {
+ return dpid;
+ }
+ @JsonProperty("IpAddr")
+ @JsonSerialize(using=IPv4Serializer.class)
+ public int getIpv4Addr() {
+ return ipv4Addr;
+ }
+ @JsonProperty("Port")
+ public int getL4Port() {
+ return l4Port;
+ }
+ @JsonProperty("Reason")
+ public String getReason() {
+ return reason;
+ }
+
+
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/linkdiscovery/internal/LinkDiscoveryManager.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/linkdiscovery/internal/LinkDiscoveryManager.java
new file mode 100644
index 0000000..bcb8b35
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/linkdiscovery/internal/LinkDiscoveryManager.java
@@ -0,0 +1,2062 @@
+/**
+ * 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.linkdiscovery.internal;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.NetworkInterface;
+import java.net.SocketAddress;
+import java.nio.ByteBuffer;
+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.Map.Entry;
+import java.util.Set;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+import net.floodlightcontroller.core.FloodlightContext;
+import net.floodlightcontroller.core.IFloodlightProviderService;
+import net.floodlightcontroller.core.IFloodlightProviderService.Role;
+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.annotations.LogMessageCategory;
+import net.floodlightcontroller.core.annotations.LogMessageDoc;
+import net.floodlightcontroller.core.annotations.LogMessageDocs;
+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.linkdiscovery.ILinkDiscovery;
+import net.floodlightcontroller.linkdiscovery.ILinkDiscovery.LinkType;
+import net.floodlightcontroller.linkdiscovery.ILinkDiscovery.SwitchType;
+import net.floodlightcontroller.linkdiscovery.ILinkDiscovery.LDUpdate;
+import net.floodlightcontroller.linkdiscovery.ILinkDiscovery.UpdateOperation;
+import net.floodlightcontroller.linkdiscovery.web.LinkDiscoveryWebRoutable;
+import net.floodlightcontroller.linkdiscovery.ILinkDiscoveryListener;
+import net.floodlightcontroller.linkdiscovery.ILinkDiscoveryService;
+import net.floodlightcontroller.linkdiscovery.LinkInfo;
+import net.floodlightcontroller.packet.BSN;
+import net.floodlightcontroller.packet.Ethernet;
+import net.floodlightcontroller.packet.IPv4;
+import net.floodlightcontroller.packet.LLDP;
+import net.floodlightcontroller.packet.LLDPTLV;
+import net.floodlightcontroller.restserver.IRestApiService;
+import net.floodlightcontroller.routing.Link;
+import net.floodlightcontroller.storage.IResultSet;
+import net.floodlightcontroller.storage.IStorageSourceService;
+import net.floodlightcontroller.storage.IStorageSourceListener;
+import net.floodlightcontroller.storage.OperatorPredicate;
+import net.floodlightcontroller.storage.StorageException;
+import net.floodlightcontroller.threadpool.IThreadPoolService;
+import net.floodlightcontroller.topology.NodePortTuple;
+import net.floodlightcontroller.util.EventHistory;
+import net.floodlightcontroller.util.EventHistory.EvAction;
+
+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.OFPortConfig;
+import org.openflow.protocol.OFPhysicalPort.OFPortState;
+import org.openflow.protocol.OFPort;
+import org.openflow.protocol.OFPortStatus;
+import org.openflow.protocol.OFPortStatus.OFPortReason;
+import org.openflow.protocol.OFType;
+import org.openflow.protocol.action.OFAction;
+import org.openflow.protocol.action.OFActionOutput;
+import org.openflow.util.HexString;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This class sends out LLDP messages containing the sending switch's datapath
+ * id as well as the outgoing port number. Received LLrescDP messages that
+ * match a known switch cause a new LinkTuple to be created according to the
+ * invariant rules listed below. This new LinkTuple is also passed to routing
+ * if it exists to trigger updates.
+ *
+ * This class also handles removing links that are associated to switch ports
+ * that go down, and switches that are disconnected.
+ *
+ * Invariants:
+ * -portLinks and switchLinks will not contain empty Sets outside of
+ * critical sections
+ * -portLinks contains LinkTuples where one of the src or dst
+ * SwitchPortTuple matches the map key
+ * -switchLinks contains LinkTuples where one of the src or dst
+ * SwitchPortTuple's id matches the switch id
+ * -Each LinkTuple will be indexed into switchLinks for both
+ * src.id and dst.id, and portLinks for each src and dst
+ * -The updates queue is only added to from within a held write lock
+ */
+@LogMessageCategory("Network Topology")
+public class LinkDiscoveryManager
+implements IOFMessageListener, IOFSwitchListener,
+IStorageSourceListener, ILinkDiscoveryService,
+IFloodlightModule, IInfoProvider, IHAListener {
+ protected static Logger log = LoggerFactory.getLogger(LinkDiscoveryManager.class);
+
+ // Names of table/fields for links in the storage API
+ 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_SRC_PORT_STATE = "src_port_state";
+ private static final String LINK_DST_SWITCH = "dst_switch_id";
+ private static final String LINK_DST_PORT = "dst_port";
+ private static final String LINK_DST_PORT_STATE = "dst_port_state";
+ 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";
+
+ protected IFloodlightProviderService floodlightProvider;
+ protected IStorageSourceService storageSource;
+ protected IThreadPoolService threadPool;
+ protected IRestApiService restApi;
+
+
+ // 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;
+
+ // 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((byte)TLV_DIRECTION_TYPE).
+ setLength((short)TLV_DIRECTION_LENGTH).
+ setValue(TLV_DIRECTION_VALUE_FORWARD);
+
+ private static final LLDPTLV reverseTLV
+ = new LLDPTLV().
+ setType((byte)TLV_DIRECTION_TYPE).
+ setLength((short)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.
+ */
+ boolean autoPortFastFeature = false;
+
+ /**
+ * 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;
+
+ /**
+ * Set of link tuples over which multicast LLDPs are received
+ * and unicast LLDPs are not received.
+ */
+ protected Map<NodePortTuple, Set<Link>> portBroadcastDomainLinks;
+
+ 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 = 5; // # of ports per iteration
+
+ /**
+ * Map of broadcast domain ports and the last time a BDDP was either
+ * sent or received on that port.
+ */
+ protected Map<NodePortTuple, Long> broadcastDomainPortTimeMap;
+
+ /**
+ * 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;
+ }
+
+ public Map<NodePortTuple, Set<Link>> getPortLinks() {
+ return portLinks;
+ }
+
+ public Set<NodePortTuple> getSuppressLLDPsInfo() {
+ return suppressLinkDiscovery;
+ }
+
+ /**
+ * Add a switch port to the suppressed LLDP list.
+ * Remove any known links on the switch port.
+ */
+ 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.
+ */
+ public void RemoveFromSuppressLLDPs(long sw, short port)
+ {
+ NodePortTuple npt = new NodePortTuple(sw, port);
+ this.suppressLinkDiscovery.remove(npt);
+ discover(npt);
+ }
+
+ public boolean isShuttingDown() {
+ return shuttingDown;
+ }
+
+ public boolean isFastPort(long sw, short port) {
+ return false;
+ }
+
+ 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;
+ }
+
+ @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();
+
+ 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(update);
+ }
+ }
+ catch (Exception e) {
+ log.error("Error in link discovery updates loop", e);
+ }
+ }
+ } while (updates.peek() != null);
+ }
+ private 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) {
+ log.debug("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());
+ }
+ }
+
+ 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;
+ }
+
+ private void generateSwitchPortStatusUpdate(long sw, short port) {
+ UpdateOperation operation;
+
+ IOFSwitch iofSwitch = floodlightProvider.getSwitches().get(sw);
+ if (iofSwitch == null) return;
+
+ OFPhysicalPort ofp = iofSwitch.getPort(port);
+ 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));
+ }
+
+ /**
+ * Send LLDP on known ports
+ */
+ protected void discoverOnKnownLinkPorts() {
+ // Copy the port set.
+ Set<NodePortTuple> nptSet = new HashSet<NodePortTuple>();
+ nptSet.addAll(portLinks.keySet());
+
+ // Send LLDP from each of them.
+ for(NodePortTuple npt: nptSet) {
+ discover(npt);
+ }
+ }
+
+ protected void discover(NodePortTuple npt) {
+ discover(npt.getNodeId(), npt.getPortId());
+ }
+
+ protected void discover(long sw, short port) {
+ sendDiscoveryMessage(sw, port, true, false);
+ }
+
+ /**
+ * 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) {
+
+ IOFSwitch iofSwitch = floodlightProvider.getSwitches().get(sw);
+ if (iofSwitch == null) {
+ return;
+ }
+
+ if (port == OFPort.OFPP_LOCAL.getValue())
+ return;
+
+ OFPhysicalPort ofpPort = iofSwitch.getPort(port);
+
+ if (ofpPort == null) {
+ if (log.isTraceEnabled()) {
+ log.trace("Null physical port. sw={}, port={}", sw, port);
+ }
+ return;
+ }
+
+ if (isLinkDiscoverySuppressed(sw, port)) {
+ /* Dont send LLDPs out of this port as suppressLLDPs set
+ *
+ */
+ return;
+ }
+
+ // For fast ports, do not send forward LLDPs or BDDPs.
+ if (!isReverse && autoPortFastFeature && isFastPort(sw, port))
+ return;
+
+ if (log.isTraceEnabled()) {
+ log.trace("Sending LLDP packet out of swich: {}, port: {}",
+ 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 ethernet source mac to last 6 bytes of dpid
+ System.arraycopy(dpidArray, 2, ofpPort.getHardwareAddress(), 0, 6);
+ // 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);
+
+
+ // 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 actions
+ List<OFAction> actions = new ArrayList<OFAction>();
+ actions.add(new OFActionOutput(port, (short) 0));
+ po.setActions(actions);
+ po.setActionsLength((short) OFActionOutput.MINIMUM_LENGTH);
+
+ // set data
+ po.setLengthU(OFPacketOut.MINIMUM_LENGTH + po.getActionsLength() + data.length);
+ po.setPacketData(data);
+
+ // 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 on switch {}");
+ }
+ Set<Long> switches = floodlightProvider.getSwitches().keySet();
+ // Send standard LLDPs
+ for (long sw: switches) {
+ IOFSwitch iofSwitch = floodlightProvider.getSwitches().get(sw);
+ if (iofSwitch == null) continue;
+ if (iofSwitch.getEnabledPorts() != null) {
+ for (OFPhysicalPort ofp: iofSwitch.getEnabledPorts()) {
+ if (isLinkDiscoverySuppressed(sw, ofp.getPortNumber()))
+ continue;
+ if (autoPortFastFeature && isFastPort(sw, ofp.getPortNumber()))
+ continue;
+
+ // sends forward LLDP only non-fastports.
+ sendDiscoveryMessage(sw, ofp.getPortNumber(), true, false);
+
+ // If the switch port is not alreayd in the maintenance
+ // queue, add it.
+ NodePortTuple npt = new NodePortTuple(sw, ofp.getPortNumber());
+ addToMaintenanceQueue(npt);
+ }
+ }
+ }
+ }
+
+ 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;
+ InetAddress localIPAddress = null;
+ NetworkInterface localInterface = null;
+
+ byte[] controllerTLVValue = new byte[] {0, 0, 0, 0, 0, 0, 0, 0}; // 8 byte value.
+ ByteBuffer bb = ByteBuffer.allocate(10);
+
+ try{
+ localIPAddress = java.net.InetAddress.getLocalHost();
+ localInterface = NetworkInterface.getByInetAddress(localIPAddress);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ long result = System.nanoTime();
+ if (localIPAddress != null)
+ result = result * prime + IPv4.toIPv4Address(localIPAddress.getHostAddress());
+ if (localInterface != null)
+ result = result * prime + localInterface.hashCode();
+ // 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);
+ }
+
+ @Override
+ public String getName() {
+ return "linkdiscovery";
+ }
+
+ @Override
+ public Command receive(IOFSwitch sw, OFMessage msg, FloodlightContext cntx) {
+ switch (msg.getType()) {
+ case PACKET_IN:
+ return this.handlePacketIn(sw.getId(), (OFPacketIn) msg, cntx);
+ case PORT_STATUS:
+ return this.handlePortStatus(sw.getId(), (OFPortStatus) msg);
+ default:
+ break;
+ }
+ return Command.CONTINUE;
+ }
+
+ private Command handleLldp(LLDP lldp, long sw, OFPacketIn pi, boolean isStandard, FloodlightContext cntx) {
+ // If LLDP is suppressed on this port, ignore received packet as well
+ IOFSwitch iofSwitch = floodlightProvider.getSwitches().get(sw);
+ if (iofSwitch == null) {
+ return Command.STOP;
+ }
+
+ if (isLinkDiscoverySuppressed(sw, pi.getInPort()))
+ return Command.STOP;
+
+ // If this is a malformed LLDP, or not from us, exit
+ if (lldp.getPortId() == null || lldp.getPortId().getLength() != 3)
+ return Command.CONTINUE;
+
+ 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.getSwitches().get(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.
+ if (isStandard) {
+ if (log.isTraceEnabled()) {
+ log.trace("Getting standard LLDP from a different controller and quelching it.");
+ }
+ 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;
+ }
+ }
+
+
+ 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 {}", remoteSwitch, 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 {}",
+ remoteSwitch, remotePort);
+ }
+ return Command.STOP;
+ }
+ if (!iofSwitch.portEnabled(pi.getInPort())) {
+ if (log.isTraceEnabled()) {
+ log.trace("Ignoring link with disabled dest port: switch {} port {}", sw, pi.getInPort());
+ }
+ return Command.STOP;
+ }
+
+ OFPhysicalPort physicalPort = remoteSwitch.getPort(remotePort);
+ int srcPortState = (physicalPort != null) ? physicalPort.getState() : 0;
+ physicalPort = iofSwitch.getPort(pi.getInPort());
+ int dstPortState = (physicalPort != null) ? physicalPort.getState() : 0;
+
+ // Store the time of update to this link, and push it out to routingEngine
+ Link lt = new Link(remoteSwitch.getId(), remotePort, iofSwitch.getId(), pi.getInPort());
+
+
+ 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,
+ srcPortState, dstPortState);
+
+ 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,
+ dstPortState, srcPortState);
+
+ 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
+ return Command.STOP;
+ }
+
+ protected Command handlePacketIn(long sw, OFPacketIn pi,
+ FloodlightContext cntx) {
+ Ethernet eth =
+ IFloodlightProviderService.bcStore.get(cntx,
+ IFloodlightProviderService.CONTEXT_PI_PAYLOAD);
+
+ if(eth.getEtherType() == Ethernet.TYPE_BSN) {
+ BSN bsn = (BSN) eth.getPayload();
+ if (bsn == null) return Command.STOP;
+ if (bsn.getPayload() == null) return Command.STOP;
+ // It could be a packet other than BSN LLDP, therefore
+ // continue with the regular processing.
+ if (bsn.getPayload() instanceof LLDP == false)
+ return Command.CONTINUE;
+ return handleLldp((LLDP) bsn.getPayload(), sw, pi, false, cntx);
+ } else if (eth.getEtherType() == Ethernet.TYPE_LLDP) {
+ return handleLldp((LLDP) eth.getPayload(), sw, pi, true, cntx);
+ } else if (eth.getEtherType() < 1500) {
+ long destMac = eth.getDestinationMAC().toLong();
+ if ((destMac & LINK_LOCAL_MASK) == LINK_LOCAL_VALUE){
+ if (log.isTraceEnabled()) {
+ log.trace("Ignoring packet addressed to 802.1D/Q " +
+ "reserved address.");
+ }
+ return Command.STOP;
+ }
+ }
+
+ // If packet-in is from a quarantine port, stop processing.
+ NodePortTuple npt = new NodePortTuple(sw, pi.getInPort());
+ if (quarantineQueue.contains(npt)) return Command.STOP;
+
+ return Command.CONTINUE;
+ }
+
+ 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;
+ }
+
+ protected boolean addOrUpdateLink(Link lt, LinkInfo newInfo) {
+
+ NodePortTuple srcNpt, dstNpt;
+ 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;
+
+ srcNpt = new NodePortTuple(lt.getSrc(), lt.getSrcPort());
+ dstNpt = new NodePortTuple(lt.getDst(), lt.getDstPort());
+
+ if (oldInfo == null) {
+ // 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);
+
+ // Add to portNOFLinks if the unicast valid time is null
+ if (newInfo.getUnicastValidTime() == null)
+ addLinkToBroadcastDomain(lt);
+
+ writeLinkToStorage(lt, newInfo);
+ updateOperation = UpdateOperation.LINK_UPDATED;
+ linkChanged = true;
+
+ // Add to event history
+ evHistTopoLink(lt.getSrc(),
+ lt.getDst(),
+ lt.getSrcPort(),
+ lt.getDstPort(),
+ newInfo.getSrcPortState(), newInfo.getDstPortState(),
+ getLinkType(lt, newInfo),
+ EvAction.LINK_ADDED, "LLDP Recvd");
+ } else {
+ // 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) {
+ // openflow -> non-openflow transition
+ // we need to add the link tuple to the portNOFLinks
+ addLinkToBroadcastDomain(lt);
+ linkChanged = true;
+ } else if (oldTime == null & newTime != null) {
+ // non-openflow -> openflow transition
+ // we need to remove the link from the portNOFLinks
+ removeLinkFromBroadcastDomain(lt);
+ linkChanged = true;
+ }
+
+ // Only update the port states if they've changed
+ if (newInfo.getSrcPortState().intValue() !=
+ oldInfo.getSrcPortState().intValue() ||
+ newInfo.getDstPortState().intValue() !=
+ oldInfo.getDstPortState().intValue())
+ linkChanged = true;
+
+ // 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) {
+ updateOperation = getUpdateOperation(newInfo.getSrcPortState(),
+ newInfo.getDstPortState());
+ if (log.isTraceEnabled()) {
+ log.trace("Updated link {}", lt);
+ }
+ // Add to event history
+ evHistTopoLink(lt.getSrc(),
+ lt.getDst(),
+ lt.getSrcPort(),
+ lt.getDstPort(),
+ newInfo.getSrcPortState(), newInfo.getDstPortState(),
+ getLinkType(lt, newInfo),
+ EvAction.LINK_PORT_STATE_UPDATED,
+ "LLDP Recvd");
+ }
+ }
+
+ 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;
+ }
+
+ public Map<Long, Set<Link>> getSwitchLinks() {
+ return this.switchLinks;
+ }
+
+ /**
+ * Removes links from memory and storage.
+ * @param links The List of @LinkTuple to delete.
+ */
+ protected void deleteLinks(List<Link> links, String reason) {
+ NodePortTuple srcNpt, dstNpt;
+
+ lock.writeLock().lock();
+ try {
+ for (Link lt : links) {
+ srcNpt = new NodePortTuple(lt.getSrc(), lt.getSrcPort());
+ dstNpt =new NodePortTuple(lt.getDst(), lt.getDstPort());
+
+ switchLinks.get(lt.getSrc()).remove(lt);
+ switchLinks.get(lt.getDst()).remove(lt);
+ if (switchLinks.containsKey(lt.getSrc()) &&
+ switchLinks.get(lt.getSrc()).isEmpty())
+ this.switchLinks.remove(lt.getSrc());
+ if (this.switchLinks.containsKey(lt.getDst()) &&
+ 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);
+ updates.add(new LDUpdate(lt.getSrc(), lt.getSrcPort(),
+ lt.getDst(), lt.getDstPort(),
+ getLinkType(lt, info),
+ UpdateOperation.LINK_REMOVED));
+
+ // Update Event History
+ evHistTopoLink(lt.getSrc(),
+ lt.getDst(),
+ lt.getSrcPort(),
+ lt.getDstPort(),
+ 0, 0, // Port states
+ ILinkDiscovery.LinkType.INVALID_LINK,
+ EvAction.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 (log.isTraceEnabled()) {
+ log.trace("Deleted link {}", lt);
+ }
+ }
+ } finally {
+ lock.writeLock().unlock();
+ }
+ }
+
+ /**
+ * Handles an OFPortStatus message from a switch. We will add or
+ * delete LinkTupes as well re-compute the topology if needed.
+ * @param sw The IOFSwitch that sent the port status message
+ * @param ps The OFPortStatus message
+ * @return The Command to continue or stop after we process this message
+ */
+ protected Command handlePortStatus(long sw, OFPortStatus ps) {
+
+ IOFSwitch iofSwitch = floodlightProvider.getSwitches().get(sw);
+ if (iofSwitch == null) return Command.CONTINUE;
+
+ if (log.isTraceEnabled()) {
+ log.trace("handlePortStatus: Switch {} port #{} reason {}; " +
+ "config is {} state is {}",
+ new Object[] {iofSwitch.getStringId(),
+ ps.getDesc().getPortNumber(),
+ ps.getReason(),
+ ps.getDesc().getConfig(),
+ ps.getDesc().getState()});
+ }
+
+ short port = ps.getDesc().getPortNumber();
+ NodePortTuple npt = new NodePortTuple(sw, port);
+ boolean linkDeleted = false;
+ boolean linkInfoChanged = false;
+
+ lock.writeLock().lock();
+ try {
+ // if ps is a delete, or a modify where the port is down or
+ // configured down
+ if ((byte)OFPortReason.OFPPR_DELETE.ordinal() == ps.getReason() ||
+ ((byte)OFPortReason.OFPPR_MODIFY.ordinal() ==
+ ps.getReason() && !portEnabled(ps.getDesc()))) {
+ deleteLinksOnPort(npt, "Port Status Changed");
+ LDUpdate update = new LDUpdate(sw, port, UpdateOperation.PORT_DOWN);
+ updates.add(update);
+ linkDeleted = true;
+ }
+ else if (ps.getReason() ==
+ (byte)OFPortReason.OFPPR_MODIFY.ordinal()) {
+ // If ps is a port modification and the port state has changed
+ // that affects links in the topology
+
+ if (this.portLinks.containsKey(npt)) {
+ for (Link lt: this.portLinks.get(npt)) {
+ LinkInfo linkInfo = links.get(lt);
+ assert(linkInfo != null);
+ Integer updatedSrcPortState = null;
+ Integer updatedDstPortState = null;
+ if (lt.getSrc() == npt.getNodeId() &&
+ lt.getSrcPort() == npt.getPortId() &&
+ (linkInfo.getSrcPortState() !=
+ ps.getDesc().getState())) {
+ updatedSrcPortState = ps.getDesc().getState();
+ linkInfo.setSrcPortState(updatedSrcPortState);
+ }
+ if (lt.getDst() == npt.getNodeId() &&
+ lt.getDstPort() == npt.getPortId() &&
+ (linkInfo.getDstPortState() !=
+ ps.getDesc().getState())) {
+ updatedDstPortState = ps.getDesc().getState();
+ linkInfo.setDstPortState(updatedDstPortState);
+ }
+ if ((updatedSrcPortState != null) ||
+ (updatedDstPortState != null)) {
+ // The link is already known to link discovery
+ // manager and the status has changed, therefore
+ // send an LDUpdate.
+ UpdateOperation operation =
+ getUpdateOperation(linkInfo.getSrcPortState(),
+ linkInfo.getDstPortState());
+ updates.add(new LDUpdate(lt.getSrc(), lt.getSrcPort(),
+ lt.getDst(), lt.getDstPort(),
+ getLinkType(lt, linkInfo),
+ operation));
+ writeLinkToStorage(lt, linkInfo);
+ linkInfoChanged = true;
+ }
+ }
+ }
+
+ UpdateOperation operation =
+ getUpdateOperation(ps.getDesc().getState());
+ updates.add(new LDUpdate(sw, port, operation));
+ }
+
+ if (!linkDeleted && !linkInfoChanged){
+ if (log.isTraceEnabled()) {
+ log.trace("handlePortStatus: Switch {} port #{} reason {};"+
+ " no links to update/remove",
+ new Object[] {HexString.toHexString(sw),
+ ps.getDesc().getPortNumber(),
+ ps.getReason()});
+ }
+ }
+ } finally {
+ lock.writeLock().unlock();
+ }
+
+ if (!linkDeleted) {
+ // Send LLDP right away when port state is changed for faster
+ // cluster-merge. If it is a link delete then there is not need
+ // to send the LLDPs right away and instead we wait for the LLDPs
+ // to be sent on the timer as it is normally done
+ // do it outside the write-lock
+ // sendLLDPTask.reschedule(1000, TimeUnit.MILLISECONDS);
+ processNewPort(npt.getNodeId(), npt.getPortId());
+ }
+ return Command.CONTINUE;
+ }
+
+ /**
+ * 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.
+ }
+ else if (autoPortFastFeature && isFastPort(sw, p)) {
+ // Do nothing as the port is a fast port.
+ }
+ else {
+ NodePortTuple npt = new NodePortTuple(sw, p);
+ discover(sw, p);
+ // if it is not a fast port, add it to quarantine.
+ if (!isFastPort(sw, p)) {
+ addToQuarantineQueue(npt);
+ } else {
+ // Add to maintenance queue to ensure that BDDP packets
+ // are sent out.
+ addToMaintenanceQueue(npt);
+ }
+ }
+ }
+
+ /**
+ * We send out LLDP messages when a switch is added to discover the topology
+ * @param sw The IOFSwitch that connected to the controller
+ */
+ @Override
+ public void addedSwitch(IOFSwitch sw) {
+
+ if (sw.getEnabledPorts() != null) {
+ for (Short p : sw.getEnabledPortNumbers()) {
+ processNewPort(sw.getId(), p);
+ }
+ }
+ // Update event history
+ evHistTopoSwitch(sw, EvAction.SWITCH_CONNECTED, "None");
+ LDUpdate update = new LDUpdate(sw.getId(), null,
+ UpdateOperation.SWITCH_UPDATED);
+ updates.add(update);
+ }
+
+ /**
+ * When a switch disconnects we remove any links from our map and notify.
+ * @param The id of the switch
+ */
+ @Override
+ public void removedSwitch(IOFSwitch iofSwitch) {
+ // Update event history
+ long sw = iofSwitch.getId();
+ evHistTopoSwitch(iofSwitch, EvAction.SWITCH_DISCONNECTED, "None");
+ 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));
+ }
+ // add all tuples with an endpoint on this switch to erase list
+ eraseList.addAll(switchLinks.get(sw));
+ deleteLinks(eraseList, "Switch Removed");
+
+ // Send a switch removed update
+ LDUpdate update = new LDUpdate(sw, null, UpdateOperation.SWITCH_REMOVED);
+ updates.add(update);
+ }
+ } finally {
+ lock.writeLock().unlock();
+ }
+ }
+
+ /**
+ * 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) {
+ // no-op
+ }
+
+ /**
+ * 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);
+
+ if (info.getMulticastValidTime() != null)
+ addLinkToBroadcastDomain(lt);
+ // Note that even if mTime becomes null later on,
+ // the link would be deleted, which would trigger updateClusters().
+ linkChanged = true;
+ }
+ if ((info.getMulticastValidTime()!= null) &&
+ (info.getMulticastValidTime()+ (this.LINK_TIMEOUT * 1000) < curTime)) {
+ info.setMulticastValidTime(null);
+ // if uTime is not null, then link will remain as openflow
+ // link. If uTime is null, it will be deleted. So, we
+ // don't care about linkChanged flag here.
+ removeLinkFromBroadcastDomain(lt);
+ 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) {
+ UpdateOperation operation;
+ operation = getUpdateOperation(info.getSrcPortState(),
+ info.getDstPortState());
+ updates.add(new LDUpdate(lt.getSrc(), lt.getSrcPort(),
+ lt.getDst(), lt.getDstPort(),
+ getLinkType(lt, info),
+ operation));
+ }
+ }
+
+ // if any link was deleted or any link was changed.
+ if ((eraseList.size() > 0) || linkChanged) {
+ deleteLinks(eraseList, "LLDP timeout");
+ }
+ } finally {
+ lock.writeLock().unlock();
+ }
+ }
+
+ private boolean portEnabled(OFPhysicalPort port) {
+ if (port == null)
+ return false;
+ if ((OFPortConfig.OFPPC_PORT_DOWN.getValue() & port.getConfig()) > 0)
+ return false;
+ if ((OFPortState.OFPPS_LINK_DOWN.getValue() & port.getState()) > 0)
+ return false;
+ // Port STP state doesn't work with multiple VLANs, so ignore it for now
+ // if ((port.getState() & OFPortState.OFPPS_STP_MASK.getValue()) == OFPortState.OFPPS_STP_BLOCK.getValue())
+ // return false;
+ return true;
+ }
+
+ public Map<NodePortTuple, Set<Link>> getPortBroadcastDomainLinks() {
+ return portBroadcastDomainLinks;
+ }
+
+ @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;
+ }
+
+ protected void addLinkToBroadcastDomain(Link lt) {
+
+ NodePortTuple srcNpt, dstNpt;
+ srcNpt = new NodePortTuple(lt.getSrc(), lt.getSrcPort());
+ dstNpt = new NodePortTuple(lt.getDst(), lt.getDstPort());
+
+ if (!portBroadcastDomainLinks.containsKey(lt.getSrc()))
+ portBroadcastDomainLinks.put(srcNpt, new HashSet<Link>());
+ portBroadcastDomainLinks.get(srcNpt).add(lt);
+
+ if (!portBroadcastDomainLinks.containsKey(lt.getDst()))
+ portBroadcastDomainLinks.put(dstNpt, new HashSet<Link>());
+ portBroadcastDomainLinks.get(dstNpt).add(lt);
+ }
+
+ protected void removeLinkFromBroadcastDomain(Link lt) {
+
+ NodePortTuple srcNpt, dstNpt;
+ srcNpt = new NodePortTuple(lt.getSrc(), lt.getSrcPort());
+ dstNpt = new NodePortTuple(lt.getDst(), lt.getDstPort());
+
+ if (portBroadcastDomainLinks.containsKey(srcNpt)) {
+ portBroadcastDomainLinks.get(srcNpt).remove(lt);
+ if (portBroadcastDomainLinks.get(srcNpt).isEmpty())
+ portBroadcastDomainLinks.remove(srcNpt);
+ }
+
+ if (portBroadcastDomainLinks.containsKey(dstNpt)) {
+ portBroadcastDomainLinks.get(dstNpt).remove(lt);
+ if (portBroadcastDomainLinks.get(dstNpt).isEmpty())
+ portBroadcastDomainLinks.remove(dstNpt);
+ }
+ }
+
+ // STORAGE METHODS
+ /**
+ * Deletes all links from storage
+ */
+ void clearAllLinks() {
+ storageSource.deleteRowsAsync(LINK_TABLE_NAME, null);
+ }
+
+ /**
+ * 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();
+ }
+
+ /**
+ * 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");
+
+ if (linkInfo.linkStpBlocked()) {
+ if (log.isTraceEnabled()) {
+ log.trace("writeLink, link {}, info {}, srcPortState Blocked",
+ lt, linkInfo);
+ }
+ rowValues.put(LINK_SRC_PORT_STATE,
+ OFPhysicalPort.OFPortState.OFPPS_STP_BLOCK.getValue());
+ } else {
+ if (log.isTraceEnabled()) {
+ log.trace("writeLink, link {}, info {}, srcPortState {}",
+ new Object[]{ lt, linkInfo, linkInfo.getSrcPortState() });
+ }
+ rowValues.put(LINK_SRC_PORT_STATE, linkInfo.getSrcPortState());
+ }
+ String dstDpid = HexString.toHexString(lt.getDst());
+ rowValues.put(LINK_DST_SWITCH, dstDpid);
+ rowValues.put(LINK_DST_PORT, lt.getDstPort());
+ if (linkInfo.linkStpBlocked()) {
+ if (log.isTraceEnabled()) {
+ log.trace("writeLink, link {}, info {}, dstPortState Blocked",
+ lt, linkInfo);
+ }
+ rowValues.put(LINK_DST_PORT_STATE,
+ OFPhysicalPort.OFPortState.OFPPS_STP_BLOCK.getValue());
+ } else {
+ if (log.isTraceEnabled()) {
+ log.trace("writeLink, link {}, info {}, dstPortState {}",
+ new Object[]{ lt, linkInfo, linkInfo.getDstPortState() });
+ }
+ rowValues.put(LINK_DST_PORT_STATE, linkInfo.getDstPortState());
+ }
+ storageSource.updateRowAsync(LINK_TABLE_NAME, rowValues);
+ }
+
+ 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;
+ }
+
+ /**
+ * 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);
+ }
+
+ @Override
+ public void addListener(ILinkDiscoveryListener listener) {
+ linkDiscoveryAware.add(listener);
+ }
+
+ /**
+ * Register a link discovery aware component
+ * @param linkDiscoveryAwareComponent
+ */
+ public void addLinkDiscoveryAware(ILinkDiscoveryListener linkDiscoveryAwareComponent) {
+ // TODO make this a copy on write set or lock it somehow
+ this.linkDiscoveryAware.add(linkDiscoveryAwareComponent);
+ }
+
+ /**
+ * Deregister a link discovery aware component
+ * @param linkDiscoveryAwareComponent
+ */
+ public void removeLinkDiscoveryAware(ILinkDiscoveryListener linkDiscoveryAwareComponent) {
+ // TODO make this a copy on write set or lock it somehow
+ this.linkDiscoveryAware.remove(linkDiscoveryAwareComponent);
+ }
+
+ /**
+ * Sets the IStorageSource to use for ITology
+ * @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 boolean isCallbackOrderingPrereq(OFType type, String name) {
+ return false;
+ }
+
+ @Override
+ public boolean isCallbackOrderingPostreq(OFType type, String name) {
+ return false;
+ }
+
+ @Override
+ public void rowsModified(String tableName, Set<Object> rowKeys) {
+ Map<Long, IOFSwitch> switches = floodlightProvider.getSwitches();
+ ArrayList<IOFSwitch> updated_switches = new ArrayList<IOFSwitch>();
+ for(Object key: rowKeys) {
+ Long swId = new Long(HexString.toLong((String)key));
+ if (switches.containsKey(swId)) {
+ IOFSwitch sw = switches.get(swId);
+ boolean curr_status = sw.hasAttribute(IOFSwitch.SWITCH_IS_CORE_SWITCH);
+ boolean new_status = false;
+ IResultSet resultSet = null;
+
+ try {
+ resultSet = storageSource.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();
+ if (row.containsKey(SWITCH_CONFIG_CORE_SWITCH)) {
+ new_status = ((String)row.get(SWITCH_CONFIG_CORE_SWITCH)).equals("true");
+ }
+ }
+ }
+ finally {
+ if (resultSet != null)
+ resultSet.close();
+ }
+
+ if (curr_status != new_status) {
+ updated_switches.add(sw);
+ }
+ } else {
+ if (log.isTraceEnabled()) {
+ log.trace("Update for switch which has no entry in switch " +
+ "list (dpid={}), a delete action.", (String)key);
+ }
+ }
+ }
+
+ for (IOFSwitch sw : updated_switches) {
+ // Set SWITCH_IS_CORE_SWITCH to it's inverse value
+ 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);
+ }
+ updates.add(new LDUpdate(sw.getId(), SwitchType.BASIC_SWITCH,
+ UpdateOperation.SWITCH_UPDATED));
+ }
+ else {
+ 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
+ }
+
+ // IFloodlightModule classes
+
+ @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);
+
+ // Set the autoportfast feature to false.
+ this.autoPortFastFeature = false;
+
+ // 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.portBroadcastDomainLinks = new HashMap<NodePortTuple, Set<Link>>();
+ this.switchLinks = new HashMap<Long, Set<Link>>();
+ this.quarantineQueue = new LinkedBlockingQueue<NodePortTuple>();
+ this.maintenanceQueue = new LinkedBlockingQueue<NodePortTuple>();
+
+ this.evHistTopologySwitch =
+ new EventHistory<EventHistoryTopologySwitch>("Topology: Switch");
+ this.evHistTopologyLink =
+ new EventHistory<EventHistoryTopologyLink>("Topology: Link");
+ this.evHistTopologyCluster =
+ new EventHistory<EventHistoryTopologyCluster>("Topology: Cluster");
+ }
+
+ @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) {
+ // Create our storage tables
+ if (storageSource == null) {
+ log.error("No storage source found.");
+ return;
+ }
+
+ 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);
+ } 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.
+ Role role = floodlightProvider.getRole();
+ 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.
+ Role role = floodlightProvider.getRole();
+ 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);
+ floodlightProvider.addInfoProvider("summary", this);
+ if (restApi != null)
+ restApi.addRestletRoutable(new LinkDiscoveryWebRoutable());
+ setControllerTLV();
+ }
+
+ // ****************************************************
+ // Topology Manager's Event History members and methods
+ // ****************************************************
+
+ // Topology Manager event history
+ public EventHistory<EventHistoryTopologySwitch> evHistTopologySwitch;
+ public EventHistory<EventHistoryTopologyLink> evHistTopologyLink;
+ public EventHistory<EventHistoryTopologyCluster> evHistTopologyCluster;
+ public EventHistoryTopologySwitch evTopoSwitch;
+ public EventHistoryTopologyLink evTopoLink;
+ public EventHistoryTopologyCluster evTopoCluster;
+
+ // Switch Added/Deleted
+ private void evHistTopoSwitch(IOFSwitch sw, EvAction actn, String reason) {
+ if (evTopoSwitch == null) {
+ evTopoSwitch = new EventHistoryTopologySwitch();
+ }
+ evTopoSwitch.dpid = sw.getId();
+ if ((sw.getChannel() != null) &&
+ (SocketAddress.class.isInstance(
+ sw.getChannel().getRemoteAddress()))) {
+ evTopoSwitch.ipv4Addr =
+ IPv4.toIPv4Address(((InetSocketAddress)(sw.getChannel().
+ getRemoteAddress())).getAddress().getAddress());
+ evTopoSwitch.l4Port =
+ ((InetSocketAddress)(sw.getChannel().
+ getRemoteAddress())).getPort();
+ } else {
+ evTopoSwitch.ipv4Addr = 0;
+ evTopoSwitch.l4Port = 0;
+ }
+ evTopoSwitch.reason = reason;
+ evTopoSwitch = evHistTopologySwitch.put(evTopoSwitch, actn);
+ }
+
+ private void evHistTopoLink(long srcDpid, long dstDpid, short srcPort,
+ short dstPort, int srcPortState, int dstPortState,
+ ILinkDiscovery.LinkType linkType,
+ EvAction actn, String reason) {
+ if (evTopoLink == null) {
+ evTopoLink = new EventHistoryTopologyLink();
+ }
+ evTopoLink.srcSwDpid = srcDpid;
+ evTopoLink.dstSwDpid = dstDpid;
+ evTopoLink.srcSwport = srcPort & 0xffff;
+ evTopoLink.dstSwport = dstPort & 0xffff;
+ evTopoLink.srcPortState = srcPortState;
+ evTopoLink.dstPortState = dstPortState;
+ evTopoLink.reason = reason;
+ switch (linkType) {
+ case DIRECT_LINK:
+ evTopoLink.linkType = "DIRECT_LINK";
+ break;
+ case MULTIHOP_LINK:
+ evTopoLink.linkType = "MULTIHOP_LINK";
+ break;
+ case TUNNEL:
+ evTopoLink.linkType = "TUNNEL";
+ break;
+ case INVALID_LINK:
+ default:
+ evTopoLink.linkType = "Unknown";
+ break;
+ }
+ evTopoLink = evHistTopologyLink.put(evTopoLink, actn);
+ }
+
+ public void evHistTopoCluster(long dpid, long clusterIdOld,
+ long clusterIdNew, EvAction action, String reason) {
+ if (evTopoCluster == null) {
+ evTopoCluster = new EventHistoryTopologyCluster();
+ }
+ evTopoCluster.dpid = dpid;
+ evTopoCluster.clusterIdOld = clusterIdOld;
+ evTopoCluster.clusterIdNew = clusterIdNew;
+ evTopoCluster.reason = reason;
+ evTopoCluster = evHistTopologyCluster.put(evTopoCluster, action);
+ }
+
+ @Override
+ public Map<String, Object> getInfo(String type) {
+ if (!"summary".equals(type)) return null;
+
+ Map<String, Object> info = new HashMap<String, Object>();
+
+ int num_links = 0;
+ for (Set<Link> links : switchLinks.values())
+ num_links += links.size();
+ info.put("# inter-switch links", num_links / 2);
+
+ return info;
+ }
+
+ // IHARoleListener
+ @Override
+ public void roleChanged(Role oldRole, Role newRole) {
+ switch(newRole) {
+ case MASTER:
+ if (oldRole == Role.SLAVE) {
+ if (log.isTraceEnabled()) {
+ log.trace("Sending LLDPs " +
+ "to HA change from SLAVE->MASTER");
+ }
+ clearAllLinks();
+ log.debug("Role Change to Master: Rescheduling discovery task.");
+ discoveryTask.reschedule(1, TimeUnit.MICROSECONDS);
+ }
+ break;
+ case SLAVE:
+ if (log.isTraceEnabled()) {
+ log.trace("Clearing links due to " +
+ "HA change to SLAVE");
+ }
+ switchLinks.clear();
+ links.clear();
+ portLinks.clear();
+ portBroadcastDomainLinks.clear();
+ discoverOnAllPorts();
+ break;
+ default:
+ break;
+ }
+ }
+
+ @Override
+ public void controllerNodeIPsChanged(
+ Map<String, String> curControllerNodeIPs,
+ Map<String, String> addedControllerNodeIPs,
+ Map<String, String> removedControllerNodeIPs) {
+ // ignore
+ }
+
+ public boolean isAutoPortFastFeature() {
+ return autoPortFastFeature;
+ }
+
+ public void setAutoPortFastFeature(boolean autoPortFastFeature) {
+ this.autoPortFastFeature = autoPortFastFeature;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/linkdiscovery/web/AutoPortFast.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/linkdiscovery/web/AutoPortFast.java
new file mode 100644
index 0000000..8f4f4ad
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/linkdiscovery/web/AutoPortFast.java
@@ -0,0 +1,31 @@
+package net.floodlightcontroller.linkdiscovery.web;
+
+import net.floodlightcontroller.linkdiscovery.ILinkDiscoveryService;
+
+import org.restlet.data.Status;
+import org.restlet.resource.Get;
+import org.restlet.resource.ServerResource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class AutoPortFast extends ServerResource {
+ protected static Logger log = LoggerFactory.getLogger(AutoPortFast.class);
+
+ @Get("json")
+ public String retrieve() {
+ ILinkDiscoveryService linkDiscovery;
+ linkDiscovery = (ILinkDiscoveryService)getContext().getAttributes().
+ get(ILinkDiscoveryService.class.getCanonicalName());
+
+ String param = ((String)getRequestAttributes().get("state")).toLowerCase();
+ if (param.equals("enable") || param.equals("true")) {
+ linkDiscovery.setAutoPortFastFeature(true);
+ } else if (param.equals("disable") || param.equals("false")) {
+ linkDiscovery.setAutoPortFastFeature(false);
+ }
+ setStatus(Status.SUCCESS_OK, "OK");
+ if (linkDiscovery.isAutoPortFastFeature())
+ return "enabled";
+ else return "disabled";
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/linkdiscovery/web/LinkDiscoveryWebRoutable.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/linkdiscovery/web/LinkDiscoveryWebRoutable.java
new file mode 100644
index 0000000..3990fba
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/linkdiscovery/web/LinkDiscoveryWebRoutable.java
@@ -0,0 +1,26 @@
+package net.floodlightcontroller.linkdiscovery.web;
+
+import net.floodlightcontroller.restserver.RestletRoutable;
+
+import org.restlet.Context;
+import org.restlet.routing.Router;
+
+public class LinkDiscoveryWebRoutable implements RestletRoutable {
+ /**
+ * Create the Restlet router and bind to the proper resources.
+ */
+ @Override
+ public Router getRestlet(Context context) {
+ Router router = new Router(context);
+ router.attach("/autoportfast/{state}/json", AutoPortFast.class); // enable/true or disable/false
+ return router;
+ }
+
+ /**
+ * Set the base path for the Topology
+ */
+ @Override
+ public String basePath() {
+ return "/wm/linkdiscovery";
+ }
+}
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/linkdiscovery/web/LinkWithType.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/linkdiscovery/web/LinkWithType.java
new file mode 100644
index 0000000..893e4ad
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/linkdiscovery/web/LinkWithType.java
@@ -0,0 +1,65 @@
+package net.floodlightcontroller.linkdiscovery.web;
+
+import java.io.IOException;
+
+import org.codehaus.jackson.JsonGenerator;
+import org.codehaus.jackson.JsonProcessingException;
+import org.codehaus.jackson.map.JsonSerializer;
+import org.codehaus.jackson.map.SerializerProvider;
+import org.codehaus.jackson.map.annotate.JsonSerialize;
+import org.openflow.util.HexString;
+
+import net.floodlightcontroller.linkdiscovery.ILinkDiscovery.LinkType;
+import net.floodlightcontroller.routing.Link;
+
+/**
+ * This class is both the datastructure and the serializer
+ * for a link with the corresponding type of link.
+ * @author alexreimers
+ */
+@JsonSerialize(using=LinkWithType.class)
+public class LinkWithType extends JsonSerializer<LinkWithType> {
+ public long srcSwDpid;
+ public short srcPort;
+ public int srcPortState;
+ public long dstSwDpid;
+ public short dstPort;
+ public int dstPortState;
+ public LinkType type;
+
+ // Do NOT delete this, it's required for the serializer
+ public LinkWithType() {}
+
+ public LinkWithType(Link link,
+ int srcPortState,
+ int dstPortState,
+ LinkType type) {
+ this.srcSwDpid = link.getSrc();
+ this.srcPort = link.getSrcPort();
+ this.srcPortState = srcPortState;
+ this.dstSwDpid = link.getDst();
+ this.dstPort = link.getDstPort();
+ this.dstPortState = dstPortState;
+ this.type = type;
+ }
+
+ @Override
+ public void serialize(LinkWithType lwt, JsonGenerator jgen, SerializerProvider arg2)
+ 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.writeNumberField("src-port-state", lwt.srcPortState);
+ jgen.writeStringField("dst-switch", HexString.toHexString(lwt.dstSwDpid));
+ jgen.writeNumberField("dst-port", lwt.dstPort);
+ jgen.writeNumberField("dst-port-state", lwt.dstPortState);
+ jgen.writeStringField("type", lwt.type.toString());
+ jgen.writeEndObject();
+ }
+
+ @Override
+ public Class<LinkWithType> handledType() {
+ return LinkWithType.class;
+ }
+}
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/linkdiscovery/web/LinksResource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/linkdiscovery/web/LinksResource.java
new file mode 100644
index 0000000..4cad18e
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/linkdiscovery/web/LinksResource.java
@@ -0,0 +1,37 @@
+package net.floodlightcontroller.linkdiscovery.web;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import net.floodlightcontroller.linkdiscovery.ILinkDiscoveryService;
+import net.floodlightcontroller.linkdiscovery.LinkInfo;
+import net.floodlightcontroller.routing.Link;
+
+import org.restlet.resource.Get;
+import org.restlet.resource.ServerResource;
+
+public class LinksResource extends ServerResource {
+
+ @Get("json")
+ public Set<LinkWithType> retrieve() {
+ ILinkDiscoveryService ld = (ILinkDiscoveryService)getContext().getAttributes().
+ get(ILinkDiscoveryService.class.getCanonicalName());
+ Map<Link, LinkInfo> links = new HashMap<Link, LinkInfo>();
+ Set<LinkWithType> returnLinkSet = new HashSet<LinkWithType>();
+
+ if (ld != null) {
+ links.putAll(ld.getLinks());
+ for (Link link: links.keySet()) {
+ LinkInfo info = links.get(link);
+ LinkWithType lwt = new LinkWithType(link,
+ info.getSrcPortState(),
+ info.getDstPortState(),
+ ld.getLinkType(link, info));
+ returnLinkSet.add(lwt);
+ }
+ }
+ return returnLinkSet;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/ARP.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/ARP.java
new file mode 100644
index 0000000..e8428ea
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/ARP.java
@@ -0,0 +1,316 @@
+/**
+* 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.packet;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+/**
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class ARP extends BasePacket {
+ public static short HW_TYPE_ETHERNET = 0x1;
+
+ public static short PROTO_TYPE_IP = 0x800;
+
+ public static short OP_REQUEST = 0x1;
+ public static short OP_REPLY = 0x2;
+ public static short OP_RARP_REQUEST = 0x3;
+ public static short OP_RARP_REPLY = 0x4;
+
+ protected short hardwareType;
+ protected short protocolType;
+ protected byte hardwareAddressLength;
+ protected byte protocolAddressLength;
+ protected short opCode;
+ protected byte[] senderHardwareAddress;
+ protected byte[] senderProtocolAddress;
+ protected byte[] targetHardwareAddress;
+ protected byte[] targetProtocolAddress;
+
+ /**
+ * @return the hardwareType
+ */
+ public short getHardwareType() {
+ return hardwareType;
+ }
+
+ /**
+ * @param hardwareType the hardwareType to set
+ */
+ public ARP setHardwareType(short hardwareType) {
+ this.hardwareType = hardwareType;
+ return this;
+ }
+
+ /**
+ * @return the protocolType
+ */
+ public short getProtocolType() {
+ return protocolType;
+ }
+
+ /**
+ * @param protocolType the protocolType to set
+ */
+ public ARP setProtocolType(short protocolType) {
+ this.protocolType = protocolType;
+ return this;
+ }
+
+ /**
+ * @return the hardwareAddressLength
+ */
+ public byte getHardwareAddressLength() {
+ return hardwareAddressLength;
+ }
+
+ /**
+ * @param hardwareAddressLength the hardwareAddressLength to set
+ */
+ public ARP setHardwareAddressLength(byte hardwareAddressLength) {
+ this.hardwareAddressLength = hardwareAddressLength;
+ return this;
+ }
+
+ /**
+ * @return the protocolAddressLength
+ */
+ public byte getProtocolAddressLength() {
+ return protocolAddressLength;
+ }
+
+ /**
+ * @param protocolAddressLength the protocolAddressLength to set
+ */
+ public ARP setProtocolAddressLength(byte protocolAddressLength) {
+ this.protocolAddressLength = protocolAddressLength;
+ return this;
+ }
+
+ /**
+ * @return the opCode
+ */
+ public short getOpCode() {
+ return opCode;
+ }
+
+ /**
+ * @param opCode the opCode to set
+ */
+ public ARP setOpCode(short opCode) {
+ this.opCode = opCode;
+ return this;
+ }
+
+ /**
+ * @return the senderHardwareAddress
+ */
+ public byte[] getSenderHardwareAddress() {
+ return senderHardwareAddress;
+ }
+
+ /**
+ * @param senderHardwareAddress the senderHardwareAddress to set
+ */
+ public ARP setSenderHardwareAddress(byte[] senderHardwareAddress) {
+ this.senderHardwareAddress = senderHardwareAddress;
+ return this;
+ }
+
+ /**
+ * @return the senderProtocolAddress
+ */
+ public byte[] getSenderProtocolAddress() {
+ return senderProtocolAddress;
+ }
+
+ /**
+ * @param senderProtocolAddress the senderProtocolAddress to set
+ */
+ public ARP setSenderProtocolAddress(byte[] senderProtocolAddress) {
+ this.senderProtocolAddress = senderProtocolAddress;
+ return this;
+ }
+
+ public ARP setSenderProtocolAddress(int address) {
+ this.senderProtocolAddress = ByteBuffer.allocate(4).putInt(address).array();
+ return this;
+ }
+
+ /**
+ * @return the targetHardwareAddress
+ */
+ public byte[] getTargetHardwareAddress() {
+ return targetHardwareAddress;
+ }
+
+ /**
+ * @param targetHardwareAddress the targetHardwareAddress to set
+ */
+ public ARP setTargetHardwareAddress(byte[] targetHardwareAddress) {
+ this.targetHardwareAddress = targetHardwareAddress;
+ return this;
+ }
+
+ /**
+ * @return the targetProtocolAddress
+ */
+ public byte[] getTargetProtocolAddress() {
+ return targetProtocolAddress;
+ }
+
+ /**
+ * @return True if gratuitous ARP (SPA = TPA), false otherwise
+ */
+ public boolean isGratuitous() {
+ assert(senderProtocolAddress.length == targetProtocolAddress.length);
+
+ int indx = 0;
+ while (indx < senderProtocolAddress.length) {
+ if (senderProtocolAddress[indx] != targetProtocolAddress[indx]) {
+ return false;
+ }
+ indx++;
+ }
+
+ return true;
+ }
+
+ /**
+ * @param targetProtocolAddress the targetProtocolAddress to set
+ */
+ public ARP setTargetProtocolAddress(byte[] targetProtocolAddress) {
+ this.targetProtocolAddress = targetProtocolAddress;
+ return this;
+ }
+
+ public ARP setTargetProtocolAddress(int address) {
+ this.targetProtocolAddress = ByteBuffer.allocate(4).putInt(address).array();
+ return this;
+ }
+
+ @Override
+ public byte[] serialize() {
+ int length = 8 + (2 * (0xff & this.hardwareAddressLength))
+ + (2 * (0xff & this.protocolAddressLength));
+ byte[] data = new byte[length];
+ ByteBuffer bb = ByteBuffer.wrap(data);
+ bb.putShort(this.hardwareType);
+ bb.putShort(this.protocolType);
+ bb.put(this.hardwareAddressLength);
+ bb.put(this.protocolAddressLength);
+ bb.putShort(this.opCode);
+ bb.put(this.senderHardwareAddress, 0, 0xff & this.hardwareAddressLength);
+ bb.put(this.senderProtocolAddress, 0, 0xff & this.protocolAddressLength);
+ bb.put(this.targetHardwareAddress, 0, 0xff & this.hardwareAddressLength);
+ bb.put(this.targetProtocolAddress, 0, 0xff & this.protocolAddressLength);
+ return data;
+ }
+
+ @Override
+ public IPacket deserialize(byte[] data, int offset, int length) {
+ ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
+ this.hardwareType = bb.getShort();
+ this.protocolType = bb.getShort();
+ this.hardwareAddressLength = bb.get();
+ this.protocolAddressLength = bb.get();
+ this.opCode = bb.getShort();
+ this.senderHardwareAddress = new byte[0xff & this.hardwareAddressLength];
+ bb.get(this.senderHardwareAddress, 0, this.senderHardwareAddress.length);
+ this.senderProtocolAddress = new byte[0xff & this.protocolAddressLength];
+ bb.get(this.senderProtocolAddress, 0, this.senderProtocolAddress.length);
+ this.targetHardwareAddress = new byte[0xff & this.hardwareAddressLength];
+ bb.get(this.targetHardwareAddress, 0, this.targetHardwareAddress.length);
+ this.targetProtocolAddress = new byte[0xff & this.protocolAddressLength];
+ bb.get(this.targetProtocolAddress, 0, this.targetProtocolAddress.length);
+ return this;
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ final int prime = 13121;
+ int result = super.hashCode();
+ result = prime * result + hardwareAddressLength;
+ result = prime * result + hardwareType;
+ result = prime * result + opCode;
+ result = prime * result + protocolAddressLength;
+ result = prime * result + protocolType;
+ result = prime * result + Arrays.hashCode(senderHardwareAddress);
+ result = prime * result + Arrays.hashCode(senderProtocolAddress);
+ result = prime * result + Arrays.hashCode(targetHardwareAddress);
+ result = prime * result + Arrays.hashCode(targetProtocolAddress);
+ return result;
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (!super.equals(obj))
+ return false;
+ if (!(obj instanceof ARP))
+ return false;
+ ARP other = (ARP) obj;
+ if (hardwareAddressLength != other.hardwareAddressLength)
+ return false;
+ if (hardwareType != other.hardwareType)
+ return false;
+ if (opCode != other.opCode)
+ return false;
+ if (protocolAddressLength != other.protocolAddressLength)
+ return false;
+ if (protocolType != other.protocolType)
+ return false;
+ if (!Arrays.equals(senderHardwareAddress, other.senderHardwareAddress))
+ return false;
+ if (!Arrays.equals(senderProtocolAddress, other.senderProtocolAddress))
+ return false;
+ if (!Arrays.equals(targetHardwareAddress, other.targetHardwareAddress))
+ return false;
+ if (!Arrays.equals(targetProtocolAddress, other.targetProtocolAddress))
+ return false;
+ return true;
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return "ARP [hardwareType=" + hardwareType + ", protocolType="
+ + protocolType + ", hardwareAddressLength="
+ + hardwareAddressLength + ", protocolAddressLength="
+ + protocolAddressLength + ", opCode=" + opCode
+ + ", senderHardwareAddress="
+ + Arrays.toString(senderHardwareAddress)
+ + ", senderProtocolAddress="
+ + Arrays.toString(senderProtocolAddress)
+ + ", targetHardwareAddress="
+ + Arrays.toString(targetHardwareAddress)
+ + ", targetProtocolAddress="
+ + Arrays.toString(targetProtocolAddress) + "]";
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/BPDU.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/BPDU.java
new file mode 100644
index 0000000..6c27216
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/BPDU.java
@@ -0,0 +1,138 @@
+/**
+* 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.packet;
+
+import java.nio.ByteBuffer;
+
+/**
+ * This class is a Rapid Spanning Tree Protocol
+ * Bridge Protocol Data Unit
+ * @author alexreimers
+ */
+public class BPDU extends BasePacket {
+ public enum BPDUType {
+ CONFIG,
+ TOPOLOGY_CHANGE;
+ }
+
+ private final long destMac = 0x0180c2000000L; // 01-80-c2-00-00-00
+
+ // TODO - check this for RSTP
+ private LLC llcHeader;
+ private short protocolId = 0;
+ private byte version = 0;
+ private byte type;
+ private byte flags;
+ private byte[] rootBridgeId;
+ private int rootPathCost;
+ private byte[] senderBridgeId; // switch cluster MAC
+ private short portId; // port it was transmitted from
+ private short messageAge; // 256ths of a second
+ private short maxAge; // 256ths of a second
+ private short helloTime; // 256ths of a second
+ private short forwardDelay; // 256ths of a second
+
+ public BPDU(BPDUType type) {
+ rootBridgeId = new byte[8];
+ senderBridgeId = new byte[8];
+
+ llcHeader = new LLC();
+ llcHeader.setDsap((byte) 0x42);
+ llcHeader.setSsap((byte) 0x42);
+ llcHeader.setCtrl((byte) 0x03);
+
+ switch(type) {
+ case CONFIG:
+ this.type = 0x0;
+ break;
+ case TOPOLOGY_CHANGE:
+ this.type = (byte) 0x80; // 1000 0000
+ break;
+ default:
+ this.type = 0;
+ break;
+ }
+ }
+
+ @Override
+ public byte[] serialize() {
+ byte[] data;
+ // TODO check these
+ if (type == 0x0) {
+ // config
+ data = new byte[38];
+ } else {
+ // topology change
+ data = new byte[7]; // LLC + TC notification
+ }
+
+ ByteBuffer bb = ByteBuffer.wrap(data);
+ // Serialize the LLC header
+ byte[] llc = llcHeader.serialize();
+ bb.put(llc, 0, llc.length);
+ bb.putShort(protocolId);
+ bb.put(version);
+ bb.put(type);
+
+ if (type == 0x0) {
+ bb.put(flags);
+ bb.put(rootBridgeId, 0, rootBridgeId.length);
+ bb.putInt(rootPathCost);
+ bb.put(senderBridgeId, 0, senderBridgeId.length);
+ bb.putShort(portId);
+ bb.putShort(messageAge);
+ bb.putShort(maxAge);
+ bb.putShort(helloTime);
+ bb.putShort(forwardDelay);
+ }
+
+ return data;
+ }
+
+ @Override
+ public IPacket deserialize(byte[] data, int offset, int length) {
+ ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
+
+ // LLC header
+ llcHeader.deserialize(data, offset, 3);
+
+ this.protocolId = bb.getShort();
+ this.version = bb.get();
+ this.type = bb.get();
+
+ // These fields only exist if it's a configuration BPDU
+ if (this.type == 0x0) {
+ this.flags = bb.get();
+ bb.get(rootBridgeId, 0, 6);
+ this.rootPathCost = bb.getInt();
+ bb.get(this.senderBridgeId, 0, 6);
+ this.portId = bb.getShort();
+ this.messageAge = bb.getShort();
+ this.maxAge = bb.getShort();
+ this.helloTime = bb.getShort();
+ this.forwardDelay = bb.getShort();
+ }
+ // TODO should we set other fields to 0?
+
+ return this;
+ }
+
+ public long getDestMac() {
+ return destMac;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/BSN.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/BSN.java
new file mode 100644
index 0000000..27c8f70
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/BSN.java
@@ -0,0 +1,172 @@
+/**
+* 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.packet;
+
+import java.nio.ByteBuffer;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author Shudong Zhou (shudong.zhou@bigswitch.com)
+ *
+ */
+public class BSN extends BasePacket {
+ public static final int BSN_MAGIC = 0x20000604;
+ public static final short BSN_VERSION_CURRENT = 0x0;
+ public static final short BSN_TYPE_PROBE = 0x1;
+ public static final short BSN_TYPE_BDDP = 0x2;
+ public static Map<Short, Class<? extends IPacket>> typeClassMap;
+
+ static {
+ typeClassMap = new HashMap<Short, Class<? extends IPacket>>();
+ typeClassMap.put(BSN_TYPE_PROBE, BSNPROBE.class);
+ typeClassMap.put(BSN_TYPE_BDDP, LLDP.class);
+ }
+
+ protected short type;
+ protected short version;
+
+ public BSN() {
+ version = BSN_VERSION_CURRENT;
+ }
+
+ public BSN(short type) {
+ this.type = type;
+ version = BSN_VERSION_CURRENT;
+ }
+
+ public short getType() {
+ return type;
+ }
+
+ public BSN setType(short type) {
+ this.type = type;
+ return this;
+ }
+
+ public short getVersion() {
+ return version;
+ }
+
+ public BSN setVersion(short version) {
+ this.version = version;
+ return this;
+ }
+
+ @Override
+ public byte[] serialize() {
+ short length = 4 /* magic */ + 2 /* type */ + 2 /* version */;
+
+ byte[] payloadData = null;
+ if (this.payload != null) {
+ payload.setParent(this);
+ payloadData = payload.serialize();
+ length += payloadData.length;
+ }
+
+ byte[] data = new byte[length];
+ ByteBuffer bb = ByteBuffer.wrap(data);
+ bb.putInt(BSN_MAGIC);
+ bb.putShort(this.type);
+ bb.putShort(this.version);
+ if (payloadData != null)
+ bb.put(payloadData);
+
+ if (this.parent != null && this.parent instanceof Ethernet)
+ ((Ethernet)this.parent).setEtherType(Ethernet.TYPE_BSN);
+
+ return data;
+ }
+
+ @Override
+ public IPacket deserialize(byte[] data, int offset, int length) {
+ ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
+
+ int magic = bb.getInt();
+ if (magic != BSN_MAGIC) {
+ throw new RuntimeException("Invalid BSN magic " + magic);
+ }
+
+ this.type = bb.getShort();
+ this.version = bb.getShort();
+ if (this.version != BSN_VERSION_CURRENT) {
+ throw new RuntimeException(
+ "Invalid BSN packet version " + this.version + ", should be "
+ + BSN_VERSION_CURRENT);
+ }
+
+ IPacket payload;
+ if (typeClassMap.containsKey(this.type)) {
+ Class<? extends IPacket> clazz = typeClassMap.get(this.type);
+ try {
+ payload = clazz.newInstance();
+ } catch (Exception e) {
+ throw new RuntimeException("Error parsing payload for BSN packet" + e);
+ }
+ } else {
+ payload = new Data();
+ }
+
+ this.payload = new Data();
+ this.payload = payload.deserialize(data, bb.position(), bb.limit() - bb.position());
+ this.payload.setParent(this);
+
+ return this;
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ final int prime = 883;
+ int result = super.hashCode();
+ result = prime * result + version;
+ result = prime * result + type;
+ return result;
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (!super.equals(obj))
+ return false;
+ if (!(obj instanceof BSN))
+ return false;
+ BSN other = (BSN) obj;
+ return (type == other.type &&
+ version == other.version);
+ }
+
+ public String toString() {
+ StringBuffer sb = new StringBuffer("\n");
+ sb.append("BSN packet");
+ if (typeClassMap.containsKey(this.type))
+ sb.append(" type: " + typeClassMap.get(this.type).getCanonicalName());
+ else
+ sb.append(" type: " + this.type);
+
+ return sb.toString();
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/BSNPROBE.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/BSNPROBE.java
new file mode 100644
index 0000000..720b45f
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/BSNPROBE.java
@@ -0,0 +1,197 @@
+/**
+* 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.packet;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import org.openflow.util.HexString;
+
+/**
+ * @author Shudong Zhou (shudong.zhou@bigswitch.com)
+ *
+ */
+public class BSNPROBE extends BasePacket {
+ protected long controllerId;
+ protected int sequenceId;
+ protected byte[] srcMac;
+ protected byte[] dstMac;
+ protected long srcSwDpid;
+ protected int srcPortNo;
+
+ public BSNPROBE() {
+ srcMac = new byte[6];
+ dstMac = new byte[6];
+ }
+
+
+ public long getControllerId() {
+ return this.controllerId;
+ }
+
+ public BSNPROBE setControllerId(long controllerId) {
+ this.controllerId = controllerId;
+ return this;
+ }
+
+ public int getSequenceId() {
+ return sequenceId;
+ }
+
+ public BSNPROBE setSequenceId(int sequenceId) {
+ this.sequenceId = sequenceId;
+ return this;
+ }
+
+ public byte[] getSrcMac() {
+ return this.srcMac;
+ }
+
+ public BSNPROBE setSrcMac(byte[] srcMac) {
+ this.srcMac = srcMac;
+ return this;
+ }
+
+ public byte[] getDstMac() {
+ return dstMac;
+ }
+
+ public BSNPROBE setDstMac(byte[] dstMac) {
+ this.dstMac = dstMac;
+ return this;
+ }
+
+ public long getSrcSwDpid() {
+ return srcSwDpid;
+ }
+
+ public BSNPROBE setSrcSwDpid(long srcSwDpid) {
+ this.srcSwDpid = srcSwDpid;
+ return this;
+ }
+
+ public int getSrcPortNo() {
+ return srcPortNo;
+ }
+
+ public BSNPROBE setSrcPortNo(int srcPortNo) {
+ this.srcPortNo = srcPortNo;
+ return this;
+ }
+
+ @Override
+ public byte[] serialize() {
+ short length = 8 /* controllerId */ + 4 /* seqId */
+ + 12 /* srcMac dstMac */ + 8 /* srcSwDpid */ + 4 /* srcPortNo */;
+
+ byte[] payloadData = null;
+ if (this.payload != null) {
+ payload.setParent(this);
+ payloadData = payload.serialize();
+ length += payloadData.length;
+ }
+
+ byte[] data = new byte[length];
+ ByteBuffer bb = ByteBuffer.wrap(data);
+ bb.putLong(this.controllerId);
+ bb.putInt(this.sequenceId);
+ bb.put(this.srcMac);
+ bb.put(this.dstMac);
+ bb.putLong(this.srcSwDpid);
+ bb.putInt(this.srcPortNo);
+ if (payloadData != null)
+ bb.put(payloadData);
+
+ if (this.parent != null && this.parent instanceof BSN)
+ ((BSN)this.parent).setType(BSN.BSN_TYPE_PROBE);
+
+ return data;
+ }
+
+ @Override
+ public IPacket deserialize(byte[] data, int offset, int length) {
+ ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
+
+ controllerId = bb.getLong();
+ sequenceId = bb.getInt();
+ bb.get(this.srcMac, 0, 6);
+ bb.get(this.dstMac, 0, 6);
+ this.srcSwDpid = bb.getLong();
+ this.srcPortNo = bb.getInt();
+
+ if (bb.hasRemaining()) {
+ this.payload = new Data();
+ this.payload = payload.deserialize(data, bb.position(), bb.limit() - bb.position());
+ this.payload.setParent(this);
+ }
+
+ return this;
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ final int prime = 883;
+ int result = super.hashCode();
+ result = prime * result + srcMac.hashCode();
+ result = prime * result + dstMac.hashCode();
+ result = prime * result + (int) (srcSwDpid >> 32) + (int) srcSwDpid;
+ result = prime * result + srcPortNo;
+ return result;
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (!super.equals(obj))
+ return false;
+ if (!(obj instanceof BSNPROBE))
+ return false;
+ BSNPROBE other = (BSNPROBE) obj;
+ if (!Arrays.equals(srcMac, other.srcMac))
+ return false;
+ if (!Arrays.equals(dstMac, other.dstMac))
+ return false;
+ return (sequenceId == other.sequenceId &&
+ srcSwDpid == other.srcSwDpid &&
+ srcPortNo == other.srcPortNo
+ );
+ }
+
+ public String toString() {
+ StringBuffer sb = new StringBuffer("\n");
+ sb.append("BSN Probe packet");
+ sb.append("\nSource Mac: ");
+ sb.append(HexString.toHexString(srcMac));
+ sb.append("\nDestination Mac: ");
+ sb.append(HexString.toHexString(dstMac));
+ sb.append("\nSource Switch: ");
+ sb.append(HexString.toHexString(srcSwDpid));
+ sb.append(" port: " + srcPortNo);
+ sb.append("\nSequence No.:" + sequenceId);
+
+ return sb.toString();
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/BasePacket.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/BasePacket.java
new file mode 100644
index 0000000..4ecfded
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/BasePacket.java
@@ -0,0 +1,116 @@
+/**
+* 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.packet;
+
+
+/**
+*
+* @author David Erickson (daviderickson@cs.stanford.edu)
+*/
+public abstract class BasePacket implements IPacket {
+ protected IPacket parent;
+ protected IPacket payload;
+
+ /**
+ * @return the parent
+ */
+ @Override
+ public IPacket getParent() {
+ return parent;
+ }
+
+ /**
+ * @param parent the parent to set
+ */
+ @Override
+ public IPacket setParent(IPacket parent) {
+ this.parent = parent;
+ return this;
+ }
+
+ /**
+ * @return the payload
+ */
+ @Override
+ public IPacket getPayload() {
+ return payload;
+ }
+
+ /**
+ * @param payload the payload to set
+ */
+ @Override
+ public IPacket setPayload(IPacket payload) {
+ this.payload = payload;
+ return this;
+ }
+
+ @Override
+ public void resetChecksum() {
+ if (this.parent != null)
+ this.parent.resetChecksum();
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ final int prime = 6733;
+ int result = 1;
+ result = prime * result + ((payload == null) ? 0 : payload.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 (!(obj instanceof BasePacket))
+ return false;
+ BasePacket other = (BasePacket) obj;
+ if (payload == null) {
+ if (other.payload != null)
+ return false;
+ } else if (!payload.equals(other.payload))
+ return false;
+ return true;
+ }
+
+ @Override
+ public Object clone() {
+ IPacket pkt;
+ try {
+ pkt = this.getClass().newInstance();
+ } catch (Exception e) {
+ throw new RuntimeException("Could not clone packet");
+ }
+ // TODO: we are using serialize()/deserialize() to perform the
+ // cloning. Not the most efficient way but simple. We can revisit
+ // if we hit performance problems.
+ byte[] data = this.serialize();
+ pkt.deserialize(this.serialize(), 0, data.length);
+ pkt.setParent(this.parent);
+ return pkt;
+ }
+}
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/DHCP.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/DHCP.java
new file mode 100644
index 0000000..f73d9c9
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/DHCP.java
@@ -0,0 +1,517 @@
+/**
+* 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.packet;
+
+import java.io.UnsupportedEncodingException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.ListIterator;
+
+/**
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class DHCP extends BasePacket {
+ /**
+ * ------------------------------------------
+ * |op (1) | htype(1) | hlen(1) | hops(1) |
+ * ------------------------------------------
+ * | xid (4) |
+ * ------------------------------------------
+ * | secs (2) | flags (2) |
+ * ------------------------------------------
+ * | ciaddr (4) |
+ * ------------------------------------------
+ * | yiaddr (4) |
+ * ------------------------------------------
+ * | siaddr (4) |
+ * ------------------------------------------
+ * | giaddr (4) |
+ * ------------------------------------------
+ * | chaddr (16) |
+ * ------------------------------------------
+ * | sname (64) |
+ * ------------------------------------------
+ * | file (128) |
+ * ------------------------------------------
+ * | options (312) |
+ * ------------------------------------------
+ *
+ */
+ // Header + magic without options
+ public static int MIN_HEADER_LENGTH = 240;
+ public static byte OPCODE_REQUEST = 0x1;
+ public static byte OPCODE_REPLY = 0x2;
+
+ public static byte HWTYPE_ETHERNET = 0x1;
+
+ public enum DHCPOptionCode {
+ OptionCode_SubnetMask ((byte)1),
+ OptionCode_RequestedIP ((byte)50),
+ OptionCode_LeaseTime ((byte)51),
+ OptionCode_MessageType ((byte)53),
+ OptionCode_DHCPServerIp ((byte)54),
+ OptionCode_RequestedParameters ((byte)55),
+ OptionCode_RenewalTime ((byte)58),
+ OPtionCode_RebindingTime ((byte)59),
+ OptionCode_ClientID ((byte)61),
+ OptionCode_END ((byte)255);
+
+ protected byte value;
+
+ private DHCPOptionCode(byte value) {
+ this.value = value;
+ }
+
+ public byte getValue() {
+ return value;
+ }
+ }
+
+ protected byte opCode;
+ protected byte hardwareType;
+ protected byte hardwareAddressLength;
+ protected byte hops;
+ protected int transactionId;
+ protected short seconds;
+ protected short flags;
+ protected int clientIPAddress;
+ protected int yourIPAddress;
+ protected int serverIPAddress;
+ protected int gatewayIPAddress;
+ protected byte[] clientHardwareAddress;
+ protected String serverName;
+ protected String bootFileName;
+ protected List<DHCPOption> options = new ArrayList<DHCPOption>();
+
+ /**
+ * @return the opCode
+ */
+ public byte getOpCode() {
+ return opCode;
+ }
+
+ /**
+ * @param opCode the opCode to set
+ */
+ public DHCP setOpCode(byte opCode) {
+ this.opCode = opCode;
+ return this;
+ }
+
+ /**
+ * @return the hardwareType
+ */
+ public byte getHardwareType() {
+ return hardwareType;
+ }
+
+ /**
+ * @param hardwareType the hardwareType to set
+ */
+ public DHCP setHardwareType(byte hardwareType) {
+ this.hardwareType = hardwareType;
+ return this;
+ }
+
+ /**
+ * @return the hardwareAddressLength
+ */
+ public byte getHardwareAddressLength() {
+ return hardwareAddressLength;
+ }
+
+ /**
+ * @param hardwareAddressLength the hardwareAddressLength to set
+ */
+ public DHCP setHardwareAddressLength(byte hardwareAddressLength) {
+ this.hardwareAddressLength = hardwareAddressLength;
+ return this;
+ }
+
+ /**
+ * @return the hops
+ */
+ public byte getHops() {
+ return hops;
+ }
+
+ /**
+ * @param hops the hops to set
+ */
+ public DHCP setHops(byte hops) {
+ this.hops = hops;
+ return this;
+ }
+
+ /**
+ * @return the transactionId
+ */
+ public int getTransactionId() {
+ return transactionId;
+ }
+
+ /**
+ * @param transactionId the transactionId to set
+ */
+ public DHCP setTransactionId(int transactionId) {
+ this.transactionId = transactionId;
+ return this;
+ }
+
+ /**
+ * @return the seconds
+ */
+ public short getSeconds() {
+ return seconds;
+ }
+
+ /**
+ * @param seconds the seconds to set
+ */
+ public DHCP setSeconds(short seconds) {
+ this.seconds = seconds;
+ return this;
+ }
+
+ /**
+ * @return the flags
+ */
+ public short getFlags() {
+ return flags;
+ }
+
+ /**
+ * @param flags the flags to set
+ */
+ public DHCP setFlags(short flags) {
+ this.flags = flags;
+ return this;
+ }
+
+ /**
+ * @return the clientIPAddress
+ */
+ public int getClientIPAddress() {
+ return clientIPAddress;
+ }
+
+ /**
+ * @param clientIPAddress the clientIPAddress to set
+ */
+ public DHCP setClientIPAddress(int clientIPAddress) {
+ this.clientIPAddress = clientIPAddress;
+ return this;
+ }
+
+ /**
+ * @return the yourIPAddress
+ */
+ public int getYourIPAddress() {
+ return yourIPAddress;
+ }
+
+ /**
+ * @param yourIPAddress the yourIPAddress to set
+ */
+ public DHCP setYourIPAddress(int yourIPAddress) {
+ this.yourIPAddress = yourIPAddress;
+ return this;
+ }
+
+ /**
+ * @return the serverIPAddress
+ */
+ public int getServerIPAddress() {
+ return serverIPAddress;
+ }
+
+ /**
+ * @param serverIPAddress the serverIPAddress to set
+ */
+ public DHCP setServerIPAddress(int serverIPAddress) {
+ this.serverIPAddress = serverIPAddress;
+ return this;
+ }
+
+ /**
+ * @return the gatewayIPAddress
+ */
+ public int getGatewayIPAddress() {
+ return gatewayIPAddress;
+ }
+
+ /**
+ * @param gatewayIPAddress the gatewayIPAddress to set
+ */
+ public DHCP setGatewayIPAddress(int gatewayIPAddress) {
+ this.gatewayIPAddress = gatewayIPAddress;
+ return this;
+ }
+
+ /**
+ * @return the clientHardwareAddress
+ */
+ public byte[] getClientHardwareAddress() {
+ return clientHardwareAddress;
+ }
+
+ /**
+ * @param clientHardwareAddress the clientHardwareAddress to set
+ */
+ public DHCP setClientHardwareAddress(byte[] clientHardwareAddress) {
+ this.clientHardwareAddress = clientHardwareAddress;
+ return this;
+ }
+
+ /**
+ * Gets a specific DHCP option parameter
+ * @param opetionCode The option code to get
+ * @return The value of the option if it exists, null otherwise
+ */
+ public DHCPOption getOption(DHCPOptionCode optionCode) {
+ for (DHCPOption opt : options) {
+ if (opt.code == optionCode.value)
+ return opt;
+ }
+ return null;
+ }
+
+ /**
+ * @return the options
+ */
+ public List<DHCPOption> getOptions() {
+ return options;
+ }
+
+ /**
+ * @param options the options to set
+ */
+ public DHCP setOptions(List<DHCPOption> options) {
+ this.options = options;
+ return this;
+ }
+
+ /**
+ * @return the packetType base on option 53
+ */
+ public DHCPPacketType getPacketType() {
+ ListIterator<DHCPOption> lit = options.listIterator();
+ while (lit.hasNext()) {
+ DHCPOption option = lit.next();
+ // only care option 53
+ if (option.getCode() == 53) {
+ return DHCPPacketType.getType(option.getData()[0]);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * @return the serverName
+ */
+ public String getServerName() {
+ return serverName;
+ }
+
+ /**
+ * @param serverName the serverName to set
+ */
+ public DHCP setServerName(String serverName) {
+ this.serverName = serverName;
+ return this;
+ }
+
+ /**
+ * @return the bootFileName
+ */
+ public String getBootFileName() {
+ return bootFileName;
+ }
+
+ /**
+ * @param bootFileName the bootFileName to set
+ */
+ public DHCP setBootFileName(String bootFileName) {
+ this.bootFileName = bootFileName;
+ return this;
+ }
+
+ @Override
+ public byte[] serialize() {
+ // not guaranteed to retain length/exact format
+ resetChecksum();
+
+ // minimum size 240 including magic cookie, options generally padded to 300
+ int optionsLength = 0;
+ for (DHCPOption option : this.options) {
+ if (option.getCode() == 0 || option.getCode() == 255) {
+ optionsLength += 1;
+ } else {
+ optionsLength += 2 + (int)(0xff & option.getLength());
+ }
+ }
+ int optionsPadLength = 0;
+ if (optionsLength < 60)
+ optionsPadLength = 60 - optionsLength;
+
+ byte[] data = new byte[240+optionsLength+optionsPadLength];
+ ByteBuffer bb = ByteBuffer.wrap(data);
+ bb.put(this.opCode);
+ bb.put(this.hardwareType);
+ bb.put(this.hardwareAddressLength);
+ bb.put(this.hops);
+ bb.putInt(this.transactionId);
+ bb.putShort(this.seconds);
+ bb.putShort(this.flags);
+ bb.putInt(this.clientIPAddress);
+ bb.putInt(this.yourIPAddress);
+ bb.putInt(this.serverIPAddress);
+ bb.putInt(this.gatewayIPAddress);
+ bb.put(this.clientHardwareAddress);
+ if (this.clientHardwareAddress.length < 16) {
+ for (int i = 0; i < (16 - this.clientHardwareAddress.length); ++i) {
+ bb.put((byte) 0x0);
+ }
+ }
+ writeString(this.serverName, bb, 64);
+ writeString(this.bootFileName, bb, 128);
+ // magic cookie
+ bb.put((byte) 0x63);
+ bb.put((byte) 0x82);
+ bb.put((byte) 0x53);
+ bb.put((byte) 0x63);
+ for (DHCPOption option : this.options) {
+ int code = option.getCode() & 0xff;
+ bb.put((byte) code);
+ if ((code != 0) && (code != 255)) {
+ bb.put(option.getLength());
+ bb.put(option.getData());
+ }
+ }
+ // assume the rest is padded out with zeroes
+ return data;
+ }
+
+ protected void writeString(String string, ByteBuffer bb, int maxLength) {
+ if (string == null) {
+ for (int i = 0; i < maxLength; ++i) {
+ bb.put((byte) 0x0);
+ }
+ } else {
+ byte[] bytes = null;
+ try {
+ bytes = string.getBytes("ascii");
+ } catch (UnsupportedEncodingException e) {
+ throw new RuntimeException("Failure encoding server name", e);
+ }
+ int writeLength = bytes.length;
+ if (writeLength > maxLength) {
+ writeLength = maxLength;
+ }
+ bb.put(bytes, 0, writeLength);
+ for (int i = writeLength; i < maxLength; ++i) {
+ bb.put((byte) 0x0);
+ }
+ }
+ }
+
+ @Override
+ public IPacket deserialize(byte[] data, int offset, int length) {
+ ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
+ if (bb.remaining() < MIN_HEADER_LENGTH) {
+ return this;
+ }
+
+ this.opCode = bb.get();
+ this.hardwareType = bb.get();
+ this.hardwareAddressLength = bb.get();
+ this.hops = bb.get();
+ this.transactionId = bb.getInt();
+ this.seconds = bb.getShort();
+ this.flags = bb.getShort();
+ this.clientIPAddress = bb.getInt();
+ this.yourIPAddress = bb.getInt();
+ this.serverIPAddress = bb.getInt();
+ this.gatewayIPAddress = bb.getInt();
+ int hardwareAddressLength = 0xff & this.hardwareAddressLength;
+ this.clientHardwareAddress = new byte[hardwareAddressLength];
+
+ bb.get(this.clientHardwareAddress);
+ for (int i = hardwareAddressLength; i < 16; ++i)
+ bb.get();
+ this.serverName = readString(bb, 64);
+ this.bootFileName = readString(bb, 128);
+ // read the magic cookie
+ // magic cookie
+ bb.get();
+ bb.get();
+ bb.get();
+ bb.get();
+ // read options
+ while (bb.hasRemaining()) {
+ DHCPOption option = new DHCPOption();
+ int code = 0xff & bb.get(); // convert signed byte to int in range [0,255]
+ option.setCode((byte) code);
+ if (code == 0) {
+ // skip these
+ continue;
+ } else if (code != 255) {
+ if (bb.hasRemaining()) {
+ int l = 0xff & bb.get(); // convert signed byte to int in range [0,255]
+ option.setLength((byte) l);
+ if (bb.remaining() >= l) {
+ byte[] optionData = new byte[l];
+ bb.get(optionData);
+ option.setData(optionData);
+ } else {
+ // Skip the invalid option and set the END option
+ code = 0xff;
+ option.setCode((byte)code);
+ option.setLength((byte) 0);
+ }
+ } else {
+ // Skip the invalid option and set the END option
+ code = 0xff;
+ option.setCode((byte)code);
+ option.setLength((byte) 0);
+ }
+ }
+ this.options.add(option);
+ if (code == 255) {
+ // remaining bytes are supposed to be 0, but ignore them just in case
+ break;
+ }
+ }
+
+ return this;
+ }
+
+ protected String readString(ByteBuffer bb, int maxLength) {
+ byte[] bytes = new byte[maxLength];
+ bb.get(bytes);
+ String result = null;
+ try {
+ result = new String(bytes, "ascii").trim();
+ } catch (UnsupportedEncodingException e) {
+ throw new RuntimeException("Failure decoding string", e);
+ }
+ return result;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/DHCPOption.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/DHCPOption.java
new file mode 100644
index 0000000..1fcc324
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/DHCPOption.java
@@ -0,0 +1,118 @@
+/**
+* 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.packet;
+
+import java.util.Arrays;
+
+/**
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class DHCPOption {
+ protected byte code;
+ protected byte length;
+ protected byte[] data;
+
+ /**
+ * @return the code
+ */
+ public byte getCode() {
+ return code;
+ }
+
+ /**
+ * @param code the code to set
+ */
+ public DHCPOption setCode(byte code) {
+ this.code = code;
+ return this;
+ }
+
+ /**
+ * @return the length
+ */
+ public byte getLength() {
+ return length;
+ }
+
+ /**
+ * @param length the length to set
+ */
+ public DHCPOption setLength(byte length) {
+ this.length = length;
+ return this;
+ }
+
+ /**
+ * @return the data
+ */
+ public byte[] getData() {
+ return data;
+ }
+
+ /**
+ * @param data the data to set
+ */
+ public DHCPOption setData(byte[] data) {
+ this.data = data;
+ return this;
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + code;
+ result = prime * result + Arrays.hashCode(data);
+ result = prime * result + length;
+ 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 (!(obj instanceof DHCPOption))
+ return false;
+ DHCPOption other = (DHCPOption) obj;
+ if (code != other.code)
+ return false;
+ if (!Arrays.equals(data, other.data))
+ return false;
+ if (length != other.length)
+ return false;
+ return true;
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return "DHCPOption [code=" + code + ", length=" + length + ", data="
+ + Arrays.toString(data) + "]";
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/DHCPPacketType.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/DHCPPacketType.java
new file mode 100644
index 0000000..3417a18
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/DHCPPacketType.java
@@ -0,0 +1,116 @@
+/**
+* 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.packet;
+
+public enum DHCPPacketType {
+ // From RFC 1533
+ DHCPDISCOVER (1),
+ DHCPOFFER (2),
+ DHCPREQUEST (3),
+ DHCPDECLINE (4),
+ DHCPACK (5),
+ DHCPNAK (6),
+ DHCPRELEASE (7),
+
+ // From RFC2132
+ DHCPINFORM (8),
+
+ // From RFC3203
+ DHCPFORCERENEW (9),
+
+ // From RFC4388
+ DHCPLEASEQUERY (10),
+ DHCPLEASEUNASSIGNED (11),
+ DHCPLEASEUNKNOWN (12),
+ DHCPLEASEACTIVE (13);
+
+ protected int value;
+
+ private DHCPPacketType(int value) {
+ this.value = value;
+ }
+
+ public int getValue() {
+ return value;
+ }
+
+ public String toString(){
+ switch (value) {
+ case 1:
+ return "DHCPDISCOVER";
+ case 2:
+ return "DHCPOFFER";
+ case 3:
+ return "DHCPREQUEST";
+ case 4:
+ return "DHCPDECLINE";
+ case 5:
+ return "DHCPACK";
+ case 6:
+ return "DHCPNAK";
+ case 7:
+ return "DHCPRELEASE";
+ case 8:
+ return "DHCPINFORM";
+ case 9:
+ return "DHCPFORCERENEW";
+ case 10:
+ return "DHCPLEASEQUERY";
+ case 11:
+ return "DHCPLEASEUNASSIGNED";
+ case 12:
+ return "DHCPLEASEUNKNOWN";
+ case 13:
+ return "DHCPLEASEACTIVE";
+ }
+
+ return null;
+ }
+ public static DHCPPacketType getType(int value) {
+ switch (value) {
+ case 1:
+ return DHCPDISCOVER;
+ case 2:
+ return DHCPOFFER;
+ case 3:
+ return DHCPREQUEST;
+ case 4:
+ return DHCPDECLINE;
+ case 5:
+ return DHCPACK;
+ case 6:
+ return DHCPNAK;
+ case 7:
+ return DHCPRELEASE;
+ case 8:
+ return DHCPINFORM;
+ case 9:
+ return DHCPFORCERENEW;
+ case 10:
+ return DHCPLEASEQUERY;
+ case 11:
+ return DHCPLEASEUNASSIGNED;
+ case 12:
+ return DHCPLEASEUNKNOWN;
+ case 13:
+ return DHCPLEASEACTIVE;
+ }
+
+ return null;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/Data.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/Data.java
new file mode 100644
index 0000000..47762da
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/Data.java
@@ -0,0 +1,94 @@
+/**
+* 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.packet;
+
+import java.util.Arrays;
+
+/**
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class Data extends BasePacket {
+ protected byte[] data;
+
+ /**
+ *
+ */
+ public Data() {
+ }
+
+ /**
+ * @param data
+ */
+ public Data(byte[] data) {
+ this.data = data;
+ }
+
+ /**
+ * @return the data
+ */
+ public byte[] getData() {
+ return data;
+ }
+
+ /**
+ * @param data the data to set
+ */
+ public Data setData(byte[] data) {
+ this.data = data;
+ return this;
+ }
+
+ public byte[] serialize() {
+ return this.data;
+ }
+
+ @Override
+ public IPacket deserialize(byte[] data, int offset, int length) {
+ this.data = Arrays.copyOfRange(data, offset, data.length);
+ return this;
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ final int prime = 1571;
+ int result = super.hashCode();
+ result = prime * result + Arrays.hashCode(data);
+ return result;
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (!super.equals(obj))
+ return false;
+ if (!(obj instanceof Data))
+ return false;
+ Data other = (Data) obj;
+ if (!Arrays.equals(data, other.data))
+ return false;
+ return true;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/Ethernet.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/Ethernet.java
new file mode 100644
index 0000000..6bd627b
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/Ethernet.java
@@ -0,0 +1,468 @@
+/**
+* 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.packet;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+import net.floodlightcontroller.util.MACAddress;
+import org.openflow.util.HexString;
+
+/**
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class Ethernet extends BasePacket {
+ private static String HEXES = "0123456789ABCDEF";
+ public static final short TYPE_ARP = 0x0806;
+ public static final short TYPE_RARP = (short) 0x8035;
+ 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 DATALAYER_ADDRESS_LENGTH = 6; // bytes
+ public static Map<Short, Class<? extends IPacket>> etherTypeClassMap;
+
+ static {
+ etherTypeClassMap = new HashMap<Short, Class<? extends IPacket>>();
+ etherTypeClassMap.put(TYPE_ARP, ARP.class);
+ etherTypeClassMap.put(TYPE_RARP, ARP.class);
+ etherTypeClassMap.put(TYPE_IPv4, IPv4.class);
+ etherTypeClassMap.put(TYPE_LLDP, LLDP.class);
+ etherTypeClassMap.put(TYPE_BSN, BSN.class);
+ }
+
+ protected MACAddress destinationMACAddress;
+ protected MACAddress sourceMACAddress;
+ protected byte priorityCode;
+ protected short vlanID;
+ protected short etherType;
+ protected boolean pad = false;
+
+ /**
+ * By default, set Ethernet to untagged
+ */
+ public Ethernet() {
+ super();
+ 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() {
+ return destinationMACAddress;
+ }
+
+ /**
+ * @param destinationMACAddress the destination MAC to set
+ */
+ public Ethernet setDestinationMACAddress(byte[] destinationMACAddress) {
+ this.destinationMACAddress = MACAddress.valueOf(destinationMACAddress);
+ return this;
+ }
+
+ /**
+ * @param destinationMACAddress the destination MAC to set
+ */
+ public Ethernet setDestinationMACAddress(String destinationMACAddress) {
+ this.destinationMACAddress = MACAddress.valueOf(destinationMACAddress);
+ return this;
+ }
+
+ /**
+ * @return the source MACAddress as a byte array
+ */
+ public byte[] getSourceMACAddress() {
+ return sourceMACAddress.toBytes();
+ }
+
+ /**
+ * @return the source MACAddress
+ */
+ public MACAddress getSourceMAC() {
+ return sourceMACAddress;
+ }
+
+ /**
+ * @param sourceMACAddress the source MAC to set
+ */
+ public Ethernet setSourceMACAddress(byte[] sourceMACAddress) {
+ this.sourceMACAddress = MACAddress.valueOf(sourceMACAddress);
+ return this;
+ }
+
+ /**
+ * @param sourceMACAddress the source MAC to set
+ */
+ public Ethernet setSourceMACAddress(String sourceMACAddress) {
+ this.sourceMACAddress = MACAddress.valueOf(sourceMACAddress);
+ return this;
+ }
+
+ /**
+ * @return the priorityCode
+ */
+ public byte getPriorityCode() {
+ return priorityCode;
+ }
+
+ /**
+ * @param priorityCode the priorityCode to set
+ */
+ public Ethernet setPriorityCode(byte priorityCode) {
+ this.priorityCode = priorityCode;
+ return this;
+ }
+
+ /**
+ * @return the vlanID
+ */
+ public short getVlanID() {
+ return vlanID;
+ }
+
+ /**
+ * @param vlanID the vlanID to set
+ */
+ public Ethernet setVlanID(short vlanID) {
+ this.vlanID = vlanID;
+ return this;
+ }
+
+ /**
+ * @return the etherType
+ */
+ public short getEtherType() {
+ return etherType;
+ }
+
+ /**
+ * @param etherType the etherType to set
+ */
+ public Ethernet setEtherType(short etherType) {
+ this.etherType = etherType;
+ return this;
+ }
+
+ /**
+ * @return True if the Ethernet frame is broadcast, false otherwise
+ */
+ public boolean isBroadcast() {
+ assert(destinationMACAddress.length() == 6);
+ return destinationMACAddress.isBroadcast();
+ }
+
+ /**
+ * @return True is the Ethernet frame is multicast, False otherwise
+ */
+ public boolean isMulticast() {
+ return destinationMACAddress.isMulticast();
+ }
+ /**
+ * Pad this packet to 60 bytes minimum, filling with zeros?
+ * @return the pad
+ */
+ public boolean isPad() {
+ return pad;
+ }
+
+ /**
+ * Pad this packet to 60 bytes minimum, filling with zeros?
+ * @param pad the pad to set
+ */
+ public Ethernet setPad(boolean pad) {
+ this.pad = pad;
+ return this;
+ }
+
+ public byte[] serialize() {
+ byte[] payloadData = null;
+ if (payload != null) {
+ payload.setParent(this);
+ payloadData = payload.serialize();
+ }
+ int length = 14 + ((vlanID == VLAN_UNTAGGED) ? 0 : 4) +
+ ((payloadData == null) ? 0 : payloadData.length);
+ if (pad && length < 60) {
+ length = 60;
+ }
+ byte[] data = new byte[length];
+ ByteBuffer bb = ByteBuffer.wrap(data);
+ bb.put(destinationMACAddress.toBytes());
+ bb.put(sourceMACAddress.toBytes());
+ if (vlanID != VLAN_UNTAGGED) {
+ bb.putShort((short) 0x8100);
+ bb.putShort((short) ((priorityCode << 13) | (vlanID & 0x0fff)));
+ }
+ bb.putShort(etherType);
+ if (payloadData != null)
+ bb.put(payloadData);
+ if (pad) {
+ Arrays.fill(data, bb.position(), data.length, (byte)0x0);
+ }
+ return data;
+ }
+
+ @Override
+ public IPacket deserialize(byte[] data, int offset, int length) {
+ if (length <= 0)
+ 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];
+ bb.get(dstAddr);
+ this.destinationMACAddress = MACAddress.valueOf(dstAddr);
+
+ if (this.sourceMACAddress == null)
+ this.sourceMACAddress = MACAddress.valueOf(new byte[6]);
+ byte[] srcAddr = new byte[MACAddress.MAC_ADDRESS_LENGTH];
+ bb.get(srcAddr);
+ this.sourceMACAddress = MACAddress.valueOf(srcAddr);
+
+ short etherType = bb.getShort();
+ if (etherType == (short) 0x8100) {
+ short tci = bb.getShort();
+ this.priorityCode = (byte) ((tci >> 13) & 0x07);
+ this.vlanID = (short) (tci & 0x0fff);
+ etherType = bb.getShort();
+ } else {
+ this.vlanID = VLAN_UNTAGGED;
+ }
+ this.etherType = etherType;
+
+ IPacket payload;
+ if (Ethernet.etherTypeClassMap.containsKey(this.etherType)) {
+ Class<? extends IPacket> clazz = Ethernet.etherTypeClassMap.get(this.etherType);
+ try {
+ payload = clazz.newInstance();
+ } catch (Exception e) {
+ throw new RuntimeException("Error parsing payload for Ethernet packet", e);
+ }
+ } else {
+ payload = new Data();
+ }
+ this.payload = payload.deserialize(data, bb.position(), bb.limit()-bb.position());
+ this.payload.setParent(this);
+ return this;
+ }
+
+ /**
+ * Checks to see if a string is a valid MAC address.
+ * @param macAddress
+ * @return True if macAddress is a valid MAC, False otherwise
+ */
+ public static boolean isMACAddress(String macAddress) {
+ String[] macBytes = macAddress.split(":");
+ if (macBytes.length != 6)
+ return false;
+ for (int i = 0; i < 6; ++i) {
+ if (HEXES.indexOf(macBytes[i].toUpperCase().charAt(0)) == -1 ||
+ HEXES.indexOf(macBytes[i].toUpperCase().charAt(1)) == -1) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Accepts a MAC address of the form 00:aa:11:bb:22:cc, case does not
+ * matter, and returns a corresponding byte[].
+ * @param macAddress The MAC address to convert into a bye array
+ * @return The macAddress as a byte array
+ */
+ public static byte[] toMACAddress(String macAddress) {
+ return MACAddress.valueOf(macAddress).toBytes();
+ }
+
+
+ /**
+ * Accepts a MAC address and returns the corresponding long, where the
+ * MAC bytes are set on the lower order bytes of the long.
+ * @param macAddress
+ * @return a long containing the mac address bytes
+ */
+ public static long toLong(byte[] macAddress) {
+ return MACAddress.valueOf(macAddress).toLong();
+ }
+
+ /**
+ * Convert a long MAC address to a byte array
+ * @param macAddress
+ * @return the bytes of the mac address
+ */
+ public static byte[] toByteArray(long macAddress) {
+ return MACAddress.valueOf(macAddress).toBytes();
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ final int prime = 7867;
+ int result = super.hashCode();
+ result = prime * result + destinationMACAddress.hashCode();
+ result = prime * result + etherType;
+ result = prime * result + vlanID;
+ result = prime * result + priorityCode;
+ result = prime * result + (pad ? 1231 : 1237);
+ result = prime * result + sourceMACAddress.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 (!super.equals(obj))
+ return false;
+ if (!(obj instanceof Ethernet))
+ return false;
+ Ethernet other = (Ethernet) obj;
+ if (!destinationMACAddress.equals(other.destinationMACAddress))
+ return false;
+ if (priorityCode != other.priorityCode)
+ return false;
+ if (vlanID != other.vlanID)
+ return false;
+ if (etherType != other.etherType)
+ return false;
+ if (pad != other.pad)
+ return false;
+ if (!sourceMACAddress.equals(other.sourceMACAddress))
+ return false;
+ return true;
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#toString(java.lang.Object)
+ */
+ @Override
+ public String toString() {
+
+ StringBuffer sb = new StringBuffer("\n");
+
+ IPacket pkt = (IPacket) this.getPayload();
+
+ if (pkt instanceof ARP)
+ sb.append("arp");
+ else if (pkt instanceof LLDP)
+ sb.append("lldp");
+ else if (pkt instanceof ICMP)
+ sb.append("icmp");
+ else if (pkt instanceof IPv4)
+ sb.append("ip");
+ else if (pkt instanceof DHCP)
+ sb.append("dhcp");
+ else sb.append(this.getEtherType());
+
+ sb.append("\ndl_vlan: ");
+ if (this.getVlanID() == Ethernet.VLAN_UNTAGGED)
+ sb.append("untagged");
+ else
+ sb.append(this.getVlanID());
+ sb.append("\ndl_vlan_pcp: ");
+ sb.append(this.getPriorityCode());
+ sb.append("\ndl_src: ");
+ sb.append(HexString.toHexString(this.getSourceMACAddress()));
+ sb.append("\ndl_dst: ");
+ sb.append(HexString.toHexString(this.getDestinationMACAddress()));
+
+
+ if (pkt instanceof ARP) {
+ ARP p = (ARP) pkt;
+ sb.append("\nnw_src: ");
+ sb.append(IPv4.fromIPv4Address(IPv4.toIPv4Address(p.getSenderProtocolAddress())));
+ sb.append("\nnw_dst: ");
+ sb.append(IPv4.fromIPv4Address(IPv4.toIPv4Address(p.getTargetProtocolAddress())));
+ }
+ else if (pkt instanceof LLDP) {
+ sb.append("lldp packet");
+ }
+ else if (pkt instanceof ICMP) {
+ ICMP icmp = (ICMP) pkt;
+ sb.append("\nicmp_type: ");
+ sb.append(icmp.getIcmpType());
+ sb.append("\nicmp_code: ");
+ sb.append(icmp.getIcmpCode());
+ }
+ else if (pkt instanceof IPv4) {
+ IPv4 p = (IPv4) pkt;
+ sb.append("\nnw_src: ");
+ sb.append(IPv4.fromIPv4Address(p.getSourceAddress()));
+ sb.append("\nnw_dst: ");
+ sb.append(IPv4.fromIPv4Address(p.getDestinationAddress()));
+ sb.append("\nnw_tos: ");
+ sb.append(p.getDiffServ());
+ sb.append("\nnw_proto: ");
+ sb.append(p.getProtocol());
+
+ if (pkt instanceof TCP) {
+ sb.append("\ntp_src: ");
+ sb.append(((TCP) pkt).getSourcePort());
+ sb.append("\ntp_dst: ");
+ sb.append(((TCP) pkt).getDestinationPort());
+
+ } else if (pkt instanceof UDP) {
+ sb.append("\ntp_src: ");
+ sb.append(((UDP) pkt).getSourcePort());
+ sb.append("\ntp_dst: ");
+ sb.append(((UDP) pkt).getDestinationPort());
+ }
+
+ if (pkt instanceof ICMP) {
+ ICMP icmp = (ICMP) pkt;
+ sb.append("\nicmp_type: ");
+ sb.append(icmp.getIcmpType());
+ sb.append("\nicmp_code: ");
+ sb.append(icmp.getIcmpCode());
+ }
+
+ }
+ else if (pkt instanceof DHCP) {
+ sb.append("\ndhcp packet");
+ }
+ else if (pkt instanceof Data) {
+ sb.append("\ndata packet");
+ }
+ else if (pkt instanceof LLC) {
+ sb.append("\nllc packet");
+ }
+ else if (pkt instanceof BPDU) {
+ sb.append("\nbpdu packet");
+ }
+ else sb.append("\nunknwon packet");
+
+ return sb.toString();
+ }
+
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/ICMP.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/ICMP.java
new file mode 100644
index 0000000..5431277
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/ICMP.java
@@ -0,0 +1,170 @@
+/**
+* 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.packet;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Implements ICMP packet format
+ * @author shudong.zhou@bigswitch.com
+ */
+public class ICMP extends BasePacket {
+ protected byte icmpType;
+ protected byte icmpCode;
+ protected short checksum;
+
+ /**
+ * @return the icmpType
+ */
+ public byte getIcmpType() {
+ return icmpType;
+ }
+
+ /**
+ * @param icmpType to set
+ */
+ public ICMP setIcmpType(byte icmpType) {
+ this.icmpType = icmpType;
+ return this;
+ }
+
+ /**
+ * @return the icmp code
+ */
+ public byte getIcmpCode() {
+ return icmpCode;
+ }
+
+ /**
+ * @param icmpCode code to set
+ */
+ public ICMP setIcmpCode(byte icmpCode) {
+ this.icmpCode = icmpCode;
+ return this;
+ }
+
+ /**
+ * @return the checksum
+ */
+ public short getChecksum() {
+ return checksum;
+ }
+
+ /**
+ * @param checksum the checksum to set
+ */
+ public ICMP setChecksum(short checksum) {
+ this.checksum = checksum;
+ return this;
+ }
+
+ /**
+ * Serializes the packet. Will compute and set the following fields if they
+ * are set to specific values at the time serialize is called:
+ * -checksum : 0
+ * -length : 0
+ */
+ public byte[] serialize() {
+ int length = 4;
+ byte[] payloadData = null;
+ if (payload != null) {
+ payload.setParent(this);
+ payloadData = payload.serialize();
+ length += payloadData.length;
+ }
+
+ byte[] data = new byte[length];
+ ByteBuffer bb = ByteBuffer.wrap(data);
+
+ bb.put(this.icmpType);
+ bb.put(this.icmpCode);
+ bb.putShort(this.checksum);
+ if (payloadData != null)
+ bb.put(payloadData);
+
+ if (this.parent != null && this.parent instanceof IPv4)
+ ((IPv4)this.parent).setProtocol(IPv4.PROTOCOL_ICMP);
+
+ // compute checksum if needed
+ if (this.checksum == 0) {
+ bb.rewind();
+ int accumulation = 0;
+
+ for (int i = 0; i < length / 2; ++i) {
+ accumulation += 0xffff & bb.getShort();
+ }
+ // pad to an even number of shorts
+ if (length % 2 > 0) {
+ accumulation += (bb.get() & 0xff) << 8;
+ }
+
+ accumulation = ((accumulation >> 16) & 0xffff)
+ + (accumulation & 0xffff);
+ this.checksum = (short) (~accumulation & 0xffff);
+ bb.putShort(2, this.checksum);
+ }
+ return data;
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ final int prime = 5807;
+ int result = super.hashCode();
+ result = prime * result + icmpType;
+ result = prime * result + icmpCode;
+ result = prime * result + checksum;
+ return result;
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (!super.equals(obj))
+ return false;
+ if (!(obj instanceof ICMP))
+ return false;
+ ICMP other = (ICMP) obj;
+ if (icmpType != other.icmpType)
+ return false;
+ if (icmpCode != other.icmpCode)
+ return false;
+ if (checksum != other.checksum)
+ return false;
+ return true;
+ }
+
+ @Override
+ public IPacket deserialize(byte[] data, int offset, int length) {
+ ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
+ this.icmpType = bb.get();
+ this.icmpCode = bb.get();
+ this.checksum = bb.getShort();
+
+ this.payload = new Data();
+ this.payload = payload.deserialize(data, bb.position(), bb.limit()-bb.position());
+ this.payload.setParent(this);
+ return this;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/IPacket.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/IPacket.java
new file mode 100644
index 0000000..02376cd
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/IPacket.java
@@ -0,0 +1,77 @@
+/**
+* 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.packet;
+
+/**
+*
+* @author David Erickson (daviderickson@cs.stanford.edu)
+*/
+public interface IPacket {
+ /**
+ *
+ * @return
+ */
+ public IPacket getPayload();
+
+ /**
+ *
+ * @param packet
+ * @return
+ */
+ public IPacket setPayload(IPacket packet);
+
+ /**
+ *
+ * @return
+ */
+ public IPacket getParent();
+
+ /**
+ *
+ * @param packet
+ * @return
+ */
+ public IPacket setParent(IPacket packet);
+
+ /**
+ * Reset any checksums as needed, and call resetChecksum on all parents
+ */
+ public void resetChecksum();
+
+ /**
+ * Sets all payloads parent packet if applicable, then serializes this
+ * packet and all payloads
+ * @return a byte[] containing this packet and payloads
+ */
+ public byte[] serialize();
+
+ /**
+ * Deserializes this packet layer and all possible payloads
+ * @param data
+ * @param offset offset to start deserializing from
+ * @param length length of the data to deserialize
+ * @return the deserialized data
+ */
+ public IPacket deserialize(byte[] data, int offset, int length);
+
+ /** Clone this packet and its payload packet but not its parent.
+ *
+ * @return
+ */
+ public Object clone();
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/IPv4.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/IPv4.java
new file mode 100644
index 0000000..01f886d
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/IPv4.java
@@ -0,0 +1,558 @@
+/**
+* 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.packet;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ *
+ */
+public class IPv4 extends BasePacket {
+ public static final byte PROTOCOL_ICMP = 0x1;
+ public static final byte PROTOCOL_TCP = 0x6;
+ public static final byte PROTOCOL_UDP = 0x11;
+ public static Map<Byte, Class<? extends IPacket>> protocolClassMap;
+
+ static {
+ protocolClassMap = new HashMap<Byte, Class<? extends IPacket>>();
+ protocolClassMap.put(PROTOCOL_ICMP, ICMP.class);
+ protocolClassMap.put(PROTOCOL_TCP, TCP.class);
+ protocolClassMap.put(PROTOCOL_UDP, UDP.class);
+ }
+
+ protected byte version;
+ protected byte headerLength;
+ protected byte diffServ;
+ protected short totalLength;
+ protected short identification;
+ protected byte flags;
+ protected short fragmentOffset;
+ protected byte ttl;
+ protected byte protocol;
+ protected short checksum;
+ protected int sourceAddress;
+ protected int destinationAddress;
+ protected byte[] options;
+
+ protected boolean isTruncated;
+
+ /**
+ * Default constructor that sets the version to 4.
+ */
+ public IPv4() {
+ super();
+ this.version = 4;
+ isTruncated = false;
+ }
+
+ /**
+ * @return the version
+ */
+ public byte getVersion() {
+ return version;
+ }
+
+ /**
+ * @param version the version to set
+ */
+ public IPv4 setVersion(byte version) {
+ this.version = version;
+ return this;
+ }
+
+ /**
+ * @return the headerLength
+ */
+ public byte getHeaderLength() {
+ return headerLength;
+ }
+
+ /**
+ * @return the diffServ
+ */
+ public byte getDiffServ() {
+ return diffServ;
+ }
+
+ /**
+ * @param diffServ the diffServ to set
+ */
+ public IPv4 setDiffServ(byte diffServ) {
+ this.diffServ = diffServ;
+ return this;
+ }
+
+ /**
+ * @return the totalLength
+ */
+ public short getTotalLength() {
+ return totalLength;
+ }
+
+ /**
+ * @return the identification
+ */
+ public short getIdentification() {
+ return identification;
+ }
+
+ public boolean isTruncated() {
+ return isTruncated;
+ }
+
+ public void setTruncated(boolean isTruncated) {
+ this.isTruncated = isTruncated;
+ }
+
+ /**
+ * @param identification the identification to set
+ */
+ public IPv4 setIdentification(short identification) {
+ this.identification = identification;
+ return this;
+ }
+
+ /**
+ * @return the flags
+ */
+ public byte getFlags() {
+ return flags;
+ }
+
+ /**
+ * @param flags the flags to set
+ */
+ public IPv4 setFlags(byte flags) {
+ this.flags = flags;
+ return this;
+ }
+
+ /**
+ * @return the fragmentOffset
+ */
+ public short getFragmentOffset() {
+ return fragmentOffset;
+ }
+
+ /**
+ * @param fragmentOffset the fragmentOffset to set
+ */
+ public IPv4 setFragmentOffset(short fragmentOffset) {
+ this.fragmentOffset = fragmentOffset;
+ return this;
+ }
+
+ /**
+ * @return the ttl
+ */
+ public byte getTtl() {
+ return ttl;
+ }
+
+ /**
+ * @param ttl the ttl to set
+ */
+ public IPv4 setTtl(byte ttl) {
+ this.ttl = ttl;
+ return this;
+ }
+
+ /**
+ * @return the protocol
+ */
+ public byte getProtocol() {
+ return protocol;
+ }
+
+ /**
+ * @param protocol the protocol to set
+ */
+ public IPv4 setProtocol(byte protocol) {
+ this.protocol = protocol;
+ return this;
+ }
+
+ /**
+ * @return the checksum
+ */
+ public short getChecksum() {
+ return checksum;
+ }
+
+ /**
+ * @param checksum the checksum to set
+ */
+ public IPv4 setChecksum(short checksum) {
+ this.checksum = checksum;
+ return this;
+ }
+ @Override
+ public void resetChecksum() {
+ this.checksum = 0;
+ super.resetChecksum();
+ }
+
+ /**
+ * @return the sourceAddress
+ */
+ public int getSourceAddress() {
+ return sourceAddress;
+ }
+
+ /**
+ * @param sourceAddress the sourceAddress to set
+ */
+ public IPv4 setSourceAddress(int sourceAddress) {
+ this.sourceAddress = sourceAddress;
+ return this;
+ }
+
+ /**
+ * @param sourceAddress the sourceAddress to set
+ */
+ public IPv4 setSourceAddress(String sourceAddress) {
+ this.sourceAddress = IPv4.toIPv4Address(sourceAddress);
+ return this;
+ }
+
+ /**
+ * @return the destinationAddress
+ */
+ public int getDestinationAddress() {
+ return destinationAddress;
+ }
+
+ /**
+ * @param destinationAddress the destinationAddress to set
+ */
+ public IPv4 setDestinationAddress(int destinationAddress) {
+ this.destinationAddress = destinationAddress;
+ return this;
+ }
+
+ /**
+ * @param destinationAddress the destinationAddress to set
+ */
+ public IPv4 setDestinationAddress(String destinationAddress) {
+ this.destinationAddress = IPv4.toIPv4Address(destinationAddress);
+ return this;
+ }
+
+ /**
+ * @return the options
+ */
+ public byte[] getOptions() {
+ return options;
+ }
+
+ /**
+ * @param options the options to set
+ */
+ public IPv4 setOptions(byte[] options) {
+ if (options != null && (options.length % 4) > 0)
+ throw new IllegalArgumentException(
+ "Options length must be a multiple of 4");
+ this.options = options;
+ return this;
+ }
+
+ /**
+ * Serializes the packet. Will compute and set the following fields if they
+ * are set to specific values at the time serialize is called:
+ * -checksum : 0
+ * -headerLength : 0
+ * -totalLength : 0
+ */
+ public byte[] serialize() {
+ byte[] payloadData = null;
+ if (payload != null) {
+ payload.setParent(this);
+ payloadData = payload.serialize();
+ }
+
+ int optionsLength = 0;
+ if (this.options != null)
+ optionsLength = this.options.length / 4;
+ this.headerLength = (byte) (5 + optionsLength);
+
+ this.totalLength = (short) (this.headerLength * 4 + ((payloadData == null) ? 0
+ : payloadData.length));
+
+ byte[] data = new byte[this.totalLength];
+ ByteBuffer bb = ByteBuffer.wrap(data);
+
+ bb.put((byte) (((this.version & 0xf) << 4) | (this.headerLength & 0xf)));
+ bb.put(this.diffServ);
+ bb.putShort(this.totalLength);
+ bb.putShort(this.identification);
+ bb.putShort((short) (((this.flags & 0x7) << 13) | (this.fragmentOffset & 0x1fff)));
+ bb.put(this.ttl);
+ bb.put(this.protocol);
+ bb.putShort(this.checksum);
+ bb.putInt(this.sourceAddress);
+ bb.putInt(this.destinationAddress);
+ if (this.options != null)
+ bb.put(this.options);
+ if (payloadData != null)
+ bb.put(payloadData);
+
+ // compute checksum if needed
+ if (this.checksum == 0) {
+ bb.rewind();
+ int accumulation = 0;
+ for (int i = 0; i < this.headerLength * 2; ++i) {
+ accumulation += 0xffff & bb.getShort();
+ }
+ accumulation = ((accumulation >> 16) & 0xffff)
+ + (accumulation & 0xffff);
+ this.checksum = (short) (~accumulation & 0xffff);
+ bb.putShort(10, this.checksum);
+ }
+ return data;
+ }
+
+ @Override
+ public IPacket deserialize(byte[] data, int offset, int length) {
+ ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
+ short sscratch;
+
+ this.version = bb.get();
+ this.headerLength = (byte) (this.version & 0xf);
+ this.version = (byte) ((this.version >> 4) & 0xf);
+ this.diffServ = bb.get();
+ this.totalLength = bb.getShort();
+ this.identification = bb.getShort();
+ sscratch = bb.getShort();
+ this.flags = (byte) ((sscratch >> 13) & 0x7);
+ this.fragmentOffset = (short) (sscratch & 0x1fff);
+ this.ttl = bb.get();
+ this.protocol = bb.get();
+ this.checksum = bb.getShort();
+ this.sourceAddress = bb.getInt();
+ this.destinationAddress = bb.getInt();
+
+ if (this.headerLength > 5) {
+ int optionsLength = (this.headerLength - 5) * 4;
+ this.options = new byte[optionsLength];
+ bb.get(this.options);
+ }
+
+ IPacket payload;
+ if (IPv4.protocolClassMap.containsKey(this.protocol)) {
+ Class<? extends IPacket> clazz = IPv4.protocolClassMap.get(this.protocol);
+ try {
+ payload = clazz.newInstance();
+ } catch (Exception e) {
+ throw new RuntimeException("Error parsing payload for IPv4 packet", e);
+ }
+ } else {
+ payload = new Data();
+ }
+ this.payload = payload.deserialize(data, bb.position(), bb.limit()-bb.position());
+ this.payload.setParent(this);
+
+ if (this.totalLength != length)
+ this.isTruncated = true;
+ else
+ this.isTruncated = false;
+
+ return this;
+ }
+
+ /**
+ * Accepts an IPv4 address of the form xxx.xxx.xxx.xxx, ie 192.168.0.1 and
+ * returns the corresponding 32 bit integer.
+ * @param ipAddress
+ * @return
+ */
+ public static int toIPv4Address(String ipAddress) {
+ if (ipAddress == null)
+ throw new IllegalArgumentException("Specified IPv4 address must" +
+ "contain 4 sets of numerical digits separated by periods");
+ String[] octets = ipAddress.split("\\.");
+ if (octets.length != 4)
+ throw new IllegalArgumentException("Specified IPv4 address must" +
+ "contain 4 sets of numerical digits separated by periods");
+
+ int result = 0;
+ for (int i = 0; i < 4; ++i) {
+ result |= Integer.valueOf(octets[i]) << ((3-i)*8);
+ }
+ return result;
+ }
+
+ /**
+ * Accepts an IPv4 address in a byte array and returns the corresponding
+ * 32-bit integer value.
+ * @param ipAddress
+ * @return
+ */
+ public static int toIPv4Address(byte[] ipAddress) {
+ int ip = 0;
+ for (int i = 0; i < 4; i++) {
+ int t = (ipAddress[i] & 0xff) << ((3-i)*8);
+ ip |= t;
+ }
+ return ip;
+ }
+
+ /**
+ * Accepts an IPv4 address and returns of string of the form xxx.xxx.xxx.xxx
+ * ie 192.168.0.1
+ *
+ * @param ipAddress
+ * @return
+ */
+ public static String fromIPv4Address(int ipAddress) {
+ StringBuffer sb = new StringBuffer();
+ int result = 0;
+ for (int i = 0; i < 4; ++i) {
+ result = (ipAddress >> ((3-i)*8)) & 0xff;
+ sb.append(Integer.valueOf(result).toString());
+ if (i != 3)
+ sb.append(".");
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Accepts a collection of IPv4 addresses as integers and returns a single
+ * String useful in toString method's containing collections of IP
+ * addresses.
+ *
+ * @param ipAddresses collection
+ * @return
+ */
+ public static String fromIPv4AddressCollection(Collection<Integer> ipAddresses) {
+ if (ipAddresses == null)
+ return "null";
+ StringBuffer sb = new StringBuffer();
+ sb.append("[");
+ for (Integer ip : ipAddresses) {
+ sb.append(fromIPv4Address(ip));
+ sb.append(",");
+ }
+ sb.replace(sb.length()-1, sb.length(), "]");
+ return sb.toString();
+ }
+
+ /**
+ * Accepts an IPv4 address of the form xxx.xxx.xxx.xxx, ie 192.168.0.1 and
+ * returns the corresponding byte array.
+ * @param ipAddress The IP address in the form xx.xxx.xxx.xxx.
+ * @return The IP address separated into bytes
+ */
+ public static byte[] toIPv4AddressBytes(String ipAddress) {
+ String[] octets = ipAddress.split("\\.");
+ if (octets.length != 4)
+ throw new IllegalArgumentException("Specified IPv4 address must" +
+ "contain 4 sets of numerical digits separated by periods");
+
+ byte[] result = new byte[4];
+ for (int i = 0; i < 4; ++i) {
+ result[i] = Integer.valueOf(octets[i]).byteValue();
+ }
+ return result;
+ }
+
+ /**
+ * Accepts an IPv4 address in the form of an integer and
+ * returns the corresponding byte array.
+ * @param ipAddress The IP address as an integer.
+ * @return The IP address separated into bytes.
+ */
+ public static byte[] toIPv4AddressBytes(int ipAddress) {
+ return new byte[] {
+ (byte)(ipAddress >>> 24),
+ (byte)(ipAddress >>> 16),
+ (byte)(ipAddress >>> 8),
+ (byte)ipAddress};
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ final int prime = 2521;
+ int result = super.hashCode();
+ result = prime * result + checksum;
+ result = prime * result + destinationAddress;
+ 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 + totalLength;
+ result = prime * result + ttl;
+ result = prime * result + version;
+ return result;
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (!super.equals(obj))
+ return false;
+ if (!(obj instanceof IPv4))
+ return false;
+ IPv4 other = (IPv4) obj;
+ if (checksum != other.checksum)
+ return false;
+ if (destinationAddress != other.destinationAddress)
+ return false;
+ if (diffServ != other.diffServ)
+ return false;
+ if (flags != other.flags)
+ return false;
+ if (fragmentOffset != other.fragmentOffset)
+ return false;
+ if (headerLength != other.headerLength)
+ return false;
+ if (identification != other.identification)
+ return false;
+ if (!Arrays.equals(options, other.options))
+ return false;
+ if (protocol != other.protocol)
+ return false;
+ if (sourceAddress != other.sourceAddress)
+ return false;
+ if (totalLength != other.totalLength)
+ return false;
+ if (ttl != other.ttl)
+ return false;
+ if (version != other.version)
+ return false;
+ return true;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/LLC.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/LLC.java
new file mode 100644
index 0000000..dc7d6d8
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/LLC.java
@@ -0,0 +1,75 @@
+/**
+* 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.packet;
+
+import java.nio.ByteBuffer;
+
+/**
+ * This class represents an Link Local Control
+ * header that is used in Ethernet 802.3.
+ * @author alexreimers
+ *
+ */
+public class LLC extends BasePacket {
+ private byte dsap = 0;
+ private byte ssap = 0;
+ private byte ctrl = 0;
+
+ public byte getDsap() {
+ return dsap;
+ }
+
+ public void setDsap(byte dsap) {
+ this.dsap = dsap;
+ }
+
+ public byte getSsap() {
+ return ssap;
+ }
+
+ public void setSsap(byte ssap) {
+ this.ssap = ssap;
+ }
+
+ public byte getCtrl() {
+ return ctrl;
+ }
+
+ public void setCtrl(byte ctrl) {
+ this.ctrl = ctrl;
+ }
+
+ @Override
+ public byte[] serialize() {
+ byte[] data = new byte[3];
+ ByteBuffer bb = ByteBuffer.wrap(data);
+ bb.put(dsap);
+ bb.put(ssap);
+ bb.put(ctrl);
+ return data;
+ }
+
+ @Override
+ public IPacket deserialize(byte[] data, int offset, int length) {
+ ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
+ dsap = bb.get();
+ ssap = bb.get();
+ ctrl = bb.get();
+ return this;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/LLDP.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/LLDP.java
new file mode 100644
index 0000000..8c2c457
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/LLDP.java
@@ -0,0 +1,204 @@
+/**
+* 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.packet;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ *
+ */
+public class LLDP extends BasePacket {
+ protected LLDPTLV chassisId;
+ protected LLDPTLV portId;
+ protected LLDPTLV ttl;
+ protected List<LLDPTLV> optionalTLVList;
+ protected short ethType;
+
+ public LLDP() {
+ this.optionalTLVList = new ArrayList<LLDPTLV>();
+ this.ethType = Ethernet.TYPE_LLDP;
+ }
+
+ /**
+ * @return the chassisId
+ */
+ public LLDPTLV getChassisId() {
+ return chassisId;
+ }
+
+ /**
+ * @param chassisId the chassisId to set
+ */
+ public LLDP setChassisId(LLDPTLV chassisId) {
+ this.chassisId = chassisId;
+ return this;
+ }
+
+ /**
+ * @return the portId
+ */
+ public LLDPTLV getPortId() {
+ return portId;
+ }
+
+ /**
+ * @param portId the portId to set
+ */
+ public LLDP setPortId(LLDPTLV portId) {
+ this.portId = portId;
+ return this;
+ }
+
+ /**
+ * @return the ttl
+ */
+ public LLDPTLV getTtl() {
+ return ttl;
+ }
+
+ /**
+ * @param ttl the ttl to set
+ */
+ public LLDP setTtl(LLDPTLV ttl) {
+ this.ttl = ttl;
+ return this;
+ }
+
+ /**
+ * @return the optionalTLVList
+ */
+ public List<LLDPTLV> getOptionalTLVList() {
+ return optionalTLVList;
+ }
+
+ /**
+ * @param optionalTLVList the optionalTLVList to set
+ */
+ public LLDP setOptionalTLVList(List<LLDPTLV> optionalTLVList) {
+ this.optionalTLVList = optionalTLVList;
+ return this;
+ }
+
+ @Override
+ public byte[] serialize() {
+ int length = 2+this.chassisId.getLength() + 2+this.portId.getLength() +
+ 2+this.ttl.getLength() + 2;
+ for (LLDPTLV tlv : this.optionalTLVList) {
+ length += 2 + tlv.getLength();
+ }
+
+ byte[] data = new byte[length];
+ ByteBuffer bb = ByteBuffer.wrap(data);
+ bb.put(this.chassisId.serialize());
+ bb.put(this.portId.serialize());
+ bb.put(this.ttl.serialize());
+ for (LLDPTLV tlv : this.optionalTLVList) {
+ bb.put(tlv.serialize());
+ }
+ bb.putShort((short) 0); // End of LLDPDU
+
+ if (this.parent != null && this.parent instanceof Ethernet)
+ ((Ethernet)this.parent).setEtherType(ethType);
+
+ return data;
+ }
+
+ @Override
+ public IPacket deserialize(byte[] data, int offset, int length) {
+ ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
+ LLDPTLV tlv;
+ do {
+ tlv = new LLDPTLV().deserialize(bb);
+
+ // if there was a failure to deserialize stop processing TLVs
+ if (tlv == null)
+ break;
+ switch (tlv.getType()) {
+ case 0x0:
+ // can throw this one away, its just an end delimiter
+ break;
+ case 0x1:
+ this.chassisId = tlv;
+ break;
+ case 0x2:
+ this.portId = tlv;
+ break;
+ case 0x3:
+ this.ttl = tlv;
+ break;
+ default:
+ this.optionalTLVList.add(tlv);
+ break;
+ }
+ } while (tlv.getType() != 0 && bb.hasRemaining());
+ return this;
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ final int prime = 883;
+ int result = super.hashCode();
+ result = prime * result
+ + ((chassisId == null) ? 0 : chassisId.hashCode());
+ result = prime * result + (optionalTLVList.hashCode());
+ result = prime * result + ((portId == null) ? 0 : portId.hashCode());
+ result = prime * result + ((ttl == null) ? 0 : ttl.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 (!super.equals(obj))
+ return false;
+ if (!(obj instanceof LLDP))
+ return false;
+ LLDP other = (LLDP) obj;
+ if (chassisId == null) {
+ if (other.chassisId != null)
+ return false;
+ } else if (!chassisId.equals(other.chassisId))
+ return false;
+ if (!optionalTLVList.equals(other.optionalTLVList))
+ return false;
+ if (portId == null) {
+ if (other.portId != null)
+ return false;
+ } else if (!portId.equals(other.portId))
+ return false;
+ if (ttl == null) {
+ if (other.ttl != null)
+ return false;
+ } else if (!ttl.equals(other.ttl))
+ return false;
+ return true;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/LLDPOrganizationalTLV.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/LLDPOrganizationalTLV.java
new file mode 100644
index 0000000..a0930bd
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/LLDPOrganizationalTLV.java
@@ -0,0 +1,181 @@
+/**
+ * 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.packet;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+import java.util.Arrays;
+
+/**
+ * The class representing LLDP Organizationally Specific TLV.
+ *
+ * @author Sho Shimizu (sho.shimizu@gmail.com)
+ */
+public class LLDPOrganizationalTLV extends LLDPTLV {
+ public static final int OUI_LENGTH = 3;
+ public static final int SUBTYPE_LENGTH = 1;
+ public static final byte ORGANIZATIONAL_TLV_TYPE = 127;
+ public static final int MAX_INFOSTRING_LENGTH = 507;
+
+ protected byte[] oui;
+ protected byte subType;
+ private byte[] infoString;
+
+ public LLDPOrganizationalTLV() {
+ type = ORGANIZATIONAL_TLV_TYPE;
+ }
+
+ /**
+ * Set the value of OUI.
+ * @param oui The value of OUI to be set.
+ * @return This LLDP Organizationally Specific TLV.
+ */
+ public LLDPOrganizationalTLV setOUI(byte[] oui) {
+ if (oui.length != OUI_LENGTH) {
+ throw new IllegalArgumentException("The length of OUI must be " + OUI_LENGTH +
+ ", but it is " + oui.length);
+ }
+ this.oui = Arrays.copyOf(oui, oui.length);
+ return this;
+ }
+
+ /**
+ * Returns the value of the OUI.
+ * @return The value of the OUI .
+ */
+ public byte[] getOUI() {
+ return Arrays.copyOf(oui, oui.length);
+ }
+
+ /**
+ * Set the value of sub type.
+ * @param subType The value of sub type to be set.
+ * @return This LLDP Organizationally Specific TLV.
+ */
+ public LLDPOrganizationalTLV setSubType(byte subType) {
+ this.subType = subType;
+ return this;
+ }
+
+ /**
+ * Returns the value of the sub type.
+ * @return The value of the sub type.
+ */
+ public byte getSubType() {
+ return subType;
+ }
+
+ /**
+ * Set the value of information string.
+ * @param infoString the byte array of the value of information string.
+ * @return This LLDP Organizationally Specific TLV.
+ */
+ public LLDPOrganizationalTLV setInfoString(byte[] infoString) {
+ if (infoString.length > MAX_INFOSTRING_LENGTH) {
+ throw new IllegalArgumentException("The length of infoString cannot exceed " + MAX_INFOSTRING_LENGTH);
+ }
+ this.infoString = Arrays.copyOf(infoString, infoString.length);
+ return this;
+ }
+
+ /**
+ * Set the value of information string.
+ * The String value is automatically converted into byte array with UTF-8 encoding.
+ * @param infoString the String value of information string.
+ * @return This LLDP Organizationally Specific TLV.
+ */
+ public LLDPOrganizationalTLV setInfoString(String infoString) {
+ byte[] infoStringBytes = infoString.getBytes(Charset.forName("UTF-8"));
+ return setInfoString(infoStringBytes);
+ }
+
+ /**
+ * Returns the value of information string.
+ * @return the value of information string.
+ */
+ public byte[] getInfoString() {
+ return Arrays.copyOf(infoString, infoString.length);
+ }
+
+ @Override
+ public byte[] serialize() {
+ int valueLength = OUI_LENGTH + SUBTYPE_LENGTH + infoString.length;
+ value = new byte[valueLength];
+ ByteBuffer bb = ByteBuffer.wrap(value);
+ bb.put(oui);
+ bb.put(subType);
+ bb.put(infoString);
+ return super.serialize();
+ }
+
+ @Override
+ public LLDPTLV deserialize(ByteBuffer bb) {
+ super.deserialize(bb);
+ ByteBuffer optionalField = ByteBuffer.wrap(value);
+
+ byte[] oui = new byte[OUI_LENGTH];
+ optionalField.get(oui);
+ setOUI(oui);
+
+ setSubType(optionalField.get());
+
+ byte[] infoString = new byte[getLength() - OUI_LENGTH - SUBTYPE_LENGTH];
+ optionalField.get(infoString);
+ setInfoString(infoString);
+ return this;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 1423;
+ int result = 1;
+ result = prime * result + type;
+ result = prime * result + length;
+ result = prime * result + Arrays.hashCode(oui);
+ result = prime * result + subType;
+ result = prime * result + Arrays.hashCode(infoString);
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+
+ if (!(o instanceof LLDPOrganizationalTLV)) {
+ return false;
+ }
+
+ LLDPOrganizationalTLV other = (LLDPOrganizationalTLV)o;
+ if (this.type != other.type) {
+ return false;
+ }
+ if (this.length != other.length) {
+ return false;
+ }
+ if (!Arrays.equals(this.oui, other.oui)) {
+ return false;
+ }
+ if (this.subType != other.subType) {
+ return false;
+ }
+ if (!Arrays.equals(this.infoString, other.infoString)) {
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/LLDPTLV.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/LLDPTLV.java
new file mode 100644
index 0000000..0552321
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/LLDPTLV.java
@@ -0,0 +1,140 @@
+/**
+* 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.packet;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+/**
+ *
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class LLDPTLV {
+ protected byte type;
+ protected short length;
+ protected byte[] value;
+
+ /**
+ * @return the type
+ */
+ public byte getType() {
+ return type;
+ }
+
+ /**
+ * @param type the type to set
+ */
+ public LLDPTLV setType(byte type) {
+ this.type = type;
+ return this;
+ }
+
+ /**
+ * @return the length
+ */
+ public short getLength() {
+ return length;
+ }
+
+ /**
+ * @param length the length to set
+ */
+ public LLDPTLV setLength(short length) {
+ this.length = length;
+ return this;
+ }
+
+ /**
+ * @return the value
+ */
+ public byte[] getValue() {
+ return value;
+ }
+
+ /**
+ * @param value the value to set
+ */
+ public LLDPTLV setValue(byte[] value) {
+ this.value = value;
+ return this;
+ }
+
+ public byte[] serialize() {
+ // type = 7 bits
+ // info string length 9 bits, each value == byte
+ // info string
+ short scratch = (short) (((0x7f & this.type) << 9) | (0x1ff & this.length));
+ byte[] data = new byte[2+this.length];
+ ByteBuffer bb = ByteBuffer.wrap(data);
+ bb.putShort(scratch);
+ if (this.value != null)
+ bb.put(this.value);
+ return data;
+ }
+
+ public LLDPTLV deserialize(ByteBuffer bb) {
+ short sscratch;
+ sscratch = bb.getShort();
+ this.type = (byte) ((sscratch >> 9) & 0x7f);
+ this.length = (short) (sscratch & 0x1ff);
+ if (this.length > 0) {
+ this.value = new byte[this.length];
+
+ // if there is an underrun just toss the TLV
+ if (bb.remaining() < this.length)
+ return null;
+ bb.get(this.value);
+ }
+ return this;
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ final int prime = 1423;
+ int result = 1;
+ result = prime * result + length;
+ result = prime * result + type;
+ result = prime * result + Arrays.hashCode(value);
+ 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 (!(obj instanceof LLDPTLV))
+ return false;
+ LLDPTLV other = (LLDPTLV) obj;
+ if (length != other.length)
+ return false;
+ if (type != other.type)
+ return false;
+ if (!Arrays.equals(value, other.value))
+ return false;
+ return true;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/TCP.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/TCP.java
new file mode 100644
index 0000000..889e4c6
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/TCP.java
@@ -0,0 +1,290 @@
+/**
+* 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.packet;
+
+import java.nio.ByteBuffer;
+
+/**
+ *
+ * @author shudong.zhou@bigswitch.com
+ */
+public class TCP extends BasePacket {
+ protected short sourcePort;
+ protected short destinationPort;
+ protected int sequence;
+ protected int acknowledge;
+ protected byte dataOffset;
+ protected short flags;
+ protected short windowSize;
+ protected short checksum;
+ protected short urgentPointer;
+ protected byte[] options;
+
+ /**
+ * @return the sourcePort
+ */
+ public short getSourcePort() {
+ return sourcePort;
+ }
+
+ /**
+ * @param sourcePort the sourcePort to set
+ */
+ public TCP setSourcePort(short sourcePort) {
+ this.sourcePort = sourcePort;
+ return this;
+ }
+
+ /**
+ * @return the destinationPort
+ */
+ public short getDestinationPort() {
+ return destinationPort;
+ }
+
+ /**
+ * @param destinationPort the destinationPort to set
+ */
+ public TCP setDestinationPort(short destinationPort) {
+ this.destinationPort = destinationPort;
+ return this;
+ }
+
+ /**
+ * @return the checksum
+ */
+ public short getChecksum() {
+ return checksum;
+ }
+
+ public int getSequence() {
+ return this.sequence;
+ }
+ public TCP setSequence(int seq) {
+ this.sequence = seq;
+ return this;
+ }
+ public int getAcknowledge() {
+ return this.acknowledge;
+ }
+ public TCP setAcknowledge(int ack) {
+ this.acknowledge = ack;
+ return this;
+ }
+ public byte getDataOffset() {
+ return this.dataOffset;
+ }
+ public TCP setDataOffset(byte offset) {
+ this.dataOffset = offset;
+ return this;
+ }
+ public short getFlags() {
+ return this.flags;
+ }
+ public TCP setFlags(short flags) {
+ this.flags = flags;
+ return this;
+ }
+ public short getWindowSize() {
+ return this.windowSize;
+ }
+ public TCP setWindowSize(short windowSize) {
+ this.windowSize = windowSize;
+ return this;
+ }
+ public short getTcpChecksum() {
+ return this.checksum;
+ }
+ public TCP setTcpChecksum(short checksum) {
+ this.checksum = checksum;
+ return this;
+ }
+
+ @Override
+ public void resetChecksum() {
+ this.checksum = 0;
+ super.resetChecksum();
+ }
+
+ public short getUrgentPointer(short urgentPointer) {
+ return this.urgentPointer;
+ }
+ public TCP setUrgentPointer(short urgentPointer) {
+ this.urgentPointer= urgentPointer;
+ return this;
+ }
+ public byte[] getOptions() {
+ return this.options;
+ }
+ public TCP setOptions(byte[] options) {
+ this.options = options;
+ this.dataOffset = (byte) ((20 + options.length + 3) >> 2);
+ return this;
+ }
+ /**
+ * @param checksum the checksum to set
+ */
+ public TCP setChecksum(short checksum) {
+ this.checksum = checksum;
+ return this;
+ }
+
+ /**
+ * Serializes the packet. Will compute and set the following fields if they
+ * are set to specific values at the time serialize is called:
+ * -checksum : 0
+ * -length : 0
+ */
+ public byte[] serialize() {
+ int length;
+ if (dataOffset == 0)
+ dataOffset = 5; // default header length
+ length = dataOffset << 2;
+ byte[] payloadData = null;
+ if (payload != null) {
+ payload.setParent(this);
+ payloadData = payload.serialize();
+ length += payloadData.length;
+ }
+
+ byte[] data = new byte[length];
+ ByteBuffer bb = ByteBuffer.wrap(data);
+
+ bb.putShort(this.sourcePort);
+ bb.putShort(this.destinationPort);
+ bb.putInt(this.sequence);
+ bb.putInt(this.acknowledge);
+ bb.putShort((short) (this.flags | (dataOffset << 12)));
+ bb.putShort(this.windowSize);
+ bb.putShort(this.checksum);
+ bb.putShort(this.urgentPointer);
+ if (dataOffset > 5) {
+ int padding;
+ bb.put(options);
+ padding = (dataOffset << 2) - 20 - options.length;
+ for (int i = 0; i < padding; i++)
+ bb.put((byte) 0);
+ }
+ if (payloadData != null)
+ bb.put(payloadData);
+
+ if (this.parent != null && this.parent instanceof IPv4)
+ ((IPv4)this.parent).setProtocol(IPv4.PROTOCOL_TCP);
+
+ // compute checksum if needed
+ if (this.checksum == 0) {
+ bb.rewind();
+ int accumulation = 0;
+
+ // 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 += length & 0xffff;
+ }
+
+ for (int i = 0; i < length / 2; ++i) {
+ accumulation += 0xffff & bb.getShort();
+ }
+ // pad to an even number of shorts
+ if (length % 2 > 0) {
+ accumulation += (bb.get() & 0xff) << 8;
+ }
+
+ accumulation = ((accumulation >> 16) & 0xffff)
+ + (accumulation & 0xffff);
+ this.checksum = (short) (~accumulation & 0xffff);
+ bb.putShort(16, this.checksum);
+ }
+ return data;
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ final int prime = 5807;
+ int result = super.hashCode();
+ result = prime * result + checksum;
+ result = prime * result + destinationPort;
+ result = prime * result + sourcePort;
+ return result;
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (!super.equals(obj))
+ return false;
+ if (!(obj instanceof TCP))
+ return false;
+ TCP other = (TCP) obj;
+ // May want to compare fields based on the flags set
+ return (checksum == other.checksum) &&
+ (destinationPort == other.destinationPort) &&
+ (sourcePort == other.sourcePort) &&
+ (sequence == other.sequence) &&
+ (acknowledge == other.acknowledge) &&
+ (dataOffset == other.dataOffset) &&
+ (flags == other.flags) &&
+ (windowSize == other.windowSize) &&
+ (urgentPointer == other.urgentPointer) &&
+ (dataOffset == 5 || options.equals(other.options));
+ }
+
+ @Override
+ public IPacket deserialize(byte[] data, int offset, int length) {
+ ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
+ this.sourcePort = bb.getShort();
+ this.destinationPort = bb.getShort();
+ this.sequence = bb.getInt();
+ this.acknowledge = bb.getInt();
+ this.flags = bb.getShort();
+ this.dataOffset = (byte) ((this.flags >> 12) & 0xf);
+ this.flags = (short) (this.flags & 0x1ff);
+ this.windowSize = bb.getShort();
+ this.checksum = bb.getShort();
+ this.urgentPointer = bb.getShort();
+ if (this.dataOffset > 5) {
+ int optLength = (dataOffset << 2) - 20;
+ if (bb.limit() < bb.position()+optLength) {
+ optLength = bb.limit() - bb.position();
+ }
+ try {
+ this.options = new byte[optLength];
+ bb.get(this.options, 0, optLength);
+ } catch (IndexOutOfBoundsException e) {
+ this.options = null;
+ }
+ }
+
+ this.payload = new Data();
+ this.payload = payload.deserialize(data, bb.position(), bb.limit()-bb.position());
+ this.payload.setParent(this);
+ return this;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/UDP.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/UDP.java
new file mode 100644
index 0000000..cbeeedf
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packet/UDP.java
@@ -0,0 +1,231 @@
+/**
+* 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.packet;
+
+import java.nio.ByteBuffer;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class UDP extends BasePacket {
+ public static Map<Short, Class<? extends IPacket>> decodeMap;
+ public static short DHCP_SERVER_PORT = (short)67;
+ public static short DHCP_CLIENT_PORT = (short)68;
+
+ static {
+ decodeMap = new HashMap<Short, Class<? extends IPacket>>();
+ /*
+ * Disable DHCP until the deserialize code is hardened to deal with garbage input
+ */
+ UDP.decodeMap.put(DHCP_SERVER_PORT, DHCP.class);
+ UDP.decodeMap.put(DHCP_CLIENT_PORT, DHCP.class);
+
+ }
+
+ protected short sourcePort;
+ protected short destinationPort;
+ protected short length;
+ protected short checksum;
+
+ /**
+ * @return the sourcePort
+ */
+ public short getSourcePort() {
+ return sourcePort;
+ }
+
+ /**
+ * @param sourcePort the sourcePort to set
+ */
+ public UDP setSourcePort(short sourcePort) {
+ this.sourcePort = sourcePort;
+ return this;
+ }
+
+ /**
+ * @return the destinationPort
+ */
+ public short getDestinationPort() {
+ return destinationPort;
+ }
+
+ /**
+ * @param destinationPort the destinationPort to set
+ */
+ public UDP setDestinationPort(short destinationPort) {
+ this.destinationPort = destinationPort;
+ return this;
+ }
+
+ /**
+ * @return the length
+ */
+ public short getLength() {
+ return length;
+ }
+
+ /**
+ * @return the checksum
+ */
+ public short getChecksum() {
+ return checksum;
+ }
+
+ /**
+ * @param checksum the checksum to set
+ */
+ public UDP setChecksum(short checksum) {
+ this.checksum = checksum;
+ return this;
+ }
+
+ @Override
+ public void resetChecksum() {
+ this.checksum = 0;
+ super.resetChecksum();
+ }
+
+ /**
+ * Serializes the packet. Will compute and set the following fields if they
+ * are set to specific values at the time serialize is called:
+ * -checksum : 0
+ * -length : 0
+ */
+ public byte[] serialize() {
+ byte[] payloadData = null;
+ if (payload != null) {
+ payload.setParent(this);
+ payloadData = payload.serialize();
+ }
+
+ this.length = (short) (8 + ((payloadData == null) ? 0
+ : payloadData.length));
+
+ byte[] data = new byte[this.length];
+ ByteBuffer bb = ByteBuffer.wrap(data);
+
+ bb.putShort(this.sourcePort);
+ bb.putShort(this.destinationPort);
+ 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);
+
+ // compute checksum if needed
+ if (this.checksum == 0) {
+ bb.rewind();
+ int accumulation = 0;
+
+ // 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 += this.length & 0xffff;
+ }
+
+ for (int i = 0; i < this.length / 2; ++i) {
+ accumulation += 0xffff & bb.getShort();
+ }
+ // pad to an even number of shorts
+ if (this.length % 2 > 0) {
+ accumulation += (bb.get() & 0xff) << 8;
+ }
+
+ accumulation = ((accumulation >> 16) & 0xffff)
+ + (accumulation & 0xffff);
+ this.checksum = (short) (~accumulation & 0xffff);
+ bb.putShort(6, this.checksum);
+ }
+ return data;
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ final int prime = 5807;
+ int result = super.hashCode();
+ result = prime * result + checksum;
+ result = prime * result + destinationPort;
+ result = prime * result + length;
+ result = prime * result + sourcePort;
+ return result;
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (!super.equals(obj))
+ return false;
+ if (!(obj instanceof UDP))
+ return false;
+ UDP other = (UDP) obj;
+ if (checksum != other.checksum)
+ return false;
+ if (destinationPort != other.destinationPort)
+ return false;
+ if (length != other.length)
+ return false;
+ if (sourcePort != other.sourcePort)
+ return false;
+ return true;
+ }
+
+ @Override
+ public IPacket deserialize(byte[] data, int offset, int length) {
+ ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
+ this.sourcePort = bb.getShort();
+ this.destinationPort = bb.getShort();
+ this.length = bb.getShort();
+ this.checksum = bb.getShort();
+
+ if (UDP.decodeMap.containsKey(this.destinationPort)) {
+ try {
+ this.payload = UDP.decodeMap.get(this.destinationPort).getConstructor().newInstance();
+ } catch (Exception e) {
+ throw new RuntimeException("Failure instantiating class", e);
+ }
+ } else if (UDP.decodeMap.containsKey(this.sourcePort)) {
+ try {
+ this.payload = UDP.decodeMap.get(this.sourcePort).getConstructor().newInstance();
+ } catch (Exception e) {
+ throw new RuntimeException("Failure instantiating class", e);
+ }
+ } else {
+ this.payload = new Data();
+ }
+ this.payload = payload.deserialize(data, bb.position(), bb.limit()-bb.position());
+ this.payload.setParent(this);
+ return this;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/packetstreamer/PacketStreamerClient.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packetstreamer/PacketStreamerClient.java
new file mode 100644
index 0000000..abed853
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packetstreamer/PacketStreamerClient.java
@@ -0,0 +1,93 @@
+package net.floodlightcontroller.packetstreamer;
+
+import net.floodlightcontroller.packetstreamer.thrift.*;
+
+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.transport.TTransportException;
+import org.apache.thrift.protocol.TBinaryProtocol;
+import org.apache.thrift.protocol.TProtocol;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The PacketStreamer Sample Client.
+ */
+public class PacketStreamerClient {
+ protected static Logger log = LoggerFactory.getLogger(PacketStreamerClient.class);
+
+ /**
+ * Main function entry point;
+ * @param args
+ */
+ public static void main(String [] args) {
+ try {
+ int serverPort = Integer.parseInt(System.getProperty("net.floodlightcontroller.packetstreamer.port", "9090"));
+ TTransport transport;
+ transport = new TFramedTransport(new TSocket("localhost", serverPort));
+ transport.open();
+
+
+ TProtocol protocol = new TBinaryProtocol(transport);
+ PacketStreamer.Client client = new PacketStreamer.Client(protocol);
+
+ sendPackets(client, (short)2, OFMessageType.PACKET_IN, true);
+ log.debug("Terminate session1");
+ client.terminateSession("session1");
+
+ transport.close();
+ } catch (TException x) {
+ x.printStackTrace();
+ }
+ }
+
+ /**
+ * Send test packets of the given OFMessageType to the packetstreamer server;
+ * @param client Packetstreamer client object
+ * @param numPackets number of test packets to be sent
+ * @param ofType OFMessageType of the test packets
+ * @param sync true if send with synchronous interface, false for asynchronous interface
+ * @throws TException
+ */
+ private static void sendPackets(PacketStreamer.Client client, short numPackets, OFMessageType ofType, boolean sync)
+ throws TException {
+ while (numPackets-- > 0) {
+ Message msg = new Message();
+ Packet packet = new Packet();
+
+ List<String> sids = new ArrayList<String>();
+ sids.add("session1");
+ sids.add("session2");
+ msg.setSessionIDs(sids);
+ packet.setMessageType(ofType);
+ long sw_dpid = numPackets/40 + 1;
+ packet.setSwPortTuple(new SwitchPortTuple(sw_dpid, (short)(numPackets - (sw_dpid-1)*40)));
+
+ String strData = "New data, sequence " + numPackets;
+ packet.setData(strData.getBytes());
+ msg.setPacket(packet);
+
+ try {
+ if (sync) {
+ client.pushMessageSync(msg);
+ log.debug("Send packet sync: " + msg.toString());
+ } else {
+ client.pushMessageAsync(msg);
+ log.debug("Send packet sync: " + msg.toString());
+ }
+ } catch (TTransportException e) {
+ log.error(e.toString());
+ }
+
+ try {
+ Thread.sleep(100);
+ } catch (Exception e) {}
+ }
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/packetstreamer/PacketStreamerHandler.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packetstreamer/PacketStreamerHandler.java
new file mode 100644
index 0000000..903295e
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packetstreamer/PacketStreamerHandler.java
@@ -0,0 +1,213 @@
+package net.floodlightcontroller.packetstreamer;
+
+import net.floodlightcontroller.core.annotations.LogMessageCategory;
+import net.floodlightcontroller.core.annotations.LogMessageDoc;
+import net.floodlightcontroller.core.annotations.LogMessageDocs;
+import net.floodlightcontroller.packetstreamer.thrift.*;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.List;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.LinkedBlockingQueue;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The PacketStreamer handler class that implements the service APIs.
+ */
+@LogMessageCategory("OpenFlow Message Tracing")
+public class PacketStreamerHandler implements PacketStreamer.Iface {
+
+ /**
+ * The queue wrapper class that contains the queue for the streamed packets.
+ */
+ protected class SessionQueue {
+ protected BlockingQueue<ByteBuffer> pQueue;
+
+ /**
+ * The queue wrapper constructor
+ */
+ public SessionQueue() {
+ this.pQueue = new LinkedBlockingQueue<ByteBuffer>();
+ }
+
+ /**
+ * The access method to get to the internal queue.
+ */
+ public BlockingQueue<ByteBuffer> getQueue() {
+ return this.pQueue;
+ }
+ }
+
+ /**
+ * The class logger object
+ */
+ protected static Logger log =
+ LoggerFactory.getLogger(PacketStreamerServer.class);
+
+ /**
+ * A sessionId-to-queue mapping
+ */
+ protected Map<String, SessionQueue> msgQueues;
+
+ /**
+ * The handler's constructor
+ */
+ public PacketStreamerHandler() {
+ this.msgQueues = new ConcurrentHashMap<String, SessionQueue>();
+ }
+
+ /**
+ * The implementation for getPackets() function.
+ * This is a blocking API.
+ *
+ * @param sessionid
+ * @return A list of packets associated with the session
+ */
+ @Override
+ @LogMessageDocs({
+ @LogMessageDoc(level="ERROR",
+ message="Interrupted while waiting for session start",
+ explanation="The thread was interrupted waiting " +
+ "for the packet streamer session to start",
+ recommendation=LogMessageDoc.CHECK_CONTROLLER),
+ @LogMessageDoc(level="ERROR",
+ message="Interrupted while waiting for packets",
+ explanation="The thread was interrupted waiting " +
+ "for packets",
+ recommendation=LogMessageDoc.CHECK_CONTROLLER)
+ })
+ public List<ByteBuffer> getPackets(String sessionid)
+ throws org.apache.thrift.TException {
+ List<ByteBuffer> packets = new ArrayList<ByteBuffer>();
+ int count = 0;
+
+ while (!msgQueues.containsKey(sessionid) && count++ < 100) {
+ log.debug("Queue for session {} doesn't exist yet.", sessionid);
+ try {
+ Thread.sleep(100); // Wait 100 ms to check again.
+ } catch (InterruptedException e) {
+ log.error("Interrupted while waiting for session start");
+ }
+ }
+
+ if (count < 100) {
+ SessionQueue pQueue = msgQueues.get(sessionid);
+ BlockingQueue<ByteBuffer> queue = pQueue.getQueue();
+ // Block if queue is empty
+ try {
+ packets.add(queue.take());
+ queue.drainTo(packets);
+ } catch (InterruptedException e) {
+ log.error("Interrupted while waiting for packets");
+ }
+ }
+
+ return packets;
+ }
+
+ /**
+ * The implementation for pushMessageSync() function.
+ *
+ * @param msg
+ * @return 1 for success, 0 for failure
+ * @throws TException
+ */
+ @Override
+ @LogMessageDocs({
+ @LogMessageDoc(level="ERROR",
+ message="Could not push empty message",
+ explanation="An empty message was sent to the packet streamer",
+ recommendation=LogMessageDoc.REPORT_CONTROLLER_BUG),
+ @LogMessageDoc(level="ERROR",
+ message="queue for session {sessionId} is null",
+ explanation="The queue for the packet streamer session " +
+ "is missing",
+ recommendation=LogMessageDoc.REPORT_CONTROLLER_BUG)
+ })
+
+ public int pushMessageSync(Message msg)
+ throws org.apache.thrift.TException {
+
+ if (msg == null) {
+ log.error("Could not push empty message");
+ return 0;
+ }
+
+ List<String> sessionids = msg.getSessionIDs();
+ for (String sid : sessionids) {
+ SessionQueue pQueue = null;
+
+ if (!msgQueues.containsKey(sid)) {
+ pQueue = new SessionQueue();
+ msgQueues.put(sid, pQueue);
+ } else {
+ pQueue = msgQueues.get(sid);
+ }
+
+ log.debug("pushMessageSync: SessionId: " + sid +
+ " Receive a message, " + msg.toString() + "\n");
+ ByteBuffer bb = ByteBuffer.wrap(msg.getPacket().getData());
+ //ByteBuffer dst = ByteBuffer.wrap(msg.getPacket().toString().getBytes());
+ BlockingQueue<ByteBuffer> queue = pQueue.getQueue();
+ if (queue != null) {
+ if (!queue.offer(bb)) {
+ log.error("Failed to queue message for session: " + sid);
+ } else {
+ log.debug("insert a message to session: " + sid);
+ }
+ } else {
+ log.error("queue for session {} is null", sid);
+ }
+ }
+
+ return 1;
+ }
+
+ /**
+ * The implementation for pushMessageAsync() function.
+ *
+ * @param msg
+ * @throws TException
+ */
+ @Override
+ public void pushMessageAsync(Message msg)
+ throws org.apache.thrift.TException {
+ pushMessageSync(msg);
+ return;
+ }
+
+ /**
+ * The implementation for terminateSession() function.
+ * It removes the session to queue association.
+ * @param sessionid
+ * @throws TException
+ */
+ @Override
+ public void terminateSession(String sessionid)
+ throws org.apache.thrift.TException {
+ if (!msgQueues.containsKey(sessionid)) {
+ return;
+ }
+
+ SessionQueue pQueue = msgQueues.get(sessionid);
+
+ log.debug("terminateSession: SessionId: " + sessionid + "\n");
+ String data = "FilterTimeout";
+ ByteBuffer bb = ByteBuffer.wrap(data.getBytes());
+ BlockingQueue<ByteBuffer> queue = pQueue.getQueue();
+ if (queue != null) {
+ if (!queue.offer(bb)) {
+ log.error("Failed to queue message for session: " + sessionid);
+ }
+ msgQueues.remove(sessionid);
+ } else {
+ log.error("queue for session {} is null", sessionid);
+ }
+ }
+}
+
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/packetstreamer/PacketStreamerServer.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packetstreamer/PacketStreamerServer.java
new file mode 100644
index 0000000..4a425e0
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/packetstreamer/PacketStreamerServer.java
@@ -0,0 +1,72 @@
+package net.floodlightcontroller.packetstreamer;
+
+import org.apache.thrift.protocol.TBinaryProtocol;
+import org.apache.thrift.server.TServer;
+import org.apache.thrift.server.THsHaServer;
+import org.apache.thrift.transport.TFramedTransport;
+import org.apache.thrift.transport.TNonblockingServerSocket;
+import org.apache.thrift.transport.TNonblockingServerTransport;
+
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import net.floodlightcontroller.core.annotations.LogMessageCategory;
+// Generated code
+import net.floodlightcontroller.packetstreamer.thrift.*;
+
+/**
+ * The PacketStreamer Server that brokers the packet streaming service.
+ */
+@LogMessageCategory("OpenFlow Message Tracing")
+public class PacketStreamerServer {
+ protected static Logger log = LoggerFactory.getLogger(PacketStreamerServer.class);
+ protected static int port = 9090;
+ protected static PacketStreamerHandler handler;
+ protected static PacketStreamer.Processor<PacketStreamerHandler> processor;
+
+
+ /**
+ * Main function entry point;
+ * @param args
+ */
+ public static void main(String [] args) {
+ try {
+ port = Integer.parseInt(System.getProperty("net.floodlightcontroller.packetstreamer.port", "9090"));
+
+ handler = new PacketStreamerHandler();
+ processor = new PacketStreamer.Processor<PacketStreamerHandler>(handler);
+
+ Runnable simple = new Runnable() {
+ public void run() {
+ hshaServer(processor);
+ }
+ };
+
+ new Thread(simple).start();
+ } catch (Exception x) {
+ x.printStackTrace();
+ }
+ }
+
+
+ /**
+ * The function to create a thrift Half-Sync and Half-Async Server.
+ * @param processor
+ */
+ public static void hshaServer(PacketStreamer.Processor<PacketStreamerHandler> processor) {
+ try {
+ TNonblockingServerTransport serverTransport = new TNonblockingServerSocket(port);
+ THsHaServer.Args args = new THsHaServer.Args(serverTransport);
+ args.processor(processor);
+ args.transportFactory(new TFramedTransport.Factory());
+ args.protocolFactory(new TBinaryProtocol.Factory(true, true));
+ TServer server = new THsHaServer(args);
+
+ log.info("Starting the packetstreamer hsha server on port {} ...", port);
+ server.serve();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/perfmon/CumulativeTimeBucket.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/perfmon/CumulativeTimeBucket.java
new file mode 100644
index 0000000..e76253d
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/perfmon/CumulativeTimeBucket.java
@@ -0,0 +1,122 @@
+package net.floodlightcontroller.perfmon;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.codehaus.jackson.map.annotate.JsonSerialize;
+
+import net.floodlightcontroller.core.IOFMessageListener;
+
+@JsonSerialize(using=CumulativeTimeBucketJSONSerializer.class)
+public class CumulativeTimeBucket {
+ private long startTime_ns; // First pkt time-stamp in this bucket
+ private Map<Integer, OneComponentTime> compStats;
+ private long totalPktCnt;
+ private long totalProcTimeNs; // total processing time for one pkt in
+ private long sumSquaredProcTimeNs2;
+ private long maxTotalProcTimeNs;
+ private long minTotalProcTimeNs;
+ private long avgTotalProcTimeNs;
+ private long sigmaTotalProcTimeNs; // std. deviation
+
+ public long getStartTimeNs() {
+ return startTime_ns;
+ }
+
+ public long getTotalPktCnt() {
+ return totalPktCnt;
+ }
+
+ public long getAverageProcTimeNs() {
+ return avgTotalProcTimeNs;
+ }
+
+ public long getMinTotalProcTimeNs() {
+ return minTotalProcTimeNs;
+ }
+
+ public long getMaxTotalProcTimeNs() {
+ return maxTotalProcTimeNs;
+ }
+
+ public long getTotalSigmaProcTimeNs() {
+ return sigmaTotalProcTimeNs;
+ }
+
+ public int getNumComps() {
+ return compStats.values().size();
+ }
+
+ public Collection<OneComponentTime> getModules() {
+ return compStats.values();
+ }
+
+ public CumulativeTimeBucket(List<IOFMessageListener> listeners) {
+ compStats = new ConcurrentHashMap<Integer, OneComponentTime>(listeners.size());
+ for (IOFMessageListener l : listeners) {
+ OneComponentTime oct = new OneComponentTime(l);
+ compStats.put(oct.hashCode(), oct);
+ }
+ startTime_ns = System.nanoTime();
+ }
+
+ private void updateSquaredProcessingTime(long curTimeNs) {
+ sumSquaredProcTimeNs2 += (Math.pow(curTimeNs, 2));
+ }
+
+ /**
+ * Resets all counters and counters for each component time
+ */
+ public void reset() {
+ startTime_ns = System.nanoTime();
+ totalPktCnt = 0;
+ totalProcTimeNs = 0;
+ avgTotalProcTimeNs = 0;
+ sumSquaredProcTimeNs2 = 0;
+ maxTotalProcTimeNs = Long.MIN_VALUE;
+ minTotalProcTimeNs = Long.MAX_VALUE;
+ sigmaTotalProcTimeNs = 0;
+ for (OneComponentTime oct : compStats.values()) {
+ oct.resetAllCounters();
+ }
+ }
+
+ private void computeSigma() {
+ // Computes std. deviation from the sum of count numbers and from
+ // the sum of the squares of count numbers
+ double temp = totalProcTimeNs;
+ temp = Math.pow(temp, 2) / totalPktCnt;
+ temp = (sumSquaredProcTimeNs2 - temp) / totalPktCnt;
+ sigmaTotalProcTimeNs = (long) Math.sqrt(temp);
+ }
+
+ public void computeAverages() {
+ // Must be called last to, needs latest info
+ computeSigma();
+
+ for (OneComponentTime oct : compStats.values()) {
+ oct.computeSigma();
+ }
+ }
+
+ public void updatePerPacketCounters(long procTimeNs) {
+ totalPktCnt++;
+ totalProcTimeNs += procTimeNs;
+ avgTotalProcTimeNs = totalProcTimeNs / totalPktCnt;
+ updateSquaredProcessingTime(procTimeNs);
+
+ if (procTimeNs > maxTotalProcTimeNs) {
+ maxTotalProcTimeNs = procTimeNs;
+ }
+
+ if (procTimeNs < minTotalProcTimeNs) {
+ minTotalProcTimeNs = procTimeNs;
+ }
+ }
+
+ public void updateOneComponent(IOFMessageListener l, long procTimeNs) {
+ compStats.get(l.hashCode()).updatePerPacketCounters(procTimeNs);
+ }
+}
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/perfmon/CumulativeTimeBucketJSONSerializer.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/perfmon/CumulativeTimeBucketJSONSerializer.java
new file mode 100644
index 0000000..e492777
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/perfmon/CumulativeTimeBucketJSONSerializer.java
@@ -0,0 +1,47 @@
+package net.floodlightcontroller.perfmon;
+
+import java.io.IOException;
+import java.sql.Timestamp;
+
+
+import org.codehaus.jackson.JsonGenerator;
+import org.codehaus.jackson.JsonProcessingException;
+import org.codehaus.jackson.map.JsonSerializer;
+import org.codehaus.jackson.map.SerializerProvider;
+
+public class CumulativeTimeBucketJSONSerializer
+ extends JsonSerializer<CumulativeTimeBucket> {
+ /**
+ * Performs the serialization of a OneComponentTime object
+ */
+ @Override
+ public void serialize(CumulativeTimeBucket ctb,
+ JsonGenerator jGen,
+ SerializerProvider serializer)
+ throws IOException, JsonProcessingException {
+ jGen.writeStartObject();
+ Timestamp ts = new Timestamp(ctb.getStartTimeNs()/1000000);
+ jGen.writeStringField("start-time", ts.toString());
+ jGen.writeStringField("current-time",
+ new Timestamp(System.currentTimeMillis()).toString());
+ jGen.writeNumberField("total-packets", ctb.getTotalPktCnt());
+ jGen.writeNumberField("average", ctb.getAverageProcTimeNs());
+ jGen.writeNumberField("min", ctb.getMinTotalProcTimeNs());
+ jGen.writeNumberField("max", ctb.getMaxTotalProcTimeNs());
+ jGen.writeNumberField("std-dev", ctb.getTotalSigmaProcTimeNs());
+ jGen.writeArrayFieldStart("modules");
+ for (OneComponentTime oct : ctb.getModules()) {
+ serializer.defaultSerializeValue(oct, jGen);
+ }
+ jGen.writeEndArray();
+ jGen.writeEndObject();
+ }
+
+ /**
+ * Tells SimpleModule that we are the serializer for OFMatch
+ */
+ @Override
+ public Class<CumulativeTimeBucket> handledType() {
+ return CumulativeTimeBucket.class;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/perfmon/IPktInProcessingTimeService.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/perfmon/IPktInProcessingTimeService.java
new file mode 100644
index 0000000..80dfda0
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/perfmon/IPktInProcessingTimeService.java
@@ -0,0 +1,37 @@
+package net.floodlightcontroller.perfmon;
+
+import java.util.List;
+
+import org.openflow.protocol.OFMessage;
+
+import net.floodlightcontroller.core.FloodlightContext;
+import net.floodlightcontroller.core.IOFMessageListener;
+import net.floodlightcontroller.core.IOFSwitch;
+import net.floodlightcontroller.core.module.IFloodlightService;
+
+public interface IPktInProcessingTimeService extends IFloodlightService {
+
+ /**
+ * Creates time buckets for a set of modules to measure their performance
+ * @param listeners The message listeners to create time buckets for
+ */
+ public void bootstrap(List<IOFMessageListener> listeners);
+
+ /**
+ * Stores a timestamp in ns. Used right before a service handles an
+ * OF message. Only stores if the service is enabled.
+ */
+ public void recordStartTimeComp(IOFMessageListener listener);
+
+ public void recordEndTimeComp(IOFMessageListener listener);
+
+ public void recordStartTimePktIn();
+
+ public void recordEndTimePktIn(IOFSwitch sw, OFMessage m, FloodlightContext cntx);
+
+ public boolean isEnabled();
+
+ public void setEnabled(boolean enabled);
+
+ public CumulativeTimeBucket getCtb();
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/perfmon/NullPktInProcessingTime.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/perfmon/NullPktInProcessingTime.java
new file mode 100644
index 0000000..3d9504b
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/perfmon/NullPktInProcessingTime.java
@@ -0,0 +1,109 @@
+package net.floodlightcontroller.perfmon;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.openflow.protocol.OFMessage;
+
+import net.floodlightcontroller.core.FloodlightContext;
+import net.floodlightcontroller.core.IOFMessageListener;
+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;
+
+/**
+ * An IPktInProcessingTimeService implementation that does nothing.
+ * This is used mainly for performance testing or if you don't
+ * want to use the IPktInProcessingTimeService features.
+ * @author alexreimers
+ *
+ */
+public class NullPktInProcessingTime
+ implements IFloodlightModule, IPktInProcessingTimeService {
+
+ private CumulativeTimeBucket ctb;
+ private boolean inited = false;
+
+ public Collection<Class<? extends IFloodlightService>> getModuleServices() {
+ Collection<Class<? extends IFloodlightService>> l =
+ new ArrayList<Class<? extends IFloodlightService>>();
+ l.add(IPktInProcessingTimeService.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(IPktInProcessingTimeService.class, this);
+ return m;
+ }
+
+ @Override
+ public Collection<Class<? extends IFloodlightService>> getModuleDependencies() {
+ // We don't have any dependencies
+ return null;
+ }
+
+ @Override
+ public void init(FloodlightModuleContext context)
+ throws FloodlightModuleException {
+
+ }
+
+ @Override
+ public void startUp(FloodlightModuleContext context) {
+ // no-op
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return false;
+ }
+
+ @Override
+ public void bootstrap(List<IOFMessageListener> listeners) {
+ if (!inited)
+ ctb = new CumulativeTimeBucket(listeners);
+ }
+
+ @Override
+ public void recordStartTimeComp(IOFMessageListener listener) {
+
+ }
+
+ @Override
+ public void recordEndTimeComp(IOFMessageListener listener) {
+
+ }
+
+ @Override
+ public void recordStartTimePktIn() {
+
+ }
+
+ @Override
+ public void recordEndTimePktIn(IOFSwitch sw, OFMessage m,
+ FloodlightContext cntx) {
+
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+
+ }
+
+ @Override
+ public CumulativeTimeBucket getCtb() {
+ return ctb;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/perfmon/OneComponentTime.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/perfmon/OneComponentTime.java
new file mode 100644
index 0000000..3e9734b
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/perfmon/OneComponentTime.java
@@ -0,0 +1,129 @@
+package net.floodlightcontroller.perfmon;
+
+import org.codehaus.jackson.annotate.JsonProperty;
+
+import net.floodlightcontroller.core.IOFMessageListener;
+
+/**
+ * Holds OF message processing time information for one IFloodlightModule.
+ * @author Subrata
+ */
+public class OneComponentTime {
+ private int compId; // hascode of IOFMessageListener
+ private String compName;
+ private int pktCnt;
+ // all times in nanoseconds
+ private long totalProcTimeNs;
+ private long sumSquaredProcTimeNs2; // squared
+ private long maxProcTimeNs;
+ private long minProcTimeNs;
+ private long avgProcTimeNs;
+ private long sigmaProcTimeNs; // std. deviation
+
+ public OneComponentTime(IOFMessageListener module) {
+ compId = module.hashCode();
+ compName = module.getClass().getCanonicalName();
+ resetAllCounters();
+ }
+
+ public void resetAllCounters() {
+ maxProcTimeNs = Long.MIN_VALUE;
+ minProcTimeNs = Long.MAX_VALUE;
+ pktCnt = 0;
+ totalProcTimeNs = 0;
+ sumSquaredProcTimeNs2 = 0;
+ avgProcTimeNs = 0;
+ sigmaProcTimeNs = 0;
+ }
+
+ @JsonProperty("module-name")
+ public String getCompName() {
+ return compName;
+ }
+
+ @JsonProperty("num-packets")
+ public int getPktCnt() {
+ return pktCnt;
+ }
+
+ @JsonProperty("total")
+ public long getSumProcTimeNs() {
+ return totalProcTimeNs;
+ }
+
+ @JsonProperty("max")
+ public long getMaxProcTimeNs() {
+ return maxProcTimeNs;
+ }
+
+ @JsonProperty("min")
+ public long getMinProcTimeNs() {
+ return minProcTimeNs;
+ }
+
+ @JsonProperty("average")
+ public long getAvgProcTimeNs() {
+ return avgProcTimeNs;
+ }
+
+ @JsonProperty("std-dev")
+ public long getSigmaProcTimeNs() {
+ return sigmaProcTimeNs;
+ }
+
+ @JsonProperty("average-squared")
+ public long getSumSquaredProcTimeNs() {
+ return sumSquaredProcTimeNs2;
+ }
+
+ // Methods used to update the counters
+
+ private void increasePktCount() {
+ pktCnt++;
+ }
+
+ private void updateTotalProcessingTime(long procTimeNs) {
+ totalProcTimeNs += procTimeNs;
+ }
+
+ private void updateAvgProcessTime() {
+ avgProcTimeNs = totalProcTimeNs / pktCnt;
+ }
+
+ private void updateSquaredProcessingTime(long procTimeNs) {
+ sumSquaredProcTimeNs2 += (Math.pow(procTimeNs, 2));
+ }
+
+ private void calculateMinProcTime(long curTimeNs) {
+ if (curTimeNs < minProcTimeNs)
+ minProcTimeNs = curTimeNs;
+ }
+
+ private void calculateMaxProcTime(long curTimeNs) {
+ if (curTimeNs > maxProcTimeNs)
+ maxProcTimeNs = curTimeNs;
+ }
+
+ public void computeSigma() {
+ // Computes std. deviation from the sum of count numbers and from
+ // the sum of the squares of count numbers
+ double temp = totalProcTimeNs;
+ temp = Math.pow(temp, 2) / pktCnt;
+ temp = (sumSquaredProcTimeNs2 - temp) / pktCnt;
+ sigmaProcTimeNs = (long) Math.sqrt(temp);
+ }
+
+ public void updatePerPacketCounters(long procTimeNs) {
+ increasePktCount();
+ updateTotalProcessingTime(procTimeNs);
+ calculateMinProcTime(procTimeNs);
+ calculateMaxProcTime(procTimeNs);
+ updateAvgProcessTime();
+ updateSquaredProcessingTime(procTimeNs);
+ }
+
+ @Override
+ public int hashCode() {
+ return compId;
+ }
+}
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/perfmon/PerfMonDataResource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/perfmon/PerfMonDataResource.java
new file mode 100644
index 0000000..c43708d
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/perfmon/PerfMonDataResource.java
@@ -0,0 +1,33 @@
+package net.floodlightcontroller.perfmon;
+
+import org.restlet.data.Status;
+import org.restlet.resource.Get;
+import org.restlet.resource.ServerResource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * Return the performance monitoring data for the get rest api call
+ * @author subrata
+ */
+public class PerfMonDataResource extends ServerResource {
+ protected static Logger logger = LoggerFactory.getLogger(PerfMonDataResource.class);
+
+ @Get("json")
+ public CumulativeTimeBucket handleApiQuery() {
+ IPktInProcessingTimeService pktinProcTime =
+ (IPktInProcessingTimeService)getContext().getAttributes().
+ get(IPktInProcessingTimeService.class.getCanonicalName());
+
+ setStatus(Status.SUCCESS_OK, "OK");
+ // Allocate output object
+ if (pktinProcTime.isEnabled()) {
+ CumulativeTimeBucket ctb = pktinProcTime.getCtb();
+ ctb.computeAverages();
+ return ctb;
+ }
+
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/perfmon/PerfMonToggleResource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/perfmon/PerfMonToggleResource.java
new file mode 100644
index 0000000..9ea1876
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/perfmon/PerfMonToggleResource.java
@@ -0,0 +1,28 @@
+package net.floodlightcontroller.perfmon;
+
+import org.restlet.data.Status;
+import org.restlet.resource.Get;
+import org.restlet.resource.ServerResource;
+
+public class PerfMonToggleResource extends ServerResource {
+
+ @Get("json")
+ public String retrieve() {
+ IPktInProcessingTimeService pktinProcTime =
+ (IPktInProcessingTimeService)getContext().getAttributes().
+ get(IPktInProcessingTimeService.class.getCanonicalName());
+
+ String param = ((String)getRequestAttributes().get("perfmonstate")).toLowerCase();
+ if (param.equals("reset")) {
+ pktinProcTime.getCtb().reset();
+ } else {
+ if (param.equals("enable") || param.equals("true")) {
+ pktinProcTime.setEnabled(true);
+ } else if (param.equals("disable") || param.equals("false")) {
+ pktinProcTime.setEnabled(false);
+ }
+ }
+ setStatus(Status.SUCCESS_OK, "OK");
+ return "{ \"enabled\" : " + pktinProcTime.isEnabled() + " }";
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/perfmon/PerfWebRoutable.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/perfmon/PerfWebRoutable.java
new file mode 100644
index 0000000..ace0bc8
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/perfmon/PerfWebRoutable.java
@@ -0,0 +1,23 @@
+package net.floodlightcontroller.perfmon;
+
+import org.restlet.Context;
+import org.restlet.Restlet;
+import org.restlet.routing.Router;
+
+import net.floodlightcontroller.restserver.RestletRoutable;
+
+public class PerfWebRoutable implements RestletRoutable {
+
+ @Override
+ public Restlet getRestlet(Context context) {
+ Router router = new Router(context);
+ router.attach("/data/json", PerfMonDataResource.class);
+ router.attach("/{perfmonstate}/json", PerfMonToggleResource.class); // enable, disable, or reset
+ return router;
+ }
+
+ @Override
+ public String basePath() {
+ return "/wm/performance";
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/perfmon/PktInProcessingTime.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/perfmon/PktInProcessingTime.java
new file mode 100644
index 0000000..639623b
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/perfmon/PktInProcessingTime.java
@@ -0,0 +1,205 @@
+/**
+ * Performance monitoring package
+ */
+package net.floodlightcontroller.perfmon;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import net.floodlightcontroller.core.FloodlightContext;
+import net.floodlightcontroller.core.IOFMessageListener;
+import net.floodlightcontroller.core.IOFSwitch;
+import net.floodlightcontroller.core.annotations.LogMessageCategory;
+import net.floodlightcontroller.core.annotations.LogMessageDoc;
+import net.floodlightcontroller.core.module.FloodlightModuleContext;
+import net.floodlightcontroller.core.module.FloodlightModuleException;
+import net.floodlightcontroller.core.module.IFloodlightModule;
+import net.floodlightcontroller.core.module.IFloodlightService;
+import net.floodlightcontroller.restserver.IRestApiService;
+
+import org.openflow.protocol.OFMessage;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This class contains a set of buckets (called time buckets as the
+ * primarily contain 'times' that are used in a circular way to
+ * store information on packet in processing time.
+ * Each bucket is meant to store the various processing time
+ * related data for a fixed duration.
+ * Buckets are reused to reduce garbage generation! Once the
+ * last bucket is used up the LRU bucket is reused.
+ *
+ * Naming convention for variable or constants
+ * variable_s : value in seconds
+ * variable_ms: value in milliseconds
+ * variable_us: value in microseconds
+ * variable_ns: value in nanoseconds
+ *
+ * Key Constants:
+ * ONE_BUCKET_DURATION_SECONDS_INT: time duration of each bucket
+ * BUCKET_SET_SIZE: Number of buckets
+ * TOT_PROC_TIME_WARN_THRESHOLD_US: if processing time for a packet
+ * exceeds this threshold then a warning LOG message is generated
+ * TOT_PROC_TIME_ALERT_THRESHOLD_US: same as above but an alert level
+ * syslog is generated instead
+ *
+ */
+@LogMessageCategory("Performance Monitoring")
+public class PktInProcessingTime
+ implements IFloodlightModule, IPktInProcessingTimeService {
+
+
+ // Our dependencies
+ private IRestApiService restApi;
+
+ protected long ptWarningThresholdInNano;
+
+ // DB storage tables
+ protected static final String ControllerTableName = "controller_controller";
+ public static final String COLUMN_ID = "id";
+ public static final String COLUMN_PERF_MON = "performance_monitor_feature";
+
+ protected static Logger logger =
+ LoggerFactory.getLogger(PktInProcessingTime.class);
+
+ protected boolean isEnabled = false;
+ protected boolean isInited = false;
+ // Maintains the time when the last packet was processed
+ protected long lastPktTime_ns;
+ private CumulativeTimeBucket ctb = null;
+
+
+ /***
+ * BUCKET_SET_SIZE buckets each holding 10s of processing time data, a total
+ * of 30*10s = 5mins of processing time data is maintained
+ */
+ protected static final int ONE_BUCKET_DURATION_SECONDS = 10;// seconds
+ protected static final long ONE_BUCKET_DURATION_NANOSECONDS =
+ ONE_BUCKET_DURATION_SECONDS * 1000000000;
+
+ @Override
+ public void bootstrap(List<IOFMessageListener> listeners) {
+ if (!isInited) {
+ ctb = new CumulativeTimeBucket(listeners);
+ isInited = true;
+ }
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return isEnabled && isInited;
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ this.isEnabled = enabled;
+ logger.debug("Setting module to " + isEnabled);
+ }
+
+ @Override
+ public CumulativeTimeBucket getCtb() {
+ return ctb;
+ }
+
+ private long startTimePktNs;
+ private long startTimeCompNs;
+ @Override
+ public void recordStartTimeComp(IOFMessageListener listener) {
+ if (isEnabled()) {
+ startTimeCompNs = System.nanoTime();
+ }
+ }
+
+ @Override
+ public void recordEndTimeComp(IOFMessageListener listener) {
+ if (isEnabled()) {
+ long procTime = System.nanoTime() - startTimeCompNs;
+ ctb.updateOneComponent(listener, procTime);
+ }
+ }
+
+ @Override
+ public void recordStartTimePktIn() {
+ if (isEnabled()) {
+ startTimePktNs = System.nanoTime();
+ }
+ }
+
+ @Override
+ @LogMessageDoc(level="WARN",
+ message="Time to process packet-in exceeded threshold: {}",
+ explanation="Time to process packet-in exceeded the configured " +
+ "performance threshold",
+ recommendation=LogMessageDoc.CHECK_CONTROLLER)
+ public void recordEndTimePktIn(IOFSwitch sw, OFMessage m, FloodlightContext cntx) {
+ if (isEnabled()) {
+ long procTimeNs = System.nanoTime() - startTimePktNs;
+ ctb.updatePerPacketCounters(procTimeNs);
+
+ if (ptWarningThresholdInNano > 0 &&
+ procTimeNs > ptWarningThresholdInNano) {
+ logger.warn("Time to process packet-in exceeded threshold: {}",
+ procTimeNs/1000);
+ }
+ }
+ }
+
+ // IFloodlightModule methods
+
+ @Override
+ public Collection<Class<? extends IFloodlightService>> getModuleServices() {
+ Collection<Class<? extends IFloodlightService>> l =
+ new ArrayList<Class<? extends IFloodlightService>>();
+ l.add(IPktInProcessingTimeService.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(IPktInProcessingTimeService.class, this);
+ return m;
+ }
+
+ @Override
+ public Collection<Class<? extends IFloodlightService>> getModuleDependencies() {
+ Collection<Class<? extends IFloodlightService>> l =
+ new ArrayList<Class<? extends IFloodlightService>>();
+ l.add(IRestApiService.class);
+ return l;
+ }
+
+ @Override
+ public void init(FloodlightModuleContext context)
+ throws FloodlightModuleException {
+ restApi = context.getServiceImpl(IRestApiService.class);
+ }
+
+ @Override
+ @LogMessageDoc(level="INFO",
+ message="Packet processing time threshold for warning" +
+ " set to {time} ms.",
+ explanation="Performance monitoring will log a warning if " +
+ "packet processing time exceeds the configured threshold")
+ public void startUp(FloodlightModuleContext context) {
+ // Add our REST API
+ restApi.addRestletRoutable(new PerfWebRoutable());
+
+ // TODO - Alex - change this to a config option
+ ptWarningThresholdInNano = Long.parseLong(System.getProperty(
+ "net.floodlightcontroller.core.PTWarningThresholdInMilli", "0")) * 1000000;
+ if (ptWarningThresholdInNano > 0) {
+ logger.info("Packet processing time threshold for warning" +
+ " set to {} ms.", ptWarningThresholdInNano/1000000);
+ }
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/restserver/IRestApiService.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/restserver/IRestApiService.java
new file mode 100644
index 0000000..d906795
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/restserver/IRestApiService.java
@@ -0,0 +1,16 @@
+package net.floodlightcontroller.restserver;
+
+import net.floodlightcontroller.core.module.IFloodlightService;
+
+public interface IRestApiService extends IFloodlightService {
+ /**
+ * Adds a REST API
+ * @param routeable
+ */
+ public void addRestletRoutable(RestletRoutable routable);
+
+ /**
+ * Runs the REST API server
+ */
+ public void run();
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/restserver/RestApiServer.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/restserver/RestApiServer.java
new file mode 100644
index 0000000..2ca8483
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/restserver/RestApiServer.java
@@ -0,0 +1,188 @@
+package net.floodlightcontroller.restserver;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.restlet.Application;
+import org.restlet.Component;
+import org.restlet.Context;
+import org.restlet.Request;
+import org.restlet.Response;
+import org.restlet.Restlet;
+import org.restlet.data.Protocol;
+import org.restlet.data.Reference;
+import org.restlet.data.Status;
+import org.restlet.ext.jackson.JacksonRepresentation;
+import org.restlet.representation.Representation;
+import org.restlet.routing.Filter;
+import org.restlet.routing.Router;
+import org.restlet.routing.Template;
+import org.restlet.service.StatusService;
+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;
+
+public class RestApiServer
+ implements IFloodlightModule, IRestApiService {
+ protected static Logger logger = LoggerFactory.getLogger(RestApiServer.class);
+ protected List<RestletRoutable> restlets;
+ protected FloodlightModuleContext fmlContext;
+ protected int restPort = 8080;
+
+ // ***********
+ // Application
+ // ***********
+
+ protected class RestApplication extends Application {
+ protected Context context;
+
+ public RestApplication() {
+ super(new Context());
+ this.context = getContext();
+ }
+
+ @Override
+ public Restlet createInboundRoot() {
+ Router baseRouter = new Router(context);
+ baseRouter.setDefaultMatchingMode(Template.MODE_STARTS_WITH);
+ for (RestletRoutable rr : restlets) {
+ baseRouter.attach(rr.basePath(), rr.getRestlet(context));
+ }
+
+ Filter slashFilter = new Filter() {
+ @Override
+ protected int beforeHandle(Request request, Response response) {
+ Reference ref = request.getResourceRef();
+ String originalPath = ref.getPath();
+ if (originalPath.contains("//"))
+ {
+ String newPath = originalPath.replaceAll("/+", "/");
+ ref.setPath(newPath);
+ }
+ return Filter.CONTINUE;
+ }
+
+ };
+ slashFilter.setNext(baseRouter);
+
+ return slashFilter;
+ }
+
+ public void run(FloodlightModuleContext fmlContext, int restPort) {
+ setStatusService(new StatusService() {
+ @Override
+ public Representation getRepresentation(Status status,
+ Request request,
+ Response response) {
+ return new JacksonRepresentation<Status>(status);
+ }
+ });
+
+ // Add everything in the module context to the rest
+ for (Class<? extends IFloodlightService> s : fmlContext.getAllServices()) {
+ if (logger.isTraceEnabled()) {
+ logger.trace("Adding {} for service {} into context",
+ s.getCanonicalName(), fmlContext.getServiceImpl(s));
+ }
+ context.getAttributes().put(s.getCanonicalName(),
+ fmlContext.getServiceImpl(s));
+ }
+
+ // Start listening for REST requests
+ try {
+ final Component component = new Component();
+ component.getServers().add(Protocol.HTTP, restPort);
+ component.getClients().add(Protocol.CLAP);
+ component.getDefaultHost().attach(this);
+ component.start();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ // ***************
+ // IRestApiService
+ // ***************
+
+ @Override
+ public void addRestletRoutable(RestletRoutable routable) {
+ restlets.add(routable);
+ }
+
+ @Override
+ public void run() {
+ if (logger.isDebugEnabled()) {
+ StringBuffer sb = new StringBuffer();
+ sb.append("REST API routables: ");
+ for (RestletRoutable routable : restlets) {
+ sb.append(routable.getClass().getSimpleName());
+ sb.append(" (");
+ sb.append(routable.basePath());
+ sb.append("), ");
+ }
+ logger.debug(sb.toString());
+ }
+
+ RestApplication restApp = new RestApplication();
+ restApp.run(fmlContext, restPort);
+ }
+
+ // *****************
+ // IFloodlightModule
+ // *****************
+
+ @Override
+ public Collection<Class<? extends IFloodlightService>> getModuleServices() {
+ Collection<Class<? extends IFloodlightService>> services =
+ new ArrayList<Class<? extends IFloodlightService>>(1);
+ services.add(IRestApiService.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(IRestApiService.class, this);
+ return m;
+ }
+
+ @Override
+ public Collection<Class<? extends IFloodlightService>> getModuleDependencies() {
+ // We don't have any
+ return null;
+ }
+
+ @Override
+ public void init(FloodlightModuleContext context)
+ throws FloodlightModuleException {
+ // This has to be done here since we don't know what order the
+ // startUp methods will be called
+ this.restlets = new ArrayList<RestletRoutable>();
+ this.fmlContext = context;
+
+ // read our config options
+ Map<String, String> configOptions = context.getConfigParams(this);
+ String port = configOptions.get("port");
+ if (port != null) {
+ restPort = Integer.parseInt(port);
+ }
+ logger.debug("REST port set to {}", restPort);
+ }
+
+ @Override
+ public void startUp(FloodlightModuleContext Context) {
+ // no-op
+ }
+}
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/restserver/RestletRoutable.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/restserver/RestletRoutable.java
new file mode 100644
index 0000000..cb7dfce
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/restserver/RestletRoutable.java
@@ -0,0 +1,40 @@
+/**
+* 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.restserver;
+
+import org.restlet.Context;
+import org.restlet.Restlet;
+
+/**
+ * Register a set of REST resources with the central controller
+ * @author readams
+ */
+public interface RestletRoutable {
+ /**
+ * Get the restlet that will map to the resources
+ * @param context the context for constructing the restlet
+ * @return the restlet
+ */
+ Restlet getRestlet(Context context);
+
+ /**
+ * Get the base path URL where the router should be registered
+ * @return the base path URL where the router should be registered
+ */
+ String basePath();
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/routing/BroadcastTree.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/routing/BroadcastTree.java
new file mode 100644
index 0000000..0c3703c
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/routing/BroadcastTree.java
@@ -0,0 +1,67 @@
+/**
+* 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.routing;
+import java.util.HashMap;
+
+import net.floodlightcontroller.routing.Link;
+
+import org.openflow.util.HexString;
+
+public class BroadcastTree {
+ protected HashMap<Long, Link> links;
+ protected HashMap<Long, Integer> costs;
+
+ public BroadcastTree() {
+ links = new HashMap<Long, Link>();
+ costs = new HashMap<Long, Integer>();
+ }
+
+ public BroadcastTree(HashMap<Long, Link> links, HashMap<Long, Integer> costs) {
+ this.links = links;
+ this.costs = costs;
+ }
+
+ public Link getTreeLink(long node) {
+ return links.get(node);
+ }
+
+ public int getCost(long node) {
+ if (costs.get(node) == null) return -1;
+ return (costs.get(node));
+ }
+
+ public HashMap<Long, Link> getLinks() {
+ return links;
+ }
+
+ public void addTreeLink(long 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) + "]");
+ }
+ return sb.toString();
+ }
+
+ public HashMap<Long, Integer> getCosts() {
+ return costs;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/routing/ForwardingBase.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/routing/ForwardingBase.java
new file mode 100644
index 0000000..22312c1
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/routing/ForwardingBase.java
@@ -0,0 +1,692 @@
+/**
+ * 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.routing;
+
+import java.io.IOException;
+import java.util.EnumSet;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import net.floodlightcontroller.core.FloodlightContext;
+import net.floodlightcontroller.core.IFloodlightProviderService;
+import net.floodlightcontroller.core.IOFMessageListener;
+import net.floodlightcontroller.core.IOFSwitch;
+import net.floodlightcontroller.core.annotations.LogMessageCategory;
+import net.floodlightcontroller.core.annotations.LogMessageDoc;
+import net.floodlightcontroller.core.annotations.LogMessageDocs;
+import net.floodlightcontroller.core.util.AppCookie;
+import net.floodlightcontroller.counter.ICounterStoreService;
+import net.floodlightcontroller.devicemanager.IDevice;
+import net.floodlightcontroller.devicemanager.IDeviceListener;
+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;
+import net.floodlightcontroller.routing.Route;
+import net.floodlightcontroller.topology.ITopologyService;
+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.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Abstract base class for implementing a forwarding module. Forwarding is
+ * responsible for programming flows to a switch in response to a policy
+ * decision.
+ */
+@LogMessageCategory("Flow Programming")
+public abstract class ForwardingBase
+ implements IOFMessageListener, IDeviceListener {
+
+ protected static Logger log =
+ LoggerFactory.getLogger(ForwardingBase.class);
+
+ protected static int OFMESSAGE_DAMPER_CAPACITY = 50000; // 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
+
+ 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
+ public 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() {
+ deviceManager.addListener(this);
+ 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.getSwitches().get(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)) {
+ 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.updatePktOutFMCounterStore(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();
+ }
+
+ // 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, match, pi, 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
+ * 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)
+ })
+ public void pushPacket(IPacket packet,
+ IOFSwitch sw,
+ int bufferId,
+ short inPort,
+ short outPort,
+ FloodlightContext cntx,
+ boolean flush) {
+
+
+ if (log.isTraceEnabled()) {
+ log.trace("PacketOut srcSwitch={} inPort={} outPort={}",
+ new Object[] {sw, inPort, outPort});
+ }
+
+ 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);
+
+ // set buffer_id, in_port
+ po.setBufferId(bufferId);
+ po.setInPort(inPort);
+
+ // set data - only if buffer_id == -1
+ if (po.getBufferId() == OFPacketOut.BUFFER_ID_NONE) {
+ if (packet == null) {
+ log.error("BufferId is not set and packet data is null. " +
+ "Cannot send packetOut. " +
+ "srcSwitch={} inPort={} outPort={}",
+ new Object[] {sw, inPort, outPort});
+ return;
+ }
+ byte[] packetData = packet.serialize();
+ poLength += packetData.length;
+ po.setPacketData(packetData);
+ }
+
+ po.setLength(poLength);
+
+ try {
+ counterStore.updatePktOutFMCounterStore(sw, po);
+ messageDamper.write(sw, po, cntx, flush);
+ } catch (IOException e) {
+ log.error("Failure writing packet out", e);
+ }
+ }
+
+ /**
+ * 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 match OFmatch
+ * @param pi packet-in
+ * @param outport output port
+ * @param cntx context of the packet
+ */
+ protected void pushPacket(IOFSwitch sw, OFMatch match, OFPacketIn pi,
+ short outport, FloodlightContext cntx) {
+
+ if (pi == null) {
+ return;
+ } else if (pi.getInPort() == outport){
+ log.warn("Packet out not sent as the outport matches inport. {}",
+ pi);
+ 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={}, match = {}, pi={}",
+ new Object[]{sw, match, pi});
+ return;
+ }
+ }
+
+ if (log.isTraceEnabled()) {
+ log.trace("PacketOut srcSwitch={} match={} pi={}",
+ new Object[] {sw, match, 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 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);
+ } else {
+ po.setBufferId(pi.getBufferId());
+ }
+
+ po.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);
+ }
+
+ po.setLength(poLength);
+
+ try {
+ counterStore.updatePktOutFMCounterStore(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.updatePktOutFMCounterStore(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.getSwitches().get(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(), new Long(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.setDataLayerSource(Ethernet.toByteArray(host_mac))
+ .setInputPort((short)inputPort)
+ .setWildcards(OFMatch.OFPFW_ALL & ~OFMatch.OFPFW_DL_SRC
+ & ~OFMatch.OFPFW_IN_PORT);
+ fm.setCookie(cookie)
+ .setHardTimeout((short) hardTimeout)
+ .setIdleTimeout(FLOWMOD_DEFAULT_IDLE_TIMEOUT)
+ .setHardTimeout(FLOWMOD_DEFAULT_HARD_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 void deviceAdded(IDevice device) {
+ // NOOP
+ }
+
+ @Override
+ public void deviceRemoved(IDevice device) {
+ // NOOP
+ }
+
+ @Override
+ public void deviceMoved(IDevice device) {
+ }
+
+ @Override
+ public void deviceIPV4AddrChanged(IDevice device) {
+
+ }
+
+ @Override
+ public void deviceVlanChanged(IDevice device) {
+
+ }
+
+ @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/ext/floodlight/src/main/java/net/floodlightcontroller/routing/IRoutingDecision.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/routing/IRoutingDecision.java
new file mode 100644
index 0000000..ed72706
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/routing/IRoutingDecision.java
@@ -0,0 +1,58 @@
+/**
+* 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.routing;
+
+import java.util.List;
+
+import net.floodlightcontroller.core.FloodlightContext;
+import net.floodlightcontroller.core.FloodlightContextStore;
+import net.floodlightcontroller.devicemanager.IDevice;
+import net.floodlightcontroller.devicemanager.SwitchPort;
+
+public interface IRoutingDecision {
+ public enum RoutingAction {
+ /*
+ * NONE: NO-OP, continue with the packet processing chain
+ * DROP: Drop this packet and this flow
+ * FORWARD: Forward this packet, and this flow, to the first (and only device) in getDestinationDevices(),
+ * if the destination is not known at this time, initiate a discovery action for it (e.g. ARP)
+ * FORWARD_OR_FLOOD: Forward this packet, and this flow, to the first (and only device) in getDestinationDevices(),
+ * if the destination is not known at this time, flood this packet on the source switch
+ * BROADCAST: Broadcast this packet on all links
+ * MULTICAST: Multicast this packet to all the interfaces and devices attached
+ */
+ NONE, DROP, FORWARD, FORWARD_OR_FLOOD, BROADCAST, MULTICAST
+ }
+
+ public static final FloodlightContextStore<IRoutingDecision> rtStore =
+ new FloodlightContextStore<IRoutingDecision>();
+ public static final String CONTEXT_DECISION =
+ "net.floodlightcontroller.routing.decision";
+
+ public void addToContext(FloodlightContext cntx);
+ public RoutingAction getRoutingAction();
+ public void setRoutingAction(RoutingAction action);
+ public SwitchPort getSourcePort();
+ public IDevice getSourceDevice();
+ public List<IDevice> getDestinationDevices();
+ public void addDestinationDevice(IDevice d);
+ public List<SwitchPort> getMulticastInterfaces();
+ public void setMulticastInterfaces(List<SwitchPort> lspt);
+ public Integer getWildcards();
+ public void setWildcards(Integer wildcards);
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/routing/IRoutingService.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/routing/IRoutingService.java
new file mode 100644
index 0000000..fcd70ad
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/routing/IRoutingService.java
@@ -0,0 +1,49 @@
+/**
+* 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.routing;
+
+import net.floodlightcontroller.core.module.IFloodlightService;
+import net.floodlightcontroller.routing.Route;
+
+public interface IRoutingService extends IFloodlightService {
+
+ /** Provides a route between src and dst that allows tunnels. */
+ public Route getRoute(long src, long dst);
+
+ /** Provides a route between src and dst, with option to allow or
+ * not allow tunnels in the path.*/
+ public Route getRoute(long src, long dst, boolean tunnelEnabled);
+
+
+ public Route getRoute(long srcId, short srcPort,
+ long dstId, short dstPort);
+
+ public Route getRoute(long srcId, short srcPort,
+ long dstId, short dstPort,
+ boolean tunnelEnabled);
+
+ /** Check if a route exists between src and dst, including tunnel links
+ * in the path.
+ */
+ public boolean routeExists(long src, long 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);
+}
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/routing/Link.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/routing/Link.java
new file mode 100755
index 0000000..7958596
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/routing/Link.java
@@ -0,0 +1,122 @@
+/**
+* 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.routing;
+
+import net.floodlightcontroller.core.web.serializers.DPIDSerializer;
+import net.floodlightcontroller.core.web.serializers.UShortSerializer;
+
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.codehaus.jackson.map.annotate.JsonSerialize;
+import org.openflow.util.HexString;
+
+public class Link {
+ private long src;
+ private short srcPort;
+ private long dst;
+ private short dstPort;
+
+
+ public Link(long srcId, short srcPort, long dstId, short 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;
+ }
+
+ @JsonProperty("src-switch")
+ @JsonSerialize(using=DPIDSerializer.class)
+ public long getSrc() {
+ return src;
+ }
+
+ @JsonProperty("src-port")
+ @JsonSerialize(using=UShortSerializer.class)
+ public short getSrcPort() {
+ return srcPort;
+ }
+
+ @JsonProperty("dst-switch")
+ @JsonSerialize(using=DPIDSerializer.class)
+ public long getDst() {
+ return dst;
+ }
+ @JsonProperty("dst-port")
+ @JsonSerialize(using=UShortSerializer.class)
+ public short getDstPort() {
+ return dstPort;
+ }
+
+ @Override
+ 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;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ Link other = (Link) obj;
+ if (dst != other.dst)
+ return false;
+ if (dstPort != other.dstPort)
+ return false;
+ if (src != other.src)
+ return false;
+ if (srcPort != other.srcPort)
+ return false;
+ return true;
+ }
+
+
+ @Override
+ public String toString() {
+ return "Link [src=" + HexString.toHexString(this.src)
+ + " outPort="
+ + (srcPort & 0xffff)
+ + ", dst=" + HexString.toHexString(this.dst)
+ + ", inPort="
+ + (dstPort & 0xffff)
+ + "]";
+ }
+
+ public String toKeyString() {
+ return (HexString.toHexString(this.src) + "|" +
+ (this.srcPort & 0xffff) + "|" +
+ HexString.toHexString(this.dst) + "|" +
+ (this.dstPort & 0xffff) );
+ }
+}
+
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/routing/Route.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/routing/Route.java
new file mode 100755
index 0000000..211a924
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/routing/Route.java
@@ -0,0 +1,117 @@
+/**
+* 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.routing;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import net.floodlightcontroller.topology.NodePortTuple;
+
+/**
+ * Represents a route between two switches
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class Route implements Comparable<Route> {
+ protected RouteId id;
+ protected List<NodePortTuple> switchPorts;
+
+ public Route(RouteId id, List<NodePortTuple> switchPorts) {
+ super();
+ this.id = id;
+ this.switchPorts = switchPorts;
+ }
+
+ public Route(Long src, Long dst) {
+ super();
+ this.id = new RouteId(src, dst);
+ this.switchPorts = new ArrayList<NodePortTuple>();
+ }
+
+ /**
+ * @return the id
+ */
+ public RouteId getId() {
+ return id;
+ }
+
+ /**
+ * @param id the id to set
+ */
+ public void setId(RouteId id) {
+ this.id = id;
+ }
+
+ /**
+ * @return the path
+ */
+ public List<NodePortTuple> getPath() {
+ return switchPorts;
+ }
+
+ /**
+ * @param path the path to set
+ */
+ public void setPath(List<NodePortTuple> switchPorts) {
+ this.switchPorts = switchPorts;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 5791;
+ int result = 1;
+ result = prime * result + ((id == null) ? 0 : id.hashCode());
+ result = prime * result + ((switchPorts == null) ? 0 : switchPorts.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;
+ Route other = (Route) obj;
+ if (id == null) {
+ if (other.id != null)
+ return false;
+ } else if (!id.equals(other.id))
+ return false;
+ if (switchPorts == null) {
+ if (other.switchPorts != null)
+ return false;
+ } else if (!switchPorts.equals(other.switchPorts))
+ return false;
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "Route [id=" + id + ", switchPorts=" + switchPorts + "]";
+ }
+
+ /**
+ * Compares the path lengths between Routes.
+ */
+ @Override
+ public int compareTo(Route o) {
+ return ((Integer)switchPorts.size()).compareTo(o.switchPorts.size());
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/routing/RouteId.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/routing/RouteId.java
new file mode 100755
index 0000000..a550961
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/routing/RouteId.java
@@ -0,0 +1,102 @@
+/**
+* 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.routing;
+
+import org.openflow.util.HexString;
+
+/**
+ * Stores the endpoints of a route, in this case datapath ids
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class RouteId implements Cloneable, Comparable<RouteId> {
+ protected Long src;
+ protected Long dst;
+
+ public RouteId(Long src, Long dst) {
+ super();
+ this.src = src;
+ this.dst = dst;
+ }
+
+ public Long getSrc() {
+ return src;
+ }
+
+ public void setSrc(Long src) {
+ this.src = src;
+ }
+
+ public Long getDst() {
+ return dst;
+ }
+
+ public void setDst(Long dst) {
+ this.dst = dst;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 2417;
+ int result = 1;
+ result = prime * result + ((dst == null) ? 0 : dst.hashCode());
+ result = prime * result + ((src == null) ? 0 : src.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;
+ RouteId other = (RouteId) obj;
+ if (dst == null) {
+ if (other.dst != null)
+ return false;
+ } else if (!dst.equals(other.dst))
+ return false;
+ if (src == null) {
+ if (other.src != null)
+ return false;
+ } else if (!src.equals(other.src))
+ return false;
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "RouteId [src=" + HexString.toHexString(this.src) + " dst="
+ + HexString.toHexString(this.dst) + "]";
+ }
+
+ @Override
+ protected Object clone() throws CloneNotSupportedException {
+ return super.clone();
+ }
+
+ @Override
+ public int compareTo(RouteId o) {
+ int result = src.compareTo(o.getSrc());
+ if (result != 0)
+ return result;
+ return dst.compareTo(o.getDst());
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/routing/RoutingDecision.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/routing/RoutingDecision.java
new file mode 100644
index 0000000..5b32b23
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/routing/RoutingDecision.java
@@ -0,0 +1,97 @@
+package net.floodlightcontroller.routing;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import net.floodlightcontroller.core.FloodlightContext;
+import net.floodlightcontroller.devicemanager.IDevice;
+import net.floodlightcontroller.devicemanager.SwitchPort;
+
+
+public class RoutingDecision implements IRoutingDecision {
+
+ protected RoutingAction action;
+ protected Integer wildcards;
+ protected SwitchPort srcPort;
+ protected IDevice srcDevice;
+ protected List<IDevice> destDevices;
+ protected List<SwitchPort> broadcastIntertfaces;
+
+ public RoutingDecision(long swDipd,
+ short 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.action = action;
+ this.wildcards = null;
+ }
+
+ @Override
+ public RoutingAction getRoutingAction() {
+ return this.action;
+ }
+
+ @Override
+ public void setRoutingAction(RoutingAction action) {
+ this.action = action;
+ }
+
+ @Override
+ public SwitchPort getSourcePort() {
+ return this.srcPort;
+ }
+
+ @Override
+ public IDevice getSourceDevice() {
+ return this.srcDevice;
+ }
+
+ @Override
+ public List<IDevice> getDestinationDevices() {
+ return this.destDevices;
+ }
+
+ @Override
+ public void addDestinationDevice(IDevice d) {
+ if (!destDevices.contains(d)) {
+ destDevices.add(d);
+ }
+ }
+
+ @Override
+ public void setMulticastInterfaces(List<SwitchPort> lspt) {
+ this.broadcastIntertfaces = lspt;
+ }
+
+ @Override
+ public List<SwitchPort> getMulticastInterfaces() {
+ return this.broadcastIntertfaces;
+ }
+
+ @Override
+ public Integer getWildcards() {
+ return this.wildcards;
+ }
+
+ @Override
+ public void setWildcards(Integer wildcards) {
+ this.wildcards = wildcards;
+ }
+
+ @Override
+ public void addToContext(FloodlightContext cntx) {
+ rtStore.put(cntx, IRoutingDecision.CONTEXT_DECISION, this);
+ }
+
+ public String toString() {
+ return "action " + action +
+ " wildcard " +
+ ((wildcards == null) ? null : "0x"+Integer.toHexString(wildcards.intValue()));
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/staticflowentry/IStaticFlowEntryPusherService.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/staticflowentry/IStaticFlowEntryPusherService.java
new file mode 100644
index 0000000..66e02dd
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/staticflowentry/IStaticFlowEntryPusherService.java
@@ -0,0 +1,44 @@
+package net.floodlightcontroller.staticflowentry;
+
+import java.util.Map;
+
+import org.openflow.protocol.OFFlowMod;
+
+import net.floodlightcontroller.core.module.IFloodlightService;
+
+public interface IStaticFlowEntryPusherService extends IFloodlightService {
+ /**
+ * Adds a static flow.
+ * @param name Name of the flow mod. Must be unique.
+ * @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);
+
+ /**
+ * Deletes a static flow
+ * @param name The name of the static flow to delete.
+ */
+ public void deleteFlow(String name);
+
+ /**
+ * Deletes all static flows for a practicular switch
+ * @param dpid The DPID of the switch to delete flows for.
+ */
+ public void deleteFlowsForSwitch(long dpid);
+
+ /**
+ * Deletes all flows.
+ */
+ public void deleteAllFlows();
+
+ /**
+ * Gets all list of all flows
+ */
+ public Map<String, Map<String, OFFlowMod>> getFlows();
+
+ /**
+ * Gets a list of flows by switch
+ */
+ public Map<String, OFFlowMod> getFlows(String dpid);
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/staticflowentry/StaticFlowEntries.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/staticflowentry/StaticFlowEntries.java
new file mode 100644
index 0000000..ba28619
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/staticflowentry/StaticFlowEntries.java
@@ -0,0 +1,831 @@
+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.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 org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+import org.codehaus.jackson.JsonParseException;
+import org.codehaus.jackson.JsonParser;
+import org.codehaus.jackson.JsonToken;
+import org.codehaus.jackson.map.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;
+
+/**
+ * Represents static flow entries to be maintained by the controller on the
+ * switches.
+ */
+@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) 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 (int)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/ext/floodlight/src/main/java/net/floodlightcontroller/staticflowentry/StaticFlowEntryPusher.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/staticflowentry/StaticFlowEntryPusher.java
new file mode 100644
index 0000000..4ed59d7
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/staticflowentry/StaticFlowEntryPusher.java
@@ -0,0 +1,679 @@
+package net.floodlightcontroller.staticflowentry;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+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 net.floodlightcontroller.core.FloodlightContext;
+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.IOFSwitchListener;
+import net.floodlightcontroller.core.annotations.LogMessageCategory;
+import net.floodlightcontroller.core.annotations.LogMessageDoc;
+import net.floodlightcontroller.core.module.FloodlightModuleContext;
+import net.floodlightcontroller.core.module.FloodlightModuleException;
+import net.floodlightcontroller.core.module.IFloodlightModule;
+import net.floodlightcontroller.core.module.IFloodlightService;
+import net.floodlightcontroller.core.util.AppCookie;
+import net.floodlightcontroller.restserver.IRestApiService;
+import net.floodlightcontroller.staticflowentry.web.StaticFlowEntryWebRoutable;
+import net.floodlightcontroller.staticflowentry.IStaticFlowEntryPusherService;
+import net.floodlightcontroller.storage.IResultSet;
+import net.floodlightcontroller.storage.IStorageSourceService;
+import net.floodlightcontroller.storage.IStorageSourceListener;
+
+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.protocol.factory.BasicFactory;
+import org.openflow.util.HexString;
+import org.openflow.util.U16;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@LogMessageCategory("Static Flow Pusher")
+/**
+ * This module is responsible for maintaining a set of static flows on
+ * switches. This is just a big 'ol dumb list of flows and something external
+ * is responsible for ensuring they make sense for the network.
+ */
+public class StaticFlowEntryPusher
+ implements IOFSwitchListener, IFloodlightModule, IStaticFlowEntryPusherService,
+ IStorageSourceListener, IOFMessageListener, IHAListener {
+ protected static Logger log = LoggerFactory.getLogger(StaticFlowEntryPusher.class);
+ public static final String StaticFlowName = "staticflowentry";
+
+ public static final int STATIC_FLOW_APP_ID = 10;
+
+ 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;
+
+ // 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;
+
+ private BasicFactory ofMessageFactory;
+
+ // 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(IOFSwitch sw) {
+ String dpid = sw.getStringId();
+
+ if ((entriesFromStorage != null) && (entriesFromStorage.containsKey(dpid))) {
+ Map<String, OFFlowMod> entries = entriesFromStorage.get(dpid);
+ List<String> sortedList = new ArrayList<String>(entries.keySet());
+ // weird that Collections.sort() returns void
+ Collections.sort( sortedList, new FlowModSorter(dpid));
+ for (String entryName : sortedList) {
+ OFFlowMod flowMod = entries.get(entryName);
+ if (flowMod != null) {
+ if (log.isDebugEnabled()) {
+ log.debug("Pushing static entry {} for {}", dpid, 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 HashMap<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();
+ if (ofMessageFactory == null) // lazy init
+ ofMessageFactory = new BasicFactory();
+
+ OFFlowMod flowMod = (OFFlowMod) ofMessageFactory
+ .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.debug(
+ "skipping entry {} on switch {} with bad data : "
+ + e.getMessage(), entryName, switchName);
+ else
+ log.debug("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 addedSwitch(IOFSwitch sw) {
+ log.debug("addedSwitch {}; processing its static entries", sw);
+ sendEntriesToSwitch(sw);
+ }
+
+ @Override
+ public void removedSwitch(IOFSwitch sw) {
+ log.debug("removedSwitch {}", sw);
+ // do NOT delete from our internal state; we're tracking the rules,
+ // not the switches
+ }
+
+ @Override
+ public void switchPortChanged(Long switchId) {
+ // no-op
+ }
+
+ /**
+ * This handles both rowInsert() and rowUpdate()
+ */
+
+ @Override
+ public void rowsModified(String tableName, Set<Object> rowKeys) {
+ 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);
+ for (Iterator<IResultSet> it = resultSet.iterator(); 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);
+ if (oldFlowMod != null) { // remove any pre-existing rule
+ oldFlowMod.setCommand(OFFlowMod.OFPFC_DELETE_STRICT);
+ outQueue.add(oldFlowMod);
+ }
+ 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 boolean deleteStaticFlowEntry(String entryName) {
+ String dpid = entry2dpid.get(entryName);
+ if (log.isDebugEnabled()) {
+ log.debug("Deleting flow {} for switch {}", entryName, dpid);
+ }
+ if (dpid == null) {
+ log.error("inconsistent internal state: no switch has rule {}",
+ entryName);
+ return false;
+ }
+
+ // 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 false;
+ }
+
+ writeFlowModToSwitch(HexString.toLong(dpid), flowMod);
+ return true;
+ }
+
+ /**
+ * 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.getSwitches().get(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 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) {
+ Map<Long,IOFSwitch> switches = floodlightProvider.getSwitches();
+ IOFSwitch ofSwitch = switches.get(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;
+ }
+
+ @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:
+ break;
+ default:
+ return Command.CONTINUE;
+ }
+ OFFlowRemoved flowRemoved = (OFFlowRemoved) msg;
+ long cookie = flowRemoved.getCookie();
+ /**
+ * This is just to sanity check our assumption that static flows
+ * never expire.
+ */
+ if( AppCookie.extractApp(cookie) == STATIC_FLOW_APP_ID) {
+ if (flowRemoved.getReason() !=
+ OFFlowRemoved.OFFlowRemovedReason.OFPRR_DELETE)
+ log.error("Got a FlowRemove message for a infinite " +
+ "timeout flow: {} from switch {}", msg, sw);
+ return Command.STOP; // only for us
+ } else
+ 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);
+ }
+
+ @Override
+ public void startUp(FloodlightModuleContext context) {
+ floodlightProvider.addOFMessageListener(OFType.FLOW_REMOVED, this);
+ floodlightProvider.addOFSwitchListener(this);
+ floodlightProvider.addHAListener(this);
+
+ // 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);
+ entry2dpid.put(name, swDpid);
+ Map<String, OFFlowMod> switchEntries = entriesFromStorage.get(swDpid);
+ if (switchEntries == null) {
+ switchEntries = new HashMap<String, OFFlowMod>();
+ entriesFromStorage.put(swDpid, switchEntries);
+ }
+ switchEntries.put(name, fm);
+ storageSource.insertRowAsync(TABLE_NAME, fmMap);
+ }
+
+ @Override
+ public void deleteFlow(String name) {
+ storageSource.deleteRowAsync(TABLE_NAME, name);
+ // TODO - What if there is a delay in storage?
+ }
+
+ @Override
+ public void deleteAllFlows() {
+ for (String entry : entry2dpid.keySet()) {
+ deleteFlow(entry);
+ }
+ }
+
+ @Override
+ public void deleteFlowsForSwitch(long dpid) {
+ String sDpid = HexString.toHexString(dpid);
+
+ for (Entry<String, String> e : entry2dpid.entrySet()) {
+ if (e.getValue().equals(sDpid))
+ deleteFlow(e.getKey());
+ }
+ }
+
+ @Override
+ public Map<String, Map<String, OFFlowMod>> getFlows() {
+ return entriesFromStorage;
+ }
+
+ @Override
+ public Map<String, OFFlowMod> getFlows(String dpid) {
+ return entriesFromStorage.get(dpid);
+ }
+
+
+ // IHAListener
+
+ @Override
+ public void roleChanged(Role oldRole, Role newRole) {
+ switch(newRole) {
+ case MASTER:
+ if (oldRole == Role.SLAVE) {
+ log.debug("Re-reading static flows from storage due " +
+ "to HA change from SLAVE->MASTER");
+ entriesFromStorage = readEntriesFromStorage();
+ entry2dpid = computeEntry2DpidMap(entriesFromStorage);
+ }
+ break;
+ case SLAVE:
+ log.debug("Clearing in-memory flows due to " +
+ "HA change to SLAVE");
+ entry2dpid.clear();
+ entriesFromStorage.clear();
+ break;
+ default:
+ break;
+ }
+ }
+
+ @Override
+ public void controllerNodeIPsChanged(
+ Map<String, String> curControllerNodeIPs,
+ Map<String, String> addedControllerNodeIPs,
+ Map<String, String> removedControllerNodeIPs) {
+ // ignore
+ }
+
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/staticflowentry/web/ClearStaticFlowEntriesResource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/staticflowentry/web/ClearStaticFlowEntriesResource.java
new file mode 100644
index 0000000..c1d826a
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/staticflowentry/web/ClearStaticFlowEntriesResource.java
@@ -0,0 +1,38 @@
+package net.floodlightcontroller.staticflowentry.web;
+
+import net.floodlightcontroller.core.web.ControllerSwitchesResource;
+import net.floodlightcontroller.staticflowentry.IStaticFlowEntryPusherService;
+
+import org.openflow.util.HexString;
+import org.restlet.data.Status;
+import org.restlet.resource.Get;
+import org.restlet.resource.ServerResource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ClearStaticFlowEntriesResource extends ServerResource {
+ protected static Logger log = LoggerFactory.getLogger(ClearStaticFlowEntriesResource.class);
+
+ @Get
+ public void ClearStaticFlowEntries() {
+ IStaticFlowEntryPusherService sfpService =
+ (IStaticFlowEntryPusherService)getContext().getAttributes().
+ get(IStaticFlowEntryPusherService.class.getCanonicalName());
+
+ String param = (String) getRequestAttributes().get("switch");
+ if (log.isDebugEnabled())
+ log.debug("Clearing all static flow entires for switch: " + param);
+
+ if (param.toLowerCase().equals("all")) {
+ sfpService.deleteAllFlows();
+ } else {
+ try {
+ sfpService.deleteFlowsForSwitch(HexString.toLong(param));
+ } catch (NumberFormatException e){
+ setStatus(Status.CLIENT_ERROR_BAD_REQUEST,
+ ControllerSwitchesResource.DPID_ERROR);
+ return;
+ }
+ }
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/staticflowentry/web/ListStaticFlowEntriesResource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/staticflowentry/web/ListStaticFlowEntriesResource.java
new file mode 100644
index 0000000..0ad778f
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/staticflowentry/web/ListStaticFlowEntriesResource.java
@@ -0,0 +1,45 @@
+package net.floodlightcontroller.staticflowentry.web;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import net.floodlightcontroller.core.web.ControllerSwitchesResource;
+import net.floodlightcontroller.staticflowentry.IStaticFlowEntryPusherService;
+
+import org.openflow.protocol.OFFlowMod;
+import org.restlet.data.Status;
+import org.restlet.resource.Get;
+import org.restlet.resource.ServerResource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ListStaticFlowEntriesResource extends ServerResource {
+ protected static Logger log = LoggerFactory.getLogger(ListStaticFlowEntriesResource.class);
+
+ @Get
+ public Map<String, Map<String, OFFlowMod>> ListStaticFlowEntries() {
+ IStaticFlowEntryPusherService sfpService =
+ (IStaticFlowEntryPusherService)getContext().getAttributes().
+ get(IStaticFlowEntryPusherService.class.getCanonicalName());
+
+ String param = (String) getRequestAttributes().get("switch");
+ if (log.isDebugEnabled())
+ log.debug("Listing all static flow entires for switch: " + param);
+
+ if (param.toLowerCase().equals("all")) {
+ return sfpService.getFlows();
+ } else {
+ try {
+ Map<String, Map<String, OFFlowMod>> retMap =
+ new HashMap<String, Map<String, OFFlowMod>>();
+ retMap.put(param, sfpService.getFlows(param));
+ return retMap;
+
+ } catch (NumberFormatException e){
+ setStatus(Status.CLIENT_ERROR_BAD_REQUEST,
+ ControllerSwitchesResource.DPID_ERROR);
+ }
+ }
+ return null;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/staticflowentry/web/StaticFlowEntryPusherResource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/staticflowentry/web/StaticFlowEntryPusherResource.java
new file mode 100644
index 0000000..3b750ae
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/staticflowentry/web/StaticFlowEntryPusherResource.java
@@ -0,0 +1,142 @@
+/**
+* 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.staticflowentry.web;
+
+import java.io.IOException;
+import java.util.Map;
+
+import org.restlet.resource.Delete;
+import org.restlet.resource.Post;
+import org.restlet.resource.ServerResource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+import net.floodlightcontroller.core.annotations.LogMessageCategory;
+import net.floodlightcontroller.core.annotations.LogMessageDoc;
+import net.floodlightcontroller.staticflowentry.StaticFlowEntries;
+import net.floodlightcontroller.staticflowentry.StaticFlowEntryPusher;
+import net.floodlightcontroller.storage.IStorageSourceService;
+
+/**
+ * Pushes a static flow entry to the storage source
+ * @author alexreimers
+ *
+ */
+@LogMessageCategory("Static Flow Pusher")
+public class StaticFlowEntryPusherResource extends ServerResource {
+ protected static Logger log = LoggerFactory.getLogger(StaticFlowEntryPusherResource.class);
+
+ /**
+ * Checks to see if the user matches IP information without
+ * checking for the correct ether-type (2048).
+ * @param rows The Map that is a string representation of
+ * the static flow.
+ * @reutrn True if they checked the ether-type, false otherwise
+ */
+ private boolean checkMatchIp(Map<String, Object> rows) {
+ boolean matchEther = false;
+ String val = (String) rows.get(StaticFlowEntryPusher.COLUMN_DL_TYPE);
+ if (val != null) {
+ int type = 0;
+ // check both hex and decimal
+ if (val.startsWith("0x")) {
+ type = Integer.parseInt(val.substring(2), 16);
+ } else {
+ try {
+ type = Integer.parseInt(val);
+ } catch (NumberFormatException e) { /* fail silently */}
+ }
+ if (type == 2048) matchEther = true;
+ }
+
+ if ((rows.containsKey(StaticFlowEntryPusher.COLUMN_NW_DST) ||
+ rows.containsKey(StaticFlowEntryPusher.COLUMN_NW_SRC) ||
+ rows.containsKey(StaticFlowEntryPusher.COLUMN_NW_PROTO) ||
+ rows.containsKey(StaticFlowEntryPusher.COLUMN_NW_TOS)) &&
+ (matchEther == false))
+ return false;
+
+ return true;
+ }
+
+ /**
+ * Takes a Static Flow Pusher string in JSON format and parses it into
+ * our database schema then pushes it to the database.
+ * @param fmJson The Static Flow Pusher entry in JSON format.
+ * @return A string status message
+ */
+ @Post
+ @LogMessageDoc(level="ERROR",
+ message="Error parsing push flow mod request: {request}",
+ explanation="An invalid request was sent to static flow pusher",
+ recommendation="Fix the format of the static flow mod request")
+ public String store(String fmJson) {
+ IStorageSourceService storageSource =
+ (IStorageSourceService)getContext().getAttributes().
+ get(IStorageSourceService.class.getCanonicalName());
+
+ Map<String, Object> rowValues;
+ try {
+ rowValues = StaticFlowEntries.jsonToStorageEntry(fmJson);
+ String status = null;
+ if (!checkMatchIp(rowValues)) {
+ status = "Warning! Pushing a static flow entry that matches IP " +
+ "fields without matching for IP payload (ether-type 2048) will cause " +
+ "the switch to wildcard higher level fields.";
+ log.error(status);
+ } else {
+ status = "Entry pushed";
+ }
+ storageSource.insertRowAsync(StaticFlowEntryPusher.TABLE_NAME, rowValues);
+ return ("{\"status\" : \"" + status + "\"}");
+ } catch (IOException e) {
+ log.error("Error parsing push flow mod request: " + fmJson, e);
+ e.printStackTrace();
+ return "{\"status\" : \"Error! Could not parse flod mod, see log for details.\"}";
+ }
+ }
+
+ @Delete
+ @LogMessageDoc(level="ERROR",
+ message="Error deleting flow mod request: {request}",
+ explanation="An invalid delete request was sent to static flow pusher",
+ recommendation="Fix the format of the static flow mod request")
+ public String del(String fmJson) {
+ IStorageSourceService storageSource =
+ (IStorageSourceService)getContext().getAttributes().
+ get(IStorageSourceService.class.getCanonicalName());
+ String fmName = null;
+ if (fmJson == null) {
+ return "{\"status\" : \"Error! No data posted.\"}";
+ }
+ try {
+ fmName = StaticFlowEntries.getEntryNameFromJson(fmJson);
+ if (fmName == null) {
+ return "{\"status\" : \"Error deleting entry, no name provided\"}";
+ }
+ } catch (IOException e) {
+ log.error("Error deleting flow mod request: " + fmJson, e);
+ e.printStackTrace();
+ return "{\"status\" : \"Error deleting entry, see log for details\"}";
+ }
+
+ storageSource.deleteRowAsync(StaticFlowEntryPusher.TABLE_NAME, fmName);
+ return "{\"status\" : \"Entry " + fmName + " deleted\"}";
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/staticflowentry/web/StaticFlowEntryWebRoutable.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/staticflowentry/web/StaticFlowEntryWebRoutable.java
new file mode 100644
index 0000000..b5a6fe1
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/staticflowentry/web/StaticFlowEntryWebRoutable.java
@@ -0,0 +1,29 @@
+package net.floodlightcontroller.staticflowentry.web;
+
+import net.floodlightcontroller.restserver.RestletRoutable;
+
+import org.restlet.Context;
+import org.restlet.Restlet;
+import org.restlet.routing.Router;
+
+public class StaticFlowEntryWebRoutable implements RestletRoutable {
+ /**
+ * Create the Restlet router and bind to the proper resources.
+ */
+ @Override
+ public Restlet getRestlet(Context context) {
+ Router router = new Router(context);
+ router.attach("/json", StaticFlowEntryPusherResource.class);
+ router.attach("/clear/{switch}/json", ClearStaticFlowEntriesResource.class);
+ router.attach("/list/{switch}/json", ListStaticFlowEntriesResource.class);
+ return router;
+ }
+
+ /**
+ * Set the base path for the Topology
+ */
+ @Override
+ public String basePath() {
+ return "/wm/staticflowentrypusher";
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/AbstractStorageSource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/AbstractStorageSource.java
new file mode 100644
index 0000000..aae3962
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/AbstractStorageSource.java
@@ -0,0 +1,534 @@
+/**
+* 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.storage;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+
+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.counter.ICounter;
+import net.floodlightcontroller.counter.CounterStore;
+import net.floodlightcontroller.counter.ICounterStoreService;
+import net.floodlightcontroller.counter.CounterValue.CounterType;
+import net.floodlightcontroller.restserver.IRestApiService;
+import net.floodlightcontroller.storage.web.StorageWebRoutable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@LogMessageCategory("System Database")
+public abstract class AbstractStorageSource
+ implements IStorageSourceService, IFloodlightModule {
+ protected static Logger logger = LoggerFactory.getLogger(AbstractStorageSource.class);
+
+ // Shared instance of the executor to use to execute the storage tasks.
+ // We make this a single threaded executor, because if we used a thread pool
+ // then storage operations could be executed out of order which would cause
+ // problems in some cases (e.g. delete and update of a row getting reordered).
+ // If we wanted to make this more multi-threaded we could have multiple
+ // worker threads/executors with affinity of operations on a given table
+ // to a single worker thread. But for now, we'll keep it simple and just have
+ // a single thread for all operations.
+ protected static ExecutorService defaultExecutorService = Executors.newSingleThreadExecutor();
+
+ protected final static String STORAGE_QUERY_COUNTER_NAME = "StorageQuery";
+ protected final static String STORAGE_UPDATE_COUNTER_NAME = "StorageUpdate";
+ protected final static String STORAGE_DELETE_COUNTER_NAME = "StorageDelete";
+
+ protected Set<String> allTableNames = new CopyOnWriteArraySet<String>();
+ protected ICounterStoreService counterStore;
+ protected ExecutorService executorService = defaultExecutorService;
+ protected IStorageExceptionHandler exceptionHandler;
+
+ private Map<String, Set<IStorageSourceListener>> listeners =
+ new ConcurrentHashMap<String, Set<IStorageSourceListener>>();
+
+ // Our dependencies
+ protected IRestApiService restApi = null;
+
+ protected static final String DB_ERROR_EXPLANATION =
+ "An unknown error occurred while executing asynchronous " +
+ "database operation";
+
+ @LogMessageDoc(level="ERROR",
+ message="Failure in asynchronous call to executeQuery",
+ explanation=DB_ERROR_EXPLANATION,
+ recommendation=LogMessageDoc.GENERIC_ACTION)
+ abstract class StorageCallable<V> implements Callable<V> {
+ public V call() {
+ try {
+ return doStorageOperation();
+ }
+ catch (StorageException e) {
+ logger.error("Failure in asynchronous call to executeQuery", e);
+ if (exceptionHandler != null)
+ exceptionHandler.handleException(e);
+ throw e;
+ }
+ }
+ abstract protected V doStorageOperation();
+ }
+
+ @LogMessageDoc(level="ERROR",
+ message="Failure in asynchronous call to updateRows",
+ explanation=DB_ERROR_EXPLANATION,
+ recommendation=LogMessageDoc.GENERIC_ACTION)
+ abstract class StorageRunnable implements Runnable {
+ public void run() {
+ try {
+ doStorageOperation();
+ }
+ catch (StorageException e) {
+ logger.error("Failure in asynchronous call to updateRows", e);
+ if (exceptionHandler != null)
+ exceptionHandler.handleException(e);
+ throw e;
+ }
+ }
+ abstract void doStorageOperation();
+ }
+
+ public AbstractStorageSource() {
+ this.executorService = defaultExecutorService;
+ }
+
+ public void setExecutorService(ExecutorService executorService) {
+ this.executorService = (executorService != null) ?
+ executorService : defaultExecutorService;
+ }
+
+ @Override
+ public void setExceptionHandler(IStorageExceptionHandler exceptionHandler) {
+ this.exceptionHandler = exceptionHandler;
+ }
+
+ @Override
+ public abstract void setTablePrimaryKeyName(String tableName, String primaryKeyName);
+
+ @Override
+ public void createTable(String tableName, Set<String> indexedColumns) {
+ allTableNames.add(tableName);
+ }
+
+ @Override
+ public Set<String> getAllTableNames() {
+ return allTableNames;
+ }
+
+ public void setCounterStore(CounterStore counterStore) {
+ this.counterStore = counterStore;
+ }
+
+ protected void updateCounters(String baseName, String tableName) {
+ if (counterStore != null) {
+ String counterName;
+ if (tableName != null) {
+ updateCounters(baseName, null);
+ counterName = baseName + CounterStore.TitleDelimitor + tableName;
+ } else {
+ counterName = baseName;
+ }
+ ICounter counter = counterStore.getCounter(counterName);
+ if (counter == null) {
+ counter = counterStore.createCounter(counterName, CounterType.LONG);
+ }
+ counter.increment();
+ }
+ }
+
+ @Override
+ public abstract IQuery createQuery(String tableName, String[] columnNames,
+ IPredicate predicate, RowOrdering ordering);
+
+ @Override
+ public IResultSet executeQuery(IQuery query) {
+ updateCounters(STORAGE_QUERY_COUNTER_NAME, query.getTableName());
+ return executeQueryImpl(query);
+ }
+
+ protected abstract IResultSet executeQueryImpl(IQuery query);
+
+ @Override
+ public IResultSet executeQuery(String tableName, String[] columnNames,
+ IPredicate predicate, RowOrdering ordering) {
+ IQuery query = createQuery(tableName, columnNames, predicate, ordering);
+ IResultSet resultSet = executeQuery(query);
+ return resultSet;
+ }
+
+ @Override
+ public Object[] executeQuery(String tableName, String[] columnNames,
+ IPredicate predicate, RowOrdering ordering, IRowMapper rowMapper) {
+ List<Object> objectList = new ArrayList<Object>();
+ IResultSet resultSet = executeQuery(tableName, columnNames, predicate, ordering);
+ while (resultSet.next()) {
+ Object object = rowMapper.mapRow(resultSet);
+ objectList.add(object);
+ }
+ return objectList.toArray();
+ }
+
+ @Override
+ public Future<IResultSet> executeQueryAsync(final IQuery query) {
+ Future<IResultSet> future = executorService.submit(
+ new StorageCallable<IResultSet>() {
+ public IResultSet doStorageOperation() {
+ return executeQuery(query);
+ }
+ });
+ return future;
+ }
+
+ @Override
+ public Future<IResultSet> executeQueryAsync(final String tableName,
+ final String[] columnNames, final IPredicate predicate,
+ final RowOrdering ordering) {
+ Future<IResultSet> future = executorService.submit(
+ new StorageCallable<IResultSet>() {
+ public IResultSet doStorageOperation() {
+ return executeQuery(tableName, columnNames,
+ predicate, ordering);
+ }
+ });
+ return future;
+ }
+
+ @Override
+ public Future<Object[]> executeQueryAsync(final String tableName,
+ final String[] columnNames, final IPredicate predicate,
+ final RowOrdering ordering, final IRowMapper rowMapper) {
+ Future<Object[]> future = executorService.submit(
+ new StorageCallable<Object[]>() {
+ public Object[] doStorageOperation() {
+ return executeQuery(tableName, columnNames, predicate,
+ ordering, rowMapper);
+ }
+ });
+ return future;
+ }
+
+ @Override
+ public Future<?> insertRowAsync(final String tableName,
+ final Map<String,Object> values) {
+ Future<?> future = executorService.submit(
+ new StorageRunnable() {
+ public void doStorageOperation() {
+ insertRow(tableName, values);
+ }
+ }, null);
+ return future;
+ }
+
+ @Override
+ public Future<?> updateRowsAsync(final String tableName, final List<Map<String,Object>> rows) {
+ Future<?> future = executorService.submit(
+ new StorageRunnable() {
+ public void doStorageOperation() {
+ updateRows(tableName, rows);
+ }
+ }, null);
+ return future;
+ }
+
+ @Override
+ public Future<?> updateMatchingRowsAsync(final String tableName,
+ final IPredicate predicate, final Map<String,Object> values) {
+ Future<?> future = executorService.submit(
+ new StorageRunnable() {
+ public void doStorageOperation() {
+ updateMatchingRows(tableName, predicate, values);
+ }
+ }, null);
+ return future;
+ }
+
+ @Override
+ public Future<?> updateRowAsync(final String tableName,
+ final Object rowKey, final Map<String,Object> values) {
+ Future<?> future = executorService.submit(
+ new StorageRunnable() {
+ public void doStorageOperation() {
+ updateRow(tableName, rowKey, values);
+ }
+ }, null);
+ return future;
+ }
+
+ @Override
+ public Future<?> updateRowAsync(final String tableName,
+ final Map<String,Object> values) {
+ Future<?> future = executorService.submit(
+ new StorageRunnable() {
+ public void doStorageOperation() {
+ updateRow(tableName, values);
+ }
+ }, null);
+ return future;
+ }
+
+ @Override
+ public Future<?> deleteRowAsync(final String tableName, final Object rowKey) {
+ Future<?> future = executorService.submit(
+ new StorageRunnable() {
+ public void doStorageOperation() {
+ deleteRow(tableName, rowKey);
+ }
+ }, null);
+ return future;
+ }
+
+ @Override
+ public Future<?> deleteRowsAsync(final String tableName, final Set<Object> rowKeys) {
+ Future<?> future = executorService.submit(
+ new StorageRunnable() {
+ public void doStorageOperation() {
+ deleteRows(tableName, rowKeys);
+ }
+ }, null);
+ return future;
+ }
+
+ @Override
+ public Future<?> deleteMatchingRowsAsync(final String tableName, final IPredicate predicate) {
+ Future<?> future = executorService.submit(
+ new StorageRunnable() {
+ public void doStorageOperation() {
+ deleteMatchingRows(tableName, predicate);
+ }
+ }, null);
+ return future;
+ }
+
+ @Override
+ public Future<?> getRowAsync(final String tableName, final Object rowKey) {
+ Future<?> future = executorService.submit(
+ new StorageRunnable() {
+ public void doStorageOperation() {
+ getRow(tableName, rowKey);
+ }
+ }, null);
+ return future;
+ }
+
+ @Override
+ public Future<?> saveAsync(final IResultSet resultSet) {
+ Future<?> future = executorService.submit(
+ new StorageRunnable() {
+ public void doStorageOperation() {
+ resultSet.save();
+ }
+ }, null);
+ return future;
+ }
+
+ @Override
+ public void insertRow(String tableName, Map<String, Object> values) {
+ updateCounters(STORAGE_UPDATE_COUNTER_NAME, tableName);
+ insertRowImpl(tableName, values);
+ }
+
+ protected abstract void insertRowImpl(String tableName, Map<String, Object> values);
+
+
+ @Override
+ public void updateRows(String tableName, List<Map<String,Object>> rows) {
+ updateCounters(STORAGE_UPDATE_COUNTER_NAME, tableName);
+ updateRowsImpl(tableName, rows);
+ }
+
+ protected abstract void updateRowsImpl(String tableName, List<Map<String,Object>> rows);
+
+ @Override
+ public void updateMatchingRows(String tableName, IPredicate predicate,
+ Map<String, Object> values) {
+ updateCounters(STORAGE_UPDATE_COUNTER_NAME, tableName);
+ updateMatchingRowsImpl(tableName, predicate, values);
+ }
+
+ protected abstract void updateMatchingRowsImpl(String tableName, IPredicate predicate,
+ Map<String, Object> values);
+
+ @Override
+ public void updateRow(String tableName, Object rowKey,
+ Map<String, Object> values) {
+ updateCounters(STORAGE_UPDATE_COUNTER_NAME, tableName);
+ updateRowImpl(tableName, rowKey, values);
+ }
+
+ protected abstract void updateRowImpl(String tableName, Object rowKey,
+ Map<String, Object> values);
+
+ @Override
+ public void updateRow(String tableName, Map<String, Object> values) {
+ updateCounters(STORAGE_UPDATE_COUNTER_NAME, tableName);
+ updateRowImpl(tableName, values);
+ }
+
+ protected abstract void updateRowImpl(String tableName, Map<String, Object> values);
+
+ @Override
+ public void deleteRow(String tableName, Object rowKey) {
+ updateCounters(STORAGE_DELETE_COUNTER_NAME, tableName);
+ deleteRowImpl(tableName, rowKey);
+ }
+
+ protected abstract void deleteRowImpl(String tableName, Object rowKey);
+
+ @Override
+ public void deleteRows(String tableName, Set<Object> rowKeys) {
+ updateCounters(STORAGE_DELETE_COUNTER_NAME, tableName);
+ deleteRowsImpl(tableName, rowKeys);
+ }
+
+ protected abstract void deleteRowsImpl(String tableName, Set<Object> rowKeys);
+
+ @Override
+ public void deleteMatchingRows(String tableName, IPredicate predicate) {
+ IResultSet resultSet = null;
+ try {
+ resultSet = executeQuery(tableName, null, predicate, null);
+ while (resultSet.next()) {
+ resultSet.deleteRow();
+ }
+ resultSet.save();
+ }
+ finally {
+ if (resultSet != null)
+ resultSet.close();
+ }
+ }
+
+ @Override
+ public IResultSet getRow(String tableName, Object rowKey) {
+ updateCounters(STORAGE_QUERY_COUNTER_NAME, tableName);
+ return getRowImpl(tableName, rowKey);
+ }
+
+ protected abstract IResultSet getRowImpl(String tableName, Object rowKey);
+
+ @Override
+ public synchronized void addListener(String tableName, IStorageSourceListener listener) {
+ Set<IStorageSourceListener> tableListeners = listeners.get(tableName);
+ if (tableListeners == null) {
+ tableListeners = new CopyOnWriteArraySet<IStorageSourceListener>();
+ listeners.put(tableName, tableListeners);
+ }
+ tableListeners.add(listener);
+ }
+
+ @Override
+ public synchronized void removeListener(String tableName, IStorageSourceListener listener) {
+ Set<IStorageSourceListener> tableListeners = listeners.get(tableName);
+ if (tableListeners != null) {
+ tableListeners.remove(listener);
+ }
+ }
+
+ @LogMessageDoc(level="ERROR",
+ message="Exception caught handling storage notification",
+ explanation="An unknown error occured while trying to notify" +
+ " storage listeners",
+ recommendation=LogMessageDoc.GENERIC_ACTION)
+ protected synchronized void notifyListeners(StorageSourceNotification notification) {
+ String tableName = notification.getTableName();
+ Set<Object> keys = notification.getKeys();
+ Set<IStorageSourceListener> tableListeners = listeners.get(tableName);
+ if (tableListeners != null) {
+ for (IStorageSourceListener listener : tableListeners) {
+ try {
+ switch (notification.getAction()) {
+ case MODIFY:
+ listener.rowsModified(tableName, keys);
+ break;
+ case DELETE:
+ listener.rowsDeleted(tableName, keys);
+ break;
+ }
+ }
+ catch (Exception e) {
+ logger.error("Exception caught handling storage notification", e);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void notifyListeners(List<StorageSourceNotification> notifications) {
+ for (StorageSourceNotification notification : notifications)
+ notifyListeners(notification);
+ }
+
+ // IFloodlightModule
+
+ @Override
+ public Collection<Class<? extends IFloodlightService>> getModuleServices() {
+ Collection<Class<? extends IFloodlightService>> l =
+ new ArrayList<Class<? extends IFloodlightService>>();
+ l.add(IStorageSourceService.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(IStorageSourceService.class, this);
+ return m;
+ }
+
+ @Override
+ public Collection<Class<? extends IFloodlightService>> getModuleDependencies() {
+ Collection<Class<? extends IFloodlightService>> l =
+ new ArrayList<Class<? extends IFloodlightService>>();
+ l.add(IRestApiService.class);
+ l.add(ICounterStoreService.class);
+ return l;
+ }
+
+ @Override
+ public void init(FloodlightModuleContext context)
+ throws FloodlightModuleException {
+ restApi =
+ context.getServiceImpl(IRestApiService.class);
+ counterStore =
+ context.getServiceImpl(ICounterStoreService.class);
+ }
+
+ @Override
+ public void startUp(FloodlightModuleContext context) {
+ restApi.addRestletRoutable(new StorageWebRoutable());
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/CompoundPredicate.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/CompoundPredicate.java
new file mode 100644
index 0000000..a23e560
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/CompoundPredicate.java
@@ -0,0 +1,52 @@
+/**
+* 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.storage;
+
+/** Predicate class to handle AND and OR combinations of a number
+ * of child predicates. The result of the logical combination of the
+ * child predicates can also be negated to support a NOT operation.
+ *
+ * @author rob
+ *
+ */
+public class CompoundPredicate implements IPredicate {
+
+ public enum Operator { AND, OR };
+
+ private Operator operator;
+ private boolean negated;
+ private IPredicate[] predicateList;
+
+ public CompoundPredicate(Operator operator, boolean negated, IPredicate... predicateList) {
+ this.operator = operator;
+ this.negated = negated;
+ this.predicateList = predicateList;
+ }
+
+ public Operator getOperator() {
+ return operator;
+ }
+
+ public boolean isNegated() {
+ return negated;
+ }
+
+ public IPredicate[] getPredicateList() {
+ return predicateList;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/IPredicate.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/IPredicate.java
new file mode 100644
index 0000000..291edff
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/IPredicate.java
@@ -0,0 +1,26 @@
+/**
+* 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.storage;
+
+/** Common base interface for the OperatorPredicate and CompoundPredicate classes.
+ * It's necessary so that you can use either type of predicate as child
+ * predicates of a CompoundPredicate.
+ * @author rob
+ */
+public interface IPredicate {
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/IQuery.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/IQuery.java
new file mode 100644
index 0000000..b75b8ae
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/IQuery.java
@@ -0,0 +1,39 @@
+/**
+* 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.storage;
+
+/** Representation of a database query. For SQL queries this maps to
+ * a prepared statement, so it will be more efficient than if you use the
+ * methods in IStorageSource that bypass the IQuery. For many NoSQL
+ * storage sources there won't be any performance improvement from keeping
+ * around the query.
+ *
+ * The query interface also supports parameterized queries (i.e. which maps
+ * to using ? values in a SQL query). The values of the parameters are set
+ * using the setParameter method. In the storage source API the parameters
+ * are named rather than positional. The format of the parameterized values
+ * in the query predicates is the parameter name bracketed with question marks
+ * (e.g. ?MinimumSalary? ).
+ *
+ * @author rob
+ *
+ */
+public interface IQuery {
+ String getTableName();
+ void setParameter(String name, Object value);
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/IResultSet.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/IResultSet.java
new file mode 100644
index 0000000..fbd2a4a
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/IResultSet.java
@@ -0,0 +1,106 @@
+/**
+* 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.storage;
+
+import java.util.Date;
+import java.util.Map;
+
+/** Interface to iterate over the results from a storage query.
+ *
+ * @author rob
+ *
+ */
+public interface IResultSet extends Iterable<IResultSet> {
+
+ /** This should be called when the client is done using the result set.
+ * This will release any underlying resources (e.g. a database connection),
+ * which you don't want to wait for or rely on finalizers to release.
+ */
+ public void close();
+
+ /** Advance to the next row in the result set.
+ * @return Returns true if there are more rows to process
+ * (i.e. if there's a valid current row) and false if there are no more
+ * rows in the result set.
+ */
+ public boolean next();
+
+ /** Save/commit any pending updates to the data in the result set.
+ * This must be called after any calls to the set methods or deleting rows
+ * for the changes to be applied/committed to the storage source. Note that
+ * this doesn't need to be called after each set method or even after each
+ * row. It is typically called at the end after updating all of the
+ * rows in the result set.
+ */
+ public void save();
+
+ /** Get the current row in the result set. This returns all of the
+ * columns in the current row.
+ * @return Map containing all of the columns in the current row, indexed
+ * by the column name.
+ */
+ public Map<String,Object> getRow();
+
+ /** Delete the current row in the result set.
+ */
+ public void deleteRow();
+
+ public boolean containsColumn(String columnName);
+
+ public String getString(String columnName);
+ public short getShort(String columnName);
+ public int getInt(String columnName);
+ public long getLong(String columnName);
+ public float getFloat(String columnName);
+ public double getDouble(String columnName);
+ public boolean getBoolean(String columnName);
+ public byte getByte(String columnName);
+ public byte[] getByteArray(String columnName);
+ public Date getDate(String columnName);
+
+ public Short getShortObject(String columnName);
+ public Integer getIntegerObject(String columnName);
+ public Long getLongObject(String columnName);
+ public Float getFloatObject(String columnName);
+ public Double getDoubleObject(String columnName);
+ public Boolean getBooleanObject(String columnName);
+ public Byte getByteObject(String columnName);
+
+ public boolean isNull(String columnName);
+
+ public void setString(String columnName, String value);
+ public void setShort(String columnName, short value);
+ public void setInt(String columnName, int value);
+ public void setLong(String columnName, long value);
+ public void setFloat(String columnName, float value);
+ public void setDouble(String columnName, double value);
+ public void setBoolean(String columnName, boolean value);
+ public void setByte(String columnName, byte value);
+ public void setByteArray(String columnName, byte[] byteArray);
+ public void setDate(String columnName, Date date);
+
+ public void setShortObject(String columnName, Short value);
+ public void setIntegerObject(String columnName, Integer value);
+ public void setLongObject(String columnName, Long value);
+ public void setFloatObject(String columnName, Float value);
+ public void setDoubleObject(String columnName, Double value);
+ public void setBooleanObject(String columnName, Boolean value);
+ public void setByteObject(String columnName, Byte value);
+
+ public void setNull(String columnName);
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/IRowMapper.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/IRowMapper.java
new file mode 100644
index 0000000..6c4502b
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/IRowMapper.java
@@ -0,0 +1,35 @@
+/**
+* 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.storage;
+
+/**
+ * Interface for mapping the current row in a result set to an object.
+ * This is based on the Spring JDBC support.
+ *
+ * @author rob
+ */
+public interface IRowMapper {
+
+ /** This method must be implemented by the client of the storage API
+ * to map the current row in the result set to a Java object.
+ *
+ * @param resultSet The result set obtained from a storage source query
+ * @return The object created from the data in the result set
+ */
+ Object mapRow(IResultSet resultSet);
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/IStorageExceptionHandler.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/IStorageExceptionHandler.java
new file mode 100644
index 0000000..e3c8e94
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/IStorageExceptionHandler.java
@@ -0,0 +1,22 @@
+/**
+* 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.storage;
+
+public interface IStorageExceptionHandler {
+ public void handleException(Exception exc);
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/IStorageSourceListener.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/IStorageSourceListener.java
new file mode 100644
index 0000000..ea3764d
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/IStorageSourceListener.java
@@ -0,0 +1,39 @@
+/**
+* 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.storage;
+
+import java.util.Set;
+
+public interface IStorageSourceListener {
+
+ /**
+ * Called when rows are inserted or updated in the table.
+ *
+ * @param tableName The table where the rows were inserted
+ * @param rowKeys The keys of the rows that were inserted
+ */
+ public void rowsModified(String tableName, Set<Object> rowKeys);
+
+ /**
+ * Called when a new row is deleted from the table.
+ *
+ * @param tableName The table where the rows were deleted
+ * @param rowKeys The keys of the rows that were deleted
+ */
+ public void rowsDeleted(String tableName, Set<Object> rowKeys);
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/IStorageSourceService.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/IStorageSourceService.java
new file mode 100644
index 0000000..b8a1be8
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/IStorageSourceService.java
@@ -0,0 +1,331 @@
+/**
+* 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.storage;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Future;
+
+import net.floodlightcontroller.core.module.IFloodlightService;
+
+public interface IStorageSourceService extends IFloodlightService {
+
+ /** Set the column to be used as the primary key for a table. This should
+ * be guaranteed to be unique for all of the rows in the table, although the
+ * storage API does not necessarily enforce this requirement. If no primary
+ * key name is specified for a table then the storage API assumes there is
+ * a column named "id" that is used as the primary key. In this case when
+ * a new row is inserted using the storage API and no id is specified
+ * explictly in the row data, the storage API automatically generates a
+ * unique ID (typically a UUID) for the id column. To work across all
+ * possible implementations of the storage API it is safest, though, to
+ * specify the primary key column explicitly.
+ * FIXME: It's sort of a kludge to have to specify the primary key column
+ * here. Ideally there would be some sort of metadata -- perhaps stored
+ * directly in the table, at least in the NoSQL case -- that the
+ * storage API could query to obtain the primary key info.
+ * @param tableName The name of the table for which we're setting the key
+ * @param primaryKeyName The name of column to be used as the primary key
+ */
+ public void setTablePrimaryKeyName(String tableName, String primaryKeyName);
+
+ /** Create a new table if one does not already exist with the given name.
+ *
+ * @param tableName The name of the table to create.
+ * @param indexedColumns Which columns should be indexed
+ */
+ void createTable(String tableName, Set<String> indexedColumns);
+
+ /**
+ * @return the set of all tables that have been created via createTable
+ */
+ Set<String> getAllTableNames();
+
+ /** Create a query object representing the given query parameters. The query
+ * object can be passed to executeQuery to actually perform the query and obtain
+ * a result set.
+ *
+ * @param tableName The name of the table to query.
+ * @param columnNames The list of columns to return in the result set.
+ * @param predicate The predicate that specifies which rows to return in the result set.
+ * @param ordering Specification of order that rows are returned from the result set
+ * returned from executing the query. If the ordering is null, then rows are returned
+ * in an implementation-specific order.
+ * @return Query object to be passed to executeQuery.
+ */
+ IQuery createQuery(String tableName, String[] columnNames, IPredicate predicate, RowOrdering ordering);
+
+ /** Execute a query created with createQuery.
+ *
+ * @param query The query to execute
+ * @return The result set containing the rows/columns specified in the query.
+ */
+ IResultSet executeQuery(IQuery query);
+
+ /** Execute a query created with the given query parameters.
+ *
+ * @param tableName The name of the table to query.
+ * @param columnNames The list of columns to return in the result set.
+ * @param predicate The predicate that specifies which rows to return in the result set.
+ * @param ordering Specification of order that rows are returned from the result set
+ * returned from executing the query. If the ordering is null, then rows are returned
+ * in an implementation-specific order.
+ * @return The result set containing the rows/columns specified in the query.
+ */
+ IResultSet executeQuery(String tableName, String[] columnNames, IPredicate predicate,
+ RowOrdering ordering);
+
+ /** Execute a query and call the row mapper to map the results to Java objects.
+ *
+ * @param tableName The name of the table to query.
+ * @param columnNames The list of columns to return in the result set.
+ * @param predicate The predicate that specifies which rows to return in the result set.
+ * @param ordering Specification of order that rows are returned from the result set
+ * returned from executing the query. If the ordering is null, then rows are returned
+ * in an implementation-specific order.
+ * @param rowMapper The client-supplied object that maps the data in a row in the result
+ * set to a client object.
+ * @return The result set containing the rows/columns specified in the query.
+ */
+ Object[] executeQuery(String tableName, String[] columnNames, IPredicate predicate,
+ RowOrdering ordering, IRowMapper rowMapper);
+
+ /** Insert a new row in the table with the given column data.
+ * If the primary key is the default value of "id" and is not specified in the
+ * then a unique id will be automatically assigned to the row.
+ * @param tableName The name of the table to which to add the row
+ * @param values The map of column names/values to add to the table.
+ */
+ void insertRow(String tableName, Map<String,Object> values);
+
+ /** Update or insert a list of rows in the table.
+ * The primary key must be included in the map of values for each row.
+ * @param tableName The table to update or insert into
+ * @param values The map of column names/values to update the rows
+ */
+ void updateRows(String tableName, List<Map<String,Object>> rows);
+
+ /** Update the rows in the given table. Any rows matching the predicate
+ * are updated with the column names/values specified in the values map.
+ * (The values map should not contain the special column "id".)
+ * @param tableName The table to update
+ * @param predicate The predicate to use to select which rows to update
+ * @param values The map of column names/values to update the rows.
+ */
+ void updateMatchingRows(String tableName, IPredicate predicate, Map<String,Object> values);
+
+ /** Update or insert a row in the table with the given row key (primary
+ * key) and column names/values. (If the values map contains the special
+ * column "id", its value must match rowId.)
+ * @param tableName The table to update or insert into
+ * @param rowKey The ID (primary key) of the row to update
+ * @param values The map of column names/values to update the rows
+ */
+ void updateRow(String tableName, Object rowKey, Map<String,Object> values);
+
+ /** Update or insert a row in the table with the given column data.
+ * The primary key must be included in the map of values.
+ * @param tableName The table to update or insert into
+ * @param values The map of column names/values to update the rows
+ */
+ void updateRow(String tableName, Map<String,Object> values);
+
+ /** Delete the row with the given primary key.
+ *
+ * @param tableName The table from which to delete the row
+ * @param rowKey The primary key of the row to delete.
+ */
+ void deleteRow(String tableName, Object rowKey);
+
+ /** Delete the rows with the given keys.
+ *
+ * @param tableName The table from which to delete the rows
+ * @param rowKeys The set of primary keys of the rows to delete.
+ */
+ void deleteRows(String tableName, Set<Object> rowKeys);
+
+ /**
+ * Delete the rows that match the predicate
+ * @param tableName
+ * @param predicate
+ */
+ void deleteMatchingRows(String tableName, IPredicate predicate);
+
+ /** Query for a row with the given ID (primary key).
+ *
+ * @param tableName The name of the table to query
+ * @param rowKey The primary key of the row
+ * @return The result set containing the row with the given ID
+ */
+ IResultSet getRow(String tableName, Object rowKey);
+
+ /**
+ * Set exception handler to use for asynchronous operations.
+ * @param exceptionHandler
+ */
+ void setExceptionHandler(IStorageExceptionHandler exceptionHandler);
+
+ /**
+ * Asynchronous variant of executeQuery.
+ *
+ * @param query
+ * @return
+ */
+ public Future<IResultSet> executeQueryAsync(final IQuery query);
+
+ /**
+ * Asynchronous variant of executeQuery.
+ *
+ * @param tableName
+ * @param columnNames
+ * @param predicate
+ * @param ordering
+ * @return
+ */
+ public Future<IResultSet> executeQueryAsync(final String tableName,
+ final String[] columnNames, final IPredicate predicate,
+ final RowOrdering ordering);
+
+ /**
+ * Asynchronous variant of executeQuery
+ *
+ * @param tableName
+ * @param columnNames
+ * @param predicate
+ * @param ordering
+ * @param rowMapper
+ * @return
+ */
+ public Future<Object[]> executeQueryAsync(final String tableName,
+ final String[] columnNames, final IPredicate predicate,
+ final RowOrdering ordering, final IRowMapper rowMapper);
+
+ /**
+ * Asynchronous variant of insertRow.
+ *
+ * @param tableName
+ * @param values
+ * @return
+ */
+ public Future<?> insertRowAsync(final String tableName, final Map<String,Object> values);
+
+ /**
+ * Asynchronous variant of updateRows
+ * @param tableName
+ * @param rows
+ */
+ public Future<?> updateRowsAsync(final String tableName, final List<Map<String,Object>> rows);
+
+ /**
+ * Asynchronous variant of updateMatchingRows
+ *
+ * @param tableName
+ * @param predicate
+ * @param values
+ * @return
+ */
+ public Future<?> updateMatchingRowsAsync(final String tableName, final IPredicate predicate,
+ final Map<String,Object> values);
+
+ /**
+ * Asynchronous variant of updateRow
+ *
+ * @param tableName
+ * @param rowKey
+ * @param values
+ * @return
+ */
+ public Future<?> updateRowAsync(final String tableName, final Object rowKey,
+ final Map<String,Object> values);
+
+ /**
+ * Asynchronous version of updateRow
+ *
+ * @param tableName
+ * @param values
+ * @return
+ */
+ public Future<?> updateRowAsync(final String tableName, final Map<String,Object> values);
+
+ /**
+ * Asynchronous version of deleteRow
+ *
+ * @param tableName
+ * @param rowKey
+ * @return
+ */
+ public Future<?> deleteRowAsync(final String tableName, final Object rowKey);
+
+ /**
+ * Asynchronous version of deleteRows
+ *
+ * @param tableName
+ * @param rowKeys
+ * @return
+ */
+ public Future<?> deleteRowsAsync(final String tableName, final Set<Object> rowKeys);
+
+ /**
+ * Asynchronous version of deleteRows
+ *
+ * @param tableName
+ * @param predicate
+ * @return
+ */
+ public Future<?> deleteMatchingRowsAsync(final String tableName, final IPredicate predicate);
+
+ /**
+ * Asynchronous version of getRow
+ *
+ * @param tableName
+ * @param rowKey
+ * @return
+ */
+ public Future<?> getRowAsync(final String tableName, final Object rowKey);
+
+ /**
+ * Asynchronous version of save
+ *
+ * @param resultSet
+ * @return
+ */
+ public Future<?> saveAsync(final IResultSet resultSet);
+
+ /** Add a listener to the specified table. The listener is called
+ * when any modifications are made to the table. You can add the same
+ * listener instance to multiple tables, since the table name is
+ * included as a parameter in the listener methods.
+ * @param tableName The name of the table to listen for modifications
+ * @param listener The listener instance to call
+ */
+ public void addListener(String tableName, IStorageSourceListener listener);
+
+ /** Remove a listener from the specified table. The listener should
+ * have been previously added to the table with addListener.
+ * @param tableName The name of the table with the listener
+ * @param listener The previously installed listener instance
+ */
+ public void removeListener(String tableName, IStorageSourceListener listener);
+
+ /** This is logically a private method and should not be called by
+ * clients of this interface.
+ * @param notifications the notifications to dispatch
+ */
+ public void notifyListeners(List<StorageSourceNotification> notifications);
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/NullValueStorageException.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/NullValueStorageException.java
new file mode 100644
index 0000000..0c148b8
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/NullValueStorageException.java
@@ -0,0 +1,44 @@
+/**
+* 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.storage;
+
+public class NullValueStorageException extends StorageException {
+
+ private static final long serialVersionUID = 897572085681189926L;
+
+ private static String makeExceptionMessage(String columnName) {
+ String message = "Null column value could not be converted to built-in type";
+ if (columnName != null) {
+ message += ": column name = ";
+ message += columnName;
+ }
+ return message;
+ }
+
+ public NullValueStorageException() {
+ super(makeExceptionMessage(null));
+ }
+
+ public NullValueStorageException(String columnName) {
+ super(makeExceptionMessage(columnName));
+ }
+
+ public NullValueStorageException(String columnName, Throwable exc) {
+ super(makeExceptionMessage(columnName), exc);
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/OperatorPredicate.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/OperatorPredicate.java
new file mode 100644
index 0000000..dc78260
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/OperatorPredicate.java
@@ -0,0 +1,51 @@
+/**
+* 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.storage;
+
+/** Predicate class to specify rows by equality or comparison operations
+ * of column values. The Storage API uses the special column name of "id"
+ * to specify the primary key values for the row.
+ *
+ * @author rob
+ */
+public class OperatorPredicate implements IPredicate {
+
+ public enum Operator { EQ, LT, LTE, GT, GTE };
+
+ private String columnName;
+ private Operator operator;
+ private Comparable<?> value;
+
+ public OperatorPredicate(String columnName, Operator operator, Comparable<?> value) {
+ this.columnName = columnName;
+ this.operator = operator;
+ this.value = value;
+ }
+
+ public String getColumnName() {
+ return columnName;
+ }
+
+ public Operator getOperator() {
+ return operator;
+ }
+
+ public Comparable<?> getValue() {
+ return value;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/ResultSetIterator.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/ResultSetIterator.java
new file mode 100644
index 0000000..669833d
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/ResultSetIterator.java
@@ -0,0 +1,64 @@
+/**
+* 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.storage;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/** Iterator wrapper for an IResultSet, useful for iterating through query
+ * results in an enhanced for (foreach) loop.
+ *
+ * Note that the iterator manipulates the state of the underlying IResultSet.
+ */
+public class ResultSetIterator implements Iterator<IResultSet> {
+ private IResultSet resultSet;
+ private boolean hasAnother;
+ private boolean peekedAtNext;
+
+ public ResultSetIterator(IResultSet resultSet) {
+ this.resultSet = resultSet;
+ this.peekedAtNext = false;
+ }
+
+ @Override
+ public IResultSet next() {
+ if (!peekedAtNext) {
+ hasAnother = resultSet.next();
+ }
+ peekedAtNext = false;
+ if (!hasAnother)
+ throw new NoSuchElementException();
+ return resultSet;
+ }
+
+ @Override
+ public boolean hasNext() {
+ if (!peekedAtNext) {
+ hasAnother = resultSet.next();
+ peekedAtNext = true;
+ }
+ return hasAnother;
+ }
+
+ /** Row removal is not supported; use IResultSet.deleteRow instead.
+ */
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/RowOrdering.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/RowOrdering.java
new file mode 100644
index 0000000..f9e61ed
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/RowOrdering.java
@@ -0,0 +1,119 @@
+/**
+* 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.storage;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class RowOrdering {
+
+ public enum Direction { ASCENDING, DESCENDING };
+
+ public class Item {
+
+ private String column;
+ private Direction direction;
+
+ public Item(String column, Direction direction) {
+ assert(column != null);
+ assert(direction != null);
+ this.column = column;
+ this.direction = direction;
+ }
+
+ public String getColumn() {
+ return column;
+ }
+
+ public Direction getDirection() {
+ return direction;
+ }
+ }
+
+ private List<Item> itemList = new ArrayList<Item>();
+
+ public RowOrdering() {
+ }
+
+ public RowOrdering(String column) {
+ add(column);
+ }
+
+ public RowOrdering(String column, Direction direction) {
+ add(column, direction);
+ }
+
+ public RowOrdering(Item item) {
+ add(item);
+ }
+
+ public RowOrdering(Item[] itemArray) {
+ add(itemArray);
+ }
+
+ public RowOrdering(List<Item> itemList) {
+ add(itemList);
+ }
+
+ public void add(String column) {
+ itemList.add(new Item(column, Direction.ASCENDING));
+ }
+
+ public void add(String column, Direction direction) {
+ itemList.add(new Item(column, direction));
+ }
+
+ public void add(Item item) {
+ assert(item != null);
+ itemList.add(item);
+ }
+
+ public void add(Item[] itemArray) {
+ for (Item item: itemArray) {
+ itemList.add(item);
+ }
+ }
+
+ public void add(List<Item> itemList) {
+ this.itemList.addAll(itemList);
+ }
+
+ public List<Item> getItemList() {
+ return itemList;
+ }
+
+ public boolean equals(RowOrdering rowOrdering) {
+ if (rowOrdering == null)
+ return false;
+
+ int len1 = itemList.size();
+ int len2 = rowOrdering.getItemList().size();
+ if (len1 != len2)
+ return false;
+
+ for (int i = 0; i < len1; i++) {
+ Item item1 = itemList.get(i);
+ Item item2 = rowOrdering.getItemList().get(i);
+ if (!item1.getColumn().equals(item2.getColumn()) ||
+ item1.getDirection() != item2.getDirection())
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/StorageException.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/StorageException.java
new file mode 100644
index 0000000..f5dea23
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/StorageException.java
@@ -0,0 +1,44 @@
+/**
+* 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.storage;
+
+public class StorageException extends RuntimeException {
+
+ static final long serialVersionUID = 7839989010156155681L;
+
+ static private String makeExceptionMessage(String s) {
+ String message = "Storage Exception";
+ if (s != null) {
+ message += ": ";
+ message += s;
+ }
+ return message;
+ }
+
+ public StorageException() {
+ super(makeExceptionMessage(null));
+ }
+
+ public StorageException(String s) {
+ super(makeExceptionMessage(s));
+ }
+
+ public StorageException(String s, Throwable exc) {
+ super(makeExceptionMessage(s), exc);
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/StorageNotificationFormatException.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/StorageNotificationFormatException.java
new file mode 100644
index 0000000..f6ce565
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/StorageNotificationFormatException.java
@@ -0,0 +1,26 @@
+/**
+* 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.storage;
+
+public class StorageNotificationFormatException extends StorageException {
+ private static final long serialVersionUID = 504758477518283156L;
+
+ public StorageNotificationFormatException() {
+ super("Invalid storage notification format");
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/StorageSourceNotification.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/StorageSourceNotification.java
new file mode 100644
index 0000000..c9a5450
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/StorageSourceNotification.java
@@ -0,0 +1,108 @@
+/**
+* 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.storage;
+
+import java.util.Set;
+
+public class StorageSourceNotification {
+
+ public enum Action { MODIFY, DELETE };
+
+ private String tableName;
+ private Action action;
+ private Set<Object> keys;
+
+ public StorageSourceNotification() {
+ }
+
+ public StorageSourceNotification(String tableName, Action action, Set<Object> keys) {
+ this.tableName = tableName;
+ this.action = action;
+ this.keys = keys;
+ }
+
+ public String getTableName() {
+ return tableName;
+ }
+
+ public Action getAction() {
+ return action;
+ }
+
+ public Set<Object> getKeys() {
+ return keys;
+ }
+
+ public void setTableName(String tableName) {
+ this.tableName = tableName;
+ }
+
+ public void setAction(Action action) {
+ this.action = action;
+ }
+
+ public void setKeys(Set<Object> keys) {
+ this.keys = keys;
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ final int prime = 7867;
+ int result = 1;
+ result = prime * result + tableName.hashCode();
+ result = prime * result + action.hashCode();
+ result = prime * result + keys.hashCode();
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (!(obj instanceof StorageSourceNotification))
+ return false;
+ StorageSourceNotification other = (StorageSourceNotification) obj;
+ if (tableName == null) {
+ if (other.tableName != null)
+ return false;
+ } else if (!tableName.equals(other.tableName))
+ return false;
+ if (action == null) {
+ if (other.action != null)
+ return false;
+ } else if (action != other.action)
+ return false;
+ if (keys == null) {
+ if (other.keys != null)
+ return false;
+ } else if (!keys.equals(other.keys))
+ return false;
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return ("StorageNotification[table=" + tableName + "; action=" +
+ action.toString() + "; keys=" + keys.toString() + "]");
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/SynchronousExecutorService.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/SynchronousExecutorService.java
new file mode 100644
index 0000000..f1e7cd3
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/SynchronousExecutorService.java
@@ -0,0 +1,177 @@
+/**
+* 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.storage;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+public class SynchronousExecutorService implements ExecutorService {
+
+ class SynchronousFuture<T> implements Future<T> {
+
+ T result;
+ Exception exc;
+
+ public SynchronousFuture() {
+ }
+
+ public SynchronousFuture(T result) {
+ this.result = result;
+ }
+
+ public SynchronousFuture(Exception exc) {
+ this.exc = exc;
+ }
+
+ @Override
+ public boolean cancel(boolean mayInterruptIfRunning) {
+ return false;
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return false;
+ }
+
+ @Override
+ public boolean isDone() {
+ return true;
+ }
+
+ @Override
+ public T get() throws InterruptedException, ExecutionException {
+ if (exc != null)
+ throw new ExecutionException(exc);
+ return result;
+ }
+
+ @Override
+ public T get(long timeout, TimeUnit unit) throws InterruptedException,
+ ExecutionException, TimeoutException {
+ return get();
+ }
+ }
+
+ @Override
+ public void shutdown() {
+ }
+
+ @Override
+ public List<Runnable> shutdownNow() {
+ return null;
+ }
+
+ @Override
+ public boolean isShutdown() {
+ return false;
+ }
+
+ @Override
+ public boolean isTerminated() {
+ return false;
+ }
+
+ @Override
+ public boolean awaitTermination(long timeout, TimeUnit unit)
+ throws InterruptedException {
+ return false;
+ }
+
+ @Override
+ public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
+ throws InterruptedException {
+ List<Future<T>> l = new ArrayList<Future<T>>();
+ for (Callable<T> task : tasks) {
+ Future<T> future = submit(task);
+ l.add(future);
+ }
+ return l;
+ }
+
+ @Override
+ public <T> List<Future<T>> invokeAll(
+ Collection<? extends Callable<T>> tasks, long timeout, TimeUnit units)
+ throws InterruptedException {
+ return invokeAll(tasks);
+ }
+
+ @Override
+ public <T> T invokeAny(Collection<? extends Callable<T>> tasks)
+ throws InterruptedException, ExecutionException {
+ for (Callable<T> task : tasks) {
+ try {
+ task.call();
+ } catch (Exception e) {
+
+ }
+ }
+ throw new ExecutionException(new Exception("no task completed successfully"));
+ }
+
+ @Override
+ public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout,
+ TimeUnit units) throws InterruptedException, ExecutionException,
+ TimeoutException {
+ return invokeAny(tasks);
+ }
+
+ @Override
+ public <T> Future<T> submit(Callable<T> callable) {
+ try {
+ T result = callable.call();
+ return new SynchronousFuture<T>(result);
+ }
+ catch (Exception exc) {
+ return new SynchronousFuture<T>(exc);
+ }
+ }
+
+ @Override
+ public Future<?> submit(Runnable runnable) {
+ try {
+ runnable.run();
+ return new SynchronousFuture<Void>();
+ }
+ catch (Exception exc) {
+ return new SynchronousFuture<Void>(exc);
+ }
+ }
+
+ @Override
+ public <T> Future<T> submit(Runnable runnable, T result) {
+ try {
+ runnable.run();
+ return new SynchronousFuture<T>(result);
+ }
+ catch (Exception exc) {
+ return new SynchronousFuture<T>(exc);
+ }
+ }
+
+ @Override
+ public void execute(Runnable command) {
+ command.run();
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/TypeMismatchStorageException.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/TypeMismatchStorageException.java
new file mode 100644
index 0000000..5643140
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/TypeMismatchStorageException.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.storage;
+
+public class TypeMismatchStorageException extends StorageException {
+
+ private static final long serialVersionUID = -7923586656854871345L;
+
+ private static String makeExceptionMessage(String requestedType, String actualType, String columnName) {
+ if (requestedType == null)
+ requestedType = "???";
+ if (actualType == null)
+ actualType = "???";
+ if (columnName == null)
+ columnName = "???";
+ String message = "The requested type (" + requestedType + ") does not match the actual type (" + actualType + ") of the value for column \"" + columnName + "\".";
+ return message;
+ }
+
+ public TypeMismatchStorageException() {
+ super(makeExceptionMessage(null, null, null));
+ }
+
+ public TypeMismatchStorageException(String requestedType, String actualType, String columnName) {
+ super(makeExceptionMessage(requestedType, actualType, columnName));
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/memory/MemoryStorageSource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/memory/MemoryStorageSource.java
new file mode 100644
index 0000000..8a69eca
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/memory/MemoryStorageSource.java
@@ -0,0 +1,198 @@
+/**
+* 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.storage.memory;
+
+import net.floodlightcontroller.core.module.FloodlightModuleContext;
+import net.floodlightcontroller.perfmon.IPktInProcessingTimeService;
+import net.floodlightcontroller.storage.nosql.NoSqlStorageSource;
+import net.floodlightcontroller.storage.SynchronousExecutorService;
+import net.floodlightcontroller.storage.IStorageSourceService;
+import net.floodlightcontroller.core.module.IFloodlightService;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import net.floodlightcontroller.storage.StorageException;
+
+public class MemoryStorageSource extends NoSqlStorageSource {
+
+ private Map<String, MemoryTable> tableMap = new HashMap<String,MemoryTable>();
+ IPktInProcessingTimeService pktinProcessingTime;
+
+ synchronized private MemoryTable getTable(String tableName, boolean create) {
+ MemoryTable table = tableMap.get(tableName);
+ if (table == null) {
+ if (!create)
+ throw new StorageException("Table " + tableName + " does not exist");
+ table = new MemoryTable(tableName);
+ tableMap.put(tableName, table);
+ }
+ return table;
+ }
+
+ @Override
+ protected Collection<Map<String,Object>> getAllRows(String tableName, String[] columnNameList) {
+ MemoryTable table = getTable(tableName, false);
+ return table.getAllRows();
+ }
+
+ @Override
+ protected Map<String,Object> getRow(String tableName, String[] columnNameList, Object rowKey) {
+ MemoryTable table = getTable(tableName, false);
+ return table.getRow(rowKey);
+ }
+
+ @Override
+ protected List<Map<String,Object>> executeEqualityQuery(String tableName,
+ String[] columnNameList, String predicateColumnName, Comparable<?> value) {
+ MemoryTable table = getTable(tableName, false);
+ List<Map<String,Object>> result = new ArrayList<Map<String,Object>>();
+ synchronized (table) {
+ Collection<Map<String,Object>> allRows = table.getAllRows();
+ for (Map<String,Object> row : allRows) {
+ Object v = row.get(predicateColumnName);
+ if (value.equals(v)) {
+ result.add(row);
+ }
+ }
+ }
+ return result;
+ }
+
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ @Override
+ protected List<Map<String,Object>> executeRangeQuery(String tableName,
+ String[] columnNameList, String predicateColumnName,
+ Comparable<?> startValue, boolean startInclusive, Comparable<?> endValue, boolean endInclusive) {
+ MemoryTable table = getTable(tableName, false);
+ List<Map<String,Object>> result = new ArrayList<Map<String,Object>>();
+ synchronized (table) {
+ Collection<Map<String,Object>> allRows = table.getAllRows();
+ for (Map<String,Object> row : allRows) {
+ Comparable value = (Comparable) row.get(predicateColumnName);
+ if (value != null) {
+ int compareResult = value.compareTo(startValue);
+ if ((compareResult > 0) || (startInclusive && (compareResult >= 0))) {
+ compareResult = value.compareTo(endValue);
+ if ((compareResult < 0) || (startInclusive && (compareResult <= 0))) {
+ result.add(row);
+ }
+ }
+ }
+ }
+ }
+ return result;
+ }
+
+ @Override
+ protected void insertRows(String tableName, List<Map<String,Object>> insertRowList) {
+ MemoryTable table = getTable(tableName, false);
+ String primaryKeyName = getTablePrimaryKeyName(tableName);
+ synchronized (table) {
+ for (Map<String,Object> row : insertRowList) {
+ Object primaryKey = row.get(primaryKeyName);
+ if (primaryKey == null) {
+ if (primaryKeyName.equals(DEFAULT_PRIMARY_KEY_NAME)) {
+ row = new HashMap<String,Object>(row);
+ primaryKey = table.getNextId();
+ row.put(primaryKeyName, primaryKey);
+ }
+ }
+ table.insertRow(primaryKey, row);
+ }
+ }
+ }
+
+ @Override
+ protected void updateRows(String tableName, Set<Object> rowKeys, Map<String,Object> updateRowList) {
+ MemoryTable table = getTable(tableName, false);
+ synchronized (table) {
+ for (Object rowKey : rowKeys) {
+ Map<String,Object> row = table.getRow(rowKey);
+ if (row == null)
+ row = table.newRow(rowKey);
+ for (Map.Entry<String,Object> entry: updateRowList.entrySet()) {
+ row.put(entry.getKey(), entry.getValue());
+ }
+ }
+ }
+ }
+
+ @Override
+ protected void updateRowsImpl(String tableName, List<Map<String,Object>> updateRowList) {
+ MemoryTable table = getTable(tableName, false);
+ String primaryKeyName = getTablePrimaryKeyName(tableName);
+ synchronized (table) {
+ for (Map<String,Object> updateRow : updateRowList) {
+ Object rowKey = updateRow.get(primaryKeyName);
+ if (rowKey == null)
+ throw new StorageException("Primary key not found.");
+ Map<String,Object> row = table.getRow(rowKey);
+ if (row == null)
+ row = table.newRow(rowKey);
+ for (Map.Entry<String,Object> entry: updateRow.entrySet()) {
+ row.put(entry.getKey(), entry.getValue());
+ }
+ }
+ }
+ }
+
+ @Override
+ protected void deleteRowsImpl(String tableName, Set<Object> rowKeys) {
+ MemoryTable table = getTable(tableName, false);
+ synchronized (table) {
+ for (Object rowKey : rowKeys) {
+ table.deleteRow(rowKey);
+ }
+ }
+ }
+
+ @Override
+ public void createTable(String tableName, Set<String> indexedColumnNames) {
+ super.createTable(tableName, indexedColumnNames);
+ getTable(tableName, true);
+ }
+
+ public void setPktinProcessingTime(
+ IPktInProcessingTimeService pktinProcessingTime) {
+ this.pktinProcessingTime = pktinProcessingTime;
+ }
+
+ // IFloodlightModule methods
+
+ @Override
+ public void startUp(FloodlightModuleContext context) {
+ super.startUp(context);
+ executorService = new SynchronousExecutorService();
+ }
+
+ @Override
+ public Map<Class<? extends IFloodlightService>,
+ IFloodlightService> getServiceImpls() {
+ Map<Class<? extends IFloodlightService>,
+ IFloodlightService> m =
+ new HashMap<Class<? extends IFloodlightService>,
+ IFloodlightService>();
+ m.put(IStorageSourceService.class, this);
+ return m;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/memory/MemoryTable.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/memory/MemoryTable.java
new file mode 100644
index 0000000..f87ee45
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/memory/MemoryTable.java
@@ -0,0 +1,72 @@
+/**
+* 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.storage.memory;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.TreeMap;
+
+public class MemoryTable {
+
+ private String tableName;
+ private Map<Object,Map<String,Object>> rowMap;
+ private int nextId;
+
+ MemoryTable(String tableName) {
+ this.tableName = tableName;
+ rowMap = new TreeMap<Object,Map<String,Object>>();
+ nextId = 0;
+ }
+
+ String getTableName() {
+ return tableName;
+ }
+
+ Collection<Map<String,Object>> getAllRows() {
+ return rowMap.values();
+ }
+
+ Map<String,Object> getRow(Object key) {
+ Map<String,Object> row = rowMap.get(key);
+ return row;
+ }
+
+ // rkv: Do we still need this? Probably needs to be tweaked a bit
+ // to work with the support for specifying which column to use as the
+ // primary key
+ Map<String,Object> newRow(Object key) {
+ Map<String,Object> row = new HashMap<String, Object>();
+ row.put("id", key);
+ rowMap.put(key, row);
+ return row;
+ }
+
+ void insertRow(Object key, Map<String,Object> rowValues) {
+ assert(key != null);
+ rowMap.put(key, rowValues);
+ }
+
+ void deleteRow(Object rowKey) {
+ rowMap.remove(rowKey);
+ }
+
+ Integer getNextId() {
+ return new Integer(++nextId);
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/nosql/NoSqlQuery.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/nosql/NoSqlQuery.java
new file mode 100644
index 0000000..05f8fc7
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/nosql/NoSqlQuery.java
@@ -0,0 +1,77 @@
+/**
+* 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.storage.nosql;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import net.floodlightcontroller.storage.IPredicate;
+import net.floodlightcontroller.storage.IQuery;
+import net.floodlightcontroller.storage.RowOrdering;
+
+public class NoSqlQuery implements IQuery {
+
+ private String tableName;
+ private String[] columnNameList;
+ private IPredicate predicate;
+ private RowOrdering rowOrdering;
+ private Map<String,Comparable<?>> parameterMap;
+
+ NoSqlQuery(String className, String[] columnNameList, IPredicate predicate, RowOrdering rowOrdering) {
+ this.tableName = className;
+ this.columnNameList = columnNameList;
+ this.predicate = predicate;
+ this.rowOrdering = rowOrdering;
+ }
+
+ @Override
+ public void setParameter(String name, Object value) {
+ if (parameterMap == null)
+ parameterMap = new HashMap<String,Comparable<?>>();
+ parameterMap.put(name, (Comparable<?>)value);
+ }
+
+ @Override
+ public String getTableName() {
+ return tableName;
+ }
+
+ String[] getColumnNameList() {
+ return columnNameList;
+ }
+
+ IPredicate getPredicate() {
+ return predicate;
+ }
+
+ RowOrdering getRowOrdering() {
+ return rowOrdering;
+ }
+
+ Comparable<?> getParameter(String name) {
+ Comparable<?> value = null;
+ if (parameterMap != null) {
+ value = parameterMap.get(name);
+ }
+ return value;
+ }
+
+ Map<String,Comparable<?>> getParameterMap() {
+ return parameterMap;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/nosql/NoSqlResultSet.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/nosql/NoSqlResultSet.java
new file mode 100644
index 0000000..b3a8c20
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/nosql/NoSqlResultSet.java
@@ -0,0 +1,487 @@
+/**
+* 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.storage.nosql;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.TimeZone;
+
+import net.floodlightcontroller.storage.IResultSet;
+import net.floodlightcontroller.storage.NullValueStorageException;
+import net.floodlightcontroller.storage.ResultSetIterator;
+import net.floodlightcontroller.storage.StorageException;
+import net.floodlightcontroller.storage.TypeMismatchStorageException;
+
+public class NoSqlResultSet implements IResultSet {
+
+ NoSqlStorageSource storageSource;
+ String tableName;
+ String primaryKeyName;
+ List<Map<String,Object>> rowList;
+ int currentIndex;
+ Map<String,Object> currentRowUpdate;
+ List<Map<String,Object>> rowUpdateList;
+ Set<Object> rowDeleteSet;
+ Iterator<IResultSet> resultSetIterator;
+
+ NoSqlResultSet(NoSqlStorageSource storageSource, String tableName, List<Map<String,Object>> rowList) {
+ this.storageSource = storageSource;
+ this.primaryKeyName = storageSource.getTablePrimaryKeyName(tableName);
+ this.tableName = tableName;
+ if (rowList == null)
+ rowList = new ArrayList<Map<String,Object>>();
+ this.rowList = rowList;
+ currentIndex = -1;
+ }
+
+ void addRow(Map<String,Object> row) {
+ rowList.add(row);
+ }
+
+ @Override
+ public Map<String,Object> getRow() {
+ if ((currentIndex < 0) || (currentIndex >= rowList.size())) {
+ throw new StorageException("No current row in result set.");
+ }
+
+ return rowList.get(currentIndex);
+ }
+
+ @Override
+ public boolean containsColumn(String columnName) {
+ return getObject(columnName) != null;
+ }
+
+ @Override
+ public void close() {
+ }
+
+ private void endCurrentRowUpdate() {
+ if (currentRowUpdate != null) {
+ if (rowUpdateList == null)
+ rowUpdateList = new ArrayList<Map<String,Object>>();
+ rowUpdateList.add(currentRowUpdate);
+ currentRowUpdate = null;
+ }
+ }
+
+ @Override
+ public boolean next() {
+ endCurrentRowUpdate();
+ currentIndex++;
+ return currentIndex < rowList.size();
+ }
+
+ @Override
+ public void save() {
+ endCurrentRowUpdate();
+
+ if (rowUpdateList != null) {
+ storageSource.updateRows(tableName, rowUpdateList);
+ rowUpdateList = null;
+ }
+
+ if (rowDeleteSet != null) {
+ storageSource.deleteRows(tableName, rowDeleteSet);
+ rowDeleteSet = null;
+ }
+ }
+
+ Object getObject(String columnName) {
+ Map<String,Object> row = rowList.get(currentIndex);
+ Object value = row.get(columnName);
+ return value;
+ }
+
+ @Override
+ public boolean getBoolean(String columnName) {
+ Boolean b = getBooleanObject(columnName);
+ if (b == null)
+ throw new NullValueStorageException(columnName);
+ return b.booleanValue();
+ }
+
+ @Override
+ public byte getByte(String columnName) {
+ Byte b = getByteObject(columnName);
+ if (b == null)
+ throw new NullValueStorageException(columnName);
+ return b.byteValue();
+ }
+
+ @Override
+ public byte[] getByteArray(String columnName) {
+ byte[] b = null;
+ Object obj = getObject(columnName);
+ if (obj != null) {
+ if (!(obj instanceof byte[]))
+ throw new StorageException("Invalid byte array value");
+ b = (byte[])obj;
+ }
+ return b;
+ }
+
+ @Override
+ public double getDouble(String columnName) {
+ Double d = getDoubleObject(columnName);
+ if (d == null)
+ throw new NullValueStorageException(columnName);
+ return d.doubleValue();
+ }
+
+ @Override
+ public float getFloat(String columnName) {
+ Float f = getFloatObject(columnName);
+ if (f == null)
+ throw new NullValueStorageException(columnName);
+ return f.floatValue();
+ }
+
+ @Override
+ public int getInt(String columnName) {
+ Integer i = getIntegerObject(columnName);
+ if (i == null)
+ throw new NullValueStorageException(columnName);
+ return i.intValue();
+ }
+
+ @Override
+ public long getLong(String columnName) {
+ Long l = getLongObject(columnName);
+ if (l == null)
+ throw new NullValueStorageException(columnName);
+ return l.longValue();
+ }
+
+ @Override
+ public short getShort(String columnName) {
+ Short s = getShortObject(columnName);
+ if (s == null)
+ throw new NullValueStorageException(columnName);
+ return s.shortValue();
+ }
+
+ @Override
+ public String getString(String columnName) {
+ Object obj = getObject(columnName);
+ if (obj == null)
+ return null;
+ return obj.toString();
+ }
+
+ @Override
+ public Date getDate(String column) {
+ Date d;
+ Object obj = getObject(column);
+ if (obj == null) {
+ d = null;
+ } else if (obj instanceof Date) {
+ d = (Date) obj;
+ } else {
+ SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
+ dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
+ try {
+ d = dateFormat.parse(obj.toString());
+ }
+ catch (ParseException exc) {
+ throw new TypeMismatchStorageException(Date.class.getName(), obj.getClass().getName(), column);
+ }
+ }
+ return d;
+ }
+
+
+ @Override
+ public Short getShortObject(String columnName)
+ {
+ Short s;
+ Object obj = getObject(columnName);
+ if (obj instanceof Short) {
+ s = (Short)obj;
+ } else if (obj != null) {
+ try {
+ s = Short.parseShort(obj.toString());
+ }
+ catch (NumberFormatException exc) {
+ throw new TypeMismatchStorageException(Short.class.getName(), obj.getClass().getName(), columnName);
+ }
+ } else {
+ s = null;
+ }
+ return s;
+ }
+
+ @Override
+ public Integer getIntegerObject(String columnName)
+ {
+ Integer i;
+ Object obj = getObject(columnName);
+ if (obj instanceof Integer) {
+ i = (Integer)obj;
+ } else if (obj != null) {
+ try {
+ i = Integer.parseInt(obj.toString());
+ }
+ catch (NumberFormatException exc) {
+ throw new TypeMismatchStorageException(Integer.class.getName(), obj.getClass().getName(), columnName);
+ }
+ } else {
+ i = null;
+ }
+ return i;
+ }
+
+ @Override
+ public Long getLongObject(String columnName)
+ {
+ Long l;
+ Object obj = getObject(columnName);
+ if (obj instanceof Long) {
+ l = (Long)obj;
+ } else if (obj != null) {
+ try {
+ l = Long.parseLong(obj.toString());
+ }
+ catch (NumberFormatException exc) {
+ throw new TypeMismatchStorageException(Long.class.getName(), obj.getClass().getName(), columnName);
+ }
+ } else {
+ l = null;
+ }
+ return l;
+ }
+
+ @Override
+ public Float getFloatObject(String columnName)
+ {
+ Float f;
+ Object obj = getObject(columnName);
+ if (obj instanceof Float) {
+ f = (Float)obj;
+ } else if (obj != null) {
+ try {
+ f = Float.parseFloat(obj.toString());
+ }
+ catch (NumberFormatException exc) {
+ throw new TypeMismatchStorageException(Float.class.getName(), obj.getClass().getName(), columnName);
+ }
+ } else {
+ f = null;
+ }
+ return f;
+ }
+
+ @Override
+ public Double getDoubleObject(String columnName)
+ {
+ Double d;
+ Object obj = getObject(columnName);
+ if (obj instanceof Double) {
+ d = (Double)obj;
+ } else if (obj != null) {
+ try {
+ d = Double.parseDouble(obj.toString());
+ }
+ catch (NumberFormatException exc) {
+ throw new TypeMismatchStorageException(Double.class.getName(), obj.getClass().getName(), columnName);
+ }
+ } else {
+ d = null;
+ }
+ return d;
+ }
+
+ @Override
+ public Boolean getBooleanObject(String columnName)
+ {
+ Boolean b;
+ Object obj = getObject(columnName);
+ if (obj instanceof Boolean) {
+ b = (Boolean)obj;
+ } else if (obj != null) {
+ try {
+ b = Boolean.parseBoolean(obj.toString());
+ }
+ catch (NumberFormatException exc) {
+ throw new TypeMismatchStorageException(Boolean.class.getName(), obj.getClass().getName(), columnName);
+ }
+ } else {
+ b = null;
+ }
+ return b;
+ }
+
+ @Override
+ public Byte getByteObject(String columnName)
+ {
+ Byte b;
+ Object obj = getObject(columnName);
+ if (obj instanceof Byte) {
+ b = (Byte)obj;
+ } else if (obj != null) {
+ try {
+ b = Byte.parseByte(obj.toString());
+ }
+ catch (NumberFormatException exc) {
+ throw new TypeMismatchStorageException(Byte.class.getName(), obj.getClass().getName(), columnName);
+ }
+ } else {
+ b = null;
+ }
+ return b;
+ }
+
+
+ @Override
+ public boolean isNull(String columnName)
+ {
+ Object obj = getObject(columnName);
+ return (obj == null);
+ }
+
+ private void addRowUpdate(String column, Object value) {
+ if (currentRowUpdate == null) {
+ currentRowUpdate = new HashMap<String,Object>();
+ Object key = rowList.get(currentIndex).get(primaryKeyName);
+ currentRowUpdate.put(primaryKeyName, key);
+ }
+ currentRowUpdate.put(column, value);
+ }
+
+ @Override
+ public void setBoolean(String columnName, boolean value) {
+ addRowUpdate(columnName, value);
+ }
+
+ @Override
+ public void setByte(String columnName, byte value) {
+ addRowUpdate(columnName, value);
+ }
+
+ @Override
+ public void setByteArray(String columnName, byte[] byteArray) {
+ addRowUpdate(columnName, byteArray);
+ }
+
+ @Override
+ public void setDouble(String columnName, double value) {
+ addRowUpdate(columnName, value);
+ }
+
+ @Override
+ public void setFloat(String columnName, float value) {
+ addRowUpdate(columnName, value);
+ }
+
+ @Override
+ public void setInt(String columnName, int value) {
+ addRowUpdate(columnName, value);
+ }
+
+ @Override
+ public void setLong(String columnName, long value) {
+ addRowUpdate(columnName, value);
+ }
+
+ @Override
+ public void setShort(String columnName, short value) {
+ addRowUpdate(columnName, value);
+ }
+
+ @Override
+ public void setString(String columnName, String value) {
+ addRowUpdate(columnName, value);
+ }
+
+ @Override
+ public void setShortObject(String columnName, Short value)
+ {
+ addRowUpdate(columnName, value);
+ }
+
+ @Override
+ public void setIntegerObject(String columnName, Integer value)
+ {
+ addRowUpdate(columnName, value);
+ }
+
+ @Override
+ public void setLongObject(String columnName, Long value)
+ {
+ addRowUpdate(columnName, value);
+ }
+
+ @Override
+ public void setFloatObject(String columnName, Float value)
+ {
+ addRowUpdate(columnName, value);
+ }
+
+ @Override
+ public void setDoubleObject(String columnName, Double value)
+ {
+ addRowUpdate(columnName, value);
+ }
+
+ @Override
+ public void setBooleanObject(String columnName, Boolean value)
+ {
+ addRowUpdate(columnName, value);
+ }
+
+ @Override
+ public void setByteObject(String columnName, Byte value)
+ {
+ addRowUpdate(columnName, value);
+ }
+
+ @Override
+ public void setDate(String column, Date value) {
+ addRowUpdate(column, value);
+ }
+
+
+ public void setNull(String columnName)
+ {
+ addRowUpdate(columnName, null);
+ }
+
+
+ @Override
+ public void deleteRow() {
+ Object key = (String) rowList.get(currentIndex).get(primaryKeyName);
+ if (rowDeleteSet == null)
+ rowDeleteSet = new HashSet<Object>();
+ rowDeleteSet.add(key);
+ }
+
+ @Override
+ public Iterator<IResultSet> iterator() {
+ if (resultSetIterator == null)
+ resultSetIterator = new ResultSetIterator(this);
+ return resultSetIterator;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/nosql/NoSqlStorageSource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/nosql/NoSqlStorageSource.java
new file mode 100644
index 0000000..d7e5f95
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/nosql/NoSqlStorageSource.java
@@ -0,0 +1,823 @@
+/**
+* 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.storage.nosql;
+
+import java.lang.Class;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+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.TimeZone;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import net.floodlightcontroller.storage.AbstractStorageSource;
+import net.floodlightcontroller.storage.CompoundPredicate;
+import net.floodlightcontroller.storage.IPredicate;
+import net.floodlightcontroller.storage.IQuery;
+import net.floodlightcontroller.storage.IResultSet;
+import net.floodlightcontroller.storage.OperatorPredicate;
+import net.floodlightcontroller.storage.RowOrdering;
+import net.floodlightcontroller.storage.StorageException;
+import net.floodlightcontroller.storage.StorageSourceNotification;
+import net.floodlightcontroller.storage.TypeMismatchStorageException;
+
+public abstract class NoSqlStorageSource extends AbstractStorageSource {
+ protected static Logger log = LoggerFactory.getLogger(NoSqlStorageSource.class);
+
+ public enum ColumnIndexMode { NOT_INDEXED, RANGE_INDEXED, EQUALITY_INDEXED };
+
+ protected static final String DEFAULT_PRIMARY_KEY_NAME = "id";
+
+ private Map<String,String> tablePrimaryKeyMap = new HashMap<String,String>();
+ private Map<String, Map<String,ColumnIndexMode>> tableIndexedColumnMap =
+ new HashMap<String,Map<String,ColumnIndexMode>>();
+
+ abstract class NoSqlPredicate {
+
+ public boolean incorporateComparison(String columnName,
+ OperatorPredicate.Operator operator, Comparable<?> value,
+ CompoundPredicate.Operator parentOperator) {
+ return false;
+ }
+
+ public boolean canExecuteEfficiently() {
+ return false;
+ }
+
+ public List<Map<String,Object>> execute(String[] columnNames) {
+ assert(false);
+ return null;
+ }
+
+ abstract public boolean matchesRow(Map<String,Object> row);
+ }
+
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ class NoSqlRangePredicate extends NoSqlPredicate {
+ NoSqlStorageSource storageSource;
+ String tableName;
+ String columnName;
+ Comparable<?> startValue;
+ boolean startInclusive;
+ Comparable<?> endValue;
+ boolean endInclusive;
+
+ NoSqlRangePredicate(NoSqlStorageSource storageSource, String tableName,
+ String columnName, Comparable<?> startValue, boolean startInclusive,
+ Comparable<?> endValue, boolean endInclusive) {
+ this.storageSource = storageSource;
+ this.tableName = tableName;
+ this.columnName = columnName;
+ this.startValue = startValue;
+ this.startInclusive = startInclusive;
+ this.endValue = endValue;
+ this.endInclusive = endInclusive;
+ }
+
+ public boolean incorporateComparison(String columnName,
+ OperatorPredicate.Operator operator, Comparable<?> value,
+ CompoundPredicate.Operator parentOperator) {
+
+ assert(operator != null);
+ assert(parentOperator != null);
+
+ // Must be the same column to incorporate
+ if (!this.columnName.equals(columnName))
+ return false;
+
+ // The only time we allow a null value is if it's an EQ operator.
+ // In that case we can only incorporate if this predicate is also
+ // a null equality predicate.
+ if (value == null) {
+ return ((operator == OperatorPredicate.Operator.EQ) &&
+ (startValue == null) && (endValue == null) &&
+ startInclusive && endInclusive);
+ }
+
+ // Don't incorporate parameterized values
+ if (value instanceof String) {
+ String s = (String)value;
+ if (s.startsWith("?") && s.endsWith("?")) {
+ return false;
+ }
+ }
+
+ if (parentOperator == CompoundPredicate.Operator.AND) {
+ switch (operator) {
+ case EQ:
+ if (matchesValue(value)) {
+ startValue = endValue = value;
+ startInclusive = endInclusive = true;
+ return true;
+ }
+ break;
+ case LT:
+ if ((endValue == null) || (((Comparable)value).compareTo(endValue) <= 0)) {
+ endValue = value;
+ endInclusive = false;
+ return true;
+ }
+ break;
+ case LTE:
+ if ((endValue == null) || (((Comparable)value).compareTo(endValue) < 0)) {
+ endValue = value;
+ endInclusive = true;
+ return true;
+ }
+ break;
+ case GT:
+ if ((startValue == null) || (((Comparable)value).compareTo(startValue) >= 0)) {
+ startValue = value;
+ startInclusive = false;
+ return true;
+ }
+ break;
+ case GTE:
+ if ((startValue == null) || (((Comparable)value).compareTo(startValue) > 0)) {
+ startValue = value;
+ startInclusive = true;
+ return true;
+ }
+ break;
+ }
+ } else {
+ switch (operator) {
+ case EQ:
+ if (matchesValue(value))
+ return true;
+ break;
+ case LT:
+ if ((endValue == null) || (((Comparable)value).compareTo(endValue) > 0)) {
+ endValue = value;
+ endInclusive = false;
+ return true;
+ }
+ break;
+ case LTE:
+ if ((endValue == null) || (((Comparable)value).compareTo(endValue) >= 0)) {
+ endValue = value;
+ endInclusive = true;
+ return true;
+ }
+ break;
+ case GT:
+ if ((startValue == null) || (((Comparable)value).compareTo(startValue) < 0)) {
+ startValue = value;
+ startInclusive = false;
+ return true;
+ }
+ break;
+ case GTE:
+ if ((startValue == null) || (((Comparable)value).compareTo(startValue) <= 0)) {
+ startValue = value;
+ startInclusive = true;
+ return true;
+ }
+ break;
+ }
+ }
+
+ return false;
+ }
+
+ private boolean isEqualityRange() {
+ return (startValue == endValue) && startInclusive && endInclusive;
+ }
+
+ public boolean canExecuteEfficiently() {
+ ColumnIndexMode indexMode = storageSource.getColumnIndexMode(tableName, columnName);
+ switch (indexMode) {
+ case NOT_INDEXED:
+ return false;
+ case RANGE_INDEXED:
+ return true;
+ case EQUALITY_INDEXED:
+ return isEqualityRange();
+ }
+ return true;
+ }
+
+ public List<Map<String,Object>> execute(String columnNameList[]) {
+ List<Map<String,Object>> rowList;
+ if (isEqualityRange())
+ rowList = storageSource.executeEqualityQuery(tableName, columnNameList, columnName, startValue);
+ else
+ rowList = storageSource.executeRangeQuery(tableName, columnNameList, columnName,
+ startValue, startInclusive, endValue, endInclusive);
+
+ return rowList;
+ }
+
+ Comparable<?> coerceValue(Comparable<?> value, Class targetClass) {
+
+ if (value == null)
+ return null;
+
+ if (value.getClass() == targetClass)
+ return value;
+
+ // FIXME: For now we convert by first converting the source value to a
+ // string and then converting to the target type. This logic probably needs
+ // another pass to make it more robust/optimized.
+
+ String s = value.toString();
+ Comparable<?> obj = null;
+
+ try {
+ if (targetClass == Integer.class) {
+ obj = new Integer(s);
+ } else if (targetClass == Long.class) {
+ obj = new Long(s);
+ } else if (targetClass == Short.class) {
+ obj = new Short(s);
+ } else if (targetClass == Boolean.class) {
+ obj = new Boolean(s);
+ } else if (targetClass == Float.class) {
+ obj = new Float(s);
+ } else if (targetClass == Double.class) {
+ obj = new Double(s);
+ } else if (targetClass == Byte.class) {
+ obj = new Byte(s);
+ } else if (targetClass == String.class) {
+ obj = s;
+ } else if (targetClass == Date.class) {
+ SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
+ dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
+ try {
+ obj = dateFormat.parse(s);
+ }
+ catch (ParseException exc) {
+ throw new TypeMismatchStorageException(Date.class.getName(), value.getClass().getName(), "???");
+ }
+ }
+ }
+ catch (Exception exc) {
+ // Ignore the exception here. In this case obj will not be set, so we'll
+ // throw the StorageException below when we check for a null obj.
+ }
+
+ if (obj == null)
+ throw new StorageException("Column value could not be coerced to the correct type");
+
+ return obj;
+ }
+
+ boolean matchesValue(Comparable<?> value) {
+ boolean isNullEqPredicate = (startValue == null) && (endValue == null) && startInclusive && endInclusive;
+ if (value == null)
+ return isNullEqPredicate;
+
+ if (isNullEqPredicate)
+ return false;
+
+ int result;
+ Comparable<?> coercedValue;
+ if (startValue != null) {
+ coercedValue = coerceValue(value, startValue.getClass());
+ result = ((Comparable)coercedValue).compareTo(startValue);
+ if ((result < 0) || (!startInclusive && (result == 0)))
+ return false;
+ }
+ if (endValue != null) {
+ coercedValue = coerceValue(value, endValue.getClass());
+ result = ((Comparable)coercedValue).compareTo(endValue);
+ if ((result > 0) || (!endInclusive && (result == 0)))
+ return false;
+ }
+ return true;
+ }
+
+ public boolean matchesRow(Map<String,Object> row) {
+ Comparable value = (Comparable)row.get(columnName);
+ return matchesValue(value);
+ }
+ }
+
+ class NoSqlOperatorPredicate extends NoSqlPredicate {
+
+ NoSqlStorageSource storageSource;
+ String columnName;
+ OperatorPredicate.Operator operator;
+ Object value;
+
+ NoSqlOperatorPredicate(NoSqlStorageSource storageSource, String columnName,
+ OperatorPredicate.Operator operator, Object value) {
+ this.storageSource = storageSource;
+ this.columnName = columnName;
+ this.operator = operator;
+ this.value = value;
+ }
+
+ public boolean incorporateComparison(String columnName,
+ OperatorPredicate.Operator operator, Comparable<?> value,
+ CompoundPredicate.Operator parentOperator) {
+ return false;
+ }
+
+ public boolean canExecuteEfficiently() {
+ return false;
+ }
+
+ public List<Map<String,Object>> execute(String columnNames[]) {
+ throw new StorageException("Unimplemented predicate.");
+ }
+
+ public boolean matchesRow(Map<String,Object> row) {
+ return false;
+ }
+ }
+
+ class NoSqlCompoundPredicate extends NoSqlPredicate {
+
+ NoSqlStorageSource storageSource;
+ CompoundPredicate.Operator operator;
+ boolean negated;
+ List<NoSqlPredicate> predicateList;
+
+ NoSqlCompoundPredicate(NoSqlStorageSource storageSource, CompoundPredicate.Operator operator,
+ boolean negated, List<NoSqlPredicate> predicateList) {
+ this.storageSource = storageSource;
+ this.operator = operator;
+ this.negated = negated;
+ this.predicateList = predicateList;
+ }
+
+ public boolean incorporateComparison(String columnName,
+ OperatorPredicate.Operator operator, Comparable<?> value,
+ CompoundPredicate.Operator parentOperator) {
+ // It may be possible to incorporate other operator predicate into this one,
+ // but it would need to take into account the negated attribute and I'd need
+ // to think about it some more to make sure it was correct, so for now we just
+ // disallow incorporation
+ //if (parentOperator == this.operator) {
+ // for (NoSqlPredicate predicate: predicateList) {
+ // if (predicate.incorporateComparison(columnName, operator, value, parentOperator))
+ // return true;
+ // }
+ //}
+ return false;
+ }
+
+ public boolean canExecuteEfficiently() {
+ if (operator == CompoundPredicate.Operator.AND) {
+ for (NoSqlPredicate predicate: predicateList) {
+ if (predicate.canExecuteEfficiently()) {
+ return true;
+ }
+ }
+ return false;
+ } else {
+ for (NoSqlPredicate predicate: predicateList) {
+ if (!predicate.canExecuteEfficiently()) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ class RowComparator implements Comparator<Map<String,Object>> {
+ private String primaryKeyName;
+
+ public RowComparator(String primaryKeyName) {
+ this.primaryKeyName = primaryKeyName;
+ }
+
+ public int compare(Map<String,Object> row1, Map<String,Object> row2) {
+ Comparable key1 = (Comparable)row1.get(primaryKeyName);
+ Comparable key2 = (Comparable)row2.get(primaryKeyName);
+ return key1.compareTo(key2);
+ }
+
+ public boolean equals(Object obj) {
+ if (!(obj instanceof RowComparator))
+ return false;
+ RowComparator rc = (RowComparator)obj;
+ if (rc.primaryKeyName == null)
+ return this.primaryKeyName == null;
+ return rc.primaryKeyName.equals(this.primaryKeyName);
+ }
+ }
+
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ private List<Map<String,Object>> combineRowLists(String primaryKeyName,
+ List<Map<String,Object>> list1, List<Map<String,Object>> list2,
+ CompoundPredicate.Operator operator) {
+ ArrayList<Map<String,Object>> combinedRowList = new ArrayList<Map<String,Object>>();
+ RowComparator rc = new RowComparator(primaryKeyName);
+ Collections.sort(list1, rc);
+ Collections.sort(list2,rc);
+
+ Iterator<Map<String,Object>> iterator1 = list1.iterator();
+ Iterator<Map<String,Object>> iterator2 = list2.iterator();
+ boolean update1 = true;
+ boolean update2 = true;
+ Map<String,Object> row1 = null;
+ Map<String,Object> row2 = null;
+ Comparable<?> key1 = null;
+ Comparable<?> key2 = null;
+
+ while (true) {
+ if (update1) {
+ if (iterator1.hasNext()) {
+ row1 = iterator1.next();
+ key1 = (Comparable<?>)row1.get(primaryKeyName);
+ } else {
+ row1 = null;
+ }
+ }
+ if (update2) {
+ if (iterator2.hasNext()) {
+ row2 = iterator1.next();
+ key2 = (Comparable<?>)row2.get(primaryKeyName);
+ } else {
+ row2 = null;
+ }
+ }
+ if (operator == CompoundPredicate.Operator.AND) {
+ if ((row1 == null) || (row2 == null))
+ break;
+ if (key1.equals(key2))
+ combinedRowList.add(row1);
+ } else {
+ if (row1 == null) {
+ if (row2 == null)
+ break;
+ combinedRowList.add(row2);
+ } else if ((row2 == null) || (((Comparable)key1).compareTo(key2) <= 0)) {
+ combinedRowList.add(row2);
+ } else {
+ combinedRowList.add(row1);
+ }
+ }
+
+ update1 = (key2 == null) || (((Comparable)key1).compareTo(key2) <= 0);
+ update2 = (key1 == null) || (((Comparable)key2).compareTo(key1) <= 0);
+ }
+
+ return combinedRowList;
+ }
+
+ public List<Map<String,Object>> execute(String columnNames[]) {
+ List<Map<String,Object>> combinedRowList = null;
+ for (NoSqlPredicate predicate: predicateList) {
+ List<Map<String,Object>> rowList = predicate.execute(columnNames);
+ if (combinedRowList != null) {
+ combinedRowList = combineRowLists("id", combinedRowList, rowList, operator);
+ } else {
+ combinedRowList = rowList;
+ }
+ }
+ return combinedRowList;
+ }
+
+ public boolean matchesRow(Map<String,Object> row) {
+ if (operator == CompoundPredicate.Operator.AND) {
+ for (NoSqlPredicate predicate : predicateList) {
+ if (!predicate.matchesRow(row)) {
+ return false;
+ }
+ }
+ return true;
+ } else {
+ for (NoSqlPredicate predicate : predicateList) {
+ if (predicate.matchesRow(row)) {
+ return true;
+ }
+ }
+ return false;
+
+ }
+ }
+ }
+
+ public NoSqlStorageSource() {
+ super();
+ }
+
+ @Override
+ public void createTable(String tableName, Set<String> indexedColumns) {
+ super.createTable(tableName, indexedColumns);
+ if (indexedColumns == null) return;
+ for (String columnName : indexedColumns) {
+ setColumnIndexMode(tableName, columnName,
+ ColumnIndexMode.EQUALITY_INDEXED);
+ }
+ }
+
+ public void setTablePrimaryKeyName(String tableName, String primaryKeyName) {
+ if ((tableName == null) || (primaryKeyName == null))
+ throw new NullPointerException();
+ tablePrimaryKeyMap.put(tableName, primaryKeyName);
+ }
+
+ protected String getTablePrimaryKeyName(String tableName) {
+ String primaryKeyName = tablePrimaryKeyMap.get(tableName);
+ if (primaryKeyName == null)
+ primaryKeyName = DEFAULT_PRIMARY_KEY_NAME;
+ return primaryKeyName;
+ }
+
+ protected ColumnIndexMode getColumnIndexMode(String tableName, String columnName) {
+ ColumnIndexMode columnIndexMode = null;
+ Map<String, ColumnIndexMode> indexedColumnMap = tableIndexedColumnMap.get(tableName);
+ if (indexedColumnMap != null)
+ columnIndexMode = indexedColumnMap.get(columnName);
+ if (columnIndexMode == null)
+ return ColumnIndexMode.NOT_INDEXED;
+ return columnIndexMode;
+ }
+
+ public void setColumnIndexMode(String tableName, String columnName, ColumnIndexMode indexMode) {
+ Map<String, ColumnIndexMode> indexedColumnMap = tableIndexedColumnMap.get(tableName);
+ if (indexedColumnMap == null) {
+ indexedColumnMap = new HashMap<String,ColumnIndexMode>();
+ tableIndexedColumnMap.put(tableName, indexedColumnMap);
+ }
+ indexedColumnMap.put(columnName, indexMode);
+ }
+
+ Comparable<?> getOperatorPredicateValue(OperatorPredicate predicate, Map<String,Comparable<?>> parameterMap) {
+ Comparable<?> value = predicate.getValue();
+ if (value instanceof String) {
+ String stringValue = (String) value;
+ if ((stringValue.charAt(0) == '?') && (stringValue.charAt(stringValue.length()-1) == '?')) {
+ String parameterName = stringValue.substring(1,stringValue.length()-1);
+ value = parameterMap.get(parameterName);
+ }
+ }
+ return value;
+ }
+
+ NoSqlPredicate convertPredicate(IPredicate predicate, String tableName, Map<String,Comparable<?>> parameterMap) {
+ if (predicate == null)
+ return null;
+ NoSqlPredicate convertedPredicate = null;
+ if (predicate instanceof CompoundPredicate) {
+ CompoundPredicate compoundPredicate = (CompoundPredicate)predicate;
+ ArrayList<NoSqlPredicate> noSqlPredicateList = new ArrayList<NoSqlPredicate>();
+ for (IPredicate childPredicate: compoundPredicate.getPredicateList()) {
+ boolean incorporated = false;
+ if (childPredicate instanceof OperatorPredicate) {
+ OperatorPredicate childOperatorPredicate = (OperatorPredicate)childPredicate;
+ for (NoSqlPredicate childNoSqlPredicate: noSqlPredicateList) {
+ incorporated = childNoSqlPredicate.incorporateComparison(
+ childOperatorPredicate.getColumnName(), childOperatorPredicate.getOperator(),
+ getOperatorPredicateValue(childOperatorPredicate, parameterMap),
+ compoundPredicate.getOperator());
+ if (incorporated)
+ break;
+ }
+ }
+ if (!incorporated) {
+ NoSqlPredicate noSqlPredicate = convertPredicate(childPredicate, tableName, parameterMap);
+ noSqlPredicateList.add(noSqlPredicate);
+ }
+ }
+ convertedPredicate = new NoSqlCompoundPredicate(this, compoundPredicate.getOperator(),
+ compoundPredicate.isNegated(), noSqlPredicateList);
+ } else if (predicate instanceof OperatorPredicate) {
+ OperatorPredicate operatorPredicate = (OperatorPredicate) predicate;
+ Comparable<?> value = getOperatorPredicateValue(operatorPredicate, parameterMap);
+ switch (operatorPredicate.getOperator()) {
+ case EQ:
+ convertedPredicate = new NoSqlRangePredicate(this, tableName,
+ operatorPredicate.getColumnName(), value, true, value, true);
+ break;
+ case LT:
+ convertedPredicate = new NoSqlRangePredicate(this, tableName,
+ operatorPredicate.getColumnName(), null, false, value, false);
+ break;
+ case LTE:
+ convertedPredicate = new NoSqlRangePredicate(this, tableName,
+ operatorPredicate.getColumnName(), null, false, value, true);
+ break;
+ case GT:
+ convertedPredicate = new NoSqlRangePredicate(this, tableName,
+ operatorPredicate.getColumnName(), value, false, null, false);
+ break;
+ case GTE:
+ convertedPredicate = new NoSqlRangePredicate(this, tableName,
+ operatorPredicate.getColumnName(), value, true, null, false);
+ break;
+ default:
+ convertedPredicate = new NoSqlOperatorPredicate(this, operatorPredicate.getColumnName(),
+ operatorPredicate.getOperator(), value);
+ }
+ } else {
+ throw new StorageException("Unknown predicate type");
+ }
+
+ return convertedPredicate;
+ }
+
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ class RowComparator implements Comparator<Map<String,Object>> {
+ private RowOrdering rowOrdering;
+
+ public RowComparator(RowOrdering rowOrdering) {
+ this.rowOrdering = rowOrdering;
+ }
+
+ public int compare(Map<String,Object> row1, Map<String,Object> row2) {
+ if (rowOrdering == null)
+ return 0;
+
+ for (RowOrdering.Item item: rowOrdering.getItemList()) {
+ Comparable key1 = (Comparable)row1.get(item.getColumn());
+ Comparable key2 = (Comparable)row2.get(item.getColumn());
+ int result = key1.compareTo(key2);
+ if (result != 0) {
+ if (item.getDirection() == RowOrdering.Direction.DESCENDING)
+ result = -result;
+ return result;
+ }
+ }
+
+ return 0;
+ }
+
+ public boolean equals(Object obj) {
+ if (!(obj instanceof RowComparator))
+ return false;
+ RowComparator rc = (RowComparator)obj;
+ if (rc.rowOrdering == null)
+ return this.rowOrdering == null;
+ return rc.rowOrdering.equals(this.rowOrdering);
+ }
+ }
+
+ private NoSqlResultSet executeParameterizedQuery(String tableName, String[] columnNameList,
+ IPredicate predicate, RowOrdering rowOrdering, Map<String,Comparable<?>> parameterMap) {
+ NoSqlPredicate noSqlPredicate = convertPredicate(predicate, tableName, parameterMap);
+ List<Map<String,Object>> rowList;
+ if ((noSqlPredicate != null) && noSqlPredicate.canExecuteEfficiently()) {
+ rowList = noSqlPredicate.execute(columnNameList);
+ } else {
+ rowList = new ArrayList<Map<String,Object>>();
+ Collection<Map<String,Object>> allRowList = getAllRows(tableName, columnNameList);
+ for (Map<String,Object> row: allRowList) {
+ if ((noSqlPredicate == null) || noSqlPredicate.matchesRow(row)) {
+ rowList.add(row);
+ }
+ }
+ }
+ if (rowOrdering != null)
+ Collections.sort(rowList, new RowComparator(rowOrdering));
+
+ return new NoSqlResultSet(this, tableName, rowList);
+ }
+
+ @Override
+ public IQuery createQuery(String tableName, String[] columnNameList,
+ IPredicate predicate, RowOrdering rowOrdering) {
+ return new NoSqlQuery(tableName, columnNameList, predicate, rowOrdering);
+ }
+
+ @Override
+ public IResultSet executeQueryImpl(IQuery query) {
+ NoSqlQuery noSqlQuery = (NoSqlQuery) query;
+ return executeParameterizedQuery(noSqlQuery.getTableName(),
+ noSqlQuery.getColumnNameList(), noSqlQuery.getPredicate(),
+ noSqlQuery.getRowOrdering(), noSqlQuery.getParameterMap());
+ }
+
+ protected void sendNotification(String tableName, StorageSourceNotification.Action action,
+ List<Map<String,Object>> rows) {
+ Set<Object> rowKeys = new HashSet<Object>();
+ String primaryKeyName = getTablePrimaryKeyName(tableName);
+ for (Map<String,Object> row : rows) {
+ Object rowKey = row.get(primaryKeyName);
+ rowKeys.add(rowKey);
+ }
+ StorageSourceNotification notification =
+ new StorageSourceNotification(tableName, action, rowKeys);
+ notifyListeners(notification);
+ }
+
+ protected void sendNotification(String tableName,
+ StorageSourceNotification.Action action, Set<Object> rowKeys) {
+ StorageSourceNotification notification =
+ new StorageSourceNotification(tableName, action, rowKeys);
+ notifyListeners(notification);
+ }
+
+ protected void insertRowsAndNotify(String tableName, List<Map<String,Object>> insertRowList) {
+ insertRows(tableName, insertRowList);
+ sendNotification(tableName, StorageSourceNotification.Action.MODIFY, insertRowList);
+ }
+
+ @Override
+ public void insertRowImpl(String tableName, Map<String, Object> values) {
+ ArrayList<Map<String,Object>> rowList = new ArrayList<Map<String,Object>>();
+ rowList.add(values);
+ insertRowsAndNotify(tableName, rowList);
+ }
+
+ protected void updateRowsAndNotify(String tableName, Set<Object> rowKeys, Map<String,Object> updateRowList) {
+ updateRows(tableName, rowKeys, updateRowList);
+ sendNotification(tableName, StorageSourceNotification.Action.MODIFY, rowKeys);
+ }
+
+ protected void updateRowsAndNotify(String tableName, List<Map<String,Object>> updateRowList) {
+ updateRows(tableName, updateRowList);
+ sendNotification(tableName, StorageSourceNotification.Action.MODIFY, updateRowList);
+ }
+
+ @Override
+ public void updateMatchingRowsImpl(String tableName, IPredicate predicate, Map<String,Object> values) {
+ String primaryKeyName = getTablePrimaryKeyName(tableName);
+ String[] columnNameList = {primaryKeyName};
+ IResultSet resultSet = executeQuery(tableName, columnNameList, predicate, null);
+ Set<Object> rowKeys = new HashSet<Object>();
+ while (resultSet.next()) {
+ String rowKey = resultSet.getString(primaryKeyName);
+ rowKeys.add(rowKey);
+ }
+ updateRowsAndNotify(tableName, rowKeys, values);
+ }
+
+ @Override
+ public void updateRowImpl(String tableName, Object rowKey, Map<String,Object> values) {
+ Map<String,Object> valuesWithKey = new HashMap<String,Object>(values);
+ String primaryKeyName = getTablePrimaryKeyName(tableName);
+ valuesWithKey.put(primaryKeyName, rowKey);
+ List<Map<String,Object>> rowList = new ArrayList<Map<String,Object>>();
+ rowList.add(valuesWithKey);
+ updateRowsAndNotify(tableName, rowList);
+ }
+
+ @Override
+ public void updateRowImpl(String tableName, Map<String,Object> values) {
+ List<Map<String,Object>> rowKeys = new ArrayList<Map<String,Object>>();
+ rowKeys.add(values);
+ updateRowsAndNotify(tableName, rowKeys);
+ }
+
+ protected void deleteRowsAndNotify(String tableName, Set<Object> rowKeyList) {
+ deleteRows(tableName, rowKeyList);
+ sendNotification(tableName, StorageSourceNotification.Action.DELETE, rowKeyList);
+ }
+
+ @Override
+ public void deleteRowImpl(String tableName, Object key) {
+ HashSet<Object> keys = new HashSet<Object>();
+ keys.add(key);
+ deleteRowsAndNotify(tableName, keys);
+ }
+
+ @Override
+ public IResultSet getRowImpl(String tableName, Object rowKey) {
+ List<Map<String,Object>> rowList = new ArrayList<Map<String,Object>>();
+ Map<String,Object> row = getRow(tableName, null, rowKey);
+ if (row != null)
+ rowList.add(row);
+ NoSqlResultSet resultSet = new NoSqlResultSet(this, tableName, rowList);
+ return resultSet;
+ }
+
+ // Below are the methods that must be implemented by the subclasses
+
+ protected abstract Collection<Map<String,Object>> getAllRows(String tableName, String[] columnNameList);
+
+ protected abstract Map<String,Object> getRow(String tableName, String[] columnNameList, Object rowKey);
+
+ protected abstract List<Map<String,Object>> executeEqualityQuery(String tableName,
+ String[] columnNameList, String predicateColumnName, Comparable<?> value);
+
+ protected abstract List<Map<String,Object>> executeRangeQuery(String tableName,
+ String[] columnNameList, String predicateColumnName,
+ Comparable<?> startValue, boolean startInclusive, Comparable<?> endValue, boolean endInclusive);
+
+ protected abstract void insertRows(String tableName, List<Map<String,Object>> insertRowList);
+
+ protected abstract void updateRows(String tableName, Set<Object> rowKeys, Map<String,Object> updateColumnMap);
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/web/StorageNotifyResource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/web/StorageNotifyResource.java
new file mode 100644
index 0000000..fcfa96f
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/web/StorageNotifyResource.java
@@ -0,0 +1,55 @@
+/**
+* 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.storage.web;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import net.floodlightcontroller.storage.IStorageSourceService;
+import net.floodlightcontroller.storage.StorageSourceNotification;
+
+import org.codehaus.jackson.map.ObjectMapper;
+import org.codehaus.jackson.type.TypeReference;
+import org.restlet.resource.Post;
+import org.restlet.resource.ServerResource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class StorageNotifyResource extends ServerResource {
+ protected static Logger log = LoggerFactory.getLogger(StorageNotifyResource.class);
+
+ @Post("json")
+ public Map<String,Object> notify(String entity) throws Exception {
+ List<StorageSourceNotification> notifications = null;
+ ObjectMapper mapper = new ObjectMapper();
+ notifications =
+ mapper.readValue(entity,
+ new TypeReference<List<StorageSourceNotification>>(){});
+
+ IStorageSourceService storageSource =
+ (IStorageSourceService)getContext().getAttributes().
+ get(IStorageSourceService.class.getCanonicalName());
+ storageSource.notifyListeners(notifications);
+
+ HashMap<String, Object> model = new HashMap<String,Object>();
+ model.put("output", "OK");
+ return model;
+ }
+
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/web/StorageWebRoutable.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/web/StorageWebRoutable.java
new file mode 100644
index 0000000..681847d
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/storage/web/StorageWebRoutable.java
@@ -0,0 +1,45 @@
+/**
+* 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.storage.web;
+
+import org.restlet.Context;
+import org.restlet.Restlet;
+import org.restlet.routing.Router;
+
+import net.floodlightcontroller.restserver.RestletRoutable;
+
+/**
+ * Creates a router to handle the storage web URIs
+ * @author readams
+ *
+ */
+public class StorageWebRoutable implements RestletRoutable {
+
+ @Override
+ public String basePath() {
+ return "/wm/storage";
+ }
+
+ @Override
+ public Restlet getRestlet(Context context) {
+ Router router = new Router(context);
+ router.attach("/notify/json", StorageNotifyResource.class);
+ return router;
+ }
+
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/threadpool/IThreadPoolService.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/threadpool/IThreadPoolService.java
new file mode 100644
index 0000000..a537a3a
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/threadpool/IThreadPoolService.java
@@ -0,0 +1,15 @@
+package net.floodlightcontroller.threadpool;
+
+import java.util.concurrent.ScheduledExecutorService;
+
+import net.floodlightcontroller.core.module.IFloodlightService;
+
+public interface IThreadPoolService extends IFloodlightService {
+ /**
+ * Get the master scheduled thread pool executor maintained by the
+ * ThreadPool provider. This can be used by other modules as a centralized
+ * way to schedule tasks.
+ * @return
+ */
+ public ScheduledExecutorService getScheduledExecutor();
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/threadpool/ThreadPool.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/threadpool/ThreadPool.java
new file mode 100644
index 0000000..aa426a7
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/threadpool/ThreadPool.java
@@ -0,0 +1,64 @@
+package net.floodlightcontroller.threadpool;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+
+import net.floodlightcontroller.core.module.FloodlightModuleContext;
+import net.floodlightcontroller.core.module.FloodlightModuleException;
+import net.floodlightcontroller.core.module.IFloodlightModule;
+import net.floodlightcontroller.core.module.IFloodlightService;
+
+public class ThreadPool implements IThreadPoolService, IFloodlightModule {
+ protected ScheduledExecutorService executor = null;
+
+ // IThreadPoolService
+
+ @Override
+ public ScheduledExecutorService getScheduledExecutor() {
+ return executor;
+ }
+
+ // IFloodlightModule
+
+ @Override
+ public Collection<Class<? extends IFloodlightService>> getModuleServices() {
+ Collection<Class<? extends IFloodlightService>> l =
+ new ArrayList<Class<? extends IFloodlightService>>();
+ l.add(IThreadPoolService.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(IThreadPoolService.class, this);
+ // We are the class that implements the service
+ return m;
+ }
+
+ @Override
+ public Collection<Class<? extends IFloodlightService>>
+ getModuleDependencies() {
+ // No dependencies
+ return null;
+ }
+
+ @Override
+ public void init(FloodlightModuleContext context)
+ throws FloodlightModuleException {
+ executor = Executors.newScheduledThreadPool(15);
+ }
+
+ @Override
+ public void startUp(FloodlightModuleContext context) {
+ // no-op
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/Cluster.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/Cluster.java
new file mode 100644
index 0000000..606b079
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/Cluster.java
@@ -0,0 +1,79 @@
+package net.floodlightcontroller.topology;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import net.floodlightcontroller.routing.Link;
+
+import org.openflow.util.HexString;
+
+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.
+
+ public Cluster() {
+ id = Long.MAX_VALUE;
+ links = new HashMap<Long, Set<Link>>();
+ }
+
+ public long getId() {
+ return id;
+ }
+
+ public void setId(long id) {
+ this.id = id;
+ }
+
+ public Map<Long, Set<Link>> getLinks() {
+ return links;
+ }
+
+ public Set<Long> getNodes() {
+ return links.keySet();
+ }
+
+ void add(long n) {
+ if (links.containsKey(n) == false) {
+ links.put(n, new HashSet<Link>());
+ if (n < id) 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();
+ }
+ 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();
+ }
+ links.get(l.getDst()).add(l);
+ }
+
+ @Override
+ public int hashCode() {
+ return (int) (id + id >>>32);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+
+ Cluster other = (Cluster) obj;
+ return (this.id == other.id);
+ }
+
+ public String toString() {
+ return "[Cluster id=" + HexString.toHexString(id) + ", " + links.keySet() + "]";
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/ITopologyListener.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/ITopologyListener.java
new file mode 100644
index 0000000..9f06992
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/ITopologyListener.java
@@ -0,0 +1,8 @@
+package net.floodlightcontroller.topology;
+
+public interface ITopologyListener {
+ /**
+ * Happens when the switch clusters are recomputed
+ */
+ void topologyChanged();
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/ITopologyService.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/ITopologyService.java
new file mode 100644
index 0000000..cc62e82
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/ITopologyService.java
@@ -0,0 +1,198 @@
+package net.floodlightcontroller.topology;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Set;
+
+import net.floodlightcontroller.core.module.IFloodlightService;
+import net.floodlightcontroller.linkdiscovery.ILinkDiscovery.LDUpdate;
+
+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);
+
+ /**
+ * 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();
+
+ /**
+ * ITopologyListener provides topologyChanged notification,
+ * but not *what* the changes were.
+ * This method returns the delta in the linkUpdates between the current and the previous topology instance.
+ * @return
+ */
+ public List<LDUpdate> getLastLinkUpdates();
+
+ /**
+ * Switch methods
+ */
+ public Set<Short> getPorts(long sw);
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/NodePair.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/NodePair.java
new file mode 100644
index 0000000..ff954a0
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/NodePair.java
@@ -0,0 +1,53 @@
+package net.floodlightcontroller.topology;
+
+public class NodePair {
+ private long min;
+ private long max;
+
+ public NodePair(long a, long b) {
+ if (a < b) {
+ min = a;
+ max = b;
+ } else {
+ min = b;
+ max = a;
+ }
+ }
+
+ public long getNode() {
+ return min;
+ }
+
+ public long getOtherNode() {
+ return max;
+ }
+
+ public String toString() {
+ return "[" + new Long(min) + ", " + new Long(max) + "]";
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + (int) (max ^ (max >>> 32));
+ result = prime * result + (int) (min ^ (min >>> 32));
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ NodePair other = (NodePair) obj;
+ if (max != other.max)
+ return false;
+ if (min != other.min)
+ return false;
+ return true;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/NodePortTuple.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/NodePortTuple.java
new file mode 100644
index 0000000..4983529
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/NodePortTuple.java
@@ -0,0 +1,90 @@
+package net.floodlightcontroller.topology;
+
+import net.floodlightcontroller.core.web.serializers.DPIDSerializer;
+import net.floodlightcontroller.core.web.serializers.UShortSerializer;
+
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.codehaus.jackson.map.annotate.JsonSerialize;
+import org.openflow.util.HexString;
+
+/**
+ * A NodePortTuple is similar to a SwitchPortTuple
+ * but it only stores IDs instead of references
+ * to the actual objects.
+ * @author srini
+ */
+public class NodePortTuple {
+ protected long nodeId; // switch DPID
+ protected short 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) {
+ 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() {
+ return nodeId;
+ }
+ public void setNodeId(long nodeId) {
+ this.nodeId = nodeId;
+ }
+ @JsonProperty("port")
+ @JsonSerialize(using=UShortSerializer.class)
+ public short getPortId() {
+ return portId;
+ }
+ public void setPortId(short portId) {
+ this.portId = portId;
+ }
+
+ public String toString() {
+ return "[id=" + HexString.toHexString(nodeId) + ", port=" + new Short(portId) + "]";
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + (int) (nodeId ^ (nodeId >>> 32));
+ result = prime * result + portId;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ NodePortTuple other = (NodePortTuple) obj;
+ if (nodeId != other.nodeId)
+ return false;
+ if (portId != other.portId)
+ return false;
+ return true;
+ }
+
+ /**
+ * API to return a String value formed wtih NodeID and PortID
+ * The portID is a 16-bit field, so mask it as an integer to get full
+ * positive value
+ * @return
+ */
+ public String toKeyString() {
+ return (HexString.toHexString(nodeId)+ "|" + (portId & 0xffff));
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/OrderedNodePair.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/OrderedNodePair.java
new file mode 100644
index 0000000..af9e677
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/OrderedNodePair.java
@@ -0,0 +1,49 @@
+package net.floodlightcontroller.topology;
+
+public class OrderedNodePair {
+ private long src;
+ private long dst;
+
+ public OrderedNodePair(long s, long d) {
+ src = s;
+ dst = d;
+ }
+
+ public long getSrc() {
+ return src;
+ }
+
+ public long getDst() {
+ return dst;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 2417;
+ int result = 1;
+ result = prime * result + (int) (dst ^ (dst >>> 32));
+ result = prime * result + (int) (src ^ (src >>> 32));
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ OrderedNodePair other = (OrderedNodePair) obj;
+ if (dst != other.dst)
+ return false;
+ if (src != other.src)
+ return false;
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "OrderedNodePair [src=" + src + ", dst=" + dst + "]";
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/TopologyInstance.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/TopologyInstance.java
new file mode 100644
index 0000000..85ac6b8
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/TopologyInstance.java
@@ -0,0 +1,782 @@
+package net.floodlightcontroller.topology;
+
+import java.util.ArrayList;
+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.PriorityQueue;
+import java.util.Set;
+
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import net.floodlightcontroller.util.ClusterDFS;
+import net.floodlightcontroller.core.annotations.LogMessageCategory;
+import net.floodlightcontroller.core.annotations.LogMessageDoc;
+import net.floodlightcontroller.routing.BroadcastTree;
+import net.floodlightcontroller.routing.Link;
+import net.floodlightcontroller.routing.Route;
+import net.floodlightcontroller.routing.RouteId;
+import net.floodlightcontroller.util.LRUHashMap;
+
+/**
+ * A representation of a network topology. Used internally by
+ * {@link TopologyManager}
+ */
+@LogMessageCategory("Network Topology")
+public class TopologyInstance {
+
+ public static final short LT_SH_LINK = 1;
+ public static final short LT_BD_LINK = 2;
+ public static final short LT_TUNNEL = 3;
+
+ public static final int MAX_LINK_WEIGHT = 10000;
+ public static final int MAX_PATH_WEIGHT = Integer.MAX_VALUE - MAX_LINK_WEIGHT - 1;
+ public static final int PATH_CACHE_SIZE = 1000;
+
+ protected static Logger log = LoggerFactory.getLogger(TopologyInstance.class);
+
+ protected Map<Long, Set<Short>> 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.
+ */
+ protected Set<NodePortTuple> blockedPorts;
+ protected Map<NodePortTuple, Set<Link>> switchPortLinks; // Set of links organized by node port tuple
+ /** Set of links that are blocked. */
+ protected Set<Link> blockedLinks;
+
+ protected Set<Long> 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
+
+ // States for routing
+ protected Map<Long, BroadcastTree> destinationRootedTrees;
+ protected Map<Long, Set<NodePortTuple>> clusterBroadcastNodePorts;
+ protected Map<Long, BroadcastTree> clusterBroadcastTrees;
+ protected LRUHashMap<RouteId, Route> pathcache;
+
+ public TopologyInstance() {
+ this.switches = new HashSet<Long>();
+ this.switchPorts = new HashMap<Long, Set<Short>>();
+ this.switchPortLinks = new HashMap<NodePortTuple, Set<Link>>();
+ this.broadcastDomainPorts = new HashSet<NodePortTuple>();
+ this.tunnelPorts = new HashSet<NodePortTuple>();
+ this.blockedPorts = new HashSet<NodePortTuple>();
+ this.blockedLinks = new HashSet<Link>();
+ }
+
+ public TopologyInstance(Map<Long, Set<Short>> switchPorts,
+ Map<NodePortTuple, Set<Link>> switchPortLinks)
+ {
+ this.switches = new HashSet<Long>(switchPorts.keySet());
+ this.switchPorts = new HashMap<Long, Set<Short>>(switchPorts);
+ this.switchPortLinks = new HashMap<NodePortTuple,
+ Set<Link>>(switchPortLinks);
+ this.broadcastDomainPorts = new HashSet<NodePortTuple>();
+ this.tunnelPorts = new HashSet<NodePortTuple>();
+ this.blockedPorts = new HashSet<NodePortTuple>();
+ this.blockedLinks = new HashSet<Link>();
+
+ clusters = new HashSet<Cluster>();
+ switchClusterMap = new HashMap<Long, Cluster>();
+ }
+ public TopologyInstance(Map<Long, Set<Short>> 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.blockedPorts = new HashSet<NodePortTuple>(blockedPorts);
+ this.switchPortLinks = new HashMap<NodePortTuple, Set<Link>>();
+ for(NodePortTuple npt: switchPortLinks.keySet()) {
+ this.switchPortLinks.put(npt,
+ new HashSet<Link>(switchPortLinks.get(npt)));
+ }
+ this.broadcastDomainPorts = new HashSet<NodePortTuple>(broadcastDomainPorts);
+ this.tunnelPorts = new HashSet<NodePortTuple>(tunnelPorts);
+
+ 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>>();
+ pathcache = new LRUHashMap<RouteId, Route>(PATH_CACHE_SIZE);
+ }
+
+ public void compute() {
+
+ // Step 1: Compute clusters ignoring broadcast domain links
+ // Create nodes for clusters in the higher level topology
+ // Must ignore blocked links.
+ identifyOpenflowDomains();
+
+ // Step 0: Remove all links connected to blocked ports.
+ // removeLinksOnBlockedPorts();
+
+
+ // Step 1.1: Add links to clusters
+ // Avoid adding blocked links to clusters
+ addLinksToOpenflowDomains();
+
+ // Step 2. Compute shortest path trees in each cluster for
+ // unicast routing. The trees are rooted at the destination.
+ // Cost for tunnel links and direct links are the same.
+ calculateShortestPathTreeInClusters();
+
+ // Step 3. Compute broadcast tree in each cluster.
+ // Cost for tunnel links are high to discourage use of
+ // tunnel links. The cost is set to the number of nodes
+ // in the cluster + 1, to use as minimum number of
+ // clusters as possible.
+ calculateBroadcastNodePortsInClusters();
+
+ // Step 4. print topology.
+ // printTopology();
+ }
+
+ public void printTopology() {
+ log.trace("-----------------------------------------------");
+ log.trace("Links: {}",this.switchPortLinks);
+ log.trace("broadcastDomainPorts: {}", broadcastDomainPorts);
+ log.trace("tunnelPorts: {}", tunnelPorts);
+ log.trace("clusters: {}", clusters);
+ log.trace("destinationRootedTrees: {}", destinationRootedTrees);
+ log.trace("clusterBroadcastNodePorts: {}", clusterBroadcastNodePorts);
+ log.trace("-----------------------------------------------");
+ }
+
+ protected void addLinksToOpenflowDomains() {
+ for(long s: switches) {
+ if (switchPorts.get(s) == null) continue;
+ for (short p: switchPorts.get(s)) {
+ NodePortTuple np = new NodePortTuple(s, p);
+ if (switchPortLinks.get(np) == null) continue;
+ if (isBroadcastDomainPort(np)) continue;
+ for(Link l: switchPortLinks.get(np)) {
+ if (isBlockedLink(l)) continue;
+ if (isBroadcastDomainLink(l)) continue;
+ Cluster c1 = switchClusterMap.get(l.getSrc());
+ Cluster c2 = switchClusterMap.get(l.getDst());
+ if (c1 ==c2) {
+ c1.addLink(l);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * @author Srinivasan Ramasubramanian
+ *
+ * This function divides the network into clusters. Every cluster is
+ * a strongly connected component. The network may contain unidirectional
+ * links. The function calls dfsTraverse for performing depth first
+ * search and cluster formation.
+ *
+ * The computation of strongly connected components is based on
+ * Tarjan's algorithm. For more details, please see the Wikipedia
+ * link below.
+ *
+ * http://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm
+ */
+ @LogMessageDoc(level="ERROR",
+ message="No DFS object for switch {} found.",
+ 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>();
+
+ if (switches == null) return;
+
+ for (Long key: switches) {
+ ClusterDFS cdfs = new ClusterDFS();
+ dfsList.put(key, cdfs);
+ }
+ Set<Long> currSet = new HashSet<Long>();
+
+ for (Long sw: switches) {
+ ClusterDFS cdfs = dfsList.get(sw);
+ if (cdfs == null) {
+ log.error("No DFS object for switch {} found.", sw);
+ }else if (!cdfs.isVisited()) {
+ dfsTraverse(0, 1, sw, dfsList, currSet);
+ }
+ }
+ }
+
+
+ /**
+ * @author Srinivasan Ramasubramanian
+ *
+ * This algorithm computes the depth first search (DFS) traversal of the
+ * switches in the network, computes the lowpoint, and creates clusters
+ * (of strongly connected components).
+ *
+ * The computation of strongly connected components is based on
+ * Tarjan's algorithm. For more details, please see the Wikipedia
+ * link below.
+ *
+ * http://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm
+ *
+ * The initialization of lowpoint and the check condition for when a
+ * cluster should be formed is modified as we do not remove switches that
+ * are already part of a cluster.
+ *
+ * A return value of -1 indicates that dfsTraverse failed somewhere in the middle
+ * of computation. This could happen when a switch is removed during the cluster
+ * computation procedure.
+ *
+ * @param parentIndex: DFS index of the parent node
+ * @param currIndex: DFS index to be assigned to a newly visited node
+ * @param currSw: ID of the current switch
+ * @param dfsList: HashMap of DFS data structure for each switch
+ * @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) {
+
+ //Get the DFS object corresponding to the current switch
+ ClusterDFS currDFS = dfsList.get(currSw);
+ // Get all the links corresponding to this switch
+
+
+ //Assign the DFS object with right values.
+ currDFS.setVisited(true);
+ currDFS.setDfsIndex(currIndex);
+ currDFS.setParentDFSIndex(parentIndex);
+ currIndex++;
+
+ // Traverse the graph through every outgoing link.
+ if (switchPorts.get(currSw) != null){
+ for(Short 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();
+
+ // ignore incoming links.
+ if (dstSw == currSw) continue;
+
+ // ignore if the destination is already added to
+ // another cluster
+ if (switchClusterMap.get(dstSw) != null) continue;
+
+ // ignore the link if it is blocked.
+ if (isBlockedLink(l)) continue;
+
+ // ignore this link if it is in broadcast domain
+ if (isBroadcastDomainLink(l)) continue;
+
+ // Get the DFS object corresponding to the dstSw
+ ClusterDFS dstDFS = dfsList.get(dstSw);
+
+ if (dstDFS.getDfsIndex() < currDFS.getDfsIndex()) {
+ // could be a potential lowpoint
+ if (dstDFS.getDfsIndex() < currDFS.getLowpoint())
+ currDFS.setLowpoint(dstDFS.getDfsIndex());
+
+ } else if (!dstDFS.isVisited()) {
+ // make a DFS visit
+ currIndex = dfsTraverse(currDFS.getDfsIndex(), currIndex, dstSw,
+ dfsList, currSet);
+
+ if (currIndex < 0) return -1;
+
+ // update lowpoint after the visit
+ if (dstDFS.getLowpoint() < currDFS.getLowpoint())
+ currDFS.setLowpoint(dstDFS.getLowpoint());
+ }
+ // else, it is a node already visited with a higher
+ // dfs index, just ignore.
+ }
+ }
+ }
+
+ // Add current node to currSet.
+ currSet.add(currSw);
+
+ // Cluster computation.
+ // If the node's lowpoint is greater than its parent's DFS index,
+ // we need to form a new cluster with all the switches in the
+ // currSet.
+ if (currDFS.getLowpoint() > currDFS.getParentDFSIndex()) {
+ // The cluster thus far forms a strongly connected component.
+ // create a new switch cluster and the switches in the current
+ // set to the switch cluster.
+ Cluster sc = new Cluster();
+ for(long sw: currSet){
+ sc.add(sw);
+ switchClusterMap.put(sw, sc);
+ }
+ // delete all the nodes in the current set.
+ currSet.clear();
+ // add the newly formed switch clusters to the cluster set.
+ clusters.add(sc);
+ }
+
+ return currIndex;
+ }
+
+ /**
+ * Go through every link and identify it is a blocked link or not.
+ * If blocked, remove it from the switchport links and put them in the
+ * blocked link category.
+ *
+ * Note that we do not update the tunnel ports and broadcast domain
+ * port structures. We need those to still answer the question if the
+ * ports are tunnel or broadcast domain ports.
+ *
+ * If we add additional ports to blocked ports later on, we may simply
+ * call this method again to remove the links on the newly blocked ports.
+ */
+ protected void removeLinksOnBlockedPorts() {
+ Iterator<NodePortTuple> nptIter;
+ Iterator<Link> linkIter;
+
+ // Iterate through all the links and all the switch ports
+ // and move the links on blocked switch ports to blocked links
+ nptIter = this.switchPortLinks.keySet().iterator();
+ while (nptIter.hasNext()) {
+ NodePortTuple npt = nptIter.next();
+ linkIter = switchPortLinks.get(npt).iterator();
+ while (linkIter.hasNext()) {
+ Link link = linkIter.next();
+ if (isBlockedLink(link)) {
+ this.blockedLinks.add(link);
+ linkIter.remove();
+ }
+ }
+ // Note that at this point, the switchport may have
+ // no links in it. We could delete the switch port,
+ // but we will leave it as is.
+ }
+ }
+
+ public Set<NodePortTuple> getBlockedPorts() {
+ return this.blockedPorts;
+ }
+
+ protected Set<Link> getBlockedLinks() {
+ return this.blockedLinks;
+ }
+
+ /** Returns true if a link has either one of its switch ports
+ * blocked.
+ * @param l
+ * @return
+ */
+ protected boolean isBlockedLink(Link l) {
+ NodePortTuple n1 = new NodePortTuple(l.getSrc(), l.getSrcPort());
+ NodePortTuple n2 = new NodePortTuple(l.getDst(), l.getDstPort());
+ return (isBlockedPort(n1) || isBlockedPort(n2));
+ }
+
+ protected boolean isBlockedPort(NodePortTuple npt) {
+ return blockedPorts.contains(npt);
+ }
+
+ protected boolean isTunnelPort(NodePortTuple npt) {
+ return tunnelPorts.contains(npt);
+ }
+
+ protected boolean isTunnelLink(Link l) {
+ NodePortTuple n1 = new NodePortTuple(l.getSrc(), l.getSrcPort());
+ NodePortTuple n2 = new NodePortTuple(l.getDst(), l.getDstPort());
+ return (isTunnelPort(n1) || isTunnelPort(n2));
+ }
+
+ public boolean isBroadcastDomainLink(Link l) {
+ NodePortTuple n1 = new NodePortTuple(l.getSrc(), l.getSrcPort());
+ NodePortTuple n2 = new NodePortTuple(l.getDst(), l.getDstPort());
+ return (isBroadcastDomainPort(n1) || isBroadcastDomainPort(n2));
+ }
+
+ public boolean isBroadcastDomainPort(NodePortTuple npt) {
+ return broadcastDomainPorts.contains(npt);
+ }
+
+ class NodeDist implements Comparable<NodeDist> {
+ private Long node;
+ public Long getNode() {
+ return node;
+ }
+
+ private int dist;
+ public int getDist() {
+ return dist;
+ }
+
+ public NodeDist(Long node, int dist) {
+ this.node = node;
+ this.dist = dist;
+ }
+
+ public int compareTo(NodeDist o) {
+ if (o.dist == this.dist) {
+ return (int)(o.node - this.node);
+ }
+ return o.dist - this.dist;
+ }
+ }
+
+ protected BroadcastTree dijkstra(Cluster c, Long root,
+ Map<Link, Integer> linkCost,
+ boolean isDstRooted) {
+ HashMap<Long, Link> nexthoplinks = new HashMap<Long, Link>();
+ //HashMap<Long, Long> nexthopnodes = new HashMap<Long, Long>();
+ HashMap<Long, Integer> cost = new HashMap<Long, Integer>();
+ int w;
+
+ for (Long 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>();
+ 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();
+ 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;
+
+ if (isDstRooted == true) neighbor = link.getSrc();
+ else neighbor = link.getDst();
+
+ // links directed toward cnode will result in this condition
+ // if (neighbor == cnode) continue;
+
+ if (linkCost == null || linkCost.get(link)==null) w = 1;
+ else w = linkCost.get(link);
+
+ int ndist = cdist + w; // the weight of the link, always 1 in current version of floodlight.
+ if (ndist < cost.get(neighbor)) {
+ cost.put(neighbor, ndist);
+ nexthoplinks.put(neighbor, link);
+ //nexthopnodes.put(neighbor, cnode);
+ nodeq.add(new NodeDist(neighbor, ndist));
+ }
+ }
+ }
+
+ BroadcastTree ret = new BroadcastTree(nexthoplinks, cost);
+ return ret;
+ }
+
+ protected void calculateShortestPathTreeInClusters() {
+ pathcache.clear();
+ destinationRootedTrees.clear();
+
+ Map<Link, Integer> linkCost = new HashMap<Link, Integer>();
+ int tunnel_weight = switchPorts.size() + 1;
+
+ for(NodePortTuple npt: tunnelPorts) {
+ if (switchPortLinks.get(npt) == null) continue;
+ for(Link link: switchPortLinks.get(npt)) {
+ if (link == null) continue;
+ linkCost.put(link, tunnel_weight);
+ }
+ }
+
+ for(Cluster c: clusters) {
+ for (Long node : c.links.keySet()) {
+ BroadcastTree tree = dijkstra(c, node, linkCost, true);
+ destinationRootedTrees.put(node, tree);
+ }
+ }
+ }
+
+ protected void calculateBroadcastTreeInClusters() {
+ for(Cluster c: clusters) {
+ // c.id is the smallest node that's in the cluster
+ BroadcastTree tree = destinationRootedTrees.get(c.id);
+ clusterBroadcastTrees.put(c.id, tree);
+ }
+ }
+
+ protected void calculateBroadcastNodePortsInClusters() {
+
+ clusterBroadcastTrees.clear();
+
+ calculateBroadcastTreeInClusters();
+
+ for(Cluster c: clusters) {
+ // c.id is the smallest node that's in the cluster
+ BroadcastTree tree = clusterBroadcastTrees.get(c.id);
+ //log.info("Broadcast Tree {}", tree);
+
+ Set<NodePortTuple> nptSet = new HashSet<NodePortTuple>();
+ Map<Long, Link> links = tree.getLinks();
+ if (links == null) continue;
+ for(long nodeId: links.keySet()) {
+ Link l = links.get(nodeId);
+ if (l == null) continue;
+ NodePortTuple npt1 = new NodePortTuple(l.getSrc(), l.getSrcPort());
+ NodePortTuple npt2 = new NodePortTuple(l.getDst(), l.getDstPort());
+ nptSet.add(npt1);
+ nptSet.add(npt2);
+ }
+ clusterBroadcastNodePorts.put(c.id, nptSet);
+ }
+ }
+
+ protected Route buildroute(RouteId id, long srcId, long dstId) {
+ NodePortTuple npt;
+
+ 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();
+
+ if (!switches.contains(srcId) || !switches.contains(dstId)) {
+ // This is a switch that is not connected to any other switch
+ // hence there was no update for links (and hence it is not
+ // in the network)
+ log.debug("buildroute: Standalone switch: {}", srcId);
+
+ // 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)) {
+ while (srcId != dstId) {
+ Link l = nexthoplinks.get(srcId);
+
+ npt = new NodePortTuple(l.getSrc(), l.getSrcPort());
+ switchPorts.addLast(npt);
+ npt = new NodePortTuple(l.getDst(), l.getDstPort());
+ switchPorts.addLast(npt);
+ srcId = nexthoplinks.get(srcId).getDst();
+ }
+ }
+ // else, no path exists, and path equals null
+
+ Route result = null;
+ if (switchPorts != null && !switchPorts.isEmpty())
+ result = new Route(id, switchPorts);
+ if (log.isTraceEnabled()) {
+ log.trace("buildroute: {}", result);
+ }
+ return result;
+ }
+
+ protected int getCost(long srcId, long dstId) {
+ BroadcastTree bt = destinationRootedTrees.get(dstId);
+ if (bt == null) return -1;
+ return (bt.getCost(srcId));
+ }
+
+ /*
+ * Getter Functions
+ */
+
+ protected Set<Cluster> getClusters() {
+ return clusters;
+ }
+
+ // IRoutingEngineService interfaces
+ protected boolean routeExists(long srcId, long dstId) {
+ BroadcastTree bt = destinationRootedTrees.get(dstId);
+ if (bt == null) return false;
+ Link link = bt.getLinks().get(srcId);
+ if (link == null) return false;
+ return true;
+ }
+
+ protected Route getRoute(long srcId, short srcPort,
+ long dstId, short dstPort) {
+
+
+ // Return null the route source and desitnation are the
+ // same switchports.
+ if (srcId == dstId && srcPort == dstPort)
+ return null;
+
+ List<NodePortTuple> nptList;
+ NodePortTuple npt;
+ Route r = getRoute(srcId, dstId);
+ if (r == null && srcId != dstId) return null;
+
+ if (r != null) {
+ nptList= new ArrayList<NodePortTuple>(r.getPath());
+ } else {
+ nptList = new ArrayList<NodePortTuple>();
+ }
+ npt = new NodePortTuple(srcId, srcPort);
+ nptList.add(0, npt); // add src port to the front
+ npt = new NodePortTuple(dstId, dstPort);
+ nptList.add(npt); // add dst port to the end
+
+ RouteId id = new RouteId(srcId, dstId);
+ r = new Route(id, nptList);
+ return r;
+ }
+
+ protected Route getRoute(long srcId, long dstId) {
+ RouteId id = new RouteId(srcId, dstId);
+ Route result = null;
+ if (pathcache.containsKey(id)) {
+ result = pathcache.get(id);
+ } else {
+ result = buildroute(id, srcId, dstId);
+ pathcache.put(id, result);
+ }
+ if (log.isTraceEnabled()) {
+ log.trace("getRoute: {} -> {}", id, result);
+ }
+ return result;
+ }
+
+ protected BroadcastTree getBroadcastTreeForCluster(long clusterId){
+ Cluster c = switchClusterMap.get(clusterId);
+ if (c == null) return null;
+ return clusterBroadcastTrees.get(c.id);
+ }
+
+ //
+ // ITopologyService interface method helpers.
+ //
+
+ protected boolean isInternalToOpenflowDomain(long switchid, short port) {
+ return !isAttachmentPointPort(switchid, port);
+ }
+
+ public boolean isAttachmentPointPort(long switchid, short port) {
+ NodePortTuple npt = new NodePortTuple(switchid, port);
+ if (switchPortLinks.containsKey(npt)) return false;
+ return true;
+ }
+
+ protected long getOpenflowDomainId(long switchId) {
+ Cluster c = switchClusterMap.get(switchId);
+ if (c == null) return switchId;
+ return c.getId();
+ }
+
+ protected long getL2DomainId(long switchId) {
+ return getOpenflowDomainId(switchId);
+ }
+
+ protected Set<Long> getSwitchesInOpenflowDomain(long switchId) {
+ Cluster c = switchClusterMap.get(switchId);
+ if (c == null) return null;
+ return (c.getNodes());
+ }
+
+ protected boolean inSameOpenflowDomain(long switch1, long switch2) {
+ Cluster c1 = switchClusterMap.get(switch1);
+ Cluster c2 = switchClusterMap.get(switch2);
+ if (c1 != null && c2 != null)
+ return (c1.getId() == c2.getId());
+ return (switch1 == switch2);
+ }
+
+ public boolean isAllowed(long sw, short portId) {
+ return true;
+ }
+
+ protected boolean
+ isIncomingBroadcastAllowedOnSwitchPort(long sw, short portId) {
+ if (isInternalToOpenflowDomain(sw, portId)) {
+ long clusterId = getOpenflowDomainId(sw);
+ NodePortTuple npt = new NodePortTuple(sw, portId);
+ if (clusterBroadcastNodePorts.get(clusterId).contains(npt))
+ return true;
+ else return false;
+ }
+ return true;
+ }
+
+ public boolean isConsistent(long oldSw, short oldPort, long newSw,
+ short newPort) {
+ if (isInternalToOpenflowDomain(newSw, newPort)) return true;
+ return (oldSw == newSw && oldPort == newPort);
+ }
+
+ protected Set<NodePortTuple>
+ getBroadcastNodePortsInCluster(long sw) {
+ long clusterId = getOpenflowDomainId(sw);
+ return clusterBroadcastNodePorts.get(clusterId);
+ }
+
+ public boolean inSameBroadcastDomain(long s1, short p1, long s2, short p2) {
+ return false;
+ }
+
+ public boolean inSameL2Domain(long switch1, long switch2) {
+ return inSameOpenflowDomain(switch1, switch2);
+ }
+
+ public NodePortTuple getOutgoingSwitchPort(long src, short srcPort,
+ long dst, short 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) {
+ // Use this function to reinject traffic from a different port if needed.
+ return new NodePortTuple(src, srcPort);
+ }
+
+ public Set<Long> getSwitches() {
+ return switches;
+ }
+
+ public Set<Short> getPortsWithLinks(long 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);
+ for(NodePortTuple npt: clusterBroadcastNodePorts.get(clusterId)) {
+ if (npt.getNodeId() == targetSw) {
+ result.add(npt.getPortId());
+ }
+ }
+ return result;
+ }
+
+ public NodePortTuple
+ getAllowedOutgoingBroadcastPort(long src, short srcPort, long dst,
+ short dstPort) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public NodePortTuple
+ getAllowedIncomingBroadcastPort(long src, short srcPort) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/TopologyManager.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/TopologyManager.java
new file mode 100644
index 0000000..ba17483
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/TopologyManager.java
@@ -0,0 +1,1169 @@
+package net.floodlightcontroller.topology;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+import net.floodlightcontroller.core.FloodlightContext;
+import net.floodlightcontroller.core.IFloodlightProviderService;
+import net.floodlightcontroller.core.IFloodlightProviderService.Role;
+import net.floodlightcontroller.core.IOFMessageListener;
+import net.floodlightcontroller.core.IOFSwitch;
+import net.floodlightcontroller.core.IHAListener;
+import net.floodlightcontroller.core.annotations.LogMessageCategory;
+import net.floodlightcontroller.core.annotations.LogMessageDoc;
+import net.floodlightcontroller.core.module.FloodlightModuleContext;
+import net.floodlightcontroller.core.module.FloodlightModuleException;
+import net.floodlightcontroller.core.module.IFloodlightModule;
+import net.floodlightcontroller.core.module.IFloodlightService;
+import net.floodlightcontroller.core.util.SingletonTask;
+import net.floodlightcontroller.counter.ICounterStoreService;
+import net.floodlightcontroller.linkdiscovery.ILinkDiscoveryListener;
+import net.floodlightcontroller.linkdiscovery.ILinkDiscoveryService;
+import net.floodlightcontroller.packet.BSN;
+import net.floodlightcontroller.packet.Ethernet;
+import net.floodlightcontroller.packet.LLDP;
+import net.floodlightcontroller.restserver.IRestApiService;
+import net.floodlightcontroller.routing.IRoutingService;
+import net.floodlightcontroller.routing.Link;
+import net.floodlightcontroller.routing.Route;
+import net.floodlightcontroller.threadpool.IThreadPoolService;
+import net.floodlightcontroller.topology.web.TopologyWebRoutable;
+
+import org.openflow.protocol.OFMessage;
+import org.openflow.protocol.OFPacketIn;
+import org.openflow.protocol.OFPacketOut;
+import org.openflow.protocol.OFPort;
+import org.openflow.protocol.action.OFAction;
+import org.openflow.protocol.action.OFActionOutput;
+import org.openflow.protocol.OFType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Topology manager is responsible for maintaining the controller's notion
+ * of the network graph, as well as implementing tools for finding routes
+ * through the topology.
+ */
+@LogMessageCategory("Network Topology")
+public class TopologyManager implements
+ IFloodlightModule, ITopologyService,
+ IRoutingService, ILinkDiscoveryListener,
+ IOFMessageListener, IHAListener {
+
+ protected static Logger log = LoggerFactory.getLogger(TopologyManager.class);
+
+ public static final String CONTEXT_TUNNEL_ENABLED =
+ "com.bigswitch.floodlight.topologymanager.tunnelEnabled";
+
+ /**
+ * Set of ports for each switch
+ */
+ protected Map<Long, Set<Short>> switchPorts;
+
+ /**
+ * Set of links organized by node port tuple
+ */
+ protected Map<NodePortTuple, Set<Link>> switchPortLinks;
+
+ /**
+ * Set of direct links
+ */
+ protected Map<NodePortTuple, Set<Link>> directLinks;
+
+ /**
+ * set of links that are broadcast domain links.
+ */
+ protected Map<NodePortTuple, Set<Link>> portBroadcastDomainLinks;
+
+ /**
+ * set of tunnel links
+ */
+ protected Map<NodePortTuple, Set<Link>> tunnelLinks;
+
+ protected ILinkDiscoveryService linkDiscovery;
+ protected IThreadPoolService threadPool;
+ protected IFloodlightProviderService floodlightProvider;
+ protected IRestApiService restApi;
+
+ // Modules that listen to our updates
+ protected ArrayList<ITopologyListener> topologyAware;
+
+ protected BlockingQueue<LDUpdate> ldUpdates;
+ protected List<LDUpdate> appliedUpdates;
+
+ // These must be accessed using getCurrentInstance(), not directly
+ protected TopologyInstance currentInstance;
+ protected TopologyInstance currentInstanceWithoutTunnels;
+
+ protected SingletonTask newInstanceTask;
+ private Date lastUpdateTime;
+
+ /**
+ * Flag that indicates if links (direct/tunnel/multihop links) were
+ * updated as part of LDUpdate.
+ */
+ protected boolean linksUpdated;
+ /**
+ * Flag that indicates if direct or tunnel links were updated as
+ * part of LDUpdate.
+ */
+ protected boolean dtLinksUpdated;
+
+ /**
+ * Thread for recomputing topology. The thread is always running,
+ * however the function applyUpdates() has a blocking call.
+ */
+ @LogMessageDoc(level="ERROR",
+ message="Error in topology instance task thread",
+ explanation="An unknown error occured in the topology " +
+ "discovery module.",
+ recommendation=LogMessageDoc.CHECK_CONTROLLER)
+ protected class UpdateTopologyWorker implements Runnable {
+ @Override
+ public void run() {
+ try {
+ updateTopology();
+ }
+ catch (Exception e) {
+ log.error("Error in topology instance task thread", e);
+ }
+ }
+ }
+
+ public boolean updateTopology() {
+ boolean newInstanceFlag;
+ linksUpdated = false;
+ dtLinksUpdated = false;
+ applyUpdates();
+ newInstanceFlag = createNewInstance();
+ lastUpdateTime = new Date();
+ informListeners();
+ return newInstanceFlag;
+ }
+
+ // **********************
+ // ILinkDiscoveryListener
+ // **********************
+
+ @Override
+ public void linkDiscoveryUpdate(LDUpdate update) {
+ boolean scheduleFlag = false;
+ // if there's no udpates in the queue, then
+ // we need to schedule an update.
+ if (ldUpdates.peek() == null)
+ scheduleFlag = true;
+
+ if (log.isTraceEnabled()) {
+ log.trace("Queuing update: {}", update);
+ }
+ ldUpdates.add(update);
+
+ if (scheduleFlag) {
+ newInstanceTask.reschedule(1, TimeUnit.MICROSECONDS);
+ }
+ }
+
+ // ****************
+ // ITopologyService
+ // ****************
+
+ //
+ // ITopologyService interface methods
+ //
+ @Override
+ public Date getLastUpdateTime() {
+ return lastUpdateTime;
+ }
+
+ @Override
+ public void addListener(ITopologyListener listener) {
+ topologyAware.add(listener);
+ }
+
+ @Override
+ public boolean isAttachmentPointPort(long switchid, short port) {
+ return isAttachmentPointPort(switchid, port, true);
+ }
+
+ @Override
+ public boolean isAttachmentPointPort(long switchid, short port,
+ boolean tunnelEnabled) {
+ TopologyInstance ti = getCurrentInstance(tunnelEnabled);
+
+ // if the port is not attachment point port according to
+ // topology instance, then return false
+ if (ti.isAttachmentPointPort(switchid, port) == false)
+ return false;
+
+ // Check whether the port is a physical port. We should not learn
+ // attachment points on "special" ports.
+ if ((port & 0xff00) == 0xff00 && port != (short)0xfffe) return false;
+
+ // Make sure that the port is enabled.
+ IOFSwitch sw = floodlightProvider.getSwitches().get(switchid);
+ if (sw == null) return false;
+ return (sw.portEnabled(port));
+ }
+
+ public long getOpenflowDomainId(long switchId) {
+ return getOpenflowDomainId(switchId, true);
+ }
+
+ public long getOpenflowDomainId(long switchId, boolean tunnelEnabled) {
+ TopologyInstance ti = getCurrentInstance(tunnelEnabled);
+ return ti.getOpenflowDomainId(switchId);
+ }
+
+ @Override
+ public long getL2DomainId(long switchId) {
+ return getL2DomainId(switchId, true);
+ }
+
+ @Override
+ public long getL2DomainId(long switchId, boolean tunnelEnabled) {
+ TopologyInstance ti = getCurrentInstance(tunnelEnabled);
+ return ti.getL2DomainId(switchId);
+ }
+
+ @Override
+ public boolean inSameOpenflowDomain(long switch1, long switch2) {
+ return inSameOpenflowDomain(switch1, switch2, true);
+ }
+
+ @Override
+ public boolean inSameOpenflowDomain(long switch1, long switch2,
+ boolean tunnelEnabled) {
+ TopologyInstance ti = getCurrentInstance(tunnelEnabled);
+ return ti.inSameOpenflowDomain(switch1, switch2);
+ }
+
+ @Override
+ public boolean isAllowed(long sw, short portId) {
+ return isAllowed(sw, portId, true);
+ }
+
+ @Override
+ public boolean isAllowed(long sw, short portId, boolean tunnelEnabled) {
+ TopologyInstance ti = getCurrentInstance(tunnelEnabled);
+ return ti.isAllowed(sw, portId);
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ ////////////////////////////////////////////////////////////////////////
+ @Override
+ public boolean isIncomingBroadcastAllowed(long sw, short portId) {
+ return isIncomingBroadcastAllowed(sw, portId, true);
+ }
+
+ public boolean isIncomingBroadcastAllowed(long sw, short portId,
+ boolean tunnelEnabled) {
+ TopologyInstance ti = getCurrentInstance(tunnelEnabled);
+ return ti.isIncomingBroadcastAllowedOnSwitchPort(sw, portId);
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ ////////////////////////////////////////////////////////////////////////
+ /** Get all the ports connected to the switch */
+ @Override
+ public Set<Short> getPortsWithLinks(long sw) {
+ return getPortsWithLinks(sw, true);
+ }
+
+ /** Get all the ports connected to the switch */
+ @Override
+ public Set<Short> getPortsWithLinks(long sw, boolean tunnelEnabled) {
+ TopologyInstance ti = getCurrentInstance(tunnelEnabled);
+ return ti.getPortsWithLinks(sw);
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ ////////////////////////////////////////////////////////////////////////
+ /** Get all the ports on the target switch (targetSw) on which a
+ * broadcast packet must be sent from a host whose attachment point
+ * is on switch port (src, srcPort).
+ */
+ public Set<Short> getBroadcastPorts(long targetSw,
+ long src, short srcPort) {
+ return getBroadcastPorts(targetSw, src, srcPort, true);
+ }
+
+ /** Get all the ports on the target switch (targetSw) on which a
+ * broadcast packet must be sent from a host whose attachment point
+ * is on switch port (src, srcPort).
+ */
+ public Set<Short> getBroadcastPorts(long targetSw,
+ long src, short srcPort,
+ boolean tunnelEnabled) {
+ TopologyInstance ti = getCurrentInstance(tunnelEnabled);
+ return ti.getBroadcastPorts(targetSw, src, srcPort);
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ ////////////////////////////////////////////////////////////////////////
+ @Override
+ public NodePortTuple getOutgoingSwitchPort(long src, short srcPort,
+ long dst, short dstPort) {
+ // Use this function to redirect traffic if needed.
+ return getOutgoingSwitchPort(src, srcPort, dst, dstPort, true);
+ }
+
+ @Override
+ public NodePortTuple getOutgoingSwitchPort(long src, short srcPort,
+ long dst, short dstPort,
+ boolean tunnelEnabled) {
+ // Use this function to redirect traffic if needed.
+ TopologyInstance ti = getCurrentInstance(tunnelEnabled);
+ return ti.getOutgoingSwitchPort(src, srcPort,
+ dst, dstPort);
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ ////////////////////////////////////////////////////////////////////////
+ @Override
+ public NodePortTuple getIncomingSwitchPort(long src, short srcPort,
+ long dst, short dstPort) {
+ return getIncomingSwitchPort(src, srcPort, dst, dstPort, true);
+ }
+
+ @Override
+ public NodePortTuple getIncomingSwitchPort(long src, short srcPort,
+ long dst, short dstPort,
+ boolean tunnelEnabled) {
+ TopologyInstance ti = getCurrentInstance(tunnelEnabled);
+ return ti.getIncomingSwitchPort(src, srcPort,
+ dst, dstPort);
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ ////////////////////////////////////////////////////////////////////////
+ /**
+ * Checks if the two switchports belong to the same broadcast domain.
+ */
+ @Override
+ public boolean isInSameBroadcastDomain(long s1, short p1, long s2,
+ short p2) {
+ return isInSameBroadcastDomain(s1, p1, s2, p2, true);
+
+ }
+
+ @Override
+ public boolean isInSameBroadcastDomain(long s1, short p1,
+ long s2, short p2,
+ boolean tunnelEnabled) {
+ TopologyInstance ti = getCurrentInstance(tunnelEnabled);
+ return ti.inSameBroadcastDomain(s1, p1, s2, p2);
+
+ }
+
+
+ /**
+ * Checks if the switchport is a broadcast domain port or not.
+ */
+ @Override
+ public boolean isBroadcastDomainPort(long sw, short port) {
+ return isBroadcastDomainPort(sw, port, true);
+ }
+
+ @Override
+ public boolean isBroadcastDomainPort(long sw, short port,
+ boolean tunnelEnabled) {
+ TopologyInstance ti = getCurrentInstance(tunnelEnabled);
+ return ti.isBroadcastDomainPort(new NodePortTuple(sw, port));
+ }
+
+
+ /**
+ * Checks if the new attachment point port is consistent with the
+ * old attachment point port.
+ */
+ @Override
+ public boolean isConsistent(long oldSw, short oldPort,
+ long newSw, short newPort) {
+ return isConsistent(oldSw, oldPort,
+ newSw, newPort, true);
+ }
+
+ @Override
+ public boolean isConsistent(long oldSw, short oldPort,
+ long newSw, short newPort,
+ boolean tunnelEnabled) {
+ TopologyInstance ti = getCurrentInstance(tunnelEnabled);
+ return ti.isConsistent(oldSw, oldPort, newSw, newPort);
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ ////////////////////////////////////////////////////////////////////////
+ /**
+ * Checks if the two switches are in the same Layer 2 domain.
+ */
+ @Override
+ public boolean inSameL2Domain(long switch1, long switch2) {
+ return inSameL2Domain(switch1, switch2, true);
+ }
+
+ @Override
+ public boolean inSameL2Domain(long switch1, long switch2,
+ boolean tunnelEnabled) {
+ TopologyInstance ti = getCurrentInstance(tunnelEnabled);
+ return ti.inSameL2Domain(switch1, switch2);
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ ////////////////////////////////////////////////////////////////////////
+ @Override
+ public NodePortTuple getAllowedOutgoingBroadcastPort(long src,
+ short srcPort,
+ long dst,
+ short dstPort) {
+ return getAllowedOutgoingBroadcastPort(src, srcPort,
+ dst, dstPort, true);
+ }
+
+ @Override
+ public NodePortTuple getAllowedOutgoingBroadcastPort(long src,
+ short srcPort,
+ long dst,
+ short dstPort,
+ boolean tunnelEnabled){
+ TopologyInstance ti = getCurrentInstance(tunnelEnabled);
+ return ti.getAllowedOutgoingBroadcastPort(src, srcPort,
+ dst, dstPort);
+ }
+ ////////////////////////////////////////////////////////////////////////
+ ////////////////////////////////////////////////////////////////////////
+ @Override
+ public NodePortTuple
+ getAllowedIncomingBroadcastPort(long src, short srcPort) {
+ return getAllowedIncomingBroadcastPort(src,srcPort, true);
+ }
+
+ @Override
+ public NodePortTuple
+ getAllowedIncomingBroadcastPort(long src, short srcPort,
+ boolean tunnelEnabled) {
+ TopologyInstance ti = getCurrentInstance(tunnelEnabled);
+ return ti.getAllowedIncomingBroadcastPort(src,srcPort);
+ }
+
+ @Override
+ public Set<NodePortTuple> getBroadcastDomainPorts() {
+ return portBroadcastDomainLinks.keySet();
+ }
+
+ @Override
+ public Set<NodePortTuple> getTunnelPorts() {
+ return tunnelLinks.keySet();
+ }
+
+ @Override
+ public Set<NodePortTuple> getBlockedPorts() {
+ Set<NodePortTuple> bp;
+ Set<NodePortTuple> blockedPorts =
+ new HashSet<NodePortTuple>();
+
+ // As we might have two topologies, simply get the union of
+ // both of them and send it.
+ bp = getCurrentInstance(true).getBlockedPorts();
+ if (bp != null)
+ blockedPorts.addAll(bp);
+
+ bp = getCurrentInstance(false).getBlockedPorts();
+ if (bp != null)
+ blockedPorts.addAll(bp);
+
+ return blockedPorts;
+ }
+
+ @Override
+ public List<LDUpdate> getLastLinkUpdates() {
+ return appliedUpdates;
+ }
+ ////////////////////////////////////////////////////////////////////////
+ ////////////////////////////////////////////////////////////////////////
+
+ // ***************
+ // IRoutingService
+ // ***************
+
+ @Override
+ public Route getRoute(long src, long dst) {
+ return getRoute(src, dst, true);
+ }
+
+ @Override
+ public Route getRoute(long src, long dst, boolean tunnelEnabled) {
+ TopologyInstance ti = getCurrentInstance(tunnelEnabled);
+ return ti.getRoute(src, dst);
+ }
+
+ @Override
+ public Route getRoute(long src, short srcPort, long dst, short dstPort) {
+ return getRoute(src, srcPort, dst, dstPort, true);
+ }
+
+ @Override
+ public Route getRoute(long src, short srcPort, long dst, short dstPort,
+ boolean tunnelEnabled) {
+ TopologyInstance ti = getCurrentInstance(tunnelEnabled);
+ return ti.getRoute(src, srcPort, dst, dstPort);
+ }
+
+ @Override
+ public boolean routeExists(long src, long dst) {
+ return routeExists(src, dst, true);
+ }
+
+ @Override
+ public boolean routeExists(long src, long dst, boolean tunnelEnabled) {
+ TopologyInstance ti = getCurrentInstance(tunnelEnabled);
+ return ti.routeExists(src, dst);
+ }
+
+
+ // ******************
+ // IOFMessageListener
+ // ******************
+
+ @Override
+ public String getName() {
+ return "topology";
+ }
+
+ @Override
+ public boolean isCallbackOrderingPrereq(OFType type, String name) {
+ return "linkdiscovery".equals(name);
+ }
+
+ @Override
+ public boolean isCallbackOrderingPostreq(OFType type, String name) {
+ return false;
+ }
+
+ @Override
+ public Command receive(IOFSwitch sw, OFMessage msg,
+ FloodlightContext cntx) {
+ switch (msg.getType()) {
+ case PACKET_IN:
+ return this.processPacketInMessage(sw,
+ (OFPacketIn) msg, cntx);
+ default:
+ break;
+ }
+
+ return Command.CONTINUE;
+ }
+
+ // ***************
+ // IHAListener
+ // ***************
+
+ @Override
+ public void roleChanged(Role oldRole, Role newRole) {
+ switch(newRole) {
+ case MASTER:
+ if (oldRole == Role.SLAVE) {
+ log.debug("Re-computing topology due " +
+ "to HA change from SLAVE->MASTER");
+ newInstanceTask.reschedule(1, TimeUnit.MILLISECONDS);
+ }
+ break;
+ case SLAVE:
+ log.debug("Clearing topology due to " +
+ "HA change to SLAVE");
+ clearCurrentTopology();
+ break;
+ default:
+ break;
+ }
+ }
+
+ @Override
+ public void controllerNodeIPsChanged(
+ Map<String, String> curControllerNodeIPs,
+ Map<String, String> addedControllerNodeIPs,
+ Map<String, String> removedControllerNodeIPs) {
+ // no-op
+ }
+
+ // *****************
+ // IFloodlightModule
+ // *****************
+
+ @Override
+ public Collection<Class<? extends IFloodlightService>> getModuleServices() {
+ Collection<Class<? extends IFloodlightService>> l =
+ new ArrayList<Class<? extends IFloodlightService>>();
+ l.add(ITopologyService.class);
+ l.add(IRoutingService.class);
+ return l;
+ }
+
+ @Override
+ public Map<Class<? extends IFloodlightService>, IFloodlightService>
+ getServiceImpls() {
+ Map<Class<? extends IFloodlightService>,
+ IFloodlightService> m =
+ new HashMap<Class<? extends IFloodlightService>,
+ IFloodlightService>();
+ // We are the class that implements the service
+ m.put(ITopologyService.class, this);
+ m.put(IRoutingService.class, this);
+ return m;
+ }
+
+ @Override
+ public Collection<Class<? extends IFloodlightService>>
+ getModuleDependencies() {
+ Collection<Class<? extends IFloodlightService>> l =
+ new ArrayList<Class<? extends IFloodlightService>>();
+ l.add(ILinkDiscoveryService.class);
+ l.add(IThreadPoolService.class);
+ l.add(IFloodlightProviderService.class);
+ l.add(ICounterStoreService.class);
+ l.add(IRestApiService.class);
+ return l;
+ }
+
+ @Override
+ public void init(FloodlightModuleContext context)
+ throws FloodlightModuleException {
+ linkDiscovery = context.getServiceImpl(ILinkDiscoveryService.class);
+ threadPool = context.getServiceImpl(IThreadPoolService.class);
+ floodlightProvider =
+ context.getServiceImpl(IFloodlightProviderService.class);
+ restApi = context.getServiceImpl(IRestApiService.class);
+
+ switchPorts = new HashMap<Long,Set<Short>>();
+ switchPortLinks = new HashMap<NodePortTuple, Set<Link>>();
+ directLinks = new HashMap<NodePortTuple, Set<Link>>();
+ portBroadcastDomainLinks = new HashMap<NodePortTuple, Set<Link>>();
+ tunnelLinks = new HashMap<NodePortTuple, Set<Link>>();
+ topologyAware = new ArrayList<ITopologyListener>();
+ ldUpdates = new LinkedBlockingQueue<LDUpdate>();
+ appliedUpdates = new ArrayList<LDUpdate>();
+ clearCurrentTopology();
+ }
+
+ @Override
+ public void startUp(FloodlightModuleContext context) {
+ ScheduledExecutorService ses = threadPool.getScheduledExecutor();
+ newInstanceTask = new SingletonTask(ses, new UpdateTopologyWorker());
+ linkDiscovery.addListener(this);
+ floodlightProvider.addOFMessageListener(OFType.PACKET_IN, this);
+ floodlightProvider.addHAListener(this);
+ addRestletRoutable();
+ }
+
+ protected void addRestletRoutable() {
+ restApi.addRestletRoutable(new TopologyWebRoutable());
+ }
+
+ // ****************
+ // Internal methods
+ // ****************
+ /**
+ * If the packet-in switch port is disabled for all data traffic, then
+ * the packet will be dropped. Otherwise, the packet will follow the
+ * normal processing chain.
+ * @param sw
+ * @param pi
+ * @param cntx
+ * @return
+ */
+ protected Command dropFilter(long sw, OFPacketIn pi,
+ FloodlightContext cntx) {
+ Command result = Command.CONTINUE;
+ short port = pi.getInPort();
+
+ // If the input port is not allowed for data traffic, drop everything.
+ // BDDP packets will not reach this stage.
+ if (isAllowed(sw, port) == false) {
+ if (log.isTraceEnabled()) {
+ log.trace("Ignoring packet because of topology " +
+ "restriction on switch={}, port={}", sw, port);
+ result = Command.STOP;
+ }
+ }
+
+ // if sufficient information is available, then drop broadcast
+ // packets here as well.
+ return result;
+ }
+
+ /**
+ * TODO This method must be moved to a layer below forwarding
+ * so that anyone can use it.
+ * @param packetData
+ * @param sw
+ * @param ports
+ * @param cntx
+ */
+ @LogMessageDoc(level="ERROR",
+ message="Failed to clear all flows on switch {switch}",
+ explanation="An I/O error occured while trying send " +
+ "topology discovery packet",
+ recommendation=LogMessageDoc.CHECK_SWITCH)
+ public void doMultiActionPacketOut(byte[] packetData, IOFSwitch sw,
+ Set<Short> ports,
+ FloodlightContext cntx) {
+
+ if (ports == null) return;
+ if (packetData == null || packetData.length <= 0) return;
+
+ OFPacketOut po =
+ (OFPacketOut) floodlightProvider.getOFMessageFactory().
+ getMessage(OFType.PACKET_OUT);
+
+ List<OFAction> actions = new ArrayList<OFAction>();
+ for(short p: ports) {
+ actions.add(new OFActionOutput(p, (short) 0));
+ }
+
+ // set actions
+ po.setActions(actions);
+ // set action length
+ po.setActionsLength((short) (OFActionOutput.MINIMUM_LENGTH *
+ ports.size()));
+ // set buffer-id to BUFFER_ID_NONE
+ po.setBufferId(OFPacketOut.BUFFER_ID_NONE);
+ // set in-port to OFPP_NONE
+ po.setInPort(OFPort.OFPP_NONE.getValue());
+
+ // set packet data
+ po.setPacketData(packetData);
+
+ // compute and set packet length.
+ short poLength = (short)(OFPacketOut.MINIMUM_LENGTH +
+ po.getActionsLength() +
+ packetData.length);
+
+ po.setLength(poLength);
+
+ try {
+ //counterStore.updatePktOutFMCounterStore(sw, po);
+ if (log.isTraceEnabled()) {
+ log.trace("write broadcast packet on switch-id={} " +
+ "interaces={} packet-data={} packet-out={}",
+ new Object[] {sw.getId(), ports, packetData, po});
+ }
+ sw.write(po, cntx);
+
+ } catch (IOException e) {
+ log.error("Failure writing packet out", e);
+ }
+ }
+
+
+ /**
+ * The BDDP packets are forwarded out of all the ports out of an
+ * openflowdomain. Get all the switches in the same openflow
+ * domain as the sw (disabling tunnels). Then get all the
+ * external switch ports and send these packets out.
+ * @param sw
+ * @param pi
+ * @param cntx
+ */
+ protected void doFloodBDDP(long pinSwitch, OFPacketIn pi,
+ FloodlightContext cntx) {
+
+ TopologyInstance ti = getCurrentInstance(false);
+
+ Set<Long> switches = ti.getSwitchesInOpenflowDomain(pinSwitch);
+
+ if (switches == null)
+ {
+ // indicates no links are connected to the switches
+ switches = new HashSet<Long>();
+ switches.add(pinSwitch);
+ }
+
+ for(long sid: switches) {
+ IOFSwitch sw = floodlightProvider.getSwitches().get(sid);
+ if (sw == null) continue;
+ Collection<Short> enabledPorts = sw.getEnabledPortNumbers();
+ if (enabledPorts == null)
+ continue;
+ Set<Short> ports = new HashSet<Short>();
+ ports.addAll(enabledPorts);
+
+ // all the ports known to topology // without tunnels.
+ // out of these, we need to choose only those that are
+ // broadcast port, otherwise, we should eliminate.
+ Set<Short> portsKnownToTopo = ti.getPortsWithLinks(sid);
+
+ if (portsKnownToTopo != null) {
+ for(short p: portsKnownToTopo) {
+ NodePortTuple npt =
+ new NodePortTuple(sid, p);
+ if (ti.isBroadcastDomainPort(npt) == false) {
+ ports.remove(p);
+ }
+ }
+ }
+
+ // remove the incoming switch port
+ if (pinSwitch == sid) {
+ ports.remove(pi.getInPort());
+ }
+
+ // we have all the switch ports to which we need to broadcast.
+ doMultiActionPacketOut(pi.getPacketData(), sw, ports, cntx);
+ }
+
+ }
+
+ protected Command processPacketInMessage(IOFSwitch sw, OFPacketIn pi,
+ FloodlightContext cntx) {
+
+ // get the packet-in switch.
+ Ethernet eth =
+ IFloodlightProviderService.bcStore.
+ get(cntx,IFloodlightProviderService.CONTEXT_PI_PAYLOAD);
+
+ if (eth.getEtherType() == Ethernet.TYPE_BSN) {
+ BSN bsn = (BSN) eth.getPayload();
+ if (bsn == null) return Command.STOP;
+ if (bsn.getPayload() == null) return Command.STOP;
+
+ // It could be a packet other than BSN LLDP, therefore
+ // continue with the regular processing.
+ if (bsn.getPayload() instanceof LLDP == false)
+ return Command.CONTINUE;
+
+ doFloodBDDP(sw.getId(), pi, cntx);
+ } else {
+ return dropFilter(sw.getId(), pi, cntx);
+ }
+ return Command.STOP;
+ }
+
+
+ /**
+ * Updates concerning switch disconnect and port down are not processed.
+ * LinkDiscoveryManager is expected to process those messages and send
+ * multiple link removed messages. However, all the updates from
+ * LinkDiscoveryManager would be propagated to the listeners of topology.
+ */
+ @LogMessageDoc(level="ERROR",
+ message="Error reading link discovery update.",
+ explanation="Unable to process link discovery update",
+ recommendation=LogMessageDoc.REPORT_CONTROLLER_BUG)
+ public void applyUpdates() {
+ appliedUpdates.clear();
+ LDUpdate update = null;
+ while (ldUpdates.peek() != null) {
+ try {
+ update = ldUpdates.take();
+ } catch (Exception e) {
+ log.error("Error reading link discovery update.", e);
+ }
+ if (log.isTraceEnabled()) {
+ log.trace("Applying update: {}", update);
+ }
+ if (update.getOperation() == UpdateOperation.LINK_UPDATED) {
+ addOrUpdateLink(update.getSrc(), update.getSrcPort(),
+ update.getDst(), update.getDstPort(),
+ update.getType());
+ } else if (update.getOperation() == UpdateOperation.LINK_REMOVED){
+ removeLink(update.getSrc(), update.getSrcPort(),
+ update.getDst(), update.getDstPort());
+ }
+ // Add to the list of applied updates.
+ appliedUpdates.add(update);
+ }
+ }
+
+ /**
+ * This function computes a new topology.
+ */
+ /**
+ * This function computes a new topology instance.
+ * It ignores links connected to all broadcast domain ports
+ * and tunnel ports. The method returns if a new instance of
+ * topology was created or not.
+ */
+ protected boolean createNewInstance() {
+ Set<NodePortTuple> blockedPorts = new HashSet<NodePortTuple>();
+
+ if (!linksUpdated) return false;
+
+ Map<NodePortTuple, Set<Link>> openflowLinks;
+ openflowLinks =
+ new HashMap<NodePortTuple, Set<Link>>(switchPortLinks);
+
+ // Remove all tunnel links.
+ for(NodePortTuple npt: tunnelLinks.keySet()) {
+ if (openflowLinks.get(npt) != null)
+ openflowLinks.remove(npt);
+ }
+
+ // Remove all broadcast domain links.
+ for(NodePortTuple npt: portBroadcastDomainLinks.keySet()) {
+ if (openflowLinks.get(npt) != null)
+ openflowLinks.remove(npt);
+ }
+
+ TopologyInstance nt = new TopologyInstance(switchPorts,
+ blockedPorts,
+ openflowLinks,
+ portBroadcastDomainLinks.keySet(),
+ tunnelLinks.keySet());
+ nt.compute();
+ // We set the instances with and without tunnels to be identical.
+ // If needed, we may compute them differently.
+ currentInstance = nt;
+ currentInstanceWithoutTunnels = nt;
+ return true;
+ }
+
+
+ public void informListeners() {
+ for(int i=0; i<topologyAware.size(); ++i) {
+ ITopologyListener listener = topologyAware.get(i);
+ listener.topologyChanged();
+ }
+ }
+
+ public void addSwitch(long sid) {
+ if (switchPorts.containsKey(sid) == false) {
+ switchPorts.put(sid, new HashSet<Short>());
+ }
+ }
+
+ private void addPortToSwitch(long s, short p) {
+ addSwitch(s);
+ switchPorts.get(s).add(p);
+ }
+
+ public boolean removeSwitchPort(long sw, short port) {
+
+ Set<Link> linksToRemove = new HashSet<Link>();
+ NodePortTuple npt = new NodePortTuple(sw, port);
+ if (switchPortLinks.containsKey(npt) == false) return false;
+
+ linksToRemove.addAll(switchPortLinks.get(npt));
+ for(Link link: linksToRemove) {
+ removeLink(link);
+ }
+ return true;
+ }
+
+ public boolean removeSwitch(long sid) {
+ // Delete all the links in the switch, switch and all
+ // associated data should be deleted.
+ if (switchPorts.containsKey(sid) == false) return false;
+
+ Set<Link> linksToRemove = new HashSet<Link>();
+ for(Short p: switchPorts.get(sid)) {
+ NodePortTuple n1 = new NodePortTuple(sid, p);
+ linksToRemove.addAll(switchPortLinks.get(n1));
+ }
+
+ if (linksToRemove.isEmpty()) return false;
+
+ for(Link link: linksToRemove) {
+ removeLink(link);
+ }
+ return true;
+ }
+
+ /**
+ * Add the given link to the data structure. Returns true if a link was
+ * added.
+ * @param s
+ * @param l
+ * @return
+ */
+ private boolean addLinkToStructure(Map<NodePortTuple,
+ Set<Link>> s, Link l) {
+ boolean result1 = false, result2 = false;
+
+ NodePortTuple n1 = new NodePortTuple(l.getSrc(), l.getSrcPort());
+ NodePortTuple n2 = new NodePortTuple(l.getDst(), l.getDstPort());
+
+ if (s.get(n1) == null) {
+ s.put(n1, new HashSet<Link>());
+ }
+ if (s.get(n2) == null) {
+ s.put(n2, new HashSet<Link>());
+ }
+ result1 = s.get(n1).add(l);
+ result2 = s.get(n2).add(l);
+
+ return (result1 || result2);
+ }
+
+ /**
+ * Delete the given link from the data strucure. Returns true if the
+ * link was deleted.
+ * @param s
+ * @param l
+ * @return
+ */
+ private boolean removeLinkFromStructure(Map<NodePortTuple,
+ Set<Link>> s, Link l) {
+
+ boolean result1 = false, result2 = false;
+ NodePortTuple n1 = new NodePortTuple(l.getSrc(), l.getSrcPort());
+ NodePortTuple n2 = new NodePortTuple(l.getDst(), l.getDstPort());
+
+ if (s.get(n1) != null) {
+ result1 = s.get(n1).remove(l);
+ if (s.get(n1).isEmpty()) s.remove(n1);
+ }
+ if (s.get(n2) != null) {
+ result2 = s.get(n2).remove(l);
+ if (s.get(n2).isEmpty()) s.remove(n2);
+ }
+ return result1 || result2;
+ }
+
+ public void addOrUpdateLink(long srcId, short srcPort, long dstId,
+ short dstPort, LinkType type) {
+ boolean flag1 = false, flag2 = false;
+
+ Link link = new Link(srcId, srcPort, dstId, dstPort);
+ addPortToSwitch(srcId, srcPort);
+ addPortToSwitch(dstId, dstPort);
+
+ addLinkToStructure(switchPortLinks, link);
+
+ if (type.equals(LinkType.MULTIHOP_LINK)) {
+ addLinkToStructure(portBroadcastDomainLinks, link);
+ flag1 = removeLinkFromStructure(tunnelLinks, link);
+ flag2 = removeLinkFromStructure(directLinks, link);
+ dtLinksUpdated = flag1 || flag2;
+ } else if (type.equals(LinkType.TUNNEL)) {
+ addLinkToStructure(tunnelLinks, link);
+ removeLinkFromStructure(portBroadcastDomainLinks, link);
+ removeLinkFromStructure(directLinks, link);
+ dtLinksUpdated = true;
+ } else if (type.equals(LinkType.DIRECT_LINK)) {
+ addLinkToStructure(directLinks, link);
+ removeLinkFromStructure(tunnelLinks, link);
+ removeLinkFromStructure(portBroadcastDomainLinks, link);
+ dtLinksUpdated = true;
+ }
+ linksUpdated = true;
+ }
+
+ public void removeLink(Link link) {
+ boolean flag1 = false, flag2 = false;
+
+ flag1 = removeLinkFromStructure(directLinks, link);
+ flag2 = removeLinkFromStructure(tunnelLinks, link);
+
+ linksUpdated = true;
+ dtLinksUpdated = flag1 || flag2;
+
+ removeLinkFromStructure(portBroadcastDomainLinks, link);
+ removeLinkFromStructure(switchPortLinks, link);
+
+ NodePortTuple srcNpt =
+ new NodePortTuple(link.getSrc(), link.getSrcPort());
+ NodePortTuple dstNpt =
+ new NodePortTuple(link.getDst(), link.getDstPort());
+
+ // Remove switch ports if there are no links through those switch ports
+ if (switchPortLinks.get(srcNpt) == null) {
+ if (switchPorts.get(srcNpt.getNodeId()) != null)
+ switchPorts.get(srcNpt.getNodeId()).remove(srcNpt.getPortId());
+ }
+ if (switchPortLinks.get(dstNpt) == null) {
+ if (switchPorts.get(dstNpt.getNodeId()) != null)
+ switchPorts.get(dstNpt.getNodeId()).remove(dstNpt.getPortId());
+ }
+
+ // Remove the node if no ports are present
+ if (switchPorts.get(srcNpt.getNodeId())!=null &&
+ switchPorts.get(srcNpt.getNodeId()).isEmpty()) {
+ switchPorts.remove(srcNpt.getNodeId());
+ }
+ if (switchPorts.get(dstNpt.getNodeId())!=null &&
+ switchPorts.get(dstNpt.getNodeId()).isEmpty()) {
+ switchPorts.remove(dstNpt.getNodeId());
+ }
+ }
+
+ public void removeLink(long srcId, short srcPort,
+ long dstId, short dstPort) {
+ Link link = new Link(srcId, srcPort, dstId, dstPort);
+ removeLink(link);
+ }
+
+ public void clear() {
+ switchPorts.clear();
+ switchPortLinks.clear();
+ portBroadcastDomainLinks.clear();
+ tunnelLinks.clear();
+ directLinks.clear();
+ appliedUpdates.clear();
+ }
+
+ /**
+ * Clears the current topology. Note that this does NOT
+ * send out updates.
+ */
+ public void clearCurrentTopology() {
+ this.clear();
+ linksUpdated = true;
+ dtLinksUpdated = true;
+ createNewInstance();
+ lastUpdateTime = new Date();
+ }
+
+ /**
+ * Getters. No Setters.
+ */
+ public Map<Long, Set<Short>> getSwitchPorts() {
+ return switchPorts;
+ }
+
+ public Map<NodePortTuple, Set<Link>> getSwitchPortLinks() {
+ return switchPortLinks;
+ }
+
+ public Map<NodePortTuple, Set<Link>> getPortBroadcastDomainLinks() {
+ return portBroadcastDomainLinks;
+ }
+
+ public TopologyInstance getCurrentInstance(boolean tunnelEnabled) {
+ if (tunnelEnabled)
+ return currentInstance;
+ else return this.currentInstanceWithoutTunnels;
+ }
+
+ public TopologyInstance getCurrentInstance() {
+ return this.getCurrentInstance(true);
+ }
+
+ /**
+ * Switch methods
+ */
+ public Set<Short> getPorts(long sw) {
+ Set<Short> ports = new HashSet<Short>();
+ IOFSwitch iofSwitch = floodlightProvider.getSwitches().get(sw);
+ if (iofSwitch == null) return null;
+
+ Collection<Short> ofpList = iofSwitch.getEnabledPortNumbers();
+ if (ofpList == null) return null;
+
+ Set<Short> qPorts = linkDiscovery.getQuarantinedPorts(sw);
+ if (qPorts != null)
+ ofpList.removeAll(qPorts);
+
+ ports.addAll(ofpList);
+ return ports;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/web/BlockedPortsResource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/web/BlockedPortsResource.java
new file mode 100644
index 0000000..dc4ac61
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/web/BlockedPortsResource.java
@@ -0,0 +1,20 @@
+package net.floodlightcontroller.topology.web;
+
+import java.util.Set;
+
+import net.floodlightcontroller.topology.ITopologyService;
+import net.floodlightcontroller.topology.NodePortTuple;
+
+import org.restlet.resource.Get;
+import org.restlet.resource.ServerResource;
+
+public class BlockedPortsResource extends ServerResource {
+ @Get("json")
+ public Set<NodePortTuple> retrieve() {
+ ITopologyService topology =
+ (ITopologyService)getContext().getAttributes().
+ get(ITopologyService.class.getCanonicalName());
+
+ return topology.getBlockedPorts();
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/web/BroadcastDomainPortsResource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/web/BroadcastDomainPortsResource.java
new file mode 100644
index 0000000..61b4338
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/web/BroadcastDomainPortsResource.java
@@ -0,0 +1,20 @@
+package net.floodlightcontroller.topology.web;
+
+import java.util.Set;
+
+import net.floodlightcontroller.topology.ITopologyService;
+import net.floodlightcontroller.topology.NodePortTuple;
+
+import org.restlet.resource.Get;
+import org.restlet.resource.ServerResource;
+
+public class BroadcastDomainPortsResource extends ServerResource {
+ @Get("json")
+ public Set<NodePortTuple> retrieve() {
+ ITopologyService topology =
+ (ITopologyService)getContext().getAttributes().
+ get(ITopologyService.class.getCanonicalName());
+
+ return topology.getBroadcastDomainPorts();
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/web/EnabledPortsResource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/web/EnabledPortsResource.java
new file mode 100644
index 0000000..aa75321
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/web/EnabledPortsResource.java
@@ -0,0 +1,42 @@
+package net.floodlightcontroller.topology.web;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+import net.floodlightcontroller.core.IFloodlightProviderService;
+import net.floodlightcontroller.topology.ITopologyService;
+import net.floodlightcontroller.topology.NodePortTuple;
+
+import org.restlet.resource.Get;
+import org.restlet.resource.ServerResource;
+
+public class EnabledPortsResource extends ServerResource {
+ @Get("json")
+ public List<NodePortTuple> retrieve() {
+ List<NodePortTuple> result = new ArrayList<NodePortTuple>();
+
+ IFloodlightProviderService floodlightProvider =
+ (IFloodlightProviderService)getContext().getAttributes().
+ get(IFloodlightProviderService.class.getCanonicalName());
+
+ ITopologyService topology=
+ (ITopologyService)getContext().getAttributes().
+ get(ITopologyService.class.getCanonicalName());
+
+ if (floodlightProvider == null || topology == null)
+ return result;
+
+ Set<Long> switches = floodlightProvider.getSwitches().keySet();
+ if (switches == null) return result;
+
+ for(long sw: switches) {
+ Set<Short> ports = topology.getPorts(sw);
+ if (ports == null) continue;
+ for(short p: ports) {
+ result.add(new NodePortTuple(sw, p));
+ }
+ }
+ return result;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/web/RouteResource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/web/RouteResource.java
new file mode 100644
index 0000000..70e406f
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/web/RouteResource.java
@@ -0,0 +1,47 @@
+package net.floodlightcontroller.topology.web;
+
+import java.util.List;
+
+import net.floodlightcontroller.routing.IRoutingService;
+import net.floodlightcontroller.routing.Route;
+import net.floodlightcontroller.topology.NodePortTuple;
+
+import org.openflow.util.HexString;
+import org.restlet.resource.Get;
+import org.restlet.resource.ServerResource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class RouteResource extends ServerResource {
+
+ protected static Logger log = LoggerFactory.getLogger(RouteResource.class);
+
+ @Get("json")
+ public List<NodePortTuple> retrieve() {
+ IRoutingService routing =
+ (IRoutingService)getContext().getAttributes().
+ get(IRoutingService.class.getCanonicalName());
+
+ String srcDpid = (String) getRequestAttributes().get("src-dpid");
+ String srcPort = (String) getRequestAttributes().get("src-port");
+ String dstDpid = (String) getRequestAttributes().get("dst-dpid");
+ String dstPort = (String) getRequestAttributes().get("dst-port");
+
+ 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);
+
+ Route result = routing.getRoute(longSrcDpid, shortSrcPort, longDstDpid, shortDstPort);
+
+ if (result!=null) {
+ return routing.getRoute(longSrcDpid, shortSrcPort, longDstDpid, shortDstPort).getPath();
+ }
+ else {
+ log.debug("ERROR! no route found");
+ return null;
+ }
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/web/SwitchClustersResource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/web/SwitchClustersResource.java
new file mode 100644
index 0000000..f52d27a
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/web/SwitchClustersResource.java
@@ -0,0 +1,72 @@
+/**
+* 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.topology.web;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+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.topology.ITopologyService;
+
+import org.openflow.util.HexString;
+import org.restlet.data.Form;
+import org.restlet.resource.Get;
+import org.restlet.resource.ServerResource;
+
+/**
+ * Returns a JSON map of <ClusterId, List<SwitchDpids>>
+ */
+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().
+ get(ITopologyService.class.getCanonicalName());
+
+ Form form = getQuery();
+ String queryType = form.getFirstValue("type", true);
+ boolean openflowDomain = true;
+ if (queryType != null && "l2".equals(queryType)) {
+ openflowDomain = false;
+ }
+
+ Map<String, List<String>> switchClusterMap = new HashMap<String, List<String>>();
+ for (Entry<Long, IOFSwitch> entry : floodlightProvider.getSwitches().entrySet()) {
+ Long clusterDpid =
+ (openflowDomain
+ ? topology.getOpenflowDomainId(entry.getValue().getId())
+ :topology.getL2DomainId(entry.getValue().getId()));
+ List<String> switchesInCluster = switchClusterMap.get(HexString.toHexString(clusterDpid));
+ if (switchesInCluster != null) {
+ switchesInCluster.add(HexString.toHexString(entry.getKey()));
+ } else {
+ List<String> l = new ArrayList<String>();
+ l.add(HexString.toHexString(entry.getKey()));
+ switchClusterMap.put(HexString.toHexString(clusterDpid), l);
+ }
+ }
+ return switchClusterMap;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/web/TopologyWebRoutable.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/web/TopologyWebRoutable.java
new file mode 100644
index 0000000..7989413
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/web/TopologyWebRoutable.java
@@ -0,0 +1,33 @@
+package net.floodlightcontroller.topology.web;
+
+import org.restlet.Context;
+import org.restlet.routing.Router;
+
+import net.floodlightcontroller.linkdiscovery.web.LinksResource;
+import net.floodlightcontroller.restserver.RestletRoutable;
+
+public class TopologyWebRoutable implements RestletRoutable {
+ /**
+ * Create the Restlet router and bind to the proper resources.
+ */
+ @Override
+ public Router getRestlet(Context context) {
+ Router router = new Router(context);
+ router.attach("/links/json", LinksResource.class);
+ router.attach("/tunnellinks/json", TunnelLinksResource.class);
+ router.attach("/switchclusters/json", SwitchClustersResource.class);
+ router.attach("/broadcastdomainports/json", BroadcastDomainPortsResource.class);
+ router.attach("/enabledports/json", EnabledPortsResource.class);
+ router.attach("/blockedports/json", BlockedPortsResource.class);
+ router.attach("/route/{src-dpid}/{src-port}/{dst-dpid}/{dst-port}/json", RouteResource.class);
+ return router;
+ }
+
+ /**
+ * Set the base path for the Topology
+ */
+ @Override
+ public String basePath() {
+ return "/wm/topology";
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/web/TunnelLinksResource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/web/TunnelLinksResource.java
new file mode 100644
index 0000000..71c3f12
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/topology/web/TunnelLinksResource.java
@@ -0,0 +1,20 @@
+package net.floodlightcontroller.topology.web;
+
+import java.util.Set;
+
+import net.floodlightcontroller.topology.ITopologyService;
+import net.floodlightcontroller.topology.NodePortTuple;
+
+import org.restlet.resource.Get;
+import org.restlet.resource.ServerResource;
+
+public class TunnelLinksResource extends ServerResource {
+ @Get("json")
+ public Set<NodePortTuple> retrieve() {
+ ITopologyService topology =
+ (ITopologyService)getContext().getAttributes().
+ get(ITopologyService.class.getCanonicalName());
+
+ return topology.getTunnelPorts();
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/ui/web/StaticWebRoutable.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/ui/web/StaticWebRoutable.java
new file mode 100644
index 0000000..c1d5b5f
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/ui/web/StaticWebRoutable.java
@@ -0,0 +1,70 @@
+package net.floodlightcontroller.ui.web;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Map;
+
+import org.restlet.Client;
+import org.restlet.Context;
+import org.restlet.Restlet;
+import org.restlet.data.Protocol;
+import org.restlet.resource.Directory;
+import org.restlet.routing.Router;
+
+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.restserver.IRestApiService;
+import net.floodlightcontroller.restserver.RestletRoutable;
+
+public class StaticWebRoutable implements RestletRoutable, IFloodlightModule {
+
+ private IRestApiService restApi;
+
+ @Override
+ public Collection<Class<? extends IFloodlightService>> getModuleDependencies() {
+ Collection<Class<? extends IFloodlightService>> l =
+ new ArrayList<Class<? extends IFloodlightService>>();
+ l.add(IRestApiService.class);
+ return l;
+ }
+
+ @Override
+ public Collection<Class<? extends IFloodlightService>> getModuleServices() {
+ return null;
+ }
+
+ @Override
+ public Map<Class<? extends IFloodlightService>, IFloodlightService>
+ getServiceImpls() {
+ return null;
+ }
+
+ @Override
+ public void init(FloodlightModuleContext context)
+ throws FloodlightModuleException {
+ restApi = context.getServiceImpl(IRestApiService.class);
+ }
+
+ @Override
+ public void startUp(FloodlightModuleContext context) {
+ // Add our REST API
+ restApi.addRestletRoutable(this);
+
+ }
+
+ @Override
+ public Restlet getRestlet(Context context) {
+ Router router = new Router(context);
+ router.attach("", new Directory(context, "clap://classloader/web/"));
+ context.setClientDispatcher(new Client(context, Protocol.CLAP));
+ return router;
+ }
+
+ @Override
+ public String basePath() {
+ return "/ui/";
+ }
+
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/util/BundleAction.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/util/BundleAction.java
new file mode 100644
index 0000000..0d82275
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/util/BundleAction.java
@@ -0,0 +1,54 @@
+/**
+* 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.util;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public enum BundleAction {
+ START,
+ STOP,
+ UNINSTALL,
+ REFRESH;
+
+ public static List<BundleAction> getAvailableActions(BundleState state) {
+ List<BundleAction> actions = new ArrayList<BundleAction>();
+ if (Arrays.binarySearch(new BundleState[] {
+ BundleState.ACTIVE, BundleState.STARTING,
+ BundleState.UNINSTALLED }, state) < 0) {
+ actions.add(START);
+ }
+ if (Arrays.binarySearch(new BundleState[] {
+ BundleState.ACTIVE}, state) >= 0) {
+ actions.add(STOP);
+ }
+ if (Arrays.binarySearch(new BundleState[] {
+ BundleState.UNINSTALLED}, state) < 0) {
+ actions.add(UNINSTALL);
+ }
+
+ // Always capable of refresh?
+ actions.add(REFRESH);
+ return actions;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/util/BundleState.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/util/BundleState.java
new file mode 100644
index 0000000..f89bc0b
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/util/BundleState.java
@@ -0,0 +1,62 @@
+/**
+* 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.util;
+
+/**
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public enum BundleState {
+ ACTIVE (32),
+ INSTALLED (2),
+ RESOLVED (4),
+ STARTING (8),
+ STOPPING (16),
+ UNINSTALLED (1);
+
+ protected int value;
+
+ private BundleState(int value) {
+ this.value = value;
+ }
+
+ /**
+ * @return the value
+ */
+ public int getValue() {
+ return value;
+ }
+
+ public static BundleState getState(int value) {
+ switch (value) {
+ case 32:
+ return ACTIVE;
+ case 2:
+ return INSTALLED;
+ case 4:
+ return RESOLVED;
+ case 8:
+ return STARTING;
+ case 16:
+ return STOPPING;
+ case 1:
+ return UNINSTALLED;
+ }
+ return null;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/util/ClusterDFS.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/util/ClusterDFS.java
new file mode 100644
index 0000000..3efd5bc
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/util/ClusterDFS.java
@@ -0,0 +1,47 @@
+package net.floodlightcontroller.util;
+
+public class ClusterDFS {
+ long dfsIndex;
+ long parentDFSIndex;
+ long lowpoint;
+ boolean visited;
+
+ public ClusterDFS() {
+ visited = false;
+ dfsIndex = Long.MAX_VALUE;
+ parentDFSIndex = Long.MAX_VALUE;
+ lowpoint = Long.MAX_VALUE;
+ }
+
+ public long getDfsIndex() {
+ return dfsIndex;
+ }
+
+ public void setDfsIndex(long dfsIndex) {
+ this.dfsIndex = dfsIndex;
+ }
+
+ public long getParentDFSIndex() {
+ return parentDFSIndex;
+ }
+
+ public void setParentDFSIndex(long parentDFSIndex) {
+ this.parentDFSIndex = parentDFSIndex;
+ }
+
+ public long getLowpoint() {
+ return lowpoint;
+ }
+
+ public void setLowpoint(long lowpoint) {
+ this.lowpoint = lowpoint;
+ }
+
+ public boolean isVisited() {
+ return visited;
+ }
+
+ public void setVisited(boolean visited) {
+ this.visited = visited;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/util/EventHistory.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/util/EventHistory.java
new file mode 100644
index 0000000..69031ba
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/util/EventHistory.java
@@ -0,0 +1,179 @@
+/**
+ *
+ */
+package net.floodlightcontroller.util;
+
+import java.util.ArrayList;
+
+/**
+ * @author subrata
+ *
+ */
+
+public class EventHistory<T> {
+ public static final int EV_HISTORY_DEFAULT_SIZE = 1024;
+
+ public String description;
+ public int event_history_size;
+ public int current_index;
+ public boolean full; // true if all are in use
+ public ArrayList<Event> events;
+
+ public String getDescription() {
+ return description;
+ }
+ public int getEvent_history_size() {
+ return event_history_size;
+ }
+ public int getCurrent_index() {
+ return current_index;
+ }
+ public boolean isFull() {
+ return full;
+ }
+ public ArrayList<Event> getEvents() {
+ return events;
+ }
+
+ public class Event {
+ public EventHistoryBaseInfo base_info;
+ public T info;
+ }
+
+ public enum EvState {
+ FREE, // no valid event written yet
+ BEING_MODIFIED, // event is being updated with new value, skip
+ ACTIVE, // event is active and can be displayed
+ }
+
+ public enum EvAction {
+ ADDED, // specific entry added
+ REMOVED, // specific entry removed
+ UPDATED, // Entry updated
+ BLOCKED, // Blocked - used for Attachment Points
+ UNBLOCKED,
+ CLEARED, // All entries are removed
+ PKT_IN,
+ PKT_OUT,
+ SWITCH_CONNECTED,
+ SWITCH_DISCONNECTED,
+ LINK_ADDED,
+ LINK_DELETED,
+ LINK_PORT_STATE_UPDATED,
+ CLUSTER_ID_CHANGED_FOR_CLUSTER,
+ CLUSTER_ID_CHANGED_FOR_A_SWITCH,
+ }
+
+ // Constructor
+ public EventHistory(int maxEvents, String desc) {
+ events = new ArrayList<Event>(maxEvents);
+
+ for (int idx = 0; idx < maxEvents; idx++) {
+ Event evH = new Event();
+ evH.base_info = new EventHistoryBaseInfo();
+ evH.info = null;
+ evH.base_info.state = EvState.FREE;
+ evH.base_info.idx = idx;
+ events.add(idx, evH);
+ }
+
+ description = "Event-History:" + desc;
+ event_history_size = maxEvents;
+ current_index = 0;
+ full = false;
+ }
+
+ // Constructor for default size
+ public EventHistory(String desc) {
+ this(EV_HISTORY_DEFAULT_SIZE, desc);
+ }
+
+ // Copy constructor - copy latest k items of the event history
+ public EventHistory(EventHistory<T> eventHist, int latestK) {
+
+ if (eventHist == null) {
+ description = "No event found";
+ return;
+ }
+ int curSize = (eventHist.full)?eventHist.event_history_size:
+ eventHist.current_index;
+ int size = (latestK < curSize)?latestK:curSize;
+ int evIdx = eventHist.current_index;
+ int topSz = (evIdx >= size)?size:evIdx;
+
+ // Need to create a new one since size is different
+ events = new ArrayList<Event>(size);
+
+ // Get the top part
+ int origIdx = evIdx;
+ for (int idx = 0; idx < topSz; idx++) {
+ Event evH = eventHist.events.get(--origIdx);
+ evH.base_info.idx = idx;
+ events.add(idx, evH);
+ }
+
+ // Get the bottom part
+ origIdx = eventHist.event_history_size;
+ for (int idx = topSz; idx < size; idx++) {
+ Event evH = eventHist.events.get(--origIdx);
+ evH.base_info.idx = idx;
+ events.add(idx, evH);
+ }
+
+ description = eventHist.description;
+ event_history_size = size;
+ current_index = 0; // since it is full
+ full = true;
+ }
+
+ // Get an index for writing a new event. This method is synchronized for
+ // this event history infra. to be thread-safe. Once the index is obtained
+ // by the caller event at the index is updated without any lock
+ public synchronized int NextIdx() {
+ // curIdx should be in the 0 to evArraySz-1
+ if (current_index == (event_history_size-1)) {
+ current_index = 0;
+ full = true;
+ return (event_history_size-1);
+ } else {
+ current_index++;
+ return (current_index-1);
+ }
+ }
+
+ /**
+ * Add an event to the event history
+ * Eliminate java garbage cration by reusing the same object T
+ * Supplied object t is used to populate the event history array
+ * and the current object at that array location is returned to the
+ * calling process so that the calling process can use that object
+ * for the next event of the same type
+ * @param t
+ * @param op
+ * @return
+ */
+
+ public T put(T t, EvAction action) {
+ int idx = NextIdx();
+ Event evH = events.get(idx);
+ evH.base_info.state = EvState.BEING_MODIFIED;
+ evH.base_info.time_ms = System.currentTimeMillis();
+ evH.base_info.action = action;
+ T temp = evH.info;
+ evH.info = t;
+ evH.base_info.state = EvState.ACTIVE;
+ return temp;
+ }
+
+ /***
+ * Clear the event history, needs to be done under lock
+ */
+ public void clear() {
+ for (int idx = 0; idx < event_history_size; idx++) {
+ Event evH = events.get(idx);
+ evH.base_info.state = EvState.FREE;
+ current_index = 0;
+ full = false;
+ }
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/util/EventHistoryBaseInfo.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/util/EventHistoryBaseInfo.java
new file mode 100644
index 0000000..74fc973
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/util/EventHistoryBaseInfo.java
@@ -0,0 +1,26 @@
+package net.floodlightcontroller.util;
+
+
+import org.codehaus.jackson.map.annotate.JsonSerialize;
+
+@JsonSerialize(using=EventHistoryBaseInfoJSONSerializer.class)
+public class EventHistoryBaseInfo {
+ public int idx;
+ public long time_ms; // timestamp in milliseconds
+ public EventHistory.EvState state;
+ public EventHistory.EvAction action;
+
+ // Getters
+ public int getIdx() {
+ return idx;
+ }
+ public long getTime_ms() {
+ return time_ms;
+ }
+ public EventHistory.EvState getState() {
+ return state;
+ }
+ public EventHistory.EvAction getAction() {
+ return action;
+ }
+}
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/util/EventHistoryBaseInfoJSONSerializer.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/util/EventHistoryBaseInfoJSONSerializer.java
new file mode 100644
index 0000000..6f1d1ff
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/util/EventHistoryBaseInfoJSONSerializer.java
@@ -0,0 +1,69 @@
+/**
+* Copyright 2011, 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.io.IOException;
+
+import java.sql.Timestamp;
+
+
+import org.codehaus.jackson.JsonGenerator;
+import org.codehaus.jackson.JsonProcessingException;
+import org.codehaus.jackson.map.JsonSerializer;
+import org.codehaus.jackson.map.SerializerProvider;
+
+
+/**
+ * @author subrata
+ *
+ */
+
+public class EventHistoryBaseInfoJSONSerializer extends
+ JsonSerializer<EventHistoryBaseInfo> {
+
+
+ /**
+ * Performs the serialization of a EventHistory.BaseInfo object
+ */
+ @Override
+ public void serialize(EventHistoryBaseInfo base_info, JsonGenerator jGen,
+ SerializerProvider serializer)
+ throws IOException, JsonProcessingException {
+ jGen.writeStartObject();
+ jGen.writeNumberField("Idx", base_info.getIdx());
+ Timestamp ts = new Timestamp(base_info.getTime_ms());
+ String tsStr = ts.toString();
+ while (tsStr.length() < 23) {
+ tsStr = tsStr.concat("0");
+ }
+ jGen.writeStringField("Time", tsStr);
+ jGen.writeStringField("State", base_info.getState().name());
+ String acStr = base_info.getAction().name().toLowerCase();
+ // Capitalize the first letter
+ acStr = acStr.substring(0,1).toUpperCase().concat(acStr.substring(1));
+ jGen.writeStringField("Action", acStr);
+ jGen.writeEndObject();
+ }
+
+ /**
+ * Tells SimpleModule that we are the serializer for OFMatch
+ */
+ @Override
+ public Class<EventHistoryBaseInfo> handledType() {
+ return EventHistoryBaseInfo.class;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/util/FilterIterator.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/util/FilterIterator.java
new file mode 100644
index 0000000..47cd5c9
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/util/FilterIterator.java
@@ -0,0 +1,80 @@
+/**
+* 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.util;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/**
+ * An iterator that will filter values from an iterator and return only
+ * those values that match the predicate.
+ */
+public abstract class FilterIterator<T> implements Iterator<T> {
+ protected Iterator<T> subIterator;
+ protected T next;
+
+ /**
+ * Construct a filter iterator from the given sub iterator
+ * @param subIterator the sub iterator over which we'll filter
+ */
+ public FilterIterator(Iterator<T> subIterator) {
+ super();
+ this.subIterator = subIterator;
+ }
+
+ /**
+ * Check whether the given value should be returned by the
+ * filter
+ * @param value the value to check
+ * @return true if the value should be included
+ */
+ protected abstract boolean matches(T value);
+
+ // ***********
+ // Iterator<T>
+ // ***********
+
+ @Override
+ public boolean hasNext() {
+ if (next != null) return true;
+
+ while (subIterator.hasNext()) {
+ next = subIterator.next();
+ if (matches(next))
+ return true;
+ }
+ next = null;
+ return false;
+ }
+
+ @Override
+ public T next() {
+ if (hasNext()) {
+ T cur = next;
+ next = null;
+ return cur;
+ }
+ throw new NoSuchElementException();
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/util/IterableIterator.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/util/IterableIterator.java
new file mode 100644
index 0000000..584de08
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/util/IterableIterator.java
@@ -0,0 +1,66 @@
+/**
+* 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.util;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/**
+ * Iterator over all values in an iterator of iterators
+ *
+ * @param <T> the type of elements returned by this iterator
+ */
+public class IterableIterator<T> implements Iterator<T> {
+ Iterator<? extends Iterable<T>> subIterator;
+ Iterator<T> current = null;
+
+ public IterableIterator(Iterator<? extends Iterable<T>> subIterator) {
+ super();
+ this.subIterator = subIterator;
+ }
+
+ @Override
+ public boolean hasNext() {
+ if (current == null) {
+ if (subIterator.hasNext()) {
+ current = subIterator.next().iterator();
+ } else {
+ return false;
+ }
+ }
+ while (!current.hasNext() && subIterator.hasNext()) {
+ current = subIterator.next().iterator();
+ }
+
+ return current.hasNext();
+ }
+
+ @Override
+ public T next() {
+ if (hasNext())
+ return current.next();
+ throw new NoSuchElementException();
+ }
+
+ @Override
+ public void remove() {
+ if (hasNext())
+ current.remove();
+ throw new NoSuchElementException();
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/util/LRUHashMap.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/util/LRUHashMap.java
new file mode 100644
index 0000000..477e886
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/util/LRUHashMap.java
@@ -0,0 +1,38 @@
+/**
+* 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.util;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+public class LRUHashMap<K, V> extends LinkedHashMap<K, V> {
+
+ private static final long serialVersionUID = 1L;
+
+ private final int capacity;
+ public LRUHashMap(int capacity)
+ {
+ super(capacity+1, 0.75f, true);
+ this.capacity = capacity;
+ }
+
+ protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
+ return size() > capacity;
+ }
+
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/util/MACAddress.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/util/MACAddress.java
new file mode 100644
index 0000000..4ba9dad
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/util/MACAddress.java
@@ -0,0 +1,157 @@
+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/ext/floodlight/src/main/java/net/floodlightcontroller/util/MultiIterator.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/util/MultiIterator.java
new file mode 100644
index 0000000..bcbc916
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/util/MultiIterator.java
@@ -0,0 +1,66 @@
+/**
+* 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.util;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/**
+ * Iterator over all values in an iterator of iterators
+ *
+ * @param <T> the type of elements returned by this iterator
+ */
+public class MultiIterator<T> implements Iterator<T> {
+ Iterator<Iterator<T>> subIterator;
+ Iterator<T> current = null;
+
+ public MultiIterator(Iterator<Iterator<T>> subIterator) {
+ super();
+ this.subIterator = subIterator;
+ }
+
+ @Override
+ public boolean hasNext() {
+ if (current == null) {
+ if (subIterator.hasNext()) {
+ current = subIterator.next();
+ } else {
+ return false;
+ }
+ }
+ while (!current.hasNext() && subIterator.hasNext()) {
+ current = subIterator.next();
+ }
+
+ return current.hasNext();
+ }
+
+ @Override
+ public T next() {
+ if (hasNext())
+ return current.next();
+ throw new NoSuchElementException();
+ }
+
+ @Override
+ public void remove() {
+ if (hasNext())
+ current.remove();
+ throw new NoSuchElementException();
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/util/OFMessageDamper.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/util/OFMessageDamper.java
new file mode 100644
index 0000000..4dfb60b
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/util/OFMessageDamper.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright Big Switch Networks 2012
+ */
+
+package net.floodlightcontroller.util;
+
+import java.io.IOException;
+import java.util.EnumSet;
+import java.util.Set;
+
+import net.floodlightcontroller.core.FloodlightContext;
+import net.floodlightcontroller.core.IOFSwitch;
+
+import org.openflow.protocol.OFMessage;
+import org.openflow.protocol.OFType;
+
+/**
+ * Dampens OFMessages sent to an OF switch. A message is only written to
+ * a switch if the same message (as defined by .equals()) has not been written
+ * in the last n milliseconds. Timer granularity is based on TimedCache
+ * @author gregor
+ *
+ */
+public class OFMessageDamper {
+ /**
+ * An entry in the TimedCache. A cache entry consists of the sent message
+ * as well as the switch to which the message was sent.
+ *
+ * NOTE: We currently use the full OFMessage object. To save space, we
+ * could use a cryptographic hash (e.g., SHA-1). However, this would
+ * obviously be more time-consuming....
+ *
+ * We also store a reference to the actual IOFSwitch object and /not/
+ * the switch DPID. This way we are guarnteed to not dampen messages if
+ * a switch disconnects and then reconnects.
+ *
+ * @author gregor
+ */
+ protected static class DamperEntry {
+ OFMessage msg;
+ IOFSwitch sw;
+ public DamperEntry(OFMessage msg, IOFSwitch sw) {
+ super();
+ this.msg = msg;
+ this.sw = sw;
+ }
+ /* (non-Javadoc)
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((msg == null) ? 0 : msg.hashCode());
+ result = prime * result + ((sw == null) ? 0 : sw.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;
+ DamperEntry other = (DamperEntry) obj;
+ if (msg == null) {
+ if (other.msg != null) return false;
+ } else if (!msg.equals(other.msg)) return false;
+ if (sw == null) {
+ if (other.sw != null) return false;
+ } else if (!sw.equals(other.sw)) return false;
+ return true;
+ }
+
+
+ }
+ TimedCache<DamperEntry> cache;
+ EnumSet<OFType> msgTypesToCache;
+ /**
+ *
+ * @param capacity the maximum number of messages that should be
+ * kept
+ * @param typesToDampen The set of OFMessageTypes that should be
+ * dampened by this instance. Other types will be passed through
+ * @param timeout The dampening timeout. A message will only be
+ * written if the last write for the an equal message more than
+ * timeout ms ago.
+ */
+ public OFMessageDamper(int capacity,
+ Set<OFType> typesToDampen,
+ int timeout) {
+ cache = new TimedCache<DamperEntry>(capacity, timeout);
+ msgTypesToCache = EnumSet.copyOf(typesToDampen);
+ }
+
+ /**
+ * write the messag to the switch according to our dampening settings
+ * @param sw
+ * @param msg
+ * @param cntx
+ * @return true if the message was written to the switch, false if
+ * the message was dampened.
+ * @throws IOException
+ */
+ public boolean write(IOFSwitch sw, OFMessage msg, FloodlightContext cntx)
+ throws IOException {
+ return write(sw, msg, cntx, false);
+ }
+
+ /**
+ * write the messag to the switch according to our dampening settings
+ * @param sw
+ * @param msg
+ * @param cntx
+ * @param flush true to flush the packet immidiately
+ * @return true if the message was written to the switch, false if
+ * the message was dampened.
+ * @throws IOException
+ */
+ public boolean write(IOFSwitch sw, OFMessage msg,
+ FloodlightContext cntx, boolean flush)
+ throws IOException {
+ if (! msgTypesToCache.contains(msg.getType())) {
+ sw.write(msg, cntx);
+ if (flush) {
+ sw.flush();
+ }
+ return true;
+ }
+
+ DamperEntry entry = new DamperEntry(msg, sw);
+ if (cache.update(entry)) {
+ // entry exists in cache. Dampening.
+ return false;
+ } else {
+ sw.write(msg, cntx);
+ if (flush) {
+ sw.flush();
+ }
+ return true;
+ }
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/util/TimedCache.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/util/TimedCache.java
new file mode 100644
index 0000000..7341df7
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/util/TimedCache.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.util;
+
+import com.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+/**
+ * The key is any object/hash-code
+ * The value is time-stamp in milliseconds
+ * The time interval denotes the interval for which the entry should remain in the hashmap.
+ * If an entry is present in the Linkedhashmap, it does not mean that it's valid (recently seen)
+ *
+ * @param <K> Type of the values in this cache
+ */
+public class TimedCache<K> {
+ private final long timeoutInterval; //specified in milliseconds.
+ private ConcurrentMap<K, Long> cache;
+
+ /**
+ *
+ * @param capacity the maximum number of entries in the cache before the
+ * oldest entry is evicted.
+ * @param timeToLive specified in milliseconds
+ */
+ public TimedCache(int capacity, int timeToLive) {
+ cache = new ConcurrentLinkedHashMap.Builder<K, Long>()
+ .maximumWeightedCapacity(capacity)
+ .build();
+ this.timeoutInterval = timeToLive;
+ }
+
+ public long getTimeoutInterval() {
+ return this.timeoutInterval;
+ }
+
+ /**
+ * Always try to update the cache and set the last-seen value for this key.
+ *
+ * Return true, if a valid existing field was updated, else return false.
+ * (note: if multiple threads update simultaneously, one of them will succeed,
+ * other wills return false)
+ *
+ * @param key
+ * @return boolean
+ */
+ public boolean update(K key)
+ {
+ Long curr = new Long(System.currentTimeMillis());
+ Long prev = cache.putIfAbsent(key, curr);
+
+ if (prev == null) {
+ return false;
+ }
+
+ if (curr - prev > this.timeoutInterval) {
+ if (cache.replace(key, prev, curr)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/virtualnetwork/HostResource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/virtualnetwork/HostResource.java
new file mode 100644
index 0000000..6021e3d
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/virtualnetwork/HostResource.java
@@ -0,0 +1,95 @@
+package net.floodlightcontroller.virtualnetwork;
+
+import java.io.IOException;
+
+import net.floodlightcontroller.util.MACAddress;
+
+import org.codehaus.jackson.JsonParseException;
+import org.codehaus.jackson.JsonParser;
+import org.codehaus.jackson.JsonToken;
+import org.codehaus.jackson.map.MappingJsonFactory;
+import org.restlet.data.Status;
+import org.restlet.resource.Delete;
+import org.restlet.resource.Put;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class HostResource extends org.restlet.resource.ServerResource {
+ protected static Logger log = LoggerFactory.getLogger(HostResource.class);
+
+ public class HostDefinition {
+ String port = null; // Logical port name
+ String guid = null; // Network ID
+ String mac = null; // MAC Address
+ String attachment = null; // Attachment name
+ }
+
+ protected void jsonToHostDefinition(String json, HostDefinition host) throws IOException {
+ MappingJsonFactory f = new MappingJsonFactory();
+ JsonParser jp;
+
+ try {
+ jp = f.createJsonParser(json);
+ } 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;
+ else if (n.equals("attachment")) {
+ while (jp.nextToken() != JsonToken.END_OBJECT) {
+ String field = jp.getCurrentName();
+ if (field.equals("id")) {
+ host.attachment = jp.getText();
+ } else if (field.equals("mac")) {
+ host.mac = jp.getText();
+ }
+ }
+ }
+ }
+
+ jp.close();
+ }
+
+ @Put
+ public String addHost(String postData) {
+ IVirtualNetworkService vns =
+ (IVirtualNetworkService)getContext().getAttributes().
+ get(IVirtualNetworkService.class.getCanonicalName());
+ HostDefinition host = new HostDefinition();
+ host.port = (String) getRequestAttributes().get("port");
+ host.guid = (String) getRequestAttributes().get("network");
+ try {
+ jsonToHostDefinition(postData, host);
+ } catch (IOException e) {
+ log.error("Could not parse JSON {}", e.getMessage());
+ }
+ vns.addHost(MACAddress.valueOf(host.mac), host.guid, host.port);
+ setStatus(Status.SUCCESS_OK);
+ return "{\"status\":\"ok\"}";
+ }
+
+
+ @Delete
+ public String deleteHost() {
+ String port = (String) getRequestAttributes().get("port");
+ IVirtualNetworkService vns =
+ (IVirtualNetworkService)getContext().getAttributes().
+ get(IVirtualNetworkService.class.getCanonicalName());
+ vns.deleteHost(null, port);
+ setStatus(Status.SUCCESS_OK);
+ return "{\"status\":\"ok\"}";
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/virtualnetwork/IVirtualNetworkService.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/virtualnetwork/IVirtualNetworkService.java
new file mode 100644
index 0000000..4304a33
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/virtualnetwork/IVirtualNetworkService.java
@@ -0,0 +1,46 @@
+package net.floodlightcontroller.virtualnetwork;
+
+import java.util.Collection;
+import net.floodlightcontroller.core.module.IFloodlightService;
+import net.floodlightcontroller.util.MACAddress;
+
+public interface IVirtualNetworkService extends IFloodlightService {
+ /**
+ * Creates a new virtual network. This can also be called
+ * to modify a virtual network. To update a network you specify the GUID
+ * and the fields you want to update.
+ * @param network The network name. Must be unique.
+ * @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);
+
+ /**
+ * Deletes a virtual network.
+ * @param guid The ID (not name) of virtual network to delete.
+ */
+ public void deleteNetwork(String guid);
+
+ /**
+ * Adds a host to a virtual network. If a mapping already exists the
+ * new one will override the old mapping.
+ * @param mac The MAC address of the host to add.
+ * @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);
+
+ /**
+ * Deletes a host from a virtual network. Either the MAC or Port must
+ * be specified.
+ * @param mac The MAC address to delete.
+ * @param port The logical port the host is attached to.
+ */
+ public void deleteHost(MACAddress mac, String port);
+
+ /**
+ * Return list of all virtual networks.
+ * @return Collection <VirtualNetwork>
+ */
+ public Collection <VirtualNetwork> listNetworks();
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/virtualnetwork/NetworkResource.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/virtualnetwork/NetworkResource.java
new file mode 100644
index 0000000..2efe52a
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/virtualnetwork/NetworkResource.java
@@ -0,0 +1,133 @@
+package net.floodlightcontroller.virtualnetwork;
+
+import java.io.IOException;
+import java.util.Collection;
+
+import net.floodlightcontroller.packet.IPv4;
+
+import org.codehaus.jackson.JsonParseException;
+import org.codehaus.jackson.JsonParser;
+import org.codehaus.jackson.JsonToken;
+import org.codehaus.jackson.map.MappingJsonFactory;
+import org.restlet.data.Status;
+import org.restlet.resource.Delete;
+import org.restlet.resource.Get;
+import org.restlet.resource.Post;
+import org.restlet.resource.Put;
+import org.restlet.resource.ServerResource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class NetworkResource extends ServerResource {
+ protected static Logger log = LoggerFactory.getLogger(NetworkResource.class);
+
+ public class NetworkDefinition {
+ public String name = null;
+ public String guid = null;
+ public String gateway = null;
+ }
+
+ protected void jsonToNetworkDefinition(String json, NetworkDefinition network) throws IOException {
+ MappingJsonFactory f = new MappingJsonFactory();
+ JsonParser jp;
+
+ try {
+ jp = f.createJsonParser(json);
+ } 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;
+ else if (n.equals("network")) {
+ while (jp.nextToken() != JsonToken.END_OBJECT) {
+ String field = jp.getCurrentName();
+ if (field.equals("name")) {
+ network.name = jp.getText();
+ } else if (field.equals("gateway")) {
+ String gw = jp.getText();
+ if ((gw != null) && (!gw.equals("null")))
+ network.gateway = gw;
+ } else if (field.equals("id")) {
+ network.guid = jp.getText();
+ } else {
+ log.warn("Unrecognized field {} in " +
+ "parsing network definition",
+ jp.getText());
+ }
+ }
+ }
+ }
+
+ jp.close();
+ }
+
+ @Get("json")
+ public Collection <VirtualNetwork> retrieve() {
+ IVirtualNetworkService vns =
+ (IVirtualNetworkService)getContext().getAttributes().
+ get(IVirtualNetworkService.class.getCanonicalName());
+
+ return vns.listNetworks();
+ }
+
+ @Put
+ @Post
+ public String createNetwork(String postData) {
+ NetworkDefinition network = new NetworkDefinition();
+ try {
+ jsonToNetworkDefinition(postData, network);
+ } catch (IOException e) {
+ log.error("Could not parse JSON {}", e.getMessage());
+ }
+
+ // We try to get the ID from the URI only if it's not
+ // in the POST data
+ if (network.guid == null) {
+ String guid = (String) getRequestAttributes().get("network");
+ if ((guid != null) && (!guid.equals("null")))
+ network.guid = guid;
+ }
+
+ IVirtualNetworkService vns =
+ (IVirtualNetworkService)getContext().getAttributes().
+ get(IVirtualNetworkService.class.getCanonicalName());
+
+ Integer gw = null;
+ if (network.gateway != null) {
+ try {
+ gw = IPv4.toIPv4Address(network.gateway);
+ } catch (IllegalArgumentException e) {
+ log.warn("Could not parse gateway {} as IP for network {}, setting as null",
+ network.gateway, network.name);
+ network.gateway = null;
+ }
+ }
+ vns.createNetwork(network.guid, network.name, gw);
+ setStatus(Status.SUCCESS_OK);
+ return "{\"status\":\"ok\"}";
+ }
+
+ @Delete
+ public String deleteNetwork() {
+ IVirtualNetworkService vns =
+ (IVirtualNetworkService)getContext().getAttributes().
+ get(IVirtualNetworkService.class.getCanonicalName());
+ String guid = (String) getRequestAttributes().get("network");
+ vns.deleteNetwork(guid);
+ setStatus(Status.SUCCESS_OK);
+ return "{\"status\":\"ok\"}";
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/virtualnetwork/NoOp.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/virtualnetwork/NoOp.java
new file mode 100644
index 0000000..a184a95
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/virtualnetwork/NoOp.java
@@ -0,0 +1,21 @@
+package net.floodlightcontroller.virtualnetwork;
+
+import org.restlet.data.Status;
+import org.restlet.resource.Get;
+import org.restlet.resource.Post;
+import org.restlet.resource.Put;
+import org.restlet.resource.ServerResource;
+
+public class NoOp extends ServerResource {
+ /**
+ * Does nothing and returns 200 OK with a status message
+ * @return status: ok
+ */
+ @Get
+ @Put
+ @Post
+ public String noOp(String postdata) {
+ setStatus(Status.SUCCESS_OK);
+ return "{\"status\":\"ok\"}";
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/virtualnetwork/VirtualNetwork.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/virtualnetwork/VirtualNetwork.java
new file mode 100644
index 0000000..f5dfb21
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/virtualnetwork/VirtualNetwork.java
@@ -0,0 +1,88 @@
+package net.floodlightcontroller.virtualnetwork;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import org.codehaus.jackson.map.annotate.JsonSerialize;
+
+import net.floodlightcontroller.util.MACAddress;
+
+/**
+ * Data structure for storing and outputing information of a virtual network created
+ * by VirtualNetworkFilter
+ *
+ * @author KC Wang
+ */
+
+@JsonSerialize(using=VirtualNetworkSerializer.class)
+public class VirtualNetwork{
+ protected String name; // network name
+ protected String guid; // network id
+ protected String gateway; // network gateway
+ protected Collection<MACAddress> hosts; // array of hosts explicitly added to this network
+
+ /**
+ * Constructor requires network name and id
+ * @param name: network name
+ * @param guid: network id
+ */
+ public VirtualNetwork(String name, String guid) {
+ this.name = name;
+ this.guid = guid;
+ this.gateway = null;
+ this.hosts = new ArrayList<MACAddress>();
+ return;
+ }
+
+ /**
+ * Sets network name
+ * @param gateway: IP address as String
+ */
+ public void setName(String name){
+ this.name = name;
+ return;
+ }
+
+ /**
+ * Sets network gateway IP address
+ * @param gateway: IP address as String
+ */
+ public void setGateway(String gateway){
+ this.gateway = gateway;
+ return;
+ }
+
+ /**
+ * Adds a host to this network record
+ * @param host: MAC address as MACAddress
+ */
+ public void addHost(MACAddress host){
+ this.hosts.add(host);
+ return;
+ }
+
+ /**
+ * Removes a host from this network record
+ * @param host: MAC address as MACAddress
+ * @return boolean: true: removed, false: host not found
+ */
+ public boolean removeHost(MACAddress host){
+ Iterator<MACAddress> iter = this.hosts.iterator();
+ while(iter.hasNext()){
+ MACAddress element = iter.next();
+ if(element.equals(host) ){
+ //assuming MAC address for host is unique
+ iter.remove();
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Removes all hosts from this network record
+ */
+ public void clearHosts(){
+ this.hosts.clear();
+ }
+}
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/virtualnetwork/VirtualNetworkFilter.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/virtualnetwork/VirtualNetworkFilter.java
new file mode 100644
index 0000000..012dfb6
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/virtualnetwork/VirtualNetworkFilter.java
@@ -0,0 +1,521 @@
+package net.floodlightcontroller.virtualnetwork;
+
+import java.io.IOException;
+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.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.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import net.floodlightcontroller.core.FloodlightContext;
+import net.floodlightcontroller.core.IFloodlightProviderService;
+import net.floodlightcontroller.core.IOFMessageListener;
+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.core.util.AppCookie;
+import net.floodlightcontroller.devicemanager.IDevice;
+import net.floodlightcontroller.devicemanager.IDeviceListener;
+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
+ * you to create simple L2 networks (host + gateway) and will drop traffic if
+ * they are not on the same virtual network.
+ *
+ * LIMITATIONS
+ * - This module does not allow overlapping of IPs or MACs
+ * - You can only have 1 gateway per virtual network (can be shared)
+ * - There is filtering of multicast/broadcast traffic
+ * - All DHCP traffic will be allowed, regardless of unicast/broadcast
+ *
+ * @author alexreimers
+ */
+public class VirtualNetworkFilter
+ implements IFloodlightModule, IVirtualNetworkService, IOFMessageListener, IDeviceListener {
+ protected static Logger log = LoggerFactory.getLogger(VirtualNetworkFilter.class);
+
+ private final short APP_ID = 20;
+
+ // 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
+
+ /**
+ * 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;
+ }
+ }
+ }
+ 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(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(vNetsByGuid.get(macToGuid.get(host)) != null)
+ vNetsByGuid.get(macToGuid.get(host)).removeHost(host);
+ 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>();
+ }
+
+ @Override
+ public void startUp(FloodlightModuleContext context) {
+ floodlightProvider.addOFMessageListener(OFType.PACKET_IN, this);
+ restApi.addRestletRoutable(new VirtualNetworkWebRoutable());
+ deviceService.addListener(this);
+ }
+
+ // 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
+ 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;
+ }
+
+ // 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 Collection <VirtualNetwork> listNetworks() {
+ return vNetsByGuid.values();
+
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/virtualnetwork/VirtualNetworkSerializer.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/virtualnetwork/VirtualNetworkSerializer.java
new file mode 100644
index 0000000..6902f6c
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/virtualnetwork/VirtualNetworkSerializer.java
@@ -0,0 +1,38 @@
+package net.floodlightcontroller.virtualnetwork;
+
+import java.io.IOException;
+import java.util.Iterator;
+
+import net.floodlightcontroller.util.MACAddress;
+
+import org.codehaus.jackson.JsonGenerator;
+import org.codehaus.jackson.JsonProcessingException;
+import org.codehaus.jackson.map.JsonSerializer;
+import org.codehaus.jackson.map.SerializerProvider;
+
+/**
+ * Serialize a VirtualNetwork object
+ * @author KC Wang
+ */
+public class VirtualNetworkSerializer extends JsonSerializer<VirtualNetwork> {
+
+ @Override
+ public void serialize(VirtualNetwork vNet, JsonGenerator jGen,
+ SerializerProvider serializer) throws IOException,
+ JsonProcessingException {
+ jGen.writeStartObject();
+
+ jGen.writeStringField("name", vNet.name);
+ jGen.writeStringField("guid", vNet.guid);
+ jGen.writeStringField("gateway", vNet.gateway);
+
+ jGen.writeArrayFieldStart("mac");
+ Iterator<MACAddress> hit = vNet.hosts.iterator();
+ while (hit.hasNext())
+ jGen.writeString(hit.next().toString());
+ jGen.writeEndArray();
+
+ jGen.writeEndObject();
+ }
+
+}
diff --git a/src/ext/floodlight/src/main/java/net/floodlightcontroller/virtualnetwork/VirtualNetworkWebRoutable.java b/src/ext/floodlight/src/main/java/net/floodlightcontroller/virtualnetwork/VirtualNetworkWebRoutable.java
new file mode 100644
index 0000000..61769ec
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/net/floodlightcontroller/virtualnetwork/VirtualNetworkWebRoutable.java
@@ -0,0 +1,26 @@
+package net.floodlightcontroller.virtualnetwork;
+
+import org.restlet.Context;
+import org.restlet.Restlet;
+import org.restlet.routing.Router;
+
+import net.floodlightcontroller.restserver.RestletRoutable;
+
+public class VirtualNetworkWebRoutable implements RestletRoutable {
+
+ @Override
+ public Restlet getRestlet(Context context) {
+ Router router = new Router(context);
+ router.attach("/tenants/{tenant}/networks", NetworkResource.class); // GET
+ router.attach("/tenants/{tenant}/networks/{network}", NetworkResource.class); // PUT, DELETE
+ router.attach("/tenants/{tenant}/networks", NetworkResource.class); // POST
+ router.attach("/tenants/{tenant}/networks/{network}/ports/{port}/attachment", HostResource.class);
+ router.attachDefault(NoOp.class);
+ return router;
+ }
+
+ @Override
+ public String basePath() {
+ return "/quantum/v1.0";
+ }
+}
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/Instantiable.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/Instantiable.java
new file mode 100644
index 0000000..1358ba7
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/Instantiable.java
@@ -0,0 +1,31 @@
+/**
+* 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;
+
+/**
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public interface Instantiable<E> {
+
+ /**
+ * Create a new instance of a given subclass.
+ * @return the new instance.
+ */
+ public E instantiate();
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/OFBarrierReply.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFBarrierReply.java
new file mode 100644
index 0000000..a79a15f
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFBarrierReply.java
@@ -0,0 +1,32 @@
+/**
+* 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.openflow.util.U16;
+
+/**
+ * Represents an OFPT_BARRIER_REPLY message
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class OFBarrierReply extends OFMessage {
+ public OFBarrierReply() {
+ super();
+ this.type = OFType.BARRIER_REPLY;
+ this.length = U16.t(OFMessage.MINIMUM_LENGTH);
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/OFBarrierRequest.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFBarrierRequest.java
new file mode 100644
index 0000000..9992186
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFBarrierRequest.java
@@ -0,0 +1,32 @@
+/**
+* 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.openflow.util.U16;
+
+/**
+ * Represents an OFPT_BARRIER_REQUEST message
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class OFBarrierRequest extends OFMessage {
+ public OFBarrierRequest() {
+ super();
+ this.type = OFType.BARRIER_REQUEST;
+ this.length = U16.t(OFMessage.MINIMUM_LENGTH);
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/OFEchoReply.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFEchoReply.java
new file mode 100644
index 0000000..3e282a7
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFEchoReply.java
@@ -0,0 +1,36 @@
+/**
+* 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.openflow.util.U16;
+
+/**
+ * Represents an ofp_echo_reply message
+ *
+ * @author Rob Sherwood (rob.sherwood@stanford.edu)
+ */
+
+public class OFEchoReply extends OFEchoRequest {
+ public static int MINIMUM_LENGTH = 8;
+
+ public OFEchoReply() {
+ super();
+ this.type = OFType.ECHO_REPLY;
+ this.length = U16.t(MINIMUM_LENGTH);
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/OFEchoRequest.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFEchoRequest.java
new file mode 100644
index 0000000..295a397
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFEchoRequest.java
@@ -0,0 +1,101 @@
+/**
+* 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 org.jboss.netty.buffer.ChannelBuffer;
+import org.openflow.util.U16;
+
+/**
+ * Represents an ofp_echo_request message
+ *
+ * @author Rob Sherwood (rob.sherwood@stanford.edu)
+ */
+
+public class OFEchoRequest extends OFMessage {
+ public static int MINIMUM_LENGTH = 8;
+ byte[] payload;
+
+ public OFEchoRequest() {
+ super();
+ this.type = OFType.ECHO_REQUEST;
+ this.length = U16.t(MINIMUM_LENGTH);
+ }
+
+ @Override
+ public void readFrom(ChannelBuffer bb) {
+ super.readFrom(bb);
+ int datalen = this.getLengthU() - MINIMUM_LENGTH;
+ if (datalen > 0) {
+ this.payload = new byte[datalen];
+ bb.readBytes(payload);
+ }
+ }
+
+ /**
+ * @return the payload
+ */
+ public byte[] getPayload() {
+ return payload;
+ }
+
+ /**
+ * @param payload
+ * the payload to set
+ */
+ public void setPayload(byte[] payload) {
+ this.payload = payload;
+ }
+
+ @Override
+ public void writeTo(ChannelBuffer bb) {
+ super.writeTo(bb);
+ if (payload != null)
+ bb.writeBytes(payload);
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = super.hashCode();
+ result = prime * result + Arrays.hashCode(payload);
+ return result;
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (!super.equals(obj))
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ OFEchoRequest other = (OFEchoRequest) obj;
+ if (!Arrays.equals(payload, other.payload))
+ return false;
+ return true;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/OFError.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFError.java
new file mode 100644
index 0000000..515a79c
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFError.java
@@ -0,0 +1,319 @@
+/**
+* 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 java.util.List;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.jboss.netty.buffer.ChannelBuffers;
+import org.openflow.protocol.factory.MessageParseException;
+import org.openflow.protocol.factory.OFMessageFactory;
+import org.openflow.protocol.factory.OFMessageFactoryAware;
+import org.openflow.util.U16;
+
+/**
+ * Represents an ofp_error_msg
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ * @author Rob Sherwood (rob.sherwood@stanford.edu)
+ */
+public class OFError extends OFMessage implements OFMessageFactoryAware {
+ public static int MINIMUM_LENGTH = 12;
+
+ public enum OFErrorType {
+ // OFPET_VENDOR_ERROR is an extension that was added in Open vSwitch and isn't
+ // in the OF 1.0 spec, but it was easier to add it here instead of adding
+ // generic support for extensible vendor-defined error messages.
+ // It uses the random value 0xb0c2 to avoid conflicts with other possible new
+ // error types. Support for vendor-defined extended errors has been standardized
+ // in the OF 1.2 spec, so this workaround is only needed for 1.0.
+ OFPET_HELLO_FAILED, OFPET_BAD_REQUEST, OFPET_BAD_ACTION, OFPET_FLOW_MOD_FAILED, OFPET_PORT_MOD_FAILED, OFPET_QUEUE_OP_FAILED, OFPET_VENDOR_ERROR((short)0xb0c2);
+
+ protected short value;
+
+ private OFErrorType() {
+ this.value = (short) this.ordinal();
+ }
+
+ private OFErrorType(short value) {
+ this.value = value;
+ }
+
+ public short getValue() {
+ return value;
+ }
+ }
+
+ public enum OFHelloFailedCode {
+ OFPHFC_INCOMPATIBLE, OFPHFC_EPERM
+ }
+
+ public enum OFBadRequestCode {
+ OFPBRC_BAD_VERSION, OFPBRC_BAD_TYPE, OFPBRC_BAD_STAT, OFPBRC_BAD_VENDOR, OFPBRC_BAD_SUBTYPE, OFPBRC_EPERM, OFPBRC_BAD_LEN, OFPBRC_BUFFER_EMPTY, OFPBRC_BUFFER_UNKNOWN
+ }
+
+ public enum OFBadActionCode {
+ OFPBAC_BAD_TYPE, OFPBAC_BAD_LEN, OFPBAC_BAD_VENDOR, OFPBAC_BAD_VENDOR_TYPE, OFPBAC_BAD_OUT_PORT, OFPBAC_BAD_ARGUMENT, OFPBAC_EPERM, OFPBAC_TOO_MANY, OFPBAC_BAD_QUEUE
+ }
+
+ public enum OFFlowModFailedCode {
+ OFPFMFC_ALL_TABLES_FULL, OFPFMFC_OVERLAP, OFPFMFC_EPERM, OFPFMFC_BAD_EMERG_TIMEOUT, OFPFMFC_BAD_COMMAND, OFPFMFC_UNSUPPORTED
+ }
+
+ public enum OFPortModFailedCode {
+ OFPPMFC_BAD_PORT, OFPPMFC_BAD_HW_ADDR
+ }
+
+ public enum OFQueueOpFailedCode {
+ OFPQOFC_BAD_PORT, OFPQOFC_BAD_QUEUE, OFPQOFC_EPERM
+ }
+
+ protected short errorType;
+ protected short errorCode;
+ protected int vendor;
+ protected int vendorErrorType;
+ protected short vendorErrorCode;
+ protected OFMessageFactory factory;
+ protected byte[] error;
+ protected boolean errorIsAscii;
+
+ public OFError() {
+ super();
+ this.type = OFType.ERROR;
+ this.length = U16.t(MINIMUM_LENGTH);
+ }
+
+ /**
+ * @return the errorType
+ */
+ public short getErrorType() {
+ return errorType;
+ }
+
+ /**
+ * @param errorType
+ * the errorType to set
+ */
+ public void setErrorType(short errorType) {
+ this.errorType = errorType;
+ }
+
+ public void setErrorType(OFErrorType type) {
+ this.errorType = type.getValue();
+ }
+
+ /**
+ * @return true if the error is an extended vendor error
+ */
+ public boolean isVendorError() {
+ return errorType == OFErrorType.OFPET_VENDOR_ERROR.getValue();
+ }
+
+ /**
+ * @return the errorCode
+ */
+ public short getErrorCode() {
+ return errorCode;
+ }
+
+ /**
+ * @param errorCode
+ * the errorCode to set
+ */
+ public void setErrorCode(OFHelloFailedCode code) {
+ this.errorCode = (short) code.ordinal();
+ }
+
+ public void setErrorCode(short errorCode) {
+ this.errorCode = errorCode;
+ }
+
+ public void setErrorCode(OFBadRequestCode code) {
+ this.errorCode = (short) code.ordinal();
+ }
+
+ public void setErrorCode(OFBadActionCode code) {
+ this.errorCode = (short) code.ordinal();
+ }
+
+ public void setErrorCode(OFFlowModFailedCode code) {
+ this.errorCode = (short) code.ordinal();
+ }
+
+ public void setErrorCode(OFPortModFailedCode code) {
+ this.errorCode = (short) code.ordinal();
+ }
+
+ public void setErrorCode(OFQueueOpFailedCode code) {
+ this.errorCode = (short) code.ordinal();
+ }
+
+ public int getVendorErrorType() {
+ return vendorErrorType;
+ }
+
+ public void setVendorErrorType(int vendorErrorType) {
+ this.vendorErrorType = vendorErrorType;
+ }
+
+ public short getVendorErrorCode() {
+ return vendorErrorCode;
+ }
+
+ public void setVendorErrorCode(short vendorErrorCode) {
+ this.vendorErrorCode = vendorErrorCode;
+ }
+
+ public OFMessage getOffendingMsg() throws MessageParseException {
+ // should only have one message embedded; if more than one, just
+ // grab first
+ if (this.error == null)
+ return null;
+ ChannelBuffer errorMsg = ChannelBuffers.wrappedBuffer(this.error);
+ if (factory == null)
+ throw new RuntimeException("MessageFactory not set");
+
+ List<OFMessage> msglist = this.factory.parseMessage(errorMsg);
+ if (msglist == null)
+ return null;
+ return msglist.get(0);
+ }
+
+ /**
+ * Write this offending message into the payload of the Error message
+ *
+ * @param offendingMsg
+ */
+
+ public void setOffendingMsg(OFMessage offendingMsg) {
+ if (offendingMsg == null) {
+ super.setLengthU(MINIMUM_LENGTH);
+ } else {
+ this.error = new byte[offendingMsg.getLengthU()];
+ ChannelBuffer data = ChannelBuffers.wrappedBuffer(this.error);
+ data.writerIndex(0);
+ offendingMsg.writeTo(data);
+ super.setLengthU(MINIMUM_LENGTH + offendingMsg.getLengthU());
+ }
+ }
+
+ public OFMessageFactory getFactory() {
+ return factory;
+ }
+
+ @Override
+ public void setMessageFactory(OFMessageFactory factory) {
+ this.factory = factory;
+ }
+
+ /**
+ * @return the error
+ */
+ public byte[] getError() {
+ return error;
+ }
+
+ /**
+ * @param error
+ * the error to set
+ */
+ public void setError(byte[] error) {
+ this.error = error;
+ }
+
+ /**
+ * @return the errorIsAscii
+ */
+ public boolean isErrorIsAscii() {
+ return errorIsAscii;
+ }
+
+ /**
+ * @param errorIsAscii
+ * the errorIsAscii to set
+ */
+ public void setErrorIsAscii(boolean errorIsAscii) {
+ this.errorIsAscii = errorIsAscii;
+ }
+
+ @Override
+ public void readFrom(ChannelBuffer data) {
+ super.readFrom(data);
+ this.errorType = data.readShort();
+ this.errorCode = data.readShort();
+ int dataLength = this.getLengthU() - MINIMUM_LENGTH;
+ if (dataLength > 0) {
+ this.error = new byte[dataLength];
+ data.readBytes(this.error);
+ if (this.errorType == OFErrorType.OFPET_HELLO_FAILED.getValue())
+ this.errorIsAscii = true;
+ }
+ }
+
+ @Override
+ public void writeTo(ChannelBuffer data) {
+ super.writeTo(data);
+ data.writeShort(errorType);
+ data.writeShort(errorCode);
+ if (error != null)
+ data.writeBytes(error);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = super.hashCode();
+ result = prime * result + Arrays.hashCode(error);
+ result = prime * result + errorCode;
+ result = prime * result + (errorIsAscii ? 1231 : 1237);
+ result = prime * result + errorType;
+ return result;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (!super.equals(obj))
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ OFError other = (OFError) obj;
+ if (!Arrays.equals(error, other.error))
+ return false;
+ if (errorCode != other.errorCode)
+ return false;
+ if (errorIsAscii != other.errorIsAscii)
+ return false;
+ if (errorType != other.errorType)
+ return false;
+ return true;
+ }
+
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/OFFeaturesReply.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFFeaturesReply.java
new file mode 100644
index 0000000..d3af574
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFFeaturesReply.java
@@ -0,0 +1,258 @@
+/**
+* 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 org.codehaus.jackson.map.annotate.JsonSerialize;
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.openflow.protocol.serializers.OFFeaturesReplyJSONSerializer;
+import org.openflow.util.U16;
+
+
+/**
+ * Represents a features reply message
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ *
+ */
+@JsonSerialize(using=OFFeaturesReplyJSONSerializer.class)
+public class OFFeaturesReply extends OFMessage {
+ public static int MINIMUM_LENGTH = 32;
+
+ /**
+ * Corresponds to bits on the capabilities field
+ */
+ public enum OFCapabilities {
+ OFPC_FLOW_STATS (1 << 0),
+ OFPC_TABLE_STATS (1 << 1),
+ OFPC_PORT_STATS (1 << 2),
+ OFPC_STP (1 << 3),
+ OFPC_RESERVED (1 << 4),
+ OFPC_IP_REASM (1 << 5),
+ OFPC_QUEUE_STATS (1 << 6),
+ OFPC_ARP_MATCH_IP (1 << 7);
+
+ protected int value;
+
+ private OFCapabilities(int value) {
+ this.value = value;
+ }
+
+ /**
+ * @return the value
+ */
+ public int getValue() {
+ return value;
+ }
+ }
+
+ protected long datapathId;
+ protected int buffers;
+ protected byte tables;
+ protected int capabilities;
+ protected int actions;
+ protected List<OFPhysicalPort> ports;
+
+ public OFFeaturesReply() {
+ super();
+ this.type = OFType.FEATURES_REPLY;
+ this.length = U16.t(MINIMUM_LENGTH);
+ }
+
+ /**
+ * @return the datapathId
+ */
+ public long getDatapathId() {
+ return datapathId;
+ }
+
+ /**
+ * @param datapathId the datapathId to set
+ */
+ public void setDatapathId(long datapathId) {
+ this.datapathId = datapathId;
+ }
+
+ /**
+ * @return the buffers
+ */
+ public int getBuffers() {
+ return buffers;
+ }
+
+ /**
+ * @param buffers the buffers to set
+ */
+ public void setBuffers(int buffers) {
+ this.buffers = buffers;
+ }
+
+ /**
+ * @return the tables
+ */
+ public byte getTables() {
+ return tables;
+ }
+
+ /**
+ * @param tables the tables to set
+ */
+ public void setTables(byte tables) {
+ this.tables = tables;
+ }
+
+ /**
+ * @return the capabilities
+ */
+ public int getCapabilities() {
+ return capabilities;
+ }
+
+ /**
+ * @param capabilities the capabilities to set
+ */
+ public void setCapabilities(int capabilities) {
+ this.capabilities = capabilities;
+ }
+
+ /**
+ * @return the actions
+ */
+ public int getActions() {
+ return actions;
+ }
+
+ /**
+ * @param actions the actions to set
+ */
+ public void setActions(int actions) {
+ this.actions = actions;
+ }
+
+ /**
+ * @return the ports
+ */
+ public List<OFPhysicalPort> getPorts() {
+ return ports;
+ }
+
+ /**
+ * @param ports the ports to set
+ */
+ public void setPorts(List<OFPhysicalPort> ports) {
+ this.ports = ports;
+ if (ports == null) {
+ this.setLengthU(MINIMUM_LENGTH);
+ } else {
+ this.setLengthU(MINIMUM_LENGTH + ports.size()
+ * OFPhysicalPort.MINIMUM_LENGTH);
+ }
+ }
+
+ @Override
+ public void readFrom(ChannelBuffer data) {
+ super.readFrom(data);
+ this.datapathId = data.readLong();
+ this.buffers = data.readInt();
+ this.tables = data.readByte();
+ data.readerIndex(data.readerIndex() + 3); // pad
+ this.capabilities = data.readInt();
+ this.actions = data.readInt();
+ if (this.ports == null) {
+ this.ports = new ArrayList<OFPhysicalPort>();
+ } else {
+ this.ports.clear();
+ }
+ int portCount = (super.getLengthU() - 32)
+ / OFPhysicalPort.MINIMUM_LENGTH;
+ OFPhysicalPort port;
+ for (int i = 0; i < portCount; ++i) {
+ port = new OFPhysicalPort();
+ port.readFrom(data);
+ this.ports.add(port);
+ }
+ }
+
+ @Override
+ public void writeTo(ChannelBuffer data) {
+ super.writeTo(data);
+ data.writeLong(this.datapathId);
+ data.writeInt(this.buffers);
+ data.writeByte(this.tables);
+ data.writeShort((short) 0); // pad
+ data.writeByte((byte) 0); // pad
+ data.writeInt(this.capabilities);
+ data.writeInt(this.actions);
+ if (this.ports != null)
+ for (OFPhysicalPort port : this.ports) {
+ port.writeTo(data);
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 139;
+ int result = super.hashCode();
+ result = prime * result + actions;
+ result = prime * result + buffers;
+ result = prime * result + capabilities;
+ result = prime * result + (int) (datapathId ^ (datapathId >>> 32));
+ result = prime * result + ((ports == null) ? 0 : ports.hashCode());
+ result = prime * result + tables;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!super.equals(obj)) {
+ return false;
+ }
+ if (!(obj instanceof OFFeaturesReply)) {
+ return false;
+ }
+ OFFeaturesReply other = (OFFeaturesReply) obj;
+ if (actions != other.actions) {
+ return false;
+ }
+ if (buffers != other.buffers) {
+ return false;
+ }
+ if (capabilities != other.capabilities) {
+ return false;
+ }
+ if (datapathId != other.datapathId) {
+ return false;
+ }
+ if (ports == null) {
+ if (other.ports != null) {
+ return false;
+ }
+ } else if (!ports.equals(other.ports)) {
+ return false;
+ }
+ if (tables != other.tables) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/OFFeaturesRequest.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFFeaturesRequest.java
new file mode 100644
index 0000000..0a89e4f
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFFeaturesRequest.java
@@ -0,0 +1,36 @@
+/**
+* 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.openflow.util.U16;
+
+
+/**
+ * Represents a features request message
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ *
+ */
+public class OFFeaturesRequest extends OFMessage {
+ public static int MINIMUM_LENGTH = 8;
+
+ public OFFeaturesRequest() {
+ super();
+ this.type = OFType.FEATURES_REQUEST;
+ this.length = U16.t(MINIMUM_LENGTH);
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/OFFlowMod.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFFlowMod.java
new file mode 100644
index 0000000..51b5de3
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFFlowMod.java
@@ -0,0 +1,388 @@
+/**
+* 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.LinkedList;
+import java.util.List;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.openflow.protocol.action.OFAction;
+import org.openflow.protocol.factory.OFActionFactory;
+import org.openflow.protocol.factory.OFActionFactoryAware;
+import org.openflow.util.U16;
+
+/**
+ * Represents an ofp_flow_mod message
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ *
+ */
+public class OFFlowMod extends OFMessage implements OFActionFactoryAware, Cloneable {
+ public static int MINIMUM_LENGTH = 72;
+
+ public static final short OFPFC_ADD = 0; /* New flow. */
+ public static final short OFPFC_MODIFY = 1; /* Modify all matching flows. */
+ public static final short OFPFC_MODIFY_STRICT = 2; /* Modify entry strictly matching wildcards */
+ public static final short OFPFC_DELETE=3; /* Delete all matching flows. */
+ public static final short OFPFC_DELETE_STRICT =4; /* Strictly match wildcards and priority. */
+
+ // Open Flow Flow Mod Flags. Use "or" operation to set multiple flags
+ public static final short OFPFF_SEND_FLOW_REM = 0x1; // 1 << 0
+ public static final short OFPFF_CHECK_OVERLAP = 0x2; // 1 << 1
+ public static final short OFPFF_EMERG = 0x4; // 1 << 2
+
+ protected OFActionFactory actionFactory;
+ protected OFMatch match;
+ protected long cookie;
+ protected short command;
+ protected short idleTimeout;
+ protected short hardTimeout;
+ protected short priority;
+ protected int bufferId;
+ protected short outPort;
+ protected short flags;
+ protected List<OFAction> actions;
+
+ public OFFlowMod() {
+ super();
+ this.type = OFType.FLOW_MOD;
+ this.length = U16.t(MINIMUM_LENGTH);
+ }
+
+ /**
+ * Get buffer_id
+ * @return
+ */
+ public int getBufferId() {
+ return this.bufferId;
+ }
+
+ /**
+ * Set buffer_id
+ * @param bufferId
+ */
+ public OFFlowMod setBufferId(int bufferId) {
+ this.bufferId = bufferId;
+ return this;
+ }
+
+ /**
+ * Get cookie
+ * @return
+ */
+ public long getCookie() {
+ return this.cookie;
+ }
+
+ /**
+ * Set cookie
+ * @param cookie
+ */
+ public OFFlowMod setCookie(long cookie) {
+ this.cookie = cookie;
+ return this;
+ }
+
+ /**
+ * Get command
+ * @return
+ */
+ public short getCommand() {
+ return this.command;
+ }
+
+ /**
+ * Set command
+ * @param command
+ */
+ public OFFlowMod setCommand(short command) {
+ this.command = command;
+ return this;
+ }
+
+ /**
+ * Get flags
+ * @return
+ */
+ public short getFlags() {
+ return this.flags;
+ }
+
+ /**
+ * Set flags
+ * @param flags
+ */
+ public OFFlowMod setFlags(short flags) {
+ this.flags = flags;
+ return this;
+ }
+
+ /**
+ * Get hard_timeout
+ * @return
+ */
+ public short getHardTimeout() {
+ return this.hardTimeout;
+ }
+
+ /**
+ * Set hard_timeout
+ * @param hardTimeout
+ */
+ public OFFlowMod setHardTimeout(short hardTimeout) {
+ this.hardTimeout = hardTimeout;
+ return this;
+ }
+
+ /**
+ * Get idle_timeout
+ * @return
+ */
+ public short getIdleTimeout() {
+ return this.idleTimeout;
+ }
+
+ /**
+ * Set idle_timeout
+ * @param idleTimeout
+ */
+ public OFFlowMod setIdleTimeout(short idleTimeout) {
+ this.idleTimeout = idleTimeout;
+ return this;
+ }
+
+ /**
+ * Gets a copy of the OFMatch object for this FlowMod, changes to this
+ * object do not modify the FlowMod
+ * @return
+ */
+ public OFMatch getMatch() {
+ return this.match;
+ }
+
+ /**
+ * Set match
+ * @param match
+ */
+ public OFFlowMod setMatch(OFMatch match) {
+ this.match = match;
+ return this;
+ }
+
+ /**
+ * Get out_port
+ * @return
+ */
+ public short getOutPort() {
+ return this.outPort;
+ }
+
+ /**
+ * Set out_port
+ * @param outPort
+ */
+ public OFFlowMod setOutPort(short outPort) {
+ this.outPort = outPort;
+ return this;
+ }
+
+ /**
+ * Set out_port
+ * @param port
+ */
+ public OFFlowMod setOutPort(OFPort port) {
+ this.outPort = port.getValue();
+ return this;
+ }
+
+ /**
+ * Get priority
+ * @return
+ */
+ public short getPriority() {
+ return this.priority;
+ }
+
+ /**
+ * Set priority
+ * @param priority
+ */
+ public OFFlowMod setPriority(short priority) {
+ this.priority = priority;
+ return this;
+ }
+
+ /**
+ * Returns read-only copies of the actions contained in this Flow Mod
+ * @return a list of ordered OFAction objects
+ */
+ public List<OFAction> getActions() {
+ return this.actions;
+ }
+
+ /**
+ * Sets the list of actions this Flow Mod contains
+ * @param actions a list of ordered OFAction objects
+ */
+ public OFFlowMod setActions(List<OFAction> actions) {
+ this.actions = actions;
+ return this;
+ }
+
+ @Override
+ public void readFrom(ChannelBuffer data) {
+ super.readFrom(data);
+ if (this.match == null)
+ this.match = new OFMatch();
+ this.match.readFrom(data);
+ this.cookie = data.readLong();
+ this.command = data.readShort();
+ this.idleTimeout = data.readShort();
+ this.hardTimeout = data.readShort();
+ this.priority = data.readShort();
+ this.bufferId = data.readInt();
+ this.outPort = data.readShort();
+ this.flags = data.readShort();
+ if (this.actionFactory == null)
+ throw new RuntimeException("OFActionFactory not set");
+ this.actions = this.actionFactory.parseActions(data, getLengthU() -
+ MINIMUM_LENGTH);
+ }
+
+ @Override
+ public void writeTo(ChannelBuffer data) {
+ super.writeTo(data);
+ this.match.writeTo(data);
+ data.writeLong(cookie);
+ data.writeShort(command);
+ data.writeShort(idleTimeout);
+ data.writeShort(hardTimeout);
+ data.writeShort(priority);
+ data.writeInt(bufferId);
+ data.writeShort(outPort);
+ data.writeShort(flags);
+ if (actions != null) {
+ for (OFAction action : actions) {
+ action.writeTo(data);
+ }
+ }
+ }
+
+ @Override
+ public void setActionFactory(OFActionFactory actionFactory) {
+ this.actionFactory = actionFactory;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 227;
+ int result = super.hashCode();
+ result = prime * result + ((actions == null) ? 0 : actions.hashCode());
+ result = prime * result + bufferId;
+ result = prime * result + command;
+ result = prime * result + (int) (cookie ^ (cookie >>> 32));
+ result = prime * result + flags;
+ result = prime * result + hardTimeout;
+ result = prime * result + idleTimeout;
+ result = prime * result + ((match == null) ? 0 : match.hashCode());
+ result = prime * result + outPort;
+ result = prime * result + priority;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!super.equals(obj)) {
+ return false;
+ }
+ if (!(obj instanceof OFFlowMod)) {
+ return false;
+ }
+ OFFlowMod other = (OFFlowMod) obj;
+ if (actions == null) {
+ if (other.actions != null) {
+ return false;
+ }
+ } else if (!actions.equals(other.actions)) {
+ return false;
+ }
+ if (bufferId != other.bufferId) {
+ return false;
+ }
+ if (command != other.command) {
+ return false;
+ }
+ if (cookie != other.cookie) {
+ return false;
+ }
+ if (flags != other.flags) {
+ return false;
+ }
+ if (hardTimeout != other.hardTimeout) {
+ return false;
+ }
+ if (idleTimeout != other.idleTimeout) {
+ return false;
+ }
+ if (match == null) {
+ if (other.match != null) {
+ return false;
+ }
+ } else if (!match.equals(other.match)) {
+ return false;
+ }
+ if (outPort != other.outPort) {
+ return false;
+ }
+ if (priority != other.priority) {
+ return false;
+ }
+ return true;
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#clone()
+ */
+ @Override
+ public OFFlowMod clone() throws CloneNotSupportedException {
+ OFMatch neoMatch = match.clone();
+ OFFlowMod flowMod= (OFFlowMod) super.clone();
+ flowMod.setMatch(neoMatch);
+ List<OFAction> neoActions = new LinkedList<OFAction>();
+ for(OFAction action: this.actions)
+ neoActions.add((OFAction) action.clone());
+ flowMod.setActions(neoActions);
+ return flowMod;
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return "OFFlowMod [actionFactory=" + actionFactory + ", actions="
+ + actions + ", bufferId=" + bufferId + ", command=" + command
+ + ", cookie=" + cookie + ", flags=" + flags + ", hardTimeout="
+ + hardTimeout + ", idleTimeout=" + idleTimeout + ", match="
+ + match + ", outPort=" + outPort + ", priority=" + priority
+ + ", length=" + length + ", type=" + type + ", version="
+ + version + ", xid=" + xid + "]";
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/OFFlowRemoved.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFFlowRemoved.java
new file mode 100644
index 0000000..4f4e0c7
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFFlowRemoved.java
@@ -0,0 +1,290 @@
+/**
+* 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.jboss.netty.buffer.ChannelBuffer;
+import org.openflow.util.U16;
+
+/**
+ * Represents an ofp_flow_removed message
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ *
+ */
+public class OFFlowRemoved extends OFMessage {
+ public static int MINIMUM_LENGTH = 88;
+
+ public enum OFFlowRemovedReason {
+ OFPRR_IDLE_TIMEOUT,
+ OFPRR_HARD_TIMEOUT,
+ OFPRR_DELETE
+ }
+
+ protected OFMatch match;
+ protected long cookie;
+ protected short priority;
+ protected OFFlowRemovedReason reason;
+ protected int durationSeconds;
+ protected int durationNanoseconds;
+ protected short idleTimeout;
+ protected long packetCount;
+ protected long byteCount;
+
+ public OFFlowRemoved() {
+ super();
+ this.type = OFType.FLOW_REMOVED;
+ this.length = U16.t(MINIMUM_LENGTH);
+ }
+
+ /**
+ * Get cookie
+ * @return
+ */
+ public long getCookie() {
+ return this.cookie;
+ }
+
+ /**
+ * Set cookie
+ * @param cookie
+ */
+ public void setCookie(long cookie) {
+ this.cookie = cookie;
+ }
+
+ /**
+ * Get idle_timeout
+ * @return
+ */
+ public short getIdleTimeout() {
+ return this.idleTimeout;
+ }
+
+ /**
+ * Set idle_timeout
+ * @param idleTimeout
+ */
+ public void setIdleTimeout(short idleTimeout) {
+ this.idleTimeout = idleTimeout;
+ }
+
+ /**
+ * Gets a copy of the OFMatch object for this FlowMod, changes to this
+ * object do not modify the FlowMod
+ * @return
+ */
+ public OFMatch getMatch() {
+ return this.match;
+ }
+
+ /**
+ * Set match
+ * @param match
+ */
+ public void setMatch(OFMatch match) {
+ this.match = match;
+ }
+
+ /**
+ * Get priority
+ * @return
+ */
+ public short getPriority() {
+ return this.priority;
+ }
+
+ /**
+ * Set priority
+ * @param priority
+ */
+ public void setPriority(short priority) {
+ this.priority = priority;
+ }
+
+ /**
+ * @return the reason
+ */
+ public OFFlowRemovedReason getReason() {
+ return reason;
+ }
+
+ /**
+ * @param reason the reason to set
+ */
+ public void setReason(OFFlowRemovedReason reason) {
+ this.reason = reason;
+ }
+
+ /**
+ * @return the durationSeconds
+ */
+ public int getDurationSeconds() {
+ return durationSeconds;
+ }
+
+ /**
+ * @param durationSeconds the durationSeconds to set
+ */
+ public void setDurationSeconds(int durationSeconds) {
+ this.durationSeconds = durationSeconds;
+ }
+
+ /**
+ * @return the durationNanoseconds
+ */
+ public int getDurationNanoseconds() {
+ return durationNanoseconds;
+ }
+
+ /**
+ * @param durationNanoseconds the durationNanoseconds to set
+ */
+ public void setDurationNanoseconds(int durationNanoseconds) {
+ this.durationNanoseconds = durationNanoseconds;
+ }
+
+ /**
+ * @return the packetCount
+ */
+ public long getPacketCount() {
+ return packetCount;
+ }
+
+ /**
+ * @param packetCount the packetCount to set
+ */
+ public void setPacketCount(long packetCount) {
+ this.packetCount = packetCount;
+ }
+
+ /**
+ * @return the byteCount
+ */
+ public long getByteCount() {
+ return byteCount;
+ }
+
+ /**
+ * @param byteCount the byteCount to set
+ */
+ public void setByteCount(long byteCount) {
+ this.byteCount = byteCount;
+ }
+
+ @Override
+ public void readFrom(ChannelBuffer data) {
+ super.readFrom(data);
+ if (this.match == null)
+ this.match = new OFMatch();
+ this.match.readFrom(data);
+ this.cookie = data.readLong();
+ this.priority = data.readShort();
+ this.reason = OFFlowRemovedReason.values()[(0xff & data.readByte())];
+ data.readByte(); // pad
+ this.durationSeconds = data.readInt();
+ this.durationNanoseconds = data.readInt();
+ this.idleTimeout = data.readShort();
+ data.readByte(); // pad
+ data.readByte(); // pad
+ this.packetCount = data.readLong();
+ this.byteCount = data.readLong();
+ }
+
+ @Override
+ public void writeTo(ChannelBuffer data) {
+ super.writeTo(data);
+ this.match.writeTo(data);
+ data.writeLong(cookie);
+ data.writeShort(priority);
+ data.writeByte((byte) this.reason.ordinal());
+ data.writeByte((byte) 0);
+ data.writeInt(this.durationSeconds);
+ data.writeInt(this.durationNanoseconds);
+ data.writeShort(idleTimeout);
+ data.writeByte((byte) 0); // pad
+ data.writeByte((byte) 0); // pad
+ data.writeLong(this.packetCount);
+ data.writeLong(this.byteCount);
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 271;
+ int result = super.hashCode();
+ result = prime * result + (int) (byteCount ^ (byteCount >>> 32));
+ result = prime * result + (int) (cookie ^ (cookie >>> 32));
+ result = prime * result + durationNanoseconds;
+ result = prime * result + durationSeconds;
+ result = prime * result + idleTimeout;
+ result = prime * result + ((match == null) ? 0 : match.hashCode());
+ result = prime * result + (int) (packetCount ^ (packetCount >>> 32));
+ result = prime * result + priority;
+ result = prime * result + ((reason == null) ? 0 : reason.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!super.equals(obj)) {
+ return false;
+ }
+ if (!(obj instanceof OFFlowRemoved)) {
+ return false;
+ }
+ OFFlowRemoved other = (OFFlowRemoved) obj;
+ if (byteCount != other.byteCount) {
+ return false;
+ }
+ if (cookie != other.cookie) {
+ return false;
+ }
+ if (durationNanoseconds != other.durationNanoseconds) {
+ return false;
+ }
+ if (durationSeconds != other.durationSeconds) {
+ return false;
+ }
+ if (idleTimeout != other.idleTimeout) {
+ return false;
+ }
+ if (match == null) {
+ if (other.match != null) {
+ return false;
+ }
+ } else if (!match.equals(other.match)) {
+ return false;
+ }
+ if (packetCount != other.packetCount) {
+ return false;
+ }
+ if (priority != other.priority) {
+ return false;
+ }
+ if (reason == null) {
+ if (other.reason != null) {
+ return false;
+ }
+ } else if (!reason.equals(other.reason)) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/OFGetConfigReply.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFGetConfigReply.java
new file mode 100644
index 0000000..257867a
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFGetConfigReply.java
@@ -0,0 +1,29 @@
+/**
+* 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;
+
+/**
+ * Represents an OFPT_GET_CONFIG_REPLY type message
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class OFGetConfigReply extends OFSwitchConfig {
+ public OFGetConfigReply() {
+ super();
+ this.type = OFType.GET_CONFIG_REPLY;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/OFGetConfigRequest.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFGetConfigRequest.java
new file mode 100644
index 0000000..85c7499
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFGetConfigRequest.java
@@ -0,0 +1,32 @@
+/**
+* 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.openflow.util.U16;
+
+/**
+ * Represents an OFPT_GET_CONFIG_REQUEST type message
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class OFGetConfigRequest extends OFMessage {
+ public OFGetConfigRequest() {
+ super();
+ this.type = OFType.GET_CONFIG_REQUEST;
+ this.length = U16.t(OFMessage.MINIMUM_LENGTH);
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/OFHello.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFHello.java
new file mode 100644
index 0000000..e702ca4
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFHello.java
@@ -0,0 +1,39 @@
+/**
+* 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.openflow.util.U16;
+
+
+/**
+ * Represents an ofp_hello message
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu) - Feb 8, 2010
+ */
+public class OFHello extends OFMessage {
+ public static int MINIMUM_LENGTH = 8;
+
+ /**
+ * Construct a ofp_hello message
+ */
+ public OFHello() {
+ super();
+ this.type = OFType.HELLO;
+ this.length = U16.t(MINIMUM_LENGTH);
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/OFMatch.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFMatch.java
new file mode 100644
index 0000000..0336d7d
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFMatch.java
@@ -0,0 +1,945 @@
+/**
+* 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.io.Serializable;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+
+import org.codehaus.jackson.map.annotate.JsonSerialize;
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.openflow.protocol.serializers.OFMatchJSONSerializer;
+import org.openflow.util.HexString;
+import org.openflow.util.U16;
+import org.openflow.util.U8;
+
+/**
+ * Represents an ofp_match structure
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ * @author Rob Sherwood (rob.sherwood@stanford.edu)
+ *
+ */
+@JsonSerialize(using=OFMatchJSONSerializer.class)
+public class OFMatch implements Cloneable, Serializable {
+ /**
+ *
+ */
+ private static final long serialVersionUID = 1L;
+ public static int MINIMUM_LENGTH = 40;
+ final public static int OFPFW_ALL = ((1 << 22) - 1);
+
+ final public static int OFPFW_IN_PORT = 1 << 0; /* Switch input port. */
+ final public static int OFPFW_DL_VLAN = 1 << 1; /* VLAN id. */
+ final public static int OFPFW_DL_SRC = 1 << 2; /* Ethernet source address. */
+ final public static int OFPFW_DL_DST = 1 << 3; /*
+ * Ethernet destination
+ * address.
+ */
+ final public static int OFPFW_DL_TYPE = 1 << 4; /* Ethernet frame type. */
+ final public static int OFPFW_NW_PROTO = 1 << 5; /* IP protocol. */
+ final public static int OFPFW_TP_SRC = 1 << 6; /* TCP/UDP source port. */
+ final public static int OFPFW_TP_DST = 1 << 7; /* TCP/UDP destination port. */
+
+ /*
+ * IP source address wildcard bit count. 0 is exact match, 1 ignores the
+ * LSB, 2 ignores the 2 least-significant bits, ..., 32 and higher wildcard
+ * the entire field. This is the *opposite* of the usual convention where
+ * e.g. /24 indicates that 8 bits (not 24 bits) are wildcarded.
+ */
+ final public static int OFPFW_NW_SRC_SHIFT = 8;
+ final public static int OFPFW_NW_SRC_BITS = 6;
+ final public static int OFPFW_NW_SRC_MASK = ((1 << OFPFW_NW_SRC_BITS) - 1) << OFPFW_NW_SRC_SHIFT;
+ final public static int OFPFW_NW_SRC_ALL = 32 << OFPFW_NW_SRC_SHIFT;
+
+ /* IP destination address wildcard bit count. Same format as source. */
+ final public static int OFPFW_NW_DST_SHIFT = 14;
+ final public static int OFPFW_NW_DST_BITS = 6;
+ final public static int OFPFW_NW_DST_MASK = ((1 << OFPFW_NW_DST_BITS) - 1) << OFPFW_NW_DST_SHIFT;
+ final public static int OFPFW_NW_DST_ALL = 32 << OFPFW_NW_DST_SHIFT;
+
+ final public static int OFPFW_DL_VLAN_PCP = 1 << 20; /* VLAN priority. */
+ final public static int OFPFW_NW_TOS = 1 << 21; /*
+ * IP ToS (DSCP field, 6
+ * bits).
+ */
+
+ /* List of Strings for marshalling and unmarshalling to human readable forms */
+ final public static String STR_IN_PORT = "in_port";
+ final public static String STR_DL_DST = "dl_dst";
+ final public static String STR_DL_SRC = "dl_src";
+ final public static String STR_DL_TYPE = "dl_type";
+ final public static String STR_DL_VLAN = "dl_vlan";
+ final public static String STR_DL_VLAN_PCP = "dl_vlan_pcp";
+ final public static String STR_NW_DST = "nw_dst";
+ final public static String STR_NW_SRC = "nw_src";
+ final public static String STR_NW_PROTO = "nw_proto";
+ final public static String STR_NW_TOS = "nw_tos";
+ final public static String STR_TP_DST = "tp_dst";
+ final public static String STR_TP_SRC = "tp_src";
+
+ protected int wildcards;
+ protected short inputPort;
+ protected byte[] dataLayerSource;
+ protected byte[] dataLayerDestination;
+ protected short dataLayerVirtualLan;
+ protected byte dataLayerVirtualLanPriorityCodePoint;
+ protected short dataLayerType;
+ protected byte networkTypeOfService;
+ protected byte networkProtocol;
+ protected int networkSource;
+ protected int networkDestination;
+ protected short transportSource;
+ protected short transportDestination;
+
+ /**
+ * By default, create a OFMatch that matches everything
+ *
+ * (mostly because it's the least amount of work to make a valid OFMatch)
+ */
+ public OFMatch() {
+ this.wildcards = OFPFW_ALL;
+ this.dataLayerDestination = new byte[] {0x0, 0x0, 0x0, 0x0, 0x0, 0x0};
+ this.dataLayerSource = new byte[] {0x0, 0x0, 0x0, 0x0, 0x0, 0x0};
+ this.dataLayerVirtualLan = -1;
+ this.dataLayerVirtualLanPriorityCodePoint = 0;
+ this.dataLayerType = 0;
+ this.inputPort = 0;
+ this.networkProtocol = 0;
+ this.networkTypeOfService = 0;
+ this.networkSource = 0;
+ this.networkDestination = 0;
+ this.transportDestination = 0;
+ this.transportSource = 0;
+ }
+
+ /**
+ * Get dl_dst
+ *
+ * @return an arrays of bytes
+ */
+ public byte[] getDataLayerDestination() {
+ return this.dataLayerDestination;
+ }
+
+ /**
+ * Set dl_dst
+ *
+ * @param dataLayerDestination
+ */
+ public OFMatch setDataLayerDestination(byte[] dataLayerDestination) {
+ this.dataLayerDestination = dataLayerDestination;
+ return this;
+ }
+
+ /**
+ * Set dl_dst, but first translate to byte[] using HexString
+ *
+ * @param mac
+ * A colon separated string of 6 pairs of octets, e..g.,
+ * "00:17:42:EF:CD:8D"
+ */
+ public OFMatch setDataLayerDestination(String mac) {
+ byte bytes[] = HexString.fromHexString(mac);
+ if (bytes.length != 6)
+ throw new IllegalArgumentException(
+ "expected string with 6 octets, got '" + mac + "'");
+ this.dataLayerDestination = bytes;
+ return this;
+ }
+
+ /**
+ * Get dl_src
+ *
+ * @return an array of bytes
+ */
+ public byte[] getDataLayerSource() {
+ return this.dataLayerSource;
+ }
+
+ /**
+ * Set dl_src
+ *
+ * @param dataLayerSource
+ */
+ public OFMatch setDataLayerSource(byte[] dataLayerSource) {
+ this.dataLayerSource = dataLayerSource;
+ return this;
+ }
+
+ /**
+ * Set dl_src, but first translate to byte[] using HexString
+ *
+ * @param mac
+ * A colon separated string of 6 pairs of octets, e..g.,
+ * "00:17:42:EF:CD:8D"
+ */
+ public OFMatch setDataLayerSource(String mac) {
+ byte bytes[] = HexString.fromHexString(mac);
+ if (bytes.length != 6)
+ throw new IllegalArgumentException(
+ "expected string with 6 octets, got '" + mac + "'");
+ this.dataLayerSource = bytes;
+ return this;
+ }
+
+ /**
+ * Get dl_type
+ *
+ * @return ether_type
+ */
+ public short getDataLayerType() {
+ return this.dataLayerType;
+ }
+
+ /**
+ * Set dl_type
+ *
+ * @param dataLayerType
+ */
+ public OFMatch setDataLayerType(short dataLayerType) {
+ this.dataLayerType = dataLayerType;
+ return this;
+ }
+
+ /**
+ * Get dl_vlan
+ *
+ * @return vlan tag; VLAN_NONE == no tag
+ */
+ public short getDataLayerVirtualLan() {
+ return this.dataLayerVirtualLan;
+ }
+
+ /**
+ * Set dl_vlan
+ *
+ * @param dataLayerVirtualLan
+ */
+ public OFMatch setDataLayerVirtualLan(short dataLayerVirtualLan) {
+ this.dataLayerVirtualLan = dataLayerVirtualLan;
+ return this;
+ }
+
+ /**
+ * Get dl_vlan_pcp
+ *
+ * @return
+ */
+ public byte getDataLayerVirtualLanPriorityCodePoint() {
+ return this.dataLayerVirtualLanPriorityCodePoint;
+ }
+
+ /**
+ * Set dl_vlan_pcp
+ *
+ * @param pcp
+ */
+ public OFMatch setDataLayerVirtualLanPriorityCodePoint(byte pcp) {
+ this.dataLayerVirtualLanPriorityCodePoint = pcp;
+ return this;
+ }
+
+ /**
+ * Get in_port
+ *
+ * @return
+ */
+ public short getInputPort() {
+ return this.inputPort;
+ }
+
+ /**
+ * Set in_port
+ *
+ * @param inputPort
+ */
+ public OFMatch setInputPort(short inputPort) {
+ this.inputPort = inputPort;
+ return this;
+ }
+
+ /**
+ * Get nw_dst
+ *
+ * @return
+ */
+ public int getNetworkDestination() {
+ return this.networkDestination;
+ }
+
+ /**
+ * Set nw_dst
+ *
+ * @param networkDestination
+ */
+ public OFMatch setNetworkDestination(int networkDestination) {
+ this.networkDestination = networkDestination;
+ return this;
+ }
+
+ /**
+ * Parse this match's wildcard fields and return the number of significant
+ * bits in the IP destination field.
+ *
+ * NOTE: this returns the number of bits that are fixed, i.e., like CIDR,
+ * not the number of bits that are free like OpenFlow encodes.
+ *
+ * @return a number between 0 (matches all IPs) and 63 ( 32>= implies exact
+ * match)
+ */
+ public int getNetworkDestinationMaskLen() {
+ return Math
+ .max(32 - ((wildcards & OFPFW_NW_DST_MASK) >> OFPFW_NW_DST_SHIFT),
+ 0);
+ }
+
+ /**
+ * Parse this match's wildcard fields and return the number of significant
+ * bits in the IP destination field.
+ *
+ * NOTE: this returns the number of bits that are fixed, i.e., like CIDR,
+ * not the number of bits that are free like OpenFlow encodes.
+ *
+ * @return a number between 0 (matches all IPs) and 32 (exact match)
+ */
+ public int getNetworkSourceMaskLen() {
+ return Math
+ .max(32 - ((wildcards & OFPFW_NW_SRC_MASK) >> OFPFW_NW_SRC_SHIFT),
+ 0);
+ }
+
+ /**
+ * Get nw_proto
+ *
+ * @return
+ */
+ public byte getNetworkProtocol() {
+ return this.networkProtocol;
+ }
+
+ /**
+ * Set nw_proto
+ *
+ * @param networkProtocol
+ */
+ public OFMatch setNetworkProtocol(byte networkProtocol) {
+ this.networkProtocol = networkProtocol;
+ return this;
+ }
+
+ /**
+ * Get nw_src
+ *
+ * @return
+ */
+ public int getNetworkSource() {
+ return this.networkSource;
+ }
+
+ /**
+ * Set nw_src
+ *
+ * @param networkSource
+ */
+ public OFMatch setNetworkSource(int networkSource) {
+ this.networkSource = networkSource;
+ return this;
+ }
+
+ /**
+ * Get nw_tos
+ * OFMatch stores the ToS bits as top 6-bits, so right shift by 2 bits
+ * before returning the value
+ *
+ * @return : 6-bit DSCP value (0-63)
+ */
+ public byte getNetworkTypeOfService() {
+ return (byte) ((this.networkTypeOfService >> 2) & 0x3f);
+ }
+
+ /**
+ * Set nw_tos
+ * OFMatch stores the ToS bits as top 6-bits, so left shift by 2 bits
+ * before storing the value
+ *
+ * @param networkTypeOfService : 6-bit DSCP value (0-63)
+ */
+ public OFMatch setNetworkTypeOfService(byte networkTypeOfService) {
+ this.networkTypeOfService = (byte)(networkTypeOfService << 2);
+ return this;
+ }
+
+
+ /**
+ * Get tp_dst
+ *
+ * @return
+ */
+ public short getTransportDestination() {
+ return this.transportDestination;
+ }
+
+ /**
+ * Set tp_dst
+ *
+ * @param transportDestination
+ */
+ public OFMatch setTransportDestination(short transportDestination) {
+ this.transportDestination = transportDestination;
+ return this;
+ }
+
+ /**
+ * Get tp_src
+ *
+ * @return
+ */
+ public short getTransportSource() {
+ return this.transportSource;
+ }
+
+ /**
+ * Set tp_src
+ *
+ * @param transportSource
+ */
+ public OFMatch setTransportSource(short transportSource) {
+ this.transportSource = transportSource;
+ return this;
+ }
+
+ /**
+ * Get wildcards
+ *
+ * @return
+ */
+ public int getWildcards() {
+ return this.wildcards;
+ }
+
+ /**
+ * Set wildcards
+ *
+ * @param wildcards
+ */
+ public OFMatch setWildcards(int wildcards) {
+ this.wildcards = wildcards;
+ return this;
+ }
+
+ /**
+ * Initializes this OFMatch structure with the corresponding data from the
+ * specified packet.
+ *
+ * Must specify the input port, to ensure that this.in_port is set
+ * correctly.
+ *
+ * Specify OFPort.NONE or OFPort.ANY if input port not applicable or
+ * available
+ *
+ * @param packetData
+ * The packet's data
+ * @param inputPort
+ * the port the packet arrived on
+ */
+ public OFMatch loadFromPacket(byte[] packetData, short inputPort) {
+ short scratch;
+ int transportOffset = 34;
+ ByteBuffer packetDataBB = ByteBuffer.wrap(packetData);
+ int limit = packetDataBB.limit();
+
+ this.wildcards = 0; // all fields have explicit entries
+
+ this.inputPort = inputPort;
+
+ if (inputPort == OFPort.OFPP_ALL.getValue())
+ this.wildcards |= OFPFW_IN_PORT;
+
+ assert (limit >= 14);
+ // dl dst
+ this.dataLayerDestination = new byte[6];
+ packetDataBB.get(this.dataLayerDestination);
+ // dl src
+ this.dataLayerSource = new byte[6];
+ packetDataBB.get(this.dataLayerSource);
+ // dl type
+ this.dataLayerType = packetDataBB.getShort();
+
+ if (getDataLayerType() != (short) 0x8100) { // need cast to avoid signed
+ // bug
+ setDataLayerVirtualLan((short) 0xffff);
+ setDataLayerVirtualLanPriorityCodePoint((byte) 0);
+ } else {
+ // has vlan tag
+ scratch = packetDataBB.getShort();
+ setDataLayerVirtualLan((short) (0xfff & scratch));
+ setDataLayerVirtualLanPriorityCodePoint((byte) ((0xe000 & scratch) >> 13));
+ this.dataLayerType = packetDataBB.getShort();
+ }
+
+ switch (getDataLayerType()) {
+ case 0x0800:
+ // ipv4
+ // check packet length
+ scratch = packetDataBB.get();
+ scratch = (short) (0xf & scratch);
+ transportOffset = (packetDataBB.position() - 1) + (scratch * 4);
+ // nw tos (dscp)
+ scratch = packetDataBB.get();
+ setNetworkTypeOfService((byte) ((0xfc & scratch) >> 2));
+ // nw protocol
+ packetDataBB.position(packetDataBB.position() + 7);
+ this.networkProtocol = packetDataBB.get();
+ // nw src
+ packetDataBB.position(packetDataBB.position() + 2);
+ this.networkSource = packetDataBB.getInt();
+ // nw dst
+ this.networkDestination = packetDataBB.getInt();
+ packetDataBB.position(transportOffset);
+ break;
+ case 0x0806:
+ // arp
+ int arpPos = packetDataBB.position();
+ // opcode
+ scratch = packetDataBB.getShort(arpPos + 6);
+ setNetworkProtocol((byte) (0xff & scratch));
+
+ scratch = packetDataBB.getShort(arpPos + 2);
+ // if ipv4 and addr len is 4
+ if (scratch == 0x800 && packetDataBB.get(arpPos + 5) == 4) {
+ // nw src
+ this.networkSource = packetDataBB.getInt(arpPos + 14);
+ // nw dst
+ this.networkDestination = packetDataBB.getInt(arpPos + 24);
+ } else {
+ setNetworkSource(0);
+ setNetworkDestination(0);
+ }
+ break;
+ default:
+ setNetworkTypeOfService((byte) 0);
+ setNetworkProtocol((byte) 0);
+ setNetworkSource(0);
+ setNetworkDestination(0);
+ break;
+ }
+
+ switch (getNetworkProtocol()) {
+ case 0x01:
+ // icmp
+ // type
+ this.transportSource = U8.f(packetDataBB.get());
+ // code
+ this.transportDestination = U8.f(packetDataBB.get());
+ break;
+ case 0x06:
+ // tcp
+ // tcp src
+ this.transportSource = packetDataBB.getShort();
+ // tcp dest
+ this.transportDestination = packetDataBB.getShort();
+ break;
+ case 0x11:
+ // udp
+ // udp src
+ this.transportSource = packetDataBB.getShort();
+ // udp dest
+ this.transportDestination = packetDataBB.getShort();
+ break;
+ default:
+ setTransportDestination((short) 0);
+ setTransportSource((short) 0);
+ break;
+ }
+ return this;
+ }
+
+ /**
+ * Read this message off the wire from the specified ByteBuffer
+ *
+ * @param data
+ */
+ public void readFrom(ChannelBuffer data) {
+ this.wildcards = data.readInt();
+ this.inputPort = data.readShort();
+ this.dataLayerSource = new byte[6];
+ data.readBytes(this.dataLayerSource);
+ this.dataLayerDestination = new byte[6];
+ data.readBytes(this.dataLayerDestination);
+ this.dataLayerVirtualLan = data.readShort();
+ this.dataLayerVirtualLanPriorityCodePoint = data.readByte();
+ data.readByte(); // pad
+ this.dataLayerType = data.readShort();
+ this.networkTypeOfService = data.readByte();
+ this.networkProtocol = data.readByte();
+ data.readByte(); // pad
+ data.readByte(); // pad
+ this.networkSource = data.readInt();
+ this.networkDestination = data.readInt();
+ this.transportSource = data.readShort();
+ this.transportDestination = data.readShort();
+ }
+
+ /**
+ * Write this message's binary format to the specified ByteBuffer
+ *
+ * @param data
+ */
+ public void writeTo(ChannelBuffer data) {
+ data.writeInt(wildcards);
+ data.writeShort(inputPort);
+ data.writeBytes(this.dataLayerSource);
+ data.writeBytes(this.dataLayerDestination);
+ data.writeShort(dataLayerVirtualLan);
+ data.writeByte(dataLayerVirtualLanPriorityCodePoint);
+ data.writeByte((byte) 0x0); // pad
+ data.writeShort(dataLayerType);
+ data.writeByte(networkTypeOfService);
+ data.writeByte(networkProtocol);
+ data.writeByte((byte) 0x0); // pad
+ data.writeByte((byte) 0x0); // pad
+ data.writeInt(networkSource);
+ data.writeInt(networkDestination);
+ data.writeShort(transportSource);
+ data.writeShort(transportDestination);
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 131;
+ int result = 1;
+ result = prime * result + Arrays.hashCode(dataLayerDestination);
+ result = prime * result + Arrays.hashCode(dataLayerSource);
+ result = prime * result + dataLayerType;
+ result = prime * result + dataLayerVirtualLan;
+ result = prime * result + dataLayerVirtualLanPriorityCodePoint;
+ result = prime * result + inputPort;
+ result = prime * result + networkDestination;
+ result = prime * result + networkProtocol;
+ result = prime * result + networkSource;
+ result = prime * result + networkTypeOfService;
+ result = prime * result + transportDestination;
+ result = prime * result + transportSource;
+ result = prime * result + wildcards;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (!(obj instanceof OFMatch)) {
+ return false;
+ }
+ OFMatch other = (OFMatch) obj;
+ if (!Arrays.equals(dataLayerDestination, other.dataLayerDestination)) {
+ return false;
+ }
+ if (!Arrays.equals(dataLayerSource, other.dataLayerSource)) {
+ return false;
+ }
+ if (dataLayerType != other.dataLayerType) {
+ return false;
+ }
+ if (dataLayerVirtualLan != other.dataLayerVirtualLan) {
+ return false;
+ }
+ if (dataLayerVirtualLanPriorityCodePoint != other.dataLayerVirtualLanPriorityCodePoint) {
+ return false;
+ }
+ if (inputPort != other.inputPort) {
+ return false;
+ }
+ if (networkDestination != other.networkDestination) {
+ return false;
+ }
+ if (networkProtocol != other.networkProtocol) {
+ return false;
+ }
+ if (networkSource != other.networkSource) {
+ return false;
+ }
+ if (networkTypeOfService != other.networkTypeOfService) {
+ return false;
+ }
+ if (transportDestination != other.transportDestination) {
+ return false;
+ }
+ if (transportSource != other.transportSource) {
+ return false;
+ }
+ if ((wildcards & OFMatch.OFPFW_ALL) != (other.wildcards & OFPFW_ALL)) { // only
+ // consider
+ // allocated
+ // part
+ // of
+ // wildcards
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Implement clonable interface
+ */
+ @Override
+ public OFMatch clone() {
+ try {
+ OFMatch ret = (OFMatch) super.clone();
+ ret.dataLayerDestination = this.dataLayerDestination.clone();
+ ret.dataLayerSource = this.dataLayerSource.clone();
+ return ret;
+ } catch (CloneNotSupportedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Output a dpctl-styled string, i.e., only list the elements that are not
+ * wildcarded
+ *
+ * A match-everything OFMatch outputs "OFMatch[]"
+ *
+ * @return
+ * "OFMatch[dl_src:00:20:01:11:22:33,nw_src:192.168.0.0/24,tp_dst:80]"
+ */
+ @Override
+ public String toString() {
+ String str = "";
+
+ // 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 + "="
+ + HexString.toHexString(this.dataLayerDestination);
+ 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 + "=" + this.networkProtocol;
+ if ((wildcards & OFPFW_NW_TOS) == 0)
+ str += "," + STR_NW_TOS + "=" + this.getNetworkTypeOfService();
+
+ // l4
+ if ((wildcards & OFPFW_TP_DST) == 0)
+ str += "," + STR_TP_DST + "=" + this.transportDestination;
+ if ((wildcards & OFPFW_TP_SRC) == 0)
+ str += "," + STR_TP_SRC + "=" + this.transportSource;
+ if ((str.length() > 0) && (str.charAt(0) == ','))
+ str = str.substring(1); // trim the leading ","
+ // done
+ return "OFMatch[" + str + "]";
+ }
+
+ private String cidrToString(int ip, int prefix) {
+ String str;
+ if (prefix >= 32) {
+ str = ipToString(ip);
+ } else {
+ // use the negation of mask to fake endian magic
+ int mask = ~((1 << (32 - prefix)) - 1);
+ str = ipToString(ip & mask) + "/" + prefix;
+ }
+
+ return str;
+ }
+
+ /**
+ * Set this OFMatch'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","eth_src", "dl_dst","eth_dst"
+ * <TD>hex-string
+ * </TR>
+ * <TR>
+ * <TD>"dl_type", "dl_vlan", "dl_vlan_pcp"
+ * <TD>integer
+ * </TR>
+ * <TR>
+ * <TD>"nw_src", "nw_dst", "ip_src", "ip_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,ip_dst=192.168.0.0/16,tp_src=80"
+ * @throws IllegalArgumentException
+ * on unexpected key or value
+ */
+
+ public void fromString(String match) throws IllegalArgumentException {
+ if (match.equals("") || match.equalsIgnoreCase("any")
+ || match.equalsIgnoreCase("all") || match.equals("[]"))
+ match = "OFMatch[]";
+ String[] tokens = match.split("[\\[,\\]]");
+ String[] values;
+ int initArg = 0;
+ if (tokens[0].equals("OFMatch"))
+ initArg = 1;
+ this.wildcards = OFPFW_ALL;
+ int i;
+ for (i = initArg; i < tokens.length; i++) {
+ values = tokens[i].split("=");
+ if (values.length != 2)
+ throw new IllegalArgumentException("Token " + tokens[i]
+ + " does not have form 'key=value' parsing " + match);
+ values[0] = values[0].toLowerCase(); // try to make this case insens
+ if (values[0].equals(STR_IN_PORT) || values[0].equals("input_port")) {
+ this.inputPort = U16.t(Integer.valueOf(values[1]));
+ this.wildcards &= ~OFPFW_IN_PORT;
+ } else if (values[0].equals(STR_DL_DST) || values[0].equals("eth_dst")) {
+ this.dataLayerDestination = HexString.fromHexString(values[1]);
+ this.wildcards &= ~OFPFW_DL_DST;
+ } else if (values[0].equals(STR_DL_SRC) || values[0].equals("eth_src")) {
+ this.dataLayerSource = HexString.fromHexString(values[1]);
+ this.wildcards &= ~OFPFW_DL_SRC;
+ } else if (values[0].equals(STR_DL_TYPE) || values[0].equals("eth_type")) {
+ if (values[1].startsWith("0x"))
+ this.dataLayerType = U16.t(Integer.valueOf(
+ values[1].replaceFirst("0x", ""), 16));
+ else
+ this.dataLayerType = U16.t(Integer.valueOf(values[1]));
+ this.wildcards &= ~OFPFW_DL_TYPE;
+ } else if (values[0].equals(STR_DL_VLAN)) {
+ if (values[1].startsWith("0x"))
+ this.dataLayerVirtualLan = U16.t(Integer.valueOf(
+ values[1].replaceFirst("0x", ""),16));
+ else
+ this.dataLayerVirtualLan = U16.t(Integer.valueOf(values[1]));
+ this.wildcards &= ~OFPFW_DL_VLAN;
+ } else if (values[0].equals(STR_DL_VLAN_PCP)) {
+ this.dataLayerVirtualLanPriorityCodePoint = U8.t(Short
+ .valueOf(values[1]));
+ this.wildcards &= ~OFPFW_DL_VLAN_PCP;
+ } else if (values[0].equals(STR_NW_DST) || values[0].equals("ip_dst")) {
+ setFromCIDR(values[1], STR_NW_DST);
+ } else if (values[0].equals(STR_NW_SRC) || values[0].equals("ip_src")) {
+ setFromCIDR(values[1], STR_NW_SRC);
+ } else if (values[0].equals(STR_NW_PROTO)) {
+ this.networkProtocol = U8.t(Short.valueOf(values[1]));
+ this.wildcards &= ~OFPFW_NW_PROTO;
+ } else if (values[0].equals(STR_NW_TOS)) {
+ this.setNetworkTypeOfService(U8.t(Short.valueOf(values[1])));
+ this.wildcards &= ~OFPFW_NW_TOS;
+ } else if (values[0].equals(STR_TP_DST)) {
+ this.transportDestination = U16.t(Integer.valueOf(values[1]));
+ this.wildcards &= ~OFPFW_TP_DST;
+ } else if (values[0].equals(STR_TP_SRC)) {
+ this.transportSource = U16.t(Integer.valueOf(values[1]));
+ this.wildcards &= ~OFPFW_TP_SRC;
+ } else {
+ throw new IllegalArgumentException("unknown token " + tokens[i]
+ + " parsing " + match);
+ }
+ }
+ }
+
+ /**
+ * Set the networkSource or networkDestionation address and their wildcards
+ * from the CIDR string
+ *
+ * @param cidr
+ * "192.168.0.0/16" or "172.16.1.5"
+ * @param which
+ * one of STR_NW_DST or STR_NW_SRC
+ * @throws IllegalArgumentException
+ */
+ private void setFromCIDR(String cidr, String which)
+ throws IllegalArgumentException {
+ String values[] = cidr.split("/");
+ String[] ip_str = values[0].split("\\.");
+ int ip = 0;
+ ip += Integer.valueOf(ip_str[0]) << 24;
+ ip += Integer.valueOf(ip_str[1]) << 16;
+ ip += Integer.valueOf(ip_str[2]) << 8;
+ ip += Integer.valueOf(ip_str[3]);
+ int prefix = 32; // all bits are fixed, by default
+
+ if (values.length >= 2)
+ prefix = Integer.valueOf(values[1]);
+ int mask = 32 - prefix;
+ if (which.equals(STR_NW_DST)) {
+ this.networkDestination = ip;
+ this.wildcards = (wildcards & ~OFPFW_NW_DST_MASK)
+ | (mask << OFPFW_NW_DST_SHIFT);
+ } else if (which.equals(STR_NW_SRC)) {
+ this.networkSource = ip;
+ this.wildcards = (wildcards & ~OFPFW_NW_SRC_MASK)
+ | (mask << OFPFW_NW_SRC_SHIFT);
+ }
+ }
+
+ protected static String ipToString(int ip) {
+ return Integer.toString(U8.f((byte) ((ip & 0xff000000) >> 24))) + "."
+ + Integer.toString((ip & 0x00ff0000) >> 16) + "."
+ + Integer.toString((ip & 0x0000ff00) >> 8) + "."
+ + Integer.toString(ip & 0x000000ff);
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/OFMatchBeanInfo.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFMatchBeanInfo.java
new file mode 100644
index 0000000..16a813f
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFMatchBeanInfo.java
@@ -0,0 +1,106 @@
+/**
+* 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.beans.IntrospectionException;
+import java.beans.PropertyDescriptor;
+import java.beans.SimpleBeanInfo;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Extra info for how to treat OFMatch as a JavaBean
+ *
+ * For some (inane!) reason, using chained setters in OFMatch breaks a lot of the JavaBean defaults.
+ *
+ * We don't really use OFMatch as a java bean, but there are a lot of nice XML utils that work for
+ * free if OFMatch follows the java bean paradigm.
+ *
+ * @author Rob Sherwood (rob.sherwood@stanford.edu)
+ *
+ */
+
+public class OFMatchBeanInfo extends SimpleBeanInfo {
+
+ @Override
+ public PropertyDescriptor[] getPropertyDescriptors() {
+ List<PropertyDescriptor> descs = new LinkedList<PropertyDescriptor>();
+ Field[] fields = OFMatch.class.getDeclaredFields();
+ String name;
+ for (int i=0; i< fields.length; i++) {
+ int mod = fields[i].getModifiers();
+ if(Modifier.isFinal(mod) || // don't expose static or final fields
+ Modifier.isStatic(mod))
+ continue;
+
+ name = fields[i].getName();
+ Class<?> type = fields[i].getType();
+
+ try {
+ descs.add(new PropertyDescriptor(name,
+ name2getter(OFMatch.class, name),
+ name2setter(OFMatch.class, name, type)));
+ } catch (IntrospectionException e) {
+ e.printStackTrace();
+ throw new RuntimeException(e);
+ }
+ }
+
+ return descs.toArray(new PropertyDescriptor[0]);
+ }
+
+
+ private Method name2setter(Class<OFMatch> c, String name, Class<?> type) {
+ String mName = "set" + toLeadingCaps(name);
+ Method m = null;
+ try {
+ m = c.getMethod(mName, new Class[]{ type});
+ } catch (SecurityException e) {
+ e.printStackTrace();
+ throw new RuntimeException(e);
+ } catch (NoSuchMethodException e) {
+ e.printStackTrace();
+ throw new RuntimeException(e);
+ }
+ return m;
+ }
+
+ private Method name2getter(Class<OFMatch> c, String name) {
+ String mName= "get" + toLeadingCaps(name);
+ Method m = null;
+ try {
+ m = c.getMethod(mName, new Class[]{});
+ } catch (SecurityException e) {
+ e.printStackTrace();
+ throw new RuntimeException(e);
+ } catch (NoSuchMethodException e) {
+ e.printStackTrace();
+ throw new RuntimeException(e);
+ }
+ return m;
+ }
+
+ private String toLeadingCaps(String s) {
+ char[] array = s.toCharArray();
+ array[0] = Character.toUpperCase(array[0]);
+ return String.valueOf(array, 0, array.length);
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/OFMatchWithSwDpid.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFMatchWithSwDpid.java
new file mode 100644
index 0000000..052a652
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFMatchWithSwDpid.java
@@ -0,0 +1,39 @@
+package org.openflow.protocol;
+
+import org.openflow.util.HexString;
+
+public class OFMatchWithSwDpid {
+ protected OFMatch ofMatch;
+ protected long switchDataPathId;
+
+ public OFMatchWithSwDpid() {
+ this.ofMatch = new OFMatch();
+ this.switchDataPathId = 0;
+ }
+
+ public OFMatchWithSwDpid(OFMatch ofm, long swDpid) {
+ this.ofMatch = ofm.clone();
+ this.switchDataPathId = swDpid;
+ }
+ public OFMatch getOfMatch() {
+ return ofMatch;
+ }
+
+ public void setOfMatch(OFMatch ofMatch) {
+ this.ofMatch = ofMatch.clone();
+ }
+
+ public long getSwitchDataPathId() {
+ return this.switchDataPathId;
+ }
+
+ public OFMatchWithSwDpid setSwitchDataPathId(long dpid) {
+ this.switchDataPathId = dpid;
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return "OFMatchWithSwDpid [" + HexString.toHexString(switchDataPathId) + ofMatch + "]";
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/OFMessage.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFMessage.java
new file mode 100644
index 0000000..7ea69a7
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFMessage.java
@@ -0,0 +1,334 @@
+/**
+* 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.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.concurrent.ConcurrentHashMap;
+
+import net.floodlightcontroller.core.FloodlightContext;
+import net.floodlightcontroller.core.IFloodlightProviderService;
+import net.floodlightcontroller.core.IOFSwitch;
+import net.floodlightcontroller.packet.Ethernet;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.openflow.util.HexString;
+import org.openflow.util.U16;
+import org.openflow.util.U32;
+import org.openflow.util.U8;
+
+/**
+ * The base class for all OpenFlow protocol messages. This class contains the
+ * equivalent of the ofp_header which is present in all OpenFlow messages.
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu) - Feb 3, 2010
+ * @author Rob Sherwood (rob.sherwood@stanford.edu) - Feb 3, 2010
+ */
+public class OFMessage {
+ public static byte OFP_VERSION = 0x01;
+ public static int MINIMUM_LENGTH = 8;
+
+ protected byte version;
+ protected OFType type;
+ protected short length;
+ protected int xid;
+
+ private ConcurrentHashMap<String, Object> storage;
+
+ public OFMessage() {
+ storage = null;
+ this.version = OFP_VERSION;
+ }
+
+ protected synchronized ConcurrentHashMap<String, Object> getMessageStore() {
+ if (storage == null) {
+ storage = new ConcurrentHashMap<String, Object>();;
+ }
+ return storage;
+ }
+
+ /**
+ * Get the length of this message
+ *
+ * @return
+ */
+ public short getLength() {
+ return length;
+ }
+
+ /**
+ * Get the length of this message, unsigned
+ *
+ * @return
+ */
+ public int getLengthU() {
+ return U16.f(length);
+ }
+
+ /**
+ * Set the length of this message
+ *
+ * @param length
+ */
+ public OFMessage setLength(short length) {
+ this.length = length;
+ return this;
+ }
+
+ /**
+ * Set the length of this message, unsigned
+ *
+ * @param length
+ */
+ public OFMessage setLengthU(int length) {
+ this.length = U16.t(length);
+ return this;
+ }
+
+ /**
+ * Get the type of this message
+ *
+ * @return
+ */
+ public OFType getType() {
+ return type;
+ }
+
+ /**
+ * Set the type of this message
+ *
+ * @param type
+ */
+ public void setType(OFType type) {
+ this.type = type;
+ }
+
+ /**
+ * Get the OpenFlow version of this message
+ *
+ * @return
+ */
+ public byte getVersion() {
+ return version;
+ }
+
+ /**
+ * Set the OpenFlow version of this message
+ *
+ * @param version
+ */
+ public void setVersion(byte version) {
+ this.version = version;
+ }
+
+ /**
+ * Get the transaction id of this message
+ *
+ * @return
+ */
+ public int getXid() {
+ return xid;
+ }
+
+ /**
+ * Set the transaction id of this message
+ *
+ * @param xid
+ */
+ public void setXid(int xid) {
+ this.xid = xid;
+ }
+
+ /**
+ * Read this message off the wire from the specified ByteBuffer
+ * @param data
+ */
+ public void readFrom(ChannelBuffer data) {
+ this.version = data.readByte();
+ this.type = OFType.valueOf(data.readByte());
+ this.length = data.readShort();
+ this.xid = data.readInt();
+ }
+
+ /**
+ * Write this message's binary format to the specified ByteBuffer
+ * @param data
+ */
+ public void writeTo(ChannelBuffer data) {
+ data.writeByte(version);
+ data.writeByte(type.getTypeValue());
+ data.writeShort(length);
+ data.writeInt(xid);
+ }
+
+ /**
+ * Returns a summary of the message
+ * @return "ofmsg=v=$version;t=$type:l=$len:xid=$xid"
+ */
+ public String toString() {
+ return "ofmsg" +
+ ":v=" + U8.f(this.getVersion()) +
+ ";t=" + this.getType() +
+ ";l=" + this.getLengthU() +
+ ";x=" + U32.f(this.getXid());
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 97;
+ int result = 1;
+ result = prime * result + length;
+ result = prime * result + ((type == null) ? 0 : type.hashCode());
+ result = prime * result + version;
+ result = prime * result + xid;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (!(obj instanceof OFMessage)) {
+ return false;
+ }
+ OFMessage other = (OFMessage) obj;
+ if (length != other.length) {
+ return false;
+ }
+ if (type == null) {
+ if (other.type != null) {
+ return false;
+ }
+ } else if (!type.equals(other.type)) {
+ return false;
+ }
+ if (version != other.version) {
+ return false;
+ }
+ if (xid != other.xid) {
+ return false;
+ }
+ return true;
+ }
+
+ public static String getDataAsString(IOFSwitch sw, OFMessage msg, FloodlightContext cntx) {
+
+ Ethernet eth;
+ StringBuffer sb = new StringBuffer("");
+
+ DateFormat dateFormat = new SimpleDateFormat("HH:mm:ss.SSS");
+ Date date = new Date();
+
+ sb.append(dateFormat.format(date));
+ sb.append(" ");
+
+ switch (msg.getType()) {
+ case PACKET_IN:
+ OFPacketIn pktIn = (OFPacketIn) msg;
+ sb.append("packet_in [ ");
+ sb.append(sw.getStringId());
+ sb.append(" -> Controller");
+ sb.append(" ]");
+
+ sb.append("\ntotal length: ");
+ sb.append(pktIn.getTotalLength());
+ sb.append("\nin_port: ");
+ sb.append(pktIn.getInPort());
+ sb.append("\ndata_length: ");
+ sb.append(pktIn.getTotalLength() - OFPacketIn.MINIMUM_LENGTH);
+ sb.append("\nbuffer: ");
+ sb.append(pktIn.getBufferId());
+
+ // If the conext is not set by floodlight, then ignore.
+ if (cntx != null) {
+ // packet type icmp, arp, etc.
+ eth = IFloodlightProviderService.bcStore.get(cntx,
+ IFloodlightProviderService.CONTEXT_PI_PAYLOAD);
+ if (eth != null)
+ sb.append(eth.toString());
+ }
+ break;
+
+ case PACKET_OUT:
+ OFPacketOut pktOut = (OFPacketOut) msg;
+ sb.append("packet_out [ ");
+ sb.append("Controller -> ");
+ sb.append(HexString.toHexString(sw.getId()));
+ sb.append(" ]");
+
+ sb.append("\nin_port: ");
+ sb.append(pktOut.getInPort());
+ sb.append("\nactions_len: ");
+ sb.append(pktOut.getActionsLength());
+ if (pktOut.getActions() != null) {
+ sb.append("\nactions: ");
+ sb.append(pktOut.getActions().toString());
+ }
+ break;
+
+ case FLOW_MOD:
+ OFFlowMod fm = (OFFlowMod) msg;
+ sb.append("flow_mod [ ");
+ sb.append("Controller -> ");
+ sb.append(HexString.toHexString(sw.getId()));
+ sb.append(" ]");
+
+ // If the conext is not set by floodlight, then ignore.
+ if (cntx != null) {
+ eth = IFloodlightProviderService.bcStore.get(cntx,
+ IFloodlightProviderService.CONTEXT_PI_PAYLOAD);
+ if (eth != null)
+ sb.append(eth.toString());
+ }
+
+ sb.append("\nADD: cookie: ");
+ sb.append(fm.getCookie());
+ sb.append(" idle: ");
+ sb.append(fm.getIdleTimeout());
+ sb.append(" hard: ");
+ sb.append(fm.getHardTimeout());
+ sb.append(" pri: ");
+ sb.append(fm.getPriority());
+ sb.append(" buf: ");
+ sb.append(fm.getBufferId());
+ sb.append(" flg: ");
+ sb.append(fm.getFlags());
+ if (fm.getActions() != null) {
+ sb.append("\nactions: ");
+ sb.append(fm.getActions().toString());
+ }
+ break;
+
+ default:
+ sb.append("[Unknown Packet]");
+ }
+
+ sb.append("\n\n");
+ return sb.toString();
+
+ }
+
+ public static byte[] getData(IOFSwitch sw, OFMessage msg, FloodlightContext cntx) {
+ return OFMessage.getDataAsString(sw, msg, cntx).getBytes();
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/OFMessageContextStore.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFMessageContextStore.java
new file mode 100644
index 0000000..b60aa1c
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFMessageContextStore.java
@@ -0,0 +1,39 @@
+/**
+* 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.openflow.protocol.OFMessage;
+
+public class OFMessageContextStore<V> {
+ protected OFMessage msg;
+ String namespace;
+
+ public OFMessageContextStore(OFMessage msg, String namespace) {
+ this.msg = msg;
+ this.namespace = namespace;
+ }
+
+ @SuppressWarnings("unchecked")
+ public V get(String key) {
+ return (V)msg.getMessageStore().get(namespace + "|" + key);
+ }
+
+ public void put(String key, V value) {
+ msg.getMessageStore().put(namespace + "|" + key, value);
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/OFPacketIn.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFPacketIn.java
new file mode 100644
index 0000000..c37c918
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFPacketIn.java
@@ -0,0 +1,211 @@
+/**
+* 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 org.jboss.netty.buffer.ChannelBuffer;
+import org.openflow.util.U16;
+import org.openflow.util.U32;
+import org.openflow.util.U8;
+
+/**
+ * Represents an ofp_packet_in
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu) - Feb 8, 2010
+ */
+public class OFPacketIn extends OFMessage {
+ public static short MINIMUM_LENGTH = 18;
+
+ public enum OFPacketInReason {
+ NO_MATCH, ACTION
+ }
+
+ protected int bufferId;
+ protected short totalLength;
+ protected short inPort;
+ protected OFPacketInReason reason;
+ protected byte[] packetData;
+
+ public OFPacketIn() {
+ super();
+ this.type = OFType.PACKET_IN;
+ this.length = U16.t(MINIMUM_LENGTH);
+ }
+
+ /**
+ * Get buffer_id
+ * @return
+ */
+ public int getBufferId() {
+ return this.bufferId;
+ }
+
+ /**
+ * Set buffer_id
+ * @param bufferId
+ */
+ public OFPacketIn setBufferId(int bufferId) {
+ this.bufferId = bufferId;
+ return this;
+ }
+
+ /**
+ * Returns the packet data
+ * @return
+ */
+ public byte[] getPacketData() {
+ return this.packetData;
+ }
+
+ /**
+ * Sets the packet data, and updates the length of this message
+ * @param packetData
+ */
+ public OFPacketIn setPacketData(byte[] packetData) {
+ this.packetData = packetData;
+ this.length = U16.t(OFPacketIn.MINIMUM_LENGTH + packetData.length);
+ return this;
+ }
+
+ /**
+ * Get in_port
+ * @return
+ */
+ public short getInPort() {
+ return this.inPort;
+ }
+
+ /**
+ * Set in_port
+ * @param inPort
+ */
+ public OFPacketIn setInPort(short inPort) {
+ this.inPort = inPort;
+ return this;
+ }
+
+ /**
+ * Get reason
+ * @return
+ */
+ public OFPacketInReason getReason() {
+ return this.reason;
+ }
+
+ /**
+ * Set reason
+ * @param reason
+ */
+ public OFPacketIn setReason(OFPacketInReason reason) {
+ this.reason = reason;
+ return this;
+ }
+
+ /**
+ * Get total_len
+ * @return
+ */
+ public short getTotalLength() {
+ return this.totalLength;
+ }
+
+ /**
+ * Set total_len
+ * @param totalLength
+ */
+ public OFPacketIn setTotalLength(short totalLength) {
+ this.totalLength = totalLength;
+ return this;
+ }
+
+ @Override
+ public void readFrom(ChannelBuffer data) {
+ super.readFrom(data);
+ this.bufferId = data.readInt();
+ this.totalLength = data.readShort();
+ this.inPort = data.readShort();
+ this.reason = OFPacketInReason.values()[U8.f(data.readByte())];
+ data.readByte(); // pad
+ this.packetData = new byte[getLengthU() - MINIMUM_LENGTH];
+ data.readBytes(this.packetData);
+ }
+
+ @Override
+ public void writeTo(ChannelBuffer data) {
+ super.writeTo(data);
+ data.writeInt(bufferId);
+ data.writeShort(totalLength);
+ data.writeShort(inPort);
+ data.writeByte((byte) reason.ordinal());
+ data.writeByte((byte) 0x0); // pad
+ data.writeBytes(this.packetData);
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 283;
+ int result = super.hashCode();
+ result = prime * result + bufferId;
+ result = prime * result + inPort;
+ result = prime * result + Arrays.hashCode(packetData);
+ result = prime * result + ((reason == null) ? 0 : reason.hashCode());
+ result = prime * result + totalLength;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!super.equals(obj)) {
+ return false;
+ }
+ if (!(obj instanceof OFPacketIn)) {
+ return false;
+ }
+ OFPacketIn other = (OFPacketIn) obj;
+ if (bufferId != other.bufferId) {
+ return false;
+ }
+ if (inPort != other.inPort) {
+ return false;
+ }
+ if (!Arrays.equals(packetData, other.packetData)) {
+ return false;
+ }
+ if (reason == null) {
+ if (other.reason != null) {
+ return false;
+ }
+ } else if (!reason.equals(other.reason)) {
+ return false;
+ }
+ if (totalLength != other.totalLength) {
+ return false;
+ }
+ return true;
+ }
+
+ public String toString() {
+ String myStr = super.toString();
+ return "packetIn" +
+ ":bufferId=" + U32.f(this.bufferId) + myStr;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/OFPacketOut.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFPacketOut.java
new file mode 100644
index 0000000..2b45e8a
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFPacketOut.java
@@ -0,0 +1,240 @@
+/**
+* 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 java.util.List;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.openflow.protocol.action.OFAction;
+import org.openflow.protocol.factory.OFActionFactory;
+import org.openflow.protocol.factory.OFActionFactoryAware;
+import org.openflow.util.U16;
+
+/**
+ * Represents an ofp_packet_out message
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu) - Mar 12, 2010
+ */
+public class OFPacketOut extends OFMessage implements OFActionFactoryAware {
+ public static int MINIMUM_LENGTH = 16;
+ public static int BUFFER_ID_NONE = 0xffffffff;
+
+ protected OFActionFactory actionFactory;
+ protected int bufferId;
+ protected short inPort;
+ protected short actionsLength;
+ protected List<OFAction> actions;
+ protected byte[] packetData;
+
+ public OFPacketOut() {
+ super();
+ this.type = OFType.PACKET_OUT;
+ this.length = U16.t(MINIMUM_LENGTH);
+ }
+
+ /**
+ * Get buffer_id
+ * @return
+ */
+ public int getBufferId() {
+ return this.bufferId;
+ }
+
+ /**
+ * Set buffer_id
+ * @param bufferId
+ */
+ public OFPacketOut setBufferId(int bufferId) {
+ this.bufferId = bufferId;
+ return this;
+ }
+
+ /**
+ * Returns the packet data
+ * @return
+ */
+ public byte[] getPacketData() {
+ return this.packetData;
+ }
+
+ /**
+ * Sets the packet data
+ * @param packetData
+ */
+ public OFPacketOut setPacketData(byte[] packetData) {
+ this.packetData = packetData;
+ return this;
+ }
+
+ /**
+ * Get in_port
+ * @return
+ */
+ public short getInPort() {
+ return this.inPort;
+ }
+
+ /**
+ * Set in_port
+ * @param inPort
+ */
+ public OFPacketOut setInPort(short inPort) {
+ this.inPort = inPort;
+ return this;
+ }
+
+ /**
+ * Set in_port. Convenience method using OFPort enum.
+ * @param inPort
+ */
+ public OFPacketOut setInPort(OFPort inPort) {
+ this.inPort = inPort.getValue();
+ return this;
+ }
+
+ /**
+ * Get actions_len
+ * @return
+ */
+ public short getActionsLength() {
+ return this.actionsLength;
+ }
+
+ /**
+ * Get actions_len, unsigned
+ * @return
+ */
+ public int getActionsLengthU() {
+ return U16.f(this.actionsLength);
+ }
+
+ /**
+ * Set actions_len
+ * @param actionsLength
+ */
+ public OFPacketOut setActionsLength(short actionsLength) {
+ this.actionsLength = actionsLength;
+ return this;
+ }
+
+ /**
+ * Returns the actions contained in this message
+ * @return a list of ordered OFAction objects
+ */
+ public List<OFAction> getActions() {
+ return this.actions;
+ }
+
+ /**
+ * Sets the list of actions on this message
+ * @param actions a list of ordered OFAction objects
+ */
+ public OFPacketOut setActions(List<OFAction> actions) {
+ this.actions = actions;
+ return this;
+ }
+
+ @Override
+ public void setActionFactory(OFActionFactory actionFactory) {
+ this.actionFactory = actionFactory;
+ }
+
+ @Override
+ public void readFrom(ChannelBuffer data) {
+ super.readFrom(data);
+ this.bufferId = data.readInt();
+ this.inPort = data.readShort();
+ this.actionsLength = data.readShort();
+ if ( this.actionFactory == null)
+ throw new RuntimeException("ActionFactory not set");
+ this.actions = this.actionFactory.parseActions(data, getActionsLengthU());
+ this.packetData = new byte[getLengthU() - MINIMUM_LENGTH - getActionsLengthU()];
+ data.readBytes(this.packetData);
+ }
+
+ @Override
+ public void writeTo(ChannelBuffer data) {
+ super.writeTo(data);
+ data.writeInt(bufferId);
+ data.writeShort(inPort);
+ data.writeShort(actionsLength);
+ for (OFAction action : actions) {
+ action.writeTo(data);
+ }
+ if (this.packetData != null)
+ data.writeBytes(this.packetData);
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 293;
+ int result = super.hashCode();
+ result = prime * result + ((actions == null) ? 0 : actions.hashCode());
+ result = prime * result + actionsLength;
+ result = prime * result + bufferId;
+ result = prime * result + inPort;
+ result = prime * result + Arrays.hashCode(packetData);
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!super.equals(obj)) {
+ return false;
+ }
+ if (!(obj instanceof OFPacketOut)) {
+ return false;
+ }
+ OFPacketOut other = (OFPacketOut) obj;
+ if (actions == null) {
+ if (other.actions != null) {
+ return false;
+ }
+ } else if (!actions.equals(other.actions)) {
+ return false;
+ }
+ if (actionsLength != other.actionsLength) {
+ return false;
+ }
+ if (bufferId != other.bufferId) {
+ return false;
+ }
+ if (inPort != other.inPort) {
+ return false;
+ }
+ if (!Arrays.equals(packetData, other.packetData)) {
+ return false;
+ }
+ return true;
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return "OFPacketOut [actionFactory=" + actionFactory + ", actions="
+ + actions + ", actionsLength=" + actionsLength + ", bufferId=0x"
+ + Integer.toHexString(bufferId) + ", inPort=" + inPort + ", packetData="
+ + Arrays.toString(packetData) + "]";
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/OFPhysicalPort.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFPhysicalPort.java
new file mode 100644
index 0000000..58fdae5
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFPhysicalPort.java
@@ -0,0 +1,470 @@
+/**
+* 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.io.UnsupportedEncodingException;
+import java.nio.charset.Charset;
+import java.util.Arrays;
+
+import net.floodlightcontroller.core.web.serializers.ByteArrayMACSerializer;
+import net.floodlightcontroller.core.web.serializers.UShortSerializer;
+
+import org.codehaus.jackson.map.annotate.JsonSerialize;
+import org.jboss.netty.buffer.ChannelBuffer;
+
+/**
+ * Represents ofp_phy_port
+ * @author David Erickson (daviderickson@cs.stanford.edu) - Mar 25, 2010
+ */
+public class OFPhysicalPort {
+ public static int MINIMUM_LENGTH = 48;
+ public static int OFP_ETH_ALEN = 6;
+
+ public enum OFPortConfig {
+ OFPPC_PORT_DOWN (1 << 0) {
+ public String toString() {
+ return "port-down (0x1)";
+ }
+ },
+ OFPPC_NO_STP (1 << 1) {
+ public String toString() {
+ return "no-stp (0x2)";
+ }
+ },
+ OFPPC_NO_RECV (1 << 2) {
+ public String toString() {
+ return "no-recv (0x4)";
+ }
+ },
+ OFPPC_NO_RECV_STP (1 << 3) {
+ public String toString() {
+ return "no-recv-stp (0x8)";
+ }
+ },
+ OFPPC_NO_FLOOD (1 << 4) {
+ public String toString() {
+ return "no-flood (0x10)";
+ }
+ },
+ OFPPC_NO_FWD (1 << 5) {
+ public String toString() {
+ return "no-fwd (0x20)";
+ }
+ },
+ OFPPC_NO_PACKET_IN (1 << 6) {
+ public String toString() {
+ return "no-pkt-in (0x40)";
+ }
+ };
+
+ protected int value;
+
+ private OFPortConfig(int value) {
+ this.value = value;
+ }
+
+ /**
+ * @return the value
+ */
+ public int getValue() {
+ return value;
+ }
+ }
+
+ public enum OFPortState {
+ OFPPS_LINK_DOWN (1 << 0) {
+ public String toString() {
+ return "link-down (0x1)";
+ }
+ },
+ OFPPS_STP_LISTEN (0 << 8) {
+ public String toString() {
+ return "listen (0x0)";
+ }
+ },
+ OFPPS_STP_LEARN (1 << 8) {
+ public String toString() {
+ return "learn-no-relay (0x100)";
+ }
+ },
+ OFPPS_STP_FORWARD (2 << 8) {
+ public String toString() {
+ return "forward (0x200)";
+ }
+ },
+ OFPPS_STP_BLOCK (3 << 8) {
+ public String toString() {
+ return "block-broadcast (0x300)";
+ }
+ },
+ OFPPS_STP_MASK (3 << 8) {
+ public String toString() {
+ return "block-broadcast (0x300)";
+ }
+ };
+
+ protected int value;
+
+ private OFPortState(int value) {
+ this.value = value;
+ }
+
+ /**
+ * @return the value
+ */
+ public int getValue() {
+ return value;
+ }
+ }
+
+ public enum OFPortFeatures {
+ OFPPF_10MB_HD (1 << 0) {
+ public String toString() {
+ return "10mb-hd (0x1)";
+ }
+ },
+ OFPPF_10MB_FD (1 << 1) {
+ public String toString() {
+ return "10mb-fd (0x2)";
+ }
+ },
+ OFPPF_100MB_HD (1 << 2) {
+ public String toString() {
+ return "100mb-hd (0x4)";
+ }
+ },
+ OFPPF_100MB_FD (1 << 3) {
+ public String toString() {
+ return "100mb-fd (0x8)";
+ }
+ },
+ OFPPF_1GB_HD (1 << 4) {
+ public String toString() {
+ return "1gb-hd (0x10)";
+ }
+ },
+ OFPPF_1GB_FD (1 << 5) {
+ public String toString() {
+ return "1gb-fd (0x20)";
+ }
+ },
+ OFPPF_10GB_FD (1 << 6) {
+ public String toString() {
+ return "10gb-fd (0x40)";
+ }
+ },
+ OFPPF_COPPER (1 << 7) {
+ public String toString() {
+ return "copper (0x80)";
+ }
+ },
+ OFPPF_FIBER (1 << 8) {
+ public String toString() {
+ return "fiber (0x100)";
+ }
+ },
+ OFPPF_AUTONEG (1 << 9) {
+ public String toString() {
+ return "autoneg (0x200)";
+ }
+ },
+ OFPPF_PAUSE (1 << 10) {
+ public String toString() {
+ return "pause (0x400)";
+ }
+ },
+ OFPPF_PAUSE_ASYM (1 << 11) {
+ public String toString() {
+ return "pause-asym (0x800)";
+ }
+ };
+
+ protected int value;
+
+ private OFPortFeatures(int value) {
+ this.value = value;
+ }
+
+ /**
+ * @return the value
+ */
+ public int getValue() {
+ return value;
+ }
+ }
+
+ protected short portNumber;
+ protected byte[] hardwareAddress;
+ protected String name;
+ protected int config;
+ protected int state;
+ protected int currentFeatures;
+ protected int advertisedFeatures;
+ protected int supportedFeatures;
+ protected int peerFeatures;
+
+ /**
+ * @return the portNumber
+ */
+ @JsonSerialize(using=UShortSerializer.class)
+ public short getPortNumber() {
+ return portNumber;
+ }
+
+ /**
+ * @param portNumber the portNumber to set
+ */
+ public void setPortNumber(short portNumber) {
+ this.portNumber = portNumber;
+ }
+
+ /**
+ * @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;
+ }
+
+ /**
+ * @return the name
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * @param name the name to set
+ */
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ /**
+ * @return the config
+ */
+ public int getConfig() {
+ return config;
+ }
+
+ /**
+ * @param config the config to set
+ */
+ public void setConfig(int config) {
+ this.config = config;
+ }
+
+ /**
+ * @return the state
+ */
+ public int getState() {
+ return state;
+ }
+
+ /**
+ * @param state the state to set
+ */
+ public void setState(int state) {
+ this.state = state;
+ }
+
+ /**
+ * @return the currentFeatures
+ */
+ public int getCurrentFeatures() {
+ return currentFeatures;
+ }
+
+ /**
+ * @param currentFeatures the currentFeatures to set
+ */
+ public void setCurrentFeatures(int currentFeatures) {
+ this.currentFeatures = currentFeatures;
+ }
+
+ /**
+ * @return the advertisedFeatures
+ */
+ public int getAdvertisedFeatures() {
+ return advertisedFeatures;
+ }
+
+ /**
+ * @param advertisedFeatures the advertisedFeatures to set
+ */
+ public void setAdvertisedFeatures(int advertisedFeatures) {
+ this.advertisedFeatures = advertisedFeatures;
+ }
+
+ /**
+ * @return the supportedFeatures
+ */
+ public int getSupportedFeatures() {
+ return supportedFeatures;
+ }
+
+ /**
+ * @param supportedFeatures the supportedFeatures to set
+ */
+ public void setSupportedFeatures(int supportedFeatures) {
+ this.supportedFeatures = supportedFeatures;
+ }
+
+ /**
+ * @return the peerFeatures
+ */
+ public int getPeerFeatures() {
+ return peerFeatures;
+ }
+
+ /**
+ * @param peerFeatures the peerFeatures to set
+ */
+ public void setPeerFeatures(int peerFeatures) {
+ this.peerFeatures = peerFeatures;
+ }
+
+ /**
+ * Read this message off the wire from the specified ByteBuffer
+ * @param data
+ */
+ public void readFrom(ChannelBuffer data) {
+ this.portNumber = data.readShort();
+ if (this.hardwareAddress == null)
+ this.hardwareAddress = new byte[OFP_ETH_ALEN];
+ data.readBytes(this.hardwareAddress);
+ 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"));
+ this.config = data.readInt();
+ this.state = data.readInt();
+ this.currentFeatures = data.readInt();
+ this.advertisedFeatures = data.readInt();
+ this.supportedFeatures = data.readInt();
+ this.peerFeatures = data.readInt();
+ }
+
+ /**
+ * Write this message's binary format to the specified ByteBuffer
+ * @param data
+ */
+ public void writeTo(ChannelBuffer data) {
+ data.writeShort(this.portNumber);
+ data.writeBytes(hardwareAddress);
+ try {
+ byte[] name = this.name.getBytes("ASCII");
+ if (name.length < 16) {
+ data.writeBytes(name);
+ for (int i = name.length; i < 16; ++i) {
+ data.writeByte((byte) 0);
+ }
+ } else {
+ data.writeBytes(name, 0, 15);
+ data.writeByte((byte) 0);
+ }
+ } catch (UnsupportedEncodingException e) {
+ throw new RuntimeException(e);
+ }
+ data.writeInt(this.config);
+ data.writeInt(this.state);
+ data.writeInt(this.currentFeatures);
+ data.writeInt(this.advertisedFeatures);
+ data.writeInt(this.supportedFeatures);
+ data.writeInt(this.peerFeatures);
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 307;
+ int result = 1;
+ result = prime * result + advertisedFeatures;
+ result = prime * result + config;
+ result = prime * result + currentFeatures;
+ result = prime * result + Arrays.hashCode(hardwareAddress);
+ result = prime * result + ((name == null) ? 0 : name.hashCode());
+ result = prime * result + peerFeatures;
+ result = prime * result + portNumber;
+ result = prime * result + state;
+ result = prime * result + supportedFeatures;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (!(obj instanceof OFPhysicalPort)) {
+ return false;
+ }
+ OFPhysicalPort other = (OFPhysicalPort) obj;
+ if (advertisedFeatures != other.advertisedFeatures) {
+ return false;
+ }
+ if (config != other.config) {
+ return false;
+ }
+ if (currentFeatures != other.currentFeatures) {
+ return false;
+ }
+ if (!Arrays.equals(hardwareAddress, other.hardwareAddress)) {
+ return false;
+ }
+ if (name == null) {
+ if (other.name != null) {
+ return false;
+ }
+ } else if (!name.equals(other.name)) {
+ return false;
+ }
+ if (peerFeatures != other.peerFeatures) {
+ return false;
+ }
+ if (portNumber != other.portNumber) {
+ return false;
+ }
+ if (state != other.state) {
+ return false;
+ }
+ if (supportedFeatures != other.supportedFeatures) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/OFPort.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFPort.java
new file mode 100644
index 0000000..93301bc
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFPort.java
@@ -0,0 +1,43 @@
+/**
+* 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;
+
+public enum OFPort {
+ OFPP_MAX ((short)0xff00),
+ OFPP_IN_PORT ((short)0xfff8),
+ OFPP_TABLE ((short)0xfff9),
+ OFPP_NORMAL ((short)0xfffa),
+ OFPP_FLOOD ((short)0xfffb),
+ OFPP_ALL ((short)0xfffc),
+ OFPP_CONTROLLER ((short)0xfffd),
+ OFPP_LOCAL ((short)0xfffe),
+ OFPP_NONE ((short)0xffff);
+
+ protected short value;
+
+ private OFPort(short value) {
+ this.value = value;
+ }
+
+ /**
+ * @return the value
+ */
+ public short getValue() {
+ return value;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/OFPortMod.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFPortMod.java
new file mode 100644
index 0000000..876e856
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFPortMod.java
@@ -0,0 +1,182 @@
+/**
+* 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 org.jboss.netty.buffer.ChannelBuffer;
+import org.openflow.util.U16;
+
+/**
+ * Represents an ofp_port_mod message
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class OFPortMod extends OFMessage {
+ public static int MINIMUM_LENGTH = 32;
+
+ protected short portNumber;
+ protected byte[] hardwareAddress;
+ protected int config;
+ protected int mask;
+ protected int advertise;
+
+ public OFPortMod() {
+ super();
+ this.type = OFType.PORT_MOD;
+ this.length = U16.t(MINIMUM_LENGTH);
+ }
+
+ /**
+ * @return the portNumber
+ */
+ public short getPortNumber() {
+ return portNumber;
+ }
+
+ /**
+ * @param portNumber the portNumber to set
+ */
+ public void setPortNumber(short portNumber) {
+ this.portNumber = portNumber;
+ }
+
+ /**
+ * @return the hardwareAddress
+ */
+ public byte[] getHardwareAddress() {
+ return hardwareAddress;
+ }
+
+ /**
+ * @param hardwareAddress the hardwareAddress to set
+ */
+ public void setHardwareAddress(byte[] hardwareAddress) {
+ if (hardwareAddress.length != OFPhysicalPort.OFP_ETH_ALEN)
+ throw new RuntimeException("Hardware address must have length "
+ + OFPhysicalPort.OFP_ETH_ALEN);
+ this.hardwareAddress = hardwareAddress;
+ }
+
+ /**
+ * @return the config
+ */
+ public int getConfig() {
+ return config;
+ }
+
+ /**
+ * @param config the config to set
+ */
+ public void setConfig(int config) {
+ this.config = config;
+ }
+
+ /**
+ * @return the mask
+ */
+ public int getMask() {
+ return mask;
+ }
+
+ /**
+ * @param mask the mask to set
+ */
+ public void setMask(int mask) {
+ this.mask = mask;
+ }
+
+ /**
+ * @return the advertise
+ */
+ public int getAdvertise() {
+ return advertise;
+ }
+
+ /**
+ * @param advertise the advertise to set
+ */
+ public void setAdvertise(int advertise) {
+ this.advertise = advertise;
+ }
+
+ @Override
+ public void readFrom(ChannelBuffer data) {
+ super.readFrom(data);
+ this.portNumber = data.readShort();
+ if (this.hardwareAddress == null)
+ this.hardwareAddress = new byte[OFPhysicalPort.OFP_ETH_ALEN];
+ data.readBytes(this.hardwareAddress);
+ this.config = data.readInt();
+ this.mask = data.readInt();
+ this.advertise = data.readInt();
+ data.readInt(); // pad
+ }
+
+ @Override
+ public void writeTo(ChannelBuffer data) {
+ super.writeTo(data);
+ data.writeShort(this.portNumber);
+ data.writeBytes(this.hardwareAddress);
+ data.writeInt(this.config);
+ data.writeInt(this.mask);
+ data.writeInt(this.advertise);
+ data.writeInt(0); // pad
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 311;
+ int result = super.hashCode();
+ result = prime * result + advertise;
+ result = prime * result + config;
+ result = prime * result + Arrays.hashCode(hardwareAddress);
+ result = prime * result + mask;
+ result = prime * result + portNumber;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!super.equals(obj)) {
+ return false;
+ }
+ if (!(obj instanceof OFPortMod)) {
+ return false;
+ }
+ OFPortMod other = (OFPortMod) obj;
+ if (advertise != other.advertise) {
+ return false;
+ }
+ if (config != other.config) {
+ return false;
+ }
+ if (!Arrays.equals(hardwareAddress, other.hardwareAddress)) {
+ return false;
+ }
+ if (mask != other.mask) {
+ return false;
+ }
+ if (portNumber != other.portNumber) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/OFPortStatus.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFPortStatus.java
new file mode 100644
index 0000000..8bde6e7
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFPortStatus.java
@@ -0,0 +1,126 @@
+/**
+* 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.jboss.netty.buffer.ChannelBuffer;
+import org.openflow.util.U16;
+
+/**
+ * Represents an ofp_port_status message
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class OFPortStatus extends OFMessage {
+ public static int MINIMUM_LENGTH = 64;
+
+ public enum OFPortReason {
+ OFPPR_ADD,
+ OFPPR_DELETE,
+ OFPPR_MODIFY
+ }
+
+ protected byte reason;
+ protected OFPhysicalPort desc;
+
+ /**
+ * @return the reason
+ */
+ public byte getReason() {
+ return reason;
+ }
+
+ /**
+ * @param reason the reason to set
+ */
+ public void setReason(byte reason) {
+ this.reason = reason;
+ }
+
+ /**
+ * @return the desc
+ */
+ public OFPhysicalPort getDesc() {
+ return desc;
+ }
+
+ /**
+ * @param desc the desc to set
+ */
+ public void setDesc(OFPhysicalPort desc) {
+ this.desc = desc;
+ }
+
+ public OFPortStatus() {
+ super();
+ this.type = OFType.PORT_STATUS;
+ this.length = U16.t(MINIMUM_LENGTH);
+ }
+
+ @Override
+ public void readFrom(ChannelBuffer data) {
+ super.readFrom(data);
+ this.reason = data.readByte();
+ data.readerIndex(data.readerIndex() + 7); // skip 7 bytes of padding
+ if (this.desc == null)
+ this.desc = new OFPhysicalPort();
+ this.desc.readFrom(data);
+ }
+
+ @Override
+ public void writeTo(ChannelBuffer data) {
+ super.writeTo(data);
+ data.writeByte(this.reason);
+ for (int i = 0; i < 7; ++i)
+ data.writeByte((byte) 0);
+ this.desc.writeTo(data);
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 313;
+ int result = super.hashCode();
+ result = prime * result + ((desc == null) ? 0 : desc.hashCode());
+ result = prime * result + reason;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!super.equals(obj)) {
+ return false;
+ }
+ if (!(obj instanceof OFPortStatus)) {
+ return false;
+ }
+ OFPortStatus other = (OFPortStatus) obj;
+ if (desc == null) {
+ if (other.desc != null) {
+ return false;
+ }
+ } else if (!desc.equals(other.desc)) {
+ return false;
+ }
+ if (reason != other.reason) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/OFSetConfig.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFSetConfig.java
new file mode 100644
index 0000000..4b23564
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFSetConfig.java
@@ -0,0 +1,29 @@
+/**
+* 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;
+
+/**
+ * Represents an OFPT_SET_CONFIG type message
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class OFSetConfig extends OFSwitchConfig {
+ public OFSetConfig() {
+ super();
+ this.type = OFType.SET_CONFIG;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/OFStatisticsMessageBase.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFStatisticsMessageBase.java
new file mode 100644
index 0000000..cf6ace3
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFStatisticsMessageBase.java
@@ -0,0 +1,157 @@
+/**
+* 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 org.jboss.netty.buffer.ChannelBuffer;
+import org.openflow.protocol.factory.OFStatisticsFactory;
+import org.openflow.protocol.factory.OFStatisticsFactoryAware;
+import org.openflow.protocol.statistics.OFStatistics;
+import org.openflow.protocol.statistics.OFStatisticsType;
+
+
+/**
+ * Base class for statistics requests/replies
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu) - Mar 27, 2010
+ */
+public abstract class OFStatisticsMessageBase extends OFMessage implements
+ OFStatisticsFactoryAware {
+ public static int MINIMUM_LENGTH = 12;
+
+ protected OFStatisticsFactory statisticsFactory;
+ protected OFStatisticsType statisticType;
+ protected short flags;
+ protected List<OFStatistics> statistics;
+
+ /**
+ * @return the statisticType
+ */
+ public OFStatisticsType getStatisticType() {
+ return statisticType;
+ }
+
+ /**
+ * @param statisticType the statisticType to set
+ */
+ public void setStatisticType(OFStatisticsType statisticType) {
+ this.statisticType = statisticType;
+ }
+
+ /**
+ * @return the flags
+ */
+ public short getFlags() {
+ return flags;
+ }
+
+ /**
+ * @param flags the flags to set
+ */
+ public void setFlags(short flags) {
+ this.flags = flags;
+ }
+
+ /**
+ * @return the statistics
+ */
+ public List<OFStatistics> getStatistics() {
+ return statistics;
+ }
+
+ /**
+ * @param statistics the statistics to set
+ */
+ public void setStatistics(List<OFStatistics> statistics) {
+ this.statistics = statistics;
+ }
+
+ @Override
+ public void setStatisticsFactory(OFStatisticsFactory statisticsFactory) {
+ this.statisticsFactory = statisticsFactory;
+ }
+
+ @Override
+ public void readFrom(ChannelBuffer data) {
+ super.readFrom(data);
+ this.statisticType = OFStatisticsType.valueOf(data.readShort(), this
+ .getType());
+ this.flags = data.readShort();
+ if (this.statisticsFactory == null)
+ throw new RuntimeException("OFStatisticsFactory not set");
+ this.statistics = statisticsFactory.parseStatistics(this.getType(),
+ this.statisticType, data, super.getLengthU() - MINIMUM_LENGTH);
+ }
+
+ @Override
+ public void writeTo(ChannelBuffer data) {
+ super.writeTo(data);
+ data.writeShort(this.statisticType.getTypeValue());
+ data.writeShort(this.flags);
+ if (this.statistics != null) {
+ for (OFStatistics statistic : this.statistics) {
+ statistic.writeTo(data);
+ }
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 317;
+ int result = super.hashCode();
+ result = prime * result + flags;
+ result = prime * result
+ + ((statisticType == null) ? 0 : statisticType.hashCode());
+ result = prime * result
+ + ((statistics == null) ? 0 : statistics.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!super.equals(obj)) {
+ return false;
+ }
+ if (!(obj instanceof OFStatisticsMessageBase)) {
+ return false;
+ }
+ OFStatisticsMessageBase other = (OFStatisticsMessageBase) obj;
+ if (flags != other.flags) {
+ return false;
+ }
+ if (statisticType == null) {
+ if (other.statisticType != null) {
+ return false;
+ }
+ } else if (!statisticType.equals(other.statisticType)) {
+ return false;
+ }
+ if (statistics == null) {
+ if (other.statistics != null) {
+ return false;
+ }
+ } else if (!statistics.equals(other.statistics)) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/OFStatisticsReply.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFStatisticsReply.java
new file mode 100644
index 0000000..ddc7267
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFStatisticsReply.java
@@ -0,0 +1,46 @@
+/**
+* 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.openflow.util.U16;
+
+/**
+ * Represents an ofp_stats_reply message
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class OFStatisticsReply extends OFStatisticsMessageBase {
+ public enum OFStatisticsReplyFlags {
+ REPLY_MORE (1 << 0);
+
+ protected short type;
+
+ OFStatisticsReplyFlags(int type) {
+ this.type = (short) type;
+ }
+
+ public short getTypeValue() {
+ return type;
+ }
+ }
+
+ public OFStatisticsReply() {
+ super();
+ this.type = OFType.STATS_REPLY;
+ this.length = U16.t(OFStatisticsMessageBase.MINIMUM_LENGTH);
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/OFStatisticsRequest.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFStatisticsRequest.java
new file mode 100644
index 0000000..d1d8010
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFStatisticsRequest.java
@@ -0,0 +1,32 @@
+/**
+* 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.openflow.util.U16;
+
+/**
+ * Represents an ofp_stats_request message
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class OFStatisticsRequest extends OFStatisticsMessageBase {
+ public OFStatisticsRequest() {
+ super();
+ this.type = OFType.STATS_REQUEST;
+ this.length = U16.t(OFStatisticsMessageBase.MINIMUM_LENGTH);
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/OFSwitchConfig.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFSwitchConfig.java
new file mode 100644
index 0000000..e04e3fa
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFSwitchConfig.java
@@ -0,0 +1,118 @@
+/**
+* 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.jboss.netty.buffer.ChannelBuffer;
+
+/**
+ * Base class representing ofp_switch_config based messages
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public abstract class OFSwitchConfig extends OFMessage {
+ public static int MINIMUM_LENGTH = 12;
+
+ public enum OFConfigFlags {
+ OFPC_FRAG_NORMAL,
+ OFPC_FRAG_DROP,
+ OFPC_FRAG_REASM,
+ OFPC_FRAG_MASK
+ }
+
+ protected short flags;
+ protected short missSendLength;
+
+ public OFSwitchConfig() {
+ super();
+ super.setLengthU(MINIMUM_LENGTH);
+ }
+
+ /**
+ * @return the flags
+ */
+ public short getFlags() {
+ return flags;
+ }
+
+ /**
+ * @param flags the flags to set
+ */
+ public OFSwitchConfig setFlags(short flags) {
+ this.flags = flags;
+ return this;
+ }
+
+ /**
+ * @return the missSendLength
+ */
+ public short getMissSendLength() {
+ return missSendLength;
+ }
+
+ /**
+ * @param missSendLength the missSendLength to set
+ */
+ public OFSwitchConfig setMissSendLength(short missSendLength) {
+ this.missSendLength = missSendLength;
+ return this;
+ }
+
+ @Override
+ public void readFrom(ChannelBuffer data) {
+ super.readFrom(data);
+ this.flags = data.readShort();
+ this.missSendLength = data.readShort();
+ }
+
+ @Override
+ public void writeTo(ChannelBuffer data) {
+ super.writeTo(data);
+ data.writeShort(this.flags);
+ data.writeShort(this.missSendLength);
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 331;
+ int result = super.hashCode();
+ result = prime * result + flags;
+ result = prime * result + missSendLength;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!super.equals(obj)) {
+ return false;
+ }
+ if (!(obj instanceof OFSwitchConfig)) {
+ return false;
+ }
+ OFSwitchConfig other = (OFSwitchConfig) obj;
+ if (flags != other.flags) {
+ return false;
+ }
+ if (missSendLength != other.missSendLength) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/OFType.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFType.java
new file mode 100644
index 0000000..c828f0a
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFType.java
@@ -0,0 +1,239 @@
+/**
+* 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.lang.reflect.Constructor;
+
+/**
+ * List of OpenFlow types and mappings to wire protocol value and derived
+ * classes
+ *
+ * @author Rob Sherwood (rob.sherwood@stanford.edu)
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ *
+ */
+public enum OFType {
+ HELLO (0, OFHello.class, new Instantiable<OFMessage>() {
+ @Override
+ public OFMessage instantiate() {
+ return new OFHello();
+ }}),
+ ERROR (1, OFError.class, new Instantiable<OFMessage>() {
+ @Override
+ public OFMessage instantiate() {
+ return new OFError();
+ }}),
+ ECHO_REQUEST (2, OFEchoRequest.class, new Instantiable<OFMessage>() {
+ @Override
+ public OFMessage instantiate() {
+ return new OFEchoRequest();
+ }}),
+ ECHO_REPLY (3, OFEchoReply.class, new Instantiable<OFMessage>() {
+ @Override
+ public OFMessage instantiate() {
+ return new OFEchoReply();
+ }}),
+ VENDOR (4, OFVendor.class, new Instantiable<OFMessage>() {
+ @Override
+ public OFMessage instantiate() {
+ return new OFVendor();
+ }}),
+ FEATURES_REQUEST (5, OFFeaturesRequest.class, new Instantiable<OFMessage>() {
+ @Override
+ public OFMessage instantiate() {
+ return new OFFeaturesRequest();
+ }}),
+ FEATURES_REPLY (6, OFFeaturesReply.class, new Instantiable<OFMessage>() {
+ @Override
+ public OFMessage instantiate() {
+ return new OFFeaturesReply();
+ }}),
+ GET_CONFIG_REQUEST (7, OFGetConfigRequest.class, new Instantiable<OFMessage>() {
+ @Override
+ public OFMessage instantiate() {
+ return new OFGetConfigRequest();
+ }}),
+ GET_CONFIG_REPLY (8, OFGetConfigReply.class, new Instantiable<OFMessage>() {
+ @Override
+ public OFMessage instantiate() {
+ return new OFGetConfigReply();
+ }}),
+ SET_CONFIG (9, OFSetConfig.class, new Instantiable<OFMessage>() {
+ @Override
+ public OFMessage instantiate() {
+ return new OFSetConfig();
+ }}),
+ PACKET_IN (10, OFPacketIn.class, new Instantiable<OFMessage>() {
+ @Override
+ public OFMessage instantiate() {
+ return new OFPacketIn();
+ }}),
+ FLOW_REMOVED (11, OFFlowRemoved.class, new Instantiable<OFMessage>() {
+ @Override
+ public OFMessage instantiate() {
+ return new OFFlowRemoved();
+ }}),
+ PORT_STATUS (12, OFPortStatus.class, new Instantiable<OFMessage>() {
+ @Override
+ public OFMessage instantiate() {
+ return new OFPortStatus();
+ }}),
+ PACKET_OUT (13, OFPacketOut.class, new Instantiable<OFMessage>() {
+ @Override
+ public OFMessage instantiate() {
+ return new OFPacketOut();
+ }}),
+ FLOW_MOD (14, OFFlowMod.class, new Instantiable<OFMessage>() {
+ @Override
+ public OFMessage instantiate() {
+ return new OFFlowMod();
+ }}),
+ PORT_MOD (15, OFPortMod.class, new Instantiable<OFMessage>() {
+ @Override
+ public OFMessage instantiate() {
+ return new OFPortMod();
+ }}),
+ STATS_REQUEST (16, OFStatisticsRequest.class, new Instantiable<OFMessage>() {
+ @Override
+ public OFMessage instantiate() {
+ return new OFStatisticsRequest();
+ }}),
+ STATS_REPLY (17, OFStatisticsReply.class, new Instantiable<OFMessage>() {
+ @Override
+ public OFMessage instantiate() {
+ return new OFStatisticsReply();
+ }}),
+ BARRIER_REQUEST (18, OFBarrierRequest.class, new Instantiable<OFMessage>() {
+ @Override
+ public OFMessage instantiate() {
+ return new OFBarrierRequest();
+ }}),
+ BARRIER_REPLY (19, OFBarrierReply.class, new Instantiable<OFMessage>() {
+ @Override
+ public OFMessage instantiate() {
+ return new OFBarrierReply();
+ }});
+
+ static OFType[] mapping;
+
+ protected Class<? extends OFMessage> clazz;
+ protected Constructor<? extends OFMessage> constructor;
+ protected Instantiable<OFMessage> instantiable;
+ protected byte type;
+
+ /**
+ * Store some information about the OpenFlow type, including wire protocol
+ * type number, length, and derived class
+ *
+ * @param type Wire protocol number associated with this OFType
+ * @param clazz The Java class corresponding to this type of OpenFlow
+ * message
+ * @param instantiator An Instantiator<OFMessage> implementation that creates an
+ * instance of the specified OFMessage
+ */
+ OFType(int type, Class<? extends OFMessage> clazz, Instantiable<OFMessage> instantiator) {
+ this.type = (byte) type;
+ this.clazz = clazz;
+ this.instantiable = instantiator;
+ try {
+ this.constructor = clazz.getConstructor(new Class[]{});
+ } catch (Exception e) {
+ throw new RuntimeException(
+ "Failure getting constructor for class: " + clazz, e);
+ }
+ OFType.addMapping(this.type, this);
+ }
+
+ /**
+ * Adds a mapping from type value to OFType enum
+ *
+ * @param i OpenFlow wire protocol type
+ * @param t type
+ */
+ static public void addMapping(byte i, OFType t) {
+ if (mapping == null)
+ mapping = new OFType[32];
+ OFType.mapping[i] = t;
+ }
+
+ /**
+ * Remove a mapping from type value to OFType enum
+ *
+ * @param i OpenFlow wire protocol type
+ */
+ static public void removeMapping(byte i) {
+ OFType.mapping[i] = null;
+ }
+
+ /**
+ * Given a wire protocol OpenFlow type number, return the OFType associated
+ * with it
+ *
+ * @param i wire protocol number
+ * @return OFType enum type
+ */
+
+ static public OFType valueOf(Byte i) {
+ return OFType.mapping[i];
+ }
+
+ /**
+ * @return Returns the wire protocol value corresponding to this OFType
+ */
+ public byte getTypeValue() {
+ return this.type;
+ }
+
+ /**
+ * @return return the OFMessage subclass corresponding to this OFType
+ */
+ public Class<? extends OFMessage> toClass() {
+ return clazz;
+ }
+
+ /**
+ * Returns the no-argument Constructor of the implementation class for
+ * this OFType
+ * @return the constructor
+ */
+ public Constructor<? extends OFMessage> getConstructor() {
+ return constructor;
+ }
+
+ /**
+ * Returns a new instance of the OFMessage represented by this OFType
+ * @return the new object
+ */
+ public OFMessage newInstance() {
+ return instantiable.instantiate();
+ }
+
+ /**
+ * @return the instantiable
+ */
+ public Instantiable<OFMessage> getInstantiable() {
+ return instantiable;
+ }
+
+ /**
+ * @param instantiable the instantiable to set
+ */
+ public void setInstantiable(Instantiable<OFMessage> instantiable) {
+ this.instantiable = instantiable;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/OFVendor.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFVendor.java
new file mode 100644
index 0000000..8ecb862
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/OFVendor.java
@@ -0,0 +1,131 @@
+/**
+* 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.jboss.netty.buffer.ChannelBuffer;
+import org.openflow.util.U16;
+import org.openflow.protocol.factory.OFVendorDataFactory;
+import org.openflow.protocol.factory.OFVendorDataFactoryAware;
+import org.openflow.protocol.vendor.OFVendorData;
+
+/**
+ * Represents ofp_vendor_header
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class OFVendor extends OFMessage implements OFVendorDataFactoryAware {
+ public static int MINIMUM_LENGTH = 12;
+
+ protected int vendor;
+ protected OFVendorData vendorData;
+ protected OFVendorDataFactory vendorDataFactory;
+
+ public OFVendor() {
+ super();
+ this.type = OFType.VENDOR;
+ this.length = U16.t(MINIMUM_LENGTH);
+ }
+
+ /**
+ * @return the vendor
+ */
+ public int getVendor() {
+ return vendor;
+ }
+
+ /**
+ * @param vendor the vendor to set
+ */
+ public void setVendor(int vendor) {
+ this.vendor = vendor;
+ }
+
+ /**
+ * @return the data
+ */
+ public OFVendorData getVendorData() {
+ return vendorData;
+ }
+
+ /**
+ * @param data the data to set
+ */
+ public void setVendorData(OFVendorData vendorData) {
+ this.vendorData = vendorData;
+ }
+
+ @Override
+ public void setVendorDataFactory(OFVendorDataFactory vendorDataFactory) {
+ this.vendorDataFactory = vendorDataFactory;
+ }
+
+ @Override
+ public void readFrom(ChannelBuffer data) {
+ super.readFrom(data);
+ this.vendor = data.readInt();
+ if (vendorDataFactory == null)
+ throw new RuntimeException("OFVendorDataFactory not set");
+
+ this.vendorData = vendorDataFactory.parseVendorData(vendor,
+ data, super.getLengthU() - MINIMUM_LENGTH);
+ }
+
+ @Override
+ public void writeTo(ChannelBuffer data) {
+ super.writeTo(data);
+ data.writeInt(this.vendor);
+ if (vendorData != null)
+ vendorData.writeTo(data);
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ final int prime = 337;
+ int result = super.hashCode();
+ result = prime * result + vendor;
+ if (vendorData != null)
+ result = prime * result + vendorData.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 (!super.equals(obj))
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ OFVendor other = (OFVendor) obj;
+ if (vendor != other.vendor)
+ return false;
+ if (vendorData == null) {
+ if (other.vendorData != null) {
+ return false;
+ }
+ } else if (!vendorData.equals(other.vendorData)) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFAction.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFAction.java
new file mode 100644
index 0000000..57b5dc1
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFAction.java
@@ -0,0 +1,173 @@
+/**
+* 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.action;
+
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.openflow.util.U16;
+
+/**
+ * The base class for all OpenFlow Actions.
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu) - Mar 11, 2010
+ */
+public class OFAction implements Cloneable {
+ /**
+ * Note the true minimum length for this header is 8 including a pad to 64
+ * bit alignment, however as this base class is used for demuxing an
+ * incoming Action, it is only necessary to read the first 4 bytes. All
+ * Actions extending this class are responsible for reading/writing the
+ * first 8 bytes, including the pad if necessary.
+ */
+ public static int MINIMUM_LENGTH = 4;
+ public static int OFFSET_LENGTH = 2;
+ public static int OFFSET_TYPE = 0;
+
+ protected OFActionType type;
+ protected short length;
+
+ /**
+ * Get the length of this message
+ *
+ * @return
+ */
+ public short getLength() {
+ return length;
+ }
+
+ /**
+ * Get the length of this message, unsigned
+ *
+ * @return
+ */
+ public int getLengthU() {
+ return U16.f(length);
+ }
+
+ /**
+ * Set the length of this message
+ *
+ * @param length
+ */
+ public OFAction setLength(short length) {
+ this.length = length;
+ return this;
+ }
+
+ /**
+ * Get the type of this message
+ *
+ * @return OFActionType enum
+ */
+ public OFActionType getType() {
+ return this.type;
+ }
+
+ /**
+ * Set the type of this message
+ *
+ * @param type
+ */
+ public void setType(OFActionType type) {
+ this.type = type;
+ }
+
+ /**
+ * Returns a summary of the message
+ * @return "ofmsg=v=$version;t=$type:l=$len:xid=$xid"
+ */
+ public String toString() {
+ return "ofaction" +
+ ";t=" + this.getType() +
+ ";l=" + this.getLength();
+ }
+
+ /**
+ * Given the output from toString(),
+ * create a new OFAction
+ * @param val
+ * @return
+ */
+ public static OFAction fromString(String val) {
+ String tokens[] = val.split(";");
+ if (!tokens[0].equals("ofaction"))
+ throw new IllegalArgumentException("expected 'ofaction' but got '" +
+ tokens[0] + "'");
+ String type_tokens[] = tokens[1].split("=");
+ String len_tokens[] = tokens[2].split("=");
+ OFAction action = new OFAction();
+ action.setLength(Short.valueOf(len_tokens[1]));
+ action.setType(OFActionType.valueOf(type_tokens[1]));
+ return action;
+ }
+
+ public void readFrom(ChannelBuffer data) {
+ this.type = OFActionType.valueOf(data.readShort());
+ this.length = data.readShort();
+ // Note missing PAD, see MINIMUM_LENGTH comment for details
+ }
+
+ public void writeTo(ChannelBuffer data) {
+ data.writeShort(type.getTypeValue());
+ data.writeShort(length);
+ // Note missing PAD, see MINIMUM_LENGTH comment for details
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 347;
+ int result = 1;
+ result = prime * result + length;
+ result = prime * result + ((type == null) ? 0 : type.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (!(obj instanceof OFAction)) {
+ return false;
+ }
+ OFAction other = (OFAction) obj;
+ if (length != other.length) {
+ return false;
+ }
+ if (type == null) {
+ if (other.type != null) {
+ return false;
+ }
+ } else if (!type.equals(other.type)) {
+ return false;
+ }
+ return true;
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#clone()
+ */
+ @Override
+ public OFAction clone() throws CloneNotSupportedException {
+ return (OFAction) super.clone();
+ }
+
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionDataLayer.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionDataLayer.java
new file mode 100644
index 0000000..6832728
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionDataLayer.java
@@ -0,0 +1,98 @@
+/**
+* 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.
+**/
+
+/**
+ * @author David Erickson (daviderickson@cs.stanford.edu) - Mar 11, 2010
+ */
+package org.openflow.protocol.action;
+
+import java.util.Arrays;
+
+import net.floodlightcontroller.core.web.serializers.ByteArrayMACSerializer;
+
+import org.codehaus.jackson.map.annotate.JsonSerialize;
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.openflow.protocol.OFPhysicalPort;
+
+/**
+ * Represents an ofp_action_dl_addr
+ * @author David Erickson (daviderickson@cs.stanford.edu) - Mar 11, 2010
+ */
+public abstract class OFActionDataLayer extends OFAction {
+ public static int MINIMUM_LENGTH = 16;
+
+ protected byte[] dataLayerAddress;
+
+ /**
+ * @return the dataLayerAddress
+ */
+ @JsonSerialize(using=ByteArrayMACSerializer.class)
+ public byte[] getDataLayerAddress() {
+ return dataLayerAddress;
+ }
+
+ /**
+ * @param dataLayerAddress the dataLayerAddress to set
+ */
+ public void setDataLayerAddress(byte[] dataLayerAddress) {
+ this.dataLayerAddress = dataLayerAddress;
+ }
+
+ @Override
+ public void readFrom(ChannelBuffer data) {
+ super.readFrom(data);
+ if (this.dataLayerAddress == null)
+ this.dataLayerAddress = new byte[OFPhysicalPort.OFP_ETH_ALEN];
+ data.readBytes(this.dataLayerAddress);
+ data.readInt();
+ data.readShort();
+ }
+
+ @Override
+ public void writeTo(ChannelBuffer data) {
+ super.writeTo(data);
+ data.writeBytes(this.dataLayerAddress);
+ data.writeInt(0);
+ data.writeShort((short) 0);
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 347;
+ int result = super.hashCode();
+ result = prime * result + Arrays.hashCode(dataLayerAddress);
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!super.equals(obj)) {
+ return false;
+ }
+ if (!(obj instanceof OFActionDataLayer)) {
+ return false;
+ }
+ OFActionDataLayer other = (OFActionDataLayer) obj;
+ if (!Arrays.equals(dataLayerAddress, other.dataLayerAddress)) {
+ return false;
+ }
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionDataLayerDestination.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionDataLayerDestination.java
new file mode 100644
index 0000000..48b8d0f
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionDataLayerDestination.java
@@ -0,0 +1,35 @@
+/**
+* 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.action;
+
+/**
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class OFActionDataLayerDestination extends OFActionDataLayer {
+ public OFActionDataLayerDestination() {
+ super();
+ super.setType(OFActionType.SET_DL_DST);
+ super.setLength((short) OFActionDataLayer.MINIMUM_LENGTH);
+ }
+
+ public OFActionDataLayerDestination(byte[] address) {
+ this();
+ this.dataLayerAddress = address;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionDataLayerSource.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionDataLayerSource.java
new file mode 100644
index 0000000..e04561c
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionDataLayerSource.java
@@ -0,0 +1,35 @@
+/**
+* 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.action;
+
+/**
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class OFActionDataLayerSource extends OFActionDataLayer {
+ public OFActionDataLayerSource() {
+ super();
+ super.setType(OFActionType.SET_DL_SRC);
+ super.setLength((short) OFActionDataLayer.MINIMUM_LENGTH);
+ }
+
+ public OFActionDataLayerSource(byte[] address) {
+ this();
+ this.dataLayerAddress = address;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionEnqueue.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionEnqueue.java
new file mode 100644
index 0000000..0ec2fa3
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionEnqueue.java
@@ -0,0 +1,124 @@
+/**
+* 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.
+**/
+
+/**
+ * @author David Erickson (daviderickson@cs.stanford.edu) - Mar 11, 2010
+ */
+package org.openflow.protocol.action;
+
+
+import org.jboss.netty.buffer.ChannelBuffer;
+
+/**
+ * Represents an ofp_action_enqueue
+ * @author David Erickson (daviderickson@cs.stanford.edu) - Mar 11, 2010
+ */
+public class OFActionEnqueue extends OFAction {
+ public static int MINIMUM_LENGTH = 16;
+
+ protected short port;
+ protected int queueId;
+
+ public OFActionEnqueue() {
+ super.setType(OFActionType.OPAQUE_ENQUEUE);
+ super.setLength((short) MINIMUM_LENGTH);
+ }
+
+ public OFActionEnqueue(short port, int queueId) {
+ this();
+ this.port = port;
+ this.queueId = queueId;
+ }
+
+ /**
+ * Get the output port
+ * @return
+ */
+ public short getPort() {
+ return this.port;
+ }
+
+ /**
+ * Set the output port
+ * @param port
+ */
+ public void setPort(short port) {
+ this.port = port;
+ }
+
+ /**
+ * @return the queueId
+ */
+ public int getQueueId() {
+ return queueId;
+ }
+
+ /**
+ * @param queueId the queueId to set
+ */
+ public void setQueueId(int queueId) {
+ this.queueId = queueId;
+ }
+
+ @Override
+ public void readFrom(ChannelBuffer data) {
+ super.readFrom(data);
+ this.port = data.readShort();
+ data.readShort();
+ data.readInt();
+ this.queueId = data.readInt();
+ }
+
+ @Override
+ public void writeTo(ChannelBuffer data) {
+ super.writeTo(data);
+ data.writeShort(this.port);
+ data.writeShort((short) 0);
+ data.writeInt(0);
+ data.writeInt(this.queueId);
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 349;
+ int result = super.hashCode();
+ result = prime * result + port;
+ result = prime * result + queueId;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!super.equals(obj)) {
+ return false;
+ }
+ if (!(obj instanceof OFActionEnqueue)) {
+ return false;
+ }
+ OFActionEnqueue other = (OFActionEnqueue) obj;
+ if (port != other.port) {
+ return false;
+ }
+ if (queueId != other.queueId) {
+ return false;
+ }
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionNetworkLayerAddress.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionNetworkLayerAddress.java
new file mode 100644
index 0000000..dc65ae9
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionNetworkLayerAddress.java
@@ -0,0 +1,86 @@
+/**
+* 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.
+**/
+
+/**
+ * @author David Erickson (daviderickson@cs.stanford.edu) - Mar 11, 2010
+ */
+package org.openflow.protocol.action;
+
+
+import org.jboss.netty.buffer.ChannelBuffer;
+
+/**
+ * Represents an ofp_action_nw_addr
+ * @author David Erickson (daviderickson@cs.stanford.edu) - Mar 11, 2010
+ */
+public abstract class OFActionNetworkLayerAddress extends OFAction {
+ public static int MINIMUM_LENGTH = 8;
+
+ protected int networkAddress;
+
+ /**
+ * @return the networkAddress
+ */
+ public int getNetworkAddress() {
+ return networkAddress;
+ }
+
+ /**
+ * @param networkAddress the networkAddress to set
+ */
+ public void setNetworkAddress(int networkAddress) {
+ this.networkAddress = networkAddress;
+ }
+
+ @Override
+ public void readFrom(ChannelBuffer data) {
+ super.readFrom(data);
+ this.networkAddress = data.readInt();
+ }
+
+ @Override
+ public void writeTo(ChannelBuffer data) {
+ super.writeTo(data);
+ data.writeInt(this.networkAddress);
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 353;
+ int result = super.hashCode();
+ result = prime * result + networkAddress;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!super.equals(obj)) {
+ return false;
+ }
+ if (!(obj instanceof OFActionNetworkLayerAddress)) {
+ return false;
+ }
+ OFActionNetworkLayerAddress other = (OFActionNetworkLayerAddress) obj;
+ if (networkAddress != other.networkAddress) {
+ return false;
+ }
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionNetworkLayerDestination.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionNetworkLayerDestination.java
new file mode 100644
index 0000000..13c14ff
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionNetworkLayerDestination.java
@@ -0,0 +1,35 @@
+/**
+* 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.action;
+
+/**
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class OFActionNetworkLayerDestination extends OFActionNetworkLayerAddress {
+ public OFActionNetworkLayerDestination() {
+ super();
+ super.setType(OFActionType.SET_NW_DST);
+ super.setLength((short) OFActionNetworkLayerAddress.MINIMUM_LENGTH);
+ }
+
+ public OFActionNetworkLayerDestination(int ip) {
+ this();
+ this.networkAddress = ip;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionNetworkLayerSource.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionNetworkLayerSource.java
new file mode 100644
index 0000000..ef1d005
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionNetworkLayerSource.java
@@ -0,0 +1,35 @@
+/**
+* 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.action;
+
+/**
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class OFActionNetworkLayerSource extends OFActionNetworkLayerAddress {
+ public OFActionNetworkLayerSource() {
+ super();
+ super.setType(OFActionType.SET_NW_SRC);
+ super.setLength((short) OFActionNetworkLayerAddress.MINIMUM_LENGTH);
+ }
+
+ public OFActionNetworkLayerSource(int ip) {
+ this();
+ this.networkAddress = ip;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionNetworkTypeOfService.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionNetworkTypeOfService.java
new file mode 100644
index 0000000..0d38180
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionNetworkTypeOfService.java
@@ -0,0 +1,101 @@
+/**
+* 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.
+**/
+
+/**
+ * @author David Erickson (daviderickson@cs.stanford.edu) - Mar 11, 2010
+ */
+package org.openflow.protocol.action;
+
+
+import org.jboss.netty.buffer.ChannelBuffer;
+
+/**
+ * Represents an ofp_action_enqueue
+ * @author David Erickson (daviderickson@cs.stanford.edu) - Mar 11, 2010
+ */
+public class OFActionNetworkTypeOfService extends OFAction {
+ public static int MINIMUM_LENGTH = 8;
+
+ protected byte networkTypeOfService;
+
+ public OFActionNetworkTypeOfService() {
+ super.setType(OFActionType.SET_NW_TOS);
+ super.setLength((short) MINIMUM_LENGTH);
+ }
+
+ public OFActionNetworkTypeOfService(byte tos) {
+ this();
+ this.networkTypeOfService = tos;
+ }
+
+
+ /**
+ * @return the networkTypeOfService
+ */
+ public byte getNetworkTypeOfService() {
+ return networkTypeOfService;
+ }
+
+ /**
+ * @param networkTypeOfService the networkTypeOfService to set
+ */
+ public void setNetworkTypeOfService(byte networkTypeOfService) {
+ this.networkTypeOfService = networkTypeOfService;
+ }
+
+ @Override
+ public void readFrom(ChannelBuffer data) {
+ super.readFrom(data);
+ this.networkTypeOfService = data.readByte();
+ data.readShort();
+ data.readByte();
+ }
+
+ @Override
+ public void writeTo(ChannelBuffer data) {
+ super.writeTo(data);
+ data.writeByte(this.networkTypeOfService);
+ data.writeShort((short) 0);
+ data.writeByte((byte) 0);
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 359;
+ int result = super.hashCode();
+ result = prime * result + networkTypeOfService;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!super.equals(obj)) {
+ return false;
+ }
+ if (!(obj instanceof OFActionNetworkTypeOfService)) {
+ return false;
+ }
+ OFActionNetworkTypeOfService other = (OFActionNetworkTypeOfService) obj;
+ if (networkTypeOfService != other.networkTypeOfService) {
+ return false;
+ }
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionOutput.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionOutput.java
new file mode 100644
index 0000000..b9521d0
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionOutput.java
@@ -0,0 +1,158 @@
+/**
+* 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.
+**/
+
+/**
+ * @author David Erickson (daviderickson@cs.stanford.edu) - Mar 11, 2010
+ */
+package org.openflow.protocol.action;
+
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.openflow.util.U16;
+
+/**
+ * @author David Erickson (daviderickson@cs.stanford.edu) - Mar 11, 2010
+ * @author Rob Sherwood (rob.sherwood@stanford.edu)
+ */
+public class OFActionOutput extends OFAction implements Cloneable {
+ public static int MINIMUM_LENGTH = 8;
+
+ protected short port;
+ protected short maxLength;
+
+ public OFActionOutput() {
+ super.setType(OFActionType.OUTPUT);
+ super.setLength((short) MINIMUM_LENGTH);
+ }
+
+ /**
+ * Create an Output Action sending packets out the specified
+ * OpenFlow port.
+ *
+ * This is the most common creation pattern for OFActions.
+ *
+ * @param port
+ */
+
+ public OFActionOutput(short port) {
+ this(port, (short) 65535);
+ }
+
+ /**
+ * Create an Output Action specifying both the port AND
+ * the snaplen of the packet to send out that port.
+ * The length field is only meaningful when port == OFPort.OFPP_CONTROLLER
+ * @param port
+ * @param maxLength The maximum number of bytes of the packet to send.
+ * Most hardware only supports this value for OFPP_CONTROLLER
+ */
+
+ public OFActionOutput(short port, short maxLength) {
+ super();
+ super.setType(OFActionType.OUTPUT);
+ super.setLength((short) MINIMUM_LENGTH);
+ this.port = port;
+ this.maxLength = maxLength;
+ }
+
+ /**
+ * Get the output port
+ * @return
+ */
+ public short getPort() {
+ return this.port;
+ }
+
+ /**
+ * Set the output port
+ * @param port
+ */
+ public OFActionOutput setPort(short port) {
+ this.port = port;
+ return this;
+ }
+
+ /**
+ * Get the max length to send to the controller
+ * @return
+ */
+ public short getMaxLength() {
+ return this.maxLength;
+ }
+
+ /**
+ * Set the max length to send to the controller
+ * @param maxLength
+ */
+ public OFActionOutput setMaxLength(short maxLength) {
+ this.maxLength = maxLength;
+ return this;
+ }
+
+ @Override
+ public void readFrom(ChannelBuffer data) {
+ super.readFrom(data);
+ this.port = data.readShort();
+ this.maxLength = data.readShort();
+ }
+
+ @Override
+ public void writeTo(ChannelBuffer data) {
+ super.writeTo(data);
+ data.writeShort(port);
+ data.writeShort(maxLength);
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 367;
+ int result = super.hashCode();
+ result = prime * result + maxLength;
+ result = prime * result + port;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!super.equals(obj)) {
+ return false;
+ }
+ if (!(obj instanceof OFActionOutput)) {
+ return false;
+ }
+ OFActionOutput other = (OFActionOutput) obj;
+ if (maxLength != other.maxLength) {
+ return false;
+ }
+ if (port != other.port) {
+ return false;
+ }
+ return true;
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return "OFActionOutput [maxLength=" + maxLength + ", port=" + U16.f(port)
+ + ", length=" + length + ", type=" + type + "]";
+ }
+}
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionStripVirtualLan.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionStripVirtualLan.java
new file mode 100644
index 0000000..7d6b849
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionStripVirtualLan.java
@@ -0,0 +1,53 @@
+/**
+* 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.
+**/
+
+/**
+ * @author David Erickson (daviderickson@cs.stanford.edu) - Mar 11, 2010
+ */
+package org.openflow.protocol.action;
+
+
+import org.jboss.netty.buffer.ChannelBuffer;
+
+
+/**
+ * Represents an ofp_action_strip_vlan
+ * @author David Erickson (daviderickson@cs.stanford.edu) - Mar 11, 2010
+ */
+public class OFActionStripVirtualLan extends OFAction {
+ public static int MINIMUM_LENGTH = 8;
+
+ public OFActionStripVirtualLan() {
+ super();
+ super.setType(OFActionType.STRIP_VLAN);
+ super.setLength((short) MINIMUM_LENGTH);
+ }
+
+ @Override
+ public void readFrom(ChannelBuffer data) {
+ super.readFrom(data);
+ // PAD
+ data.readInt();
+ }
+
+ @Override
+ public void writeTo(ChannelBuffer data) {
+ super.writeTo(data);
+ // PAD
+ data.writeInt(0);
+ }
+}
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionTransportLayer.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionTransportLayer.java
new file mode 100644
index 0000000..0bc09c9
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionTransportLayer.java
@@ -0,0 +1,88 @@
+/**
+* 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.
+**/
+
+/**
+ * @author David Erickson (daviderickson@cs.stanford.edu) - Mar 11, 2010
+ */
+package org.openflow.protocol.action;
+
+
+import org.jboss.netty.buffer.ChannelBuffer;
+
+/**
+ * Represents an ofp_action_tp_port
+ * @author David Erickson (daviderickson@cs.stanford.edu) - Mar 11, 2010
+ */
+public abstract class OFActionTransportLayer extends OFAction {
+ public static int MINIMUM_LENGTH = 8;
+
+ protected short transportPort;
+
+ /**
+ * @return the transportPort
+ */
+ public short getTransportPort() {
+ return transportPort;
+ }
+
+ /**
+ * @param transportPort the transportPort to set
+ */
+ public void setTransportPort(short transportPort) {
+ this.transportPort = transportPort;
+ }
+
+ @Override
+ public void readFrom(ChannelBuffer data) {
+ super.readFrom(data);
+ this.transportPort = data.readShort();
+ data.readShort();
+ }
+
+ @Override
+ public void writeTo(ChannelBuffer data) {
+ super.writeTo(data);
+ data.writeShort(this.transportPort);
+ data.writeShort((short) 0);
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 373;
+ int result = super.hashCode();
+ result = prime * result + transportPort;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!super.equals(obj)) {
+ return false;
+ }
+ if (!(obj instanceof OFActionTransportLayer)) {
+ return false;
+ }
+ OFActionTransportLayer other = (OFActionTransportLayer) obj;
+ if (transportPort != other.transportPort) {
+ return false;
+ }
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionTransportLayerDestination.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionTransportLayerDestination.java
new file mode 100644
index 0000000..7e7b0f1
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionTransportLayerDestination.java
@@ -0,0 +1,35 @@
+/**
+* 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.action;
+
+/**
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class OFActionTransportLayerDestination extends OFActionTransportLayer {
+ public OFActionTransportLayerDestination() {
+ super();
+ super.setType(OFActionType.SET_TP_DST);
+ super.setLength((short) OFActionTransportLayer.MINIMUM_LENGTH);
+ }
+
+ public OFActionTransportLayerDestination(short port) {
+ this();
+ this.transportPort = port;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionTransportLayerSource.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionTransportLayerSource.java
new file mode 100644
index 0000000..385aa53
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionTransportLayerSource.java
@@ -0,0 +1,35 @@
+/**
+* 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.action;
+
+/**
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class OFActionTransportLayerSource extends OFActionTransportLayer {
+ public OFActionTransportLayerSource() {
+ super();
+ super.setType(OFActionType.SET_TP_SRC);
+ super.setLength((short) OFActionTransportLayer.MINIMUM_LENGTH);
+ }
+
+ public OFActionTransportLayerSource(short port) {
+ this();
+ this.transportPort = port;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionType.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionType.java
new file mode 100644
index 0000000..b0c2c47
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionType.java
@@ -0,0 +1,203 @@
+/**
+* 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.action;
+
+import java.lang.reflect.Constructor;
+
+import org.openflow.protocol.Instantiable;
+
+/**
+ * List of OpenFlow Action types and mappings to wire protocol value and
+ * derived classes
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public enum OFActionType {
+ OUTPUT (0, OFActionOutput.class, new Instantiable<OFAction>() {
+ @Override
+ public OFAction instantiate() {
+ return new OFActionOutput();
+ }}),
+ SET_VLAN_ID (1, OFActionVirtualLanIdentifier.class, new Instantiable<OFAction>() {
+ @Override
+ public OFAction instantiate() {
+ return new OFActionVirtualLanIdentifier();
+ }}),
+ SET_VLAN_PCP (2, OFActionVirtualLanPriorityCodePoint.class, new Instantiable<OFAction>() {
+ @Override
+ public OFAction instantiate() {
+ return new OFActionVirtualLanPriorityCodePoint();
+ }}),
+ STRIP_VLAN (3, OFActionStripVirtualLan.class, new Instantiable<OFAction>() {
+ @Override
+ public OFAction instantiate() {
+ return new OFActionStripVirtualLan();
+ }}),
+ SET_DL_SRC (4, OFActionDataLayerSource.class, new Instantiable<OFAction>() {
+ @Override
+ public OFAction instantiate() {
+ return new OFActionDataLayerSource();
+ }}),
+ SET_DL_DST (5, OFActionDataLayerDestination.class, new Instantiable<OFAction>() {
+ @Override
+ public OFAction instantiate() {
+ return new OFActionDataLayerDestination();
+ }}),
+ SET_NW_SRC (6, OFActionNetworkLayerSource.class, new Instantiable<OFAction>() {
+ @Override
+ public OFAction instantiate() {
+ return new OFActionNetworkLayerSource();
+ }}),
+ SET_NW_DST (7, OFActionNetworkLayerDestination.class, new Instantiable<OFAction>() {
+ @Override
+ public OFAction instantiate() {
+ return new OFActionNetworkLayerDestination();
+ }}),
+ SET_NW_TOS (8, OFActionNetworkTypeOfService.class, new Instantiable<OFAction>() {
+ @Override
+ public OFAction instantiate() {
+ return new OFActionNetworkTypeOfService();
+ }}),
+ SET_TP_SRC (9, OFActionTransportLayerSource.class, new Instantiable<OFAction>() {
+ @Override
+ public OFAction instantiate() {
+ return new OFActionTransportLayerSource();
+ }}),
+ SET_TP_DST (10, OFActionTransportLayerDestination.class, new Instantiable<OFAction>() {
+ @Override
+ public OFAction instantiate() {
+ return new OFActionTransportLayerDestination();
+ }}),
+ OPAQUE_ENQUEUE (11, OFActionEnqueue.class, new Instantiable<OFAction>() {
+ @Override
+ public OFAction instantiate() {
+ return new OFActionEnqueue();
+ }}),
+ VENDOR (0xffff, OFActionVendor.class, new Instantiable<OFAction>() {
+ @Override
+ public OFAction instantiate() {
+ return new OFActionVendor();
+ }});
+
+ protected static OFActionType[] mapping;
+
+ protected Class<? extends OFAction> clazz;
+ protected Constructor<? extends OFAction> constructor;
+ protected Instantiable<OFAction> instantiable;
+ protected int minLen;
+ protected short type;
+
+ /**
+ * Store some information about the OpenFlow Action type, including wire
+ * protocol type number, length, and derrived class
+ *
+ * @param type Wire protocol number associated with this OFType
+ * @param clazz The Java class corresponding to this type of OpenFlow Action
+ * @param instantiable the instantiable for the OFAction this type represents
+ */
+ OFActionType(int type, Class<? extends OFAction> clazz, Instantiable<OFAction> instantiable) {
+ this.type = (short) type;
+ this.clazz = clazz;
+ this.instantiable = instantiable;
+ try {
+ this.constructor = clazz.getConstructor(new Class[]{});
+ } catch (Exception e) {
+ throw new RuntimeException(
+ "Failure getting constructor for class: " + clazz, e);
+ }
+ OFActionType.addMapping(this.type, this);
+ }
+
+ /**
+ * Adds a mapping from type value to OFActionType enum
+ *
+ * @param i OpenFlow wire protocol Action type value
+ * @param t type
+ */
+ static public void addMapping(short i, OFActionType t) {
+ if (mapping == null)
+ mapping = new OFActionType[16];
+ // bring higher mappings down to the edge of our array
+ if (i < 0)
+ i = (short) (16 + i);
+ OFActionType.mapping[i] = t;
+ }
+
+ /**
+ * Given a wire protocol OpenFlow type number, return the OFType associated
+ * with it
+ *
+ * @param i wire protocol number
+ * @return OFType enum type
+ */
+
+ static public OFActionType valueOf(short i) {
+ if (i < 0)
+ i = (short) (16+i);
+ return OFActionType.mapping[i];
+ }
+
+ /**
+ * @return Returns the wire protocol value corresponding to this
+ * OFActionType
+ */
+ public short getTypeValue() {
+ return this.type;
+ }
+
+ /**
+ * @return return the OFAction subclass corresponding to this OFActionType
+ */
+ public Class<? extends OFAction> toClass() {
+ return clazz;
+ }
+
+ /**
+ * Returns the no-argument Constructor of the implementation class for
+ * this OFActionType
+ * @return the constructor
+ */
+ public Constructor<? extends OFAction> getConstructor() {
+ return constructor;
+ }
+
+ /**
+ * Returns a new instance of the OFAction represented by this OFActionType
+ * @return the new object
+ */
+ public OFAction newInstance() {
+ return instantiable.instantiate();
+ }
+
+ /**
+ * @return the instantiable
+ */
+ public Instantiable<OFAction> getInstantiable() {
+ return instantiable;
+ }
+
+ /**
+ * @param instantiable the instantiable to set
+ */
+ public void setInstantiable(Instantiable<OFAction> instantiable) {
+ this.instantiable = instantiable;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionVendor.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionVendor.java
new file mode 100644
index 0000000..b5a15c2
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionVendor.java
@@ -0,0 +1,89 @@
+/**
+* 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.action;
+
+
+import org.jboss.netty.buffer.ChannelBuffer;
+
+/**
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class OFActionVendor extends OFAction {
+ public static int MINIMUM_LENGTH = 8;
+
+ protected int vendor;
+
+ public OFActionVendor() {
+ super();
+ super.setType(OFActionType.VENDOR);
+ super.setLength((short) MINIMUM_LENGTH);
+ }
+
+ /**
+ * @return the vendor
+ */
+ public int getVendor() {
+ return vendor;
+ }
+
+ /**
+ * @param vendor the vendor to set
+ */
+ public void setVendor(int vendor) {
+ this.vendor = vendor;
+ }
+
+ @Override
+ public void readFrom(ChannelBuffer data) {
+ super.readFrom(data);
+ this.vendor = data.readInt();
+ }
+
+ @Override
+ public void writeTo(ChannelBuffer data) {
+ super.writeTo(data);
+ data.writeInt(this.vendor);
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 379;
+ int result = super.hashCode();
+ result = prime * result + vendor;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!super.equals(obj)) {
+ return false;
+ }
+ if (!(obj instanceof OFActionVendor)) {
+ return false;
+ }
+ OFActionVendor other = (OFActionVendor) obj;
+ if (vendor != other.vendor) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionVirtualLanIdentifier.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionVirtualLanIdentifier.java
new file mode 100644
index 0000000..5bd0e0b
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionVirtualLanIdentifier.java
@@ -0,0 +1,98 @@
+/**
+* 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.
+**/
+
+/**
+ * @author David Erickson (daviderickson@cs.stanford.edu) - Mar 11, 2010
+ */
+package org.openflow.protocol.action;
+
+
+import org.jboss.netty.buffer.ChannelBuffer;
+
+/**
+ * Represents an ofp_action_vlan_vid
+ * @author David Erickson (daviderickson@cs.stanford.edu) - Mar 11, 2010
+ */
+public class OFActionVirtualLanIdentifier extends OFAction {
+ public static int MINIMUM_LENGTH = 8;
+
+ protected short virtualLanIdentifier;
+
+ public OFActionVirtualLanIdentifier() {
+ super.setType(OFActionType.SET_VLAN_ID);
+ super.setLength((short) MINIMUM_LENGTH);
+ }
+
+ public OFActionVirtualLanIdentifier(short vlanId) {
+ this();
+ this.virtualLanIdentifier = vlanId;
+ }
+
+ /**
+ * @return the virtualLanIdentifier
+ */
+ public short getVirtualLanIdentifier() {
+ return virtualLanIdentifier;
+ }
+
+ /**
+ * @param virtualLanIdentifier the virtualLanIdentifier to set
+ */
+ public void setVirtualLanIdentifier(short virtualLanIdentifier) {
+ this.virtualLanIdentifier = virtualLanIdentifier;
+ }
+
+ @Override
+ public void readFrom(ChannelBuffer data) {
+ super.readFrom(data);
+ this.virtualLanIdentifier = data.readShort();
+ data.readShort();
+ }
+
+ @Override
+ public void writeTo(ChannelBuffer data) {
+ super.writeTo(data);
+ data.writeShort(this.virtualLanIdentifier);
+ data.writeShort((short) 0);
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 383;
+ int result = super.hashCode();
+ result = prime * result + virtualLanIdentifier;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!super.equals(obj)) {
+ return false;
+ }
+ if (!(obj instanceof OFActionVirtualLanIdentifier)) {
+ return false;
+ }
+ OFActionVirtualLanIdentifier other = (OFActionVirtualLanIdentifier) obj;
+ if (virtualLanIdentifier != other.virtualLanIdentifier) {
+ return false;
+ }
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionVirtualLanPriorityCodePoint.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionVirtualLanPriorityCodePoint.java
new file mode 100644
index 0000000..9202df3
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/action/OFActionVirtualLanPriorityCodePoint.java
@@ -0,0 +1,100 @@
+/**
+* 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.
+**/
+
+/**
+ * @author David Erickson (daviderickson@cs.stanford.edu) - Mar 11, 2010
+ */
+package org.openflow.protocol.action;
+
+
+import org.jboss.netty.buffer.ChannelBuffer;
+
+/**
+ * Represents an ofp_action_vlan_pcp
+ * @author David Erickson (daviderickson@cs.stanford.edu) - Mar 11, 2010
+ */
+public class OFActionVirtualLanPriorityCodePoint extends OFAction {
+ public static int MINIMUM_LENGTH = 8;
+
+ protected byte virtualLanPriorityCodePoint;
+
+ public OFActionVirtualLanPriorityCodePoint() {
+ super.setType(OFActionType.SET_VLAN_PCP);
+ super.setLength((short) MINIMUM_LENGTH);
+ }
+
+ public OFActionVirtualLanPriorityCodePoint(byte priority) {
+ this();
+ this.virtualLanPriorityCodePoint = priority;
+ }
+
+ /**
+ * @return the virtualLanPriorityCodePoint
+ */
+ public byte getVirtualLanPriorityCodePoint() {
+ return virtualLanPriorityCodePoint;
+ }
+
+ /**
+ * @param virtualLanPriorityCodePoint the virtualLanPriorityCodePoint to set
+ */
+ public void setVirtualLanPriorityCodePoint(byte virtualLanPriorityCodePoint) {
+ this.virtualLanPriorityCodePoint = virtualLanPriorityCodePoint;
+ }
+
+ @Override
+ public void readFrom(ChannelBuffer data) {
+ super.readFrom(data);
+ this.virtualLanPriorityCodePoint = data.readByte();
+ data.readShort(); // pad
+ data.readByte(); // pad
+ }
+
+ @Override
+ public void writeTo(ChannelBuffer data) {
+ super.writeTo(data);
+ data.writeByte(this.virtualLanPriorityCodePoint);
+ data.writeShort((short) 0);
+ data.writeByte((byte) 0);
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 389;
+ int result = super.hashCode();
+ result = prime * result + virtualLanPriorityCodePoint;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!super.equals(obj)) {
+ return false;
+ }
+ if (!(obj instanceof OFActionVirtualLanPriorityCodePoint)) {
+ return false;
+ }
+ OFActionVirtualLanPriorityCodePoint other = (OFActionVirtualLanPriorityCodePoint) obj;
+ if (virtualLanPriorityCodePoint != other.virtualLanPriorityCodePoint) {
+ return false;
+ }
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/factory/BasicFactory.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/factory/BasicFactory.java
new file mode 100644
index 0000000..7b15e82
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/factory/BasicFactory.java
@@ -0,0 +1,294 @@
+/**
+* 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.factory;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.openflow.protocol.OFMessage;
+import org.openflow.protocol.OFType;
+import org.openflow.protocol.action.OFAction;
+import org.openflow.protocol.action.OFActionType;
+import org.openflow.protocol.statistics.OFStatistics;
+import org.openflow.protocol.statistics.OFStatisticsType;
+import org.openflow.protocol.statistics.OFVendorStatistics;
+import org.openflow.protocol.vendor.OFByteArrayVendorData;
+import org.openflow.protocol.vendor.OFVendorData;
+import org.openflow.protocol.vendor.OFVendorDataType;
+import org.openflow.protocol.vendor.OFVendorId;
+
+
+/**
+ * A basic OpenFlow factory that supports naive creation of both Messages and
+ * Actions.
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ * @author Rob Sherwood (rob.sherwood@stanford.edu)
+ *
+ */
+public class BasicFactory implements OFMessageFactory, OFActionFactory,
+ OFStatisticsFactory, OFVendorDataFactory {
+ @Override
+ public OFMessage getMessage(OFType t) {
+ return t.newInstance();
+ }
+
+ @Override
+ public List<OFMessage> parseMessage(ChannelBuffer data) throws MessageParseException {
+ List<OFMessage> msglist = new ArrayList<OFMessage>();
+ OFMessage msg = null;
+
+ while (data.readableBytes() >= OFMessage.MINIMUM_LENGTH) {
+ data.markReaderIndex();
+ msg = this.parseMessageOne(data);
+ if (msg == null) {
+ data.resetReaderIndex();
+ break;
+ }
+ else {
+ msglist.add(msg);
+ }
+ }
+
+ if (msglist.size() == 0) {
+ return null;
+ }
+ return msglist;
+
+ }
+
+ public OFMessage parseMessageOne(ChannelBuffer data) throws MessageParseException {
+ try {
+ OFMessage demux = new OFMessage();
+ OFMessage ofm = null;
+
+ if (data.readableBytes() < OFMessage.MINIMUM_LENGTH)
+ return ofm;
+
+ data.markReaderIndex();
+ demux.readFrom(data);
+ data.resetReaderIndex();
+
+ if (demux.getLengthU() > data.readableBytes())
+ return ofm;
+
+ ofm = getMessage(demux.getType());
+ if (ofm == null)
+ return null;
+
+ if (ofm instanceof OFActionFactoryAware) {
+ ((OFActionFactoryAware)ofm).setActionFactory(this);
+ }
+ if (ofm instanceof OFMessageFactoryAware) {
+ ((OFMessageFactoryAware)ofm).setMessageFactory(this);
+ }
+ if (ofm instanceof OFStatisticsFactoryAware) {
+ ((OFStatisticsFactoryAware)ofm).setStatisticsFactory(this);
+ }
+ if (ofm instanceof OFVendorDataFactoryAware) {
+ ((OFVendorDataFactoryAware)ofm).setVendorDataFactory(this);
+ }
+ ofm.readFrom(data);
+ if (OFMessage.class.equals(ofm.getClass())) {
+ // advance the position for un-implemented messages
+ data.readerIndex(data.readerIndex()+(ofm.getLengthU() -
+ OFMessage.MINIMUM_LENGTH));
+ }
+
+ return ofm;
+ } catch (Exception e) {
+ /* Write the offending data along with the error message */
+ data.resetReaderIndex();
+ String msg =
+ "Message Parse Error for packet:" + dumpBuffer(data) +
+ "\nException: " + e.toString();
+ data.resetReaderIndex();
+
+ throw new MessageParseException(msg, e);
+ }
+ }
+
+ @Override
+ public OFAction getAction(OFActionType t) {
+ return t.newInstance();
+ }
+
+ @Override
+ public List<OFAction> parseActions(ChannelBuffer data, int length) {
+ return parseActions(data, length, 0);
+ }
+
+ @Override
+ public List<OFAction> parseActions(ChannelBuffer data, int length, int limit) {
+ List<OFAction> results = new ArrayList<OFAction>();
+ OFAction demux = new OFAction();
+ OFAction ofa;
+ int end = data.readerIndex() + length;
+
+ while (limit == 0 || results.size() <= limit) {
+ if ((data.readableBytes() < OFAction.MINIMUM_LENGTH ||
+ (data.readerIndex() + OFAction.MINIMUM_LENGTH) > end))
+ return results;
+
+ data.markReaderIndex();
+ demux.readFrom(data);
+ data.resetReaderIndex();
+
+ if ((demux.getLengthU() > data.readableBytes() ||
+ (data.readerIndex() + demux.getLengthU()) > end))
+ return results;
+
+ ofa = getAction(demux.getType());
+ ofa.readFrom(data);
+ if (OFAction.class.equals(ofa.getClass())) {
+ // advance the position for un-implemented messages
+ data.readerIndex(data.readerIndex()+(ofa.getLengthU() -
+ OFAction.MINIMUM_LENGTH));
+ }
+ results.add(ofa);
+ }
+
+ return results;
+ }
+
+ @Override
+ public OFActionFactory getActionFactory() {
+ return this;
+ }
+
+ @Override
+ public OFStatistics getStatistics(OFType t, OFStatisticsType st) {
+ return st.newInstance(t);
+ }
+
+ @Override
+ public List<OFStatistics> parseStatistics(OFType t, OFStatisticsType st,
+ ChannelBuffer data, int length) {
+ return parseStatistics(t, st, data, length, 0);
+ }
+
+ /**
+ * @param t
+ * OFMessage type: should be one of stats_request or stats_reply
+ * @param st
+ * statistics type of this message, e.g., DESC, TABLE
+ * @param data
+ * buffer to read from
+ * @param length
+ * length of statistics
+ * @param limit
+ * number of statistics to grab; 0 == all
+ *
+ * @return list of statistics
+ */
+
+ @Override
+ public List<OFStatistics> parseStatistics(OFType t, OFStatisticsType st,
+ ChannelBuffer data, int length, int limit) {
+ List<OFStatistics> results = new ArrayList<OFStatistics>();
+ OFStatistics statistics = getStatistics(t, st);
+
+ int start = data.readerIndex();
+ int count = 0;
+
+ while (limit == 0 || results.size() <= limit) {
+ // TODO Create a separate MUX/DEMUX path for vendor stats
+ if (statistics instanceof OFVendorStatistics)
+ ((OFVendorStatistics)statistics).setLength(length);
+
+ /**
+ * can't use data.remaining() here, b/c there could be other data
+ * buffered past this message
+ */
+ if ((length - count) >= statistics.getLength()) {
+ if (statistics instanceof OFActionFactoryAware)
+ ((OFActionFactoryAware)statistics).setActionFactory(this);
+ statistics.readFrom(data);
+ results.add(statistics);
+ count += statistics.getLength();
+ statistics = getStatistics(t, st);
+ } else {
+ if (count < length) {
+ /**
+ * Nasty case: partial/incomplete statistic found even
+ * though we have a full message. Found when NOX sent
+ * agg_stats request with wrong agg statistics length (52
+ * instead of 56)
+ *
+ * just throw the rest away, or we will break framing
+ */
+ data.readerIndex(start + length);
+ }
+ return results;
+ }
+ }
+ return results; // empty; no statistics at all
+ }
+
+
+ @Override
+ public OFVendorData getVendorData(OFVendorId vendorId,
+ OFVendorDataType vendorDataType) {
+ if (vendorDataType == null)
+ return null;
+
+ return vendorDataType.newInstance();
+ }
+
+ /**
+ * Attempts to parse and return the OFVendorData contained in the given
+ * ChannelBuffer, beginning right after the vendor id.
+ * @param vendor the vendor id that was parsed from the OFVendor message.
+ * @param data the ChannelBuffer from which to parse the vendor data
+ * @param length the length to the end of the enclosing message.
+ * @return an OFVendorData instance
+ */
+ public OFVendorData parseVendorData(int vendor, ChannelBuffer data,
+ int length) {
+ OFVendorDataType vendorDataType = null;
+ OFVendorId vendorId = OFVendorId.lookupVendorId(vendor);
+ if (vendorId != null) {
+ data.markReaderIndex();
+ vendorDataType = vendorId.parseVendorDataType(data, length);
+ data.resetReaderIndex();
+ }
+
+ OFVendorData vendorData = getVendorData(vendorId, vendorDataType);
+ if (vendorData == null)
+ vendorData = new OFByteArrayVendorData();
+
+ vendorData.readFrom(data, length);
+
+ return vendorData;
+ }
+
+ public static String dumpBuffer(ChannelBuffer data) {
+ // NOTE: Reads all the bytes in buffer from current read offset.
+ // Set/Reset ReaderIndex if you want to read from a different location
+ int len = data.readableBytes();
+ StringBuffer sb = new StringBuffer();
+ for (int i=0 ; i<len; i++) {
+ if (i%32 == 0) sb.append("\n");
+ if (i%4 == 0) sb.append(" ");
+ sb.append(String.format("%02x", data.getUnsignedByte(i)));
+ }
+ return sb.toString();
+ }
+
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/factory/MessageParseException.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/factory/MessageParseException.java
new file mode 100644
index 0000000..b685e5d
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/factory/MessageParseException.java
@@ -0,0 +1,29 @@
+package org.openflow.protocol.factory;
+
+/**
+ * Exception thrown when an openflow message fails to parse properly
+ */
+public class MessageParseException extends Exception {
+ /**
+ *
+ */
+ private static final long serialVersionUID = -75893812926304726L;
+
+ public MessageParseException() {
+ super();
+ }
+
+ public MessageParseException(String message, Throwable cause) {
+ super(message, cause);
+ this.setStackTrace(cause.getStackTrace());
+ }
+
+ public MessageParseException(String message) {
+ super(message);
+ }
+
+ public MessageParseException(Throwable cause) {
+ super(cause);
+ this.setStackTrace(cause.getStackTrace());
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/factory/OFActionFactory.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/factory/OFActionFactory.java
new file mode 100644
index 0000000..c3cd062
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/factory/OFActionFactory.java
@@ -0,0 +1,61 @@
+/**
+* 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.factory;
+
+import java.util.List;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.openflow.protocol.action.OFAction;
+import org.openflow.protocol.action.OFActionType;
+
+
+/**
+ * The interface to factories used for retrieving OFAction instances. All
+ * methods are expected to be thread-safe.
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public interface OFActionFactory {
+ /**
+ * Retrieves an OFAction instance corresponding to the specified
+ * OFActionType
+ * @param t the type of the OFAction to be retrieved
+ * @return an OFAction instance
+ */
+ public OFAction getAction(OFActionType t);
+
+ /**
+ * Attempts to parse and return all OFActions contained in the given
+ * ByteBuffer, beginning at the ByteBuffer's position, and ending at
+ * position+length.
+ * @param data the ChannelBuffer to parse for OpenFlow actions
+ * @param length the number of Bytes to examine for OpenFlow actions
+ * @return a list of OFAction instances
+ */
+ public List<OFAction> parseActions(ChannelBuffer data, int length);
+
+ /**
+ * Attempts to parse and return all OFActions contained in the given
+ * ByteBuffer, beginning at the ByteBuffer's position, and ending at
+ * position+length.
+ * @param data the ChannelBuffer to parse for OpenFlow actions
+ * @param length the number of Bytes to examine for OpenFlow actions
+ * @param limit the maximum number of messages to return, 0 means no limit
+ * @return a list of OFAction instances
+ */
+ public List<OFAction> parseActions(ChannelBuffer data, int length, int limit);
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/factory/OFActionFactoryAware.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/factory/OFActionFactoryAware.java
new file mode 100644
index 0000000..a97a95c
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/factory/OFActionFactoryAware.java
@@ -0,0 +1,31 @@
+/**
+* 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.factory;
+
+/**
+ * Objects implementing this interface are expected to be instantiated with an
+ * instance of an OFActionFactory
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public interface OFActionFactoryAware {
+ /**
+ * Sets the OFActionFactory
+ * @param actionFactory
+ */
+ public void setActionFactory(OFActionFactory actionFactory);
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/factory/OFMessageFactory.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/factory/OFMessageFactory.java
new file mode 100644
index 0000000..8bb7045
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/factory/OFMessageFactory.java
@@ -0,0 +1,55 @@
+/**
+* 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.factory;
+
+import java.util.List;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.openflow.protocol.OFMessage;
+import org.openflow.protocol.OFType;
+
+
+/**
+ * The interface to factories used for retrieving OFMessage instances. All
+ * methods are expected to be thread-safe.
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public interface OFMessageFactory {
+ /**
+ * Retrieves an OFMessage instance corresponding to the specified OFType
+ * @param t the type of the OFMessage to be retrieved
+ * @return an OFMessage instance
+ */
+ public OFMessage getMessage(OFType t);
+
+ /**
+ * Attempts to parse and return a OFMessages contained in the given
+ * ChannelBuffer, beginning at the ChannelBuffer's position, and ending at the
+ * after the first parsed message
+ * @param data the ChannelBuffer to parse for an OpenFlow message
+ * @return a list of OFMessage instances
+ * @throws MessageParseException
+ */
+ public List<OFMessage> parseMessage(ChannelBuffer data) throws MessageParseException;
+
+ /**
+ * Retrieves an OFActionFactory
+ * @return an OFActionFactory
+ */
+ public OFActionFactory getActionFactory();
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/factory/OFMessageFactoryAware.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/factory/OFMessageFactoryAware.java
new file mode 100644
index 0000000..adb1421
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/factory/OFMessageFactoryAware.java
@@ -0,0 +1,35 @@
+/**
+* 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.factory;
+
+/**
+ * @author Rob Sherwood (rob.sherwood@stanford.edu)
+ *
+ */
+public interface OFMessageFactoryAware {
+
+ /**
+ * Sets the message factory for this object
+ *
+ * @param factory
+ */
+ void setMessageFactory(OFMessageFactory factory);
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/factory/OFStatisticsFactory.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/factory/OFStatisticsFactory.java
new file mode 100644
index 0000000..32eb3cb
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/factory/OFStatisticsFactory.java
@@ -0,0 +1,72 @@
+/**
+* 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.factory;
+
+import java.util.List;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.openflow.protocol.OFType;
+import org.openflow.protocol.statistics.OFStatistics;
+import org.openflow.protocol.statistics.OFStatisticsType;
+
+
+/**
+ * The interface to factories used for retrieving OFStatistics instances. All
+ * methods are expected to be thread-safe.
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public interface OFStatisticsFactory {
+ /**
+ * Retrieves an OFStatistics instance corresponding to the specified
+ * OFStatisticsType
+ * @param t the type of the containing OFMessage, only accepts statistics
+ * request or reply
+ * @param st the type of the OFStatistics to be retrieved
+ * @return an OFStatistics instance
+ */
+ public OFStatistics getStatistics(OFType t, OFStatisticsType st);
+
+ /**
+ * Attempts to parse and return all OFStatistics contained in the given
+ * ByteBuffer, beginning at the ByteBuffer's position, and ending at
+ * position+length.
+ * @param t the type of the containing OFMessage, only accepts statistics
+ * request or reply
+ * @param st the type of the OFStatistics to be retrieved
+ * @param data the ChannelBuffer to parse for OpenFlow Statistics
+ * @param length the number of Bytes to examine for OpenFlow Statistics
+ * @return a list of OFStatistics instances
+ */
+ public List<OFStatistics> parseStatistics(OFType t,
+ OFStatisticsType st, ChannelBuffer data, int length);
+
+ /**
+ * Attempts to parse and return all OFStatistics contained in the given
+ * ByteBuffer, beginning at the ByteBuffer's position, and ending at
+ * position+length.
+ * @param t the type of the containing OFMessage, only accepts statistics
+ * request or reply
+ * @param st the type of the OFStatistics to be retrieved
+ * @param data the ChannelBuffer to parse for OpenFlow Statistics
+ * @param length the number of Bytes to examine for OpenFlow Statistics
+ * @param limit the maximum number of messages to return, 0 means no limit
+ * @return a list of OFStatistics instances
+ */
+ public List<OFStatistics> parseStatistics(OFType t,
+ OFStatisticsType st, ChannelBuffer data, int length, int limit);
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/factory/OFStatisticsFactoryAware.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/factory/OFStatisticsFactoryAware.java
new file mode 100644
index 0000000..52ab09a
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/factory/OFStatisticsFactoryAware.java
@@ -0,0 +1,31 @@
+/**
+* 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.factory;
+
+/**
+ * Objects implementing this interface are expected to be instantiated with an
+ * instance of an OFStatisticsFactory
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public interface OFStatisticsFactoryAware {
+ /**
+ * Sets the OFStatisticsFactory
+ * @param statisticsFactory
+ */
+ public void setStatisticsFactory(OFStatisticsFactory statisticsFactory);
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/factory/OFVendorDataFactory.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/factory/OFVendorDataFactory.java
new file mode 100644
index 0000000..d754a4a
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/factory/OFVendorDataFactory.java
@@ -0,0 +1,69 @@
+/**
+* Copyright 2011, Big Switch Networks, Inc.
+* Originally created by David Erickson & Rob Sherwood, 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 org.openflow.protocol.factory;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.openflow.protocol.vendor.OFVendorData;
+import org.openflow.protocol.vendor.OFVendorDataType;
+import org.openflow.protocol.vendor.OFVendorId;
+
+/**
+ * The interface to factories used for parsing/creating OFVendorData instances.
+ * All methods are expected to be thread-safe.
+ *
+ * @author Rob Vaterlaus (rob.vaterlaus@bigswitch.com)
+ */
+public interface OFVendorDataFactory {
+ /**
+ * Retrieves an OFVendorData instance corresponding to the specified
+ * OFVendorId and OFVendorDataType. There are 3 possible cases for
+ * how this will be called:
+ *
+ * 1) If the vendor id in the OFVendor message is an unknown value,
+ * then this method is called with both vendorId and vendorDataType
+ * set to null. In this case typically the factory method should
+ * return an instance of OFGenericVendorData that just contains
+ * the raw byte array of the vendor data.
+ *
+ * 2) If the vendor id is known but no vendor data type has been
+ * registered for the data in the message, then vendorId is set to
+ * the appropriate OFVendorId instance and OFVendorDataType is set
+ * to null. This would typically be handled the same way as #1
+ *
+ * 3) If both the vendor id and and vendor data type are known, then
+ * typically you'd just call the method in OFVendorDataType to
+ * instantiate the appropriate subclass of OFVendorData.
+ *
+ * @param vendorId the vendorId of the containing OFVendor message
+ * @param vendorDataType the type of the OFVendorData to be retrieved
+ * @return an OFVendorData instance
+ */
+ public OFVendorData getVendorData(OFVendorId vendorId,
+ OFVendorDataType vendorDataType);
+
+ /**
+ * Attempts to parse and return the OFVendorData contained in the given
+ * ChannelBuffer, beginning right after the vendor id.
+ * @param vendorId the vendor id that was parsed from the OFVendor message.
+ * @param data the ChannelBuffer from which to parse the vendor data
+ * @param length the length to the end of the enclosing message.
+ * @return an OFVendorData instance
+ */
+ public OFVendorData parseVendorData(int vendorId, ChannelBuffer data,
+ int length);
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/factory/OFVendorDataFactoryAware.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/factory/OFVendorDataFactoryAware.java
new file mode 100644
index 0000000..23614b0
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/factory/OFVendorDataFactoryAware.java
@@ -0,0 +1,28 @@
+/**
+* Copyright 2011, Big Switch Networks, Inc.
+* Originally created by David Erickson & Rob Sherwood, 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 org.openflow.protocol.factory;
+
+/**
+ * Classes implementing this interface are expected to be instantiated with an
+ * instance of an OFVendorDataFactory
+ *
+ * @author Rob Vaterlaus (rob.vaterlaus@bigswitch.com)
+ */
+public interface OFVendorDataFactoryAware {
+ public void setVendorDataFactory(OFVendorDataFactory vendorDataFactory);
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/serializers/OFFeaturesReplyJSONSerializer.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/serializers/OFFeaturesReplyJSONSerializer.java
new file mode 100644
index 0000000..ad57312
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/serializers/OFFeaturesReplyJSONSerializer.java
@@ -0,0 +1,57 @@
+/**
+* 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 org.openflow.protocol.serializers;
+
+import java.io.IOException;
+
+import org.codehaus.jackson.JsonGenerator;
+import org.codehaus.jackson.JsonProcessingException;
+import org.codehaus.jackson.map.JsonSerializer;
+import org.codehaus.jackson.map.SerializerProvider;
+import org.openflow.protocol.OFFeaturesReply;
+import org.openflow.util.HexString;
+
+public class OFFeaturesReplyJSONSerializer extends JsonSerializer<OFFeaturesReply> {
+
+ /**
+ * Performs the serialization of a OFFeaturesReply object
+ */
+ @Override
+ public void serialize(OFFeaturesReply reply, JsonGenerator jGen, SerializerProvider serializer) throws IOException, JsonProcessingException {
+ jGen.writeStartObject();
+ jGen.writeNumberField("actions", reply.getActions());
+ jGen.writeNumberField("buffers", reply.getBuffers());
+ jGen.writeNumberField("capabilities", reply.getCapabilities());
+ jGen.writeStringField("datapathId", HexString.toHexString(reply.getDatapathId()));
+ jGen.writeNumberField("length", reply.getLength());
+ serializer.defaultSerializeField("ports", reply.getPorts(), jGen);
+ jGen.writeNumberField("tables", reply.getTables());
+ jGen.writeStringField("type", reply.getType().toString());
+ jGen.writeNumberField("version", reply.getVersion());
+ jGen.writeNumberField("xid", reply.getXid());
+ jGen.writeEndObject();
+ }
+
+ /**
+ * Tells SimpleModule that we are the serializer for OFFeaturesReply
+ */
+ @Override
+ public Class<OFFeaturesReply> handledType() {
+ return OFFeaturesReply.class;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/serializers/OFMatchJSONSerializer.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/serializers/OFMatchJSONSerializer.java
new file mode 100644
index 0000000..69312fe
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/serializers/OFMatchJSONSerializer.java
@@ -0,0 +1,91 @@
+/**
+* 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 org.openflow.protocol.serializers;
+
+import java.io.IOException;
+
+import org.codehaus.jackson.JsonGenerator;
+import org.codehaus.jackson.JsonProcessingException;
+import org.codehaus.jackson.map.JsonSerializer;
+import org.codehaus.jackson.map.SerializerProvider;
+import org.openflow.protocol.OFMatch;
+import org.openflow.util.HexString;
+
+public class OFMatchJSONSerializer extends JsonSerializer<OFMatch> {
+
+ /**
+ * Converts an IP in a 32 bit integer to a dotted-decimal string
+ * @param i The IP address in a 32 bit integer
+ * @return An IP address string in dotted-decimal
+ */
+ private String intToIp(int i) {
+ return ((i >> 24 ) & 0xFF) + "." +
+ ((i >> 16 ) & 0xFF) + "." +
+ ((i >> 8 ) & 0xFF) + "." +
+ ( i & 0xFF);
+ }
+
+ /**
+ * Performs the serialization of a OFMatch object
+ */
+ @Override
+ public void serialize(OFMatch match, JsonGenerator jGen,
+ SerializerProvider serializer)
+ throws IOException, JsonProcessingException {
+ jGen.writeStartObject();
+ jGen.writeStringField("dataLayerDestination",
+ HexString.toHexString(match.getDataLayerDestination()));
+ jGen.writeStringField("dataLayerSource",
+ HexString.toHexString(match.getDataLayerSource()));
+ String dataType = Integer.toHexString(match.getDataLayerType());
+ while (dataType.length() < 4) {
+ dataType = "0".concat(dataType);
+ }
+ jGen.writeStringField("dataLayerType", "0x" + dataType);
+ jGen.writeNumberField("dataLayerVirtualLan",
+ match.getDataLayerVirtualLan());
+ jGen.writeNumberField("dataLayerVirtualLanPriorityCodePoint",
+ match.getDataLayerVirtualLanPriorityCodePoint());
+ jGen.writeNumberField("inputPort", match.getInputPort());
+ jGen.writeStringField("networkDestination",
+ intToIp(match.getNetworkDestination()));
+ jGen.writeNumberField("networkDestinationMaskLen",
+ match.getNetworkDestinationMaskLen());
+ jGen.writeNumberField("networkProtocol", match.getNetworkProtocol());
+ jGen.writeStringField("networkSource",
+ intToIp(match.getNetworkSource()));
+ jGen.writeNumberField("networkSourceMaskLen",
+ match.getNetworkSourceMaskLen());
+ jGen.writeNumberField("networkTypeOfService",
+ match.getNetworkTypeOfService());
+ jGen.writeNumberField("transportDestination",
+ match.getTransportDestination());
+ jGen.writeNumberField("transportSource",
+ match.getTransportSource());
+ jGen.writeNumberField("wildcards", match.getWildcards());
+ jGen.writeEndObject();
+ }
+
+ /**
+ * Tells SimpleModule that we are the serializer for OFMatch
+ */
+ @Override
+ public Class<OFMatch> handledType() {
+ return OFMatch.class;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/statistics/OFAggregateStatisticsReply.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/statistics/OFAggregateStatisticsReply.java
new file mode 100644
index 0000000..7dec16b
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/statistics/OFAggregateStatisticsReply.java
@@ -0,0 +1,130 @@
+/**
+* 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.statistics;
+
+
+import org.codehaus.jackson.annotate.JsonIgnore;
+import org.jboss.netty.buffer.ChannelBuffer;
+
+/**
+ * Represents an ofp_aggregate_stats_reply structure
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class OFAggregateStatisticsReply implements OFStatistics {
+ protected long packetCount;
+ protected long byteCount;
+ protected int flowCount;
+
+ /**
+ * @return the packetCount
+ */
+ public long getPacketCount() {
+ return packetCount;
+ }
+
+ /**
+ * @param packetCount the packetCount to set
+ */
+ public void setPacketCount(long packetCount) {
+ this.packetCount = packetCount;
+ }
+
+ /**
+ * @return the byteCount
+ */
+ public long getByteCount() {
+ return byteCount;
+ }
+
+ /**
+ * @param byteCount the byteCount to set
+ */
+ public void setByteCount(long byteCount) {
+ this.byteCount = byteCount;
+ }
+
+ /**
+ * @return the flowCount
+ */
+ public int getFlowCount() {
+ return flowCount;
+ }
+
+ /**
+ * @param flowCount the flowCount to set
+ */
+ public void setFlowCount(int flowCount) {
+ this.flowCount = flowCount;
+ }
+
+ @Override
+ @JsonIgnore
+ public int getLength() {
+ return 24;
+ }
+
+ @Override
+ public void readFrom(ChannelBuffer data) {
+ this.packetCount = data.readLong();
+ this.byteCount = data.readLong();
+ this.flowCount = data.readInt();
+ data.readInt(); // pad
+ }
+
+ @Override
+ public void writeTo(ChannelBuffer data) {
+ data.writeLong(this.packetCount);
+ data.writeLong(this.byteCount);
+ data.writeInt(this.flowCount);
+ data.writeInt(0); // pad
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 397;
+ int result = 1;
+ result = prime * result + (int) (byteCount ^ (byteCount >>> 32));
+ result = prime * result + flowCount;
+ result = prime * result + (int) (packetCount ^ (packetCount >>> 32));
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (!(obj instanceof OFAggregateStatisticsReply)) {
+ return false;
+ }
+ OFAggregateStatisticsReply other = (OFAggregateStatisticsReply) obj;
+ if (byteCount != other.byteCount) {
+ return false;
+ }
+ if (flowCount != other.flowCount) {
+ return false;
+ }
+ if (packetCount != other.packetCount) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/statistics/OFAggregateStatisticsRequest.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/statistics/OFAggregateStatisticsRequest.java
new file mode 100644
index 0000000..f41a4f1
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/statistics/OFAggregateStatisticsRequest.java
@@ -0,0 +1,135 @@
+/**
+* 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.statistics;
+
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.openflow.protocol.OFMatch;
+
+/**
+ * Represents an ofp_aggregate_stats_request structure
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class OFAggregateStatisticsRequest implements OFStatistics {
+ protected OFMatch match;
+ protected byte tableId;
+ protected short outPort;
+
+ /**
+ * @return the match
+ */
+ public OFMatch getMatch() {
+ return match;
+ }
+
+ /**
+ * @param match the match to set
+ */
+ public void setMatch(OFMatch match) {
+ this.match = match;
+ }
+
+ /**
+ * @return the tableId
+ */
+ public byte getTableId() {
+ return tableId;
+ }
+
+ /**
+ * @param tableId the tableId to set
+ */
+ public void setTableId(byte tableId) {
+ this.tableId = tableId;
+ }
+
+ /**
+ * @return the outPort
+ */
+ public short getOutPort() {
+ return outPort;
+ }
+
+ /**
+ * @param outPort the outPort to set
+ */
+ public void setOutPort(short outPort) {
+ this.outPort = outPort;
+ }
+
+ @Override
+ public int getLength() {
+ return 44;
+ }
+
+ @Override
+ public void readFrom(ChannelBuffer data) {
+ if (this.match == null)
+ this.match = new OFMatch();
+ this.match.readFrom(data);
+ this.tableId = data.readByte();
+ data.readByte(); // pad
+ this.outPort = data.readShort();
+ }
+
+ @Override
+ public void writeTo(ChannelBuffer data) {
+ this.match.writeTo(data);
+ data.writeByte(this.tableId);
+ data.writeByte((byte) 0);
+ data.writeShort(this.outPort);
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 401;
+ int result = 1;
+ result = prime * result + ((match == null) ? 0 : match.hashCode());
+ result = prime * result + outPort;
+ result = prime * result + tableId;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (!(obj instanceof OFAggregateStatisticsRequest)) {
+ return false;
+ }
+ OFAggregateStatisticsRequest other = (OFAggregateStatisticsRequest) obj;
+ if (match == null) {
+ if (other.match != null) {
+ return false;
+ }
+ } else if (!match.equals(other.match)) {
+ return false;
+ }
+ if (outPort != other.outPort) {
+ return false;
+ }
+ if (tableId != other.tableId) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/statistics/OFDescriptionStatistics.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/statistics/OFDescriptionStatistics.java
new file mode 100644
index 0000000..6799fa3
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/statistics/OFDescriptionStatistics.java
@@ -0,0 +1,216 @@
+/**
+* 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.statistics;
+
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.openflow.util.StringByteSerializer;
+
+/**
+ * Represents an ofp_desc_stats structure
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class OFDescriptionStatistics implements OFStatistics {
+ public static int DESCRIPTION_STRING_LENGTH = 256;
+ public static int SERIAL_NUMBER_LENGTH = 32;
+
+ protected String manufacturerDescription;
+ protected String hardwareDescription;
+ protected String softwareDescription;
+ protected String serialNumber;
+ protected String datapathDescription;
+
+ /**
+ * @return the manufacturerDescription
+ */
+ public String getManufacturerDescription() {
+ return manufacturerDescription;
+ }
+
+ /**
+ * @param manufacturerDescription the manufacturerDescription to set
+ */
+ public void setManufacturerDescription(String manufacturerDescription) {
+ this.manufacturerDescription = manufacturerDescription;
+ }
+
+ /**
+ * @return the hardwareDescription
+ */
+ public String getHardwareDescription() {
+ return hardwareDescription;
+ }
+
+ /**
+ * @param hardwareDescription the hardwareDescription to set
+ */
+ public void setHardwareDescription(String hardwareDescription) {
+ this.hardwareDescription = hardwareDescription;
+ }
+
+ /**
+ * @return the softwareDescription
+ */
+ public String getSoftwareDescription() {
+ return softwareDescription;
+ }
+
+ /**
+ * @param softwareDescription the softwareDescription to set
+ */
+ public void setSoftwareDescription(String softwareDescription) {
+ this.softwareDescription = softwareDescription;
+ }
+
+ /**
+ * @return the serialNumber
+ */
+ public String getSerialNumber() {
+ return serialNumber;
+ }
+
+ /**
+ * @param serialNumber the serialNumber to set
+ */
+ public void setSerialNumber(String serialNumber) {
+ this.serialNumber = serialNumber;
+ }
+
+ /**
+ * @return the datapathDescription
+ */
+ public String getDatapathDescription() {
+ return datapathDescription;
+ }
+
+ /**
+ * @param datapathDescription the datapathDescription to set
+ */
+ public void setDatapathDescription(String datapathDescription) {
+ this.datapathDescription = datapathDescription;
+ }
+
+ @Override
+ public int getLength() {
+ return 1056;
+ }
+
+ @Override
+ public void readFrom(ChannelBuffer data) {
+ this.manufacturerDescription = StringByteSerializer.readFrom(data,
+ DESCRIPTION_STRING_LENGTH);
+ this.hardwareDescription = StringByteSerializer.readFrom(data,
+ DESCRIPTION_STRING_LENGTH);
+ this.softwareDescription = StringByteSerializer.readFrom(data,
+ DESCRIPTION_STRING_LENGTH);
+ this.serialNumber = StringByteSerializer.readFrom(data,
+ SERIAL_NUMBER_LENGTH);
+ this.datapathDescription = StringByteSerializer.readFrom(data,
+ DESCRIPTION_STRING_LENGTH);
+ }
+
+ @Override
+ public void writeTo(ChannelBuffer data) {
+ StringByteSerializer.writeTo(data, DESCRIPTION_STRING_LENGTH,
+ this.manufacturerDescription);
+ StringByteSerializer.writeTo(data, DESCRIPTION_STRING_LENGTH,
+ this.hardwareDescription);
+ StringByteSerializer.writeTo(data, DESCRIPTION_STRING_LENGTH,
+ this.softwareDescription);
+ StringByteSerializer.writeTo(data, SERIAL_NUMBER_LENGTH,
+ this.serialNumber);
+ StringByteSerializer.writeTo(data, DESCRIPTION_STRING_LENGTH,
+ this.datapathDescription);
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 409;
+ 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 (!(obj instanceof OFDescriptionStatistics)) {
+ return false;
+ }
+ OFDescriptionStatistics other = (OFDescriptionStatistics) 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;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/statistics/OFFlowStatisticsReply.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/statistics/OFFlowStatisticsReply.java
new file mode 100644
index 0000000..bea7f1e
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/statistics/OFFlowStatisticsReply.java
@@ -0,0 +1,359 @@
+/**
+* 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.statistics;
+
+import java.util.List;
+
+import org.codehaus.jackson.annotate.JsonIgnore;
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.openflow.protocol.OFMatch;
+import org.openflow.protocol.action.OFAction;
+import org.openflow.protocol.factory.OFActionFactory;
+import org.openflow.protocol.factory.OFActionFactoryAware;
+import org.openflow.util.U16;
+
+/**
+ * Represents an ofp_flow_stats structure
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class OFFlowStatisticsReply implements OFStatistics, OFActionFactoryAware {
+ public static int MINIMUM_LENGTH = 88;
+
+ protected OFActionFactory actionFactory;
+ protected short length = (short) MINIMUM_LENGTH;
+ protected byte tableId;
+ protected OFMatch match;
+ protected int durationSeconds;
+ protected int durationNanoseconds;
+ protected short priority;
+ protected short idleTimeout;
+ protected short hardTimeout;
+ protected long cookie;
+ protected long packetCount;
+ protected long byteCount;
+ protected List<OFAction> actions;
+
+ /**
+ * @return the tableId
+ */
+ public byte getTableId() {
+ return tableId;
+ }
+
+ /**
+ * @param tableId the tableId to set
+ */
+ public void setTableId(byte tableId) {
+ this.tableId = tableId;
+ }
+
+ /**
+ * @return the match
+ */
+ public OFMatch getMatch() {
+ return match;
+ }
+
+ /**
+ * @param match the match to set
+ */
+ public void setMatch(OFMatch match) {
+ this.match = match;
+ }
+
+ /**
+ * @return the durationSeconds
+ */
+ public int getDurationSeconds() {
+ return durationSeconds;
+ }
+
+ /**
+ * @param durationSeconds the durationSeconds to set
+ */
+ public void setDurationSeconds(int durationSeconds) {
+ this.durationSeconds = durationSeconds;
+ }
+
+ /**
+ * @return the durationNanoseconds
+ */
+ public int getDurationNanoseconds() {
+ return durationNanoseconds;
+ }
+
+ /**
+ * @param durationNanoseconds the durationNanoseconds to set
+ */
+ public void setDurationNanoseconds(int durationNanoseconds) {
+ this.durationNanoseconds = durationNanoseconds;
+ }
+
+ /**
+ * @return the priority
+ */
+ public short getPriority() {
+ return priority;
+ }
+
+ /**
+ * @param priority the priority to set
+ */
+ public void setPriority(short priority) {
+ this.priority = priority;
+ }
+
+ /**
+ * @return the idleTimeout
+ */
+ public short getIdleTimeout() {
+ return idleTimeout;
+ }
+
+ /**
+ * @param idleTimeout the idleTimeout to set
+ */
+ public void setIdleTimeout(short idleTimeout) {
+ this.idleTimeout = idleTimeout;
+ }
+
+ /**
+ * @return the hardTimeout
+ */
+ public short getHardTimeout() {
+ return hardTimeout;
+ }
+
+ /**
+ * @param hardTimeout the hardTimeout to set
+ */
+ public void setHardTimeout(short hardTimeout) {
+ this.hardTimeout = hardTimeout;
+ }
+
+ /**
+ * @return the cookie
+ */
+ public long getCookie() {
+ return cookie;
+ }
+
+ /**
+ * @param cookie the cookie to set
+ */
+ public void setCookie(long cookie) {
+ this.cookie = cookie;
+ }
+
+ /**
+ * @return the packetCount
+ */
+ public long getPacketCount() {
+ return packetCount;
+ }
+
+ /**
+ * @param packetCount the packetCount to set
+ */
+ public void setPacketCount(long packetCount) {
+ this.packetCount = packetCount;
+ }
+
+ /**
+ * @return the byteCount
+ */
+ public long getByteCount() {
+ return byteCount;
+ }
+
+ /**
+ * @param byteCount the byteCount to set
+ */
+ public void setByteCount(long byteCount) {
+ this.byteCount = byteCount;
+ }
+
+ /**
+ * @param length the length to set
+ */
+ public void setLength(short length) {
+ this.length = length;
+ }
+
+ @Override
+ @JsonIgnore
+ public int getLength() {
+ return U16.f(length);
+ }
+
+ /**
+ * @param actionFactory the actionFactory to set
+ */
+ @Override
+ public void setActionFactory(OFActionFactory actionFactory) {
+ this.actionFactory = actionFactory;
+ }
+
+ /**
+ * @return the actions
+ */
+ public List<OFAction> getActions() {
+ return actions;
+ }
+
+ /**
+ * @param actions the actions to set
+ */
+ public void setActions(List<OFAction> actions) {
+ this.actions = actions;
+ }
+
+ @Override
+ public void readFrom(ChannelBuffer data) {
+ this.length = data.readShort();
+ this.tableId = data.readByte();
+ data.readByte(); // pad
+ if (this.match == null)
+ this.match = new OFMatch();
+ this.match.readFrom(data);
+ this.durationSeconds = data.readInt();
+ this.durationNanoseconds = data.readInt();
+ this.priority = data.readShort();
+ this.idleTimeout = data.readShort();
+ this.hardTimeout = data.readShort();
+ data.readInt(); // pad
+ data.readShort(); // pad
+ this.cookie = data.readLong();
+ this.packetCount = data.readLong();
+ this.byteCount = data.readLong();
+ if (this.actionFactory == null)
+ throw new RuntimeException("OFActionFactory not set");
+ this.actions = this.actionFactory.parseActions(data, getLength() -
+ MINIMUM_LENGTH);
+ }
+
+ @Override
+ public void writeTo(ChannelBuffer data) {
+ data.writeShort(this.length);
+ data.writeByte(this.tableId);
+ data.writeByte((byte) 0);
+ this.match.writeTo(data);
+ data.writeInt(this.durationSeconds);
+ data.writeInt(this.durationNanoseconds);
+ data.writeShort(this.priority);
+ data.writeShort(this.idleTimeout);
+ data.writeShort(this.hardTimeout);
+ data.writeInt(0); // pad
+ data.writeShort((short)0); // pad
+ data.writeLong(this.cookie);
+ data.writeLong(this.packetCount);
+ data.writeLong(this.byteCount);
+ if (actions != null) {
+ for (OFAction action : actions) {
+ action.writeTo(data);
+ }
+ }
+ }
+
+ @Override
+ public String toString() {
+ String str = "match=" + this.match;
+ str += " tableId=" + this.tableId;
+ str += " durationSeconds=" + this.durationSeconds;
+ str += " durationNanoseconds=" + this.durationNanoseconds;
+ str += " priority=" + this.priority;
+ str += " idleTimeout=" + this.idleTimeout;
+ str += " hardTimeout=" + this.hardTimeout;
+ str += " cookie=" + this.cookie;
+ str += " packetCount=" + this.packetCount;
+ str += " byteCount=" + this.byteCount;
+ str += " action=" + this.actions;
+
+ return str;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 419;
+ int result = 1;
+ result = prime * result + (int) (byteCount ^ (byteCount >>> 32));
+ result = prime * result + (int) (cookie ^ (cookie >>> 32));
+ result = prime * result + durationNanoseconds;
+ result = prime * result + durationSeconds;
+ result = prime * result + hardTimeout;
+ result = prime * result + idleTimeout;
+ result = prime * result + length;
+ result = prime * result + ((match == null) ? 0 : match.hashCode());
+ result = prime * result + (int) (packetCount ^ (packetCount >>> 32));
+ result = prime * result + priority;
+ result = prime * result + tableId;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (!(obj instanceof OFFlowStatisticsReply)) {
+ return false;
+ }
+ OFFlowStatisticsReply other = (OFFlowStatisticsReply) obj;
+ if (byteCount != other.byteCount) {
+ return false;
+ }
+ if (cookie != other.cookie) {
+ return false;
+ }
+ if (durationNanoseconds != other.durationNanoseconds) {
+ return false;
+ }
+ if (durationSeconds != other.durationSeconds) {
+ return false;
+ }
+ if (hardTimeout != other.hardTimeout) {
+ return false;
+ }
+ if (idleTimeout != other.idleTimeout) {
+ return false;
+ }
+ if (length != other.length) {
+ return false;
+ }
+ if (match == null) {
+ if (other.match != null) {
+ return false;
+ }
+ } else if (!match.equals(other.match)) {
+ return false;
+ }
+ if (packetCount != other.packetCount) {
+ return false;
+ }
+ if (priority != other.priority) {
+ return false;
+ }
+ if (tableId != other.tableId) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/statistics/OFFlowStatisticsRequest.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/statistics/OFFlowStatisticsRequest.java
new file mode 100644
index 0000000..b21de0c
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/statistics/OFFlowStatisticsRequest.java
@@ -0,0 +1,135 @@
+/**
+* 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.statistics;
+
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.openflow.protocol.OFMatch;
+
+/**
+ * Represents an ofp_flow_stats_request structure
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class OFFlowStatisticsRequest implements OFStatistics {
+ protected OFMatch match;
+ protected byte tableId;
+ protected short outPort;
+
+ /**
+ * @return the match
+ */
+ public OFMatch getMatch() {
+ return match;
+ }
+
+ /**
+ * @param match the match to set
+ */
+ public void setMatch(OFMatch match) {
+ this.match = match;
+ }
+
+ /**
+ * @return the tableId
+ */
+ public byte getTableId() {
+ return tableId;
+ }
+
+ /**
+ * @param tableId the tableId to set
+ */
+ public void setTableId(byte tableId) {
+ this.tableId = tableId;
+ }
+
+ /**
+ * @return the outPort
+ */
+ public short getOutPort() {
+ return outPort;
+ }
+
+ /**
+ * @param outPort the outPort to set
+ */
+ public void setOutPort(short outPort) {
+ this.outPort = outPort;
+ }
+
+ @Override
+ public int getLength() {
+ return 44;
+ }
+
+ @Override
+ public void readFrom(ChannelBuffer data) {
+ if (this.match == null)
+ this.match = new OFMatch();
+ this.match.readFrom(data);
+ this.tableId = data.readByte();
+ data.readByte(); // pad
+ this.outPort = data.readShort();
+ }
+
+ @Override
+ public void writeTo(ChannelBuffer data) {
+ this.match.writeTo(data);
+ data.writeByte(this.tableId);
+ data.writeByte((byte) 0);
+ data.writeShort(this.outPort);
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 421;
+ int result = 1;
+ result = prime * result + ((match == null) ? 0 : match.hashCode());
+ result = prime * result + outPort;
+ result = prime * result + tableId;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (!(obj instanceof OFFlowStatisticsRequest)) {
+ return false;
+ }
+ OFFlowStatisticsRequest other = (OFFlowStatisticsRequest) obj;
+ if (match == null) {
+ if (other.match != null) {
+ return false;
+ }
+ } else if (!match.equals(other.match)) {
+ return false;
+ }
+ if (outPort != other.outPort) {
+ return false;
+ }
+ if (tableId != other.tableId) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/statistics/OFPortStatisticsReply.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/statistics/OFPortStatisticsReply.java
new file mode 100644
index 0000000..87a2465
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/statistics/OFPortStatisticsReply.java
@@ -0,0 +1,352 @@
+/**
+* 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.statistics;
+
+
+import org.codehaus.jackson.annotate.JsonIgnore;
+import org.jboss.netty.buffer.ChannelBuffer;
+
+/**
+ * Represents an ofp_port_stats structure
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class OFPortStatisticsReply implements OFStatistics {
+ protected short portNumber;
+ protected long receivePackets;
+ protected long transmitPackets;
+ protected long receiveBytes;
+ protected long transmitBytes;
+ protected long receiveDropped;
+ protected long transmitDropped;
+ protected long receiveErrors;
+ protected long transmitErrors;
+ protected long receiveFrameErrors;
+ protected long receiveOverrunErrors;
+ protected long receiveCRCErrors;
+ protected long collisions;
+
+ /**
+ * @return the portNumber
+ */
+ public short getPortNumber() {
+ return portNumber;
+ }
+
+ /**
+ * @param portNumber the portNumber to set
+ */
+ public void setPortNumber(short portNumber) {
+ this.portNumber = portNumber;
+ }
+
+ /**
+ * @return the receivePackets
+ */
+ public long getreceivePackets() {
+ return receivePackets;
+ }
+
+ /**
+ * @param receivePackets the receivePackets to set
+ */
+ public void setreceivePackets(long receivePackets) {
+ this.receivePackets = receivePackets;
+ }
+
+ /**
+ * @return the transmitPackets
+ */
+ public long getTransmitPackets() {
+ return transmitPackets;
+ }
+
+ /**
+ * @param transmitPackets the transmitPackets to set
+ */
+ public void setTransmitPackets(long transmitPackets) {
+ this.transmitPackets = transmitPackets;
+ }
+
+ /**
+ * @return the receiveBytes
+ */
+ public long getReceiveBytes() {
+ return receiveBytes;
+ }
+
+ /**
+ * @param receiveBytes the receiveBytes to set
+ */
+ public void setReceiveBytes(long receiveBytes) {
+ this.receiveBytes = receiveBytes;
+ }
+
+ /**
+ * @return the transmitBytes
+ */
+ public long getTransmitBytes() {
+ return transmitBytes;
+ }
+
+ /**
+ * @param transmitBytes the transmitBytes to set
+ */
+ public void setTransmitBytes(long transmitBytes) {
+ this.transmitBytes = transmitBytes;
+ }
+
+ /**
+ * @return the receiveDropped
+ */
+ public long getReceiveDropped() {
+ return receiveDropped;
+ }
+
+ /**
+ * @param receiveDropped the receiveDropped to set
+ */
+ public void setReceiveDropped(long receiveDropped) {
+ this.receiveDropped = receiveDropped;
+ }
+
+ /**
+ * @return the transmitDropped
+ */
+ public long getTransmitDropped() {
+ return transmitDropped;
+ }
+
+ /**
+ * @param transmitDropped the transmitDropped to set
+ */
+ public void setTransmitDropped(long transmitDropped) {
+ this.transmitDropped = transmitDropped;
+ }
+
+ /**
+ * @return the receiveErrors
+ */
+ public long getreceiveErrors() {
+ return receiveErrors;
+ }
+
+ /**
+ * @param receiveErrors the receiveErrors to set
+ */
+ public void setreceiveErrors(long receiveErrors) {
+ this.receiveErrors = receiveErrors;
+ }
+
+ /**
+ * @return the transmitErrors
+ */
+ public long getTransmitErrors() {
+ return transmitErrors;
+ }
+
+ /**
+ * @param transmitErrors the transmitErrors to set
+ */
+ public void setTransmitErrors(long transmitErrors) {
+ this.transmitErrors = transmitErrors;
+ }
+
+ /**
+ * @return the receiveFrameErrors
+ */
+ public long getReceiveFrameErrors() {
+ return receiveFrameErrors;
+ }
+
+ /**
+ * @param receiveFrameErrors the receiveFrameErrors to set
+ */
+ public void setReceiveFrameErrors(long receiveFrameErrors) {
+ this.receiveFrameErrors = receiveFrameErrors;
+ }
+
+ /**
+ * @return the receiveOverrunErrors
+ */
+ public long getReceiveOverrunErrors() {
+ return receiveOverrunErrors;
+ }
+
+ /**
+ * @param receiveOverrunErrors the receiveOverrunErrors to set
+ */
+ public void setReceiveOverrunErrors(long receiveOverrunErrors) {
+ this.receiveOverrunErrors = receiveOverrunErrors;
+ }
+
+ /**
+ * @return the receiveCRCErrors
+ */
+ public long getReceiveCRCErrors() {
+ return receiveCRCErrors;
+ }
+
+ /**
+ * @param receiveCRCErrors the receiveCRCErrors to set
+ */
+ public void setReceiveCRCErrors(long receiveCRCErrors) {
+ this.receiveCRCErrors = receiveCRCErrors;
+ }
+
+ /**
+ * @return the collisions
+ */
+ public long getCollisions() {
+ return collisions;
+ }
+
+ /**
+ * @param collisions the collisions to set
+ */
+ public void setCollisions(long collisions) {
+ this.collisions = collisions;
+ }
+
+ @Override
+ @JsonIgnore
+ public int getLength() {
+ return 104;
+ }
+
+ @Override
+ public void readFrom(ChannelBuffer data) {
+ this.portNumber = data.readShort();
+ data.readShort(); // pad
+ data.readInt(); // pad
+ this.receivePackets = data.readLong();
+ this.transmitPackets = data.readLong();
+ this.receiveBytes = data.readLong();
+ this.transmitBytes = data.readLong();
+ this.receiveDropped = data.readLong();
+ this.transmitDropped = data.readLong();
+ this.receiveErrors = data.readLong();
+ this.transmitErrors = data.readLong();
+ this.receiveFrameErrors = data.readLong();
+ this.receiveOverrunErrors = data.readLong();
+ this.receiveCRCErrors = data.readLong();
+ this.collisions = data.readLong();
+ }
+
+ @Override
+ public void writeTo(ChannelBuffer data) {
+ data.writeShort(this.portNumber);
+ data.writeShort((short) 0); // pad
+ data.writeInt(0); // pad
+ data.writeLong(this.receivePackets);
+ data.writeLong(this.transmitPackets);
+ data.writeLong(this.receiveBytes);
+ data.writeLong(this.transmitBytes);
+ data.writeLong(this.receiveDropped);
+ data.writeLong(this.transmitDropped);
+ data.writeLong(this.receiveErrors);
+ data.writeLong(this.transmitErrors);
+ data.writeLong(this.receiveFrameErrors);
+ data.writeLong(this.receiveOverrunErrors);
+ data.writeLong(this.receiveCRCErrors);
+ data.writeLong(this.collisions);
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 431;
+ int result = 1;
+ result = prime * result + (int) (collisions ^ (collisions >>> 32));
+ result = prime * result + portNumber;
+ result = prime * result
+ + (int) (receivePackets ^ (receivePackets >>> 32));
+ result = prime * result + (int) (receiveBytes ^ (receiveBytes >>> 32));
+ result = prime * result
+ + (int) (receiveCRCErrors ^ (receiveCRCErrors >>> 32));
+ result = prime * result
+ + (int) (receiveDropped ^ (receiveDropped >>> 32));
+ result = prime * result
+ + (int) (receiveFrameErrors ^ (receiveFrameErrors >>> 32));
+ result = prime * result
+ + (int) (receiveOverrunErrors ^ (receiveOverrunErrors >>> 32));
+ result = prime * result
+ + (int) (receiveErrors ^ (receiveErrors >>> 32));
+ result = prime * result
+ + (int) (transmitBytes ^ (transmitBytes >>> 32));
+ result = prime * result
+ + (int) (transmitDropped ^ (transmitDropped >>> 32));
+ result = prime * result
+ + (int) (transmitErrors ^ (transmitErrors >>> 32));
+ result = prime * result
+ + (int) (transmitPackets ^ (transmitPackets >>> 32));
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (!(obj instanceof OFPortStatisticsReply)) {
+ return false;
+ }
+ OFPortStatisticsReply other = (OFPortStatisticsReply) obj;
+ if (collisions != other.collisions) {
+ return false;
+ }
+ if (portNumber != other.portNumber) {
+ return false;
+ }
+ if (receivePackets != other.receivePackets) {
+ return false;
+ }
+ if (receiveBytes != other.receiveBytes) {
+ return false;
+ }
+ if (receiveCRCErrors != other.receiveCRCErrors) {
+ return false;
+ }
+ if (receiveDropped != other.receiveDropped) {
+ return false;
+ }
+ if (receiveFrameErrors != other.receiveFrameErrors) {
+ return false;
+ }
+ if (receiveOverrunErrors != other.receiveOverrunErrors) {
+ return false;
+ }
+ if (receiveErrors != other.receiveErrors) {
+ return false;
+ }
+ if (transmitBytes != other.transmitBytes) {
+ return false;
+ }
+ if (transmitDropped != other.transmitDropped) {
+ return false;
+ }
+ if (transmitErrors != other.transmitErrors) {
+ return false;
+ }
+ if (transmitPackets != other.transmitPackets) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/statistics/OFPortStatisticsRequest.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/statistics/OFPortStatisticsRequest.java
new file mode 100644
index 0000000..c07a895
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/statistics/OFPortStatisticsRequest.java
@@ -0,0 +1,88 @@
+/**
+* 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.statistics;
+
+
+import org.jboss.netty.buffer.ChannelBuffer;
+
+/**
+ * Represents an ofp_port_stats_request structure
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class OFPortStatisticsRequest implements OFStatistics {
+ protected short portNumber;
+
+ /**
+ * @return the portNumber
+ */
+ public short getPortNumber() {
+ return portNumber;
+ }
+
+ /**
+ * @param portNumber the portNumber to set
+ */
+ public void setPortNumber(short portNumber) {
+ this.portNumber = portNumber;
+ }
+
+ @Override
+ public int getLength() {
+ return 8;
+ }
+
+ @Override
+ public void readFrom(ChannelBuffer data) {
+ this.portNumber = data.readShort();
+ data.readShort(); // pad
+ data.readInt(); // pad
+ }
+
+ @Override
+ public void writeTo(ChannelBuffer data) {
+ data.writeShort(this.portNumber);
+ data.writeShort((short) 0); // pad
+ data.writeInt(0); // pad
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 433;
+ int result = 1;
+ result = prime * result + portNumber;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (!(obj instanceof OFPortStatisticsRequest)) {
+ return false;
+ }
+ OFPortStatisticsRequest other = (OFPortStatisticsRequest) obj;
+ if (portNumber != other.portNumber) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/statistics/OFQueueStatisticsReply.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/statistics/OFQueueStatisticsReply.java
new file mode 100644
index 0000000..03cbb9c
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/statistics/OFQueueStatisticsReply.java
@@ -0,0 +1,175 @@
+/**
+* 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.statistics;
+
+
+import org.codehaus.jackson.annotate.JsonIgnore;
+import org.jboss.netty.buffer.ChannelBuffer;
+
+/**
+ * Represents an ofp_queue_stats structure
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class OFQueueStatisticsReply implements OFStatistics {
+ protected short portNumber;
+ protected int queueId;
+ protected long transmitBytes;
+ protected long transmitPackets;
+ protected long transmitErrors;
+
+ /**
+ * @return the portNumber
+ */
+ public short getPortNumber() {
+ return portNumber;
+ }
+
+ /**
+ * @param portNumber the portNumber to set
+ */
+ public void setPortNumber(short portNumber) {
+ this.portNumber = portNumber;
+ }
+
+ /**
+ * @return the queueId
+ */
+ public int getQueueId() {
+ return queueId;
+ }
+
+ /**
+ * @param queueId the queueId to set
+ */
+ public void setQueueId(int queueId) {
+ this.queueId = queueId;
+ }
+
+ /**
+ * @return the transmitBytes
+ */
+ public long getTransmitBytes() {
+ return transmitBytes;
+ }
+
+ /**
+ * @param transmitBytes the transmitBytes to set
+ */
+ public void setTransmitBytes(long transmitBytes) {
+ this.transmitBytes = transmitBytes;
+ }
+
+ /**
+ * @return the transmitPackets
+ */
+ public long getTransmitPackets() {
+ return transmitPackets;
+ }
+
+ /**
+ * @param transmitPackets the transmitPackets to set
+ */
+ public void setTransmitPackets(long transmitPackets) {
+ this.transmitPackets = transmitPackets;
+ }
+
+ /**
+ * @return the transmitErrors
+ */
+ public long getTransmitErrors() {
+ return transmitErrors;
+ }
+
+ /**
+ * @param transmitErrors the transmitErrors to set
+ */
+ public void setTransmitErrors(long transmitErrors) {
+ this.transmitErrors = transmitErrors;
+ }
+
+ @Override
+ @JsonIgnore
+ public int getLength() {
+ return 32;
+ }
+
+ @Override
+ public void readFrom(ChannelBuffer data) {
+ this.portNumber = data.readShort();
+ data.readShort(); // pad
+ this.queueId = data.readInt();
+ this.transmitBytes = data.readLong();
+ this.transmitPackets = data.readLong();
+ this.transmitErrors = data.readLong();
+ }
+
+ @Override
+ public void writeTo(ChannelBuffer data) {
+ data.writeShort(this.portNumber);
+ data.writeShort((short) 0); // pad
+ data.writeInt(this.queueId);
+ data.writeLong(this.transmitBytes);
+ data.writeLong(this.transmitPackets);
+ data.writeLong(this.transmitErrors);
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 439;
+ int result = 1;
+ result = prime * result + portNumber;
+ result = prime * result + queueId;
+ result = prime * result
+ + (int) (transmitBytes ^ (transmitBytes >>> 32));
+ result = prime * result
+ + (int) (transmitErrors ^ (transmitErrors >>> 32));
+ result = prime * result
+ + (int) (transmitPackets ^ (transmitPackets >>> 32));
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (!(obj instanceof OFQueueStatisticsReply)) {
+ return false;
+ }
+ OFQueueStatisticsReply other = (OFQueueStatisticsReply) obj;
+ if (portNumber != other.portNumber) {
+ return false;
+ }
+ if (queueId != other.queueId) {
+ return false;
+ }
+ if (transmitBytes != other.transmitBytes) {
+ return false;
+ }
+ if (transmitErrors != other.transmitErrors) {
+ return false;
+ }
+ if (transmitPackets != other.transmitPackets) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/statistics/OFQueueStatisticsRequest.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/statistics/OFQueueStatisticsRequest.java
new file mode 100644
index 0000000..3331453
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/statistics/OFQueueStatisticsRequest.java
@@ -0,0 +1,107 @@
+/**
+* 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.statistics;
+
+
+import org.jboss.netty.buffer.ChannelBuffer;
+
+/**
+ * Represents an ofp_queue_stats_request structure
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class OFQueueStatisticsRequest implements OFStatistics {
+ protected short portNumber;
+ protected int queueId;
+
+ /**
+ * @return the portNumber
+ */
+ public short getPortNumber() {
+ return portNumber;
+ }
+
+ /**
+ * @param portNumber the portNumber to set
+ */
+ public void setPortNumber(short portNumber) {
+ this.portNumber = portNumber;
+ }
+
+ /**
+ * @return the queueId
+ */
+ public int getQueueId() {
+ return queueId;
+ }
+
+ /**
+ * @param queueId the queueId to set
+ */
+ public void setQueueId(int queueId) {
+ this.queueId = queueId;
+ }
+
+ @Override
+ public int getLength() {
+ return 8;
+ }
+
+ @Override
+ public void readFrom(ChannelBuffer data) {
+ this.portNumber = data.readShort();
+ data.readShort(); // pad
+ this.queueId = data.readInt();
+ }
+
+ @Override
+ public void writeTo(ChannelBuffer data) {
+ data.writeShort(this.portNumber);
+ data.writeShort((short) 0); // pad
+ data.writeInt(this.queueId);
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 443;
+ int result = 1;
+ result = prime * result + portNumber;
+ result = prime * result + queueId;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (!(obj instanceof OFQueueStatisticsRequest)) {
+ return false;
+ }
+ OFQueueStatisticsRequest other = (OFQueueStatisticsRequest) obj;
+ if (portNumber != other.portNumber) {
+ return false;
+ }
+ if (queueId != other.queueId) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/statistics/OFStatistics.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/statistics/OFStatistics.java
new file mode 100644
index 0000000..5e8f4dd
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/statistics/OFStatistics.java
@@ -0,0 +1,45 @@
+/**
+* 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.statistics;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+
+/**
+ * The base class for all OpenFlow statistics.
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu) - Mar 11, 2010
+ */
+public interface OFStatistics {
+ /**
+ * Returns the wire length of this message in bytes
+ * @return the length
+ */
+ public int getLength();
+
+ /**
+ * Read this message off the wire from the specified ByteBuffer
+ * @param data
+ */
+ public void readFrom(ChannelBuffer data);
+
+ /**
+ * Write this message's binary format to the specified ByteBuffer
+ * @param data
+ */
+ public void writeTo(ChannelBuffer data);
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/statistics/OFStatisticsType.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/statistics/OFStatisticsType.java
new file mode 100644
index 0000000..f2b9e30
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/statistics/OFStatisticsType.java
@@ -0,0 +1,317 @@
+/**
+* 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.statistics;
+
+import java.lang.reflect.Constructor;
+
+import org.openflow.protocol.Instantiable;
+import org.openflow.protocol.OFType;
+
+public enum OFStatisticsType {
+ DESC (0, OFDescriptionStatistics.class, OFDescriptionStatistics.class,
+ new Instantiable<OFStatistics>() {
+ @Override
+ public OFStatistics instantiate() {
+ return new OFDescriptionStatistics();
+ }
+ },
+ new Instantiable<OFStatistics>() {
+ @Override
+ public OFStatistics instantiate() {
+ return new OFDescriptionStatistics();
+ }
+ }),
+ FLOW (1, OFFlowStatisticsRequest.class, OFFlowStatisticsReply.class,
+ new Instantiable<OFStatistics>() {
+ @Override
+ public OFStatistics instantiate() {
+ return new OFFlowStatisticsRequest();
+ }
+ },
+ new Instantiable<OFStatistics>() {
+ @Override
+ public OFStatistics instantiate() {
+ return new OFFlowStatisticsReply();
+ }
+ }),
+ AGGREGATE (2, OFAggregateStatisticsRequest.class, OFAggregateStatisticsReply.class,
+ new Instantiable<OFStatistics>() {
+ @Override
+ public OFStatistics instantiate() {
+ return new OFAggregateStatisticsRequest();
+ }
+ },
+ new Instantiable<OFStatistics>() {
+ @Override
+ public OFStatistics instantiate() {
+ return new OFAggregateStatisticsReply();
+ }
+ }),
+ TABLE (3, OFTableStatistics.class, OFTableStatistics.class,
+ new Instantiable<OFStatistics>() {
+ @Override
+ public OFStatistics instantiate() {
+ return new OFTableStatistics();
+ }
+ },
+ new Instantiable<OFStatistics>() {
+ @Override
+ public OFStatistics instantiate() {
+ return new OFTableStatistics();
+ }
+ }),
+ PORT (4, OFPortStatisticsRequest.class, OFPortStatisticsReply.class,
+ new Instantiable<OFStatistics>() {
+ @Override
+ public OFStatistics instantiate() {
+ return new OFPortStatisticsRequest();
+ }
+ },
+ new Instantiable<OFStatistics>() {
+ @Override
+ public OFStatistics instantiate() {
+ return new OFPortStatisticsReply();
+ }
+ }),
+ QUEUE (5, OFQueueStatisticsRequest.class, OFQueueStatisticsReply.class,
+ new Instantiable<OFStatistics>() {
+ @Override
+ public OFStatistics instantiate() {
+ return new OFQueueStatisticsRequest();
+ }
+ },
+ new Instantiable<OFStatistics>() {
+ @Override
+ public OFStatistics instantiate() {
+ return new OFQueueStatisticsReply();
+ }
+ }),
+ VENDOR (0xffff, OFVendorStatistics.class, OFVendorStatistics.class,
+ new Instantiable<OFStatistics>() {
+ @Override
+ public OFStatistics instantiate() {
+ return new OFVendorStatistics();
+ }
+ },
+ new Instantiable<OFStatistics>() {
+ @Override
+ public OFStatistics instantiate() {
+ return new OFVendorStatistics();
+ }
+ });
+
+ static OFStatisticsType[] requestMapping;
+ static OFStatisticsType[] replyMapping;
+
+ protected Class<? extends OFStatistics> requestClass;
+ protected Constructor<? extends OFStatistics> requestConstructor;
+ protected Instantiable<OFStatistics> requestInstantiable;
+ protected Class<? extends OFStatistics> replyClass;
+ protected Constructor<? extends OFStatistics> replyConstructor;
+ protected Instantiable<OFStatistics> replyInstantiable;
+ protected short type;
+
+ /**
+ * Store some information about the OpenFlow Statistic type, including wire
+ * protocol type number, and derived class
+ *
+ * @param type Wire protocol number associated with this OFStatisticsType
+ * @param requestClass The Statistics Java class to return when the
+ * containing OFType is STATS_REQUEST
+ * @param replyClass The Statistics Java class to return when the
+ * containing OFType is STATS_REPLY
+ */
+ OFStatisticsType(int type, Class<? extends OFStatistics> requestClass,
+ Class<? extends OFStatistics> replyClass,
+ Instantiable<OFStatistics> requestInstantiable,
+ Instantiable<OFStatistics> replyInstantiable) {
+ this.type = (short) type;
+ this.requestClass = requestClass;
+ try {
+ this.requestConstructor = requestClass.getConstructor(new Class[]{});
+ } catch (Exception e) {
+ throw new RuntimeException(
+ "Failure getting constructor for class: " + requestClass, e);
+ }
+
+ this.replyClass = replyClass;
+ try {
+ this.replyConstructor = replyClass.getConstructor(new Class[]{});
+ } catch (Exception e) {
+ throw new RuntimeException(
+ "Failure getting constructor for class: " + replyClass, e);
+ }
+ this.requestInstantiable = requestInstantiable;
+ this.replyInstantiable = replyInstantiable;
+ OFStatisticsType.addMapping(this.type, OFType.STATS_REQUEST, this);
+ OFStatisticsType.addMapping(this.type, OFType.STATS_REPLY, this);
+ }
+
+ /**
+ * Adds a mapping from type value to OFStatisticsType enum
+ *
+ * @param i OpenFlow wire protocol type
+ * @param t type of containing OFMessage, only accepts STATS_REQUEST or
+ * STATS_REPLY
+ * @param st type
+ */
+ static public void addMapping(short i, OFType t, OFStatisticsType st) {
+ if (i < 0)
+ i = (short) (16+i);
+ if (t == OFType.STATS_REQUEST) {
+ if (requestMapping == null)
+ requestMapping = new OFStatisticsType[16];
+ OFStatisticsType.requestMapping[i] = st;
+ } else if (t == OFType.STATS_REPLY){
+ if (replyMapping == null)
+ replyMapping = new OFStatisticsType[16];
+ OFStatisticsType.replyMapping[i] = st;
+ } else {
+ throw new RuntimeException(t.toString() + " is an invalid OFType");
+ }
+ }
+
+ /**
+ * Remove a mapping from type value to OFStatisticsType enum
+ *
+ * @param i OpenFlow wire protocol type
+ * @param t type of containing OFMessage, only accepts STATS_REQUEST or
+ * STATS_REPLY
+ */
+ static public void removeMapping(short i, OFType t) {
+ if (i < 0)
+ i = (short) (16+i);
+ if (t == OFType.STATS_REQUEST) {
+ requestMapping[i] = null;
+ } else if (t == OFType.STATS_REPLY){
+ replyMapping[i] = null;
+ } else {
+ throw new RuntimeException(t.toString() + " is an invalid OFType");
+ }
+ }
+
+ /**
+ * Given a wire protocol OpenFlow type number, return the OFStatisticsType
+ * associated with it
+ *
+ * @param i wire protocol number
+ * @param t type of containing OFMessage, only accepts STATS_REQUEST or
+ * STATS_REPLY
+ * @return OFStatisticsType enum type
+ */
+ static public OFStatisticsType valueOf(short i, OFType t) {
+ if (i < 0)
+ i = (short) (16+i);
+ if (t == OFType.STATS_REQUEST) {
+ return requestMapping[i];
+ } else if (t == OFType.STATS_REPLY){
+ return replyMapping[i];
+ } else {
+ throw new RuntimeException(t.toString() + " is an invalid OFType");
+ }
+ }
+
+ /**
+ * @return Returns the wire protocol value corresponding to this
+ * OFStatisticsType
+ */
+ public short getTypeValue() {
+ return this.type;
+ }
+
+ /**
+ * @param t type of containing OFMessage, only accepts STATS_REQUEST or
+ * STATS_REPLY
+ * @return return the OFMessage subclass corresponding to this
+ * OFStatisticsType
+ */
+ public Class<? extends OFStatistics> toClass(OFType t) {
+ if (t == OFType.STATS_REQUEST) {
+ return requestClass;
+ } else if (t == OFType.STATS_REPLY){
+ return replyClass;
+ } else {
+ throw new RuntimeException(t.toString() + " is an invalid OFType");
+ }
+ }
+
+ /**
+ * Returns the no-argument Constructor of the implementation class for
+ * this OFStatisticsType, either request or reply based on the supplied
+ * OFType
+ *
+ * @param t
+ * @return
+ */
+ public Constructor<? extends OFStatistics> getConstructor(OFType t) {
+ if (t == OFType.STATS_REQUEST) {
+ return requestConstructor;
+ } else if (t == OFType.STATS_REPLY) {
+ return replyConstructor;
+ } else {
+ throw new RuntimeException(t.toString() + " is an invalid OFType");
+ }
+ }
+
+ /**
+ * @return the requestInstantiable
+ */
+ public Instantiable<OFStatistics> getRequestInstantiable() {
+ return requestInstantiable;
+ }
+
+ /**
+ * @param requestInstantiable the requestInstantiable to set
+ */
+ public void setRequestInstantiable(
+ Instantiable<OFStatistics> requestInstantiable) {
+ this.requestInstantiable = requestInstantiable;
+ }
+
+ /**
+ * @return the replyInstantiable
+ */
+ public Instantiable<OFStatistics> getReplyInstantiable() {
+ return replyInstantiable;
+ }
+
+ /**
+ * @param replyInstantiable the replyInstantiable to set
+ */
+ public void setReplyInstantiable(Instantiable<OFStatistics> replyInstantiable) {
+ this.replyInstantiable = replyInstantiable;
+ }
+
+ /**
+ * Returns a new instance of the implementation class for
+ * this OFStatisticsType, either request or reply based on the supplied
+ * OFType
+ *
+ * @param t
+ * @return
+ */
+ public OFStatistics newInstance(OFType t) {
+ if (t == OFType.STATS_REQUEST) {
+ return requestInstantiable.instantiate();
+ } else if (t == OFType.STATS_REPLY) {
+ return replyInstantiable.instantiate();
+ } else {
+ throw new RuntimeException(t.toString() + " is an invalid OFType");
+ }
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/statistics/OFTableStatistics.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/statistics/OFTableStatistics.java
new file mode 100644
index 0000000..9e6d34e
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/statistics/OFTableStatistics.java
@@ -0,0 +1,223 @@
+/**
+* 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.statistics;
+
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.openflow.util.StringByteSerializer;
+
+/**
+ * Represents an ofp_table_stats structure
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class OFTableStatistics implements OFStatistics {
+ public static int MAX_TABLE_NAME_LEN = 32;
+
+ protected byte tableId;
+ protected String name;
+ protected int wildcards;
+ protected int maximumEntries;
+ protected int activeCount;
+ protected long lookupCount;
+ protected long matchedCount;
+
+ /**
+ * @return the tableId
+ */
+ public byte getTableId() {
+ return tableId;
+ }
+
+ /**
+ * @param tableId the tableId to set
+ */
+ public void setTableId(byte tableId) {
+ this.tableId = tableId;
+ }
+
+ /**
+ * @return the name
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * @param name the name to set
+ */
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ /**
+ * @return the wildcards
+ */
+ public int getWildcards() {
+ return wildcards;
+ }
+
+ /**
+ * @param wildcards the wildcards to set
+ */
+ public void setWildcards(int wildcards) {
+ this.wildcards = wildcards;
+ }
+
+ /**
+ * @return the maximumEntries
+ */
+ public int getMaximumEntries() {
+ return maximumEntries;
+ }
+
+ /**
+ * @param maximumEntries the maximumEntries to set
+ */
+ public void setMaximumEntries(int maximumEntries) {
+ this.maximumEntries = maximumEntries;
+ }
+
+ /**
+ * @return the activeCount
+ */
+ public int getActiveCount() {
+ return activeCount;
+ }
+
+ /**
+ * @param activeCount the activeCount to set
+ */
+ public void setActiveCount(int activeCount) {
+ this.activeCount = activeCount;
+ }
+
+ /**
+ * @return the lookupCount
+ */
+ public long getLookupCount() {
+ return lookupCount;
+ }
+
+ /**
+ * @param lookupCount the lookupCount to set
+ */
+ public void setLookupCount(long lookupCount) {
+ this.lookupCount = lookupCount;
+ }
+
+ /**
+ * @return the matchedCount
+ */
+ public long getMatchedCount() {
+ return matchedCount;
+ }
+
+ /**
+ * @param matchedCount the matchedCount to set
+ */
+ public void setMatchedCount(long matchedCount) {
+ this.matchedCount = matchedCount;
+ }
+
+ @Override
+ public int getLength() {
+ return 64;
+ }
+
+ @Override
+ public void readFrom(ChannelBuffer data) {
+ this.tableId = data.readByte();
+ data.readByte(); // pad
+ data.readByte(); // pad
+ data.readByte(); // pad
+ this.name = StringByteSerializer.readFrom(data, MAX_TABLE_NAME_LEN);
+ this.wildcards = data.readInt();
+ this.maximumEntries = data.readInt();
+ this.activeCount = data.readInt();
+ this.lookupCount = data.readLong();
+ this.matchedCount = data.readLong();
+ }
+
+ @Override
+ public void writeTo(ChannelBuffer data) {
+ data.writeByte(this.tableId);
+ data.writeByte((byte) 0); // pad
+ data.writeByte((byte) 0); // pad
+ data.writeByte((byte) 0); // pad
+ StringByteSerializer.writeTo(data, MAX_TABLE_NAME_LEN, this.name);
+ data.writeInt(this.wildcards);
+ data.writeInt(this.maximumEntries);
+ data.writeInt(this.activeCount);
+ data.writeLong(this.lookupCount);
+ data.writeLong(this.matchedCount);
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 449;
+ int result = 1;
+ result = prime * result + activeCount;
+ result = prime * result + (int) (lookupCount ^ (lookupCount >>> 32));
+ result = prime * result + (int) (matchedCount ^ (matchedCount >>> 32));
+ result = prime * result + maximumEntries;
+ result = prime * result + ((name == null) ? 0 : name.hashCode());
+ result = prime * result + tableId;
+ result = prime * result + wildcards;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (!(obj instanceof OFTableStatistics)) {
+ return false;
+ }
+ OFTableStatistics other = (OFTableStatistics) obj;
+ if (activeCount != other.activeCount) {
+ return false;
+ }
+ if (lookupCount != other.lookupCount) {
+ return false;
+ }
+ if (matchedCount != other.matchedCount) {
+ return false;
+ }
+ if (maximumEntries != other.maximumEntries) {
+ return false;
+ }
+ if (name == null) {
+ if (other.name != null) {
+ return false;
+ }
+ } else if (!name.equals(other.name)) {
+ return false;
+ }
+ if (tableId != other.tableId) {
+ return false;
+ }
+ if (wildcards != other.wildcards) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/statistics/OFVendorStatistics.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/statistics/OFVendorStatistics.java
new file mode 100644
index 0000000..0257a6a
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/statistics/OFVendorStatistics.java
@@ -0,0 +1,83 @@
+/**
+* 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.statistics;
+
+
+import org.jboss.netty.buffer.ChannelBuffer;
+
+/**
+ * The base class for vendor implemented statistics
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class OFVendorStatistics implements OFStatistics {
+ protected int vendor;
+ protected byte[] body;
+
+ // non-message fields
+ protected int length = 0;
+
+ @Override
+ public void readFrom(ChannelBuffer data) {
+ this.vendor = data.readInt();
+ if (body == null)
+ body = new byte[length - 4];
+ data.readBytes(body);
+ }
+
+ @Override
+ public void writeTo(ChannelBuffer data) {
+ data.writeInt(this.vendor);
+ if (body != null)
+ data.writeBytes(body);
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 457;
+ int result = 1;
+ result = prime * result + vendor;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (!(obj instanceof OFVendorStatistics)) {
+ return false;
+ }
+ OFVendorStatistics other = (OFVendorStatistics) obj;
+ if (vendor != other.vendor) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int getLength() {
+ return length;
+ }
+
+ public void setLength(int length) {
+ this.length = length;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/vendor/OFBasicVendorDataType.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/vendor/OFBasicVendorDataType.java
new file mode 100644
index 0000000..1f0e14b
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/vendor/OFBasicVendorDataType.java
@@ -0,0 +1,71 @@
+/**
+* Copyright 2011, Big Switch Networks, Inc.
+* Originally created by David Erickson & Rob Sherwood, 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 org.openflow.protocol.vendor;
+
+import org.openflow.protocol.Instantiable;
+
+/**
+ * Subclass of OFVendorDataType that works with any vendor data format that
+ * begins with a integral value to indicate the format of the remaining data.
+ * It maps from the per-vendor-id integral data type code to the object
+ * used to instantiate the class associated with that vendor data type.
+ *
+ * @author Rob Vaterlaus (rob.vaterlaus@bigswitch.com)
+ */
+public class OFBasicVendorDataType extends OFVendorDataType {
+
+ /**
+ * The data type value at the beginning of the vendor data.
+ */
+ protected long type;
+
+ /**
+ * Construct an empty (i.e. no specified data type value) vendor data type.
+ */
+ public OFBasicVendorDataType() {
+ super();
+ this.type = 0;
+ }
+
+ /**
+ * Store some information about the vendor data type, including wire protocol
+ * type number, derived class and instantiator.
+ *
+ * @param type Wire protocol number associated with this vendor data type
+ * @param instantiator An Instantiator<OFVendorData> implementation that
+ * creates an instance of an appropriate subclass of OFVendorData.
+ */
+ public OFBasicVendorDataType(long type, Instantiable<OFVendorData> instantiator) {
+ super(instantiator);
+ this.type = type;
+ }
+
+ /**
+ * @return Returns the wire protocol value corresponding to this OFVendorDataType
+ */
+ public long getTypeValue() {
+ return this.type;
+ }
+
+ /**
+ * @param type the wire protocol value for this data type
+ */
+ public void setTypeValue(long type) {
+ this.type = type;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/vendor/OFBasicVendorId.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/vendor/OFBasicVendorId.java
new file mode 100644
index 0000000..09365d7
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/vendor/OFBasicVendorId.java
@@ -0,0 +1,162 @@
+/**
+* Copyright 2011, Big Switch Networks, Inc.
+* Originally created by David Erickson & Rob Sherwood, 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 org.openflow.protocol.vendor;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.openflow.protocol.Instantiable;
+
+/**
+ * Basic subclass of OFVendorId that works with any vendor data format where
+ * the data begins with an integral data type value.
+ *
+ * @author Rob Vaterlaus (rob.vaterlaus@bigswitch.com)
+ */
+public class OFBasicVendorId extends OFVendorId {
+
+ /**
+ * The size of the data type value at the beginning of all vendor
+ * data associated with this vendor id. The data type size must be
+ * either 1, 2, 4 or 8.
+ */
+ protected int dataTypeSize;
+
+ /**
+ * Map of the vendor data types that have been registered for this
+ * vendor id.
+ */
+ protected Map<Long, OFBasicVendorDataType> dataTypeMap =
+ new HashMap<Long, OFBasicVendorDataType>();
+
+ /**
+ * Construct an OFVendorId that where the vendor data begins
+ * with a data type value whose size is dataTypeSize.
+ * @param id the id of the vendor, typically the OUI of a vendor
+ * prefixed with 0.
+ * @param dataTypeSize the size of the integral data type value
+ * at the beginning of the vendor data. The value must be the
+ * size of an integeral data type (i.e. either 1,2,4 or 8).
+ */
+ public OFBasicVendorId(int id, int dataTypeSize) {
+ super(id);
+ assert (dataTypeSize == 1) || (dataTypeSize == 2) ||
+ (dataTypeSize == 4) || (dataTypeSize == 8);
+ this.dataTypeSize = dataTypeSize;
+ }
+
+ /**
+ * Get the size of the data type value at the beginning of the vendor
+ * data. OFBasicVendorId assumes that this value is common across all of
+ * the vendor data formats associated with a given vendor id.
+ * @return
+ */
+ public int getDataTypeSize() {
+ return dataTypeSize;
+ }
+
+ /**
+ * Register a vendor data type with this vendor id.
+ * @param vendorDataType
+ */
+ public void registerVendorDataType(OFBasicVendorDataType vendorDataType) {
+ dataTypeMap.put(vendorDataType.getTypeValue(), vendorDataType);
+ }
+
+ /**
+ * Lookup the OFVendorDataType instance that has been registered with
+ * this vendor id.
+ *
+ * @param vendorDataType the integer code that was parsed from the
+ * @return
+ */
+ public OFVendorDataType lookupVendorDataType(int vendorDataType) {
+ return dataTypeMap.get(vendorDataType);
+ }
+
+ /**
+ * This function parses enough of the data from the buffer to be able
+ * to determine the appropriate OFVendorDataType for the data. It is meant
+ * to be a reasonably generic implementation that will work for most
+ * formats of vendor extensions. If the vendor data doesn't fit the
+ * assumptions listed below, then this method will need to be overridden
+ * to implement custom parsing.
+ *
+ * This implementation assumes that the vendor data begins with a data
+ * type code that is used to distinguish different formats of vendor
+ * data associated with a particular vendor ID.
+ * The exact format of the data is vendor-defined, so we don't know how
+ * how big the code is (or really even if there is a code). This code
+ * assumes that the common case will be that the data does include
+ * an initial type code (i.e. so that the vendor can have multiple
+ * message/data types) and that the size is either 1, 2 or 4 bytes.
+ * The size of the initial type code is configured by the subclass of
+ * OFVendorId.
+ *
+ * @param data the channel buffer containing the vendor data.
+ * @param length the length to the end of the enclosing message
+ * @return the OFVendorDataType that can be used to instantiate the
+ * appropriate subclass of OFVendorData.
+ */
+ public OFVendorDataType parseVendorDataType(ChannelBuffer data, int length) {
+ OFVendorDataType vendorDataType = null;
+
+ // Parse out the type code from the vendor data.
+ long dataTypeValue = 0;
+ if ((length == 0) || (length >= dataTypeSize)) {
+ switch (dataTypeSize) {
+ case 1:
+ dataTypeValue = data.readByte();
+ break;
+ case 2:
+ dataTypeValue = data.readShort();
+ break;
+ case 4:
+ dataTypeValue = data.readInt();
+ break;
+ case 8:
+ dataTypeValue = data.readLong();
+ break;
+ default:
+ // This would be indicative of a coding error where the
+ // dataTypeSize was specified incorrectly. This should have been
+ // caught in the constructor for OFVendorId.
+ assert false;
+ }
+
+ vendorDataType = dataTypeMap.get(dataTypeValue);
+ }
+
+ // If we weren't able to parse/map the data to a known OFVendorDataType,
+ // then map it to a generic vendor data type.
+ if (vendorDataType == null) {
+ vendorDataType = new OFBasicVendorDataType(dataTypeValue,
+ new Instantiable<OFVendorData>() {
+ @Override
+ public OFVendorData instantiate() {
+ return new OFByteArrayVendorData();
+ }
+ }
+ );
+ }
+
+ return vendorDataType;
+ }
+
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/vendor/OFByteArrayVendorData.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/vendor/OFByteArrayVendorData.java
new file mode 100644
index 0000000..08fa003
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/vendor/OFByteArrayVendorData.java
@@ -0,0 +1,94 @@
+/**
+* Copyright 2011, Big Switch Networks, Inc.
+* Originally created by David Erickson & Rob Sherwood, 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 org.openflow.protocol.vendor;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+
+/**
+ * Basic implementation of OFVendorData that just treats the data as a
+ * byte array. This is used if there's an OFVendor message where there's
+ * no registered OFVendorId or no specific OFVendorDataType that can be
+ * determined from the data.
+ *
+ * @author Rob Vaterlaus (rob.vaterlaus@bigswitch.com)
+ */
+public class OFByteArrayVendorData implements OFVendorData {
+
+ protected byte[] bytes;
+
+ /**
+ * Construct vendor data with an empty byte array.
+ */
+ public OFByteArrayVendorData() {
+ }
+
+ /**
+ * Construct vendor data with the specified byte array.
+ * @param bytes
+ */
+ public OFByteArrayVendorData(byte[] bytes) {
+ this.bytes = bytes;
+ }
+
+ /**
+ * Get the associated byte array for this vendor data.
+ * @return the byte array containing the raw vendor data.
+ */
+ public byte[] getBytes() {
+ return bytes;
+ }
+
+ /**
+ * Set the byte array for the vendor data.
+ * @param bytes the raw byte array containing the vendor data.
+ */
+ public void setBytes(byte[] bytes) {
+ this.bytes = bytes;
+ }
+
+ /**
+ * Get the length of the vendor data. In this case it's just then length
+ * of the underlying byte array.
+ * @return the length of the vendor data
+ */
+ @Override
+ public int getLength() {
+ return (bytes != null) ? bytes.length : 0;
+ }
+
+ /**
+ * Read the vendor data from the ChannelBuffer into the byte array.
+ * @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) {
+ bytes = new byte[length];
+ data.readBytes(bytes);
+ }
+
+ /**
+ * Write the vendor data bytes to the ChannelBuffer
+ * @param data the channel buffer to which we're serializing
+ */
+ @Override
+ public void writeTo(ChannelBuffer data) {
+ if (bytes != null)
+ data.writeBytes(bytes);
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/vendor/OFVendorData.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/vendor/OFVendorData.java
new file mode 100644
index 0000000..6dfb4e6
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/vendor/OFVendorData.java
@@ -0,0 +1,44 @@
+/**
+* Copyright 2011, Big Switch Networks, Inc.
+* Originally created by David Erickson & Rob Sherwood, 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 org.openflow.protocol.vendor;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+
+/**
+ * The base class for all vendor data.
+ *
+ * @author Rob Vaterlaus (rob.vaterlaus@bigswitch.com)
+ */
+public interface OFVendorData {
+ /**
+ * @return length of the data
+ */
+ public int getLength();
+
+ /**
+ * Read the vendor data from the specified ChannelBuffer
+ * @param data
+ */
+ public void readFrom(ChannelBuffer data, int length);
+
+ /**
+ * Write the vendor data to the specified ChannelBuffer
+ * @param data
+ */
+ public void writeTo(ChannelBuffer data);
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/vendor/OFVendorDataType.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/vendor/OFVendorDataType.java
new file mode 100644
index 0000000..ecae482
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/vendor/OFVendorDataType.java
@@ -0,0 +1,79 @@
+/**
+* Copyright 2011, Big Switch Networks, Inc.
+* Originally created by David Erickson & Rob Sherwood, 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 org.openflow.protocol.vendor;
+
+import org.openflow.protocol.Instantiable;
+
+/**
+ * Class that represents a specific vendor data type format in an
+ * OFVendor message. Typically the vendor data will begin with an integer
+ * code that determines the format of the rest of the data, but this
+ * class does not assume that. It's basically just a holder for an
+ * instantiator of the appropriate subclass of OFVendorData.
+ *
+ * @author Rob Vaterlaus (rob.vaterlaus@bigswitch.com)
+ */
+public class OFVendorDataType {
+
+ /**
+ * Object that instantiates the subclass of OFVendorData
+ * associated with this data type.
+ */
+ protected Instantiable<OFVendorData> instantiable;
+
+ /**
+ * Construct an empty vendor data type.
+ */
+ public OFVendorDataType() {
+ super();
+ }
+
+ /**
+ * Construct a vendor data type with the specified instantiable.
+ * @param instantiable object that creates the subclass of OFVendorData
+ * associated with this data type.
+ */
+ public OFVendorDataType(Instantiable<OFVendorData> instantiable) {
+ this.instantiable = instantiable;
+ }
+
+ /**
+ * Returns a new instance of a subclass of OFVendorData associated with
+ * this OFVendorDataType.
+ *
+ * @return the new object
+ */
+ public OFVendorData newInstance() {
+ return instantiable.instantiate();
+ }
+
+ /**
+ * @return the instantiable
+ */
+ public Instantiable<OFVendorData> getInstantiable() {
+ return instantiable;
+ }
+
+ /**
+ * @param instantiable the instantiable to set
+ */
+ public void setInstantiable(Instantiable<OFVendorData> instantiable) {
+ this.instantiable = instantiable;
+ }
+
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/protocol/vendor/OFVendorId.java b/src/ext/floodlight/src/main/java/org/openflow/protocol/vendor/OFVendorId.java
new file mode 100644
index 0000000..f0af8a7
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/protocol/vendor/OFVendorId.java
@@ -0,0 +1,85 @@
+/**
+* Copyright 2011, Big Switch Networks, Inc.
+* Originally created by David Erickson & Rob Sherwood, 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 org.openflow.protocol.vendor;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+
+/**
+ * Base class for the vendor ID corresponding to vendor extensions from a
+ * given vendor. It is responsible for knowing how to parse out some sort of
+ * data type value from the vendor data in an OFVendor message so that we can
+ * dispatch to the different subclasses of OFVendorData corresponding to the
+ * different formats of data for the vendor extensions.
+ *
+ * @author Rob Vaterlaus (rob.vaterlaus@bigswitch.com)
+ */
+public abstract class OFVendorId {
+ static Map<Integer, OFVendorId> mapping = new HashMap<Integer, OFVendorId>();
+
+ /**
+ * The vendor id value, typically the OUI of the vendor prefixed with 0.
+ */
+ protected int id;
+
+ /**
+ * Register a new vendor id.
+ * @param vendorId the vendor id to register
+ */
+ public static void registerVendorId(OFVendorId vendorId) {
+ mapping.put(vendorId.getId(), vendorId);
+ }
+
+ /**
+ * Lookup the OFVendorId instance corresponding to the given id value.
+ * @param id the integer vendor id value
+ * @return the corresponding OFVendorId that's been registered for the
+ * given value, or null if there id has not been registered.
+ */
+ public static OFVendorId lookupVendorId(int id) {
+ return mapping.get(id);
+ }
+
+ /**
+ * Create an OFVendorId with the give vendor id value
+ * @param id
+ */
+ public OFVendorId(int id) {
+ this.id = id;
+ }
+
+ /**
+ * @return the vendor id value
+ */
+ public int getId() {
+ return id;
+ }
+
+ /**
+ * This function parses enough of the data from the channel buffer to be
+ * able to determine the appropriate OFVendorDataType for the data.
+ *
+ * @param data the channel buffer containing the vendor data.
+ * @param length the length to the end of the enclosing message
+ * @return the OFVendorDataType that can be used to instantiate the
+ * appropriate subclass of OFVendorData.
+ */
+ public abstract OFVendorDataType parseVendorDataType(ChannelBuffer data, int length);
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/util/HexString.java b/src/ext/floodlight/src/main/java/org/openflow/util/HexString.java
new file mode 100644
index 0000000..5777612
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/util/HexString.java
@@ -0,0 +1,92 @@
+/**
+* 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;
+
+public class HexString {
+ /**
+ * Convert a string of bytes to a ':' separated hex string
+ * @param bytes
+ * @return "0f:ca:fe:de:ad:be:ef"
+ */
+ public static String toHexString(byte[] bytes) {
+ int i;
+ String ret = "";
+ String tmp;
+ for(i=0; i< bytes.length; i++) {
+ if(i> 0)
+ ret += ":";
+ tmp = Integer.toHexString(U8.f(bytes[i]));
+ if (tmp.length() == 1)
+ ret += "0";
+ ret += tmp;
+ }
+ return ret;
+ }
+
+ public static String toHexString(long val, int padTo) {
+ char arr[] = Long.toHexString(val).toCharArray();
+ String ret = "";
+ // prepend the right number of leading zeros
+ int i = 0;
+ for (; i < (padTo * 2 - arr.length); i++) {
+ ret += "0";
+ if ((i % 2) == 1)
+ ret += ":";
+ }
+ for (int j = 0; j < arr.length; j++) {
+ ret += arr[j];
+ if ((((i + j) % 2) == 1) && (j < (arr.length - 1)))
+ ret += ":";
+ }
+ return ret;
+ }
+
+ public static String toHexString(long val) {
+ return toHexString(val, 8);
+ }
+
+
+ /**
+ * Convert a string of hex values into a string of bytes
+ * @param values "0f:ca:fe:de:ad:be:ef"
+ * @return [15, 5 ,2, 5, 17]
+ * @throws NumberFormatException If the string can not be parsed
+ */
+ public static byte[] fromHexString(String values) throws NumberFormatException {
+ String[] octets = values.split(":");
+ byte[] ret = new byte[octets.length];
+
+ for(int i = 0; i < octets.length; i++) {
+ if (octets[i].length() > 2)
+ throw new NumberFormatException("Invalid octet length");
+ ret[i] = Integer.valueOf(octets[i], 16).byteValue();
+ }
+ return ret;
+ }
+
+ public static long toLong(String values) throws NumberFormatException {
+ // Long.parseLong() can't handle HexStrings with MSB set. Sigh.
+ BigInteger bi = new BigInteger(values.replaceAll(":", ""),16);
+ if (bi.bitLength() > 64)
+ throw new NumberFormatException("Input string too big to fit in long: " + values);
+ return bi.longValue();
+ }
+
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/util/LRULinkedHashMap.java b/src/ext/floodlight/src/main/java/org/openflow/util/LRULinkedHashMap.java
new file mode 100644
index 0000000..7f05381
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/util/LRULinkedHashMap.java
@@ -0,0 +1,42 @@
+/**
+* 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.util.LinkedHashMap;
+
+public class LRULinkedHashMap<K, V> extends LinkedHashMap<K, V> {
+ private static final long serialVersionUID = -2964986094089626647L;
+ protected int maximumCapacity;
+
+ public LRULinkedHashMap(int initialCapacity, int maximumCapacity) {
+ super(initialCapacity, 0.75f, true);
+ this.maximumCapacity = maximumCapacity;
+ }
+
+ public LRULinkedHashMap(int maximumCapacity) {
+ super(16, 0.75f, true);
+ this.maximumCapacity = maximumCapacity;
+ }
+
+ @Override
+ protected boolean removeEldestEntry(java.util.Map.Entry<K, V> eldest) {
+ if (this.size() > maximumCapacity)
+ return true;
+ return false;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/util/StringByteSerializer.java b/src/ext/floodlight/src/main/java/org/openflow/util/StringByteSerializer.java
new file mode 100644
index 0000000..9287fd2
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/util/StringByteSerializer.java
@@ -0,0 +1,58 @@
+/**
+* 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.io.UnsupportedEncodingException;
+import java.nio.charset.Charset;
+import java.util.Arrays;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+
+public class StringByteSerializer {
+ public static String readFrom(ChannelBuffer data, int length) {
+ byte[] stringBytes = new byte[length];
+ data.readBytes(stringBytes);
+ // find the first index of 0
+ int index = 0;
+ for (byte b : stringBytes) {
+ if (0 == b)
+ break;
+ ++index;
+ }
+ return new String(Arrays.copyOf(stringBytes, index),
+ Charset.forName("ascii"));
+ }
+
+ public static void writeTo(ChannelBuffer data, int length, String value) {
+ try {
+ byte[] name = value.getBytes("ASCII");
+ if (name.length < length) {
+ data.writeBytes(name);
+ for (int i = name.length; i < length; ++i) {
+ data.writeByte((byte) 0);
+ }
+ } else {
+ data.writeBytes(name, 0, length-1);
+ data.writeByte((byte) 0);
+ }
+ } catch (UnsupportedEncodingException e) {
+ throw new RuntimeException(e);
+ }
+
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/util/U16.java b/src/ext/floodlight/src/main/java/org/openflow/util/U16.java
new file mode 100644
index 0000000..0d8917d
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/util/U16.java
@@ -0,0 +1,28 @@
+/**
+* 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;
+
+public class U16 {
+ public static int f(short i) {
+ return (int)i & 0xffff;
+ }
+
+ public static short t(int l) {
+ return (short) l;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/util/U32.java b/src/ext/floodlight/src/main/java/org/openflow/util/U32.java
new file mode 100644
index 0000000..3aab400
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/util/U32.java
@@ -0,0 +1,28 @@
+/**
+* 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;
+
+public class U32 {
+ public static long f(int i) {
+ return (long)i & 0xffffffffL;
+ }
+
+ public static int t(long l) {
+ return (int) l;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/util/U64.java b/src/ext/floodlight/src/main/java/org/openflow/util/U64.java
new file mode 100644
index 0000000..c6ae0f7
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/util/U64.java
@@ -0,0 +1,30 @@
+/**
+* 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;
+
+public class U64 {
+ public static BigInteger f(long i) {
+ return new BigInteger(Long.toBinaryString(i), 2);
+ }
+
+ public static long t(BigInteger l) {
+ return l.longValue();
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/util/U8.java b/src/ext/floodlight/src/main/java/org/openflow/util/U8.java
new file mode 100644
index 0000000..0b575ad
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/util/U8.java
@@ -0,0 +1,28 @@
+/**
+* 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;
+
+public class U8 {
+ public static short f(byte i) {
+ return (short) ((short)i & 0xff);
+ }
+
+ public static byte t(short l) {
+ return (byte) l;
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/util/Unsigned.java b/src/ext/floodlight/src/main/java/org/openflow/util/Unsigned.java
new file mode 100644
index 0000000..0754a3f
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/util/Unsigned.java
@@ -0,0 +1,212 @@
+/**
+* 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;
+
+/*****
+ * A util library class for dealing with the lack of unsigned datatypes in Java
+ *
+ * @author Rob Sherwood (rob.sherwood@stanford.edu)
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+
+public class Unsigned {
+ /**
+ * Get an unsigned byte from the current position of the ByteBuffer
+ *
+ * @param bb ByteBuffer to get the byte from
+ * @return an unsigned byte contained in a short
+ */
+ public static short getUnsignedByte(ByteBuffer bb) {
+ return ((short) (bb.get() & (short) 0xff));
+ }
+
+ /**
+ * Get an unsigned byte from the specified offset in the ByteBuffer
+ *
+ * @param bb ByteBuffer to get the byte from
+ * @param offset the offset to get the byte from
+ * @return an unsigned byte contained in a short
+ */
+ public static short getUnsignedByte(ByteBuffer bb, int offset) {
+ return ((short) (bb.get(offset) & (short) 0xff));
+ }
+
+ /**
+ * Put an unsigned byte into the specified ByteBuffer at the current
+ * position
+ *
+ * @param bb ByteBuffer to put the byte into
+ * @param v the short containing the unsigned byte
+ */
+ public static void putUnsignedByte(ByteBuffer bb, short v) {
+ bb.put((byte) (v & 0xff));
+ }
+
+ /**
+ * Put an unsigned byte into the specified ByteBuffer at the specified
+ * offset
+ *
+ * @param bb ByteBuffer to put the byte into
+ * @param v the short containing the unsigned byte
+ * @param offset the offset to insert the unsigned byte at
+ */
+ public static void putUnsignedByte(ByteBuffer bb, short v, int offset) {
+ bb.put(offset, (byte) (v & 0xff));
+ }
+
+ /**
+ * Get an unsigned short from the current position of the ByteBuffer
+ *
+ * @param bb ByteBuffer to get the byte from
+ * @return an unsigned short contained in a int
+ */
+ public static int getUnsignedShort(ByteBuffer bb) {
+ return (bb.getShort() & 0xffff);
+ }
+
+ /**
+ * Get an unsigned short from the specified offset in the ByteBuffer
+ *
+ * @param bb ByteBuffer to get the short from
+ * @param offset the offset to get the short from
+ * @return an unsigned short contained in a int
+ */
+ public static int getUnsignedShort(ByteBuffer bb, int offset) {
+ return (bb.getShort(offset) & 0xffff);
+ }
+
+ /**
+ * Put an unsigned short into the specified ByteBuffer at the current
+ * position
+ *
+ * @param bb ByteBuffer to put the short into
+ * @param v the int containing the unsigned short
+ */
+ public static void putUnsignedShort(ByteBuffer bb, int v) {
+ bb.putShort((short) (v & 0xffff));
+ }
+
+ /**
+ * Put an unsigned short into the specified ByteBuffer at the specified
+ * offset
+ *
+ * @param bb ByteBuffer to put the short into
+ * @param v the int containing the unsigned short
+ * @param offset the offset to insert the unsigned short at
+ */
+ public static void putUnsignedShort(ByteBuffer bb, int v, int offset) {
+ bb.putShort(offset, (short) (v & 0xffff));
+ }
+
+ /**
+ * Get an unsigned int from the current position of the ByteBuffer
+ *
+ * @param bb ByteBuffer to get the int from
+ * @return an unsigned int contained in a long
+ */
+ public static long getUnsignedInt(ByteBuffer bb) {
+ return ((long) bb.getInt() & 0xffffffffL);
+ }
+
+ /**
+ * Get an unsigned int from the specified offset in the ByteBuffer
+ *
+ * @param bb ByteBuffer to get the int from
+ * @param offset the offset to get the int from
+ * @return an unsigned int contained in a long
+ */
+ public static long getUnsignedInt(ByteBuffer bb, int offset) {
+ return ((long) bb.getInt(offset) & 0xffffffffL);
+ }
+
+ /**
+ * Put an unsigned int into the specified ByteBuffer at the current position
+ *
+ * @param bb ByteBuffer to put the int into
+ * @param v the long containing the unsigned int
+ */
+ public static void putUnsignedInt(ByteBuffer bb, long v) {
+ bb.putInt((int) (v & 0xffffffffL));
+ }
+
+ /**
+ * Put an unsigned int into the specified ByteBuffer at the specified offset
+ *
+ * @param bb ByteBuffer to put the int into
+ * @param v the long containing the unsigned int
+ * @param offset the offset to insert the unsigned int at
+ */
+ public static void putUnsignedInt(ByteBuffer bb, long v, int offset) {
+ bb.putInt(offset, (int) (v & 0xffffffffL));
+ }
+
+ /**
+ * Get an unsigned long from the current position of the ByteBuffer
+ *
+ * @param bb ByteBuffer to get the long from
+ * @return an unsigned long contained in a BigInteger
+ */
+ public static BigInteger getUnsignedLong(ByteBuffer bb) {
+ byte[] v = new byte[8];
+ for (int i = 0; i < 8; ++i) {
+ v[i] = bb.get(i);
+ }
+ return new BigInteger(1, v);
+ }
+
+ /**
+ * Get an unsigned long from the specified offset in the ByteBuffer
+ *
+ * @param bb ByteBuffer to get the long from
+ * @param offset the offset to get the long from
+ * @return an unsigned long contained in a BigInteger
+ */
+ public static BigInteger getUnsignedLong(ByteBuffer bb, int offset) {
+ byte[] v = new byte[8];
+ for (int i = 0; i < 8; ++i) {
+ v[i] = bb.get(offset+i);
+ }
+ return new BigInteger(1, v);
+ }
+
+ /**
+ * Put an unsigned long into the specified ByteBuffer at the current
+ * position
+ *
+ * @param bb ByteBuffer to put the long into
+ * @param v the BigInteger containing the unsigned long
+ */
+ public static void putUnsignedLong(ByteBuffer bb, BigInteger v) {
+ bb.putLong(v.longValue());
+ }
+
+ /**
+ * Put an unsigned long into the specified ByteBuffer at the specified
+ * offset
+ *
+ * @param bb ByteBuffer to put the long into
+ * @param v the BigInteger containing the unsigned long
+ * @param offset the offset to insert the unsigned long at
+ */
+ public static void putUnsignedLong(ByteBuffer bb, BigInteger v, int offset) {
+ bb.putLong(offset, v.longValue());
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/vendor/nicira/OFNiciraVendorData.java b/src/ext/floodlight/src/main/java/org/openflow/vendor/nicira/OFNiciraVendorData.java
new file mode 100644
index 0000000..687d544
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/vendor/nicira/OFNiciraVendorData.java
@@ -0,0 +1,98 @@
+/**
+* Copyright 2011, Big Switch Networks, Inc.
+* Originally created by David Erickson & Rob Sherwood, 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 org.openflow.vendor.nicira;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.openflow.protocol.vendor.OFVendorData;
+
+/**
+ * Base class for vendor data corresponding to a Nicira vendor extension.
+ * Nicira vendor data always starts with a 4-byte integer data type value.
+ *
+ * @author Rob Vaterlaus (rob.vaterlaus@bigswitch.com)
+ */
+public class OFNiciraVendorData implements OFVendorData {
+
+ public static final int NX_VENDOR_ID = 0x00002320;
+ /**
+ * The value of the integer data type at the beginning of the vendor data
+ */
+ protected int dataType;
+
+ /**
+ * Construct empty (i.e. unspecified data type) Nicira vendor data.
+ */
+ public OFNiciraVendorData() {
+ }
+
+ /**
+ * Contruct Nicira vendor data with the specified data type
+ * @param dataType the data type value at the beginning of the vendor data.
+ */
+ public OFNiciraVendorData(int dataType) {
+ this.dataType = dataType;
+ }
+
+ /**
+ * Get the data type value at the beginning of the vendor data
+ * @return the integer data type value
+ */
+ public int getDataType() {
+ return dataType;
+ }
+
+ /**
+ * Set the data type value
+ * @param dataType the integer data type value at the beginning of the
+ * vendor data.
+ */
+ 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);
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/vendor/nicira/OFRoleReplyVendorData.java b/src/ext/floodlight/src/main/java/org/openflow/vendor/nicira/OFRoleReplyVendorData.java
new file mode 100644
index 0000000..fa28c71
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/vendor/nicira/OFRoleReplyVendorData.java
@@ -0,0 +1,66 @@
+/**
+* Copyright 2011, Big Switch Networks, Inc.
+* Originally created by David Erickson & Rob Sherwood, 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 org.openflow.vendor.nicira;
+
+import org.openflow.protocol.Instantiable;
+import org.openflow.protocol.vendor.OFVendorData;
+
+/**
+ * Subclass of OFVendorData representing the vendor data associated with
+ * a role reply vendor extension.
+ *
+ * @author Rob Vaterlaus (rob.vaterlaus@bigswitch.com)
+ */
+public class OFRoleReplyVendorData extends OFRoleVendorData {
+
+ protected static Instantiable<OFVendorData> instantiable =
+ new Instantiable<OFVendorData>() {
+ public OFVendorData instantiate() {
+ return new OFRoleReplyVendorData();
+ }
+ };
+
+ /**
+ * @return a subclass of Instantiable<OFVendorData> that instantiates
+ * an instance of OFRoleReplyVendorData.
+ */
+ public static Instantiable<OFVendorData> getInstantiable() {
+ return instantiable;
+ }
+
+ /**
+ * The data type value for a role reply
+ */
+ public static final int NXT_ROLE_REPLY = 11;
+
+ /**
+ * Construct a role reply vendor data with an unspecified role value.
+ */
+ public OFRoleReplyVendorData() {
+ super(NXT_ROLE_REPLY);
+ }
+
+ /**
+ * Construct a role reply vendor data with the specified role value.
+ * @param role the role value for the role reply. Should be one of
+ * NX_ROLE_OTHER, NX_ROLE_MASTER or NX_ROLE_SLAVE.
+ */
+ public OFRoleReplyVendorData(int role) {
+ super(NXT_ROLE_REPLY, role);
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/vendor/nicira/OFRoleRequestVendorData.java b/src/ext/floodlight/src/main/java/org/openflow/vendor/nicira/OFRoleRequestVendorData.java
new file mode 100644
index 0000000..e7dbe71
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/vendor/nicira/OFRoleRequestVendorData.java
@@ -0,0 +1,66 @@
+/**
+* Copyright 2011, Big Switch Networks, Inc.
+* Originally created by David Erickson & Rob Sherwood, 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 org.openflow.vendor.nicira;
+
+import org.openflow.protocol.Instantiable;
+import org.openflow.protocol.vendor.OFVendorData;
+
+/**
+ * Subclass of OFVendorData representing the vendor data associated with
+ * a role request vendor extension.
+ *
+ * @author Rob Vaterlaus (rob.vaterlaus@bigswitch.com)
+ */
+public class OFRoleRequestVendorData extends OFRoleVendorData {
+
+ protected static Instantiable<OFVendorData> instantiable =
+ new Instantiable<OFVendorData>() {
+ public OFVendorData instantiate() {
+ return new OFRoleRequestVendorData();
+ }
+ };
+
+ /**
+ * @return a subclass of Instantiable<OFVendorData> that instantiates
+ * an instance of OFRoleRequestVendorData.
+ */
+ public static Instantiable<OFVendorData> getInstantiable() {
+ return instantiable;
+ }
+
+ /**
+ * The data type value for a role request
+ */
+ public static final int NXT_ROLE_REQUEST = 10;
+
+ /**
+ * Construct a role request vendor data with an unspecified role value.
+ */
+ public OFRoleRequestVendorData() {
+ super(NXT_ROLE_REQUEST);
+ }
+
+ /**
+ * Construct a role request vendor data with the specified role value.
+ * @param role the role value for the role request. Should be one of
+ * NX_ROLE_OTHER, NX_ROLE_MASTER or NX_ROLE_SLAVE.
+ */
+ public OFRoleRequestVendorData(int role) {
+ super(NXT_ROLE_REQUEST, role);
+ }
+}
diff --git a/src/ext/floodlight/src/main/java/org/openflow/vendor/nicira/OFRoleVendorData.java b/src/ext/floodlight/src/main/java/org/openflow/vendor/nicira/OFRoleVendorData.java
new file mode 100644
index 0000000..e7c8bf2
--- /dev/null
+++ b/src/ext/floodlight/src/main/java/org/openflow/vendor/nicira/OFRoleVendorData.java
@@ -0,0 +1,113 @@
+/**
+* Copyright 2011, Big Switch Networks, Inc.
+* Originally created by David Erickson & Rob Sherwood, 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 org.openflow.vendor.nicira;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+
+/**
+ * Class that represents the vendor data in the role request
+ * extension implemented by Open vSwitch to support high availability.
+ *
+ * @author Rob Vaterlaus (rob.vaterlaus@bigswitch.com)
+ */
+public class OFRoleVendorData extends OFNiciraVendorData {
+
+ /**
+ * Role value indicating that the controller is in the OTHER role.
+ */
+ public static final int NX_ROLE_OTHER = 0;
+
+ /**
+ * Role value indicating that the controller is in the MASTER role.
+ */
+ public static final int NX_ROLE_MASTER = 1;
+
+ /**
+ * Role value indicating that the controller is in the SLAVE role.
+ */
+ public static final int NX_ROLE_SLAVE = 2;
+
+ protected int role;
+
+ /**
+ * Construct an uninitialized OFRoleVendorData
+ */
+ public OFRoleVendorData() {
+ super();
+ }
+
+ /**
+ * Construct an OFRoleVendorData with the specified data type
+ * (i.e. either request or reply) and an unspecified role.
+ * @param dataType
+ */
+ public OFRoleVendorData(int dataType) {
+ super(dataType);
+ }
+
+ /**
+ * Construct an OFRoleVendorData with the specified data type
+ * (i.e. either request or reply) and role (i.e. one of of
+ * master, slave, or other).
+ * @param dataType either role request or role reply data type
+ */
+ public OFRoleVendorData(int dataType, int role) {
+ super(dataType);
+ this.role = role;
+ }
+ /**
+ * @return the role value of the role vendor data
+ */
+ public int getRole() {
+ return role;
+ }
+
+ /**
+ * @param role the role value of the role vendor data
+ */
+ public void setRole(int role) {
+ this.role = role;
+ }
+
+ /**
+ * @return the total length of the role vendor data
+ */
+ @Override
+ public int getLength() {
+ return super.getLength() + 4;
+ }
+
+ /**
+ * Read the role 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
+ */
+ public void readFrom(ChannelBuffer data, int length) {
+ super.readFrom(data, length);
+ role = data.readInt();
+ }
+
+ /**
+ * Write the role vendor data to the ChannelBuffer
+ * @param data the channel buffer to which we're serializing
+ */
+ public void writeTo(ChannelBuffer data) {
+ super.writeTo(data);
+ data.writeInt(role);
+ }
+}
diff --git a/src/ext/floodlight/src/main/python/PythonClient.py b/src/ext/floodlight/src/main/python/PythonClient.py
new file mode 100644
index 0000000..5c9890e
--- /dev/null
+++ b/src/ext/floodlight/src/main/python/PythonClient.py
@@ -0,0 +1,48 @@
+#!/usr/bin/env python
+
+import sys
+sys.path.append('../../../target/gen-py')
+
+from packetstreamer import PacketStreamer
+from packetstreamer.ttypes import *
+
+from thrift import Thrift
+from thrift.transport import TSocket
+from thrift.transport import TTransport
+from thrift.protocol import TBinaryProtocol
+
+try:
+
+ # Make socket
+ transport = TSocket.TSocket('localhost', 9090)
+
+ # Buffering is critical. Raw sockets are very slow
+ transport = TTransport.TFramedTransport(transport)
+
+ # Wrap in a protocol
+ protocol = TBinaryProtocol.TBinaryProtocol(transport)
+
+ # Create a client to use the protocol encoder
+ client = PacketStreamer.Client(protocol)
+
+ # Connect!
+ transport.open()
+
+ while 1:
+ packets = client.getPackets("session1")
+ print 'session1 packets num: %d' % (len(packets))
+ count = 1
+ for packet in packets:
+ print "Packet %d: %s"% (count, packet)
+ if "FilterTimeout" in packet:
+ sys.exit()
+ count += 1
+
+ # Close!
+ transport.close()
+
+except Thrift.TException, tx:
+ print '%s' % (tx.message)
+
+except KeyboardInterrupt, e:
+ print 'Bye-bye'
diff --git a/src/ext/floodlight/src/main/python/PythonServer.py b/src/ext/floodlight/src/main/python/PythonServer.py
new file mode 100644
index 0000000..c3c844e
--- /dev/null
+++ b/src/ext/floodlight/src/main/python/PythonServer.py
@@ -0,0 +1,49 @@
+#!/usr/bin/env python
+
+import sys
+import logging
+sys.path.append('../../../target/gen-py')
+
+from packetstreamer import PacketStreamer
+from packetstreamer.ttypes import *
+
+from thrift.transport import TSocket
+from thrift.transport import TTransport
+from thrift.protocol import TBinaryProtocol
+from thrift.server import TServer
+
+class PacketStreamerHandler:
+ def __init__(self):
+ logging.handlers.codecs = None
+ self.log = logging.getLogger("packetstreamer")
+ self.log.setLevel(logging.DEBUG)
+ handler = logging.handlers.SysLogHandler("/dev/log")
+ handler.setFormatter(logging.Formatter("%(name)s: %(levelname)s %(message)s"))
+ self.log.addHandler(handler)
+
+ def ping(self):
+ self.log.debug('ping()')
+ return true
+
+ def pushPacketSync(self, packet):
+ self.log.debug('receive a packet synchronously: %s' %(packet))
+ return 0
+
+ def pushPacketAsync(self, packet):
+ self.log.debug('receive a packet Asynchronously: %s' %(packet))
+
+handler = PacketStreamerHandler()
+processor = PacketStreamer.Processor(handler)
+transport = TSocket.TServerSocket(9090)
+tfactory = TTransport.TBufferedTransportFactory()
+pfactory = TBinaryProtocol.TBinaryProtocolFactory()
+
+server = TServer.TSimpleServer(processor, transport, tfactory, pfactory)
+
+# You could do one of these for a multithreaded server
+#server = TServer.TThreadedServer(processor, transport, tfactory, pfactory)
+#server = TServer.TThreadPoolServer(processor, transport, tfactory, pfactory)
+
+print 'Starting the server...'
+server.serve()
+print 'done.'
diff --git a/src/ext/floodlight/src/main/python/compileall.py b/src/ext/floodlight/src/main/python/compileall.py
new file mode 100644
index 0000000..b21d95f
--- /dev/null
+++ b/src/ext/floodlight/src/main/python/compileall.py
@@ -0,0 +1,157 @@
+"""Module/script to "compile" all .py files to .pyc (or .pyo) file.
+
+When called as a script with arguments, this compiles the directories
+given as arguments recursively; the -l option prevents it from
+recursing into directories.
+
+Without arguments, if compiles all modules on sys.path, without
+recursing into subdirectories. (Even though it should do so for
+packages -- for now, you'll have to deal with packages separately.)
+
+See module py_compile for details of the actual byte-compilation.
+
+"""
+
+import os
+import sys
+import py_compile
+
+__all__ = ["compile_dir","compile_path"]
+
+def compile_dir(dir, maxlevels=10, ddir=None,
+ force=0, rx=None, quiet=0):
+ """Byte-compile all modules in the given directory tree.
+
+ Arguments (only dir is required):
+
+ dir: the directory to byte-compile
+ maxlevels: maximum recursion level (default 10)
+ ddir: if given, purported directory name (this is the
+ directory name that will show up in error messages)
+ force: if 1, force compilation, even if timestamps are up-to-date
+ quiet: if 1, be quiet during compilation
+
+ """
+ if not quiet:
+ print 'Listing', dir, '...'
+ try:
+ names = os.listdir(dir)
+ except os.error:
+ print "Can't list", dir
+ names = []
+ names.sort()
+ success = 1
+ for name in names:
+ fullname = os.path.join(dir, name)
+ if ddir is not None:
+ dfile = os.path.join(ddir, name)
+ else:
+ dfile = None
+ if rx is not None:
+ mo = rx.search(fullname)
+ if mo:
+ continue
+ if os.path.isfile(fullname):
+ head, tail = name[:-3], name[-3:]
+ if tail == '.py':
+ cfile = fullname + (__debug__ and 'c' or 'o')
+ ftime = os.stat(fullname).st_mtime
+ try: ctime = os.stat(cfile).st_mtime
+ except os.error: ctime = 0
+ if (ctime > ftime) and not force: continue
+ if not quiet:
+ print 'Compiling', fullname, '...'
+ try:
+ ok = py_compile.compile(fullname, None, dfile, True)
+ except KeyboardInterrupt:
+ raise KeyboardInterrupt
+ except py_compile.PyCompileError,err:
+ if quiet:
+ print 'Compiling', fullname, '...'
+ print err.msg
+ success = 0
+ except IOError, e:
+ print "Sorry", e
+ success = 0
+ else:
+ if ok == 0:
+ success = 0
+ elif maxlevels > 0 and \
+ name != os.curdir and name != os.pardir and \
+ os.path.isdir(fullname) and \
+ not os.path.islink(fullname):
+ if not compile_dir(fullname, maxlevels - 1, dfile, force, rx, quiet):
+ success = 0
+ return success
+
+def compile_path(skip_curdir=1, maxlevels=0, force=0, quiet=0):
+ """Byte-compile all module on sys.path.
+
+ Arguments (all optional):
+
+ skip_curdir: if true, skip current directory (default true)
+ maxlevels: max recursion level (default 0)
+ force: as for compile_dir() (default 0)
+ quiet: as for compile_dir() (default 0)
+
+ """
+ success = 1
+ for dir in sys.path:
+ if (not dir or dir == os.curdir) and skip_curdir:
+ print 'Skipping current directory'
+ else:
+ success = success and compile_dir(dir, maxlevels, None,
+ force, quiet=quiet)
+ return success
+
+def main():
+ """Script main program."""
+ import getopt
+ try:
+ opts, args = getopt.getopt(sys.argv[1:], 'lfqd:x:')
+ except getopt.error, msg:
+ print msg
+ print "usage: python compileall.py [-l] [-f] [-q] [-d destdir] " \
+ "[-x regexp] [directory ...]"
+ print "-l: don't recurse down"
+ print "-f: force rebuild even if timestamps are up-to-date"
+ print "-q: quiet operation"
+ print "-d destdir: purported directory name for error messages"
+ print " if no directory arguments, -l sys.path is assumed"
+ print "-x regexp: skip files matching the regular expression regexp"
+ print " the regexp is search for in the full path of the file"
+ sys.exit(2)
+ maxlevels = 10
+ ddir = None
+ force = 0
+ quiet = 0
+ rx = None
+ for o, a in opts:
+ if o == '-l': maxlevels = 0
+ if o == '-d': ddir = a
+ if o == '-f': force = 1
+ if o == '-q': quiet = 1
+ if o == '-x':
+ import re
+ rx = re.compile(a)
+ if ddir:
+ if len(args) != 1:
+ print "-d destdir require exactly one directory argument"
+ sys.exit(2)
+ success = 1
+ try:
+ if args:
+ for dir in args:
+ if not compile_dir(dir, maxlevels, ddir,
+ force, rx, quiet):
+ success = 0
+ else:
+ success = compile_path()
+ except KeyboardInterrupt:
+ print "\n[interrupt]"
+ success = 0
+ return success
+
+if __name__ == '__main__':
+ exit_status = int(not main())
+ sys.exit(exit_status)
diff --git a/src/ext/floodlight/src/main/python/debugserver.py b/src/ext/floodlight/src/main/python/debugserver.py
new file mode 100644
index 0000000..d8c81f9
--- /dev/null
+++ b/src/ext/floodlight/src/main/python/debugserver.py
@@ -0,0 +1,76 @@
+#!/usr/bin/env python
+
+import sys
+from threading import currentThread
+from SocketServer import BaseRequestHandler, TCPServer
+from code import InteractiveConsole
+
+_locals = None
+
+class DebugLogger(object):
+ def do_print(self, *args):
+ for i in args:
+ print i,
+ print
+ info = do_print
+ warn = do_print
+ debug = do_print
+_log = DebugLogger()
+
+
+class DebugConsole(InteractiveConsole):
+ def __init__(self, request):
+ self.request = request
+ InteractiveConsole.__init__(self, _locals)
+
+ def raw_input(self, prompt):
+ self.request.send(prompt)
+ data = self.request.recv(10000).rstrip()
+ if len(data) == 1 and ord(data[0]) == 4:
+ sys.exit()
+ return data
+
+ def write(self, data):
+ self.request.send(str(data))
+
+ def write_nl(self, data):
+ self.write(str(data)+"\r\n")
+
+class DebugServerHandler(BaseRequestHandler):
+ def __init__(self, request, client_address, server):
+ currentThread()._thread.setName("debugserver-%s:%d" % client_address)
+ _log.debug('Open connection to DebugServer from %s:%d' % client_address)
+ BaseRequestHandler.__init__(self, request, client_address, server)
+
+ def handle(self):
+ console = DebugConsole(self.request)
+ sys.displayhook = console.write_nl
+ console.interact('DebugServer')
+ self.request.close()
+
+class DebugServer(TCPServer):
+ daemon_threads = True
+ allow_reuse_address = True
+
+ def handle_error(self, request, client_address):
+ _log.debug('Closing connection to DebugServer from %s:%d' % client_address)
+ request.close()
+
+def run_server(port=6655, host='0.0.0.0', locals=locals()):
+ currentThread()._thread.setName("debugserver-main")
+
+ global _locals
+ _locals = locals
+ if "log" in locals.keys():
+ global _log
+ _log = locals["log"]
+
+ _log.info("Starting DebugServer on port %d" % port)
+ server = DebugServer(('', port), DebugServerHandler)
+ try:
+ server.serve_forever()
+ except KeyboardInterrupt:
+ pass
+
+if __name__ == "__main__":
+ run_server()
diff --git a/src/ext/floodlight/src/main/resources/META-INF/services/net.floodlightcontroller.core.module.IFloodlightModule b/src/ext/floodlight/src/main/resources/META-INF/services/net.floodlightcontroller.core.module.IFloodlightModule
new file mode 100644
index 0000000..0225a2b
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/META-INF/services/net.floodlightcontroller.core.module.IFloodlightModule
@@ -0,0 +1,25 @@
+net.floodlightcontroller.core.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.threadpool.ThreadPool
+net.floodlightcontroller.ui.web.StaticWebRoutable
+net.floodlightcontroller.virtualnetwork.VirtualNetworkFilter
+net.floodlightcontroller.devicemanager.internal.DefaultEntityClassifier
+net.floodlightcontroller.devicemanager.test.MockDeviceManager
+net.floodlightcontroller.core.test.MockFloodlightProvider
+net.floodlightcontroller.core.test.MockThreadPoolService
+net.floodlightcontroller.firewall.Firewall
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/resources/floodlightdefault.properties b/src/ext/floodlight/src/main/resources/floodlightdefault.properties
new file mode 100644
index 0000000..56d42eb
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/floodlightdefault.properties
@@ -0,0 +1,16 @@
+floodlight.modules = net.floodlightcontroller.storage.memory.MemoryStorageSource,\
+net.floodlightcontroller.core.FloodlightProvider,\
+net.floodlightcontroller.threadpool.ThreadPool,\
+net.floodlightcontroller.devicemanager.internal.DeviceManagerImpl,\
+net.floodlightcontroller.staticflowentry.StaticFlowEntryPusher,\
+net.floodlightcontroller.firewall.Firewall,\
+net.floodlightcontroller.forwarding.Forwarding,\
+net.floodlightcontroller.jython.JythonDebugInterface,\
+net.floodlightcontroller.counter.CounterStore,\
+net.floodlightcontroller.perfmon.PktInProcessingTime,\
+net.floodlightcontroller.ui.web.StaticWebRoutable
+net.floodlightcontroller.restserver.RestApiServer.port = 8080
+net.floodlightcontroller.core.FloodlightProvider.openflowport = 6633
+net.floodlightcontroller.jython.JythonDebugInterface.port = 6655
+net.floodlightcontroller.forwarding.Forwarding.idletimeout = 5
+net.floodlightcontroller.forwarding.Forwarding.hardtimeout = 0
diff --git a/src/ext/floodlight/src/main/resources/quantum.properties b/src/ext/floodlight/src/main/resources/quantum.properties
new file mode 100644
index 0000000..89c7543
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/quantum.properties
@@ -0,0 +1,15 @@
+# The default configuration for openstack
+floodlight.modules = net.floodlightcontroller.storage.memory.MemoryStorageSource,\
+net.floodlightcontroller.core.FloodlightProvider,\
+net.floodlightcontroller.devicemanager.internal.DeviceManagerImpl,\
+net.floodlightcontroller.staticflowentry.StaticFlowEntryPusher,\
+net.floodlightcontroller.forwarding.Forwarding,\
+net.floodlightcontroller.jython.JythonDebugInterface,\
+net.floodlightcontroller.counter.CounterStore,\
+net.floodlightcontroller.perfmon.PktInProcessingTime,\
+net.floodlightcontroller.ui.web.StaticWebRoutable,\
+net.floodlightcontroller.virtualnetwork.VirtualNetworkFilter,\
+net.floodlightcontroller.threadpool.ThreadPool
+net.floodlightcontroller.restserver.RestApiServer.port = 8080
+net.floodlightcontroller.core.FloodlightProvider.openflowport = 6633
+net.floodlightcontroller.jython.JythonDebugInterface.port = 6655
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/resources/web/css/bootstrap.css b/src/ext/floodlight/src/main/resources/web/css/bootstrap.css
new file mode 100644
index 0000000..98f31f3
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/css/bootstrap.css
@@ -0,0 +1,4000 @@
+/*!
+ * Bootstrap v2.0.2
+ *
+ * Copyright 2012 Twitter, Inc
+ * Licensed under the Apache License v2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Designed and built with all the love in the world @twitter by @mdo and @fat.
+ */
+article,
+aside,
+details,
+figcaption,
+figure,
+footer,
+header,
+hgroup,
+nav,
+section {
+ display: block;
+}
+audio,
+canvas,
+video {
+ display: inline-block;
+ *display: inline;
+ *zoom: 1;
+}
+audio:not([controls]) {
+ display: none;
+}
+html {
+ font-size: 100%;
+ -webkit-text-size-adjust: 100%;
+ -ms-text-size-adjust: 100%;
+}
+a:focus {
+ outline: thin dotted #333;
+ outline: 5px auto -webkit-focus-ring-color;
+ outline-offset: -2px;
+}
+a:hover,
+a:active {
+ outline: 0;
+}
+sub,
+sup {
+ position: relative;
+ font-size: 75%;
+ line-height: 0;
+ vertical-align: baseline;
+}
+sup {
+ top: -0.5em;
+}
+sub {
+ bottom: -0.25em;
+}
+img {
+ height: auto;
+ border: 0;
+ -ms-interpolation-mode: bicubic;
+ vertical-align: middle;
+}
+button,
+input,
+select,
+textarea {
+ margin: 0;
+ font-size: 100%;
+ vertical-align: middle;
+}
+button,
+input {
+ *overflow: visible;
+ line-height: normal;
+}
+button::-moz-focus-inner,
+input::-moz-focus-inner {
+ padding: 0;
+ border: 0;
+}
+button,
+input[type="button"],
+input[type="reset"],
+input[type="submit"] {
+ cursor: pointer;
+ -webkit-appearance: button;
+}
+input[type="search"] {
+ -webkit-appearance: textfield;
+ -webkit-box-sizing: content-box;
+ -moz-box-sizing: content-box;
+ box-sizing: content-box;
+}
+input[type="search"]::-webkit-search-decoration,
+input[type="search"]::-webkit-search-cancel-button {
+ -webkit-appearance: none;
+}
+textarea {
+ overflow: auto;
+ vertical-align: top;
+}
+.clearfix {
+ *zoom: 1;
+}
+.clearfix:before,
+.clearfix:after {
+ display: table;
+ content: "";
+}
+.clearfix:after {
+ clear: both;
+}
+.hide-text {
+ overflow: hidden;
+ text-indent: 100%;
+ white-space: nowrap;
+}
+.input-block-level {
+ display: block;
+ width: 100%;
+ min-height: 28px;
+ /* Make inputs at least the height of their button counterpart */
+
+ /* Makes inputs behave like true block-level elements */
+
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ -ms-box-sizing: border-box;
+ box-sizing: border-box;
+}
+body {
+ margin: 0;
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-size: 13px;
+ line-height: 18px;
+ color: #0c028; /* #333333 */
+ background-color: white; /* #ffffff; */
+}
+a {
+ color: #0088cc;
+ text-decoration: none;
+}
+a:hover {
+ color: #005580;
+ text-decoration: underline;
+}
+.row {
+ margin-left: -20px;
+ margin-top: 5px;
+ *zoom: 1;
+}
+.row:before,
+.row:after {
+ display: table;
+ content: "";
+}
+.row:after {
+ clear: both;
+}
+[class*="span"] {
+ float: left;
+ margin-left: 20px;
+}
+.container,
+.navbar-fixed-top .container,
+.navbar-fixed-bottom .container {
+ width: 940px;
+}
+.span12 {
+ width: 940px;
+}
+.span11 {
+ width: 860px;
+}
+.span10 {
+ width: 780px;
+}
+.span9 {
+ width: 700px;
+}
+.span8 {
+ width: 620px;
+}
+.span7 {
+ width: 540px;
+}
+.span6 {
+ width: 460px;
+}
+.span5 {
+ width: 380px;
+}
+.span4 {
+ width: 300px;
+}
+.span3 {
+ width: 220px;
+}
+.span2 {
+ width: 140px;
+}
+.span1 {
+ width: 60px;
+}
+.offset12 {
+ margin-left: 980px;
+}
+.offset11 {
+ margin-left: 900px;
+}
+.offset10 {
+ margin-left: 820px;
+}
+.offset9 {
+ margin-left: 740px;
+}
+.offset8 {
+ margin-left: 660px;
+}
+.offset7 {
+ margin-left: 580px;
+}
+.offset6 {
+ margin-left: 500px;
+}
+.offset5 {
+ margin-left: 420px;
+}
+.offset4 {
+ margin-left: 340px;
+}
+.offset3 {
+ margin-left: 260px;
+}
+.offset2 {
+ margin-left: 180px;
+}
+.offset1 {
+ margin-left: 100px;
+}
+.row-fluid {
+ width: 100%;
+ *zoom: 1;
+}
+.row-fluid:before,
+.row-fluid:after {
+ display: table;
+ content: "";
+}
+.row-fluid:after {
+ clear: both;
+}
+.row-fluid > [class*="span"] {
+ float: left;
+ margin-left: 2.127659574%;
+}
+.row-fluid > [class*="span"]:first-child {
+ margin-left: 0;
+}
+.row-fluid > .span12 {
+ width: 99.99999998999999%;
+}
+.row-fluid > .span11 {
+ width: 91.489361693%;
+}
+.row-fluid > .span10 {
+ width: 82.97872339599999%;
+}
+.row-fluid > .span9 {
+ width: 74.468085099%;
+}
+.row-fluid > .span8 {
+ width: 65.95744680199999%;
+}
+.row-fluid > .span7 {
+ width: 57.446808505%;
+}
+.row-fluid > .span6 {
+ width: 48.93617020799999%;
+}
+.row-fluid > .span5 {
+ width: 40.425531911%;
+}
+.row-fluid > .span4 {
+ width: 31.914893614%;
+}
+.row-fluid > .span3 {
+ width: 23.404255317%;
+}
+.row-fluid > .span2 {
+ width: 14.89361702%;
+}
+.row-fluid > .span1 {
+ width: 6.382978723%;
+}
+.container {
+ margin-left: auto;
+ margin-right: auto;
+ *zoom: 1;
+}
+.container:before,
+.container:after {
+ display: table;
+ content: "";
+}
+.container:after {
+ clear: both;
+}
+.container-fluid {
+ padding-left: 20px;
+ padding-right: 20px;
+ *zoom: 1;
+}
+.container-fluid:before,
+.container-fluid:after {
+ display: table;
+ content: "";
+}
+.container-fluid:after {
+ clear: both;
+}
+p {
+ margin: 0 0 9px;
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-size: 13px;
+ line-height: 18px;
+}
+p small {
+ font-size: 11px;
+ color: #999999;
+}
+.lead {
+ margin-bottom: 18px;
+ font-size: 20px;
+ font-weight: 200;
+ line-height: 27px;
+}
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+ margin: 0;
+ font-family: inherit;
+ font-weight: bold;
+ color: inherit;
+ text-rendering: optimizelegibility;
+}
+h1 small,
+h2 small,
+h3 small,
+h4 small,
+h5 small,
+h6 small {
+ font-weight: normal;
+ color: #999999;
+}
+h1 {
+ font-size: 23px;
+ line-height: 25px;
+}
+h1 small {
+ font-size: 18px;
+}
+h2 {
+ font-size: 18px;
+ line-height: 22px;
+}
+h2 small {
+ font-size: 18px;
+}
+h3 {
+ line-height: 18px;
+ font-size: 18px;
+}
+h3 small {
+ font-size: 14px;
+}
+h4,
+h5,
+h6 {
+ line-height: 16px;
+}
+h4 {
+ font-size: 14px;
+}
+h4 small {
+ font-size: 12px;
+}
+h5 {
+ font-size: 12px;
+}
+h6 {
+ font-size: 11px;
+ color: #999999;
+ text-transform: uppercase;
+}
+.page-header {
+ padding-bottom: 17px;
+ margin: 18px 0;
+ border-bottom: 1px solid #eeeeee;
+}
+.page-header h1 {
+ line-height: 1;
+}
+ul,
+ol {
+ padding: 0;
+ margin: 0 0 9px 25px;
+}
+ul ul,
+ul ol,
+ol ol,
+ol ul {
+ margin-bottom: 0;
+}
+ul {
+ list-style: disc;
+}
+ol {
+ list-style: decimal;
+}
+li {
+ line-height: 18px;
+}
+ul.unstyled,
+ol.unstyled {
+ margin-left: 0;
+ list-style: none;
+}
+dl {
+ margin-bottom: 18px;
+}
+dt,
+dd {
+ line-height: 18px;
+}
+dt {
+ font-weight: bold;
+ line-height: 17px;
+}
+dd {
+ margin-left: 9px;
+}
+.dl-horizontal dt {
+ float: left;
+ clear: left;
+ width: 120px;
+ text-align: right;
+}
+.dl-horizontal dd {
+ margin-left: 130px;
+}
+hr {
+ margin: 18px 0;
+ border: 0;
+ border-top: 1px solid #eeeeee;
+ border-bottom: 1px solid #ffffff;
+}
+strong {
+ font-weight: bold;
+}
+em {
+ font-style: italic;
+}
+.muted {
+ color: #999999;
+}
+abbr[title] {
+ border-bottom: 1px dotted #ddd;
+ cursor: help;
+}
+abbr.initialism {
+ font-size: 90%;
+ text-transform: uppercase;
+}
+blockquote {
+ padding: 0 0 0 15px;
+ margin: 0 0 18px;
+ border-left: 5px solid #eeeeee;
+}
+blockquote p {
+ margin-bottom: 0;
+ font-size: 16px;
+ font-weight: 300;
+ line-height: 22.5px;
+}
+blockquote small {
+ display: block;
+ line-height: 18px;
+ color: #999999;
+}
+blockquote small:before {
+ content: '\2014 \00A0';
+}
+blockquote.pull-right {
+ float: right;
+ padding-left: 0;
+ padding-right: 15px;
+ border-left: 0;
+ border-right: 5px solid #eeeeee;
+}
+blockquote.pull-right p,
+blockquote.pull-right small {
+ text-align: right;
+}
+q:before,
+q:after,
+blockquote:before,
+blockquote:after {
+ content: "";
+}
+address {
+ display: block;
+ margin-bottom: 18px;
+ line-height: 18px;
+ font-style: normal;
+}
+small {
+ font-size: 100%;
+}
+cite {
+ font-style: normal;
+}
+code,
+pre {
+ padding: 0 3px 2px;
+ font-family: Menlo, Monaco, "Courier New", monospace;
+ font-size: 12px;
+ color: #333333;
+ -webkit-border-radius: 3px;
+ -moz-border-radius: 3px;
+ border-radius: 3px;
+}
+code {
+ padding: 2px 4px;
+ color: #d14;
+ background-color: #f7f7f9;
+ border: 1px solid #e1e1e8;
+}
+pre {
+ display: block;
+ padding: 8.5px;
+ margin: 0 0 9px;
+ font-size: 12.025px;
+ line-height: 18px;
+ background-color: #f5f5f5;
+ border: 1px solid #ccc;
+ border: 1px solid rgba(0, 0, 0, 0.15);
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+ white-space: pre;
+ white-space: pre-wrap;
+ word-break: break-all;
+ word-wrap: break-word;
+}
+pre.prettyprint {
+ margin-bottom: 18px;
+}
+pre code {
+ padding: 0;
+ color: inherit;
+ background-color: transparent;
+ border: 0;
+}
+.pre-scrollable {
+ max-height: 340px;
+ overflow-y: scroll;
+}
+form {
+ margin: 0 0 18px;
+}
+fieldset {
+ padding: 0;
+ margin: 0;
+ border: 0;
+}
+legend {
+ display: block;
+ width: 100%;
+ padding: 0;
+ margin-bottom: 27px;
+ font-size: 19.5px;
+ line-height: 36px;
+ color: #333333;
+ border: 0;
+ border-bottom: 1px solid #eee;
+}
+legend small {
+ font-size: 13.5px;
+ color: #999999;
+}
+label,
+input,
+button,
+select,
+textarea {
+ font-size: 13px;
+ font-weight: normal;
+ line-height: 18px;
+}
+input,
+button,
+select,
+textarea {
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+}
+label {
+ display: block;
+ margin-bottom: 5px;
+ color: #333333;
+}
+input,
+textarea,
+select,
+.uneditable-input {
+ display: inline-block;
+ width: 210px;
+ height: 18px;
+ padding: 4px;
+ margin-bottom: 9px;
+ font-size: 13px;
+ line-height: 18px;
+ color: #555555;
+ border: 1px solid #cccccc;
+ -webkit-border-radius: 3px;
+ -moz-border-radius: 3px;
+ border-radius: 3px;
+}
+.uneditable-textarea {
+ width: auto;
+ height: auto;
+}
+label input,
+label textarea,
+label select {
+ display: block;
+}
+input[type="image"],
+input[type="checkbox"],
+input[type="radio"] {
+ width: auto;
+ height: auto;
+ padding: 0;
+ margin: 3px 0;
+ *margin-top: 0;
+ /* IE7 */
+
+ line-height: normal;
+ cursor: pointer;
+ -webkit-border-radius: 0;
+ -moz-border-radius: 0;
+ border-radius: 0;
+ border: 0 \9;
+ /* IE9 and down */
+
+}
+input[type="image"] {
+ border: 0;
+}
+input[type="file"] {
+ width: auto;
+ padding: initial;
+ line-height: initial;
+ border: initial;
+ background-color: #ffffff;
+ background-color: initial;
+ -webkit-box-shadow: none;
+ -moz-box-shadow: none;
+ box-shadow: none;
+}
+input[type="button"],
+input[type="reset"],
+input[type="submit"] {
+ width: auto;
+ height: auto;
+}
+select,
+input[type="file"] {
+ height: 28px;
+ /* In IE7, the height of the select element cannot be changed by height, only font-size */
+
+ *margin-top: 4px;
+ /* For IE7, add top margin to align select with labels */
+
+ line-height: 28px;
+}
+input[type="file"] {
+ line-height: 18px \9;
+}
+select {
+ width: 220px;
+ background-color: #ffffff;
+}
+select[multiple],
+select[size] {
+ height: auto;
+}
+input[type="image"] {
+ -webkit-box-shadow: none;
+ -moz-box-shadow: none;
+ box-shadow: none;
+}
+textarea {
+ height: auto;
+}
+input[type="hidden"] {
+ display: none;
+}
+.radio,
+.checkbox {
+ padding-left: 18px;
+}
+.radio input[type="radio"],
+.checkbox input[type="checkbox"] {
+ float: left;
+ margin-left: -18px;
+}
+.controls > .radio:first-child,
+.controls > .checkbox:first-child {
+ padding-top: 5px;
+}
+.radio.inline,
+.checkbox.inline {
+ display: inline-block;
+ padding-top: 5px;
+ margin-bottom: 0;
+ vertical-align: middle;
+}
+.radio.inline + .radio.inline,
+.checkbox.inline + .checkbox.inline {
+ margin-left: 10px;
+}
+input,
+textarea {
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+ -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+ -webkit-transition: border linear 0.2s, box-shadow linear 0.2s;
+ -moz-transition: border linear 0.2s, box-shadow linear 0.2s;
+ -ms-transition: border linear 0.2s, box-shadow linear 0.2s;
+ -o-transition: border linear 0.2s, box-shadow linear 0.2s;
+ transition: border linear 0.2s, box-shadow linear 0.2s;
+}
+input:focus,
+textarea:focus {
+ border-color: rgba(82, 168, 236, 0.8);
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
+ -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
+ outline: 0;
+ outline: thin dotted \9;
+ /* IE6-9 */
+
+}
+input[type="file"]:focus,
+input[type="radio"]:focus,
+input[type="checkbox"]:focus,
+select:focus {
+ -webkit-box-shadow: none;
+ -moz-box-shadow: none;
+ box-shadow: none;
+ outline: thin dotted #333;
+ outline: 5px auto -webkit-focus-ring-color;
+ outline-offset: -2px;
+}
+.input-mini {
+ width: 60px;
+}
+.input-small {
+ width: 90px;
+}
+.input-medium {
+ width: 150px;
+}
+.input-large {
+ width: 210px;
+}
+.input-xlarge {
+ width: 270px;
+}
+.input-xxlarge {
+ width: 530px;
+}
+input[class*="span"],
+select[class*="span"],
+textarea[class*="span"],
+.uneditable-input {
+ float: none;
+ margin-left: 0;
+}
+input,
+textarea,
+.uneditable-input {
+ margin-left: 0;
+}
+input.span12, textarea.span12, .uneditable-input.span12 {
+ width: 930px;
+}
+input.span11, textarea.span11, .uneditable-input.span11 {
+ width: 850px;
+}
+input.span10, textarea.span10, .uneditable-input.span10 {
+ width: 770px;
+}
+input.span9, textarea.span9, .uneditable-input.span9 {
+ width: 690px;
+}
+input.span8, textarea.span8, .uneditable-input.span8 {
+ width: 610px;
+}
+input.span7, textarea.span7, .uneditable-input.span7 {
+ width: 530px;
+}
+input.span6, textarea.span6, .uneditable-input.span6 {
+ width: 450px;
+}
+input.span5, textarea.span5, .uneditable-input.span5 {
+ width: 370px;
+}
+input.span4, textarea.span4, .uneditable-input.span4 {
+ width: 290px;
+}
+input.span3, textarea.span3, .uneditable-input.span3 {
+ width: 210px;
+}
+input.span2, textarea.span2, .uneditable-input.span2 {
+ width: 130px;
+}
+input.span1, textarea.span1, .uneditable-input.span1 {
+ width: 50px;
+}
+input[disabled],
+select[disabled],
+textarea[disabled],
+input[readonly],
+select[readonly],
+textarea[readonly] {
+ background-color: #eeeeee;
+ border-color: #ddd;
+ cursor: not-allowed;
+}
+.control-group.warning > label,
+.control-group.warning .help-block,
+.control-group.warning .help-inline {
+ color: #c09853;
+}
+.control-group.warning input,
+.control-group.warning select,
+.control-group.warning textarea {
+ color: #c09853;
+ border-color: #c09853;
+}
+.control-group.warning input:focus,
+.control-group.warning select:focus,
+.control-group.warning textarea:focus {
+ border-color: #a47e3c;
+ -webkit-box-shadow: 0 0 6px #dbc59e;
+ -moz-box-shadow: 0 0 6px #dbc59e;
+ box-shadow: 0 0 6px #dbc59e;
+}
+.control-group.warning .input-prepend .add-on,
+.control-group.warning .input-append .add-on {
+ color: #c09853;
+ background-color: #fcf8e3;
+ border-color: #c09853;
+}
+.control-group.error > label,
+.control-group.error .help-block,
+.control-group.error .help-inline {
+ color: #b94a48;
+}
+.control-group.error input,
+.control-group.error select,
+.control-group.error textarea {
+ color: #b94a48;
+ border-color: #b94a48;
+}
+.control-group.error input:focus,
+.control-group.error select:focus,
+.control-group.error textarea:focus {
+ border-color: #953b39;
+ -webkit-box-shadow: 0 0 6px #d59392;
+ -moz-box-shadow: 0 0 6px #d59392;
+ box-shadow: 0 0 6px #d59392;
+}
+.control-group.error .input-prepend .add-on,
+.control-group.error .input-append .add-on {
+ color: #b94a48;
+ background-color: #f2dede;
+ border-color: #b94a48;
+}
+.control-group.success > label,
+.control-group.success .help-block,
+.control-group.success .help-inline {
+ color: #468847;
+}
+.control-group.success input,
+.control-group.success select,
+.control-group.success textarea {
+ color: #468847;
+ border-color: #468847;
+}
+.control-group.success input:focus,
+.control-group.success select:focus,
+.control-group.success textarea:focus {
+ border-color: #356635;
+ -webkit-box-shadow: 0 0 6px #7aba7b;
+ -moz-box-shadow: 0 0 6px #7aba7b;
+ box-shadow: 0 0 6px #7aba7b;
+}
+.control-group.success .input-prepend .add-on,
+.control-group.success .input-append .add-on {
+ color: #468847;
+ background-color: #dff0d8;
+ border-color: #468847;
+}
+input:focus:required:invalid,
+textarea:focus:required:invalid,
+select:focus:required:invalid {
+ color: #b94a48;
+ border-color: #ee5f5b;
+}
+input:focus:required:invalid:focus,
+textarea:focus:required:invalid:focus,
+select:focus:required:invalid:focus {
+ border-color: #e9322d;
+ -webkit-box-shadow: 0 0 6px #f8b9b7;
+ -moz-box-shadow: 0 0 6px #f8b9b7;
+ box-shadow: 0 0 6px #f8b9b7;
+}
+.form-actions {
+ padding: 17px 20px 18px;
+ margin-top: 18px;
+ margin-bottom: 18px;
+ background-color: #eeeeee;
+ border-top: 1px solid #ddd;
+ *zoom: 1;
+}
+.form-actions:before,
+.form-actions:after {
+ display: table;
+ content: "";
+}
+.form-actions:after {
+ clear: both;
+}
+.uneditable-input {
+ display: block;
+ background-color: #ffffff;
+ border-color: #eee;
+ -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025);
+ -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025);
+ box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025);
+ cursor: not-allowed;
+}
+:-moz-placeholder {
+ color: #999999;
+}
+::-webkit-input-placeholder {
+ color: #999999;
+}
+.help-block,
+.help-inline {
+ color: #555555;
+}
+.help-block {
+ display: block;
+ margin-bottom: 9px;
+}
+.help-inline {
+ display: inline-block;
+ *display: inline;
+ /* IE7 inline-block hack */
+
+ *zoom: 1;
+ vertical-align: middle;
+ padding-left: 5px;
+}
+.input-prepend,
+.input-append {
+ margin-bottom: 5px;
+}
+.input-prepend input,
+.input-append input,
+.input-prepend select,
+.input-append select,
+.input-prepend .uneditable-input,
+.input-append .uneditable-input {
+ *margin-left: 0;
+ -webkit-border-radius: 0 3px 3px 0;
+ -moz-border-radius: 0 3px 3px 0;
+ border-radius: 0 3px 3px 0;
+}
+.input-prepend input:focus,
+.input-append input:focus,
+.input-prepend select:focus,
+.input-append select:focus,
+.input-prepend .uneditable-input:focus,
+.input-append .uneditable-input:focus {
+ position: relative;
+ z-index: 2;
+}
+.input-prepend .uneditable-input,
+.input-append .uneditable-input {
+ border-left-color: #ccc;
+}
+.input-prepend .add-on,
+.input-append .add-on {
+ display: inline-block;
+ width: auto;
+ min-width: 16px;
+ height: 18px;
+ padding: 4px 5px;
+ font-weight: normal;
+ line-height: 18px;
+ text-align: center;
+ text-shadow: 0 1px 0 #ffffff;
+ vertical-align: middle;
+ background-color: #eeeeee;
+ border: 1px solid #ccc;
+}
+.input-prepend .add-on,
+.input-append .add-on,
+.input-prepend .btn,
+.input-append .btn {
+ -webkit-border-radius: 3px 0 0 3px;
+ -moz-border-radius: 3px 0 0 3px;
+ border-radius: 3px 0 0 3px;
+}
+.input-prepend .active,
+.input-append .active {
+ background-color: #a9dba9;
+ border-color: #46a546;
+}
+.input-prepend .add-on,
+.input-prepend .btn {
+ margin-right: -1px;
+}
+.input-append input,
+.input-append select .uneditable-input {
+ -webkit-border-radius: 3px 0 0 3px;
+ -moz-border-radius: 3px 0 0 3px;
+ border-radius: 3px 0 0 3px;
+}
+.input-append .uneditable-input {
+ border-left-color: #eee;
+ border-right-color: #ccc;
+}
+.input-append .add-on,
+.input-append .btn {
+ margin-left: -1px;
+ -webkit-border-radius: 0 3px 3px 0;
+ -moz-border-radius: 0 3px 3px 0;
+ border-radius: 0 3px 3px 0;
+}
+.input-prepend.input-append input,
+.input-prepend.input-append select,
+.input-prepend.input-append .uneditable-input {
+ -webkit-border-radius: 0;
+ -moz-border-radius: 0;
+ border-radius: 0;
+}
+.input-prepend.input-append .add-on:first-child,
+.input-prepend.input-append .btn:first-child {
+ margin-right: -1px;
+ -webkit-border-radius: 3px 0 0 3px;
+ -moz-border-radius: 3px 0 0 3px;
+ border-radius: 3px 0 0 3px;
+}
+.input-prepend.input-append .add-on:last-child,
+.input-prepend.input-append .btn:last-child {
+ margin-left: -1px;
+ -webkit-border-radius: 0 3px 3px 0;
+ -moz-border-radius: 0 3px 3px 0;
+ border-radius: 0 3px 3px 0;
+}
+.search-query {
+ padding-left: 14px;
+ padding-right: 14px;
+ margin-bottom: 0;
+ -webkit-border-radius: 14px;
+ -moz-border-radius: 14px;
+ border-radius: 14px;
+}
+.form-search input,
+.form-inline input,
+.form-horizontal input,
+.form-search textarea,
+.form-inline textarea,
+.form-horizontal textarea,
+.form-search select,
+.form-inline select,
+.form-horizontal select,
+.form-search .help-inline,
+.form-inline .help-inline,
+.form-horizontal .help-inline,
+.form-search .uneditable-input,
+.form-inline .uneditable-input,
+.form-horizontal .uneditable-input,
+.form-search .input-prepend,
+.form-inline .input-prepend,
+.form-horizontal .input-prepend,
+.form-search .input-append,
+.form-inline .input-append,
+.form-horizontal .input-append {
+ display: inline-block;
+ margin-bottom: 0;
+}
+.form-search .hide,
+.form-inline .hide,
+.form-horizontal .hide {
+ display: none;
+}
+.form-search label,
+.form-inline label {
+ display: inline-block;
+}
+.form-search .input-append,
+.form-inline .input-append,
+.form-search .input-prepend,
+.form-inline .input-prepend {
+ margin-bottom: 0;
+}
+.form-search .radio,
+.form-search .checkbox,
+.form-inline .radio,
+.form-inline .checkbox {
+ padding-left: 0;
+ margin-bottom: 0;
+ vertical-align: middle;
+}
+.form-search .radio input[type="radio"],
+.form-search .checkbox input[type="checkbox"],
+.form-inline .radio input[type="radio"],
+.form-inline .checkbox input[type="checkbox"] {
+ float: left;
+ margin-left: 0;
+ margin-right: 3px;
+}
+.control-group {
+ margin-bottom: 9px;
+}
+legend + .control-group {
+ margin-top: 18px;
+ -webkit-margin-top-collapse: separate;
+}
+.form-horizontal .control-group {
+ margin-bottom: 18px;
+ *zoom: 1;
+}
+.form-horizontal .control-group:before,
+.form-horizontal .control-group:after {
+ display: table;
+ content: "";
+}
+.form-horizontal .control-group:after {
+ clear: both;
+}
+.form-horizontal .control-label {
+ float: left;
+ width: 140px;
+ padding-top: 5px;
+ text-align: right;
+}
+.form-horizontal .controls {
+ margin-left: 160px;
+ /* Super jank IE7 fix to ensure the inputs in .input-append and input-prepend don't inherit the margin of the parent, in this case .controls */
+
+ *display: inline-block;
+ *margin-left: 0;
+ *padding-left: 20px;
+}
+.form-horizontal .help-block {
+ margin-top: 9px;
+ margin-bottom: 0;
+}
+.form-horizontal .form-actions {
+ padding-left: 160px;
+}
+table {
+ max-width: 100%;
+ border-collapse: collapse;
+ border-spacing: 0;
+ background-color: transparent;
+}
+.table {
+ width: 100%;
+ margin-bottom: 18px;
+}
+.table th,
+.table td {
+ padding: 8px;
+ line-height: 18px;
+ text-align: left;
+ vertical-align: top;
+ border-top: 1px solid #dddddd;
+}
+.table th {
+ font-weight: bold;
+}
+.table thead th {
+ vertical-align: bottom;
+}
+.table colgroup + thead tr:first-child th,
+.table colgroup + thead tr:first-child td,
+.table thead:first-child tr:first-child th,
+.table thead:first-child tr:first-child td {
+ border-top: 0;
+}
+.table tbody + tbody {
+ border-top: 2px solid #dddddd;
+}
+.table-condensed th,
+.table-condensed td {
+ padding: 4px 5px;
+}
+.table-bordered {
+ border: 1px solid #dddddd;
+ border-left: 0;
+ border-collapse: separate;
+ *border-collapse: collapsed;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+}
+.table-bordered th,
+.table-bordered td {
+ border-left: 1px solid #dddddd;
+}
+.table-bordered thead:first-child tr:first-child th,
+.table-bordered tbody:first-child tr:first-child th,
+.table-bordered tbody:first-child tr:first-child td {
+ border-top: 0;
+}
+.table-bordered thead:first-child tr:first-child th:first-child,
+.table-bordered tbody:first-child tr:first-child td:first-child {
+ -webkit-border-radius: 4px 0 0 0;
+ -moz-border-radius: 4px 0 0 0;
+ border-radius: 4px 0 0 0;
+}
+.table-bordered thead:first-child tr:first-child th:last-child,
+.table-bordered tbody:first-child tr:first-child td:last-child {
+ -webkit-border-radius: 0 4px 0 0;
+ -moz-border-radius: 0 4px 0 0;
+ border-radius: 0 4px 0 0;
+}
+.table-bordered thead:last-child tr:last-child th:first-child,
+.table-bordered tbody:last-child tr:last-child td:first-child {
+ -webkit-border-radius: 0 0 0 4px;
+ -moz-border-radius: 0 0 0 4px;
+ border-radius: 0 0 0 4px;
+}
+.table-bordered thead:last-child tr:last-child th:last-child,
+.table-bordered tbody:last-child tr:last-child td:last-child {
+ -webkit-border-radius: 0 0 4px 0;
+ -moz-border-radius: 0 0 4px 0;
+ border-radius: 0 0 4px 0;
+}
+.table-striped tbody tr:nth-child(odd) td,
+.table-striped tbody tr:nth-child(odd) th {
+ background-color: #f9f9f9;
+}
+.table tbody tr:hover td,
+.table tbody tr:hover th {
+ background-color: #f5f5f5;
+}
+table .span1 {
+ float: none;
+ width: 44px;
+ margin-left: 0;
+}
+table .span2 {
+ float: none;
+ width: 124px;
+ margin-left: 0;
+}
+table .span3 {
+ float: none;
+ width: 204px;
+ margin-left: 0;
+}
+table .span4 {
+ float: none;
+ width: 284px;
+ margin-left: 0;
+}
+table .span5 {
+ float: none;
+ width: 364px;
+ margin-left: 0;
+}
+table .span6 {
+ float: none;
+ width: 444px;
+ margin-left: 0;
+}
+table .span7 {
+ float: none;
+ width: 524px;
+ margin-left: 0;
+}
+table .span8 {
+ float: none;
+ width: 604px;
+ margin-left: 0;
+}
+table .span9 {
+ float: none;
+ width: 684px;
+ margin-left: 0;
+}
+table .span10 {
+ float: none;
+ width: 764px;
+ margin-left: 0;
+}
+table .span11 {
+ float: none;
+ width: 844px;
+ margin-left: 0;
+}
+table .span12 {
+ float: none;
+ width: 924px;
+ margin-left: 0;
+}
+table .span13 {
+ float: none;
+ width: 1004px;
+ margin-left: 0;
+}
+table .span14 {
+ float: none;
+ width: 1084px;
+ margin-left: 0;
+}
+table .span15 {
+ float: none;
+ width: 1164px;
+ margin-left: 0;
+}
+table .span16 {
+ float: none;
+ width: 1244px;
+ margin-left: 0;
+}
+table .span17 {
+ float: none;
+ width: 1324px;
+ margin-left: 0;
+}
+table .span18 {
+ float: none;
+ width: 1404px;
+ margin-left: 0;
+}
+table .span19 {
+ float: none;
+ width: 1484px;
+ margin-left: 0;
+}
+table .span20 {
+ float: none;
+ width: 1564px;
+ margin-left: 0;
+}
+table .span21 {
+ float: none;
+ width: 1644px;
+ margin-left: 0;
+}
+table .span22 {
+ float: none;
+ width: 1724px;
+ margin-left: 0;
+}
+table .span23 {
+ float: none;
+ width: 1804px;
+ margin-left: 0;
+}
+table .span24 {
+ float: none;
+ width: 1884px;
+ margin-left: 0;
+}
+[class^="icon-"],
+[class*=" icon-"] {
+ display: inline-block;
+ width: 14px;
+ height: 14px;
+ line-height: 14px;
+ vertical-align: text-top;
+ background-image: url("../img/glyphicons-halflings.png");
+ background-position: 14px 14px;
+ background-repeat: no-repeat;
+ *margin-right: .3em;
+}
+[class^="icon-"]:last-child,
+[class*=" icon-"]:last-child {
+ *margin-left: 0;
+}
+.icon-white {
+ background-image: url("../img/glyphicons-halflings-white.png");
+}
+.icon-glass {
+ background-position: 0 0;
+}
+.icon-music {
+ background-position: -24px 0;
+}
+.icon-search {
+ background-position: -48px 0;
+}
+.icon-envelope {
+ background-position: -72px 0;
+}
+.icon-heart {
+ background-position: -96px 0;
+}
+.icon-star {
+ background-position: -120px 0;
+}
+.icon-star-empty {
+ background-position: -144px 0;
+}
+.icon-user {
+ background-position: -168px 0;
+}
+.icon-film {
+ background-position: -192px 0;
+}
+.icon-th-large {
+ background-position: -216px 0;
+}
+.icon-th {
+ background-position: -240px 0;
+}
+.icon-th-list {
+ background-position: -264px 0;
+}
+.icon-ok {
+ background-position: -288px 0;
+}
+.icon-remove {
+ background-position: -312px 0;
+}
+.icon-zoom-in {
+ background-position: -336px 0;
+}
+.icon-zoom-out {
+ background-position: -360px 0;
+}
+.icon-off {
+ background-position: -384px 0;
+}
+.icon-signal {
+ background-position: -408px 0;
+}
+.icon-cog {
+ background-position: -432px 0;
+}
+.icon-trash {
+ background-position: -456px 0;
+}
+.icon-home {
+ background-position: 0 -24px;
+}
+.icon-file {
+ background-position: -24px -24px;
+}
+.icon-time {
+ background-position: -48px -24px;
+}
+.icon-road {
+ background-position: -72px -24px;
+}
+.icon-download-alt {
+ background-position: -96px -24px;
+}
+.icon-download {
+ background-position: -120px -24px;
+}
+.icon-upload {
+ background-position: -144px -24px;
+}
+.icon-inbox {
+ background-position: -168px -24px;
+}
+.icon-play-circle {
+ background-position: -192px -24px;
+}
+.icon-repeat {
+ background-position: -216px -24px;
+}
+.icon-refresh {
+ background-position: -240px -24px;
+}
+.icon-list-alt {
+ background-position: -264px -24px;
+}
+.icon-lock {
+ background-position: -287px -24px;
+}
+.icon-flag {
+ background-position: -312px -24px;
+}
+.icon-headphones {
+ background-position: -336px -24px;
+}
+.icon-volume-off {
+ background-position: -360px -24px;
+}
+.icon-volume-down {
+ background-position: -384px -24px;
+}
+.icon-volume-up {
+ background-position: -408px -24px;
+}
+.icon-qrcode {
+ background-position: -432px -24px;
+}
+.icon-barcode {
+ background-position: -456px -24px;
+}
+.icon-tag {
+ background-position: 0 -48px;
+}
+.icon-tags {
+ background-position: -25px -48px;
+}
+.icon-book {
+ background-position: -48px -48px;
+}
+.icon-bookmark {
+ background-position: -72px -48px;
+}
+.icon-print {
+ background-position: -96px -48px;
+}
+.icon-camera {
+ background-position: -120px -48px;
+}
+.icon-font {
+ background-position: -144px -48px;
+}
+.icon-bold {
+ background-position: -167px -48px;
+}
+.icon-italic {
+ background-position: -192px -48px;
+}
+.icon-text-height {
+ background-position: -216px -48px;
+}
+.icon-text-width {
+ background-position: -240px -48px;
+}
+.icon-align-left {
+ background-position: -264px -48px;
+}
+.icon-align-center {
+ background-position: -288px -48px;
+}
+.icon-align-right {
+ background-position: -312px -48px;
+}
+.icon-align-justify {
+ background-position: -336px -48px;
+}
+.icon-list {
+ background-position: -360px -48px;
+}
+.icon-indent-left {
+ background-position: -384px -48px;
+}
+.icon-indent-right {
+ background-position: -408px -48px;
+}
+.icon-facetime-video {
+ background-position: -432px -48px;
+}
+.icon-picture {
+ background-position: -456px -48px;
+}
+.icon-pencil {
+ background-position: 0 -72px;
+}
+.icon-map-marker {
+ background-position: -24px -72px;
+}
+.icon-adjust {
+ background-position: -48px -72px;
+}
+.icon-tint {
+ background-position: -72px -72px;
+}
+.icon-edit {
+ background-position: -96px -72px;
+}
+.icon-share {
+ background-position: -120px -72px;
+}
+.icon-check {
+ background-position: -144px -72px;
+}
+.icon-move {
+ background-position: -168px -72px;
+}
+.icon-step-backward {
+ background-position: -192px -72px;
+}
+.icon-fast-backward {
+ background-position: -216px -72px;
+}
+.icon-backward {
+ background-position: -240px -72px;
+}
+.icon-play {
+ background-position: -264px -72px;
+}
+.icon-pause {
+ background-position: -288px -72px;
+}
+.icon-stop {
+ background-position: -312px -72px;
+}
+.icon-forward {
+ background-position: -336px -72px;
+}
+.icon-fast-forward {
+ background-position: -360px -72px;
+}
+.icon-step-forward {
+ background-position: -384px -72px;
+}
+.icon-eject {
+ background-position: -408px -72px;
+}
+.icon-chevron-left {
+ background-position: -432px -72px;
+}
+.icon-chevron-right {
+ background-position: -456px -72px;
+}
+.icon-plus-sign {
+ background-position: 0 -96px;
+}
+.icon-minus-sign {
+ background-position: -24px -96px;
+}
+.icon-remove-sign {
+ background-position: -48px -96px;
+}
+.icon-ok-sign {
+ background-position: -72px -96px;
+}
+.icon-question-sign {
+ background-position: -96px -96px;
+}
+.icon-info-sign {
+ background-position: -120px -96px;
+}
+.icon-screenshot {
+ background-position: -144px -96px;
+}
+.icon-remove-circle {
+ background-position: -168px -96px;
+}
+.icon-ok-circle {
+ background-position: -192px -96px;
+}
+.icon-ban-circle {
+ background-position: -216px -96px;
+}
+.icon-arrow-left {
+ background-position: -240px -96px;
+}
+.icon-arrow-right {
+ background-position: -264px -96px;
+}
+.icon-arrow-up {
+ background-position: -289px -96px;
+}
+.icon-arrow-down {
+ background-position: -312px -96px;
+}
+.icon-share-alt {
+ background-position: -336px -96px;
+}
+.icon-resize-full {
+ background-position: -360px -96px;
+}
+.icon-resize-small {
+ background-position: -384px -96px;
+}
+.icon-plus {
+ background-position: -408px -96px;
+}
+.icon-minus {
+ background-position: -433px -96px;
+}
+.icon-asterisk {
+ background-position: -456px -96px;
+}
+.icon-exclamation-sign {
+ background-position: 0 -120px;
+}
+.icon-gift {
+ background-position: -24px -120px;
+}
+.icon-leaf {
+ background-position: -48px -120px;
+}
+.icon-fire {
+ background-position: -72px -120px;
+}
+.icon-eye-open {
+ background-position: -96px -120px;
+}
+.icon-eye-close {
+ background-position: -120px -120px;
+}
+.icon-warning-sign {
+ background-position: -144px -120px;
+}
+.icon-plane {
+ background-position: -168px -120px;
+}
+.icon-calendar {
+ background-position: -192px -120px;
+}
+.icon-random {
+ background-position: -216px -120px;
+}
+.icon-comment {
+ background-position: -240px -120px;
+}
+.icon-magnet {
+ background-position: -264px -120px;
+}
+.icon-chevron-up {
+ background-position: -288px -120px;
+}
+.icon-chevron-down {
+ background-position: -313px -119px;
+}
+.icon-retweet {
+ background-position: -336px -120px;
+}
+.icon-shopping-cart {
+ background-position: -360px -120px;
+}
+.icon-folder-close {
+ background-position: -384px -120px;
+}
+.icon-folder-open {
+ background-position: -408px -120px;
+}
+.icon-resize-vertical {
+ background-position: -432px -119px;
+}
+.icon-resize-horizontal {
+ background-position: -456px -118px;
+}
+.dropdown {
+ position: relative;
+}
+.dropdown-toggle {
+ *margin-bottom: -3px;
+}
+.dropdown-toggle:active,
+.open .dropdown-toggle {
+ outline: 0;
+}
+.caret {
+ display: inline-block;
+ width: 0;
+ height: 0;
+ vertical-align: top;
+ border-left: 4px solid transparent;
+ border-right: 4px solid transparent;
+ border-top: 4px solid #000000;
+ opacity: 0.3;
+ filter: alpha(opacity=30);
+ content: "";
+}
+.dropdown .caret {
+ margin-top: 8px;
+ margin-left: 2px;
+}
+.dropdown:hover .caret,
+.open.dropdown .caret {
+ opacity: 1;
+ filter: alpha(opacity=100);
+}
+.dropdown-menu {
+ position: absolute;
+ top: 100%;
+ left: 0;
+ z-index: 1000;
+ float: left;
+ display: none;
+ min-width: 160px;
+ padding: 4px 0;
+ margin: 0;
+ list-style: none;
+ background-color: #ffffff;
+ border-color: #ccc;
+ border-color: rgba(0, 0, 0, 0.2);
+ border-style: solid;
+ border-width: 1px;
+ -webkit-border-radius: 0 0 5px 5px;
+ -moz-border-radius: 0 0 5px 5px;
+ border-radius: 0 0 5px 5px;
+ -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+ -moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+ box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+ -webkit-background-clip: padding-box;
+ -moz-background-clip: padding;
+ background-clip: padding-box;
+ *border-right-width: 2px;
+ *border-bottom-width: 2px;
+}
+.dropdown-menu.pull-right {
+ right: 0;
+ left: auto;
+}
+.dropdown-menu .divider {
+ height: 1px;
+ margin: 8px 1px;
+ overflow: hidden;
+ background-color: #e5e5e5;
+ border-bottom: 1px solid #ffffff;
+ *width: 100%;
+ *margin: -5px 0 5px;
+}
+.dropdown-menu a {
+ display: block;
+ padding: 3px 15px;
+ clear: both;
+ font-weight: normal;
+ line-height: 18px;
+ color: #333333;
+ white-space: nowrap;
+}
+.dropdown-menu li > a:hover,
+.dropdown-menu .active > a,
+.dropdown-menu .active > a:hover {
+ color: #ffffff;
+ text-decoration: none;
+ background-color: #0088cc;
+}
+.dropdown.open {
+ *z-index: 1000;
+}
+.dropdown.open .dropdown-toggle {
+ color: #ffffff;
+ background: #ccc;
+ background: rgba(0, 0, 0, 0.3);
+}
+.dropdown.open .dropdown-menu {
+ display: block;
+}
+.pull-right .dropdown-menu {
+ left: auto;
+ right: 0;
+}
+.dropup .caret,
+.navbar-fixed-bottom .dropdown .caret {
+ border-top: 0;
+ border-bottom: 4px solid #000000;
+ content: "\2191";
+}
+.dropup .dropdown-menu,
+.navbar-fixed-bottom .dropdown .dropdown-menu {
+ top: auto;
+ bottom: 100%;
+ margin-bottom: 1px;
+}
+.typeahead {
+ margin-top: 2px;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+}
+.well {
+ min-height: 20px;
+ padding: 19px;
+ margin-bottom: 20px;
+ background-color: #f5f5f5;
+ border: 1px solid #eee;
+ border: 1px solid rgba(0, 0, 0, 0.05);
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
+ -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
+}
+.well blockquote {
+ border-color: #ddd;
+ border-color: rgba(0, 0, 0, 0.15);
+}
+.well-large {
+ padding: 24px;
+ -webkit-border-radius: 6px;
+ -moz-border-radius: 6px;
+ border-radius: 6px;
+}
+.well-small {
+ padding: 9px;
+ -webkit-border-radius: 3px;
+ -moz-border-radius: 3px;
+ border-radius: 3px;
+}
+.fade {
+ -webkit-transition: opacity 0.15s linear;
+ -moz-transition: opacity 0.15s linear;
+ -ms-transition: opacity 0.15s linear;
+ -o-transition: opacity 0.15s linear;
+ transition: opacity 0.15s linear;
+ opacity: 0;
+}
+.fade.in {
+ opacity: 1;
+}
+.collapse {
+ -webkit-transition: height 0.35s ease;
+ -moz-transition: height 0.35s ease;
+ -ms-transition: height 0.35s ease;
+ -o-transition: height 0.35s ease;
+ transition: height 0.35s ease;
+ position: relative;
+ overflow: hidden;
+ height: 0;
+}
+.collapse.in {
+ height: auto;
+}
+.close {
+ float: right;
+ font-size: 20px;
+ font-weight: bold;
+ line-height: 18px;
+ color: #000000;
+ text-shadow: 0 1px 0 #ffffff;
+ opacity: 0.2;
+ filter: alpha(opacity=20);
+}
+.close:hover {
+ color: #000000;
+ text-decoration: none;
+ opacity: 0.4;
+ filter: alpha(opacity=40);
+ cursor: pointer;
+}
+.btn {
+ display: inline-block;
+ *display: inline;
+ /* IE7 inline-block hack */
+
+ *zoom: 1;
+ padding: 4px 10px 4px;
+ margin-bottom: 0;
+ font-size: 13px;
+ line-height: 18px;
+ color: #333333;
+ text-align: center;
+ text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75);
+ vertical-align: middle;
+ background-color: #f5f5f5;
+ background-image: -moz-linear-gradient(top, #ffffff, #e6e6e6);
+ background-image: -ms-linear-gradient(top, #ffffff, #e6e6e6);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#e6e6e6));
+ background-image: -webkit-linear-gradient(top, #ffffff, #e6e6e6);
+ background-image: -o-linear-gradient(top, #ffffff, #e6e6e6);
+ background-image: linear-gradient(top, #ffffff, #e6e6e6);
+ background-repeat: repeat-x;
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#e6e6e6', GradientType=0);
+ border-color: #e6e6e6 #e6e6e6 #bfbfbf;
+ border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+ filter: progid:dximagetransform.microsoft.gradient(enabled=false);
+ border: 1px solid #cccccc;
+ border-bottom-color: #b3b3b3;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+ -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+ -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+ cursor: pointer;
+ *margin-left: .3em;
+}
+.btn:hover,
+.btn:active,
+.btn.active,
+.btn.disabled,
+.btn[disabled] {
+ background-color: #e6e6e6;
+}
+.btn:active,
+.btn.active {
+ background-color: #cccccc \9;
+}
+.btn:first-child {
+ *margin-left: 0;
+}
+.btn:hover {
+ color: #333333;
+ text-decoration: none;
+ background-color: #e6e6e6;
+ background-position: 0 -15px;
+ -webkit-transition: background-position 0.1s linear;
+ -moz-transition: background-position 0.1s linear;
+ -ms-transition: background-position 0.1s linear;
+ -o-transition: background-position 0.1s linear;
+ transition: background-position 0.1s linear;
+}
+.btn:focus {
+ outline: thin dotted #333;
+ outline: 5px auto -webkit-focus-ring-color;
+ outline-offset: -2px;
+}
+.btn.active,
+.btn:active {
+ background-image: none;
+ -webkit-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
+ -moz-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
+ box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
+ background-color: #e6e6e6;
+ background-color: #d9d9d9 \9;
+ outline: 0;
+}
+.btn.disabled,
+.btn[disabled] {
+ cursor: default;
+ background-image: none;
+ background-color: #e6e6e6;
+ opacity: 0.65;
+ filter: alpha(opacity=65);
+ -webkit-box-shadow: none;
+ -moz-box-shadow: none;
+ box-shadow: none;
+}
+.btn-large {
+ padding: 9px 14px;
+ font-size: 15px;
+ line-height: normal;
+ -webkit-border-radius: 5px;
+ -moz-border-radius: 5px;
+ border-radius: 5px;
+}
+.btn-large [class^="icon-"] {
+ margin-top: 1px;
+}
+.btn-small {
+ padding: 5px 9px;
+ font-size: 11px;
+ line-height: 16px;
+}
+.btn-small [class^="icon-"] {
+ margin-top: -1px;
+}
+.btn-mini {
+ padding: 2px 6px;
+ font-size: 11px;
+ line-height: 14px;
+}
+.btn-primary,
+.btn-primary:hover,
+.btn-warning,
+.btn-warning:hover,
+.btn-danger,
+.btn-danger:hover,
+.btn-success,
+.btn-success:hover,
+.btn-info,
+.btn-info:hover,
+.btn-inverse,
+.btn-inverse:hover {
+ text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+ color: #ffffff;
+}
+.btn-primary.active,
+.btn-warning.active,
+.btn-danger.active,
+.btn-success.active,
+.btn-info.active,
+.btn-inverse.active {
+ color: rgba(255, 255, 255, 0.75);
+}
+.btn-primary {
+ background-color: #0074cc;
+ background-image: -moz-linear-gradient(top, #0088cc, #0055cc);
+ background-image: -ms-linear-gradient(top, #0088cc, #0055cc);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0055cc));
+ background-image: -webkit-linear-gradient(top, #0088cc, #0055cc);
+ background-image: -o-linear-gradient(top, #0088cc, #0055cc);
+ background-image: linear-gradient(top, #0088cc, #0055cc);
+ background-repeat: repeat-x;
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0055cc', GradientType=0);
+ border-color: #0055cc #0055cc #003580;
+ border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+ filter: progid:dximagetransform.microsoft.gradient(enabled=false);
+}
+.btn-primary:hover,
+.btn-primary:active,
+.btn-primary.active,
+.btn-primary.disabled,
+.btn-primary[disabled] {
+ background-color: #0055cc;
+}
+.btn-primary:active,
+.btn-primary.active {
+ background-color: #004099 \9;
+}
+.btn-warning {
+ background-color: #faa732;
+ background-image: -moz-linear-gradient(top, #fbb450, #f89406);
+ background-image: -ms-linear-gradient(top, #fbb450, #f89406);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fbb450), to(#f89406));
+ background-image: -webkit-linear-gradient(top, #fbb450, #f89406);
+ background-image: -o-linear-gradient(top, #fbb450, #f89406);
+ background-image: linear-gradient(top, #fbb450, #f89406);
+ background-repeat: repeat-x;
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fbb450', endColorstr='#f89406', GradientType=0);
+ border-color: #f89406 #f89406 #ad6704;
+ border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+ filter: progid:dximagetransform.microsoft.gradient(enabled=false);
+}
+.btn-warning:hover,
+.btn-warning:active,
+.btn-warning.active,
+.btn-warning.disabled,
+.btn-warning[disabled] {
+ background-color: #f89406;
+}
+.btn-warning:active,
+.btn-warning.active {
+ background-color: #c67605 \9;
+}
+.btn-danger {
+ background-color: #da4f49;
+ background-image: -moz-linear-gradient(top, #ee5f5b, #bd362f);
+ background-image: -ms-linear-gradient(top, #ee5f5b, #bd362f);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ee5f5b), to(#bd362f));
+ background-image: -webkit-linear-gradient(top, #ee5f5b, #bd362f);
+ background-image: -o-linear-gradient(top, #ee5f5b, #bd362f);
+ background-image: linear-gradient(top, #ee5f5b, #bd362f);
+ background-repeat: repeat-x;
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ee5f5b', endColorstr='#bd362f', GradientType=0);
+ border-color: #bd362f #bd362f #802420;
+ border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+ filter: progid:dximagetransform.microsoft.gradient(enabled=false);
+}
+.btn-danger:hover,
+.btn-danger:active,
+.btn-danger.active,
+.btn-danger.disabled,
+.btn-danger[disabled] {
+ background-color: #bd362f;
+}
+.btn-danger:active,
+.btn-danger.active {
+ background-color: #942a25 \9;
+}
+.btn-success {
+ background-color: #5bb75b;
+ background-image: -moz-linear-gradient(top, #62c462, #51a351);
+ background-image: -ms-linear-gradient(top, #62c462, #51a351);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#62c462), to(#51a351));
+ background-image: -webkit-linear-gradient(top, #62c462, #51a351);
+ background-image: -o-linear-gradient(top, #62c462, #51a351);
+ background-image: linear-gradient(top, #62c462, #51a351);
+ background-repeat: repeat-x;
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#62c462', endColorstr='#51a351', GradientType=0);
+ border-color: #51a351 #51a351 #387038;
+ border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+ filter: progid:dximagetransform.microsoft.gradient(enabled=false);
+}
+.btn-success:hover,
+.btn-success:active,
+.btn-success.active,
+.btn-success.disabled,
+.btn-success[disabled] {
+ background-color: #51a351;
+}
+.btn-success:active,
+.btn-success.active {
+ background-color: #408140 \9;
+}
+.btn-info {
+ background-color: #49afcd;
+ background-image: -moz-linear-gradient(top, #5bc0de, #2f96b4);
+ background-image: -ms-linear-gradient(top, #5bc0de, #2f96b4);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#5bc0de), to(#2f96b4));
+ background-image: -webkit-linear-gradient(top, #5bc0de, #2f96b4);
+ background-image: -o-linear-gradient(top, #5bc0de, #2f96b4);
+ background-image: linear-gradient(top, #5bc0de, #2f96b4);
+ background-repeat: repeat-x;
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#5bc0de', endColorstr='#2f96b4', GradientType=0);
+ border-color: #2f96b4 #2f96b4 #1f6377;
+ border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+ filter: progid:dximagetransform.microsoft.gradient(enabled=false);
+}
+.btn-info:hover,
+.btn-info:active,
+.btn-info.active,
+.btn-info.disabled,
+.btn-info[disabled] {
+ background-color: #2f96b4;
+}
+.btn-info:active,
+.btn-info.active {
+ background-color: #24748c \9;
+}
+.btn-inverse {
+ background-color: #414141;
+ background-image: -moz-linear-gradient(top, #555555, #222222);
+ background-image: -ms-linear-gradient(top, #555555, #222222);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#555555), to(#222222));
+ background-image: -webkit-linear-gradient(top, #555555, #222222);
+ background-image: -o-linear-gradient(top, #555555, #222222);
+ background-image: linear-gradient(top, #555555, #222222);
+ background-repeat: repeat-x;
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#555555', endColorstr='#222222', GradientType=0);
+ border-color: #222222 #222222 #000000;
+ border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+ filter: progid:dximagetransform.microsoft.gradient(enabled=false);
+}
+.btn-inverse:hover,
+.btn-inverse:active,
+.btn-inverse.active,
+.btn-inverse.disabled,
+.btn-inverse[disabled] {
+ background-color: #222222;
+}
+.btn-inverse:active,
+.btn-inverse.active {
+ background-color: #080808 \9;
+}
+button.btn,
+input[type="submit"].btn {
+ *padding-top: 2px;
+ *padding-bottom: 2px;
+}
+button.btn::-moz-focus-inner,
+input[type="submit"].btn::-moz-focus-inner {
+ padding: 0;
+ border: 0;
+}
+button.btn.btn-large,
+input[type="submit"].btn.btn-large {
+ *padding-top: 7px;
+ *padding-bottom: 7px;
+}
+button.btn.btn-small,
+input[type="submit"].btn.btn-small {
+ *padding-top: 3px;
+ *padding-bottom: 3px;
+}
+button.btn.btn-mini,
+input[type="submit"].btn.btn-mini {
+ *padding-top: 1px;
+ *padding-bottom: 1px;
+}
+.btn-group {
+ position: relative;
+ *zoom: 1;
+ *margin-left: .3em;
+}
+.btn-group:before,
+.btn-group:after {
+ display: table;
+ content: "";
+}
+.btn-group:after {
+ clear: both;
+}
+.btn-group:first-child {
+ *margin-left: 0;
+}
+.btn-group + .btn-group {
+ margin-left: 5px;
+}
+.btn-toolbar {
+ margin-top: 9px;
+ margin-bottom: 9px;
+}
+.btn-toolbar .btn-group {
+ display: inline-block;
+ *display: inline;
+ /* IE7 inline-block hack */
+
+ *zoom: 1;
+}
+.btn-group .btn {
+ position: relative;
+ float: left;
+ margin-left: -1px;
+ -webkit-border-radius: 0;
+ -moz-border-radius: 0;
+ border-radius: 0;
+}
+.btn-group .btn:first-child {
+ margin-left: 0;
+ -webkit-border-top-left-radius: 4px;
+ -moz-border-radius-topleft: 4px;
+ border-top-left-radius: 4px;
+ -webkit-border-bottom-left-radius: 4px;
+ -moz-border-radius-bottomleft: 4px;
+ border-bottom-left-radius: 4px;
+}
+.btn-group .btn:last-child,
+.btn-group .dropdown-toggle {
+ -webkit-border-top-right-radius: 4px;
+ -moz-border-radius-topright: 4px;
+ border-top-right-radius: 4px;
+ -webkit-border-bottom-right-radius: 4px;
+ -moz-border-radius-bottomright: 4px;
+ border-bottom-right-radius: 4px;
+}
+.btn-group .btn.large:first-child {
+ margin-left: 0;
+ -webkit-border-top-left-radius: 6px;
+ -moz-border-radius-topleft: 6px;
+ border-top-left-radius: 6px;
+ -webkit-border-bottom-left-radius: 6px;
+ -moz-border-radius-bottomleft: 6px;
+ border-bottom-left-radius: 6px;
+}
+.btn-group .btn.large:last-child,
+.btn-group .large.dropdown-toggle {
+ -webkit-border-top-right-radius: 6px;
+ -moz-border-radius-topright: 6px;
+ border-top-right-radius: 6px;
+ -webkit-border-bottom-right-radius: 6px;
+ -moz-border-radius-bottomright: 6px;
+ border-bottom-right-radius: 6px;
+}
+.btn-group .btn:hover,
+.btn-group .btn:focus,
+.btn-group .btn:active,
+.btn-group .btn.active {
+ z-index: 2;
+}
+.btn-group .dropdown-toggle:active,
+.btn-group.open .dropdown-toggle {
+ outline: 0;
+}
+.btn-group .dropdown-toggle {
+ padding-left: 8px;
+ padding-right: 8px;
+ -webkit-box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.125), inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+ -moz-box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.125), inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+ box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.125), inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+ *padding-top: 3px;
+ *padding-bottom: 3px;
+}
+.btn-group .btn-mini.dropdown-toggle {
+ padding-left: 5px;
+ padding-right: 5px;
+ *padding-top: 1px;
+ *padding-bottom: 1px;
+}
+.btn-group .btn-small.dropdown-toggle {
+ *padding-top: 4px;
+ *padding-bottom: 4px;
+}
+.btn-group .btn-large.dropdown-toggle {
+ padding-left: 12px;
+ padding-right: 12px;
+}
+.btn-group.open {
+ *z-index: 1000;
+}
+.btn-group.open .dropdown-menu {
+ display: block;
+ margin-top: 1px;
+ -webkit-border-radius: 5px;
+ -moz-border-radius: 5px;
+ border-radius: 5px;
+}
+.btn-group.open .dropdown-toggle {
+ background-image: none;
+ -webkit-box-shadow: inset 0 1px 6px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
+ -moz-box-shadow: inset 0 1px 6px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
+ box-shadow: inset 0 1px 6px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
+}
+.btn .caret {
+ margin-top: 7px;
+ margin-left: 0;
+}
+.btn:hover .caret,
+.open.btn-group .caret {
+ opacity: 1;
+ filter: alpha(opacity=100);
+}
+.btn-mini .caret {
+ margin-top: 5px;
+}
+.btn-small .caret {
+ margin-top: 6px;
+}
+.btn-large .caret {
+ margin-top: 6px;
+ border-left: 5px solid transparent;
+ border-right: 5px solid transparent;
+ border-top: 5px solid #000000;
+}
+.btn-primary .caret,
+.btn-warning .caret,
+.btn-danger .caret,
+.btn-info .caret,
+.btn-success .caret,
+.btn-inverse .caret {
+ border-top-color: #ffffff;
+ border-bottom-color: #ffffff;
+ opacity: 0.75;
+ filter: alpha(opacity=75);
+}
+.alert {
+ padding: 8px 35px 8px 14px;
+ margin-bottom: 18px;
+ text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
+ background-color: #fcf8e3;
+ border: 1px solid #fbeed5;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+ color: #c09853;
+}
+.alert-heading {
+ color: inherit;
+}
+.alert .close {
+ position: relative;
+ top: -2px;
+ right: -21px;
+ line-height: 18px;
+}
+.alert-success {
+ background-color: #dff0d8;
+ border-color: #d6e9c6;
+ color: #468847;
+}
+.alert-danger,
+.alert-error {
+ background-color: #f2dede;
+ border-color: #eed3d7;
+ color: #b94a48;
+}
+.alert-info {
+ background-color: #d9edf7;
+ border-color: #bce8f1;
+ color: #3a87ad;
+}
+.alert-block {
+ padding-top: 14px;
+ padding-bottom: 14px;
+}
+.alert-block > p,
+.alert-block > ul {
+ margin-bottom: 0;
+}
+.alert-block p + p {
+ margin-top: 5px;
+}
+.nav {
+ margin-left: 0;
+ margin-bottom: 18px;
+ list-style: none;
+}
+.nav > li > a {
+ display: block;
+}
+.nav > li > a:hover {
+ text-decoration: none;
+ background-color: #eeeeee;
+}
+.nav .nav-header {
+ display: block;
+ padding: 3px 15px;
+ font-size: 11px;
+ font-weight: bold;
+ line-height: 18px;
+ color: white;
+ text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
+ text-transform: uppercase;
+}
+.nav li + .nav-header {
+ margin-top: 9px;
+}
+.nav-list {
+ padding-left: 15px;
+ padding-right: 15px;
+ margin-bottom: 0;
+}
+.nav-list > li > a,
+.nav-list .nav-header {
+ margin-left: -15px;
+ margin-right: -15px;
+ text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
+}
+.nav-list > li > a {
+ padding: 3px 15px;
+}
+.nav-list > .active > a,
+.nav-list > .active > a:hover {
+ color: #ffffff;
+ text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.2);
+ background-color: #0088cc;
+}
+.nav-list [class^="icon-"] {
+ margin-right: 2px;
+}
+.nav-list .divider {
+ height: 1px;
+ margin: 8px 1px;
+ overflow: hidden;
+ background-color: #e5e5e5;
+ border-bottom: 1px solid #ffffff;
+ *width: 100%;
+ *margin: -5px 0 5px;
+}
+.nav-tabs,
+.nav-pills {
+ *zoom: 1;
+}
+.nav-tabs:before,
+.nav-pills:before,
+.nav-tabs:after,
+.nav-pills:after {
+ display: table;
+ content: "";
+}
+.nav-tabs:after,
+.nav-pills:after {
+ clear: both;
+}
+.nav-tabs > li,
+.nav-pills > li {
+ float: left;
+}
+.nav-tabs > li > a,
+.nav-pills > li > a {
+ padding-right: 12px;
+ padding-left: 12px;
+ margin-right: 2px;
+ line-height: 14px;
+}
+.nav-tabs {
+ border-bottom: 1px solid #ddd;
+}
+.nav-tabs > li {
+ margin-bottom: -1px;
+}
+.nav-tabs > li > a {
+ padding-top: 8px;
+ padding-bottom: 8px;
+ line-height: 18px;
+ border: 1px solid transparent;
+ -webkit-border-radius: 4px 4px 0 0;
+ -moz-border-radius: 4px 4px 0 0;
+ border-radius: 4px 4px 0 0;
+}
+.nav-tabs > li > a:hover {
+ border-color: #eeeeee #eeeeee #dddddd;
+}
+.nav-tabs > .active > a,
+.nav-tabs > .active > a:hover {
+ color: #555555;
+ background-color: #ffffff;
+ border: 1px solid #ddd;
+ border-bottom-color: transparent;
+ cursor: default;
+}
+.nav-pills > li > a {
+ padding-top: 8px;
+ padding-bottom: 8px;
+ margin-top: 2px;
+ margin-bottom: 2px;
+ -webkit-border-radius: 5px;
+ -moz-border-radius: 5px;
+ border-radius: 5px;
+}
+.nav-pills > .active > a,
+.nav-pills > .active > a:hover {
+ color: #ffffff;
+ background-color: #0088cc;
+}
+.nav-stacked > li {
+ float: none;
+}
+.nav-stacked > li > a {
+ margin-right: 0;
+}
+.nav-tabs.nav-stacked {
+ border-bottom: 0;
+}
+.nav-tabs.nav-stacked > li > a {
+ border: 1px solid #ddd;
+ -webkit-border-radius: 0;
+ -moz-border-radius: 0;
+ border-radius: 0;
+}
+.nav-tabs.nav-stacked > li:first-child > a {
+ -webkit-border-radius: 4px 4px 0 0;
+ -moz-border-radius: 4px 4px 0 0;
+ border-radius: 4px 4px 0 0;
+}
+.nav-tabs.nav-stacked > li:last-child > a {
+ -webkit-border-radius: 0 0 4px 4px;
+ -moz-border-radius: 0 0 4px 4px;
+ border-radius: 0 0 4px 4px;
+}
+.nav-tabs.nav-stacked > li > a:hover {
+ border-color: #ddd;
+ z-index: 2;
+}
+.nav-pills.nav-stacked > li > a {
+ margin-bottom: 3px;
+}
+.nav-pills.nav-stacked > li:last-child > a {
+ margin-bottom: 1px;
+}
+.nav-tabs .dropdown-menu,
+.nav-pills .dropdown-menu {
+ margin-top: 1px;
+ border-width: 1px;
+}
+.nav-pills .dropdown-menu {
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+}
+.nav-tabs .dropdown-toggle .caret,
+.nav-pills .dropdown-toggle .caret {
+ border-top-color: #0088cc;
+ border-bottom-color: #0088cc;
+ margin-top: 6px;
+}
+.nav-tabs .dropdown-toggle:hover .caret,
+.nav-pills .dropdown-toggle:hover .caret {
+ border-top-color: #005580;
+ border-bottom-color: #005580;
+}
+.nav-tabs .active .dropdown-toggle .caret,
+.nav-pills .active .dropdown-toggle .caret {
+ border-top-color: #333333;
+ border-bottom-color: #333333;
+}
+.nav > .dropdown.active > a:hover {
+ color: #000000;
+ cursor: pointer;
+}
+.nav-tabs .open .dropdown-toggle,
+.nav-pills .open .dropdown-toggle,
+.nav > .open.active > a:hover {
+ color: #ffffff;
+ background-color: #999999;
+ border-color: #999999;
+}
+.nav .open .caret,
+.nav .open.active .caret,
+.nav .open a:hover .caret {
+ border-top-color: #ffffff;
+ border-bottom-color: #ffffff;
+ opacity: 1;
+ filter: alpha(opacity=100);
+}
+.tabs-stacked .open > a:hover {
+ border-color: #999999;
+}
+.tabbable {
+ *zoom: 1;
+}
+.tabbable:before,
+.tabbable:after {
+ display: table;
+ content: "";
+}
+.tabbable:after {
+ clear: both;
+}
+.tab-content {
+ display: table;
+ width: 100%;
+}
+.tabs-below .nav-tabs,
+.tabs-right .nav-tabs,
+.tabs-left .nav-tabs {
+ border-bottom: 0;
+}
+.tab-content > .tab-pane,
+.pill-content > .pill-pane {
+ display: none;
+}
+.tab-content > .active,
+.pill-content > .active {
+ display: block;
+}
+.tabs-below .nav-tabs {
+ border-top: 1px solid #ddd;
+}
+.tabs-below .nav-tabs > li {
+ margin-top: -1px;
+ margin-bottom: 0;
+}
+.tabs-below .nav-tabs > li > a {
+ -webkit-border-radius: 0 0 4px 4px;
+ -moz-border-radius: 0 0 4px 4px;
+ border-radius: 0 0 4px 4px;
+}
+.tabs-below .nav-tabs > li > a:hover {
+ border-bottom-color: transparent;
+ border-top-color: #ddd;
+}
+.tabs-below .nav-tabs .active > a,
+.tabs-below .nav-tabs .active > a:hover {
+ border-color: transparent #ddd #ddd #ddd;
+}
+.tabs-left .nav-tabs > li,
+.tabs-right .nav-tabs > li {
+ float: none;
+}
+.tabs-left .nav-tabs > li > a,
+.tabs-right .nav-tabs > li > a {
+ min-width: 74px;
+ margin-right: 0;
+ margin-bottom: 3px;
+}
+.tabs-left .nav-tabs {
+ float: left;
+ margin-right: 19px;
+ border-right: 1px solid #ddd;
+}
+.tabs-left .nav-tabs > li > a {
+ margin-right: -1px;
+ -webkit-border-radius: 4px 0 0 4px;
+ -moz-border-radius: 4px 0 0 4px;
+ border-radius: 4px 0 0 4px;
+}
+.tabs-left .nav-tabs > li > a:hover {
+ border-color: #eeeeee #dddddd #eeeeee #eeeeee;
+}
+.tabs-left .nav-tabs .active > a,
+.tabs-left .nav-tabs .active > a:hover {
+ border-color: #ddd transparent #ddd #ddd;
+ *border-right-color: #ffffff;
+}
+.tabs-right .nav-tabs {
+ float: right;
+ margin-left: 19px;
+ border-left: 1px solid #ddd;
+}
+.tabs-right .nav-tabs > li > a {
+ margin-left: -1px;
+ -webkit-border-radius: 0 4px 4px 0;
+ -moz-border-radius: 0 4px 4px 0;
+ border-radius: 0 4px 4px 0;
+}
+.tabs-right .nav-tabs > li > a:hover {
+ border-color: #eeeeee #eeeeee #eeeeee #dddddd;
+}
+.tabs-right .nav-tabs .active > a,
+.tabs-right .nav-tabs .active > a:hover {
+ border-color: #ddd #ddd #ddd transparent;
+ *border-left-color: #ffffff;
+}
+.navbar {
+ *position: relative;
+ *z-index: 2;
+ overflow: visible;
+ margin-bottom: 18px;
+}
+.navbar-inner {
+ padding-left: 20px;
+ padding-right: 20px;
+ background-color: white;
+ border-bottom: 2px solid #221e50;
+/* background-image: -moz-linear-gradient(top, #333333, #222222);
+ background-image: -ms-linear-gradient(top, #333333, #222222);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#333333), to(#222222));
+ background-image: -webkit-linear-gradient(top, #333333, #222222);
+ background-image: -o-linear-gradient(top, #333333, #222222);
+ background-image: linear-gradient(top, #333333, #222222);
+ background-repeat: repeat-x;
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#333333', endColorstr='#222222', GradientType=0);
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+ -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25), inset 0 -1px 0 rgba(0, 0, 0, 0.1);
+ -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25), inset 0 -1px 0 rgba(0, 0, 0, 0.1);
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25), inset 0 -1px 0 rgba(0, 0, 0, 0.1);*/
+}
+.navbar .container {
+ width: auto;
+}
+.btn-navbar {
+ display: none;
+ float: right;
+ padding: 7px 10px;
+ margin-left: 5px;
+ margin-right: 5px;
+ background-color: #2c2c2c;
+ background-image: -moz-linear-gradient(top, #333333, #222222);
+ background-image: -ms-linear-gradient(top, #333333, #222222);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#333333), to(#222222));
+ background-image: -webkit-linear-gradient(top, #333333, #222222);
+ background-image: -o-linear-gradient(top, #333333, #222222);
+ background-image: linear-gradient(top, #333333, #222222);
+ background-repeat: repeat-x;
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#333333', endColorstr='#222222', GradientType=0);
+ border-color: #222222 #222222 #000000;
+ border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+ filter: progid:dximagetransform.microsoft.gradient(enabled=false);
+ -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.075);
+ -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.075);
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.075);
+}
+.btn-navbar:hover,
+.btn-navbar:active,
+.btn-navbar.active,
+.btn-navbar.disabled,
+.btn-navbar[disabled] {
+ background-color: #222222;
+}
+.btn-navbar:active,
+.btn-navbar.active {
+ background-color: #080808 \9;
+}
+.btn-navbar .icon-bar {
+ display: block;
+ width: 18px;
+ height: 2px;
+ background-color: #f5f5f5;
+ -webkit-border-radius: 1px;
+ -moz-border-radius: 1px;
+ border-radius: 1px;
+ -webkit-box-shadow: 0 1px 0 rgba(0, 0, 0, 0.25);
+ -moz-box-shadow: 0 1px 0 rgba(0, 0, 0, 0.25);
+ box-shadow: 0 1px 0 rgba(0, 0, 0, 0.25);
+}
+.btn-navbar .icon-bar + .icon-bar {
+ margin-top: 3px;
+}
+.nav-collapse.collapse {
+ height: auto;
+}
+.navbar {
+ color: #999999;
+}
+.navbar .brand:hover {
+ text-decoration: none;
+}
+.navbar .brand {
+ float: left;
+ display: block;
+ padding: 8px 20px 12px;
+ margin-left: -20px;
+ font-size: 20px;
+ font-weight: 200;
+ line-height: 1;
+ color: #ffffff;
+}
+.navbar .navbar-text {
+ margin-bottom: 0;
+ line-height: 40px;
+}
+.navbar .btn,
+.navbar .btn-group {
+ margin-top: 5px;
+}
+.navbar .btn-group .btn {
+ margin-top: 0;
+}
+.navbar-form {
+ margin-bottom: 0;
+ *zoom: 1;
+}
+.navbar-form:before,
+.navbar-form:after {
+ display: table;
+ content: "";
+}
+.navbar-form:after {
+ clear: both;
+}
+.navbar-form input,
+.navbar-form select,
+.navbar-form .radio,
+.navbar-form .checkbox {
+ margin-top: 5px;
+}
+.navbar-form input,
+.navbar-form select {
+ display: inline-block;
+ margin-bottom: 0;
+}
+.navbar-form input[type="image"],
+.navbar-form input[type="checkbox"],
+.navbar-form input[type="radio"] {
+ margin-top: 3px;
+}
+.navbar-form .input-append,
+.navbar-form .input-prepend {
+ margin-top: 6px;
+ white-space: nowrap;
+}
+.navbar-form .input-append input,
+.navbar-form .input-prepend input {
+ margin-top: 0;
+}
+.navbar-search {
+ position: relative;
+ float: left;
+ margin-top: 17px;
+ margin-bottom: 0;
+}
+.navbar-search .search-query {
+ padding: 4px 9px;
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-size: 13px;
+ font-weight: normal;
+ line-height: 1;
+ color: #ffffff;
+ background-color: #626262;
+ border: 1px solid #151515;
+ -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 0px rgba(255, 255, 255, 0.15);
+ -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 0px rgba(255, 255, 255, 0.15);
+ box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 0px rgba(255, 255, 255, 0.15);
+ -webkit-transition: none;
+ -moz-transition: none;
+ -ms-transition: none;
+ -o-transition: none;
+ transition: none;
+}
+.navbar-search .search-query:-moz-placeholder {
+ color: #cccccc;
+}
+.navbar-search .search-query::-webkit-input-placeholder {
+ color: #cccccc;
+}
+.navbar-search .search-query:focus,
+.navbar-search .search-query.focused {
+ padding: 5px 10px;
+ color: #333333;
+ text-shadow: 0 1px 0 #ffffff;
+ background-color: #ffffff;
+ border: 0;
+ -webkit-box-shadow: 0 0 3px rgba(0, 0, 0, 0.15);
+ -moz-box-shadow: 0 0 3px rgba(0, 0, 0, 0.15);
+ box-shadow: 0 0 3px rgba(0, 0, 0, 0.15);
+ outline: 0;
+}
+.navbar-fixed-top,
+.navbar-fixed-bottom {
+ position: fixed;
+ right: 0;
+ left: 0;
+ z-index: 1030;
+ margin-bottom: 0;
+}
+.navbar-fixed-top .navbar-inner,
+.navbar-fixed-bottom .navbar-inner {
+ padding-left: 0;
+ padding-right: 0;
+ -webkit-border-radius: 0;
+ -moz-border-radius: 0;
+ border-radius: 0;
+}
+.navbar-fixed-top .container,
+.navbar-fixed-bottom .container {
+ width: 940px;
+}
+.navbar-fixed-top {
+ top: 0;
+}
+.navbar-fixed-bottom {
+ bottom: 0;
+}
+.navbar .nav {
+ position: relative;
+ left: 0;
+ display: block;
+ float: left;
+ margin: 0 10px 0px 0;
+}
+.navbar .nav.pull-right {
+ float: right;
+}
+.navbar .nav > li {
+ display: block;
+ margin-top: 10px;
+ font-size: 130%;
+ float: left;
+}
+.navbar .nav > li > a {
+ float: none;
+ padding: 10px 10px 11px;
+ line-height: 25px;
+ color: #221e50;
+ text-decoration: none;
+ text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+}
+.navbar .nav > li > a:hover {
+ background-color: transparent;
+ color: #221e50;
+ text-decoration: underline;
+}
+.navbar .nav .active > a,
+.navbar .nav .active > a:hover {
+ color: #221e50;
+ text-decoration: underline;
+ background-color: white;
+}
+.navbar .divider-vertical {
+ height: 40px;
+ width: 1px;
+ margin: 0 9px;
+ overflow: hidden;
+ background-color: white;
+ border-right: 1px solid #333333;
+}
+.navbar .nav.pull-right {
+ margin-left: 10px;
+ margin-right: 0;
+}
+.navbar .dropdown-menu {
+ margin-top: 1px;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+}
+.navbar .dropdown-menu:before {
+ content: '';
+ display: inline-block;
+ border-left: 7px solid transparent;
+ border-right: 7px solid transparent;
+ border-bottom: 7px solid #ccc;
+ border-bottom-color: rgba(0, 0, 0, 0.2);
+ position: absolute;
+ top: -7px;
+ left: 9px;
+}
+.navbar .dropdown-menu:after {
+ content: '';
+ display: inline-block;
+ border-left: 6px solid transparent;
+ border-right: 6px solid transparent;
+ border-bottom: 6px solid #ffffff;
+ position: absolute;
+ top: -6px;
+ left: 10px;
+}
+.navbar-fixed-bottom .dropdown-menu:before {
+ border-top: 7px solid #ccc;
+ border-top-color: rgba(0, 0, 0, 0.2);
+ border-bottom: 0;
+ bottom: -7px;
+ top: auto;
+}
+.navbar-fixed-bottom .dropdown-menu:after {
+ border-top: 6px solid #ffffff;
+ border-bottom: 0;
+ bottom: -6px;
+ top: auto;
+}
+.navbar .nav .dropdown-toggle .caret,
+.navbar .nav .open.dropdown .caret {
+ border-top-color: #ffffff;
+ border-bottom-color: #ffffff;
+}
+.navbar .nav .active .caret {
+ opacity: 1;
+ filter: alpha(opacity=100);
+}
+.navbar .nav .open > .dropdown-toggle,
+.navbar .nav .active > .dropdown-toggle,
+.navbar .nav .open.active > .dropdown-toggle {
+ background-color: transparent;
+}
+.navbar .nav .active > .dropdown-toggle:hover {
+ color: #ffffff;
+}
+.navbar .nav.pull-right .dropdown-menu,
+.navbar .nav .dropdown-menu.pull-right {
+ left: auto;
+ right: 0;
+}
+.navbar .nav.pull-right .dropdown-menu:before,
+.navbar .nav .dropdown-menu.pull-right:before {
+ left: auto;
+ right: 12px;
+}
+.navbar .nav.pull-right .dropdown-menu:after,
+.navbar .nav .dropdown-menu.pull-right:after {
+ left: auto;
+ right: 13px;
+}
+.breadcrumb {
+ padding: 7px 14px;
+ margin: 0 0 18px;
+ list-style: none;
+ background-color: #fbfbfb;
+ background-image: -moz-linear-gradient(top, #ffffff, #f5f5f5);
+ background-image: -ms-linear-gradient(top, #ffffff, #f5f5f5);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#f5f5f5));
+ background-image: -webkit-linear-gradient(top, #ffffff, #f5f5f5);
+ background-image: -o-linear-gradient(top, #ffffff, #f5f5f5);
+ background-image: linear-gradient(top, #ffffff, #f5f5f5);
+ background-repeat: repeat-x;
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#f5f5f5', GradientType=0);
+ border: 1px solid #ddd;
+ -webkit-border-radius: 3px;
+ -moz-border-radius: 3px;
+ border-radius: 3px;
+ -webkit-box-shadow: inset 0 1px 0 #ffffff;
+ -moz-box-shadow: inset 0 1px 0 #ffffff;
+ box-shadow: inset 0 1px 0 #ffffff;
+}
+.breadcrumb li {
+ display: inline-block;
+ *display: inline;
+ /* IE7 inline-block hack */
+
+ *zoom: 1;
+ text-shadow: 0 1px 0 #ffffff;
+}
+.breadcrumb .divider {
+ padding: 0 5px;
+ color: #999999;
+}
+.breadcrumb .active a {
+ color: #333333;
+}
+.pagination {
+ height: 36px;
+ margin: 18px 0;
+}
+.pagination ul {
+ display: inline-block;
+ *display: inline;
+ /* IE7 inline-block hack */
+
+ *zoom: 1;
+ margin-left: 0;
+ margin-bottom: 0;
+ -webkit-border-radius: 3px;
+ -moz-border-radius: 3px;
+ border-radius: 3px;
+ -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
+ -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
+}
+.pagination li {
+ display: inline;
+}
+.pagination a {
+ float: left;
+ padding: 0 14px;
+ line-height: 34px;
+ text-decoration: none;
+ border: 1px solid #ddd;
+ border-left-width: 0;
+}
+.pagination a:hover,
+.pagination .active a {
+ background-color: #f5f5f5;
+}
+.pagination .active a {
+ color: #999999;
+ cursor: default;
+}
+.pagination .disabled span,
+.pagination .disabled a,
+.pagination .disabled a:hover {
+ color: #999999;
+ background-color: transparent;
+ cursor: default;
+}
+.pagination li:first-child a {
+ border-left-width: 1px;
+ -webkit-border-radius: 3px 0 0 3px;
+ -moz-border-radius: 3px 0 0 3px;
+ border-radius: 3px 0 0 3px;
+}
+.pagination li:last-child a {
+ -webkit-border-radius: 0 3px 3px 0;
+ -moz-border-radius: 0 3px 3px 0;
+ border-radius: 0 3px 3px 0;
+}
+.pagination-centered {
+ text-align: center;
+}
+.pagination-right {
+ text-align: right;
+}
+.pager {
+ margin-left: 0;
+ margin-bottom: 18px;
+ list-style: none;
+ text-align: center;
+ *zoom: 1;
+}
+.pager:before,
+.pager:after {
+ display: table;
+ content: "";
+}
+.pager:after {
+ clear: both;
+}
+.pager li {
+ display: inline;
+}
+.pager a {
+ display: inline-block;
+ padding: 5px 14px;
+ background-color: #fff;
+ border: 1px solid #ddd;
+ -webkit-border-radius: 15px;
+ -moz-border-radius: 15px;
+ border-radius: 15px;
+}
+.pager a:hover {
+ text-decoration: none;
+ background-color: #f5f5f5;
+}
+.pager .next a {
+ float: right;
+}
+.pager .previous a {
+ float: left;
+}
+.pager .disabled a,
+.pager .disabled a:hover {
+ color: #999999;
+ background-color: #fff;
+ cursor: default;
+}
+.modal-open .dropdown-menu {
+ z-index: 2050;
+}
+.modal-open .dropdown.open {
+ *z-index: 2050;
+}
+.modal-open .popover {
+ z-index: 2060;
+}
+.modal-open .tooltip {
+ z-index: 2070;
+}
+.modal-backdrop {
+ position: fixed;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ z-index: 1040;
+ background-color: #000000;
+}
+.modal-backdrop.fade {
+ opacity: 0;
+}
+.modal-backdrop,
+.modal-backdrop.fade.in {
+ opacity: 0.8;
+ filter: alpha(opacity=80);
+}
+.modal {
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ z-index: 1050;
+ overflow: auto;
+ width: 560px;
+ margin: -250px 0 0 -280px;
+ background-color: #ffffff;
+ border: 1px solid #999;
+ border: 1px solid rgba(0, 0, 0, 0.3);
+ *border: 1px solid #999;
+ /* IE6-7 */
+
+ -webkit-border-radius: 6px;
+ -moz-border-radius: 6px;
+ border-radius: 6px;
+ -webkit-box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3);
+ -moz-box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3);
+ box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3);
+ -webkit-background-clip: padding-box;
+ -moz-background-clip: padding-box;
+ background-clip: padding-box;
+}
+.modal.fade {
+ -webkit-transition: opacity .3s linear, top .3s ease-out;
+ -moz-transition: opacity .3s linear, top .3s ease-out;
+ -ms-transition: opacity .3s linear, top .3s ease-out;
+ -o-transition: opacity .3s linear, top .3s ease-out;
+ transition: opacity .3s linear, top .3s ease-out;
+ top: -25%;
+}
+.modal.fade.in {
+ top: 50%;
+}
+.modal-header {
+ padding: 9px 15px;
+ border-bottom: 1px solid #eee;
+}
+.modal-header .close {
+ margin-top: 2px;
+}
+.modal-body {
+ overflow-y: auto;
+ max-height: 400px;
+ padding: 15px;
+}
+.modal-form {
+ margin-bottom: 0;
+}
+.modal-footer {
+ padding: 14px 15px 15px;
+ margin-bottom: 0;
+ text-align: right;
+ background-color: #f5f5f5;
+ border-top: 1px solid #ddd;
+ -webkit-border-radius: 0 0 6px 6px;
+ -moz-border-radius: 0 0 6px 6px;
+ border-radius: 0 0 6px 6px;
+ -webkit-box-shadow: inset 0 1px 0 #ffffff;
+ -moz-box-shadow: inset 0 1px 0 #ffffff;
+ box-shadow: inset 0 1px 0 #ffffff;
+ *zoom: 1;
+}
+.modal-footer:before,
+.modal-footer:after {
+ display: table;
+ content: "";
+}
+.modal-footer:after {
+ clear: both;
+}
+.modal-footer .btn + .btn {
+ margin-left: 5px;
+ margin-bottom: 0;
+}
+.modal-footer .btn-group .btn + .btn {
+ margin-left: -1px;
+}
+.tooltip {
+ position: absolute;
+ z-index: 1020;
+ display: block;
+ visibility: visible;
+ padding: 5px;
+ font-size: 11px;
+ opacity: 0;
+ filter: alpha(opacity=0);
+}
+.tooltip.in {
+ opacity: 0.8;
+ filter: alpha(opacity=80);
+}
+.tooltip.top {
+ margin-top: -2px;
+}
+.tooltip.right {
+ margin-left: 2px;
+}
+.tooltip.bottom {
+ margin-top: 2px;
+}
+.tooltip.left {
+ margin-left: -2px;
+}
+.tooltip.top .tooltip-arrow {
+ bottom: 0;
+ left: 50%;
+ margin-left: -5px;
+ border-left: 5px solid transparent;
+ border-right: 5px solid transparent;
+ border-top: 5px solid #000000;
+}
+.tooltip.left .tooltip-arrow {
+ top: 50%;
+ right: 0;
+ margin-top: -5px;
+ border-top: 5px solid transparent;
+ border-bottom: 5px solid transparent;
+ border-left: 5px solid #000000;
+}
+.tooltip.bottom .tooltip-arrow {
+ top: 0;
+ left: 50%;
+ margin-left: -5px;
+ border-left: 5px solid transparent;
+ border-right: 5px solid transparent;
+ border-bottom: 5px solid #000000;
+}
+.tooltip.right .tooltip-arrow {
+ top: 50%;
+ left: 0;
+ margin-top: -5px;
+ border-top: 5px solid transparent;
+ border-bottom: 5px solid transparent;
+ border-right: 5px solid #000000;
+}
+.tooltip-inner {
+ max-width: 200px;
+ padding: 3px 8px;
+ color: #ffffff;
+ text-align: center;
+ text-decoration: none;
+ background-color: #000000;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+}
+.tooltip-arrow {
+ position: absolute;
+ width: 0;
+ height: 0;
+}
+.popover {
+ position: absolute;
+ top: 0;
+ left: 0;
+ z-index: 1010;
+ display: none;
+ padding: 5px;
+}
+.popover.top {
+ margin-top: -5px;
+}
+.popover.right {
+ margin-left: 5px;
+}
+.popover.bottom {
+ margin-top: 5px;
+}
+.popover.left {
+ margin-left: -5px;
+}
+.popover.top .arrow {
+ bottom: 0;
+ left: 50%;
+ margin-left: -5px;
+ border-left: 5px solid transparent;
+ border-right: 5px solid transparent;
+ border-top: 5px solid #000000;
+}
+.popover.right .arrow {
+ top: 50%;
+ left: 0;
+ margin-top: -5px;
+ border-top: 5px solid transparent;
+ border-bottom: 5px solid transparent;
+ border-right: 5px solid #000000;
+}
+.popover.bottom .arrow {
+ top: 0;
+ left: 50%;
+ margin-left: -5px;
+ border-left: 5px solid transparent;
+ border-right: 5px solid transparent;
+ border-bottom: 5px solid #000000;
+}
+.popover.left .arrow {
+ top: 50%;
+ right: 0;
+ margin-top: -5px;
+ border-top: 5px solid transparent;
+ border-bottom: 5px solid transparent;
+ border-left: 5px solid #000000;
+}
+.popover .arrow {
+ position: absolute;
+ width: 0;
+ height: 0;
+}
+.popover-inner {
+ padding: 3px;
+ width: 280px;
+ overflow: hidden;
+ background: #000000;
+ background: rgba(0, 0, 0, 0.8);
+ -webkit-border-radius: 6px;
+ -moz-border-radius: 6px;
+ border-radius: 6px;
+ -webkit-box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3);
+ -moz-box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3);
+ box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3);
+}
+.popover-title {
+ padding: 9px 15px;
+ line-height: 1;
+ background-color: #f5f5f5;
+ border-bottom: 1px solid #eee;
+ -webkit-border-radius: 3px 3px 0 0;
+ -moz-border-radius: 3px 3px 0 0;
+ border-radius: 3px 3px 0 0;
+}
+.popover-content {
+ padding: 14px;
+ background-color: #ffffff;
+ -webkit-border-radius: 0 0 3px 3px;
+ -moz-border-radius: 0 0 3px 3px;
+ border-radius: 0 0 3px 3px;
+ -webkit-background-clip: padding-box;
+ -moz-background-clip: padding-box;
+ background-clip: padding-box;
+}
+.popover-content p,
+.popover-content ul,
+.popover-content ol {
+ margin-bottom: 0;
+}
+.thumbnails {
+ margin-left: -20px;
+ list-style: none;
+ *zoom: 1;
+}
+.thumbnails:before,
+.thumbnails:after {
+ display: table;
+ content: "";
+}
+.thumbnails:after {
+ clear: both;
+}
+.thumbnails > li {
+ float: left;
+ margin: 0 0 18px 20px;
+}
+.thumbnail {
+ display: block;
+ padding: 4px;
+ line-height: 1;
+ border: 1px solid #ddd;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+ -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075);
+ -moz-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075);
+ box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075);
+}
+a.thumbnail:hover {
+ border-color: #0088cc;
+ -webkit-box-shadow: 0 1px 4px rgba(0, 105, 214, 0.25);
+ -moz-box-shadow: 0 1px 4px rgba(0, 105, 214, 0.25);
+ box-shadow: 0 1px 4px rgba(0, 105, 214, 0.25);
+}
+.thumbnail > img {
+ display: block;
+ max-width: 100%;
+ margin-left: auto;
+ margin-right: auto;
+}
+.thumbnail .caption {
+ padding: 9px;
+}
+.label {
+ padding: 1px 4px 2px;
+ font-size: 10.998px;
+ font-weight: bold;
+ line-height: 13px;
+ color: #ffffff;
+ vertical-align: middle;
+ white-space: nowrap;
+ text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+ background-color: #999999;
+ -webkit-border-radius: 3px;
+ -moz-border-radius: 3px;
+ border-radius: 3px;
+}
+.label:hover {
+ color: #ffffff;
+ text-decoration: none;
+}
+.label-important {
+ background-color: #b94a48;
+}
+.label-important:hover {
+ background-color: #953b39;
+}
+.label-warning {
+ background-color: #f89406;
+}
+.label-warning:hover {
+ background-color: #c67605;
+}
+.label-success {
+ background-color: #468847;
+}
+.label-success:hover {
+ background-color: #356635;
+}
+.label-info {
+ background-color: #3a87ad;
+}
+.label-info:hover {
+ background-color: #2d6987;
+}
+.label-inverse {
+ background-color: #333333;
+}
+.label-inverse:hover {
+ background-color: #1a1a1a;
+}
+.badge {
+ padding: 1px 9px 2px;
+ font-size: 12.025px;
+ font-weight: bold;
+ white-space: nowrap;
+ color: #ffffff;
+ background-color: #999999;
+ -webkit-border-radius: 9px;
+ -moz-border-radius: 9px;
+ border-radius: 9px;
+}
+.badge:hover {
+ color: #ffffff;
+ text-decoration: none;
+ cursor: pointer;
+}
+.badge-error {
+ background-color: #b94a48;
+}
+.badge-error:hover {
+ background-color: #953b39;
+}
+.badge-warning {
+ background-color: #f89406;
+}
+.badge-warning:hover {
+ background-color: #c67605;
+}
+.badge-success {
+ background-color: #468847;
+}
+.badge-success:hover {
+ background-color: #356635;
+}
+.badge-info {
+ background-color: #3a87ad;
+}
+.badge-info:hover {
+ background-color: #2d6987;
+}
+.badge-inverse {
+ background-color: #333333;
+}
+.badge-inverse:hover {
+ background-color: #1a1a1a;
+}
+@-webkit-keyframes progress-bar-stripes {
+ from {
+ background-position: 0 0;
+ }
+ to {
+ background-position: 40px 0;
+ }
+}
+@-moz-keyframes progress-bar-stripes {
+ from {
+ background-position: 0 0;
+ }
+ to {
+ background-position: 40px 0;
+ }
+}
+@-ms-keyframes progress-bar-stripes {
+ from {
+ background-position: 0 0;
+ }
+ to {
+ background-position: 40px 0;
+ }
+}
+@keyframes progress-bar-stripes {
+ from {
+ background-position: 0 0;
+ }
+ to {
+ background-position: 40px 0;
+ }
+}
+.progress {
+ overflow: hidden;
+ height: 18px;
+ margin-bottom: 18px;
+ background-color: #f7f7f7;
+ background-image: -moz-linear-gradient(top, #f5f5f5, #f9f9f9);
+ background-image: -ms-linear-gradient(top, #f5f5f5, #f9f9f9);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f5f5f5), to(#f9f9f9));
+ background-image: -webkit-linear-gradient(top, #f5f5f5, #f9f9f9);
+ background-image: -o-linear-gradient(top, #f5f5f5, #f9f9f9);
+ background-image: linear-gradient(top, #f5f5f5, #f9f9f9);
+ background-repeat: repeat-x;
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#f5f5f5', endColorstr='#f9f9f9', GradientType=0);
+ -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
+ -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
+ box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+}
+.progress .bar {
+ width: 0%;
+ height: 18px;
+ color: #ffffff;
+ font-size: 12px;
+ text-align: center;
+ text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+ background-color: #0e90d2;
+ background-image: -moz-linear-gradient(top, #149bdf, #0480be);
+ background-image: -ms-linear-gradient(top, #149bdf, #0480be);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#149bdf), to(#0480be));
+ background-image: -webkit-linear-gradient(top, #149bdf, #0480be);
+ background-image: -o-linear-gradient(top, #149bdf, #0480be);
+ background-image: linear-gradient(top, #149bdf, #0480be);
+ background-repeat: repeat-x;
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#149bdf', endColorstr='#0480be', GradientType=0);
+ -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
+ -moz-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
+ box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ -ms-box-sizing: border-box;
+ box-sizing: border-box;
+ -webkit-transition: width 0.6s ease;
+ -moz-transition: width 0.6s ease;
+ -ms-transition: width 0.6s ease;
+ -o-transition: width 0.6s ease;
+ transition: width 0.6s ease;
+}
+.progress-striped .bar {
+ background-color: #149bdf;
+ background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
+ background-image: -webkit-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: -moz-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: -ms-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: -o-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ -webkit-background-size: 40px 40px;
+ -moz-background-size: 40px 40px;
+ -o-background-size: 40px 40px;
+ background-size: 40px 40px;
+}
+.progress.active .bar {
+ -webkit-animation: progress-bar-stripes 2s linear infinite;
+ -moz-animation: progress-bar-stripes 2s linear infinite;
+ animation: progress-bar-stripes 2s linear infinite;
+}
+.progress-danger .bar {
+ background-color: #dd514c;
+ background-image: -moz-linear-gradient(top, #ee5f5b, #c43c35);
+ background-image: -ms-linear-gradient(top, #ee5f5b, #c43c35);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ee5f5b), to(#c43c35));
+ background-image: -webkit-linear-gradient(top, #ee5f5b, #c43c35);
+ background-image: -o-linear-gradient(top, #ee5f5b, #c43c35);
+ background-image: linear-gradient(top, #ee5f5b, #c43c35);
+ background-repeat: repeat-x;
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ee5f5b', endColorstr='#c43c35', GradientType=0);
+}
+.progress-danger.progress-striped .bar {
+ background-color: #ee5f5b;
+ background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
+ background-image: -webkit-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: -moz-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: -ms-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: -o-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+}
+.progress-success .bar {
+ background-color: #5eb95e;
+ background-image: -moz-linear-gradient(top, #62c462, #57a957);
+ background-image: -ms-linear-gradient(top, #62c462, #57a957);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#62c462), to(#57a957));
+ background-image: -webkit-linear-gradient(top, #62c462, #57a957);
+ background-image: -o-linear-gradient(top, #62c462, #57a957);
+ background-image: linear-gradient(top, #62c462, #57a957);
+ background-repeat: repeat-x;
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#62c462', endColorstr='#57a957', GradientType=0);
+}
+.progress-success.progress-striped .bar {
+ background-color: #62c462;
+ background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
+ background-image: -webkit-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: -moz-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: -ms-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: -o-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+}
+.progress-info .bar {
+ background-color: #4bb1cf;
+ background-image: -moz-linear-gradient(top, #5bc0de, #339bb9);
+ background-image: -ms-linear-gradient(top, #5bc0de, #339bb9);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#5bc0de), to(#339bb9));
+ background-image: -webkit-linear-gradient(top, #5bc0de, #339bb9);
+ background-image: -o-linear-gradient(top, #5bc0de, #339bb9);
+ background-image: linear-gradient(top, #5bc0de, #339bb9);
+ background-repeat: repeat-x;
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#5bc0de', endColorstr='#339bb9', GradientType=0);
+}
+.progress-info.progress-striped .bar {
+ background-color: #5bc0de;
+ background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
+ background-image: -webkit-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: -moz-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: -ms-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: -o-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+}
+.progress-warning .bar {
+ background-color: #faa732;
+ background-image: -moz-linear-gradient(top, #fbb450, #f89406);
+ background-image: -ms-linear-gradient(top, #fbb450, #f89406);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fbb450), to(#f89406));
+ background-image: -webkit-linear-gradient(top, #fbb450, #f89406);
+ background-image: -o-linear-gradient(top, #fbb450, #f89406);
+ background-image: linear-gradient(top, #fbb450, #f89406);
+ background-repeat: repeat-x;
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fbb450', endColorstr='#f89406', GradientType=0);
+}
+.progress-warning.progress-striped .bar {
+ background-color: #fbb450;
+ background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
+ background-image: -webkit-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: -moz-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: -ms-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: -o-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+}
+.accordion {
+ margin-bottom: 18px;
+}
+.accordion-group {
+ margin-bottom: 2px;
+ border: 1px solid #e5e5e5;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+}
+.accordion-heading {
+ border-bottom: 0;
+}
+.accordion-heading .accordion-toggle {
+ display: block;
+ padding: 8px 15px;
+}
+.accordion-inner {
+ padding: 9px 15px;
+ border-top: 1px solid #e5e5e5;
+}
+.carousel {
+ position: relative;
+ margin-bottom: 18px;
+ line-height: 1;
+}
+.carousel-inner {
+ overflow: hidden;
+ width: 100%;
+ position: relative;
+}
+.carousel .item {
+ display: none;
+ position: relative;
+ -webkit-transition: 0.6s ease-in-out left;
+ -moz-transition: 0.6s ease-in-out left;
+ -ms-transition: 0.6s ease-in-out left;
+ -o-transition: 0.6s ease-in-out left;
+ transition: 0.6s ease-in-out left;
+}
+.carousel .item > img {
+ display: block;
+ line-height: 1;
+}
+.carousel .active,
+.carousel .next,
+.carousel .prev {
+ display: block;
+}
+.carousel .active {
+ left: 0;
+}
+.carousel .next,
+.carousel .prev {
+ position: absolute;
+ top: 0;
+ width: 100%;
+}
+.carousel .next {
+ left: 100%;
+}
+.carousel .prev {
+ left: -100%;
+}
+.carousel .next.left,
+.carousel .prev.right {
+ left: 0;
+}
+.carousel .active.left {
+ left: -100%;
+}
+.carousel .active.right {
+ left: 100%;
+}
+.carousel-control {
+ position: absolute;
+ top: 40%;
+ left: 15px;
+ width: 40px;
+ height: 40px;
+ margin-top: -20px;
+ font-size: 60px;
+ font-weight: 100;
+ line-height: 30px;
+ color: #ffffff;
+ text-align: center;
+ background: #222222;
+ border: 3px solid #ffffff;
+ -webkit-border-radius: 23px;
+ -moz-border-radius: 23px;
+ border-radius: 23px;
+ opacity: 0.5;
+ filter: alpha(opacity=50);
+}
+.carousel-control.right {
+ left: auto;
+ right: 15px;
+}
+.carousel-control:hover {
+ color: #ffffff;
+ text-decoration: none;
+ opacity: 0.9;
+ filter: alpha(opacity=90);
+}
+.carousel-caption {
+ position: absolute;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ padding: 10px 15px 5px;
+ background: #333333;
+ background: rgba(0, 0, 0, 0.75);
+}
+.carousel-caption h4,
+.carousel-caption p {
+ color: #ffffff;
+}
+.hero-unit {
+ padding: 60px;
+ margin-bottom: 30px;
+ background-color: #eeeeee;
+ -webkit-border-radius: 6px;
+ -moz-border-radius: 6px;
+ border-radius: 6px;
+}
+.hero-unit h1 {
+ margin-bottom: 0;
+ font-size: 60px;
+ line-height: 1;
+ color: inherit;
+ letter-spacing: -1px;
+}
+.hero-unit p {
+ font-size: 18px;
+ font-weight: 200;
+ line-height: 27px;
+ color: inherit;
+}
+.pull-right {
+ float: right;
+}
+.pull-left {
+ float: left;
+}
+.hide {
+ display: none;
+}
+.show {
+ display: block;
+}
+.invisible {
+ visibility: hidden;
+}
+
+td.status-head {
+ font-weight:bold;
+ width:150px;
+ padding:7px;
+}
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/resources/web/css/styles.css b/src/ext/floodlight/src/main/resources/web/css/styles.css
new file mode 100644
index 0000000..1abe024
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/css/styles.css
@@ -0,0 +1,14 @@
+.dropdown-menu {
+ max-height: 400px;
+ overflow-y: scroll;
+ width: 220px;
+}
+
+.list-item {
+ padding-top: 6px;
+ padding-left: 56px;
+}
+
+.no-reports {
+ display: none;
+}
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/resources/web/img/floodlight.png b/src/ext/floodlight/src/main/resources/web/img/floodlight.png
new file mode 100644
index 0000000..98d0477
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/img/floodlight.png
Binary files differ
diff --git a/src/ext/floodlight/src/main/resources/web/img/glyphicons-halflings-white.png b/src/ext/floodlight/src/main/resources/web/img/glyphicons-halflings-white.png
new file mode 100644
index 0000000..a20760b
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/img/glyphicons-halflings-white.png
Binary files differ
diff --git a/src/ext/floodlight/src/main/resources/web/img/glyphicons-halflings.png b/src/ext/floodlight/src/main/resources/web/img/glyphicons-halflings.png
new file mode 100644
index 0000000..92d4445
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/img/glyphicons-halflings.png
Binary files differ
diff --git a/src/ext/floodlight/src/main/resources/web/img/logo.jpg b/src/ext/floodlight/src/main/resources/web/img/logo.jpg
new file mode 100644
index 0000000..49795ef
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/img/logo.jpg
Binary files differ
diff --git a/src/ext/floodlight/src/main/resources/web/img/openflow-logo-40px.png b/src/ext/floodlight/src/main/resources/web/img/openflow-logo-40px.png
new file mode 100644
index 0000000..b455ace
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/img/openflow-logo-40px.png
Binary files differ
diff --git a/src/ext/floodlight/src/main/resources/web/img/server.png b/src/ext/floodlight/src/main/resources/web/img/server.png
new file mode 100644
index 0000000..341c73d
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/img/server.png
Binary files differ
diff --git a/src/ext/floodlight/src/main/resources/web/img/switch.png b/src/ext/floodlight/src/main/resources/web/img/switch.png
new file mode 100644
index 0000000..f8e6910
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/img/switch.png
Binary files differ
diff --git a/src/ext/floodlight/src/main/resources/web/index.html b/src/ext/floodlight/src/main/resources/web/index.html
new file mode 100644
index 0000000..cc2c2e8
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/index.html
@@ -0,0 +1,94 @@
+<!DOCTYPE html>
+<!-- template from Christophe Coenraets -->
+<!--
+ Copyright 2012 IBM
+
+ 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.
+ -->
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+ <title>Floodlight</title>
+ <meta name="description" content="">
+ <meta name="author" content="">
+
+ <!-- Le HTML5 shim, for IE6-8 support of HTML elements -->
+ <!--[if lt IE 9]>
+ <script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
+ <!-- Place this tag in your head or just before your close body tag -->
+ <![endif]-->
+
+ <!-- Le styles -->
+ <style>
+ body {
+ padding-top: 60px; /* 60px to make the container go all the way to the bottom of the topbar */
+ }
+ </style>
+ <link href="css/bootstrap.css" rel="stylesheet">
+ <link href="css/styles.css" rel="stylesheet">
+
+ <!-- Le fav and touch icons -->
+ <!-- TODO create some icons
+ <link rel="shortcut icon" href="/img/favicon.ico">
+ <link rel="apple-touch-icon" href="/img/apple-touch-icon.png">
+ <link rel="apple-touch-icon" sizes="72x72" href="/img/apple-touch-icon-72x72.png">
+ <link rel="apple-touch-icon" sizes="114x114" href="/img/apple-touch-icon-114x114.png">
+ -->
+</head>
+
+<body>
+
+<div class="header"></div>
+
+<div class="container">
+
+<div id="content"></div>
+
+<hr>
+<footer class="footer">
+ <p><a href="http://floodlight.openflowhub.org/">Floodlight </a> © <a href="http://www.bigswitch.com/">Big Switch Networks</a>, <a href="http://www.research.ibm.com/arl/">IBM</a>, et. al.
+ Powered by <a href="http://documentcloud.github.com/backbone/">Backbone.js</a>, <a href="http://twitter.github.com/bootstrap/">Bootstrap</a>, <a href="http://jquery.com/">jQuery</a>, <a href="http://mbostock.github.com/d3/">D3.js</a>, etc.</p>
+</footer>
+
+</div> <!-- /container -->
+
+<script src="lib/jquery.min.js"></script>
+<script src="lib/underscore-min.js"></script>
+<script src="lib/backbone-min.js"></script>
+<script src="lib/d3.v2.min.js"></script>
+
+<script src="lib/bootstrap-dropdown.js"></script>
+<script src="lib/bootstrap-alert.js"></script>
+
+<script src="js/utils.js"></script>
+
+<script src="js/models/hostmodel.js"></script>
+<script src="js/models/topologymodel.js"></script>
+<script src="js/models/statusmodel.js"></script>
+<script src="js/models/switchmodel.js"></script>
+<script src="js/models/portmodel.js"></script>
+<script src="js/models/flowmodel.js"></script>
+
+<script src="js/views/header.js"></script>
+<script src="js/views/home.js"></script>
+<script src="js/views/status.js"></script>
+<script src="js/views/host.js"></script>
+<script src="js/views/switch.js"></script>
+<script src="js/views/topology.js"></script>
+<script src="js/views/port.js"></script>
+<script src="js/views/flow.js"></script>
+
+<script src="js/main.js"></script>
+
+</body>
+</html>
diff --git a/src/ext/floodlight/src/main/resources/web/js/main.js b/src/ext/floodlight/src/main/resources/web/js/main.js
new file mode 100644
index 0000000..2362757
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/js/main.js
@@ -0,0 +1,127 @@
+/*
+ Copyright 2012 IBM
+
+ 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.
+*/
+
+var hackBase = ""; // put a URL here to access a different REST server
+
+var AppRouter = Backbone.Router.extend({
+
+ routes:{
+ "":"home",
+ "topology":"topology",
+ "switches":"switchList",
+ "switch/:id":"switchDetails",
+ "switch/:id/port/:p":"portDetails", // not clear if needed
+ "hosts":"hostList",
+ "host/:id":"hostDetails",
+ // "vlans":"vlanList" // maybe one day
+ // "vlan/:id":"vlanDetails"
+ },
+
+ initialize:function () {
+ this.headerView = new HeaderView();
+ $('.header').html(this.headerView.render().el);
+
+ // Close the search dropdown on click anywhere in the UI
+ $('body').click(function () {
+ $('.dropdown').removeClass("open");
+ });
+ },
+
+ home:function () {
+ $('#content').html(new HomeView().render().el);
+ $('ul[class="nav"] > li').removeClass('active');
+ $('a[href="/"]').parent().addClass('active');
+ },
+
+ topology:function () {
+ //console.log("switching to topology view");
+ var topo = new Topology();
+ $('#content').html(new TopologyView({model:topo, hosts:hl}).render().el);
+ // TODO factor this code out
+ $('ul.nav > li').removeClass('active');
+ $('li > a[href*="topology"]').parent().addClass('active');
+ },
+
+ switchDetails:function (id) {
+ //console.log("switching [sic] to single switch view");
+ var sw = swl.get(id);
+ $('#content').html(new SwitchView({model:sw}).render().el);
+ $('ul.nav > li').removeClass('active');
+ $('li > a[href*="/switches"]').parent().addClass('active');
+ },
+
+ switchList:function () {
+ //console.log("switching [sic] to switch list view");
+ $('#content').html(new SwitchListView({model:swl}).render().el);
+ $('ul.nav > li').removeClass('active');
+ $('li > a[href*="/switches"]').parent().addClass('active');
+ },
+
+ hostDetails:function (id) {
+ //console.log("switching to single host view");
+ var h = hl.get(id);
+ $('#content').html(new HostView({model:h}).render().el);
+ $('ul.nav > li').removeClass('active');
+ $('li > a[href*="/hosts"]').parent().addClass('active');
+ },
+
+ hostList:function () {
+ //console.log("switching to host list view");
+ $('#content').html(new HostListView({model:hl}).render().el);
+ $('ul.nav > li').removeClass('active');
+ $('li > a[href*="/hosts"]').parent().addClass('active');
+ },
+
+});
+
+// load global models and reuse them
+var swl = new SwitchCollection();
+var hl = new HostCollection();
+
+tpl.loadTemplates(['home', 'status', 'topology', 'header', 'switch', 'switch-list', 'switch-list-item', 'host', 'host-list', 'host-list-item', 'port-list', 'port-list-item', 'flow-list', 'flow-list-item'],
+ function () {
+ app = new AppRouter();
+ Backbone.history.start({pushState: true});
+ // console.log("started history")
+
+ $(document).ready(function () {
+ // trigger Backbone routing when clicking on links, thanks to Atinux and pbnv
+ app.navigate("", true);
+
+ window.document.addEventListener('click', function(e) {
+ e = e || window.event
+ var target = e.target || e.srcElement
+ if ( target.nodeName.toLowerCase() === 'a' ) {
+ e.preventDefault()
+ var uri = target.getAttribute('href')
+ app.navigate(uri.substr(1), true)
+ }
+ });
+ window.addEventListener('popstate', function(e) {
+ app.navigate(location.pathname.substr(1), true);
+ });
+
+ });
+ });
+
+setInterval(function () {
+ swl.fetch();
+}, 3000);
+
+setInterval(function () {
+ hl.fetch();
+}, 3000);
+
diff --git a/src/ext/floodlight/src/main/resources/web/js/models/flowmodel.js b/src/ext/floodlight/src/main/resources/web/js/models/flowmodel.js
new file mode 100644
index 0000000..80777c3
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/js/models/flowmodel.js
@@ -0,0 +1,37 @@
+/*
+ Copyright 2012 IBM
+
+ 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.
+*/
+
+window.Flow = Backbone.Model.extend({
+
+ defaults: {
+ receiveBytes: 0,
+ receivePackets: 0,
+ transmitBytes: 0,
+ transmitPackets: 0,
+ },
+
+ // initialize:function () {}
+
+});
+
+window.FlowCollection = Backbone.Collection.extend({
+
+ model:Flow,
+
+ // instead of the collection loading its children, the switch will load them
+ // initialize:function () {}
+
+});
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/resources/web/js/models/hostmodel.js b/src/ext/floodlight/src/main/resources/web/js/models/hostmodel.js
new file mode 100644
index 0000000..47ae420
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/js/models/hostmodel.js
@@ -0,0 +1,72 @@
+/*
+ Copyright 2012 IBM
+
+ 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.
+*/
+
+window.Host = Backbone.Model.extend({
+
+ defaults: {
+ // vlan: -1,
+ lastSeen: 'never',
+ ip: ' ',
+ swport: ' ',
+ },
+
+ // initialize:function () {}
+
+});
+
+window.HostCollection = Backbone.Collection.extend({
+
+ model:Host,
+
+ initialize:function () {
+ var self = this;
+ //console.log("fetching host list")
+ $.ajax({
+ url:hackBase + "/wm/device/",
+ dataType:"json",
+ success:function (data) {
+ // console.log("fetched host list: " + data.length);
+ // console.log(data);
+ // data is a list of device hashes
+ _.each(data, function(h) {
+ if (h['attachmentPoint'].length > 0) {
+ h.id = h.mac[0];
+ h.swport = _.reduce(h['attachmentPoint'], function(memo, ap) {
+ return memo + ap.switchDPID + "-" + ap.port + " "}, "");
+ //console.log(h.swport);
+ h.lastSeen = new Date(h.lastSeen).toLocaleString();
+ self.add(h, {silent: true});
+ }
+ });
+ self.trigger('add'); // batch redraws
+ }
+ });
+
+ },
+
+ fetch:function () {
+ this.initialize();
+ }
+
+ /*
+ * findByName:function (key) { // TODO: Modify service to include firstName
+ * in search var url = (key == '') ? '/host/' : "/host/search/" + key;
+ * console.log('findByName: ' + key); var self = this; $.ajax({ url:url,
+ * dataType:"json", success:function (data) { console.log("search success: " +
+ * data.length); self.reset(data); } }); }
+ */
+
+});
diff --git a/src/ext/floodlight/src/main/resources/web/js/models/portmodel.js b/src/ext/floodlight/src/main/resources/web/js/models/portmodel.js
new file mode 100644
index 0000000..563f334
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/js/models/portmodel.js
@@ -0,0 +1,42 @@
+/*
+ Copyright 2012 IBM
+
+ 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.
+*/
+
+window.Port = Backbone.Model.extend({
+
+ defaults: {
+ name: '',
+ receiveBytes: 0,
+ receivePackets: 0,
+ transmitBytes: 0,
+ transmitPackets: 0,
+ dropped: 0,
+ errors: 0,
+ },
+
+ initialize:function () {
+ // TODO hook up associated hosts
+ }
+
+});
+
+window.PortCollection = Backbone.Collection.extend({
+
+ model:Port,
+
+ // instead of the collection loading its children, the switch will load them
+ initialize:function () {}
+
+});
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/resources/web/js/models/statusmodel.js b/src/ext/floodlight/src/main/resources/web/js/models/statusmodel.js
new file mode 100644
index 0000000..31bdff3
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/js/models/statusmodel.js
@@ -0,0 +1,64 @@
+/*
+ Copyright 2012 IBM
+
+ 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.
+*/
+
+window.Status = Backbone.Model.extend({
+ defaults: {
+ host: 'localhost',
+ ofport: 6633,
+ uptime: 'unknown',
+ free: 0,
+ total: 0,
+ healthy: 'unknown',
+ modules: [],
+ moduleText: ''
+ },
+
+ initialize:function () {
+ var self = this;
+ console.log("fetching controller status");
+ $.ajax({
+ url:hackBase + "/wm/core/health/json",
+ dataType:"json",
+ success:function (data) {
+ console.log("fetched controller status: health");
+ self.set(data);
+ // console.log(self.toJSON());
+ }
+ });
+ $.ajax({
+ url:hackBase + "/wm/core/memory/json",
+ dataType:"json",
+ success:function (data) {
+ console.log("fetched controller status: memory");
+ self.set(data);
+ // console.log(self.toJSON());
+ }
+ });
+ $.ajax({
+ url:hackBase + "/wm/core/module/loaded/json",
+ dataType:"json",
+ success:function (data) {
+ console.log("fetched controller status: modules loaded");
+ // console.log(data);
+ self.set({modules:_.keys(data)});
+ self.set({moduleText:_.reduce(_.keys(data), function(s, m)
+ {return s+m.replace("net.floodlightcontroller", "n.f")+", "}, '')});
+ }
+ });
+
+ }
+
+});
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/resources/web/js/models/switchmodel.js b/src/ext/floodlight/src/main/resources/web/js/models/switchmodel.js
new file mode 100644
index 0000000..285c912
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/js/models/switchmodel.js
@@ -0,0 +1,275 @@
+/*
+ Copyright 2012 IBM
+
+ 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.
+*/
+
+window.Switch = Backbone.Model.extend({
+
+ urlRoot:"/wm/core/switch/",
+
+ defaults: {
+ datapathDescription: '',
+ hardwareDescription: '',
+ manufacturerDescription: '',
+ serialNumber: '',
+ softwareDescription: '',
+ flowCount: ' ',
+ packetCount: ' ',
+ byteCount: ' ',
+ },
+
+ initialize:function () {
+ var self = this;
+
+ //console.log("fetching switch " + this.id + " desc")
+ $.ajax({
+ url:hackBase + "/wm/core/switch/" + self.id + '/desc/json',
+ dataType:"json",
+ success:function (data) {
+ //console.log("fetched switch " + self.id + " desc");
+ //console.log(data[self.id][0]);
+ self.set(data[self.id][0]);
+ }
+ });
+
+ //console.log("fetching switch " + this.id + " aggregate")
+ $.ajax({
+ url:hackBase + "/wm/core/switch/" + self.id + '/aggregate/json',
+ dataType:"json",
+ success:function (data) {
+ //console.log("fetched switch " + self.id + " aggregate");
+ //console.log(data[self.id][0]);
+ self.set(data[self.id][0]);
+ }
+ });
+ self.trigger('add');
+ this.ports = new PortCollection();
+ this.flows = new FlowCollection();
+ //this.loadPorts();
+ //this.loadFlows();
+ },
+
+ fetch:function () {
+ this.initialize()
+ },
+
+ loadPorts:function () {
+ var self = this;
+ //console.log("fetching switch " + this.id + " ports")
+ //console.log("fetching switch " + this.id + " features")
+ $.when($.ajax({
+ url:hackBase + "/wm/core/switch/" + self.id + '/port/json',
+ dataType:"json",
+ success:function (data) {
+ //console.log("fetched switch " + self.id + " ports");
+ //console.log(data[self.id]);
+ // create port models
+ _.each(data[self.id], function(p) {
+ p.id = self.id+'-'+p.portNumber;
+ p.dropped = p.receiveDropped + p.transmitDropped;
+ p.errors = p.receiveCRCErrors + p.receiveErrors + p.receiveOverrunErrors +
+ p.receiveFrameErrors + p.transmitErrors;
+ // this is a knda kludgy way to merge models
+ var m = self.ports.get(p.id);
+ if(m) {
+ m.set(p, {silent: true});
+ } else {
+ self.ports.add(p, {silent: true});
+ }
+ //console.log(p);
+ });
+ }
+ }),
+ $.ajax({
+ url:hackBase + "/wm/core/switch/" + self.id + '/features/json',
+ dataType:"json",
+ success:function (data) {
+ //console.log("fetched switch " + self.id + " features");
+ //console.log(data[self.id]);
+ // update port models
+ _.each(data[self.id].ports, function(p) {
+ p.id = self.id+'-'+p.portNumber;
+ if(p.name != p.portNumber) {
+ p.name = p.portNumber + ' (' + p.name + ')';
+ }
+ p.status = '';
+ p.status += (p.state & 1) ? 'DOWN' : 'UP';
+ switch(p.currentFeatures & 0x7f) {
+ case 1:
+ p.status += ' 10 Mbps';
+ break;
+ case 2:
+ p.status += ' 10 Mbps FDX';
+ break;
+ case 4:
+ p.status += ' 100 Mbps';
+ break;
+ case 8:
+ p.status += ' 100 Mbps FDX';
+ break;
+ case 16:
+ p.status += ' 1 Gbps'; // RLY?
+ break;
+ case 32:
+ p.status += ' 1 Gbps FDX';
+ break;
+ case 64:
+ p.status += ' 10 Gbps FDX';
+ break;
+ }
+ // TODO parse copper/fiber, autoneg, pause
+
+ // this is a knda kludgy way to merge models
+ var m = self.ports.get(p.id);
+ if(m) {
+ m.set(p, {silent: true});
+ } else {
+ self.ports.add(p, {silent: true});
+ }
+ //console.log(p);
+ });
+ }
+ })).done(function() {
+ self.ports.trigger('add'); // batch redraws
+ });
+ },
+
+ loadFlows:function () {
+ var self = this;
+ //console.log("fetching switch " + this.id + " flows")
+ $.ajax({
+ url:hackBase + "/wm/core/switch/" + self.id + '/flow/json',
+ dataType:"json",
+ success:function (data) {
+ //console.log("fetched switch " + self.id + " flows");
+ var flows = data[self.id];
+ //console.log(flows);
+
+ // create flow models
+ var i = 0;
+ _.each(flows, function(f) {
+ f.id = self.id + '-' + i++;
+
+ // build human-readable match
+ f.matchHTML = '';
+ if(!(f.match.wildcards & (1<<0))) { // input port
+ f.matchHTML += "port=" + f.match.inputPort + ", ";
+ }
+ if(!(f.match.wildcards & (1<<1))) { // VLAN ID
+ f.matchHTML += "VLAN=" + f.match.dataLayerVirtualLan + ", ";
+ }
+ if(!(f.match.wildcards & (1<<20))) { // VLAN prio
+ f.matchHTML += "prio=" + f.match.dataLayerVirtualLanPriorityCodePoint + ", ";
+ }
+ if(!(f.match.wildcards & (1<<2))) { // src MAC
+ f.matchHTML += "src=<a href='/host/" + f.match.dataLayerSource + "'>" +
+ f.match.dataLayerSource + "</a>, ";
+ }
+ if(!(f.match.wildcards & (1<<3))) { // dest MAC
+ f.matchHTML =+ "dest=<a href='/host/" + f.match.dataLayerDestination + "'>" +
+ f.match.dataLayerDestination + "</a>, ";
+ }
+ if(!(f.match.wildcards & (1<<4))) { // Ethertype
+ // TODO print a human-readable name instead of hex
+ f.matchHTML += "ethertype=" + f.match.dataLayerType + ", ";
+ }
+ if(!(f.match.wildcards & (1<<5))) { // IP protocol
+ // TODO print a human-readable name
+ f.matchHTML += "proto=" + f.match.networkProtocol + ", ";
+ }
+ if(!(f.match.wildcards & (1<<6))) { // TCP/UDP source port
+ f.matchHTML += "IP src port=" + f.match.transportSource + ", ";
+ }
+ if(!(f.match.wildcards & (1<<7))) { // TCP/UDP dest port
+ f.matchHTML += "IP dest port=" + f.match.transportDestination + ", ";
+ }
+ if(!(f.match.wildcards & (32<<8))) { // src IP
+ f.matchHTML += "src=" + f.match.networkSource + ", ";
+ }
+ if(!(f.match.wildcards & (32<<14))) { // dest IP
+ f.matchHTML += "dest=" + f.match.networkDestination + ", ";
+ }
+ if(!(f.match.wildcards & (1<<21))) { // IP TOS
+ f.matchHTML += "TOS=" + f.match.networkTypeOfService + ", ";
+ }
+ // remove trailing ", "
+ f.matchHTML = f.matchHTML.substr(0, f.matchHTML.length - 2);
+
+ // build human-readable action list
+ f.actionText = _.reduce(f.actions, function (memo, a) {
+ switch (a.type) {
+ case "OUTPUT":
+ return memo + "output " + a.port + ', ';
+ case "OPAQUE_ENQUEUE":
+ return memo + "enqueue " + a.port + ':' + a.queueId + ', ';
+ case "STRIP_VLAN":
+ return memo + "strip VLAN, ";
+ case "SET_VLAN_ID":
+ return memo + "VLAN=" + a.virtualLanIdentifier + ', ';
+ case "SET_VLAN_PCP":
+ return memo + "prio=" + a.virtualLanPriorityCodePoint + ', ';
+ case "SET_DL_SRC":
+ return memo + "src=" + a.dataLayerAddress + ', ';
+ case "SET_DL_DST":
+ return memo + "dest=" + a.dataLayerAddress + ', ';
+ case "SET_NW_TOS":
+ return memo + "TOS=" + a.networkTypeOfService + ', ';
+ case "SET_NW_SRC":
+ return memo + "src=" + a.networkAddress + ', ';
+ case "SET_NW_DST":
+ return memo + "dest=" + a.networkAddress + ', ';
+ case "SET_TP_SRC":
+ return memo + "src port=" + a.transportPort + ', ';
+ case "SET_TP_DST":
+ return memo + "dest port=" + a.transportPort + ', ';
+ }
+ }, "");
+ // remove trailing ", "
+ f.actionText = f.actionText.substr(0, f.actionText.length - 2);
+
+ //console.log(f);
+ self.flows.add(f, {silent: true});
+ });
+ self.flows.trigger('add');
+ }
+ });
+ },
+});
+
+window.SwitchCollection = Backbone.Collection.extend({
+
+ model:Switch,
+
+ initialize:function () {
+ var self = this;
+ //console.log("fetching switch list")
+ $.ajax({
+ url:hackBase + "/wm/core/controller/switches/json",
+ dataType:"json",
+ success:function (data) {
+ //console.log("fetched switch list: " + data.length);
+ //console.log(data);
+ _.each(data, function(sw) {self.add({id: sw['dpid'],
+ inetAddress: sw.inetAddress,
+ connectedSince: new Date(sw.connectedSince).toLocaleString()})});
+ }
+ });
+ },
+
+ fetch:function () {
+ this.initialize()
+ }
+
+
+});
diff --git a/src/ext/floodlight/src/main/resources/web/js/models/topologymodel.js b/src/ext/floodlight/src/main/resources/web/js/models/topologymodel.js
new file mode 100644
index 0000000..c5d8f9b
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/js/models/topologymodel.js
@@ -0,0 +1,65 @@
+/*
+ Copyright 2012 IBM
+
+ 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.
+*/
+
+window.Topology = Backbone.Model.extend({
+
+ url:"/wm/topology/links/json",
+
+ defaults:{
+ nodes: [],
+ links: [],
+ },
+
+ initialize:function () {
+ var self = this;
+ console.log("fetching topology")
+ $.ajax({
+ url:hackBase + self.url,
+ dataType:"json",
+ success:function (data) {
+ console.log("fetched topology: " + data.length);
+ // console.log(data);
+ self.nodes = {};
+ self.links = [];
+
+ // step 1: build unique array of switch IDs
+ /* this doesn't work if there's only one switch,
+ because there are no switch-switch links
+ _.each(data, function (l) {
+ self.nodes[l['src-switch']] = true;
+ self.nodes[l['dst-switch']] = true;
+ });
+ // console.log(self.nodes);
+ var nl = _.keys(self.nodes);
+ */
+ var nl = swl.pluck('id');
+ self.nodes = _.map(nl, function (n) {return {name:n}});
+
+ // step 2: build array of links in format D3 expects
+ _.each(data, function (l) {
+ self.links.push({source:nl.indexOf(l['src-switch']),
+ target:nl.indexOf(l['dst-switch']),
+ value:10});
+ });
+ // console.log(self.nodes);
+ // console.log(self.links);
+ self.trigger('change');
+ //self.set(data);
+ }
+ });
+ }
+
+});
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/resources/web/js/utils.js b/src/ext/floodlight/src/main/resources/web/js/utils.js
new file mode 100644
index 0000000..c086e29
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/js/utils.js
@@ -0,0 +1,36 @@
+// template loader from Christophe Coenraets
+tpl = {
+
+ // Hash of preloaded templates for the app
+ templates:{},
+
+ // Recursively pre-load all the templates for the app.
+ // This implementation should be changed in a production environment. All the template files should be
+ // concatenated in a single file.
+ loadTemplates:function (names, callback) {
+
+ var that = this;
+
+ var loadTemplate = function (index) {
+ var name = names[index];
+ console.log('Loading template: ' + name);
+ $.get('tpl/' + name + '.html', function (data) {
+ that.templates[name] = data;
+ index++;
+ if (index < names.length) {
+ loadTemplate(index);
+ } else {
+ callback();
+ }
+ });
+ }
+
+ loadTemplate(0);
+ },
+
+ // Get template by name from hash of preloaded templates
+ get:function (name) {
+ return this.templates[name];
+ }
+
+};
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/resources/web/js/views/flow.js b/src/ext/floodlight/src/main/resources/web/js/views/flow.js
new file mode 100644
index 0000000..65e0b71
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/js/views/flow.js
@@ -0,0 +1,69 @@
+/*
+ Copyright 2012 IBM
+
+ 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.
+*/
+
+// not used for now
+window.FlowView = Backbone.View.extend({
+
+ initialize:function () {
+ this.template = _.template(tpl.get('flow'));
+ this.model.bind("change", this.render, this);
+ },
+
+ render:function (eventName) {
+ $(this.el).html(this.template(this.model.toJSON()));
+ return this;
+ }
+
+});
+
+window.FlowListItemView = Backbone.View.extend({
+
+ tagName:"tr",
+
+ initialize:function () {
+ this.template = _.template(tpl.get('flow-list-item'));
+ this.model.bind("change", this.render, this);
+ },
+
+ render:function (eventName) {
+ $(this.el).html(this.template(this.model.toJSON()));
+ return this;
+ }
+
+});
+
+// TODO throughput (bps) and pps sparklines would be nice here
+// TODO hovering over a MAC address could show a compact view of that host
+window.FlowListView = Backbone.View.extend({
+
+ initialize:function () {
+ this.template = _.template(tpl.get('flow-list'));
+ this.model.bind("change", this.render, this);
+ this.model.bind("add", this.render, this);
+ },
+
+ render:function (eventName) {
+ // console.log("rendering flow list view: " + this.model.models.length);
+ $(this.el).html(this.template({nflows:this.model.length}));
+ _.each(this.model.models, function (f) {
+ $(this.el).find('table.flow-table > tbody')
+ .append(new FlowListItemView({model:f}).render().el);
+ }, this);
+ return this;
+ },
+
+});
+
diff --git a/src/ext/floodlight/src/main/resources/web/js/views/header.js b/src/ext/floodlight/src/main/resources/web/js/views/header.js
new file mode 100644
index 0000000..a5fbe00
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/js/views/header.js
@@ -0,0 +1,46 @@
+/*
+ Copyright 2012 IBM
+
+ 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.
+*/
+
+window.HeaderView = Backbone.View.extend({
+
+ initialize:function () {
+ this.template = _.template(tpl.get('header'));
+ // this.searchResults = new HostCollection();
+ // this.searchresultsView = new SearchListView({model:this.searchResults, className:'dropdown-menu'});
+ },
+
+ render:function (eventName) {
+ $(this.el).html(this.template());
+ // $('.navbar-search', this.el).append(this.searchresultsView.render().el);
+ return this;
+ },
+
+ events:{
+ "keyup .search-query":"search"
+ },
+
+ search:function (event) {
+// var key = event.target.value;
+ var key = $('#searchText').val();
+ console.log('search ' + key);
+ // TODO search the host and switch lists
+ this.searchResults.findByName(key);
+ setTimeout(function () {
+ $('#searchForm').addClass('open');
+ });
+ }
+
+});
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/resources/web/js/views/home.js b/src/ext/floodlight/src/main/resources/web/js/views/home.js
new file mode 100644
index 0000000..0283002
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/js/views/home.js
@@ -0,0 +1,41 @@
+/*
+ Copyright 2012 IBM
+
+ 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.
+*/
+
+window.HomeView = Backbone.View.extend({
+
+ initialize:function () {
+ // console.log('Initializing Home View');
+ this.template = _.template(tpl.get('home'));
+ },
+
+ events:{
+ "click #showMeBtn":"showMeBtnClick"
+ },
+
+ render:function (eventName) {
+ $(this.el).html(this.template());
+ var stats = new Status();
+ $(this.el).find('#controller-status').html(new StatusView({model:stats}).render().el);
+ $(this.el).find('#switch-list').html(new SwitchListView({model:swl}).render().el);
+ $(this.el).find('#host-list').html(new HostListView({model:hl}).render().el);
+ return this;
+ },
+
+ showMeBtnClick:function () {
+ app.headerView.search();
+ }
+
+});
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/resources/web/js/views/host.js b/src/ext/floodlight/src/main/resources/web/js/views/host.js
new file mode 100644
index 0000000..5bb7b0e
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/js/views/host.js
@@ -0,0 +1,66 @@
+/*
+ Copyright 2012 IBM
+
+ 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.
+*/
+
+window.HostView = Backbone.View.extend({
+
+ initialize:function () {
+ this.template = _.template(tpl.get('host'));
+ this.model.bind("change", this.render, this);
+ this.model.bind("add", this.render, this);
+ },
+
+ render:function (eventName) {
+ $(this.el).html(this.template(this.model.toJSON()));
+ return this;
+ }
+
+});
+
+window.HostListView = Backbone.View.extend({
+
+ initialize:function () {
+ var self = this;
+ this.template = _.template(tpl.get('host-list'));
+ this.model.bind("change", this.render, this);
+ this.model.bind("add", this.render, this);
+ },
+
+ render:function (eventName) {
+ $(this.el).html(this.template({nhosts:hl.length}));
+ _.each(this.model.models, function (h) {
+ $(this.el).find('table.host-table > tbody')
+ .append(new HostListItemView({model:h}).render().el);
+ }, this);
+ return this;
+ }
+});
+
+window.HostListItemView = Backbone.View.extend({
+
+ tagName:"tr",
+
+ initialize:function () {
+ this.template = _.template(tpl.get('host-list-item'));
+ this.model.bind("change", this.render, this);
+ this.model.bind("destroy", this.close, this);
+ },
+
+ render:function (eventName) {
+ $(this.el).html(this.template(this.model.toJSON()));
+ return this;
+ }
+
+});
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/resources/web/js/views/port.js b/src/ext/floodlight/src/main/resources/web/js/views/port.js
new file mode 100644
index 0000000..e9aadb9
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/js/views/port.js
@@ -0,0 +1,70 @@
+/*
+ Copyright 2012 IBM
+
+ 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.
+*/
+
+// not used for now
+window.PortView = Backbone.View.extend({
+
+ initialize:function () {
+ this.template = _.template(tpl.get('port'));
+ this.model.bind("change", this.render, this);
+ //this.model.bind("destroy", this.close, this);
+ },
+
+ render:function (eventName) {
+ $(this.el).html(this.template(this.model.toJSON()));
+ return this;
+ }
+
+});
+
+window.PortListItemView = Backbone.View.extend({
+
+ tagName:"tr",
+
+ initialize:function () {
+ this.template = _.template(tpl.get('port-list-item'));
+ this.model.bind("change", this.render, this);
+ //this.model.bind("destroy", this.close, this);
+ },
+
+ render:function (eventName) {
+ $(this.el).html(this.template(this.model.toJSON()));
+ return this;
+ }
+
+});
+
+// TODO throughput sparklines would be nice here
+window.PortListView = Backbone.View.extend({
+
+ initialize:function () {
+ this.template = _.template(tpl.get('port-list'));
+ this.model.bind("change", this.render, this);
+ this.model.bind("add", this.render, this);
+ },
+
+ render:function (eventName) {
+ // console.log("rendering port list view");
+ $(this.el).html(this.template({nports:this.model.length}));
+ _.each(this.model.models, function (p) {
+ $(this.el).find('table.port-table > tbody')
+ .append(new PortListItemView({model:p}).render().el);
+ }, this);
+ return this;
+ },
+
+});
+
diff --git a/src/ext/floodlight/src/main/resources/web/js/views/status.js b/src/ext/floodlight/src/main/resources/web/js/views/status.js
new file mode 100644
index 0000000..52c6c1c
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/js/views/status.js
@@ -0,0 +1,30 @@
+/*
+ Copyright 2012 IBM
+
+ 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.
+*/
+
+window.StatusView = Backbone.View.extend({
+
+ initialize:function () {
+ this.template = _.template(tpl.get('status'));
+ this.model.bind("change", this.render, this);
+ },
+
+ render:function (eventName) {
+ // console.log("rendering status");
+ $(this.el).html(this.template(this.model.toJSON()));
+ //$(this.el).html(this.template());
+ return this;
+ }
+});
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/resources/web/js/views/switch.js b/src/ext/floodlight/src/main/resources/web/js/views/switch.js
new file mode 100644
index 0000000..178dd99
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/js/views/switch.js
@@ -0,0 +1,72 @@
+/*
+ Copyright 2012 IBM
+
+ 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.
+*/
+
+window.SwitchView = Backbone.View.extend({
+
+ initialize:function () {
+ this.template = _.template(tpl.get('switch'));
+ this.model.bind("change", this.render, this);
+ //this.model.bind("destroy", this.close, this);
+
+ // some parts of the model are large and are only needed in detail view
+ this.model.loadPorts();
+ this.model.loadFlows();
+ },
+
+ render:function (eventName) {
+ $(this.el).html(this.template(this.model.toJSON()));
+ $(this.el).find('#port-list').html(new PortListView({model:this.model.ports}).render().el);
+ $(this.el).find('#flow-list').html(new FlowListView({model:this.model.flows}).render().el);
+ return this;
+ }
+
+});
+
+window.SwitchListItemView = Backbone.View.extend({
+
+ tagName:"tr",
+
+ initialize:function () {
+ this.template = _.template(tpl.get('switch-list-item'));
+ this.model.bind("change", this.render, this);
+ //this.model.bind("destroy", this.close, this);
+ },
+
+ render:function (eventName) {
+ $(this.el).html(this.template(this.model.toJSON()));
+ return this;
+ }
+
+});
+
+window.SwitchListView = Backbone.View.extend({
+
+ initialize:function () {
+ this.template = _.template(tpl.get('switch-list'));
+ this.model.bind("change", this.render, this);
+ },
+
+ render:function (eventName) {
+ $(this.el).html(this.template({nswitches:swl.length}));
+ _.each(this.model.models, function (sw) {
+ $(this.el).find('table.switch-table > tbody')
+ .append(new SwitchListItemView({model:sw}).render().el);
+ }, this);
+ return this;
+ },
+
+});
+
diff --git a/src/ext/floodlight/src/main/resources/web/js/views/topology.js b/src/ext/floodlight/src/main/resources/web/js/views/topology.js
new file mode 100644
index 0000000..77129aa
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/js/views/topology.js
@@ -0,0 +1,111 @@
+/*
+ Copyright 2012 IBM
+
+ 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.
+*/
+
+window.TopologyView = Backbone.View.extend({
+ initialize:function () {
+ this.template = _.template(tpl.get('topology'));
+ this.model.bind("change", this.render, this);
+ this.hosts = this.options.hosts.models;
+ this.host_links = [];
+ },
+
+ render:function (eventName) {
+ $(this.el).html(this.template());
+ var width = 900,
+ height = 600;
+ var color = d3.scale.category20();
+ var force = d3.layout.force()
+ .charge(-500)
+ .linkDistance(200)
+ .size([width, height]);
+ var svg = d3.select("#topology-graph").append("svg")
+ .attr("width", width)
+ .attr("height", height);
+ if(this.model.nodes) {
+ for (var i = 0; i < this.model.nodes.length; i++) {
+ this.model.nodes[i].group = 1;
+ this.model.nodes[i].id = this.model.nodes[i].name;
+ }
+
+ for (var i = 0; i < this.hosts.length; i++) {
+ host = this.hosts[i];
+ if (host.attributes['ipv4'].length > 0) {
+ host.name = host.attributes['ipv4'][0] + "\n" + host.id;
+ } else {
+ host.name = host.id;
+ }
+ host.group = 2;
+ //console.log(host);
+ }
+
+ var all_nodes = this.model.nodes.concat(this.hosts);
+
+ var all_nodes_map = [];
+
+ _.each(all_nodes, function(n) {
+ all_nodes_map[n.id] = n;
+ });
+
+ for (var i = 0; i < this.hosts.length; i++) {
+ host = this.hosts[i];
+ //for (var j = 0; j < host.attributes['attachmentPoint'].length; j++) {
+ for (var j = 0; j < 1; j++) { // FIXME hack to ignore multiple APs
+ var link = {source:all_nodes_map[host.id],
+ target:all_nodes_map[host.attributes['attachmentPoint'][j]['switchDPID']],
+ value:10};
+ //console.log(link);
+ if ( link.source && link.target) {
+ this.host_links.push(link);
+ } else {
+ console.log("Error: skipping link with undefined stuff!")
+ }
+ }
+ }
+
+ var all_links = this.model.links.concat(this.host_links);
+
+ force.nodes(all_nodes).links(all_links).start();
+ var link = svg.selectAll("line.link").data(all_links).enter()
+ .append("line").attr("class", "link")
+ .style("stroke", function (d) { return "black"; });
+ var node = svg.selectAll(".node").data(all_nodes)
+ .enter().append("g")
+ .attr("class", "node")
+ .call(force.drag);
+
+ node.append("image")
+ .attr("xlink:href", function (d) {return d.group==1 ? "/ui/img/switch.png" : "/ui/img/server.png"})
+ .attr("x", -16).attr("y", -16)
+ .attr("width", 32).attr("height", 32);
+ node.append("text").attr("dx", 20).attr("dy", ".35em")
+ .text(function(d) { return d.name });
+ node.on("click", function (d) {
+ // TODO we could add some functionality here
+ console.log('clicked '+d.name);
+ });
+ node.append("title").text(function(d) { return d.name; });
+ force.on("tick", function() {
+ link.attr("x1", function(d) { return d.source.x; })
+ .attr("y1", function(d) { return d.source.y; })
+ .attr("x2", function(d) { return d.target.x; })
+ .attr("y2", function(d) { return d.target.y; });
+ node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
+
+ });
+ }
+ return this;
+ }
+});
diff --git a/src/ext/floodlight/src/main/resources/web/lib/backbone-min.js b/src/ext/floodlight/src/main/resources/web/lib/backbone-min.js
new file mode 100644
index 0000000..c1c0d4f
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/lib/backbone-min.js
@@ -0,0 +1,38 @@
+// Backbone.js 0.9.2
+
+// (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc.
+// Backbone may be freely distributed under the MIT license.
+// For all details and documentation:
+// http://backbonejs.org
+(function(){var l=this,y=l.Backbone,z=Array.prototype.slice,A=Array.prototype.splice,g;g="undefined"!==typeof exports?exports:l.Backbone={};g.VERSION="0.9.2";var f=l._;!f&&"undefined"!==typeof require&&(f=require("underscore"));var i=l.jQuery||l.Zepto||l.ender;g.setDomLibrary=function(a){i=a};g.noConflict=function(){l.Backbone=y;return this};g.emulateHTTP=!1;g.emulateJSON=!1;var p=/\s+/,k=g.Events={on:function(a,b,c){var d,e,f,g,j;if(!b)return this;a=a.split(p);for(d=this._callbacks||(this._callbacks=
+{});e=a.shift();)f=(j=d[e])?j.tail:{},f.next=g={},f.context=c,f.callback=b,d[e]={tail:g,next:j?j.next:f};return this},off:function(a,b,c){var d,e,h,g,j,q;if(e=this._callbacks){if(!a&&!b&&!c)return delete this._callbacks,this;for(a=a?a.split(p):f.keys(e);d=a.shift();)if(h=e[d],delete e[d],h&&(b||c))for(g=h.tail;(h=h.next)!==g;)if(j=h.callback,q=h.context,b&&j!==b||c&&q!==c)this.on(d,j,q);return this}},trigger:function(a){var b,c,d,e,f,g;if(!(d=this._callbacks))return this;f=d.all;a=a.split(p);for(g=
+z.call(arguments,1);b=a.shift();){if(c=d[b])for(e=c.tail;(c=c.next)!==e;)c.callback.apply(c.context||this,g);if(c=f){e=c.tail;for(b=[b].concat(g);(c=c.next)!==e;)c.callback.apply(c.context||this,b)}}return this}};k.bind=k.on;k.unbind=k.off;var o=g.Model=function(a,b){var c;a||(a={});b&&b.parse&&(a=this.parse(a));if(c=n(this,"defaults"))a=f.extend({},c,a);b&&b.collection&&(this.collection=b.collection);this.attributes={};this._escapedAttributes={};this.cid=f.uniqueId("c");this.changed={};this._silent=
+{};this._pending={};this.set(a,{silent:!0});this.changed={};this._silent={};this._pending={};this._previousAttributes=f.clone(this.attributes);this.initialize.apply(this,arguments)};f.extend(o.prototype,k,{changed:null,_silent:null,_pending:null,idAttribute:"id",initialize:function(){},toJSON:function(){return f.clone(this.attributes)},get:function(a){return this.attributes[a]},escape:function(a){var b;if(b=this._escapedAttributes[a])return b;b=this.get(a);return this._escapedAttributes[a]=f.escape(null==
+b?"":""+b)},has:function(a){return null!=this.get(a)},set:function(a,b,c){var d,e;f.isObject(a)||null==a?(d=a,c=b):(d={},d[a]=b);c||(c={});if(!d)return this;d instanceof o&&(d=d.attributes);if(c.unset)for(e in d)d[e]=void 0;if(!this._validate(d,c))return!1;this.idAttribute in d&&(this.id=d[this.idAttribute]);var b=c.changes={},h=this.attributes,g=this._escapedAttributes,j=this._previousAttributes||{};for(e in d){a=d[e];if(!f.isEqual(h[e],a)||c.unset&&f.has(h,e))delete g[e],(c.silent?this._silent:
+b)[e]=!0;c.unset?delete h[e]:h[e]=a;!f.isEqual(j[e],a)||f.has(h,e)!=f.has(j,e)?(this.changed[e]=a,c.silent||(this._pending[e]=!0)):(delete this.changed[e],delete this._pending[e])}c.silent||this.change(c);return this},unset:function(a,b){(b||(b={})).unset=!0;return this.set(a,null,b)},clear:function(a){(a||(a={})).unset=!0;return this.set(f.clone(this.attributes),a)},fetch:function(a){var a=a?f.clone(a):{},b=this,c=a.success;a.success=function(d,e,f){if(!b.set(b.parse(d,f),a))return!1;c&&c(b,d)};
+a.error=g.wrapError(a.error,b,a);return(this.sync||g.sync).call(this,"read",this,a)},save:function(a,b,c){var d,e;f.isObject(a)||null==a?(d=a,c=b):(d={},d[a]=b);c=c?f.clone(c):{};if(c.wait){if(!this._validate(d,c))return!1;e=f.clone(this.attributes)}a=f.extend({},c,{silent:!0});if(d&&!this.set(d,c.wait?a:c))return!1;var h=this,i=c.success;c.success=function(a,b,e){b=h.parse(a,e);if(c.wait){delete c.wait;b=f.extend(d||{},b)}if(!h.set(b,c))return false;i?i(h,a):h.trigger("sync",h,a,c)};c.error=g.wrapError(c.error,
+h,c);b=this.isNew()?"create":"update";b=(this.sync||g.sync).call(this,b,this,c);c.wait&&this.set(e,a);return b},destroy:function(a){var a=a?f.clone(a):{},b=this,c=a.success,d=function(){b.trigger("destroy",b,b.collection,a)};if(this.isNew())return d(),!1;a.success=function(e){a.wait&&d();c?c(b,e):b.trigger("sync",b,e,a)};a.error=g.wrapError(a.error,b,a);var e=(this.sync||g.sync).call(this,"delete",this,a);a.wait||d();return e},url:function(){var a=n(this,"urlRoot")||n(this.collection,"url")||t();
+return this.isNew()?a:a+("/"==a.charAt(a.length-1)?"":"/")+encodeURIComponent(this.id)},parse:function(a){return a},clone:function(){return new this.constructor(this.attributes)},isNew:function(){return null==this.id},change:function(a){a||(a={});var b=this._changing;this._changing=!0;for(var c in this._silent)this._pending[c]=!0;var d=f.extend({},a.changes,this._silent);this._silent={};for(c in d)this.trigger("change:"+c,this,this.get(c),a);if(b)return this;for(;!f.isEmpty(this._pending);){this._pending=
+{};this.trigger("change",this,a);for(c in this.changed)!this._pending[c]&&!this._silent[c]&&delete this.changed[c];this._previousAttributes=f.clone(this.attributes)}this._changing=!1;return this},hasChanged:function(a){return!arguments.length?!f.isEmpty(this.changed):f.has(this.changed,a)},changedAttributes:function(a){if(!a)return this.hasChanged()?f.clone(this.changed):!1;var b,c=!1,d=this._previousAttributes,e;for(e in a)if(!f.isEqual(d[e],b=a[e]))(c||(c={}))[e]=b;return c},previous:function(a){return!arguments.length||
+!this._previousAttributes?null:this._previousAttributes[a]},previousAttributes:function(){return f.clone(this._previousAttributes)},isValid:function(){return!this.validate(this.attributes)},_validate:function(a,b){if(b.silent||!this.validate)return!0;var a=f.extend({},this.attributes,a),c=this.validate(a,b);if(!c)return!0;b&&b.error?b.error(this,c,b):this.trigger("error",this,c,b);return!1}});var r=g.Collection=function(a,b){b||(b={});b.model&&(this.model=b.model);b.comparator&&(this.comparator=b.comparator);
+this._reset();this.initialize.apply(this,arguments);a&&this.reset(a,{silent:!0,parse:b.parse})};f.extend(r.prototype,k,{model:o,initialize:function(){},toJSON:function(a){return this.map(function(b){return b.toJSON(a)})},add:function(a,b){var c,d,e,g,i,j={},k={},l=[];b||(b={});a=f.isArray(a)?a.slice():[a];c=0;for(d=a.length;c<d;c++){if(!(e=a[c]=this._prepareModel(a[c],b)))throw Error("Can't add an invalid model to a collection");g=e.cid;i=e.id;j[g]||this._byCid[g]||null!=i&&(k[i]||this._byId[i])?
+l.push(c):j[g]=k[i]=e}for(c=l.length;c--;)a.splice(l[c],1);c=0;for(d=a.length;c<d;c++)(e=a[c]).on("all",this._onModelEvent,this),this._byCid[e.cid]=e,null!=e.id&&(this._byId[e.id]=e);this.length+=d;A.apply(this.models,[null!=b.at?b.at:this.models.length,0].concat(a));this.comparator&&this.sort({silent:!0});if(b.silent)return this;c=0;for(d=this.models.length;c<d;c++)if(j[(e=this.models[c]).cid])b.index=c,e.trigger("add",e,this,b);return this},remove:function(a,b){var c,d,e,g;b||(b={});a=f.isArray(a)?
+a.slice():[a];c=0;for(d=a.length;c<d;c++)if(g=this.getByCid(a[c])||this.get(a[c]))delete this._byId[g.id],delete this._byCid[g.cid],e=this.indexOf(g),this.models.splice(e,1),this.length--,b.silent||(b.index=e,g.trigger("remove",g,this,b)),this._removeReference(g);return this},push:function(a,b){a=this._prepareModel(a,b);this.add(a,b);return a},pop:function(a){var b=this.at(this.length-1);this.remove(b,a);return b},unshift:function(a,b){a=this._prepareModel(a,b);this.add(a,f.extend({at:0},b));return a},
+shift:function(a){var b=this.at(0);this.remove(b,a);return b},get:function(a){return null==a?void 0:this._byId[null!=a.id?a.id:a]},getByCid:function(a){return a&&this._byCid[a.cid||a]},at:function(a){return this.models[a]},where:function(a){return f.isEmpty(a)?[]:this.filter(function(b){for(var c in a)if(a[c]!==b.get(c))return!1;return!0})},sort:function(a){a||(a={});if(!this.comparator)throw Error("Cannot sort a set without a comparator");var b=f.bind(this.comparator,this);1==this.comparator.length?
+this.models=this.sortBy(b):this.models.sort(b);a.silent||this.trigger("reset",this,a);return this},pluck:function(a){return f.map(this.models,function(b){return b.get(a)})},reset:function(a,b){a||(a=[]);b||(b={});for(var c=0,d=this.models.length;c<d;c++)this._removeReference(this.models[c]);this._reset();this.add(a,f.extend({silent:!0},b));b.silent||this.trigger("reset",this,b);return this},fetch:function(a){a=a?f.clone(a):{};void 0===a.parse&&(a.parse=!0);var b=this,c=a.success;a.success=function(d,
+e,f){b[a.add?"add":"reset"](b.parse(d,f),a);c&&c(b,d)};a.error=g.wrapError(a.error,b,a);return(this.sync||g.sync).call(this,"read",this,a)},create:function(a,b){var c=this,b=b?f.clone(b):{},a=this._prepareModel(a,b);if(!a)return!1;b.wait||c.add(a,b);var d=b.success;b.success=function(e,f){b.wait&&c.add(e,b);d?d(e,f):e.trigger("sync",a,f,b)};a.save(null,b);return a},parse:function(a){return a},chain:function(){return f(this.models).chain()},_reset:function(){this.length=0;this.models=[];this._byId=
+{};this._byCid={}},_prepareModel:function(a,b){b||(b={});a instanceof o?a.collection||(a.collection=this):(b.collection=this,a=new this.model(a,b),a._validate(a.attributes,b)||(a=!1));return a},_removeReference:function(a){this==a.collection&&delete a.collection;a.off("all",this._onModelEvent,this)},_onModelEvent:function(a,b,c,d){("add"==a||"remove"==a)&&c!=this||("destroy"==a&&this.remove(b,d),b&&a==="change:"+b.idAttribute&&(delete this._byId[b.previous(b.idAttribute)],this._byId[b.id]=b),this.trigger.apply(this,
+arguments))}});f.each("forEach,each,map,reduce,reduceRight,find,detect,filter,select,reject,every,all,some,any,include,contains,invoke,max,min,sortBy,sortedIndex,toArray,size,first,initial,rest,last,without,indexOf,shuffle,lastIndexOf,isEmpty,groupBy".split(","),function(a){r.prototype[a]=function(){return f[a].apply(f,[this.models].concat(f.toArray(arguments)))}});var u=g.Router=function(a){a||(a={});a.routes&&(this.routes=a.routes);this._bindRoutes();this.initialize.apply(this,arguments)},B=/:\w+/g,
+C=/\*\w+/g,D=/[-[\]{}()+?.,\\^$|#\s]/g;f.extend(u.prototype,k,{initialize:function(){},route:function(a,b,c){g.history||(g.history=new m);f.isRegExp(a)||(a=this._routeToRegExp(a));c||(c=this[b]);g.history.route(a,f.bind(function(d){d=this._extractParameters(a,d);c&&c.apply(this,d);this.trigger.apply(this,["route:"+b].concat(d));g.history.trigger("route",this,b,d)},this));return this},navigate:function(a,b){g.history.navigate(a,b)},_bindRoutes:function(){if(this.routes){var a=[],b;for(b in this.routes)a.unshift([b,
+this.routes[b]]);b=0;for(var c=a.length;b<c;b++)this.route(a[b][0],a[b][1],this[a[b][1]])}},_routeToRegExp:function(a){a=a.replace(D,"\\$&").replace(B,"([^/]+)").replace(C,"(.*?)");return RegExp("^"+a+"$")},_extractParameters:function(a,b){return a.exec(b).slice(1)}});var m=g.History=function(){this.handlers=[];f.bindAll(this,"checkUrl")},s=/^[#\/]/,E=/msie [\w.]+/;m.started=!1;f.extend(m.prototype,k,{interval:50,getHash:function(a){return(a=(a?a.location:window.location).href.match(/#(.*)$/))?a[1]:
+""},getFragment:function(a,b){if(null==a)if(this._hasPushState||b){var a=window.location.pathname,c=window.location.search;c&&(a+=c)}else a=this.getHash();a.indexOf(this.options.root)||(a=a.substr(this.options.root.length));return a.replace(s,"")},start:function(a){if(m.started)throw Error("Backbone.history has already been started");m.started=!0;this.options=f.extend({},{root:"/"},this.options,a);this._wantsHashChange=!1!==this.options.hashChange;this._wantsPushState=!!this.options.pushState;this._hasPushState=
+!(!this.options.pushState||!window.history||!window.history.pushState);var a=this.getFragment(),b=document.documentMode;if(b=E.exec(navigator.userAgent.toLowerCase())&&(!b||7>=b))this.iframe=i('<iframe src="javascript:0" tabindex="-1" />').hide().appendTo("body")[0].contentWindow,this.navigate(a);this._hasPushState?i(window).bind("popstate",this.checkUrl):this._wantsHashChange&&"onhashchange"in window&&!b?i(window).bind("hashchange",this.checkUrl):this._wantsHashChange&&(this._checkUrlInterval=setInterval(this.checkUrl,
+this.interval));this.fragment=a;a=window.location;b=a.pathname==this.options.root;if(this._wantsHashChange&&this._wantsPushState&&!this._hasPushState&&!b)return this.fragment=this.getFragment(null,!0),window.location.replace(this.options.root+"#"+this.fragment),!0;this._wantsPushState&&this._hasPushState&&b&&a.hash&&(this.fragment=this.getHash().replace(s,""),window.history.replaceState({},document.title,a.protocol+"//"+a.host+this.options.root+this.fragment));if(!this.options.silent)return this.loadUrl()},
+stop:function(){i(window).unbind("popstate",this.checkUrl).unbind("hashchange",this.checkUrl);clearInterval(this._checkUrlInterval);m.started=!1},route:function(a,b){this.handlers.unshift({route:a,callback:b})},checkUrl:function(){var a=this.getFragment();a==this.fragment&&this.iframe&&(a=this.getFragment(this.getHash(this.iframe)));if(a==this.fragment)return!1;this.iframe&&this.navigate(a);this.loadUrl()||this.loadUrl(this.getHash())},loadUrl:function(a){var b=this.fragment=this.getFragment(a);return f.any(this.handlers,
+function(a){if(a.route.test(b))return a.callback(b),!0})},navigate:function(a,b){if(!m.started)return!1;if(!b||!0===b)b={trigger:b};var c=(a||"").replace(s,"");this.fragment!=c&&(this._hasPushState?(0!=c.indexOf(this.options.root)&&(c=this.options.root+c),this.fragment=c,window.history[b.replace?"replaceState":"pushState"]({},document.title,c)):this._wantsHashChange?(this.fragment=c,this._updateHash(window.location,c,b.replace),this.iframe&&c!=this.getFragment(this.getHash(this.iframe))&&(b.replace||
+this.iframe.document.open().close(),this._updateHash(this.iframe.location,c,b.replace))):window.location.assign(this.options.root+a),b.trigger&&this.loadUrl(a))},_updateHash:function(a,b,c){c?a.replace(a.toString().replace(/(javascript:|#).*$/,"")+"#"+b):a.hash=b}});var v=g.View=function(a){this.cid=f.uniqueId("view");this._configure(a||{});this._ensureElement();this.initialize.apply(this,arguments);this.delegateEvents()},F=/^(\S+)\s*(.*)$/,w="model,collection,el,id,attributes,className,tagName".split(",");
+f.extend(v.prototype,k,{tagName:"div",$:function(a){return this.$el.find(a)},initialize:function(){},render:function(){return this},remove:function(){this.$el.remove();return this},make:function(a,b,c){a=document.createElement(a);b&&i(a).attr(b);c&&i(a).html(c);return a},setElement:function(a,b){this.$el&&this.undelegateEvents();this.$el=a instanceof i?a:i(a);this.el=this.$el[0];!1!==b&&this.delegateEvents();return this},delegateEvents:function(a){if(a||(a=n(this,"events"))){this.undelegateEvents();
+for(var b in a){var c=a[b];f.isFunction(c)||(c=this[a[b]]);if(!c)throw Error('Method "'+a[b]+'" does not exist');var d=b.match(F),e=d[1],d=d[2],c=f.bind(c,this),e=e+(".delegateEvents"+this.cid);""===d?this.$el.bind(e,c):this.$el.delegate(d,e,c)}}},undelegateEvents:function(){this.$el.unbind(".delegateEvents"+this.cid)},_configure:function(a){this.options&&(a=f.extend({},this.options,a));for(var b=0,c=w.length;b<c;b++){var d=w[b];a[d]&&(this[d]=a[d])}this.options=a},_ensureElement:function(){if(this.el)this.setElement(this.el,
+!1);else{var a=n(this,"attributes")||{};this.id&&(a.id=this.id);this.className&&(a["class"]=this.className);this.setElement(this.make(this.tagName,a),!1)}}});o.extend=r.extend=u.extend=v.extend=function(a,b){var c=G(this,a,b);c.extend=this.extend;return c};var H={create:"POST",update:"PUT","delete":"DELETE",read:"GET"};g.sync=function(a,b,c){var d=H[a];c||(c={});var e={type:d,dataType:"json"};c.url||(e.url=n(b,"url")||t());if(!c.data&&b&&("create"==a||"update"==a))e.contentType="application/json",
+e.data=JSON.stringify(b.toJSON());g.emulateJSON&&(e.contentType="application/x-www-form-urlencoded",e.data=e.data?{model:e.data}:{});if(g.emulateHTTP&&("PUT"===d||"DELETE"===d))g.emulateJSON&&(e.data._method=d),e.type="POST",e.beforeSend=function(a){a.setRequestHeader("X-HTTP-Method-Override",d)};"GET"!==e.type&&!g.emulateJSON&&(e.processData=!1);return i.ajax(f.extend(e,c))};g.wrapError=function(a,b,c){return function(d,e){e=d===b?e:d;a?a(b,e,c):b.trigger("error",b,e,c)}};var x=function(){},G=function(a,
+b,c){var d;d=b&&b.hasOwnProperty("constructor")?b.constructor:function(){a.apply(this,arguments)};f.extend(d,a);x.prototype=a.prototype;d.prototype=new x;b&&f.extend(d.prototype,b);c&&f.extend(d,c);d.prototype.constructor=d;d.__super__=a.prototype;return d},n=function(a,b){return!a||!a[b]?null:f.isFunction(a[b])?a[b]():a[b]},t=function(){throw Error('A "url" property or function must be specified');}}).call(this);
diff --git a/src/ext/floodlight/src/main/resources/web/lib/bootstrap-alert.js b/src/ext/floodlight/src/main/resources/web/lib/bootstrap-alert.js
new file mode 100644
index 0000000..d17f44e
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/lib/bootstrap-alert.js
@@ -0,0 +1,94 @@
+/* ==========================================================
+ * bootstrap-alert.js v2.0.2
+ * http://twitter.github.com/bootstrap/javascript.html#alerts
+ * ==========================================================
+ * Copyright 2012 Twitter, 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.
+ * ========================================================== */
+
+
+!function( $ ){
+
+ "use strict"
+
+ /* ALERT CLASS DEFINITION
+ * ====================== */
+
+ var dismiss = '[data-dismiss="alert"]'
+ , Alert = function ( el ) {
+ $(el).on('click', dismiss, this.close)
+ }
+
+ Alert.prototype = {
+
+ constructor: Alert
+
+ , close: function ( e ) {
+ var $this = $(this)
+ , selector = $this.attr('data-target')
+ , $parent
+
+ if (!selector) {
+ selector = $this.attr('href')
+ selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7
+ }
+
+ $parent = $(selector)
+ $parent.trigger('close')
+
+ e && e.preventDefault()
+
+ $parent.length || ($parent = $this.hasClass('alert') ? $this : $this.parent())
+
+ $parent
+ .trigger('close')
+ .removeClass('in')
+
+ function removeElement() {
+ $parent
+ .trigger('closed')
+ .remove()
+ }
+
+ $.support.transition && $parent.hasClass('fade') ?
+ $parent.on($.support.transition.end, removeElement) :
+ removeElement()
+ }
+
+ }
+
+
+ /* ALERT PLUGIN DEFINITION
+ * ======================= */
+
+ $.fn.alert = function ( option ) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('alert')
+ if (!data) $this.data('alert', (data = new Alert(this)))
+ if (typeof option == 'string') data[option].call($this)
+ })
+ }
+
+ $.fn.alert.Constructor = Alert
+
+
+ /* ALERT DATA-API
+ * ============== */
+
+ $(function () {
+ $('body').on('click.alert.data-api', dismiss, Alert.prototype.close)
+ })
+
+}( window.jQuery );
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/resources/web/lib/bootstrap-dropdown.js b/src/ext/floodlight/src/main/resources/web/lib/bootstrap-dropdown.js
new file mode 100755
index 0000000..48d3ce0
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/lib/bootstrap-dropdown.js
@@ -0,0 +1,92 @@
+/* ============================================================
+ * bootstrap-dropdown.js v2.0.0
+ * http://twitter.github.com/bootstrap/javascript.html#dropdowns
+ * ============================================================
+ * Copyright 2012 Twitter, 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.
+ * ============================================================ */
+
+
+!function( $ ){
+
+ "use strict"
+
+ /* DROPDOWN CLASS DEFINITION
+ * ========================= */
+
+ var toggle = '[data-toggle="dropdown"]'
+ , Dropdown = function ( element ) {
+ var $el = $(element).on('click.dropdown.data-api', this.toggle)
+ $('html').on('click.dropdown.data-api', function () {
+ $el.parent().removeClass('open')
+ })
+ }
+
+ Dropdown.prototype = {
+
+ constructor: Dropdown
+
+ , toggle: function ( e ) {
+ var $this = $(this)
+ , selector = $this.attr('data-target')
+ , $parent
+ , isActive
+
+ if (!selector) {
+ selector = $this.attr('href')
+ selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7
+ }
+
+ $parent = $(selector)
+ $parent.length || ($parent = $this.parent())
+
+ isActive = $parent.hasClass('open')
+
+ clearMenus()
+ !isActive && $parent.toggleClass('open')
+
+ return false
+ }
+
+ }
+
+ function clearMenus() {
+ $(toggle).parent().removeClass('open')
+ }
+
+
+ /* DROPDOWN PLUGIN DEFINITION
+ * ========================== */
+
+ $.fn.dropdown = function ( option ) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('dropdown')
+ if (!data) $this.data('dropdown', (data = new Dropdown(this)))
+ if (typeof option == 'string') data[option].call($this)
+ })
+ }
+
+ $.fn.dropdown.Constructor = Dropdown
+
+
+ /* APPLY TO STANDARD DROPDOWN ELEMENTS
+ * =================================== */
+
+ $(function () {
+ $('html').on('click.dropdown.data-api', clearMenus)
+ $('body').on('click.dropdown.data-api', toggle, Dropdown.prototype.toggle)
+ })
+
+}( window.jQuery )
diff --git a/src/ext/floodlight/src/main/resources/web/lib/d3.v2.min.js b/src/ext/floodlight/src/main/resources/web/lib/d3.v2.min.js
new file mode 100644
index 0000000..dddd929
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/lib/d3.v2.min.js
@@ -0,0 +1,4 @@
+(function(){function e(a,b){try{for(var c in b)Object.defineProperty(a.prototype,c,{value:b[c],enumerable:!1})}catch(d){a.prototype=b}}function g(a){var b=-1,c=a.length,d=[];while(++b<c)d.push(a[b]);return d}function h(a){return Array.prototype.slice.call(a)}function k(){}function n(){return this}function o(a,b,c){return function(){var d=c.apply(b,arguments);return arguments.length?a:d}}function p(a){return a!=null&&!isNaN(a)}function q(a){return a.length}function s(a){return a==null}function t(a){return a.replace(/(^\s+)|(\s+$)/g,"").replace(/\s+/g," ")}function u(a){var b=1;while(a*b%1)b*=10;return b}function x(){}function y(a){function d(){var c=b,d=-1,e=c.length,f;while(++d<e)(f=c[d].on)&&f.apply(this,arguments);return a}var b=[],c=new k;return d.on=function(d,e){var f=c.get(d),g;return arguments.length<2?f&&f.on:(f&&(f.on=null,b=b.slice(0,g=b.indexOf(f)).concat(b.slice(g+1)),c.remove(d)),e&&b.push(c.set(d,{on:e})),a)},d}function B(a,b){return b-(a?1+Math.floor(Math.log(a+Math.pow(10,1+Math.floor(Math.log(a)/Math.LN10)-b))/Math.LN10):1)}function C(a){return a+""}function D(a){var b=a.lastIndexOf("."),c=b>=0?a.substring(b):(b=a.length,""),d=[];while(b>0)d.push(a.substring(b-=3,b+3));return d.reverse().join(",")+c}function F(a,b){return{scale:Math.pow(10,(8-b)*3),symbol:a}}function L(a){return function(b){return b<=0?0:b>=1?1:a(b)}}function M(a){return function(b){return 1-a(1-b)}}function N(a){return function(b){return.5*(b<.5?a(2*b):2-a(2-2*b))}}function O(a){return a}function P(a){return function(b){return Math.pow(b,a)}}function Q(a){return 1-Math.cos(a*Math.PI/2)}function R(a){return Math.pow(2,10*(a-1))}function S(a){return 1-Math.sqrt(1-a*a)}function T(a,b){var c;return arguments.length<2&&(b=.45),arguments.length<1?(a=1,c=b/4):c=b/(2*Math.PI)*Math.asin(1/a),function(d){return 1+a*Math.pow(2,10*-d)*Math.sin((d-c)*2*Math.PI/b)}}function U(a){return a||(a=1.70158),function(b){return b*b*((a+1)*b-a)}}function V(a){return a<1/2.75?7.5625*a*a:a<2/2.75?7.5625*(a-=1.5/2.75)*a+.75:a<2.5/2.75?7.5625*(a-=2.25/2.75)*a+.9375:7.5625*(a-=2.625/2.75)*a+.984375}function W(){d3.event.stopPropagation(),d3.event.preventDefault()}function X(){var a=d3.event,b;while(b=a.sourceEvent)a=b;return a}function Y(a){var b=new x,c=0,d=arguments.length;while(++c<d)b[arguments[c]]=y(b);return b.of=function(c,d){return function(e){try{var f=e.sourceEvent=d3.event;e.target=a,d3.event=e,b[e.type].apply(c,d)}finally{d3.event=f}}},b}function $(a){return a=="transform"?d3.interpolateTransform:d3.interpolate}function _(a,b){return b=b-(a=+a)?1/(b-a):0,function(c){return(c-a)*b}}function ba(a,b){return b=b-(a=+a)?1/(b-a):0,function(c){return Math.max(0,Math.min(1,(c-a)*b))}}function bb(a,b,c){return new bc(a,b,c)}function bc(a,b,c){this.r=a,this.g=b,this.b=c}function bd(a){return a<16?"0"+Math.max(0,a).toString(16):Math.min(255,a).toString(16)}function be(a,b,c){var d=0,e=0,f=0,g,h,i;g=/([a-z]+)\((.*)\)/i.exec(a);if(g){h=g[2].split(",");switch(g[1]){case"hsl":return c(parseFloat(h[0]),parseFloat(h[1])/100,parseFloat(h[2])/100);case"rgb":return b(bg(h[0]),bg(h[1]),bg(h[2]))}}return(i=bh.get(a))?b(i.r,i.g,i.b):(a!=null&&a.charAt(0)==="#"&&(a.length===4?(d=a.charAt(1),d+=d,e=a.charAt(2),e+=e,f=a.charAt(3),f+=f):a.length===7&&(d=a.substring(1,3),e=a.substring(3,5),f=a.substring(5,7)),d=parseInt(d,16),e=parseInt(e,16),f=parseInt(f,16)),b(d,e,f))}function bf(a,b,c){var d=Math.min(a/=255,b/=255,c/=255),e=Math.max(a,b,c),f=e-d,g,h,i=(e+d)/2;return f?(h=i<.5?f/(e+d):f/(2-e-d),a==e?g=(b-c)/f+(b<c?6:0):b==e?g=(c-a)/f+2:g=(a-b)/f+4,g*=60):h=g=0,bi(g,h,i)}function bg(a){var b=parseFloat(a);return a.charAt(a.length-1)==="%"?Math.round(b*2.55):b}function bi(a,b,c){return new bj(a,b,c)}function bj(a,b,c){this.h=a,this.s=b,this.l=c}function bk(a,b,c){function f(a){return a>360?a-=360:a<0&&(a+=360),a<60?d+(e-d)*a/60:a<180?e:a<240?d+(e-d)*(240-a)/60:d}function g(a){return Math.round(f(a)*255)}var d,e;return a%=360,a<0&&(a+=360),b=b<0?0:b>1?1:b,c=c<0?0:c>1?1:c,e=c<=.5?c*(1+b):c+b-c*b,d=2*c-e,bb(g(a+120),g(a),g(a-120))}function bl(a){return j(a,br),a}function bs(a){return function(){return bm(a,this)}}function bt(a){return function(){return bn(a,this)}}function bv(a,b){function f(){if(b=this.classList)return b.add(a);var b=this.className,d=b.baseVal!=null,e=d?b.baseVal:b;c.lastIndex=0,c.test(e)||(e=t(e+" "+a),d?b.baseVal=e:this.className=e)}function g(){if(b=this.classList)return b.remove(a);var b=this.className,d=b.baseVal!=null,e=d?b.baseVal:b;e=t(e.replace(c," ")),d?b.baseVal=e:this.className=e}function h(){(b.apply(this,arguments)?f:g).call(this)}var c=new RegExp("(^|\\s+)"+d3.requote(a)+"(\\s+|$)","g");if(arguments.length<2){var d=this.node();if(e=d.classList)return e.contains(a);var e=d.className;return c.lastIndex=0,c.test(e.baseVal!=null?e.baseVal:e)}return this.each(typeof b=="function"?h:b?f:g)}function bw(a){return{__data__:a}}function bx(a){return function(){return bq(this,a)}}function by(a){return arguments.length||(a=d3.ascending),function(b,c){return a(b&&b.__data__,c&&c.__data__)}}function bA(a){return j(a,bB),a}function bC(a,b,c){j(a,bG);var d=new k,e=d3.dispatch("start","end"),f=bO;return a.id=b,a.time=c,a.tween=function(b,c){return arguments.length<2?d.get(b):(c==null?d.remove(b):d.set(b,c),a)},a.ease=function(b){return arguments.length?(f=typeof b=="function"?b:d3.ease.apply(d3,arguments),a):f},a.each=function(b,c){return arguments.length<2?bP.call(a,b):(e.on(b,c),a)},d3.timer(function(g){return a.each(function(h,i,j){function p(a){return o.active>b?r():(o.active=b,d.forEach(function(a,b){(tween=b.call(l,h,i))&&k.push(tween)}),e.start.call(l,h,i),q(a)||d3.timer(q,0,c),1)}function q(a){if(o.active!==b)return r();var c=(a-m)/n,d=f(c),g=k.length;while(g>0)k[--g].call(l,d);if(c>=1)return r(),bI=b,e.end.call(l,h,i),bI=0,1}function r(){return--o.count||delete l.__transition__,1}var k=[],l=this,m=a[j][i].delay,n=a[j][i].duration,o=l.__transition__||(l.__transition__={active:0,count:0});++o.count,m<=g?p(g):d3.timer(p,m,c)}),1},0,c),a}function bE(a,b,c){return c!=""&&bD}function bF(a,b){function d(a,d,e){var f=b.call(this,a,d);return f==null?e!=""&&bD:e!=f&&c(e,f)}function e(a,d,e){return e!=b&&c(e,b)}var c=$(a);return typeof b=="function"?d:b==null?bE:(b+="",e)}function bP(a){var b=bI,c=bO,d=bM,e=bN;bI=this.id,bO=this.ease();for(var f=0,g=this.length;f<g;f++)for(var h=this[f],i=0,j=h.length;i<j;i++){var k=h[i];k&&(bM=this[f][i].delay,bN=this[f][i].duration,a.call(k=k.node,k.__data__,i,f))}return bI=b,bO=c,bM=d,bN=e,this}function bT(){var a,b=Date.now(),c=bQ;while(c)a=b-c.then,a>=c.delay&&(c.flush=c.callback(a)),c=c.next;var d=bU()-b;d>24?(isFinite(d)&&(clearTimeout(bS),bS=setTimeout(bT,d)),bR=0):(bR=1,bV(bT))}function bU(){var a=null,b=bQ,c=Infinity;while(b)b.flush?b=a?a.next=b.next:bQ=b.next:(c=Math.min(c,b.then+b.delay),b=(a=b).next);return c}function bW(a){var b=[a.a,a.b],c=[a.c,a.d],d=bY(b),e=bX(b,c),f=bY(bZ(c,b,-e))||0;b[0]*c[1]<c[0]*b[1]&&(b[0]*=-1,b[1]*=-1,d*=-1,e*=-1),this.rotate=(d?Math.atan2(b[1],b[0]):Math.atan2(-c[0],c[1]))*b$,this.translate=[a.e,a.f],this.scale=[d,f],this.skew=f?Math.atan2(e,f)*b$:0}function bX(a,b){return a[0]*b[0]+a[1]*b[1]}function bY(a){var b=Math.sqrt(bX(a,a));return b&&(a[0]/=b,a[1]/=b),b}function bZ(a,b,c){return a[0]+=c*b[0],a[1]+=c*b[1],a}function ca(a,b){var c=a.ownerSVGElement||a;if(c.createSVGPoint){var d=c.createSVGPoint();if(b_<0&&(window.scrollX||window.scrollY)){c=d3.select(document.body).append("svg").style("position","absolute").style("top",0).style("left",0);var e=c[0][0].getScreenCTM();b_=!e.f&&!e.e,c.remove()}return b_?(d.x=b.pageX,d.y=b.pageY):(d.x=b.clientX,d.y=b.clientY),d=d.matrixTransform(a.getScreenCTM().inverse()),[d.x,d.y]}var f=a.getBoundingClientRect();return[b.clientX-f.left-a.clientLeft,b.clientY-f.top-a.clientTop]}function cb(){}function cc(a){var b=a[0],c=a[a.length-1];return b<c?[b,c]:[c,b]}function cd(a){return a.rangeExtent?a.rangeExtent():cc(a.range())}function ce(a,b){var c=0,d=a.length-1,e=a[c],f=a[d],g;f<e&&(g=c,c=d,d=g,g=e,e=f,f=g);if(g=f-e)b=b(g),a[c]=b.floor(e),a[d]=b.ceil(f);return a}function cf(){return Math}function cg(a,b,c,d){function g(){var g=Math.min(a.length,b.length)>2?cn:cm,i=d?ba:_;return e=g(a,b,i,c),f=g(b,a,i,d3.interpolate),h}function h(a){return e(a)}var e,f;return h.invert=function(a){return f(a)},h.domain=function(b){return arguments.length?(a=b.map(Number),g()):a},h.range=function(a){return arguments.length?(b=a,g()):b},h.rangeRound=function(a){return h.range(a).interpolate(d3.interpolateRound)},h.clamp=function(a){return arguments.length?(d=a,g()):d},h.interpolate=function(a){return arguments.length?(c=a,g()):c},h.ticks=function(b){return ck(a,b)},h.tickFormat=function(b){return cl(a,b)},h.nice=function(){return ce(a,ci),g()},h.copy=function(){return cg(a,b,c,d)},g()}function ch(a,b){return d3.rebind(a,b,"range","rangeRound","interpolate","clamp")}function ci(a){return a=Math.pow(10,Math.round(Math.log(a)/Math.LN10)-1),{floor:function(b){return Math.floor(b/a)*a},ceil:function(b){return Math.ceil(b/a)*a}}}function cj(a,b){var c=cc(a),d=c[1]-c[0],e=Math.pow(10,Math.floor(Math.log(d/b)/Math.LN10)),f=b/d*e;return f<=.15?e*=10:f<=.35?e*=5:f<=.75&&(e*=2),c[0]=Math.ceil(c[0]/e)*e,c[1]=Math.floor(c[1]/e)*e+e*.5,c[2]=e,c}function ck(a,b){return d3.range.apply(d3,cj(a,b))}function cl(a,b){return d3.format(",."+Math.max(0,-Math.floor(Math.log(cj(a,b)[2])/Math.LN10+.01))+"f")}function cm(a,b,c,d){var e=c(a[0],a[1]),f=d(b[0],b[1]);return function(a){return f(e(a))}}function cn(a,b,c,d){var e=[],f=[],g=0,h=Math.min(a.length,b.length)-1;a[h]<a[0]&&(a=a.slice().reverse(),b=b.slice().reverse());while(++g<=h)e.push(c(a[g-1],a[g])),f.push(d(b[g-1],b[g]));return function(b){var c=d3.bisect(a,b,1,h)-1;return f[c](e[c](b))}}function co(a,b){function d(c){return a(b(c))}var c=b.pow;return d.invert=function(b){return c(a.invert(b))},d.domain=function(e){return arguments.length?(b=e[0]<0?cr:cq,c=b.pow,a.domain(e.map(b)),d):a.domain().map(c)},d.nice=function(){return a.domain(ce(a.domain(),cf)),d},d.ticks=function(){var d=cc(a.domain()),e=[];if(d.every(isFinite)){var f=Math.floor(d[0]),g=Math.ceil(d[1]),h=c(d[0]),i=c(d[1]);if(b===cr){e.push(c(f));for(;f++<g;)for(var j=9;j>0;j--)e.push(c(f)*j)}else{for(;f<g;f++)for(var j=1;j<10;j++)e.push(c(f)*j);e.push(c(f))}for(f=0;e[f]<h;f++);for(g=e.length;e[g-1]>i;g--);e=e.slice(f,g)}return e},d.tickFormat=function(a,e){arguments.length<2&&(e=cp);if(arguments.length<1)return e;var f=a/d.ticks().length,g=b===cr?(h=-1e-12,Math.floor):(h=1e-12,Math.ceil),h;return function(a){return a/c(g(b(a)+h))<f?e(a):""}},d.copy=function(){return co(a.copy(),b)},ch(d,a)}function cq(a){return Math.log(a<0?0:a)/Math.LN10}function cr(a){return-Math.log(a>0?0:-a)/Math.LN10}function cs(a,b){function e(b){return a(c(b))}var c=ct(b),d=ct(1/b);return e.invert=function(b){return d(a.invert(b))},e.domain=function(b){return arguments.length?(a.domain(b.map(c)),e):a.domain().map(d)},e.ticks=function(a){return ck(e.domain(),a)},e.tickFormat=function(a){return cl(e.domain(),a)},e.nice=function(){return e.domain(ce(e.domain(),ci))},e.exponent=function(a){if(!arguments.length)return b;var f=e.domain();return c=ct(b=a),d=ct(1/b),e.domain(f)},e.copy=function(){return cs(a.copy(),b)},ch(e,a)}function ct(a){return function(b){return b<0?-Math.pow(-b,a):Math.pow(b,a)}}function cu(a,b){function f(b){return d[((c.get(b)||c.set(b,a.push(b)))-1)%d.length]}function g(b,c){return d3.range(a.length).map(function(a){return b+c*a})}var c,d,e;return f.domain=function(d){if(!arguments.length)return a;a=[],c=new k;var e=-1,g=d.length,h;while(++e<g)c.has(h=d[e])||c.set(h,a.push(h));return f[b.t](b.x,b.p)},f.range=function(a){return arguments.length?(d=a,e=0,b={t:"range",x:a},f):d},f.rangePoints=function(c,h){arguments.length<2&&(h=0);var i=c[0],j=c[1],k=(j-i)/(a.length-1+h);return d=g(a.length<2?(i+j)/2:i+k*h/2,k),e=0,b={t:"rangePoints",x:c,p:h},f},f.rangeBands=function(c,h){arguments.length<2&&(h=0);var i=c[1]<c[0],j=c[i-0],k=c[1-i],l=(k-j)/(a.length+h);return d=g(j+l*h,l),i&&d.reverse(),e=l*(1-h),b={t:"rangeBands",x:c,p:h},f},f.rangeRoundBands=function(c,h){arguments.length<2&&(h=0);var i=c[1]<c[0],j=c[i-0],k=c[1-i],l=Math.floor((k-j)/(a.length+h)),m=k-j-(a.length-h)*l;return d=g(j+Math.round(m/2),l),i&&d.reverse(),e=Math.round(l*(1-h)),b={t:"rangeRoundBands",x:c,p:h},f},f.rangeBand=function(){return e},f.rangeExtent=function(){return cc(b.x)},f.copy=function(){return cu(a,b)},f.domain(a)}function cz(a,b){function d(){var d=0,f=a.length,g=b.length;c=[];while(++d<g)c[d-1]=d3.quantile(a,d/g);return e}function e(a){return isNaN(a=+a)?NaN:b[d3.bisect(c,a)]}var c;return e.domain=function(b){return arguments.length?(a=b.filter(function(a){return!isNaN(a)}).sort(d3.ascending),d()):a},e.range=function(a){return arguments.length?(b=a,d()):b},e.quantiles=function(){return c},e.copy=function(){return cz(a,b)},d()}function cA(a,b,c){function f(b){return c[Math.max(0,Math.min(e,Math.floor(d*(b-a))))]}function g(){return d=c.length/(b-a),e=c.length-1,f}var d,e;return f.domain=function(c){return arguments.length?(a=+c[0],b=+c[c.length-1],g()):[a,b]},f.range=function(a){return arguments.length?(c=a,g()):c},f.copy=function(){return cA(a,b,c)},g()}function cB(a){function b(a){return+a}return b.invert=b,b.domain=b.range=function(c){return arguments.length?(a=c.map(b),b):a},b.ticks=function(b){return ck(a,b)},b.tickFormat=function(b){return cl(a,b)},b.copy=function(){return cB(a)},b}function cE(a){return a.innerRadius}function cF(a){return a.outerRadius}function cG(a){return a.startAngle}function cH(a){return a.endAngle}function cI(a){function g(d){return d.length<1?null:"M"+e(a(cJ(this,d,b,c)),f)}var b=cK,c=cL,d=cM,e=cN.get(d),f=.7;return g.x=function(a){return arguments.length?(b=a,g):b},g.y=function(a){return arguments.length?(c=a,g):c},g.interpolate=function(a){return arguments.length?(cN.has(a+="")||(a=cM),e=cN.get(d=a),g):d},g.tension=function(a){return arguments.length?(f=a,g):f},g}function cJ(a,b,c,d){var e=[],f=-1,g=b.length,h=typeof c=="function",i=typeof d=="function",j;if(h&&i)while(++f<g)e.push([c.call(a,j=b[f],f),d.call(a,j,f)]);else if(h)while(++f<g)e.push([c.call(a,b[f],f),d]);else if(i)while(++f<g)e.push([c,d.call(a,b[f],f)]);else while(++f<g)e.push([c,d]);return e}function cK(a){return a[0]}function cL(a){return a[1]}function cO(a){var b=0,c=a.length,d=a[0],e=[d[0],",",d[1]];while(++b<c)e.push("L",(d=a[b])[0],",",d[1]);return e.join("")}function cP(a){var b=0,c=a.length,d=a[0],e=[d[0],",",d[1]];while(++b<c)e.push("V",(d=a[b])[1],"H",d[0]);return e.join("")}function cQ(a){var b=0,c=a.length,d=a[0],e=[d[0],",",d[1]];while(++b<c)e.push("H",(d=a[b])[0],"V",d[1]);return e.join("")}function cR(a,b){return a.length<4?cO(a):a[1]+cU(a.slice(1,a.length-1),cV(a,b))}function cS(a,b){return a.length<3?cO(a):a[0]+cU((a.push(a[0]),a),cV([a[a.length-2]].concat(a,[a[1]]),b))}function cT(a,b,c){return a.length<3?cO(a):a[0]+cU(a,cV(a,b))}function cU(a,b){if(b.length<1||a.length!=b.length&&a.length!=b.length+2)return cO(a);var c=a.length!=b.length,d="",e=a[0],f=a[1],g=b[0],h=g,i=1;c&&(d+="Q"+(f[0]-g[0]*2/3)+","+(f[1]-g[1]*2/3)+","+f[0]+","+f[1],e=a[1],i=2);if(b.length>1){h=b[1],f=a[i],i++,d+="C"+(e[0]+g[0])+","+(e[1]+g[1])+","+(f[0]-h[0])+","+(f[1]-h[1])+","+f[0]+","+f[1];for(var j=2;j<b.length;j++,i++)f=a[i],h=b[j],d+="S"+(f[0]-h[0])+","+(f[1]-h[1])+","+f[0]+","+f[1]}if(c){var k=a[i];d+="Q"+(f[0]+h[0]*2/3)+","+(f[1]+h[1]*2/3)+","+k[0]+","+k[1]}return d}function cV(a,b){var c=[],d=(1-b)/2,e,f=a[0],g=a[1],h=1,i=a.length;while(++h<i)e=f,f=g,g=a[h],c.push([d*(g[0]-e[0]),d*(g[1]-e[1])]);return c}function cW(a){if(a.length<3)return cO(a);var b=1,c=a.length,d=a[0],e=d[0],f=d[1],g=[e,e,e,(d=a[1])[0]],h=[f,f,f,d[1]],i=[e,",",f];dc(i,g,h);while(++b<c)d=a[b],g.shift(),g.push(d[0]),h.shift(),h.push(d[1]),dc(i,g,h);b=-1;while(++b<2)g.shift(),g.push(d[0]),h.shift(),h.push(d[1]),dc(i,g,h);return i.join("")}function cX(a){if(a.length<4)return cO(a);var b=[],c=-1,d=a.length,e,f=[0],g=[0];while(++c<3)e=a[c],f.push(e[0]),g.push(e[1]);b.push(c$(db,f)+","+c$(db,g)),--c;while(++c<d)e=a[c],f.shift(),f.push(e[0]),g.shift(),g.push(e[1]),dc(b,f,g);return b.join("")}function cY(a){var b,c=-1,d=a.length,e=d+4,f,g=[],h=[];while(++c<4)f=a[c%d],g.push(f[0]),h.push(f[1]);b=[c$(db,g),",",c$(db,h)],--c;while(++c<e)f=a[c%d],g.shift(),g.push(f[0]),h.shift(),h.push(f[1]),dc(b,g,h);return b.join("")}function cZ(a,b){var c=a.length-1,d=a[0][0],e=a[0][1],f=a[c][0]-d,g=a[c][1]-e,h=-1,i,j;while(++h<=c)i=a[h],j=h/c,i[0]=b*i[0]+(1-b)*(d+j*f),i[1]=b*i[1]+(1-b)*(e+j*g);return cW(a)}function c$(a,b){return a[0]*b[0]+a[1]*b[1]+a[2]*b[2]+a[3]*b[3]}function dc(a,b,c){a.push("C",c$(c_,b),",",c$(c_,c),",",c$(da,b),",",c$(da,c),",",c$(db,b),",",c$(db,c))}function dd(a,b){return(b[1]-a[1])/(b[0]-a[0])}function de(a){var b=0,c=a.length-1,d=[],e=a[0],f=a[1],g=d[0]=dd(e,f);while(++b<c)d[b]=g+(g=dd(e=f,f=a[b+1]));return d[b]=g,d}function df(a){var b=[],c,d,e,f,g=de(a),h=-1,i=a.length-1;while(++h<i)c=dd(a[h],a[h+1]),Math.abs(c)<1e-6?g[h]=g[h+1]=0:(d=g[h]/c,e=g[h+1]/c,f=d*d+e*e,f>9&&(f=c*3/Math.sqrt(f),g[h]=f*d,g[h+1]=f*e));h=-1;while(++h<=i)f=(a[Math.min(i,h+1)][0]-a[Math.max(0,h-1)][0])/(6*(1+g[h]*g[h])),b.push([f||0,g[h]*f||0]);return b}function dg(a){return a.length<3?cO(a):a[0]+cU(a,df(a))}function dh(a){var b,c=-1,d=a.length,e,f;while(++c<d)b=a[c],e=b[0],f=b[1]+cC,b[0]=e*Math.cos(f),b[1]=e*Math.sin(f);return a}function di(a){function j(f){if(f.length<1)return null;var j=cJ(this,f,b,d),k=cJ(this,f,b===c?dj(j):c,d===e?dk(j):e);return"M"+g(a(k),i)+"L"+h(a(j.reverse()),i)+"Z"}var b=cK,c=cK,d=0,e=cL,f,g,h,i=.7;return j.x=function(a){return arguments.length?(b=c=a,j):c},j.x0=function(a){return arguments.length?(b=a,j):b},j.x1=function(a){return arguments.length?(c=a,j):c},j.y=function(a){return arguments.length?(d=e=a,j):e},j.y0=function(a){return arguments.length?(d=a,j):d},j.y1=function(a){return arguments.length?(e=a,j):e},j.interpolate=function(a){return arguments.length?(cN.has(a+="")||(a=cM),g=cN.get(f=a),h=g.reverse||g,j):f},j.tension=function(a){return arguments.length?(i=a,j):i},j.interpolate("linear")}function dj(a){return function(b,c){return a[c][0]}}function dk(a){return function(b,c){return a[c][1]}}function dl(a){return a.source}function dm(a){return a.target}function dn(a){return a.radius}function dp(a){return a.startAngle}function dq(a){return a.endAngle}function dr(a){return[a.x,a.y]}function ds(a){return function(){var b=a.apply(this,arguments),c=b[0],d=b[1]+cC;return[c*Math.cos(d),c*Math.sin(d)]}}function dt(){return 64}function du(){return"circle"}function dv(a){var b=Math.sqrt(a/Math.PI);return"M0,"+b+"A"+b+","+b+" 0 1,1 0,"+ -b+"A"+b+","+b+" 0 1,1 0,"+b+"Z"}function dz(a,b){a.attr("transform",function(a){return"translate("+b(a)+",0)"})}function dA(a,b){a.attr("transform",function(a){return"translate(0,"+b(a)+")"})}function dB(a,b,c){e=[];if(c&&b.length>1){var d=cc(a.domain()),e,f=-1,g=b.length,h=(b[1]-b[0])/++c,i,j;while(++f<g)for(i=c;--i>0;)(j=+b[f]-i*h)>=d[0]&&e.push(j);for(--f,i=0;++i<c&&(j=+b[f]+i*h)<d[1];)e.push(j)}return e}function dG(){dE||(dE=d3.select("body").append("div").style("visibility","hidden").style("top",0).style("height",0).style("width",0).style("overflow-y","scroll").append("div").style("height","2000px").node().parentNode);var a=d3.event,b;try{dE.scrollTop=1e3,dE.dispatchEvent(a),b=1e3-dE.scrollTop}catch(c){b=a.wheelDelta||-a.detail*5}return b}function dH(a){var b=a.source,c=a.target,d=dJ(b,c),e=[b];while(b!==d)b=b.parent,e.push(b);var f=e.length;while(c!==d)e.splice(f,0,c),c=c.parent;return e}function dI(a){var b=[],c=a.parent;while(c!=null)b.push(a),a=c,c=c.parent;return b.push(a),b}function dJ(a,b){if(a===b)return a;var c=dI(a),d=dI(b),e=c.pop(),f=d.pop(),g=null;while(e===f)g=e,e=c.pop(),f=d.pop();return g}function dM(a){a.fixed|=2}function dN(a){a!==dL&&(a.fixed&=1)}function dO(){dL.fixed&=1,dK=dL=null}function dP(){dL.px=d3.event.x,dL.py=d3.event.y,dK.resume()}function dQ(a,b,c){var d=0,e=0;a.charge=0;if(!a.leaf){var f=a.nodes,g=f.length,h=-1,i;while(++h<g){i=f[h];if(i==null)continue;dQ(i,b,c),a.charge+=i.charge,d+=i.charge*i.cx,e+=i.charge*i.cy}}if(a.point){a.leaf||(a.point.x+=Math.random()-.5,a.point.y+=Math.random()-.5);var j=b*c[a.point.index];a.charge+=a.pointCharge=j,d+=j*a.point.x,e+=j*a.point.y}a.cx=d/a.charge,a.cy=e/a.charge}function dR(a){return 20}function dS(a){return 1}function dU(a){return a.x}function dV(a){return a.y}function dW(a,b,c){a.y0=b,a.y=c}function dZ(a){return d3.range(a.length)}function d$(a){var b=-1,c=a[0].length,d=[];while(++b<c)d[b]=0;return d}function d_(a){var b=1,c=0,d=a[0][1],e,f=a.length;for(;b<f;++b)(e=a[b][1])>d&&(c=b,d=e);return c}function ea(a){return a.reduce(eb,0)}function eb(a,b){return a+b[1]}function ec(a,b){return ed(a,Math.ceil(Math.log(b.length)/Math.LN2+1))}function ed(a,b){var c=-1,d=+a[0],e=(a[1]-d)/b,f=[];while(++c<=b)f[c]=e*c+d;return f}function ee(a){return[d3.min(a),d3.max(a)]}function ef(a,b){return d3.rebind(a,b,"sort","children","value"),a.links=ej,a.nodes=function(b){return ek=!0,(a.nodes=a)(b)},a}function eg(a){return a.children}function eh(a){return a.value}function ei(a,b){return b.value-a.value}function ej(a){return d3.merge(a.map(function(a){return(a.children||[]).map(function(b){return{source:a,target:b}})}))}function el(a,b){return a.value-b.value}function em(a,b){var c=a._pack_next;a._pack_next=b,b._pack_prev=a,b._pack_next=c,c._pack_prev=b}function en(a,b){a._pack_next=b,b._pack_prev=a}function eo(a,b){var c=b.x-a.x,d=b.y-a.y,e=a.r+b.r;return e*e-c*c-d*d>.001}function ep(a){function l(a){b=Math.min(a.x-a.r,b),c=Math.max(a.x+a.r,c),d=Math.min(a.y-a.r,d),e=Math.max(a.y+a.r,e)}var b=Infinity,c=-Infinity,d=Infinity,e=-Infinity,f=a.length,g,h,i,j,k;a.forEach(eq),g=a[0],g.x=-g.r,g.y=0,l(g);if(f>1){h=a[1],h.x=h.r,h.y=0,l(h);if(f>2){i=a[2],eu(g,h,i),l(i),em(g,i),g._pack_prev=i,em(i,h),h=g._pack_next;for(var m=3;m<f;m++){eu(g,h,i=a[m]);var n=0,o=1,p=1;for(j=h._pack_next;j!==h;j=j._pack_next,o++)if(eo(j,i)){n=1;break}if(n==1)for(k=g._pack_prev;k!==j._pack_prev;k=k._pack_prev,p++)if(eo(k,i))break;n?(o<p||o==p&&h.r<g.r?en(g,h=j):en(g=k,h),m--):(em(g,i),h=i,l(i))}}}var q=(b+c)/2,r=(d+e)/2,s=0;for(var m=0;m<f;m++){var t=a[m];t.x-=q,t.y-=r,s=Math.max(s,t.r+Math.sqrt(t.x*t.x+t.y*t.y))}return a.forEach(er),s}function eq(a){a._pack_next=a._pack_prev=a}function er(a){delete a._pack_next,delete a._pack_prev}function es(a){var b=a.children;b&&b.length?(b.forEach(es),a.r=ep(b)):a.r=Math.sqrt(a.value)}function et(a,b,c,d){var e=a.children;a.x=b+=d*a.x,a.y=c+=d*a.y,a.r*=d;if(e){var f=-1,g=e.length;while(++f<g)et(e[f],b,c,d)}}function eu(a,b,c){var d=a.r+c.r,e=b.x-a.x,f=b.y-a.y;if(d&&(e||f)){var g=b.r+c.r,h=Math.sqrt(e*e+f*f),i=Math.max(-1,Math.min(1,(d*d+h*h-g*g)/(2*d*h))),j=Math.acos(i),k=i*(d/=h),l=Math.sin(j)*d;c.x=a.x+k*e+l*f,c.y=a.y+k*f-l*e}else c.x=a.x+d,c.y=a.y}function ev(a){return 1+d3.max(a,function(a){return a.y})}function ew(a){return a.reduce(function(a,b){return a+b.x},0)/a.length}function ex(a){var b=a.children;return b&&b.length?ex(b[0]):a}function ey(a){var b=a.children,c;return b&&(c=b.length)?ey(b[c-1]):a}function ez(a,b){return a.parent==b.parent?1:2}function eA(a){var b=a.children;return b&&b.length?b[0]:a._tree.thread}function eB(a){var b=a.children,c;return b&&(c=b.length)?b[c-1]:a._tree.thread}function eC(a,b){var c=a.children;if(c&&(e=c.length)){var d,e,f=-1;while(++f<e)b(d=eC(c[f],b),a)>0&&(a=d)}return a}function eD(a,b){return a.x-b.x}function eE(a,b){return b.x-a.x}function eF(a,b){return a.depth-b.depth}function eG(a,b){function c(a,d){var e=a.children;if(e&&(i=e.length)){var f,g=null,h=-1,i;while(++h<i)f=e[h],c(f,g),g=f}b(a,d)}c(a,null)}function eH(a){var b=0,c=0,d=a.children,e=d.length,f;while(--e>=0)f=d[e]._tree,f.prelim+=b,f.mod+=b,b+=f.shift+(c+=f.change)}function eI(a,b,c){a=a._tree,b=b._tree;var d=c/(b.number-a.number);a.change+=d,b.change-=d,b.shift+=c,b.prelim+=c,b.mod+=c}function eJ(a,b,c){return a._tree.ancestor.parent==b.parent?a._tree.ancestor:c}function eK(a){return{x:a.x,y:a.y,dx:a.dx,dy:a.dy}}function eL(a,b){var c=a.x+b[3],d=a.y+b[0],e=a.dx-b[1]-b[3],f=a.dy-b[0]-b[2];return e<0&&(c+=e/2,e=0),f<0&&(d+=f/2,f=0),{x:c,y:d,dx:e,dy:f}}function eM(a){return a.map(eN).join(",")}function eN(a){return/[",\n]/.test(a)?'"'+a.replace(/\"/g,'""')+'"':a}function eP(a,b){return function(c){return c&&a.hasOwnProperty(c.type)?a[c.type](c):b}}function eQ(a){return"m0,"+a+"a"+a+","+a+" 0 1,1 0,"+ -2*a+"a"+a+","+a+" 0 1,1 0,"+2*a+"z"}function eR(a,b){eS.hasOwnProperty(a.type)&&eS[a.type](a,b)}function eT(a,b){eR(a.geometry,b)}function eU(a,b){for(var c=a.features,d=0,e=c.length;d<e;d++)eR(c[d].geometry,b)}function eV(a,b){for(var c=a.geometries,d=0,e=c.length;d<e;d++)eR(c[d],b)}function eW(a,b){for(var c=a.coordinates,d=0,e=c.length;d<e;d++)b.apply(null,c[d])}function eX(a,b){for(var c=a.coordinates,d=0,e=c.length;d<e;d++)for(var f=c[d],g=0,h=f.length;g<h;g++)b.apply(null,f[g])}function eY(a,b){for(var c=a.coordinates,d=0,e=c.length;d<e;d++)for(var f=c[d][0],g=0,h=f.length;g<h;g++)b.apply(null,f[g])}function eZ(a,b){b.apply(null,a.coordinates)}function e$(a,b){for(var c=a.coordinates[0],d=0,e=c.length;d<e;d++)b.apply(null,c[d])}function e_(a){return a.source}function fa(a){return a.target}function fb(a,b){function q(a){var b=Math.sin(o-(a*=o))/p,c=Math.sin(a)/p,f=b*g*d+c*m*j,i=b*g*e+c*m*k,l=b*h+c*n;return[Math.atan2(i,f)/eO,Math.atan2(l,Math.sqrt(f*f+i*i))/eO]}var c=a[0]*eO,d=Math.cos(c),e=Math.sin(c),f=a[1]*eO,g=Math.cos(f),h=Math.sin(f),i=b[0]*eO,j=Math.cos(i),k=Math.sin(i),l=b[1]*eO,m=Math.cos(l),n=Math.sin(l),o=q.d=Math.acos(Math.max(-1,Math.min(1,h*n+g*m*Math.cos(i-c)))),p=Math.sin(o);return q}function fe(a){var b=0,c=0;for(;;){if(a(b,c))return[b,c];b===0?(b=c+1,c=0):(b-=1,c+=1)}}function ff(a,b,c,d){var e,f,g,h,i,j,k;return e=d[a],f=e[0],g=e[1],e=d[b],h=e[0],i=e[1],e=d[c],j=e[0],k=e[1],(k-g)*(h-f)-(i-g)*(j-f)>0}function fg(a,b,c){return(c[0]-b[0])*(a[1]-b[1])<(c[1]-b[1])*(a[0]-b[0])}function fh(a,b,c,d){var e=a[0],f=b[0],g=c[0],h=d[0],i=a[1],j=b[1],k=c[1],l=d[1],m=e-g,n=f-e,o=h-g,p=i-k,q=j-i,r=l-k,s=(o*p-r*m)/(r*n-o*q);return[e+s*n,i+s*q]}function fj(a,b){var c={list:a.map(function(a,b){return{index:b,x:a[0],y:a[1]}}).sort(function(a,b){return a.y<b.y?-1:a.y>b.y?1:a.x<b.x?-1:a.x>b.x?1:0}),bottomSite:null},d={list:[],leftEnd:null,rightEnd:null,init:function(){d.leftEnd=d.createHalfEdge(null,"l"),d.rightEnd=d.createHalfEdge(null,"l"),d.leftEnd.r=d.rightEnd,d.rightEnd.l=d.leftEnd,d.list.unshift(d.leftEnd,d.rightEnd)},createHalfEdge:function(a,b){return{edge:a,side:b,vertex:null,l:null,r:null}},insert:function(a,b){b.l=a,b.r=a.r,a.r.l=b,a.r=b},leftBound:function(a){var b=d.leftEnd;do b=b.r;while(b!=d.rightEnd&&e.rightOf(b,a));return b=b.l,b},del:function(a){a.l.r=a.r,a.r.l=a.l,a.edge=null},right:function(a){return a.r},left:function(a){return a.l},leftRegion:function(a){return a.edge==null?c.bottomSite:a.edge.region[a.side]},rightRegion:function(a){return a.edge==null?c.bottomSite:a.edge.region[fi[a.side]]}},e={bisect:function(a,b){var c={region:{l:a,r:b},ep:{l:null,r:null}},d=b.x-a.x,e=b.y-a.y,f=d>0?d:-d,g=e>0?e:-e;return c.c=a.x*d+a.y*e+(d*d+e*e)*.5,f>g?(c.a=1,c.b=e/d,c.c/=d):(c.b=1,c.a=d/e,c.c/=e),c},intersect:function(a,b){var c=a.edge,d=b.edge;if(!c||!d||c.region.r==d.region.r)return null;var e=c.a*d.b-c.b*d.a;if(Math.abs(e)<1e-10)return null;var f=(c.c*d.b-d.c*c.b)/e,g=(d.c*c.a-c.c*d.a)/e,h=c.region.r,i=d.region.r,j,k;h.y<i.y||h.y==i.y&&h.x<i.x?(j=a,k=c):(j=b,k=d);var l=f>=k.region.r.x;return l&&j.side==="l"||!l&&j.side==="r"?null:{x:f,y:g}},rightOf:function(a,b){var c=a.edge,d=c.region.r,e=b.x>d.x;if(e&&a.side==="l")return 1;if(!e&&a.side==="r")return 0;if(c.a===1){var f=b.y-d.y,g=b.x-d.x,h=0,i=0;!e&&c.b<0||e&&c.b>=0?i=h=f>=c.b*g:(i=b.x+b.y*c.b>c.c,c.b<0&&(i=!i),i||(h=1));if(!h){var j=d.x-c.region.l.x;i=c.b*(g*g-f*f)<j*f*(1+2*g/j+c.b*c.b),c.b<0&&(i=!i)}}else{var k=c.c-c.a*b.x,l=b.y-k,m=b.x-d.x,n=k-d.y;i=l*l>m*m+n*n}return a.side==="l"?i:!i},endPoint:function(a,c,d){a.ep[c]=d;if(!a.ep[fi[c]])return;b(a)},distance:function(a,b){var c=a.x-b.x,d=a.y-b.y;return Math.sqrt(c*c+d*d)}},f={list:[],insert:function(a,b,c){a.vertex=b,a.ystar=b.y+c;for(var d=0,e=f.list,g=e.length;d<g;d++){var h=e[d];if(a.ystar>h.ystar||a.ystar==h.ystar&&b.x>h.vertex.x)continue;break}e.splice(d,0,a)},del:function(a){for(var b=0,c=f.list,d=c.length;b<d&&c[b]!=a;++b);c.splice(b,1)},empty:function(){return f.list.length===0},nextEvent:function(a){for(var b=0,c=f.list,d=c.length;b<d;++b)if(c[b]==a)return c[b+1];return null},min:function(){var a=f.list[0];return{x:a.vertex.x,y:a.ystar}},extractMin:function(){return f.list.shift()}};d.init(),c.bottomSite=c.list.shift();var g=c.list.shift(),h,i,j,k,l,m,n,o,p,q,r,s,t;for(;;){f.empty()||(h=f.min());if(g&&(f.empty()||g.y<h.y||g.y==h.y&&g.x<h.x))i=d.leftBound(g),j=d.right(i),n=d.rightRegion(i),s=e.bisect(n,g),m=d.createHalfEdge(s,"l"),d.insert(i,m),q=e.intersect(i,m),q&&(f.del(i),f.insert(i,q,e.distance(q,g))),i=m,m=d.createHalfEdge(s,"r"),d.insert(i,m),q=e.intersect(m,j),q&&f.insert(m,q,e.distance(q,g)),g=c.list.shift();else if(!f.empty())i=f.extractMin(),k=d.left(i),j=d.right(i),l=d.right(j),n=d.leftRegion(i),o=d.rightRegion(j),r=i.vertex,e.endPoint(i.edge,i.side,r),e.endPoint(j.edge,j.side,r),d.del(i),f.del(j),d.del(j),t="l",n.y>o.y&&(p=n,n=o,o=p,t="r"),s=e.bisect(n,o),m=d.createHalfEdge(s,t),d.insert(k,m),e.endPoint(s,fi[t],r),q=e.intersect(k,m),q&&(f.del(k),f.insert(k,q,e.distance(q,n))),q=e.intersect(m,l),q&&f.insert(m,q,e.distance(q,n));else break}for(i=d.right(d.leftEnd);i!=d.rightEnd;i=d.right(i))b(i.edge)}function fk(){return{leaf:!0,nodes:[],point:null}}function fl(a,b,c,d,e,f){if(!a(b,c,d,e,f)){var g=(c+e)*.5,h=(d+f)*.5,i=b.nodes;i[0]&&fl(a,i[0],c,d,g,h),i[1]&&fl(a,i[1],g,d,e,h),i[2]&&fl(a,i[2],c,h,g,f),i[3]&&fl(a,i[3],g,h,e,f)}}function fm(a){return{x:a[0],y:a[1]}}function fo(){this._=new Date(arguments.length>1?Date.UTC.apply(this,arguments):arguments[0])}function fq(a,b,c,d){var e,f,g=0,h=b.length,i=c.length;while(g<h){if(d>=i)return-1;e=b.charCodeAt(g++);if(e==37){f=fw[b.charAt(g++)];if(!f||(d=f(a,c,d))<0)return-1}else if(e!=c.charCodeAt(d++))return-1}return d}function fx(a,b,c){return fz.test(b.substring(c,c+=3))?c:-1}function fy(a,b,c){fA.lastIndex=0;var d=fA.exec(b.substring(c,c+10));return d?c+=d[0].length:-1}function fB(a,b,c){var d=fC.get(b.substring(c,c+=3).toLowerCase());return d==null?-1:(a.m=d,c)}function fD(a,b,c){fE.lastIndex=0;var d=fE.exec(b.substring(c,c+12));return d?(a.m=fF.get(d[0].toLowerCase()),c+=d[0].length):-1}function fH(a,b,c){return fq(a,fv.c.toString(),b,c)}function fI(a,b,c){return fq(a,fv.x.toString(),b,c)}function fJ(a,b,c){return fq(a,fv.X.toString(),b,c)}function fK(a,b,c){fT.lastIndex=0;var d=fT.exec(b.substring(c,c+4));return d?(a.y=+d[0],c+=d[0].length):-1}function fL(a,b,c){fT.lastIndex=0;var d=fT.exec(b.substring(c,c+2));return d?(a.y=fM()+ +d[0],c+=d[0].length):-1}function fM(){return~~((new Date).getFullYear()/1e3)*1e3}function fN(a,b,c){fT.lastIndex=0;var d=fT.exec(b.substring(c,c+2));return d?(a.m=d[0]-1,c+=d[0].length):-1}function fO(a,b,c){fT.lastIndex=0;var d=fT.exec(b.substring(c,c+2));return d?(a.d=+d[0],c+=d[0].length):-1}function fP(a,b,c){fT.lastIndex=0;var d=fT.exec(b.substring(c,c+2));return d?(a.H=+d[0],c+=d[0].length):-1}function fQ(a,b,c){fT.lastIndex=0;var d=fT.exec(b.substring(c,c+2));return d?(a.M=+d[0],c+=d[0].length):-1}function fR(a,b,c){fT.lastIndex=0;var d=fT.exec(b.substring(c,c+2));return d?(a.S=+d[0],c+=d[0].length):-1}function fS(a,b,c){fT.lastIndex=0;var d=fT.exec(b.substring(c,c+3));return d?(a.L=+d[0],c+=d[0].length):-1}function fU(a,b,c){var d=fV.get(b.substring(c,c+=2).toLowerCase());return d==null?-1:(a.p=d,c)}function fW(a){var b=a.getTimezoneOffset(),c=b>0?"-":"+",d=~~(Math.abs(b)/60),e=Math.abs(b)%60;return c+fr(d)+fr(e)}function fY(a){return a.toISOString()}function fZ(a,b,c){function d(b){var c=a(b),d=f(c,1);return b-c<d-b?c:d}function e(c){return b(c=a(new fn(c-1)),1),c}function f(a,c){return b(a=new fn(+a),c),a}function g(a,d,f){var g=e(a),h=[];if(f>1)while(g<d)c(g)%f||h.push(new Date(+g)),b(g,1);else while(g<d)h.push(new Date(+g)),b(g,1);return h}function h(a,b,c){try{fn=fo;var d=new fo;return d._=a,g(d,b,c)}finally{fn=Date}}a.floor=a,a.round=d,a.ceil=e,a.offset=f,a.range=g;var i=a.utc=f$(a);return i.floor=i,i.round=f$(d),i.ceil=f$(e),i.offset=f$(f),i.range=h,a}function f$(a){return function(b,c){try{fn=fo;var d=new fo;return d._=b,a(d,c)._}finally{fn=Date}}}function f_(a,b,c){function d(b){return a(b)}return d.invert=function(b){return gb(a.invert(b))},d.domain=function(b){return arguments.length?(a.domain(b),d):a.domain().map(gb)},d.nice=function(a){var b=ga(d.domain());return d.domain([a.floor(b[0]),a.ceil(b[1])])},d.ticks=function(c,e){var f=ga(d.domain());if(typeof
+c!="function"){var g=f[1]-f[0],h=g/c,i=d3.bisect(gf,h);if(i==gf.length)return b.year(f,c);if(!i)return a.ticks(c).map(gb);Math.log(h/gf[i-1])<Math.log(gf[i]/h)&&--i,c=b[i],e=c[1],c=c[0].range}return c(f[0],new Date(+f[1]+1),e)},d.tickFormat=function(){return c},d.copy=function(){return f_(a.copy(),b,c)},d3.rebind(d,a,"range","rangeRound","interpolate","clamp")}function ga(a){var b=a[0],c=a[a.length-1];return b<c?[b,c]:[c,b]}function gb(a){return new Date(a)}function gc(a){return function(b){var c=a.length-1,d=a[c];while(!d[1](b))d=a[--c];return d[0](b)}}function gd(a){var b=new Date(a,0,1);return b.setFullYear(a),b}function ge(a){var b=a.getFullYear(),c=gd(b),d=gd(b+1);return b+(a-c)/(d-c)}function gn(a){var b=new Date(Date.UTC(a,0,1));return b.setUTCFullYear(a),b}function go(a){var b=a.getUTCFullYear(),c=gn(b),d=gn(b+1);return b+(a-c)/(d-c)}Date.now||(Date.now=function(){return+(new Date)});try{document.createElement("div").style.setProperty("opacity",0,"")}catch(a){var b=CSSStyleDeclaration.prototype,c=b.setProperty;b.setProperty=function(a,b,d){c.call(this,a,b+"",d)}}d3={version:"2.8.1"};var f=h;try{f(document.documentElement.childNodes)[0].nodeType}catch(i){f=g}var j=[].__proto__?function(a,b){a.__proto__=b}:function(a,b){for(var c in b)a[c]=b[c]};d3.map=function(a){var b=new k;for(var c in a)b.set(c,a[c]);return b},e(k,{has:function(a){return l+a in this},get:function(a){return this[l+a]},set:function(a,b){return this[l+a]=b},remove:function(a){return a=l+a,a in this&&delete this[a]},keys:function(){var a=[];return this.forEach(function(b){a.push(b)}),a},values:function(){var a=[];return this.forEach(function(b,c){a.push(c)}),a},entries:function(){var a=[];return this.forEach(function(b,c){a.push({key:b,value:c})}),a},forEach:function(a){for(var b in this)b.charCodeAt(0)===m&&a.call(this,b.substring(1),this[b])}});var l="\0",m=l.charCodeAt(0);d3.functor=function(a){return typeof a=="function"?a:function(){return a}},d3.rebind=function(a,b){var c=1,d=arguments.length,e;while(++c<d)a[e=arguments[c]]=o(a,b,b[e]);return a},d3.ascending=function(a,b){return a<b?-1:a>b?1:a>=b?0:NaN},d3.descending=function(a,b){return b<a?-1:b>a?1:b>=a?0:NaN},d3.mean=function(a,b){var c=a.length,d,e=0,f=-1,g=0;if(arguments.length===1)while(++f<c)p(d=a[f])&&(e+=(d-e)/++g);else while(++f<c)p(d=b.call(a,a[f],f))&&(e+=(d-e)/++g);return g?e:undefined},d3.median=function(a,b){return arguments.length>1&&(a=a.map(b)),a=a.filter(p),a.length?d3.quantile(a.sort(d3.ascending),.5):undefined},d3.min=function(a,b){var c=-1,d=a.length,e,f;if(arguments.length===1){while(++c<d&&((e=a[c])==null||e!=e))e=undefined;while(++c<d)(f=a[c])!=null&&e>f&&(e=f)}else{while(++c<d&&((e=b.call(a,a[c],c))==null||e!=e))e=undefined;while(++c<d)(f=b.call(a,a[c],c))!=null&&e>f&&(e=f)}return e},d3.max=function(a,b){var c=-1,d=a.length,e,f;if(arguments.length===1){while(++c<d&&((e=a[c])==null||e!=e))e=undefined;while(++c<d)(f=a[c])!=null&&f>e&&(e=f)}else{while(++c<d&&((e=b.call(a,a[c],c))==null||e!=e))e=undefined;while(++c<d)(f=b.call(a,a[c],c))!=null&&f>e&&(e=f)}return e},d3.extent=function(a,b){var c=-1,d=a.length,e,f,g;if(arguments.length===1){while(++c<d&&((e=g=a[c])==null||e!=e))e=g=undefined;while(++c<d)(f=a[c])!=null&&(e>f&&(e=f),g<f&&(g=f))}else{while(++c<d&&((e=g=b.call(a,a[c],c))==null||e!=e))e=undefined;while(++c<d)(f=b.call(a,a[c],c))!=null&&(e>f&&(e=f),g<f&&(g=f))}return[e,g]},d3.random={normal:function(a,b){return arguments.length<2&&(b=1),arguments.length<1&&(a=0),function(){var c,d,e;do c=Math.random()*2-1,d=Math.random()*2-1,e=c*c+d*d;while(!e||e>1);return a+b*c*Math.sqrt(-2*Math.log(e)/e)}}},d3.sum=function(a,b){var c=0,d=a.length,e,f=-1;if(arguments.length===1)while(++f<d)isNaN(e=+a[f])||(c+=e);else while(++f<d)isNaN(e=+b.call(a,a[f],f))||(c+=e);return c},d3.quantile=function(a,b){var c=(a.length-1)*b+1,d=Math.floor(c),e=a[d-1],f=c-d;return f?e+f*(a[d]-e):e},d3.transpose=function(a){return d3.zip.apply(d3,a)},d3.zip=function(){if(!(e=arguments.length))return[];for(var a=-1,b=d3.min(arguments,q),c=new Array(b);++a<b;)for(var d=-1,e,f=c[a]=new Array(e);++d<e;)f[d]=arguments[d][a];return c},d3.bisector=function(a){return{left:function(b,c,d,e){arguments.length<3&&(d=0),arguments.length<4&&(e=b.length);while(d<e){var f=d+e>>1;a.call(b,b[f],f)<c?d=f+1:e=f}return d},right:function(b,c,d,e){arguments.length<3&&(d=0),arguments.length<4&&(e=b.length);while(d<e){var f=d+e>>1;c<a.call(b,b[f],f)?e=f:d=f+1}return d}}};var r=d3.bisector(function(a){return a});d3.bisectLeft=r.left,d3.bisect=d3.bisectRight=r.right,d3.first=function(a,b){var c=0,d=a.length,e=a[0],f;arguments.length===1&&(b=d3.ascending);while(++c<d)b.call(a,e,f=a[c])>0&&(e=f);return e},d3.last=function(a,b){var c=0,d=a.length,e=a[0],f;arguments.length===1&&(b=d3.ascending);while(++c<d)b.call(a,e,f=a[c])<=0&&(e=f);return e},d3.nest=function(){function f(c,g){if(g>=b.length)return e?e.call(a,c):d?c.sort(d):c;var h=-1,i=c.length,j=b[g++],l,m,n=new k,o,p={};while(++h<i)(o=n.get(l=j(m=c[h])))?o.push(m):n.set(l,[m]);return n.forEach(function(a){p[a]=f(n.get(a),g)}),p}function g(a,d){if(d>=b.length)return a;var e=[],f=c[d++],h;for(h in a)e.push({key:h,values:g(a[h],d)});return f&&e.sort(function(a,b){return f(a.key,b.key)}),e}var a={},b=[],c=[],d,e;return a.map=function(a){return f(a,0)},a.entries=function(a){return g(f(a,0),0)},a.key=function(c){return b.push(c),a},a.sortKeys=function(d){return c[b.length-1]=d,a},a.sortValues=function(b){return d=b,a},a.rollup=function(b){return e=b,a},a},d3.keys=function(a){var b=[];for(var c in a)b.push(c);return b},d3.values=function(a){var b=[];for(var c in a)b.push(a[c]);return b},d3.entries=function(a){var b=[];for(var c in a)b.push({key:c,value:a[c]});return b},d3.permute=function(a,b){var c=[],d=-1,e=b.length;while(++d<e)c[d]=a[b[d]];return c},d3.merge=function(a){return Array.prototype.concat.apply([],a)},d3.split=function(a,b){var c=[],d=[],e,f=-1,g=a.length;arguments.length<2&&(b=s);while(++f<g)b.call(d,e=a[f],f)?d=[]:(d.length||c.push(d),d.push(e));return c},d3.range=function(a,b,c){arguments.length<3&&(c=1,arguments.length<2&&(b=a,a=0));if((b-a)/c===Infinity)throw new Error("infinite range");var d=[],e=u(Math.abs(c)),f=-1,g;a*=e,b*=e,c*=e;if(c<0)while((g=a+c*++f)>b)d.push(g/e);else while((g=a+c*++f)<b)d.push(g/e);return d},d3.requote=function(a){return a.replace(v,"\\$&")};var v=/[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g;d3.round=function(a,b){return b?Math.round(a*(b=Math.pow(10,b)))/b:Math.round(a)},d3.xhr=function(a,b,c){var d=new XMLHttpRequest;arguments.length<3?(c=b,b=null):b&&d.overrideMimeType&&d.overrideMimeType(b),d.open("GET",a,!0),b&&d.setRequestHeader("Accept",b),d.onreadystatechange=function(){d.readyState===4&&c(d.status<300?d:null)},d.send(null)},d3.text=function(a,b,c){function d(a){c(a&&a.responseText)}arguments.length<3&&(c=b,b=null),d3.xhr(a,b,d)},d3.json=function(a,b){d3.text(a,"application/json",function(a){b(a?JSON.parse(a):null)})},d3.html=function(a,b){d3.text(a,"text/html",function(a){if(a!=null){var c=document.createRange();c.selectNode(document.body),a=c.createContextualFragment(a)}b(a)})},d3.xml=function(a,b,c){function d(a){c(a&&a.responseXML)}arguments.length<3&&(c=b,b=null),d3.xhr(a,b,d)};var w={svg:"http://www.w3.org/2000/svg",xhtml:"http://www.w3.org/1999/xhtml",xlink:"http://www.w3.org/1999/xlink",xml:"http://www.w3.org/XML/1998/namespace",xmlns:"http://www.w3.org/2000/xmlns/"};d3.ns={prefix:w,qualify:function(a){var b=a.indexOf(":"),c=a;return b>=0&&(c=a.substring(0,b),a=a.substring(b+1)),w.hasOwnProperty(c)?{space:w[c],local:a}:a}},d3.dispatch=function(){var a=new x,b=-1,c=arguments.length;while(++b<c)a[arguments[b]]=y(a);return a},x.prototype.on=function(a,b){var c=a.indexOf("."),d="";return c>0&&(d=a.substring(c+1),a=a.substring(0,c)),arguments.length<2?this[a].on(d):this[a].on(d,b)},d3.format=function(a){var b=z.exec(a),c=b[1]||" ",d=b[3]||"",e=b[5],f=+b[6],g=b[7],h=b[8],i=b[9],j=1,k="",l=!1;h&&(h=+h.substring(1)),e&&(c="0",g&&(f-=Math.floor((f-1)/4)));switch(i){case"n":g=!0,i="g";break;case"%":j=100,k="%",i="f";break;case"p":j=100,k="%",i="r";break;case"d":l=!0,h=0;break;case"s":j=-1,i="r"}return i=="r"&&!h&&(i="g"),i=A.get(i)||C,function(a){if(l&&a%1)return"";var b=a<0&&(a=-a)?"−":d;if(j<0){var m=d3.formatPrefix(a,h);a*=m.scale,k=m.symbol}else a*=j;a=i(a,h);if(e){var n=a.length+b.length;n<f&&(a=(new Array(f-n+1)).join(c)+a),g&&(a=D(a)),a=b+a}else{g&&(a=D(a)),a=b+a;var n=a.length;n<f&&(a=(new Array(f-n+1)).join(c)+a)}return a+k}};var z=/(?:([^{])?([<>=^]))?([+\- ])?(#)?(0)?([0-9]+)?(,)?(\.[0-9]+)?([a-zA-Z%])?/,A=d3.map({g:function(a,b){return a.toPrecision(b)},e:function(a,b){return a.toExponential(b)},f:function(a,b){return a.toFixed(b)},r:function(a,b){return d3.round(a,b=B(a,b)).toFixed(Math.max(0,Math.min(20,b)))}}),E=["y","z","a","f","p","n","μ","m","","k","M","G","T","P","E","Z","Y"].map(F);d3.formatPrefix=function(a,b){var c=0;return a&&(a<0&&(a*=-1),b&&(a=d3.round(a,B(a,b))),c=1+Math.floor(1e-12+Math.log(a)/Math.LN10),c=Math.max(-24,Math.min(24,Math.floor((c<=0?c+1:c-1)/3)*3))),E[8+c/3]};var G=P(2),H=P(3),I=function(){return O},J=d3.map({linear:I,poly:P,quad:function(){return G},cubic:function(){return H},sin:function(){return Q},exp:function(){return R},circle:function(){return S},elastic:T,back:U,bounce:function(){return V}}),K=d3.map({"in":O,out:M,"in-out":N,"out-in":function(a){return N(M(a))}});d3.ease=function(a){var b=a.indexOf("-"),c=b>=0?a.substring(0,b):a,d=b>=0?a.substring(b+1):"in";return c=J.get(c)||I,d=K.get(d)||O,L(d(c.apply(null,Array.prototype.slice.call(arguments,1))))},d3.event=null,d3.interpolate=function(a,b){var c=d3.interpolators.length,d;while(--c>=0&&!(d=d3.interpolators[c](a,b)));return d},d3.interpolateNumber=function(a,b){return b-=a,function(c){return a+b*c}},d3.interpolateRound=function(a,b){return b-=a,function(c){return Math.round(a+b*c)}},d3.interpolateString=function(a,b){var c,d,e,f=0,g=0,h=[],i=[],j,k;Z.lastIndex=0;for(d=0;c=Z.exec(b);++d)c.index&&h.push(b.substring(f,g=c.index)),i.push({i:h.length,x:c[0]}),h.push(null),f=Z.lastIndex;f<b.length&&h.push(b.substring(f));for(d=0,j=i.length;(c=Z.exec(a))&&d<j;++d){k=i[d];if(k.x==c[0]){if(k.i)if(h[k.i+1]==null){h[k.i-1]+=k.x,h.splice(k.i,1);for(e=d+1;e<j;++e)i[e].i--}else{h[k.i-1]+=k.x+h[k.i+1],h.splice(k.i,2);for(e=d+1;e<j;++e)i[e].i-=2}else if(h[k.i+1]==null)h[k.i]=k.x;else{h[k.i]=k.x+h[k.i+1],h.splice(k.i+1,1);for(e=d+1;e<j;++e)i[e].i--}i.splice(d,1),j--,d--}else k.x=d3.interpolateNumber(parseFloat(c[0]),parseFloat(k.x))}while(d<j)k=i.pop(),h[k.i+1]==null?h[k.i]=k.x:(h[k.i]=k.x+h[k.i+1],h.splice(k.i+1,1)),j--;return h.length===1?h[0]==null?i[0].x:function(){return b}:function(a){for(d=0;d<j;++d)h[(k=i[d]).i]=k.x(a);return h.join("")}},d3.interpolateTransform=function(a,b){var c=[],d=[],e,f=d3.transform(a),g=d3.transform(b),h=f.translate,i=g.translate,j=f.rotate,k=g.rotate,l=f.skew,m=g.skew,n=f.scale,o=g.scale;return h[0]!=i[0]||h[1]!=i[1]?(c.push("translate(",null,",",null,")"),d.push({i:1,x:d3.interpolateNumber(h[0],i[0])},{i:3,x:d3.interpolateNumber(h[1],i[1])})):i[0]||i[1]?c.push("translate("+i+")"):c.push(""),j!=k?d.push({i:c.push(c.pop()+"rotate(",null,")")-2,x:d3.interpolateNumber(j,k)}):k&&c.push(c.pop()+"rotate("+k+")"),l!=m?d.push({i:c.push(c.pop()+"skewX(",null,")")-2,x:d3.interpolateNumber(l,m)}):m&&c.push(c.pop()+"skewX("+m+")"),n[0]!=o[0]||n[1]!=o[1]?(e=c.push(c.pop()+"scale(",null,",",null,")"),d.push({i:e-4,x:d3.interpolateNumber(n[0],o[0])},{i:e-2,x:d3.interpolateNumber(n[1],o[1])})):(o[0]!=1||o[1]!=1)&&c.push(c.pop()+"scale("+o+")"),e=d.length,function(a){var b=-1,f;while(++b<e)c[(f=d[b]).i]=f.x(a);return c.join("")}},d3.interpolateRgb=function(a,b){a=d3.rgb(a),b=d3.rgb(b);var c=a.r,d=a.g,e=a.b,f=b.r-c,g=b.g-d,h=b.b-e;return function(a){return"#"+bd(Math.round(c+f*a))+bd(Math.round(d+g*a))+bd(Math.round(e+h*a))}},d3.interpolateHsl=function(a,b){a=d3.hsl(a),b=d3.hsl(b);var c=a.h,d=a.s,e=a.l,f=b.h-c,g=b.s-d,h=b.l-e;return function(a){return bk(c+f*a,d+g*a,e+h*a).toString()}},d3.interpolateArray=function(a,b){var c=[],d=[],e=a.length,f=b.length,g=Math.min(a.length,b.length),h;for(h=0;h<g;++h)c.push(d3.interpolate(a[h],b[h]));for(;h<e;++h)d[h]=a[h];for(;h<f;++h)d[h]=b[h];return function(a){for(h=0;h<g;++h)d[h]=c[h](a);return d}},d3.interpolateObject=function(a,b){var c={},d={},e;for(e in a)e in b?c[e]=$(e)(a[e],b[e]):d[e]=a[e];for(e in b)e in a||(d[e]=b[e]);return function(a){for(e in c)d[e]=c[e](a);return d}};var Z=/[-+]?(?:\d*\.?\d+)(?:[eE][-+]?\d+)?/g;d3.interpolators=[d3.interpolateObject,function(a,b){return b instanceof Array&&d3.interpolateArray(a,b)},function(a,b){return(typeof a=="string"||typeof b=="string")&&d3.interpolateString(a+"",b+"")},function(a,b){return(typeof b=="string"?bh.has(b)||/^(#|rgb\(|hsl\()/.test(b):b instanceof bc||b instanceof bj)&&d3.interpolateRgb(a,b)},function(a,b){return!isNaN(a=+a)&&!isNaN(b=+b)&&d3.interpolateNumber(a,b)}],d3.rgb=function(a,b,c){return arguments.length===1?a instanceof bc?bb(a.r,a.g,a.b):be(""+a,bb,bk):bb(~~a,~~b,~~c)},bc.prototype.brighter=function(a){a=Math.pow(.7,arguments.length?a:1);var b=this.r,c=this.g,d=this.b,e=30;return!b&&!c&&!d?bb(e,e,e):(b&&b<e&&(b=e),c&&c<e&&(c=e),d&&d<e&&(d=e),bb(Math.min(255,Math.floor(b/a)),Math.min(255,Math.floor(c/a)),Math.min(255,Math.floor(d/a))))},bc.prototype.darker=function(a){return a=Math.pow(.7,arguments.length?a:1),bb(Math.floor(a*this.r),Math.floor(a*this.g),Math.floor(a*this.b))},bc.prototype.hsl=function(){return bf(this.r,this.g,this.b)},bc.prototype.toString=function(){return"#"+bd(this.r)+bd(this.g)+bd(this.b)};var bh=d3.map({aliceblue:"#f0f8ff",antiquewhite:"#faebd7",aqua:"#00ffff",aquamarine:"#7fffd4",azure:"#f0ffff",beige:"#f5f5dc",bisque:"#ffe4c4",black:"#000000",blanchedalmond:"#ffebcd",blue:"#0000ff",blueviolet:"#8a2be2",brown:"#a52a2a",burlywood:"#deb887",cadetblue:"#5f9ea0",chartreuse:"#7fff00",chocolate:"#d2691e",coral:"#ff7f50",cornflowerblue:"#6495ed",cornsilk:"#fff8dc",crimson:"#dc143c",cyan:"#00ffff",darkblue:"#00008b",darkcyan:"#008b8b",darkgoldenrod:"#b8860b",darkgray:"#a9a9a9",darkgreen:"#006400",darkgrey:"#a9a9a9",darkkhaki:"#bdb76b",darkmagenta:"#8b008b",darkolivegreen:"#556b2f",darkorange:"#ff8c00",darkorchid:"#9932cc",darkred:"#8b0000",darksalmon:"#e9967a",darkseagreen:"#8fbc8f",darkslateblue:"#483d8b",darkslategray:"#2f4f4f",darkslategrey:"#2f4f4f",darkturquoise:"#00ced1",darkviolet:"#9400d3",deeppink:"#ff1493",deepskyblue:"#00bfff",dimgray:"#696969",dimgrey:"#696969",dodgerblue:"#1e90ff",firebrick:"#b22222",floralwhite:"#fffaf0",forestgreen:"#228b22",fuchsia:"#ff00ff",gainsboro:"#dcdcdc",ghostwhite:"#f8f8ff",gold:"#ffd700",goldenrod:"#daa520",gray:"#808080",green:"#008000",greenyellow:"#adff2f",grey:"#808080",honeydew:"#f0fff0",hotpink:"#ff69b4",indianred:"#cd5c5c",indigo:"#4b0082",ivory:"#fffff0",khaki:"#f0e68c",lavender:"#e6e6fa",lavenderblush:"#fff0f5",lawngreen:"#7cfc00",lemonchiffon:"#fffacd",lightblue:"#add8e6",lightcoral:"#f08080",lightcyan:"#e0ffff",lightgoldenrodyellow:"#fafad2",lightgray:"#d3d3d3",lightgreen:"#90ee90",lightgrey:"#d3d3d3",lightpink:"#ffb6c1",lightsalmon:"#ffa07a",lightseagreen:"#20b2aa",lightskyblue:"#87cefa",lightslategray:"#778899",lightslategrey:"#778899",lightsteelblue:"#b0c4de",lightyellow:"#ffffe0",lime:"#00ff00",limegreen:"#32cd32",linen:"#faf0e6",magenta:"#ff00ff",maroon:"#800000",mediumaquamarine:"#66cdaa",mediumblue:"#0000cd",mediumorchid:"#ba55d3",mediumpurple:"#9370db",mediumseagreen:"#3cb371",mediumslateblue:"#7b68ee",mediumspringgreen:"#00fa9a",mediumturquoise:"#48d1cc",mediumvioletred:"#c71585",midnightblue:"#191970",mintcream:"#f5fffa",mistyrose:"#ffe4e1",moccasin:"#ffe4b5",navajowhite:"#ffdead",navy:"#000080",oldlace:"#fdf5e6",olive:"#808000",olivedrab:"#6b8e23",orange:"#ffa500",orangered:"#ff4500",orchid:"#da70d6",palegoldenrod:"#eee8aa",palegreen:"#98fb98",paleturquoise:"#afeeee",palevioletred:"#db7093",papayawhip:"#ffefd5",peachpuff:"#ffdab9",peru:"#cd853f",pink:"#ffc0cb",plum:"#dda0dd",powderblue:"#b0e0e6",purple:"#800080",red:"#ff0000",rosybrown:"#bc8f8f",royalblue:"#4169e1",saddlebrown:"#8b4513",salmon:"#fa8072",sandybrown:"#f4a460",seagreen:"#2e8b57",seashell:"#fff5ee",sienna:"#a0522d",silver:"#c0c0c0",skyblue:"#87ceeb",slateblue:"#6a5acd",slategray:"#708090",slategrey:"#708090",snow:"#fffafa",springgreen:"#00ff7f",steelblue:"#4682b4",tan:"#d2b48c",teal:"#008080",thistle:"#d8bfd8",tomato:"#ff6347",turquoise:"#40e0d0",violet:"#ee82ee",wheat:"#f5deb3",white:"#ffffff",whitesmoke:"#f5f5f5",yellow:"#ffff00",yellowgreen:"#9acd32"});bh.forEach(function(a,b){bh.set(a,be(b,bb,bk))}),d3.hsl=function(a,b,c){return arguments.length===1?a instanceof bj?bi(a.h,a.s,a.l):be(""+a,bf,bi):bi(+a,+b,+c)},bj.prototype.brighter=function(a){return a=Math.pow(.7,arguments.length?a:1),bi(this.h,this.s,this.l/a)},bj.prototype.darker=function(a){return a=Math.pow(.7,arguments.length?a:1),bi(this.h,this.s,a*this.l)},bj.prototype.rgb=function(){return bk(this.h,this.s,this.l)},bj.prototype.toString=function(){return this.rgb().toString()};var bm=function(a,b){return b.querySelector(a)},bn=function(a,b){return b.querySelectorAll(a)},bo=document.documentElement,bp=bo.matchesSelector||bo.webkitMatchesSelector||bo.mozMatchesSelector||bo.msMatchesSelector||bo.oMatchesSelector,bq=function(a,b){return bp.call(a,b)};typeof Sizzle=="function"&&(bm=function(a,b){return Sizzle(a,b)[0]},bn=function(a,b){return Sizzle.uniqueSort(Sizzle(a,b))},bq=Sizzle.matchesSelector);var br=[];d3.selection=function(){return bz},d3.selection.prototype=br,br.select=function(a){var b=[],c,d,e,f;typeof a!="function"&&(a=bs(a));for(var g=-1,h=this.length;++g<h;){b.push(c=[]),c.parentNode=(e=this[g]).parentNode;for(var i=-1,j=e.length;++i<j;)(f=e[i])?(c.push(d=a.call(f,f.__data__,i)),d&&"__data__"in f&&(d.__data__=f.__data__)):c.push(null)}return bl(b)},br.selectAll=function(a){var b=[],c,d;typeof a!="function"&&(a=bt(a));for(var e=-1,g=this.length;++e<g;)for(var h=this[e],i=-1,j=h.length;++i<j;)if(d=h[i])b.push(c=f(a.call(d,d.__data__,i))),c.parentNode=d;return bl(b)},br.attr=function(a,b){function d(){this.removeAttribute(a)}function e(){this.removeAttributeNS(a.space,a.local)}function f(){this.setAttribute(a,b)}function g(){this.setAttributeNS(a.space,a.local,b)}function h(){var c=b.apply(this,arguments);c==null?this.removeAttribute(a):this.setAttribute(a,c)}function i(){var c=b.apply(this,arguments);c==null?this.removeAttributeNS(a.space,a.local):this.setAttributeNS(a.space,a.local,c)}a=d3.ns.qualify(a);if(arguments.length<2){var c=this.node();return a.local?c.getAttributeNS(a.space,a.local):c.getAttribute(a)}return this.each(b==null?a.local?e:d:typeof b=="function"?a.local?i:h:a.local?g:f)},br.classed=function(a,b){var c=a.split(bu),d=c.length,e=-1;if(arguments.length>1){while(++e<d)bv.call(this,c[e],b);return this}while(++e<d)if(!bv.call(this,c[e]))return!1;return!0};var bu=/\s+/g;br.style=function(a,b,c){function d(){this.style.removeProperty(a)}function e(){this.style.setProperty(a,b,c)}function f(){var d=b.apply(this,arguments);d==null?this.style.removeProperty(a):this.style.setProperty(a,d,c)}return arguments.length<3&&(c=""),arguments.length<2?window.getComputedStyle(this.node(),null).getPropertyValue(a):this.each(b==null?d:typeof b=="function"?f:e)},br.property=function(a,b){function c(){delete this[a]}function d(){this[a]=b}function e(){var c=b.apply(this,arguments);c==null?delete this[a]:this[a]=c}return arguments.length<2?this.node()[a]:this.each(b==null?c:typeof b=="function"?e:d)},br.text=function(a){return arguments.length<1?this.node().textContent:this.each(typeof a=="function"?function(){var b=a.apply(this,arguments);this.textContent=b==null?"":b}:a==null?function(){this.textContent=""}:function(){this.textContent=a})},br.html=function(a){return arguments.length<1?this.node().innerHTML:this.each(typeof a=="function"?function(){var b=a.apply(this,arguments);this.innerHTML=b==null?"":b}:a==null?function(){this.innerHTML=""}:function(){this.innerHTML=a})},br.append=function(a){function b(){return this.appendChild(document.createElementNS(this.namespaceURI,a))}function c(){return this.appendChild(document.createElementNS(a.space,a.local))}return a=d3.ns.qualify(a),this.select(a.local?c:b)},br.insert=function(a,b){function c(){return this.insertBefore(document.createElementNS(this.namespaceURI,a),bm(b,this))}function d(){return this.insertBefore(document.createElementNS(a.space,a.local),bm(b,this))}return a=d3.ns.qualify(a),this.select(a.local?d:c)},br.remove=function(){return this.each(function(){var a=this.parentNode;a&&a.removeChild(this)})},br.data=function(a,b){function g(a,c){var d,e=a.length,f=c.length,g=Math.min(e,f),l=Math.max(e,f),m=[],n=[],o=[],p,q;if(b){var r=new k,s=[],t,u=c.length;for(d=-1;++d<e;)t=b.call(p=a[d],p.__data__,d),r.has(t)?o[u++]=p:r.set(t,p),s.push(t);for(d=-1;++d<f;)t=b.call(c,q=c[d],d),r.has(t)?(m[d]=p=r.get(t),p.__data__=q,n[d]=o[d]=null):(n[d]=bw(q),m[d]=o[d]=null),r.remove(t);for(d=-1;++d<e;)r.has(s[d])&&(o[d]=a[d])}else{for(d=-1;++d<g;)p=a[d],q=c[d],p?(p.__data__=q,m[d]=p,n[d]=o[d]=null):(n[d]=bw(q),m[d]=o[d]=null);for(;d<f;++d)n[d]=bw(c[d]),m[d]=o[d]=null;for(;d<l;++d)o[d]=a[d],n[d]=m[d]=null}n.update=m,n.parentNode=m.parentNode=o.parentNode=a.parentNode,h.push(n),i.push(m),j.push(o)}var c=-1,d=this.length,e,f;if(!arguments.length){a=new Array(d=(e=this[0]).length);while(++c<d)if(f=e[c])a[c]=f.__data__;return a}var h=bA([]),i=bl([]),j=bl([]);if(typeof a=="function")while(++c<d)g(e=this[c],a.call(e,e.parentNode.__data__,c));else while(++c<d)g(e=this[c],a);return i.enter=function(){return h},i.exit=function(){return j},i},br.datum=br.map=function(a){return arguments.length<1?this.property("__data__"):this.property("__data__",a)},br.filter=function(a){var b=[],c,d,e;typeof a!="function"&&(a=bx(a));for(var f=0,g=this.length;f<g;f++){b.push(c=[]),c.parentNode=(d=this[f]).parentNode;for(var h=0,i=d.length;h<i;h++)(e=d[h])&&a.call(e,e.__data__,h)&&c.push(e)}return bl(b)},br.order=function(){for(var a=-1,b=this.length;++a<b;)for(var c=this[a],d=c.length-1,e=c[d],f;--d>=0;)if(f=c[d])e&&e!==f.nextSibling&&e.parentNode.insertBefore(f,e),e=f;return this},br.sort=function(a){a=by.apply(this,arguments);for(var b=-1,c=this.length;++b<c;)this[b].sort(a);return this.order()},br.on=function(a,b,c){arguments.length<3&&(c=!1);var d="__on"+a,e=a.indexOf(".");return e>0&&(a=a.substring(0,e)),arguments.length<2?(e=this.node()[d])&&e._:this.each(function(e,f){function i(a){var c=d3.event;d3.event=a;try{b.call(g,g.__data__,f)}finally{d3.event=c}}var g=this,h=g[d];h&&(g.removeEventListener(a,h,h.$),delete g[d]),b&&(g.addEventListener(a,g[d]=i,i.$=c),i._=b)})},br.each=function(a){for(var b=-1,c=this.length;++b<c;)for(var d=this[b],e=-1,f=d.length;++e<f;){var g=d[e];g&&a.call(g,g.__data__,e,b)}return this},br.call=function(a){return a.apply(this,(arguments[0]=this,arguments)),this},br.empty=function(){return!this.node()},br.node=function(a){for(var b=0,c=this.length;b<c;b++)for(var d=this[b],e=0,f=d.length;e<f;e++){var g=d[e];if(g)return g}return null},br.transition=function(){var a=[],b,c;for(var d=-1,e=this.length;++d<e;){a.push(b=[]);for(var f=this[d],g=-1,h=f.length;++g<h;)b.push((c=f[g])?{node:c,delay:bM,duration:bN}:null)}return bC(a,bI||++bH,Date.now())};var bz=bl([[document]]);bz[0].parentNode=bo,d3.select=function(a){return typeof a=="string"?bz.select(a):bl([[a]])},d3.selectAll=function(a){return typeof a=="string"?bz.selectAll(a):bl([f(a)])};var bB=[];d3.selection.enter=bA,d3.selection.enter.prototype=bB,bB.append=br.append,bB.insert=br.insert,bB.empty=br.empty,bB.node=br.node,bB.select=function(a){var b=[],c,d,e,f,g;for(var h=-1,i=this.length;++h<i;){e=(f=this[h]).update,b.push(c=[]),c.parentNode=f.parentNode;for(var j=-1,k=f.length;++j<k;)(g=f[j])?(c.push(e[j]=d=a.call(f.parentNode,g.__data__,j)),d.__data__=g.__data__):c.push(null)}return bl(b)};var bD={},bG=[],bH=0,bI=0,bJ=0,bK=250,bL=d3.ease("cubic-in-out"),bM=bJ,bN=bK,bO=bL;bG.call=br.call,d3.transition=function(a){return arguments.length?bI?a.transition():a:bz.transition()},d3.transition.prototype=bG,bG.select=function(a){var b=[],c,d,e;typeof a!="function"&&(a=bs(a));for(var f=-1,g=this.length;++f<g;){b.push(c=[]);for(var h=this[f],i=-1,j=h.length;++i<j;)(e=h[i])&&(d=a.call(e.node,e.node.__data__,i))?("__data__"in e.node&&(d.__data__=e.node.__data__),c.push({node:d,delay:e.delay,duration:e.duration})):c.push(null)}return bC(b,this.id,this.time).ease(this.ease())},bG.selectAll=function(a){var b=[],c,d,e;typeof a!="function"&&(a=bt(a));for(var f=-1,g=this.length;++f<g;)for(var h=this[f],i=-1,j=h.length;++i<j;)if(e=h[i]){d=a.call(e.node,e.node.__data__,i),b.push(c=[]);for(var k=-1,l=d.length;++k<l;)c.push({node:d[k],delay:e.delay,duration:e.duration})}return bC(b,this.id,this.time).ease(this.ease())},bG.attr=function(a,b){return this.attrTween(a,bF(a,b))},bG.attrTween=function(a,b){function d(a,d){var e=b.call(this,a,d,this.getAttribute(c));return e===bD?(this.removeAttribute(c),null):e&&function(a){this.setAttribute(c,e(a))}}function e(a,d){var e=b.call(this,a,d,this.getAttributeNS(c.space,c.local));return e===bD?(this.removeAttributeNS(c.space,c.local),null):e&&function(a){this.setAttributeNS(c.space,c.local,e(a))}}var c=d3.ns.qualify(a);return this.tween("attr."+a,c.local?e:d)},bG.style=function(a,b,c){return arguments.length<3&&(c=""),this.styleTween(a,bF(a,b),c)},bG.styleTween=function(a,b,c){return arguments.length<3&&(c=""),this.tween("style."+a,function(d,e){var f=b.call(this,d,e,window.getComputedStyle(this,null).getPropertyValue(a));return f===bD?(this.style.removeProperty(a),null):f&&function(b){this.style.setProperty(a,f(b),c)}})},bG.text=function(a){return this.tween("text",function(b,c){this.textContent=typeof a=="function"?a.call(this,b,c):a})},bG.remove=function(){return this.each("end.transition",function(){var a;!this.__transition__&&(a=this.parentNode)&&a.removeChild(this)})},bG.delay=function(a){var b=this;return b.each(typeof a=="function"?function(c,d,e){b[e][d].delay=a.apply(this,arguments)|0}:(a|=0,function(c,d,e){b[e][d].delay=a}))},bG.duration=function(a){var b=this;return b.each(typeof a=="function"?function(c,d,e){b[e][d].duration=Math.max(1,a.apply(this,arguments)|0)}:(a=Math.max(1,a|0),function(c,d,e){b[e][d].duration=a}))},bG.transition=function(){return this.select(n)};var bQ=null,bR,bS;d3.timer=function(a,b,c){var d=!1,e,f=bQ;if(arguments.length<3){if(arguments.length<2)b=0;else if(!isFinite(b))return;c=Date.now()}while(f){if(f.callback===a){f.then=c,f.delay=b,d=!0;break}e=f,f=f.next}d||(bQ={callback:a,then:c,delay:b,next:bQ}),bR||(bS=clearTimeout(bS),bR=1,bV(bT))},d3.timer.flush=function(){var a,b=Date.now(),c=bQ;while(c)a=b-c.then,c.delay||(c.flush=c.callback(a)),c=c.next;bU()};var bV=window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(a){setTimeout(a,17)};d3.transform=function(a){var b=document.createElementNS(d3.ns.prefix.svg,"g"),c={a:1,b:0,c:0,d:1,e:0,f:0};return(d3.transform=function(a){b.setAttribute("transform",a);var d=b.transform.baseVal.consolidate();return new bW(d?d.matrix:c)})(a)},bW.prototype.toString=function(){return"translate("+this.translate+")rotate("+this.rotate+")skewX("+this.skew+")scale("+this.scale+")"};var b$=180/Math.PI;d3.mouse=function(a){return ca(a,X())};var b_=/WebKit/.test(navigator.userAgent)?-1:0;d3.touches=function(a,b){return arguments.length<2&&(b=X().touches),b?f(b).map(function(b){var c=ca(a,b);return c.identifier=b.identifier,c}):[]},d3.scale={},d3.scale.linear=function(){return cg([0,1],[0,1],d3.interpolate,!1)},d3.scale.log=function(){return co(d3.scale.linear(),cq)};var cp=d3.format(".0e");cq.pow=function(a){return Math.pow(10,a)},cr.pow=function(a){return-Math.pow(10,-a)},d3.scale.pow=function(){return cs(d3.scale.linear(),1)},d3.scale.sqrt=function(){return d3.scale.pow().exponent(.5)},d3.scale.ordinal=function(){return cu([],{t:"range",x:[]})},d3.scale.category10=function(){return d3.scale.ordinal().range(cv)},d3.scale.category20=function(){return d3.scale.ordinal().range(cw)},d3.scale.category20b=function(){return d3.scale.ordinal().range(cx)},d3.scale.category20c=function(){return d3.scale.ordinal().range(cy)};var cv=["#1f77b4","#ff7f0e","#2ca02c","#d62728","#9467bd","#8c564b","#e377c2","#7f7f7f","#bcbd22","#17becf"],cw=["#1f77b4","#aec7e8","#ff7f0e","#ffbb78","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5","#8c564b","#c49c94","#e377c2","#f7b6d2","#7f7f7f","#c7c7c7","#bcbd22","#dbdb8d","#17becf","#9edae5"],cx=["#393b79","#5254a3","#6b6ecf","#9c9ede","#637939","#8ca252","#b5cf6b","#cedb9c","#8c6d31","#bd9e39","#e7ba52","#e7cb94","#843c39","#ad494a","#d6616b","#e7969c","#7b4173","#a55194","#ce6dbd","#de9ed6"],cy=["#3182bd","#6baed6","#9ecae1","#c6dbef","#e6550d","#fd8d3c","#fdae6b","#fdd0a2","#31a354","#74c476","#a1d99b","#c7e9c0","#756bb1","#9e9ac8","#bcbddc","#dadaeb","#636363","#969696","#bdbdbd","#d9d9d9"];d3.scale.quantile=function(){return cz([],[])},d3.scale.quantize=function(){return cA(0,1,[0,1])},d3.scale.identity=function(){return cB([0,1])},d3.svg={},d3.svg.arc=function(){function e(){var e=a.apply(this,arguments),f=b.apply(this,arguments),g=c.apply(this,arguments)+cC,h=d.apply(this,arguments)+cC,i=(h<g&&(i=g,g=h,h=i),h-g),j=i<Math.PI?"0":"1",k=Math.cos(g),l=Math.sin(g),m=Math.cos(h),n=Math.sin(h);return i>=cD?e?"M0,"+f+"A"+f+","+f+" 0 1,1 0,"+ -f+"A"+f+","+f+" 0 1,1 0,"+f+"M0,"+e+"A"+e+","+e+" 0 1,0 0,"+ -e+"A"+e+","+e+" 0 1,0 0,"+e+"Z":"M0,"+f+"A"+f+","+f+" 0 1,1 0,"+ -f+"A"+f+","+f+" 0 1,1 0,"+f+"Z":e?"M"+f*k+","+f*l+"A"+f+","+f+" 0 "+j+",1 "+f*m+","+f*n+"L"+e*m+","+e*n+"A"+e+","+e+" 0 "+j+",0 "+e*k+","+e*l+"Z":"M"+f*k+","+f*l+"A"+f+","+f+" 0 "+j+",1 "+f*m+","+f*n+"L0,0"+"Z"}var a=cE,b=cF,c=cG,d=cH;return e.innerRadius=function(b){return arguments.length?(a=d3.functor(b),e):a},e.outerRadius=function(a){return arguments.length?(b=d3.functor(a),e):b},e.startAngle=function(a){return arguments.length?(c=d3.functor(a),e):c},e.endAngle=function(a){return arguments.length?(d=d3.functor(a),e):d},e.centroid=function(){var e=(a.apply(this,arguments)+b.apply(this,arguments))/2,f=(c.apply(this,arguments)+d.apply(this,arguments))/2+cC;return[Math.cos(f)*e,Math.sin(f)*e]},e};var cC=-Math.PI/2,cD=2*Math.PI-1e-6;d3.svg.line=function(){return cI(Object)};var cM="linear",cN=d3.map({linear:cO,"step-before":cP,"step-after":cQ,basis:cW,"basis-open":cX,"basis-closed":cY,bundle:cZ,cardinal:cT,"cardinal-open":cR,"cardinal-closed":cS,monotone:dg}),c_=[0,2/3,1/3,0],da=[0,1/3,2/3,0],db=[0,1/6,2/3,1/6];d3.svg.line.radial=function(){var a=cI(dh);return a.radius=a.x,delete a.x,a.angle=a.y,delete a.y,a},cP.reverse=cQ,cQ.reverse=cP,d3.svg.area=function(){return di(Object)},d3.svg.area.radial=function(){var a=di(dh);return a.radius=a.x,delete a.x,a.innerRadius=a.x0,delete a.x0,a.outerRadius=a.x1,delete a.x1,a.angle=a.y,delete a.y,a.startAngle=a.y0,delete a.y0,a.endAngle=a.y1,delete a.y1,a},d3.svg.chord=function(){function f(c,d){var e=g(this,a,c,d),f=g(this,b,c,d);return"M"+e.p0+i(e.r,e.p1,e.a1-e.a0)+(h(e,f)?j(e.r,e.p1,e.r,e.p0):j(e.r,e.p1,f.r,f.p0)+i(f.r,f.p1,f.a1-f.a0)+j(f.r,f.p1,e.r,e.p0))+"Z"}function g(a,b,f,g){var h=b.call(a,f,g),i=c.call(a,h,g),j=d.call(a,h,g)+cC,k=e.call(a,h,g)+cC;return{r:i,a0:j,a1:k,p0:[i*Math.cos(j),i*Math.sin(j)],p1:[i*Math.cos(k),i*Math.sin(k)]}}function h(a,b){return a.a0==b.a0&&a.a1==b.a1}function i(a,b,c){return"A"+a+","+a+" 0 "+ +(c>Math.PI)+",1 "+b}function j(a,b,c,d){return"Q 0,0 "+d}var a=dl,b=dm,c=dn,d=cG,e=cH;return f.radius=function(a){return arguments.length?(c=d3.functor(a),f):c},f.source=function(b){return arguments.length?(a=d3.functor(b),f):a},f.target=function(a){return arguments.length?(b=d3.functor(a),f):b},f.startAngle=function(a){return arguments.length?(d=d3.functor(a),f):d},f.endAngle=function(a){return arguments.length?(e=d3.functor(a),f):e},f},d3.svg.diagonal=function(){function d(d,e){var f=a.call(this,d,e),g=b.call(this,d,e),h=(f.y+g.y)/2,i=[f,{x:f.x,y:h},{x:g.x,y:h},g];return i=i.map(c),"M"+i[0]+"C"+i[1]+" "+i[2]+" "+i[3]}var a=dl,b=dm,c=dr;return d.source=function(b){return arguments.length?(a=d3.functor(b),d):a},d.target=function(a){return arguments.length?(b=d3.functor(a),d):b},d.projection=function(a){return arguments.length?(c=a,d):c},d},d3.svg.diagonal.radial=function(){var a=d3.svg.diagonal(),b=dr,c=a.projection;return a.projection=function(a){return arguments.length?c(ds(b=a)):b},a},d3.svg.mouse=d3.mouse,d3.svg.touches=d3.touches,d3.svg.symbol=function(){function c(c,d){return(dw.get(a.call(this,c,d))||dv)(b.call(this,c,d))}var a=du,b=dt;return c.type=function(b){return arguments.length?(a=d3.functor(b),c):a},c.size=function(a){return arguments.length?(b=d3.functor(a),c):b},c};var dw=d3.map({circle
+:dv,cross:function(a){var b=Math.sqrt(a/5)/2;return"M"+ -3*b+","+ -b+"H"+ -b+"V"+ -3*b+"H"+b+"V"+ -b+"H"+3*b+"V"+b+"H"+b+"V"+3*b+"H"+ -b+"V"+b+"H"+ -3*b+"Z"},diamond:function(a){var b=Math.sqrt(a/(2*dy)),c=b*dy;return"M0,"+ -b+"L"+c+",0"+" 0,"+b+" "+ -c+",0"+"Z"},square:function(a){var b=Math.sqrt(a)/2;return"M"+ -b+","+ -b+"L"+b+","+ -b+" "+b+","+b+" "+ -b+","+b+"Z"},"triangle-down":function(a){var b=Math.sqrt(a/dx),c=b*dx/2;return"M0,"+c+"L"+b+","+ -c+" "+ -b+","+ -c+"Z"},"triangle-up":function(a){var b=Math.sqrt(a/dx),c=b*dx/2;return"M0,"+ -c+"L"+b+","+c+" "+ -b+","+c+"Z"}});d3.svg.symbolTypes=dw.keys();var dx=Math.sqrt(3),dy=Math.tan(30*Math.PI/180);d3.svg.axis=function(){function k(k){k.each(function(){var k=d3.select(this),l=h==null?a.ticks?a.ticks.apply(a,g):a.domain():h,m=i==null?a.tickFormat?a.tickFormat.apply(a,g):String:i,n=dB(a,l,j),o=k.selectAll(".minor").data(n,String),p=o.enter().insert("line","g").attr("class","tick minor").style("opacity",1e-6),q=d3.transition(o.exit()).style("opacity",1e-6).remove(),r=d3.transition(o).style("opacity",1),s=k.selectAll("g").data(l,String),t=s.enter().insert("g","path").style("opacity",1e-6),u=d3.transition(s.exit()).style("opacity",1e-6).remove(),v=d3.transition(s).style("opacity",1),w,x=cd(a),y=k.selectAll(".domain").data([0]),z=y.enter().append("path").attr("class","domain"),A=d3.transition(y),B=a.copy(),C=this.__chart__||B;this.__chart__=B,t.append("line").attr("class","tick"),t.append("text"),v.select("text").text(m);switch(b){case"bottom":w=dz,p.attr("y2",d),r.attr("x2",0).attr("y2",d),t.select("line").attr("y2",c),t.select("text").attr("y",Math.max(c,0)+f),v.select("line").attr("x2",0).attr("y2",c),v.select("text").attr("x",0).attr("y",Math.max(c,0)+f).attr("dy",".71em").attr("text-anchor","middle"),A.attr("d","M"+x[0]+","+e+"V0H"+x[1]+"V"+e);break;case"top":w=dz,p.attr("y2",-d),r.attr("x2",0).attr("y2",-d),t.select("line").attr("y2",-c),t.select("text").attr("y",-(Math.max(c,0)+f)),v.select("line").attr("x2",0).attr("y2",-c),v.select("text").attr("x",0).attr("y",-(Math.max(c,0)+f)).attr("dy","0em").attr("text-anchor","middle"),A.attr("d","M"+x[0]+","+ -e+"V0H"+x[1]+"V"+ -e);break;case"left":w=dA,p.attr("x2",-d),r.attr("x2",-d).attr("y2",0),t.select("line").attr("x2",-c),t.select("text").attr("x",-(Math.max(c,0)+f)),v.select("line").attr("x2",-c).attr("y2",0),v.select("text").attr("x",-(Math.max(c,0)+f)).attr("y",0).attr("dy",".32em").attr("text-anchor","end"),A.attr("d","M"+ -e+","+x[0]+"H0V"+x[1]+"H"+ -e);break;case"right":w=dA,p.attr("x2",d),r.attr("x2",d).attr("y2",0),t.select("line").attr("x2",c),t.select("text").attr("x",Math.max(c,0)+f),v.select("line").attr("x2",c).attr("y2",0),v.select("text").attr("x",Math.max(c,0)+f).attr("y",0).attr("dy",".32em").attr("text-anchor","start"),A.attr("d","M"+e+","+x[0]+"H0V"+x[1]+"H"+e)}if(a.ticks)t.call(w,C),v.call(w,B),u.call(w,B),p.call(w,C),r.call(w,B),q.call(w,B);else{var D=B.rangeBand()/2,E=function(a){return B(a)+D};t.call(w,E),v.call(w,E)}})}var a=d3.scale.linear(),b="bottom",c=6,d=6,e=6,f=3,g=[10],h=null,i,j=0;return k.scale=function(b){return arguments.length?(a=b,k):a},k.orient=function(a){return arguments.length?(b=a,k):b},k.ticks=function(){return arguments.length?(g=arguments,k):g},k.tickValues=function(a){return arguments.length?(h=a,k):h},k.tickFormat=function(a){return arguments.length?(i=a,k):i},k.tickSize=function(a,b,f){if(!arguments.length)return c;var g=arguments.length-1;return c=+a,d=g>1?+b:c,e=g>0?+arguments[g]:c,k},k.tickPadding=function(a){return arguments.length?(f=+a,k):f},k.tickSubdivide=function(a){return arguments.length?(j=+a,k):j},k},d3.svg.brush=function(){function g(a){a.each(function(){var a=d3.select(this),e=a.selectAll(".background").data([0]),f=a.selectAll(".extent").data([0]),l=a.selectAll(".resize").data(d,String),m;a.style("pointer-events","all").on("mousedown.brush",k).on("touchstart.brush",k),e.enter().append("rect").attr("class","background").style("visibility","hidden").style("cursor","crosshair"),f.enter().append("rect").attr("class","extent").style("cursor","move"),l.enter().append("g").attr("class",function(a){return"resize "+a}).style("cursor",function(a){return dC[a]}).append("rect").attr("x",function(a){return/[ew]$/.test(a)?-3:null}).attr("y",function(a){return/^[ns]/.test(a)?-3:null}).attr("width",6).attr("height",6).style("visibility","hidden"),l.style("display",g.empty()?"none":null),l.exit().remove(),b&&(m=cd(b),e.attr("x",m[0]).attr("width",m[1]-m[0]),i(a)),c&&(m=cd(c),e.attr("y",m[0]).attr("height",m[1]-m[0]),j(a)),h(a)})}function h(a){a.selectAll(".resize").attr("transform",function(a){return"translate("+e[+/e$/.test(a)][0]+","+e[+/^s/.test(a)][1]+")"})}function i(a){a.select(".extent").attr("x",e[0][0]),a.selectAll(".extent,.n>rect,.s>rect").attr("width",e[1][0]-e[0][0])}function j(a){a.select(".extent").attr("y",e[0][1]),a.selectAll(".extent,.e>rect,.w>rect").attr("height",e[1][1]-e[0][1])}function k(){function x(){var a=d3.event.changedTouches;return a?d3.touches(d,a)[0]:d3.mouse(d)}function y(){d3.event.keyCode==32&&(q||(r=null,s[0]-=e[1][0],s[1]-=e[1][1],q=2),W())}function z(){d3.event.keyCode==32&&q==2&&(s[0]+=e[1][0],s[1]+=e[1][1],q=0,W())}function A(){var a=x(),d=!1;t&&(a[0]+=t[0],a[1]+=t[1]),q||(d3.event.altKey?(r||(r=[(e[0][0]+e[1][0])/2,(e[0][1]+e[1][1])/2]),s[0]=e[+(a[0]<r[0])][0],s[1]=e[+(a[1]<r[1])][1]):r=null),o&&B(a,b,0)&&(i(m),d=!0),p&&B(a,c,1)&&(j(m),d=!0),d&&(h(m),l({type:"brush",mode:q?"move":"resize"}))}function B(a,b,c){var d=cd(b),g=d[0],h=d[1],i=s[c],j=e[1][c]-e[0][c],k,l;q&&(g-=i,h-=j+i),k=Math.max(g,Math.min(h,a[c])),q?l=(k+=i)+j:(r&&(i=Math.max(g,Math.min(h,2*r[c]-k))),i<k?(l=k,k=i):l=i);if(e[0][c]!==k||e[1][c]!==l)return f=null,e[0][c]=k,e[1][c]=l,!0}function C(){A(),m.style("pointer-events","all").selectAll(".resize").style("display",g.empty()?"none":null),d3.select("body").style("cursor",null),u.on("mousemove.brush",null).on("mouseup.brush",null).on("touchmove.brush",null).on("touchend.brush",null).on("keydown.brush",null).on("keyup.brush",null),l({type:"brushend"}),W()}var d=this,k=d3.select(d3.event.target),l=a.of(d,arguments),m=d3.select(d),n=k.datum(),o=!/^(n|s)$/.test(n)&&b,p=!/^(e|w)$/.test(n)&&c,q=k.classed("extent"),r,s=x(),t,u=d3.select(window).on("mousemove.brush",A).on("mouseup.brush",C).on("touchmove.brush",A).on("touchend.brush",C).on("keydown.brush",y).on("keyup.brush",z);if(q)s[0]=e[0][0]-s[0],s[1]=e[0][1]-s[1];else if(n){var v=+/w$/.test(n),w=+/^n/.test(n);t=[e[1-v][0]-s[0],e[1-w][1]-s[1]],s[0]=e[v][0],s[1]=e[w][1]}else d3.event.altKey&&(r=s.slice());m.style("pointer-events","none").selectAll(".resize").style("display",null),d3.select("body").style("cursor",k.style("cursor")),l({type:"brushstart"}),A(),W()}var a=Y(g,"brushstart","brush","brushend"),b=null,c=null,d=dD[0],e=[[0,0],[0,0]],f;return g.x=function(a){return arguments.length?(b=a,d=dD[!b<<1|!c],g):b},g.y=function(a){return arguments.length?(c=a,d=dD[!b<<1|!c],g):c},g.extent=function(a){var d,h,i,j,k;return arguments.length?(f=[[0,0],[0,0]],b&&(d=a[0],h=a[1],c&&(d=d[0],h=h[0]),f[0][0]=d,f[1][0]=h,b.invert&&(d=b(d),h=b(h)),h<d&&(k=d,d=h,h=k),e[0][0]=d|0,e[1][0]=h|0),c&&(i=a[0],j=a[1],b&&(i=i[1],j=j[1]),f[0][1]=i,f[1][1]=j,c.invert&&(i=c(i),j=c(j)),j<i&&(k=i,i=j,j=k),e[0][1]=i|0,e[1][1]=j|0),g):(a=f||e,b&&(d=a[0][0],h=a[1][0],f||(d=e[0][0],h=e[1][0],b.invert&&(d=b.invert(d),h=b.invert(h)),h<d&&(k=d,d=h,h=k))),c&&(i=a[0][1],j=a[1][1],f||(i=e[0][1],j=e[1][1],c.invert&&(i=c.invert(i),j=c.invert(j)),j<i&&(k=i,i=j,j=k))),b&&c?[[d,i],[h,j]]:b?[d,h]:c&&[i,j])},g.clear=function(){return f=null,e[0][0]=e[0][1]=e[1][0]=e[1][1]=0,g},g.empty=function(){return b&&e[0][0]===e[1][0]||c&&e[0][1]===e[1][1]},d3.rebind(g,a,"on")};var dC={n:"ns-resize",e:"ew-resize",s:"ns-resize",w:"ew-resize",nw:"nwse-resize",ne:"nesw-resize",se:"nwse-resize",sw:"nesw-resize"},dD=[["n","e","s","w","nw","ne","se","sw"],["e","w"],["n","s"],[]];d3.behavior={},d3.behavior.drag=function(){function c(){this.on("mousedown.drag",d).on("touchstart.drag",d)}function d(){function j(){var a=c.parentNode,b=d3.event.changedTouches;return b?d3.touches(a,b)[0]:d3.mouse(a)}function k(){if(!c.parentNode)return l();var a=j(),b=a[0]-g[0],e=a[1]-g[1];h|=b|e,g=a,W(),d({type:"drag",x:a[0]+f[0],y:a[1]+f[1],dx:b,dy:e})}function l(){d({type:"dragend"}),h&&(W(),d3.event.target===e&&i.on("click.drag",m,!0)),i.on("mousemove.drag",null).on("touchmove.drag",null).on("mouseup.drag",null).on("touchend.drag",null)}function m(){W(),i.on("click.drag",null)}var c=this,d=a.of(c,arguments),e=d3.event.target,f,g=j(),h=0,i=d3.select(window).on("mousemove.drag",k).on("touchmove.drag",k).on("mouseup.drag",l,!0).on("touchend.drag",l,!0);b?(f=b.apply(c,arguments),f=[f.x-g[0],f.y-g[1]]):f=[0,0],d({type:"dragstart"})}var a=Y(c,"drag","dragstart","dragend"),b=null;return c.origin=function(a){return arguments.length?(b=a,c):b},d3.rebind(c,a,"on")},d3.behavior.zoom=function(){function l(){this.on("mousedown.zoom",r).on("mousewheel.zoom",s).on("mousemove.zoom",t).on("DOMMouseScroll.zoom",s).on("dblclick.zoom",u).on("touchstart.zoom",v).on("touchmove.zoom",w).on("touchend.zoom",v)}function m(b){return[(b[0]-a[0])/c,(b[1]-a[1])/c]}function n(b){return[b[0]*c+a[0],b[1]*c+a[1]]}function o(a){c=Math.max(e[0],Math.min(e[1],a))}function p(b,c){c=n(c),a[0]+=b[0]-c[0],a[1]+=b[1]-c[1]}function q(b){h&&h.domain(g.range().map(function(b){return(b-a[0])/c}).map(g.invert)),j&&j.domain(i.range().map(function(b){return(b-a[1])/c}).map(i.invert)),d3.event.preventDefault(),b({type:"zoom",scale:c,translate:a})}function r(){function h(){d=1,p(d3.mouse(a),g),q(b)}function i(){d&&W(),e.on("mousemove.zoom",null).on("mouseup.zoom",null),d&&d3.event.target===c&&e.on("click.zoom",j)}function j(){W(),e.on("click.zoom",null)}var a=this,b=f.of(a,arguments),c=d3.event.target,d=0,e=d3.select(window).on("mousemove.zoom",h).on("mouseup.zoom",i),g=m(d3.mouse(a));window.focus(),W()}function s(){b||(b=m(d3.mouse(this))),o(Math.pow(2,dG()*.002)*c),p(d3.mouse(this),b),q(f.of(this,arguments))}function t(){b=null}function u(){var a=d3.mouse(this),b=m(a);o(d3.event.shiftKey?c/2:c*2),p(a,b),q(f.of(this,arguments))}function v(){var a=d3.touches(this),e=Date.now();d=c,b={},a.forEach(function(a){b[a.identifier]=m(a)}),W();if(a.length===1&&e-k<500){var g=a[0],h=m(a[0]);o(c*2),p(g,h),q(f.of(this,arguments))}k=e}function w(){var a=d3.touches(this),c=a[0],e=b[c.identifier];if(g=a[1]){var g,h=b[g.identifier];c=[(c[0]+g[0])/2,(c[1]+g[1])/2],e=[(e[0]+h[0])/2,(e[1]+h[1])/2],o(d3.event.scale*d)}p(c,e),q(f.of(this,arguments))}var a=[0,0],b,c=1,d,e=dF,f=Y(l,"zoom"),g,h,i,j,k;return l.translate=function(b){return arguments.length?(a=b.map(Number),l):a},l.scale=function(a){return arguments.length?(c=+a,l):c},l.scaleExtent=function(a){return arguments.length?(e=a==null?dF:a.map(Number),l):e},l.x=function(a){return arguments.length?(h=a,g=a.copy(),l):h},l.y=function(a){return arguments.length?(j=a,i=a.copy(),l):j},d3.rebind(l,f,"on")};var dE,dF=[0,Infinity];d3.layout={},d3.layout.bundle=function(){return function(a){var b=[],c=-1,d=a.length;while(++c<d)b.push(dH(a[c]));return b}},d3.layout.chord=function(){function j(){var a={},j=[],l=d3.range(e),m=[],n,o,p,q,r;b=[],c=[],n=0,q=-1;while(++q<e){o=0,r=-1;while(++r<e)o+=d[q][r];j.push(o),m.push(d3.range(e)),n+=o}g&&l.sort(function(a,b){return g(j[a],j[b])}),h&&m.forEach(function(a,b){a.sort(function(a,c){return h(d[b][a],d[b][c])})}),n=(2*Math.PI-f*e)/n,o=0,q=-1;while(++q<e){p=o,r=-1;while(++r<e){var s=l[q],t=m[s][r],u=d[s][t],v=o,w=o+=u*n;a[s+"-"+t]={index:s,subindex:t,startAngle:v,endAngle:w,value:u}}c.push({index:s,startAngle:p,endAngle:o,value:(o-p)/n}),o+=f}q=-1;while(++q<e){r=q-1;while(++r<e){var x=a[q+"-"+r],y=a[r+"-"+q];(x.value||y.value)&&b.push(x.value<y.value?{source:y,target:x}:{source:x,target:y})}}i&&k()}function k(){b.sort(function(a,b){return i((a.source.value+a.target.value)/2,(b.source.value+b.target.value)/2)})}var a={},b,c,d,e,f=0,g,h,i;return a.matrix=function(f){return arguments.length?(e=(d=f)&&d.length,b=c=null,a):d},a.padding=function(d){return arguments.length?(f=d,b=c=null,a):f},a.sortGroups=function(d){return arguments.length?(g=d,b=c=null,a):g},a.sortSubgroups=function(c){return arguments.length?(h=c,b=null,a):h},a.sortChords=function(c){return arguments.length?(i=c,b&&k(),a):i},a.chords=function(){return b||j(),b},a.groups=function(){return c||j(),c},a},d3.layout.force=function(){function r(a){return function(b,c,d,e,f){if(b.point!==a){var g=b.cx-a.x,h=b.cy-a.y,i=1/Math.sqrt(g*g+h*h);if((e-c)*i<k){var j=b.charge*i*i;return a.px-=g*j,a.py-=h*j,!0}if(b.point&&isFinite(i)){var j=b.pointCharge*i*i;a.px-=g*j,a.py-=h*j}}return!b.charge}}function s(b){dM(dL=b),dK=a}var a={},b=d3.dispatch("start","tick","end"),c=[1,1],d,e,f=.9,g=dR,h=dS,i=-30,j=.1,k=.8,l,m=[],n=[],o,p,q;return a.tick=function(){if((e*=.99)<.005)return b.end({type:"end",alpha:e=0}),!0;var a=m.length,d=n.length,g,h,k,l,s,t,u,v,w;for(h=0;h<d;++h){k=n[h],l=k.source,s=k.target,v=s.x-l.x,w=s.y-l.y;if(t=v*v+w*w)t=e*p[h]*((t=Math.sqrt(t))-o[h])/t,v*=t,w*=t,s.x-=v*(u=l.weight/(s.weight+l.weight)),s.y-=w*u,l.x+=v*(u=1-u),l.y+=w*u}if(u=e*j){v=c[0]/2,w=c[1]/2,h=-1;if(u)while(++h<a)k=m[h],k.x+=(v-k.x)*u,k.y+=(w-k.y)*u}if(i){dQ(g=d3.geom.quadtree(m),e,q),h=-1;while(++h<a)(k=m[h]).fixed||g.visit(r(k))}h=-1;while(++h<a)k=m[h],k.fixed?(k.x=k.px,k.y=k.py):(k.x-=(k.px-(k.px=k.x))*f,k.y-=(k.py-(k.py=k.y))*f);b.tick({type:"tick",alpha:e})},a.nodes=function(b){return arguments.length?(m=b,a):m},a.links=function(b){return arguments.length?(n=b,a):n},a.size=function(b){return arguments.length?(c=b,a):c},a.linkDistance=function(b){return arguments.length?(g=d3.functor(b),a):g},a.distance=a.linkDistance,a.linkStrength=function(b){return arguments.length?(h=d3.functor(b),a):h},a.friction=function(b){return arguments.length?(f=b,a):f},a.charge=function(b){return arguments.length?(i=typeof b=="function"?b:+b,a):i},a.gravity=function(b){return arguments.length?(j=b,a):j},a.theta=function(b){return arguments.length?(k=b,a):k},a.alpha=function(c){return arguments.length?(e?c>0?e=c:e=0:c>0&&(b.start({type:"start",alpha:e=c}),d3.timer(a.tick)),a):e},a.start=function(){function s(a,c){var d=t(b),e=-1,f=d.length,g;while(++e<f)if(!isNaN(g=d[e][a]))return g;return Math.random()*c}function t(){if(!l){l=[];for(d=0;d<e;++d)l[d]=[];for(d=0;d<f;++d){var a=n[d];l[a.source.index].push(a.target),l[a.target.index].push(a.source)}}return l[b]}var b,d,e=m.length,f=n.length,j=c[0],k=c[1],l,r;for(b=0;b<e;++b)(r=m[b]).index=b,r.weight=0;o=[],p=[];for(b=0;b<f;++b)r=n[b],typeof r.source=="number"&&(r.source=m[r.source]),typeof r.target=="number"&&(r.target=m[r.target]),o[b]=g.call(this,r,b),p[b]=h.call(this,r,b),++r.source.weight,++r.target.weight;for(b=0;b<e;++b)r=m[b],isNaN(r.x)&&(r.x=s("x",j)),isNaN(r.y)&&(r.y=s("y",k)),isNaN(r.px)&&(r.px=r.x),isNaN(r.py)&&(r.py=r.y);q=[];if(typeof i=="function")for(b=0;b<e;++b)q[b]=+i.call(this,m[b],b);else for(b=0;b<e;++b)q[b]=i;return a.resume()},a.resume=function(){return a.alpha(.1)},a.stop=function(){return a.alpha(0)},a.drag=function(){d||(d=d3.behavior.drag().origin(Object).on("dragstart",s).on("drag",dP).on("dragend",dO)),this.on("mouseover.force",dM).on("mouseout.force",dN).call(d)},d3.rebind(a,b,"on")};var dK,dL;d3.layout.partition=function(){function c(a,b,d,e){var f=a.children;a.x=b,a.y=a.depth*e,a.dx=d,a.dy=e;if(f&&(h=f.length)){var g=-1,h,i,j;d=a.value?d/a.value:0;while(++g<h)c(i=f[g],b,j=i.value*d,e),b+=j}}function d(a){var b=a.children,c=0;if(b&&(f=b.length)){var e=-1,f;while(++e<f)c=Math.max(c,d(b[e]))}return 1+c}function e(e,f){var g=a.call(this,e,f);return c(g[0],0,b[0],b[1]/d(g[0])),g}var a=d3.layout.hierarchy(),b=[1,1];return e.size=function(a){return arguments.length?(b=a,e):b},ef(e,a)},d3.layout.pie=function(){function f(g,h){var i=g.map(function(b,c){return+a.call(f,b,c)}),j=+(typeof c=="function"?c.apply(this,arguments):c),k=((typeof e=="function"?e.apply(this,arguments):e)-c)/d3.sum(i),l=d3.range(g.length);b!=null&&l.sort(b===dT?function(a,b){return i[b]-i[a]}:function(a,c){return b(g[a],g[c])});var m=[];return l.forEach(function(a){m[a]={data:g[a],value:d=i[a],startAngle:j,endAngle:j+=d*k}}),m}var a=Number,b=dT,c=0,e=2*Math.PI;return f.value=function(b){return arguments.length?(a=b,f):a},f.sort=function(a){return arguments.length?(b=a,f):b},f.startAngle=function(a){return arguments.length?(c=a,f):c},f.endAngle=function(a){return arguments.length?(e=a,f):e},f};var dT={};d3.layout.stack=function(){function g(h,i){var j=h.map(function(b,c){return a.call(g,b,c)}),k=j.map(function(a,b){return a.map(function(a,b){return[e.call(g,a,b),f.call(g,a,b)]})}),l=b.call(g,k,i);j=d3.permute(j,l),k=d3.permute(k,l);var m=c.call(g,k,i),n=j.length,o=j[0].length,p,q,r;for(q=0;q<o;++q){d.call(g,j[0][q],r=m[q],k[0][q][1]);for(p=1;p<n;++p)d.call(g,j[p][q],r+=k[p-1][q][1],k[p][q][1])}return h}var a=Object,b=dZ,c=d$,d=dW,e=dU,f=dV;return g.values=function(b){return arguments.length?(a=b,g):a},g.order=function(a){return arguments.length?(b=typeof a=="function"?a:dX.get(a)||dZ,g):b},g.offset=function(a){return arguments.length?(c=typeof a=="function"?a:dY.get(a)||d$,g):c},g.x=function(a){return arguments.length?(e=a,g):e},g.y=function(a){return arguments.length?(f=a,g):f},g.out=function(a){return arguments.length?(d=a,g):d},g};var dX=d3.map({"inside-out":function(a){var b=a.length,c,d,e=a.map(d_),f=a.map(ea),g=d3.range(b).sort(function(a,b){return e[a]-e[b]}),h=0,i=0,j=[],k=[];for(c=0;c<b;++c)d=g[c],h<i?(h+=f[d],j.push(d)):(i+=f[d],k.push(d));return k.reverse().concat(j)},reverse:function(a){return d3.range(a.length).reverse()},"default":dZ}),dY=d3.map({silhouette:function(a){var b=a.length,c=a[0].length,d=[],e=0,f,g,h,i=[];for(g=0;g<c;++g){for(f=0,h=0;f<b;f++)h+=a[f][g][1];h>e&&(e=h),d.push(h)}for(g=0;g<c;++g)i[g]=(e-d[g])/2;return i},wiggle:function(a){var b=a.length,c=a[0],d=c.length,e=0,f,g,h,i,j,k,l,m,n,o=[];o[0]=m=n=0;for(g=1;g<d;++g){for(f=0,i=0;f<b;++f)i+=a[f][g][1];for(f=0,j=0,l=c[g][0]-c[g-1][0];f<b;++f){for(h=0,k=(a[f][g][1]-a[f][g-1][1])/(2*l);h<f;++h)k+=(a[h][g][1]-a[h][g-1][1])/l;j+=k*a[f][g][1]}o[g]=m-=i?j/i*l:0,m<n&&(n=m)}for(g=0;g<d;++g)o[g]-=n;return o},expand:function(a){var b=a.length,c=a[0].length,d=1/b,e,f,g,h=[];for(f=0;f<c;++f){for(e=0,g=0;e<b;e++)g+=a[e][f][1];if(g)for(e=0;e<b;e++)a[e][f][1]/=g;else for(e=0;e<b;e++)a[e][f][1]=d}for(f=0;f<c;++f)h[f]=0;return h},zero:d$});d3.layout.histogram=function(){function e(e,f){var g=[],h=e.map(b,this),i=c.call(this,h,f),j=d.call(this,i,h,f),k,f=-1,l=h.length,m=j.length-1,n=a?1:1/l,o;while(++f<m)k=g[f]=[],k.dx=j[f+1]-(k.x=j[f]),k.y=0;f=-1;while(++f<l)o=h[f],o>=i[0]&&o<=i[1]&&(k=g[d3.bisect(j,o,1,m)-1],k.y+=n,k.push(e[f]));return g}var a=!0,b=Number,c=ee,d=ec;return e.value=function(a){return arguments.length?(b=a,e):b},e.range=function(a){return arguments.length?(c=d3.functor(a),e):c},e.bins=function(a){return arguments.length?(d=typeof a=="number"?function(b){return ed(b,a)}:d3.functor(a),e):d},e.frequency=function(b){return arguments.length?(a=!!b,e):a},e},d3.layout.hierarchy=function(){function e(f,h,i){var j=b.call(g,f,h),k=ek?f:{data:f};k.depth=h,i.push(k);if(j&&(m=j.length)){var l=-1,m,n=k.children=[],o=0,p=h+1;while(++l<m)d=e(j[l],p,i),d.parent=k,n.push(d),o+=d.value;a&&n.sort(a),c&&(k.value=o)}else c&&(k.value=+c.call(g,f,h)||0);return k}function f(a,b){var d=a.children,e=0;if(d&&(i=d.length)){var h=-1,i,j=b+1;while(++h<i)e+=f(d[h],j)}else c&&(e=+c.call(g,ek?a:a.data,b)||0);return c&&(a.value=e),e}function g(a){var b=[];return e(a,0,b),b}var a=ei,b=eg,c=eh;return g.sort=function(b){return arguments.length?(a=b,g):a},g.children=function(a){return arguments.length?(b=a,g):b},g.value=function(a){return arguments.length?(c=a,g):c},g.revalue=function(a){return f(a,0),a},g};var ek=!1;d3.layout.pack=function(){function c(c,d){var e=a.call(this,c,d),f=e[0];f.x=0,f.y=0,es(f);var g=b[0],h=b[1],i=1/Math.max(2*f.r/g,2*f.r/h);return et(f,g/2,h/2,i),e}var a=d3.layout.hierarchy().sort(el),b=[1,1];return c.size=function(a){return arguments.length?(b=a,c):b},ef(c,a)},d3.layout.cluster=function(){function d(d,e){var f=a.call(this,d,e),g=f[0],h,i=0,j,k;eG(g,function(a){var c=a.children;c&&c.length?(a.x=ew(c),a.y=ev(c)):(a.x=h?i+=b(a,h):0,a.y=0,h=a)});var l=ex(g),m=ey(g),n=l.x-b(l,m)/2,o=m.x+b(m,l)/2;return eG(g,function(a){a.x=(a.x-n)/(o-n)*c[0],a.y=(1-(g.y?a.y/g.y:1))*c[1]}),f}var a=d3.layout.hierarchy().sort(null).value(null),b=ez,c=[1,1];return d.separation=function(a){return arguments.length?(b=a,d):b},d.size=function(a){return arguments.length?(c=a,d):c},ef(d,a)},d3.layout.tree=function(){function d(d,e){function h(a,c){var d=a.children,e=a._tree;if(d&&(f=d.length)){var f,g=d[0],i,k=g,l,m=-1;while(++m<f)l=d[m],h(l,i),k=j(l,i,k),i=l;eH(a);var n=.5*(g._tree.prelim+l._tree.prelim);c?(e.prelim=c._tree.prelim+b(a,c),e.mod=e.prelim-n):e.prelim=n}else c&&(e.prelim=c._tree.prelim+b(a,c))}function i(a,b){a.x=a._tree.prelim+b;var c=a.children;if(c&&(e=c.length)){var d=-1,e;b+=a._tree.mod;while(++d<e)i(c[d],b)}}function j(a,c,d){if(c){var e=a,f=a,g=c,h=a.parent.children[0],i=e._tree.mod,j=f._tree.mod,k=g._tree.mod,l=h._tree.mod,m;while(g=eB(g),e=eA(e),g&&e)h=eA(h),f=eB(f),f._tree.ancestor=a,m=g._tree.prelim+k-e._tree.prelim-i+b(g,e),m>0&&(eI(eJ(g,a,d),a,m),i+=m,j+=m),k+=g._tree.mod,i+=e._tree.mod,l+=h._tree.mod,j+=f._tree.mod;g&&!eB(f)&&(f._tree.thread=g,f._tree.mod+=k-j),e&&!eA(h)&&(h._tree.thread=e,h._tree.mod+=i-l,d=a)}return d}var f=a.call(this,d,e),g=f[0];eG(g,function(a,b){a._tree={ancestor:a,prelim:0,mod:0,change:0,shift:0,number:b?b._tree.number+1:0}}),h(g),i(g,-g._tree.prelim);var k=eC(g,eE),l=eC(g,eD),m=eC(g,eF),n=k.x-b(k,l)/2,o=l.x+b(l,k)/2,p=m.depth||1;return eG(g,function(a){a.x=(a.x-n)/(o-n)*c[0],a.y=a.depth/p*c[1],delete a._tree}),f}var a=d3.layout.hierarchy().sort(null).value(null),b=ez,c=[1,1];return d.separation=function(a){return arguments.length?(b=a,d):b},d.size=function(a){return arguments.length?(c=a,d):c},ef(d,a)},d3.layout.treemap=function(){function i(a,b){var c=-1,d=a.length,e,f;while(++c<d)f=(e=a[c]).value*(b<0?0:b),e.area=isNaN(f)||f<=0?0:f}function j(a){var b=a.children;if(b&&b.length){var c=e(a),d=[],f=b.slice(),g,h=Infinity,k,n=Math.min(c.dx,c.dy),o;i(f,c.dx*c.dy/a.value),d.area=0;while((o=f.length)>0)d.push(g=f[o-1]),d.area+=g.area,(k=l(d,n))<=h?(f.pop(),h=k):(d.area-=d.pop().area,m(d,n,c,!1),n=Math.min(c.dx,c.dy),d.length=d.area=0,h=Infinity);d.length&&(m(d,n,c,!0),d.length=d.area=0),b.forEach(j)}}function k(a){var b=a.children;if(b&&b.length){var c=e(a),d=b.slice(),f,g=[];i(d,c.dx*c.dy/a.value),g.area=0;while(f=d.pop())g.push(f),g.area+=f.area,f.z!=null&&(m(g,f.z?c.dx:c.dy,c,!d.length),g.length=g.area=0);b.forEach(k)}}function l(a,b){var c=a.area,d,e=0,f=Infinity,g=-1,i=a.length;while(++g<i){if(!(d=a[g].area))continue;d<f&&(f=d),d>e&&(e=d)}return c*=c,b*=b,c?Math.max(b*e*h/c,c/(b*f*h)):Infinity}function m(a,c,d,e){var f=-1,g=a.length,h=d.x,i=d.y,j=c?b(a.area/c):0,k;if(c==d.dx){if(e||j>d.dy)j=d.dy;while(++f<g)k=a[f],k.x=h,k.y=i,k.dy=j,h+=k.dx=Math.min(d.x+d.dx-h,j?b(k.area/j):0);k.z=!0,k.dx+=d.x+d.dx-h,d.y+=j,d.dy-=j}else{if(e||j>d.dx)j=d.dx;while(++f<g)k=a[f],k.x=h,k.y=i,k.dx=j,i+=k.dy=Math.min(d.y+d.dy-i,j?b(k.area/j):0);k.z=!1,k.dy+=d.y+d.dy-i,d.x+=j,d.dx-=j}}function n(b){var d=g||a(b),e=d[0];return e.x=0,e.y=0,e.dx=c[0],e.dy=c[1],g&&a.revalue(e),i([e],e.dx*e.dy/e.value),(g?k:j)(e),f&&(g=d),d}var a=d3.layout.hierarchy(),b=Math.round,c=[1,1],d=null,e=eK,f=!1,g,h=.5*(1+Math.sqrt(5));return n.size=function(a){return arguments.length?(c=a,n):c},n.padding=function(a){function b(b){var c=a.call(n,b,b.depth);return c==null?eK(b):eL(b,typeof c=="number"?[c,c,c,c]:c)}function c(b){return eL(b,a)}if(!arguments.length)return d;var f;return e=(d=a)==null?eK:(f=typeof a)==="function"?b:f==="number"?(a=[a,a,a,a],c):c,n},n.round=function(a){return arguments.length?(b=a?Math.round:Number,n):b!=Number},n.sticky=function(a){return arguments.length?(f=a,g=null,n):f},n.ratio=function(a){return arguments.length?(h=a,n):h},ef(n,a)},d3.csv=function(a,b){d3.text(a,"text/csv",function(a){b(a&&d3.csv.parse(a))})},d3.csv.parse=function(a){var b;return d3.csv.parseRows(a,function(a,c){if(c){var d={},e=-1,f=b.length;while(++e<f)d[b[e]]=a[e];return d}return b=a,null})},d3.csv.parseRows=function(a,b){function j(){if(f.lastIndex>=a.length)return d;if(i)return i=!1,c;var b=f.lastIndex;if(a.charCodeAt(b)===34){var e=b;while(e++<a.length)if(a.charCodeAt(e)===34){if(a.charCodeAt(e+1)!==34)break;e++}f.lastIndex=e+2;var g=a.charCodeAt(e+1);return g===13?(i=!0,a.charCodeAt(e+2)===10&&f.lastIndex++):g===10&&(i=!0),a.substring(b+1,e).replace(/""/g,'"')}var h=f.exec(a);return h?(i=h[0].charCodeAt(0)!==44,a.substring(b,h.index)):(f.lastIndex=a.length,a.substring(b))}var c={},d={},e=[],f=/\r\n|[,\r\n]/g,g=0,h,i;f.lastIndex=0;while((h=j())!==d){var k=[];while(h!==c&&h!==d)k.push(h),h=j();if(b&&!(k=b(k,g++)))continue;e.push(k)}return e},d3.csv.format=function(a){return a.map(eM).join("\n")},d3.geo={};var eO=Math.PI/180;d3.geo.azimuthal=function(){function i(b){var f=b[0]*eO-e,i=b[1]*eO,j=Math.cos(f),k=Math.sin(f),l=Math.cos(i),m=Math.sin(i),n=a!=="orthographic"?h*m+g*l*j:null,o,p=a==="stereographic"?1/(1+n):a==="gnomonic"?1/n:a==="equidistant"?(o=Math.acos(n),o?o/Math.sin(o):0):a==="equalarea"?Math.sqrt(2/(1+n)):1,q=p*l*k,r=p*(h*l*j-g*m);return[c*q+d[0],c*r+d[1]]}var a="orthographic",b,c=200,d=[480,250],e,f,g,h;return i.invert=function(b){var f=(b[0]-d[0])/c,i=(b[1]-d[1])/c,j=Math.sqrt(f*f+i*i),k=a==="stereographic"?2*Math.atan(j):a==="gnomonic"?Math.atan(j):a==="equidistant"?j:a==="equalarea"?2*Math.asin(.5*j):Math.asin(j),l=Math.sin(k),m=Math.cos(k);return[(e+Math.atan2(f*l,j*g*m+i*h*l))/eO,Math.asin(m*h-(j?i*l*g/j:0))/eO]},i.mode=function(b){return arguments.length?(a=b+"",i):a},i.origin=function(a){return arguments.length?(b=a,e=b[0]*eO,f=b[1]*eO,g=Math.cos(f),h=Math.sin(f),i):b},i.scale=function(a){return arguments.length?(c=+a,i):c},i.translate=function(a){return arguments.length?(d=[+a[0],+a[1]],i):d},i.origin([0,0])},d3.geo.albers=function(){function i(a){var b=f*(eO*a[0]-e),i=Math.sqrt(g-2*f*Math.sin(eO*a[1]))/f;return[c*i*Math.sin(b)+d[0],c*(i*Math.cos(b)-h)+d[1]]}function j(){var c=eO*b[0],d=eO*b[1],j=eO*a[1],k=Math.sin(c),l=Math.cos(c);return e=eO*a[0],f=.5*(k+Math.sin(d)),g=l*l+2*f*k,h=Math.sqrt(g-2*f*Math.sin(j))/f,i}var a=[-98,38],b=[29.5,45.5],c=1e3,d=[480,250],e,f,g,h;return i.invert=function(a){var b=(a[0]-d[0])/c,i=(a[1]-d[1])/c,j=h+i,k=Math.atan2(b,j),l=Math.sqrt(b*b+j*j);return[(e+k/f)/eO,Math.asin((g-l*l*f*f)/(2*f))/eO]},i.origin=function(b){return arguments.length?(a=[+b[0],+b[1]],j()):a},i.parallels=function(a){return arguments.length?(b=[+a[0],+a[1]],j()):b},i.scale=function(a){return arguments.length?(c=+a,i):c},i.translate=function(a){return arguments.length?(d=[+a[0],+a[1]],i):d},j()},d3.geo.albersUsa=function(){function e(e){var f=e[0],g=e[1];return(g>50?b:f<-140?c:g<21?d:a)(e)}var a=d3.geo.albers(),b=d3.geo.albers().origin([-160,60]).parallels([55,65]),c=d3.geo.albers().origin([-160,20]).parallels([8,18]),d=d3.geo.albers().origin([-60,10]).parallels([8,18]);return e.scale=function(f){return arguments.length?(a.scale(f),b.scale(f*.6),c.scale(f),d.scale(f*1.5),e.translate(a.translate())):a.scale()},e.translate=function(f){if(!arguments.length)return a.translate();var g=a.scale()/1e3,h=f[0],i=f[1];return a.translate(f),b.translate([h-400*g,i+170*g]),c.translate([h-190*g,i+200*g]),d.translate([h+580*g,i+430*g]),e},e.scale(a.scale())},d3.geo.bonne=function(){function g(g){var h=g[0]*eO-c,i=g[1]*eO-d;if(e){var j=f+e-i,k=h*Math.cos(i)/j;h=j*Math.sin(k),i=j*Math.cos(k)-f}else h*=Math.cos(i),i*=-1;return[a*h+b[0],a*i+b[1]]}var a=200,b=[480,250],c,d,e,f;return g.invert=function(d){var g=(d[0]-b[0])/a,h=(d[1]-b[1])/a;if(e){var i=f+h,j=Math.sqrt(g*g+i*i);h=f+e-j,g=c+j*Math.atan2(g,i)/Math.cos(h)}else h*=-1,g/=Math.cos(h);return[g/eO,h/eO]},g.parallel=function(a){return arguments.length?(f=1/Math.tan(e=a*eO),g):e/eO},g.origin=function(a){return arguments.length?(c=a[0]*eO,d=a[1]*eO,g):[c/eO,d/eO]},g.scale=function(b){return arguments.length?(a=+b,g):a},g.translate=function(a){return arguments.length?(b=[+a[0],+a[1]],g):b},g.origin([0,0]).parallel(45)},d3.geo.equirectangular=function(){function c(c){var d=c[0]/360,e=-c[1]/360;return[a*d+b[0],a*e+b[1]]}var a=500,b=[480,250];return c.invert=function(c){var d=(c[0]-b[0])/a,e=(c[1]-b[1])/a;return[360*d,-360*e]},c.scale=function(b){return arguments.length?(a=+b,c):a},c.translate=function(a){return arguments.length?(b=[+a[0],+a[1]],c):b},c},d3.geo.mercator=function(){function c(c){var d=c[0]/360,e=-(Math.log(Math.tan(Math.PI/4+c[1]*eO/2))/eO)/360;return[a*d+b[0],a*Math.max(-0.5,Math.min(.5,e))+b[1]]}var a=500,b=[480,250];return c.invert=function(c){var d=(c[0]-b[0])/a,e=(c[1]-b[1])/a;return[360*d,2*Math.atan(Math.exp(-360*e*eO))/eO-90]},c.scale=function(b){return arguments.length?(a=+b,c):a},c.translate=function(a){return arguments.length?(b=[+a[0],+a[1]],c):b},c},d3.geo.path=function(){function d(c,d){return typeof a=="function"&&(b=eQ(a.apply(this,arguments))),f(c)||null}function e(a){return c(a).join(",")}function h(a){var b=k(a[0]),c=0,d=a.length;while(++c<d)b-=k(a[c]);return b}function i(a){var b=d3.geom.polygon(a[0].map(c)),d=b.area(),e=b.centroid(d<0?(d*=-1,1):-1),f=e[0],g=e[1],h=d,i=0,j=a.length;while(++i<j)b=d3.geom.polygon(a[i].map(c)),d=b.area(),e=b.centroid(d<0?(d*=-1,1):-1),f-=e[0],g-=e[1],h-=d;return[f,g,6*h]}function k(a){return Math.abs(d3.geom.polygon(a.map(c)).area())}var a=4.5,b=eQ(a),c=d3.geo.albersUsa(),f=eP({FeatureCollection:function(a){var b=[],c=a.features,d=-1,e=c.length;while(++d<e)b.push(f(c[d].geometry));return b.join("")},Feature:function(a){return f(a.geometry)},Point:function(a){return"M"+e(a.coordinates)+b},MultiPoint:function(a){var c=[],d=a.coordinates,f=-1,g=d.length;while(++f<g)c.push("M",e(d[f]),b);return c.join("")},LineString:function(a){var b=["M"],c=a.coordinates,d=-1,f=c.length;while(++d<f)b.push(e(c[d]),"L");return b.pop(),b.join("")},MultiLineString:function(a){var b=[],c=a.coordinates,d=-1,f=c.length,g,h,i;while(++d<f){g=c[d],h=-1,i=g.length,b.push("M");while(++h<i)b.push(e(g[h]),"L");b.pop()}return b.join("")},Polygon:function(a){var b=[],c=a.coordinates,d=-1,f=c.length,g,h,i;while(++d<f){g=c[d],h=-1;if((i=g.length-1)>0){b.push("M");while(++h<i)b.push(e(g[h]),"L");b[b.length-1]="Z"}}return b.join("")},MultiPolygon:function(a){var b=[],c=a.coordinates,d=-1,f=c.length,g,h,i,j,k,l;while(++d<f){g=c[d],h=-1,i=g.length;while(++h<i){j=g[h],k=-1;if((l=j.length-1)>0){b.push("M");while(++k<l)b.push(e(j[k]),"L");b[b.length-1]="Z"}}}return b.join("")},GeometryCollection:function(a){var b=[],c=a.geometries,d=-1,e=c.length;while(++d<e)b.push(f(c[d]));return b.join("")}}),g=d.area=eP({FeatureCollection:function(a){var b=0,c=a.features,d=-1,e=c.length;while(++d<e)b+=g(c[d]);return b},Feature:function(a){return g(a.geometry)},Polygon:function(a){return h(a.coordinates)},MultiPolygon:function(a){var b=0,c=a.coordinates,d=-1,e=c.length;while(++d<e)b+=h(c[d]);return b},GeometryCollection:function(a){var b=0,c=a.geometries,d=-1,e=c.length;while(++d<e)b+=g(c[d]);return b}},0),j=d.centroid=eP({Feature:function(a){return j(a.geometry)},Polygon:function(a){var b=i(a.coordinates);return[b[0]/b[2],b[1]/b[2]]},MultiPolygon:function(a){var b=0,c=a.coordinates,d,e=0,f=0,g=0,h=-1,j=c.length;while(++h<j)d=i(c[h]),e+=d[0],f+=d[1],g+=d[2];return[e/g,f/g]}});return d.projection=function(a){return c=a,d},d.pointRadius=function(c){return typeof c=="function"?a=c:(a=+c,b=eQ(a)),d},d},d3.geo.bounds=function(a){var b=Infinity,c=Infinity,d=-Infinity,e=-Infinity;return eR(a,function(a,f){a<b&&(b=a),a>d&&(d=a),f<c&&(c=f),f>e&&(e=f)}),[[b,c],[d,e]]};var eS={Feature:eT,FeatureCollection:eU,GeometryCollection:eV,LineString:eW,MultiLineString:eX,MultiPoint:eW,MultiPolygon:eY,Point:eZ,Polygon:e$};d3.geo.circle=function(){function e(){}function f(a){return d.distance(a)<c}function h(a){var b=-1,e=a.length,f=[],g,h,j,k,l;while(++b<e)l=d.distance(j=a[b]),l<c?(h&&f.push(fb(h,j)((k-c)/(k-l))),f.push(j),g=h=null):(h=j,!g&&f.length&&(f.push(fb(f[f.length-1],h)((c-k)/(l-k))),g=h)),k=l;return h&&f.length&&(l=d.distance(j=f[0]),f.push(fb(h,j)((k-c)/(k-l)))),i(f)}function i(a){var b=0,c=a.length,e,f,g=c?[a[0]]:a,h,i=d.source();while(++b<c){h=d.source(a[b-1])(a[b]).coordinates;for(e=0,f=h.length;++e<f;)g.push(h[e])}return d.source(i),g}var a=[0,0],b=89.99,c=b*eO,d=d3.geo.greatArc().target(Object);e.clip=function(b){return d.source(typeof a=="function"?a.apply(this,arguments):a),g(b)};var g=eP({FeatureCollection:function(a){var b=a.features.map(g).filter(Object);return b&&(a=Object.create(a),a.features=b,a)},Feature:function(a){var b=g(a.geometry);return b&&(a=Object.create(a),a.geometry=b,a)},Point:function(a){return f
+(a.coordinates)&&a},MultiPoint:function(a){var b=a.coordinates.filter(f);return b.length&&{type:a.type,coordinates:b}},LineString:function(a){var b=h(a.coordinates);return b.length&&(a=Object.create(a),a.coordinates=b,a)},MultiLineString:function(a){var b=a.coordinates.map(h).filter(function(a){return a.length});return b.length&&(a=Object.create(a),a.coordinates=b,a)},Polygon:function(a){var b=a.coordinates.map(h);return b[0].length&&(a=Object.create(a),a.coordinates=b,a)},MultiPolygon:function(a){var b=a.coordinates.map(function(a){return a.map(h)}).filter(function(a){return a[0].length});return b.length&&(a=Object.create(a),a.coordinates=b,a)},GeometryCollection:function(a){var b=a.geometries.map(g).filter(Object);return b.length&&(a=Object.create(a),a.geometries=b,a)}});return e.origin=function(b){return arguments.length?(a=b,e):a},e.angle=function(a){return arguments.length?(c=(b=+a)*eO,e):b},e.precision=function(a){return arguments.length?(d.precision(a),e):d.precision()},e},d3.geo.greatArc=function(){function d(){var d=typeof a=="function"?a.apply(this,arguments):a,e=typeof b=="function"?b.apply(this,arguments):b,f=fb(d,e),g=c/f.d,h=0,i=[d];while((h+=g)<1)i.push(f(h));return i.push(e),{type:"LineString",coordinates:i}}var a=e_,b=fa,c=6*eO;return d.distance=function(){var c=typeof a=="function"?a.apply(this,arguments):a,d=typeof b=="function"?b.apply(this,arguments):b;return fb(c,d).d},d.source=function(b){return arguments.length?(a=b,d):a},d.target=function(a){return arguments.length?(b=a,d):b},d.precision=function(a){return arguments.length?(c=a*eO,d):c/eO},d},d3.geo.greatCircle=d3.geo.circle,d3.geom={},d3.geom.contour=function(a,b){var c=b||fe(a),d=[],e=c[0],f=c[1],g=0,h=0,i=NaN,j=NaN,k=0;do k=0,a(e-1,f-1)&&(k+=1),a(e,f-1)&&(k+=2),a(e-1,f)&&(k+=4),a(e,f)&&(k+=8),k===6?(g=j===-1?-1:1,h=0):k===9?(g=0,h=i===1?-1:1):(g=fc[k],h=fd[k]),g!=i&&h!=j&&(d.push([e,f]),i=g,j=h),e+=g,f+=h;while(c[0]!=e||c[1]!=f);return d};var fc=[1,0,1,1,-1,0,-1,1,0,0,0,0,-1,0,-1,NaN],fd=[0,-1,0,0,0,-1,0,0,1,-1,1,1,0,-1,0,NaN];d3.geom.hull=function(a){if(a.length<3)return[];var b=a.length,c=b-1,d=[],e=[],f,g,h=0,i,j,k,l,m,n,o,p;for(f=1;f<b;++f)a[f][1]<a[h][1]?h=f:a[f][1]==a[h][1]&&(h=a[f][0]<a[h][0]?f:h);for(f=0;f<b;++f){if(f===h)continue;j=a[f][1]-a[h][1],i=a[f][0]-a[h][0],d.push({angle:Math.atan2(j,i),index:f})}d.sort(function(a,b){return a.angle-b.angle}),o=d[0].angle,n=d[0].index,m=0;for(f=1;f<c;++f)g=d[f].index,o==d[f].angle?(i=a[n][0]-a[h][0],j=a[n][1]-a[h][1],k=a[g][0]-a[h][0],l=a[g][1]-a[h][1],i*i+j*j>=k*k+l*l?d[f].index=-1:(d[m].index=-1,o=d[f].angle,m=f,n=g)):(o=d[f].angle,m=f,n=g);e.push(h);for(f=0,g=0;f<2;++g)d[g].index!==-1&&(e.push(d[g].index),f++);p=e.length;for(;g<c;++g){if(d[g].index===-1)continue;while(!ff(e[p-2],e[p-1],d[g].index,a))--p;e[p++]=d[g].index}var q=[];for(f=0;f<p;++f)q.push(a[e[f]]);return q},d3.geom.polygon=function(a){return a.area=function(){var b=0,c=a.length,d=a[c-1][0]*a[0][1],e=a[c-1][1]*a[0][0];while(++b<c)d+=a[b-1][0]*a[b][1],e+=a[b-1][1]*a[b][0];return(e-d)*.5},a.centroid=function(b){var c=-1,d=a.length,e=0,f=0,g,h=a[d-1],i;arguments.length||(b=-1/(6*a.area()));while(++c<d)g=h,h=a[c],i=g[0]*h[1]-h[0]*g[1],e+=(g[0]+h[0])*i,f+=(g[1]+h[1])*i;return[e*b,f*b]},a.clip=function(b){var c,d=-1,e=a.length,f,g,h=a[e-1],i,j,k;while(++d<e){c=b.slice(),b.length=0,i=a[d],j=c[(g=c.length)-1],f=-1;while(++f<g)k=c[f],fg(k,h,i)?(fg(j,h,i)||b.push(fh(j,k,h,i)),b.push(k)):fg(j,h,i)&&b.push(fh(j,k,h,i)),j=k;h=i}return b},a},d3.geom.voronoi=function(a){var b=a.map(function(){return[]});return fj(a,function(a){var c,d,e,f,g,h;a.a===1&&a.b>=0?(c=a.ep.r,d=a.ep.l):(c=a.ep.l,d=a.ep.r),a.a===1?(g=c?c.y:-1e6,e=a.c-a.b*g,h=d?d.y:1e6,f=a.c-a.b*h):(e=c?c.x:-1e6,g=a.c-a.a*e,f=d?d.x:1e6,h=a.c-a.a*f);var i=[e,g],j=[f,h];b[a.region.l.index].push(i,j),b[a.region.r.index].push(i,j)}),b.map(function(b,c){var d=a[c][0],e=a[c][1];return b.forEach(function(a){a.angle=Math.atan2(a[0]-d,a[1]-e)}),b.sort(function(a,b){return a.angle-b.angle}).filter(function(a,c){return!c||a.angle-b[c-1].angle>1e-10})})};var fi={l:"r",r:"l"};d3.geom.delaunay=function(a){var b=a.map(function(){return[]}),c=[];return fj(a,function(c){b[c.region.l.index].push(a[c.region.r.index])}),b.forEach(function(b,d){var e=a[d],f=e[0],g=e[1];b.forEach(function(a){a.angle=Math.atan2(a[0]-f,a[1]-g)}),b.sort(function(a,b){return a.angle-b.angle});for(var h=0,i=b.length-1;h<i;h++)c.push([e,b[h],b[h+1]])}),c},d3.geom.quadtree=function(a,b,c,d,e){function k(a,b,c,d,e,f){if(isNaN(b.x)||isNaN(b.y))return;if(a.leaf){var g=a.point;g?Math.abs(g.x-b.x)+Math.abs(g.y-b.y)<.01?l(a,b,c,d,e,f):(a.point=null,l(a,g,c,d,e,f),l(a,b,c,d,e,f)):a.point=b}else l(a,b,c,d,e,f)}function l(a,b,c,d,e,f){var g=(c+e)*.5,h=(d+f)*.5,i=b.x>=g,j=b.y>=h,l=(j<<1)+i;a.leaf=!1,a=a.nodes[l]||(a.nodes[l]=fk()),i?c=g:e=g,j?d=h:f=h,k(a,b,c,d,e,f)}var f,g=-1,h=a.length;h&&isNaN(a[0].x)&&(a=a.map(fm));if(arguments.length<5)if(arguments.length===3)e=d=c,c=b;else{b=c=Infinity,d=e=-Infinity;while(++g<h)f=a[g],f.x<b&&(b=f.x),f.y<c&&(c=f.y),f.x>d&&(d=f.x),f.y>e&&(e=f.y);var i=d-b,j=e-c;i>j?e=c+i:d=b+j}var m=fk();return m.add=function(a){k(m,a,b,c,d,e)},m.visit=function(a){fl(a,m,b,c,d,e)},a.forEach(m.add),m},d3.time={};var fn=Date;fo.prototype={getDate:function(){return this._.getUTCDate()},getDay:function(){return this._.getUTCDay()},getFullYear:function(){return this._.getUTCFullYear()},getHours:function(){return this._.getUTCHours()},getMilliseconds:function(){return this._.getUTCMilliseconds()},getMinutes:function(){return this._.getUTCMinutes()},getMonth:function(){return this._.getUTCMonth()},getSeconds:function(){return this._.getUTCSeconds()},getTime:function(){return this._.getTime()},getTimezoneOffset:function(){return 0},valueOf:function(){return this._.valueOf()},setDate:function(){fp.setUTCDate.apply(this._,arguments)},setDay:function(){fp.setUTCDay.apply(this._,arguments)},setFullYear:function(){fp.setUTCFullYear.apply(this._,arguments)},setHours:function(){fp.setUTCHours.apply(this._,arguments)},setMilliseconds:function(){fp.setUTCMilliseconds.apply(this._,arguments)},setMinutes:function(){fp.setUTCMinutes.apply(this._,arguments)},setMonth:function(){fp.setUTCMonth.apply(this._,arguments)},setSeconds:function(){fp.setUTCSeconds.apply(this._,arguments)},setTime:function(){fp.setTime.apply(this._,arguments)}};var fp=Date.prototype;d3.time.format=function(a){function c(c){var d=[],e=-1,f=0,g,h;while(++e<b)a.charCodeAt(e)==37&&(d.push(a.substring(f,e),(h=fv[g=a.charAt(++e)])?h(c):g),f=e+1);return d.push(a.substring(f,e)),d.join("")}var b=a.length;return c.parse=function(b){var c={y:1900,m:0,d:1,H:0,M:0,S:0,L:0},d=fq(c,a,b,0);if(d!=b.length)return null;"p"in c&&(c.H=c.H%12+c.p*12);var e=new fn;return e.setFullYear(c.y,c.m,c.d),e.setHours(c.H,c.M,c.S,c.L),e},c.toString=function(){return a},c};var fr=d3.format("02d"),fs=d3.format("03d"),ft=d3.format("04d"),fu=d3.format("2d"),fv={a:function(a){return d3_time_weekdays[a.getDay()].substring(0,3)},A:function(a){return d3_time_weekdays[a.getDay()]},b:function(a){return fG[a.getMonth()].substring(0,3)},B:function(a){return fG[a.getMonth()]},c:d3.time.format("%a %b %e %H:%M:%S %Y"),d:function(a){return fr(a.getDate())},e:function(a){return fu(a.getDate())},H:function(a){return fr(a.getHours())},I:function(a){return fr(a.getHours()%12||12)},j:function(a){return fs(1+d3.time.dayOfYear(a))},L:function(a){return fs(a.getMilliseconds())},m:function(a){return fr(a.getMonth()+1)},M:function(a){return fr(a.getMinutes())},p:function(a){return a.getHours()>=12?"PM":"AM"},S:function(a){return fr(a.getSeconds())},U:function(a){return fr(d3.time.sundayOfYear(a))},w:function(a){return a.getDay()},W:function(a){return fr(d3.time.mondayOfYear(a))},x:d3.time.format("%m/%d/%y"),X:d3.time.format("%H:%M:%S"),y:function(a){return fr(a.getFullYear()%100)},Y:function(a){return ft(a.getFullYear()%1e4)},Z:fW,"%":function(a){return"%"}},fw={a:fx,A:fy,b:fB,B:fD,c:fH,d:fO,e:fO,H:fP,I:fP,L:fS,m:fN,M:fQ,p:fU,S:fR,x:fI,X:fJ,y:fL,Y:fK},fz=/^(?:sun|mon|tue|wed|thu|fri|sat)/i,fA=/^(?:Sunday|Monday|Tuesday|Wednesday|Thursday|Friday|Saturday)/i;d3_time_weekdays=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"];var fC=d3.map({jan:0,feb:1,mar:2,apr:3,may:4,jun:5,jul:6,aug:7,sep:8,oct:9,nov:10,dec:11}),fE=/^(?:January|February|March|April|May|June|July|August|September|October|November|December)/ig,fF=d3.map({january:0,february:1,march:2,april:3,may:4,june:5,july:6,august:7,september:8,october:9,november:10,december:11}),fG=["January","February","March","April","May","June","July","August","September","October","November","December"],fT=/\s*\d+/,fV=d3.map({am:0,pm:1});d3.time.format.utc=function(a){function c(a){try{fn=fo;var c=new fn;return c._=a,b(c)}finally{fn=Date}}var b=d3.time.format(a);return c.parse=function(a){try{fn=fo;var c=b.parse(a);return c&&c._}finally{fn=Date}},c.toString=b.toString,c};var fX=d3.time.format.utc("%Y-%m-%dT%H:%M:%S.%LZ");d3.time.format.iso=Date.prototype.toISOString?fY:fX,fY.parse=function(a){return new Date(a)},fY.toString=fX.toString,d3.time.second=fZ(function(a){return new fn(Math.floor(a/1e3)*1e3)},function(a,b){a.setTime(a.getTime()+Math.floor(b)*1e3)},function(a){return a.getSeconds()}),d3.time.seconds=d3.time.second.range,d3.time.seconds.utc=d3.time.second.utc.range,d3.time.minute=fZ(function(a){return new fn(Math.floor(a/6e4)*6e4)},function(a,b){a.setTime(a.getTime()+Math.floor(b)*6e4)},function(a){return a.getMinutes()}),d3.time.minutes=d3.time.minute.range,d3.time.minutes.utc=d3.time.minute.utc.range,d3.time.hour=fZ(function(a){var b=a.getTimezoneOffset()/60;return new fn((Math.floor(a/36e5-b)+b)*36e5)},function(a,b){a.setTime(a.getTime()+Math.floor(b)*36e5)},function(a){return a.getHours()}),d3.time.hours=d3.time.hour.range,d3.time.hours.utc=d3.time.hour.utc.range,d3.time.day=fZ(function(a){return new fn(a.getFullYear(),a.getMonth(),a.getDate())},function(a,b){a.setDate(a.getDate()+b)},function(a){return a.getDate()-1}),d3.time.days=d3.time.day.range,d3.time.days.utc=d3.time.day.utc.range,d3.time.dayOfYear=function(a){var b=d3.time.year(a);return Math.floor((a-b)/864e5-(a.getTimezoneOffset()-b.getTimezoneOffset())/1440)},d3_time_weekdays.forEach(function(a,b){a=a.toLowerCase(),b=7-b;var c=d3.time[a]=fZ(function(a){return(a=d3.time.day(a)).setDate(a.getDate()-(a.getDay()+b)%7),a},function(a,b){a.setDate(a.getDate()+Math.floor(b)*7)},function(a){var c=d3.time.year(a).getDay();return Math.floor((d3.time.dayOfYear(a)+(c+b)%7)/7)-(c!==b)});d3.time[a+"s"]=c.range,d3.time[a+"s"].utc=c.utc.range,d3.time[a+"OfYear"]=function(a){var c=d3.time.year(a).getDay();return Math.floor((d3.time.dayOfYear(a)+(c+b)%7)/7)}}),d3.time.week=d3.time.sunday,d3.time.weeks=d3.time.sunday.range,d3.time.weeks.utc=d3.time.sunday.utc.range,d3.time.weekOfYear=d3.time.sundayOfYear,d3.time.month=fZ(function(a){return new fn(a.getFullYear(),a.getMonth(),1)},function(a,b){a.setMonth(a.getMonth()+b)},function(a){return a.getMonth()}),d3.time.months=d3.time.month.range,d3.time.months.utc=d3.time.month.utc.range,d3.time.year=fZ(function(a){return new fn(a.getFullYear(),0,1)},function(a,b){a.setFullYear(a.getFullYear()+b)},function(a){return a.getFullYear()}),d3.time.years=d3.time.year.range,d3.time.years.utc=d3.time.year.utc.range;var gf=[1e3,5e3,15e3,3e4,6e4,3e5,9e5,18e5,36e5,108e5,216e5,432e5,864e5,1728e5,6048e5,2592e6,7776e6,31536e6],gg=[[d3.time.second,1],[d3.time.second,5],[d3.time.second,15],[d3.time.second,30],[d3.time.minute,1],[d3.time.minute,5],[d3.time.minute,15],[d3.time.minute,30],[d3.time.hour,1],[d3.time.hour,3],[d3.time.hour,6],[d3.time.hour,12],[d3.time.day,1],[d3.time.day,2],[d3.time.week,1],[d3.time.month,1],[d3.time.month,3],[d3.time.year,1]],gh=[[d3.time.format("%Y"),function(a){return!0}],[d3.time.format("%B"),function(a){return a.getMonth()}],[d3.time.format("%b %d"),function(a){return a.getDate()!=1}],[d3.time.format("%a %d"),function(a){return a.getDay()&&a.getDate()!=1}],[d3.time.format("%I %p"),function(a){return a.getHours()}],[d3.time.format("%I:%M"),function(a){return a.getMinutes()}],[d3.time.format(":%S"),function(a){return a.getSeconds()}],[d3.time.format(".%L"),function(a){return a.getMilliseconds()}]],gi=d3.scale.linear(),gj=gc(gh);gg.year=function(a,b){return gi.domain(a.map(ge)).ticks(b).map(gd)},d3.time.scale=function(){return f_(d3.scale.linear(),gg,gj)};var gk=gg.map(function(a){return[a[0].utc,a[1]]}),gl=[[d3.time.format.utc("%Y"),function(a){return!0}],[d3.time.format.utc("%B"),function(a){return a.getUTCMonth()}],[d3.time.format.utc("%b %d"),function(a){return a.getUTCDate()!=1}],[d3.time.format.utc("%a %d"),function(a){return a.getUTCDay()&&a.getUTCDate()!=1}],[d3.time.format.utc("%I %p"),function(a){return a.getUTCHours()}],[d3.time.format.utc("%I:%M"),function(a){return a.getUTCMinutes()}],[d3.time.format.utc(":%S"),function(a){return a.getUTCSeconds()}],[d3.time.format.utc(".%L"),function(a){return a.getUTCMilliseconds()}]],gm=gc(gl);gk.year=function(a,b){return gi.domain(a.map(go)).ticks(b).map(gn)},d3.time.scale.utc=function(){return f_(d3.scale.linear(),gk,gm)}})();
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/resources/web/lib/jquery.min.js b/src/ext/floodlight/src/main/resources/web/lib/jquery.min.js
new file mode 100644
index 0000000..16ad06c
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/lib/jquery.min.js
@@ -0,0 +1,4 @@
+/*! jQuery v1.7.2 jquery.com | jquery.org/license */
+(function(a,b){function cy(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cu(a){if(!cj[a]){var b=c.body,d=f("<"+a+">").appendTo(b),e=d.css("display");d.remove();if(e==="none"||e===""){ck||(ck=c.createElement("iframe"),ck.frameBorder=ck.width=ck.height=0),b.appendChild(ck);if(!cl||!ck.createElement)cl=(ck.contentWindow||ck.contentDocument).document,cl.write((f.support.boxModel?"<!doctype html>":"")+"<html><body>"),cl.close();d=cl.createElement(a),cl.body.appendChild(d),e=f.css(d,"display"),b.removeChild(ck)}cj[a]=e}return cj[a]}function ct(a,b){var c={};f.each(cp.concat.apply([],cp.slice(0,b)),function(){c[this]=a});return c}function cs(){cq=b}function cr(){setTimeout(cs,0);return cq=f.now()}function ci(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function ch(){try{return new a.XMLHttpRequest}catch(b){}}function cb(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g<i;g++){if(g===1)for(h in a.converters)typeof h=="string"&&(e[h.toLowerCase()]=a.converters[h]);l=k,k=d[g];if(k==="*")k=l;else if(l!=="*"&&l!==k){m=l+" "+k,n=e[m]||e["* "+k];if(!n){p=b;for(o in e){j=o.split(" ");if(j[0]===l||j[0]==="*"){p=e[j[1]+" "+k];if(p){o=e[o],o===!0?n=p:p===!0&&(n=o);break}}}}!n&&!p&&f.error("No conversion from "+m.replace(" "," to ")),n!==!0&&(c=n?n(c):p(o(c)))}}return c}function ca(a,c,d){var e=a.contents,f=a.dataTypes,g=a.responseFields,h,i,j,k;for(i in g)i in d&&(c[g[i]]=d[i]);while(f[0]==="*")f.shift(),h===b&&(h=a.mimeType||c.getResponseHeader("content-type"));if(h)for(i in e)if(e[i]&&e[i].test(h)){f.unshift(i);break}if(f[0]in d)j=f[0];else{for(i in d){if(!f[0]||a.converters[i+" "+f[0]]){j=i;break}k||(k=i)}j=j||k}if(j){j!==f[0]&&f.unshift(j);return d[j]}}function b_(a,b,c,d){if(f.isArray(b))f.each(b,function(b,e){c||bD.test(a)?d(a,e):b_(a+"["+(typeof e=="object"?b:"")+"]",e,c,d)});else if(!c&&f.type(b)==="object")for(var e in b)b_(a+"["+e+"]",b[e],c,d);else d(a,b)}function b$(a,c){var d,e,g=f.ajaxSettings.flatOptions||{};for(d in c)c[d]!==b&&((g[d]?a:e||(e={}))[d]=c[d]);e&&f.extend(!0,a,e)}function bZ(a,c,d,e,f,g){f=f||c.dataTypes[0],g=g||{},g[f]=!0;var h=a[f],i=0,j=h?h.length:0,k=a===bS,l;for(;i<j&&(k||!l);i++)l=h[i](c,d,e),typeof l=="string"&&(!k||g[l]?l=b:(c.dataTypes.unshift(l),l=bZ(a,c,d,e,l,g)));(k||!l)&&!g["*"]&&(l=bZ(a,c,d,e,"*",g));return l}function bY(a){return function(b,c){typeof b!="string"&&(c=b,b="*");if(f.isFunction(c)){var d=b.toLowerCase().split(bO),e=0,g=d.length,h,i,j;for(;e<g;e++)h=d[e],j=/^\+/.test(h),j&&(h=h.substr(1)||"*"),i=a[h]=a[h]||[],i[j?"unshift":"push"](c)}}}function bB(a,b,c){var d=b==="width"?a.offsetWidth:a.offsetHeight,e=b==="width"?1:0,g=4;if(d>0){if(c!=="border")for(;e<g;e+=2)c||(d-=parseFloat(f.css(a,"padding"+bx[e]))||0),c==="margin"?d+=parseFloat(f.css(a,c+bx[e]))||0:d-=parseFloat(f.css(a,"border"+bx[e]+"Width"))||0;return d+"px"}d=by(a,b);if(d<0||d==null)d=a.style[b];if(bt.test(d))return d;d=parseFloat(d)||0;if(c)for(;e<g;e+=2)d+=parseFloat(f.css(a,"padding"+bx[e]))||0,c!=="padding"&&(d+=parseFloat(f.css(a,"border"+bx[e]+"Width"))||0),c==="margin"&&(d+=parseFloat(f.css(a,c+bx[e]))||0);return d+"px"}function bo(a){var b=c.createElement("div");bh.appendChild(b),b.innerHTML=a.outerHTML;return b.firstChild}function bn(a){var b=(a.nodeName||"").toLowerCase();b==="input"?bm(a):b!=="script"&&typeof a.getElementsByTagName!="undefined"&&f.grep(a.getElementsByTagName("input"),bm)}function bm(a){if(a.type==="checkbox"||a.type==="radio")a.defaultChecked=a.checked}function bl(a){return typeof a.getElementsByTagName!="undefined"?a.getElementsByTagName("*"):typeof a.querySelectorAll!="undefined"?a.querySelectorAll("*"):[]}function bk(a,b){var c;b.nodeType===1&&(b.clearAttributes&&b.clearAttributes(),b.mergeAttributes&&b.mergeAttributes(a),c=b.nodeName.toLowerCase(),c==="object"?b.outerHTML=a.outerHTML:c!=="input"||a.type!=="checkbox"&&a.type!=="radio"?c==="option"?b.selected=a.defaultSelected:c==="input"||c==="textarea"?b.defaultValue=a.defaultValue:c==="script"&&b.text!==a.text&&(b.text=a.text):(a.checked&&(b.defaultChecked=b.checked=a.checked),b.value!==a.value&&(b.value=a.value)),b.removeAttribute(f.expando),b.removeAttribute("_submit_attached"),b.removeAttribute("_change_attached"))}function bj(a,b){if(b.nodeType===1&&!!f.hasData(a)){var c,d,e,g=f._data(a),h=f._data(b,g),i=g.events;if(i){delete h.handle,h.events={};for(c in i)for(d=0,e=i[c].length;d<e;d++)f.event.add(b,c,i[c][d])}h.data&&(h.data=f.extend({},h.data))}}function bi(a,b){return f.nodeName(a,"table")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function U(a){var b=V.split("|"),c=a.createDocumentFragment();if(c.createElement)while(b.length)c.createElement(b.pop());return c}function T(a,b,c){b=b||0;if(f.isFunction(b))return f.grep(a,function(a,d){var e=!!b.call(a,d,a);return e===c});if(b.nodeType)return f.grep(a,function(a,d){return a===b===c});if(typeof b=="string"){var d=f.grep(a,function(a){return a.nodeType===1});if(O.test(b))return f.filter(b,d,!c);b=f.filter(b,d)}return f.grep(a,function(a,d){return f.inArray(a,b)>=0===c})}function S(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function K(){return!0}function J(){return!1}function n(a,b,c){var d=b+"defer",e=b+"queue",g=b+"mark",h=f._data(a,d);h&&(c==="queue"||!f._data(a,e))&&(c==="mark"||!f._data(a,g))&&setTimeout(function(){!f._data(a,e)&&!f._data(a,g)&&(f.removeData(a,d,!0),h.fire())},0)}function m(a){for(var b in a){if(b==="data"&&f.isEmptyObject(a[b]))continue;if(b!=="toJSON")return!1}return!0}function l(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(k,"-$1").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:f.isNumeric(d)?+d:j.test(d)?f.parseJSON(d):d}catch(g){}f.data(a,c,d)}else d=b}return d}function h(a){var b=g[a]={},c,d;a=a.split(/\s+/);for(c=0,d=a.length;c<d;c++)b[a[c]]=!0;return b}var c=a.document,d=a.navigator,e=a.location,f=function(){function J(){if(!e.isReady){try{c.documentElement.doScroll("left")}catch(a){setTimeout(J,1);return}e.ready()}}var e=function(a,b){return new e.fn.init(a,b,h)},f=a.jQuery,g=a.$,h,i=/^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,j=/\S/,k=/^\s+/,l=/\s+$/,m=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,n=/^[\],:{}\s]*$/,o=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,p=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,q=/(?:^|:|,)(?:\s*\[)+/g,r=/(webkit)[ \/]([\w.]+)/,s=/(opera)(?:.*version)?[ \/]([\w.]+)/,t=/(msie) ([\w.]+)/,u=/(mozilla)(?:.*? rv:([\w.]+))?/,v=/-([a-z]|[0-9])/ig,w=/^-ms-/,x=function(a,b){return(b+"").toUpperCase()},y=d.userAgent,z,A,B,C=Object.prototype.toString,D=Object.prototype.hasOwnProperty,E=Array.prototype.push,F=Array.prototype.slice,G=String.prototype.trim,H=Array.prototype.indexOf,I={};e.fn=e.prototype={constructor:e,init:function(a,d,f){var g,h,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!d&&c.body){this.context=c,this[0]=c.body,this.selector=a,this.length=1;return this}if(typeof a=="string"){a.charAt(0)!=="<"||a.charAt(a.length-1)!==">"||a.length<3?g=i.exec(a):g=[null,a,null];if(g&&(g[1]||!d)){if(g[1]){d=d instanceof e?d[0]:d,k=d?d.ownerDocument||d:c,j=m.exec(a),j?e.isPlainObject(d)?(a=[c.createElement(j[1])],e.fn.attr.call(a,d,!0)):a=[k.createElement(j[1])]:(j=e.buildFragment([g[1]],[k]),a=(j.cacheable?e.clone(j.fragment):j.fragment).childNodes);return e.merge(this,a)}h=c.getElementById(g[2]);if(h&&h.parentNode){if(h.id!==g[2])return f.find(a);this.length=1,this[0]=h}this.context=c,this.selector=a;return this}return!d||d.jquery?(d||f).find(a):this.constructor(d).find(a)}if(e.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return e.makeArray(a,this)},selector:"",jquery:"1.7.2",length:0,size:function(){return this.length},toArray:function(){return F.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=this.constructor();e.isArray(a)?E.apply(d,a):e.merge(d,a),d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")");return d},each:function(a,b){return e.each(this,a,b)},ready:function(a){e.bindReady(),A.add(a);return this},eq:function(a){a=+a;return a===-1?this.slice(a):this.slice(a,a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(F.apply(this,arguments),"slice",F.call(arguments).join(","))},map:function(a){return this.pushStack(e.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:E,sort:[].sort,splice:[].splice},e.fn.init.prototype=e.fn,e.extend=e.fn.extend=function(){var a,c,d,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i=="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!="object"&&!e.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j<k;j++)if((a=arguments[j])!=null)for(c in a){d=i[c],f=a[c];if(i===f)continue;l&&f&&(e.isPlainObject(f)||(g=e.isArray(f)))?(g?(g=!1,h=d&&e.isArray(d)?d:[]):h=d&&e.isPlainObject(d)?d:{},i[c]=e.extend(l,h,f)):f!==b&&(i[c]=f)}return i},e.extend({noConflict:function(b){a.$===e&&(a.$=g),b&&a.jQuery===e&&(a.jQuery=f);return e},isReady:!1,readyWait:1,holdReady:function(a){a?e.readyWait++:e.ready(!0)},ready:function(a){if(a===!0&&!--e.readyWait||a!==!0&&!e.isReady){if(!c.body)return setTimeout(e.ready,1);e.isReady=!0;if(a!==!0&&--e.readyWait>0)return;A.fireWith(c,[e]),e.fn.trigger&&e(c).trigger("ready").off("ready")}},bindReady:function(){if(!A){A=e.Callbacks("once memory");if(c.readyState==="complete")return setTimeout(e.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",B,!1),a.addEventListener("load",e.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",B),a.attachEvent("onload",e.ready);var b=!1;try{b=a.frameElement==null}catch(d){}c.documentElement.doScroll&&b&&J()}}},isFunction:function(a){return e.type(a)==="function"},isArray:Array.isArray||function(a){return e.type(a)==="array"},isWindow:function(a){return a!=null&&a==a.window},isNumeric:function(a){return!isNaN(parseFloat(a))&&isFinite(a)},type:function(a){return a==null?String(a):I[C.call(a)]||"object"},isPlainObject:function(a){if(!a||e.type(a)!=="object"||a.nodeType||e.isWindow(a))return!1;try{if(a.constructor&&!D.call(a,"constructor")&&!D.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}var d;for(d in a);return d===b||D.call(a,d)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw new Error(a)},parseJSON:function(b){if(typeof b!="string"||!b)return null;b=e.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(n.test(b.replace(o,"@").replace(p,"]").replace(q,"")))return(new Function("return "+b))();e.error("Invalid JSON: "+b)},parseXML:function(c){if(typeof c!="string"||!c)return null;var d,f;try{a.DOMParser?(f=new DOMParser,d=f.parseFromString(c,"text/xml")):(d=new ActiveXObject("Microsoft.XMLDOM"),d.async="false",d.loadXML(c))}catch(g){d=b}(!d||!d.documentElement||d.getElementsByTagName("parsererror").length)&&e.error("Invalid XML: "+c);return d},noop:function(){},globalEval:function(b){b&&j.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(w,"ms-").replace(v,x)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var f,g=0,h=a.length,i=h===b||e.isFunction(a);if(d){if(i){for(f in a)if(c.apply(a[f],d)===!1)break}else for(;g<h;)if(c.apply(a[g++],d)===!1)break}else if(i){for(f in a)if(c.call(a[f],f,a[f])===!1)break}else for(;g<h;)if(c.call(a[g],g,a[g++])===!1)break;return a},trim:G?function(a){return a==null?"":G.call(a)}:function(a){return a==null?"":(a+"").replace(k,"").replace(l,"")},makeArray:function(a,b){var c=b||[];if(a!=null){var d=e.type(a);a.length==null||d==="string"||d==="function"||d==="regexp"||e.isWindow(a)?E.call(c,a):e.merge(c,a)}return c},inArray:function(a,b,c){var d;if(b){if(H)return H.call(b,a,c);d=b.length,c=c?c<0?Math.max(0,d+c):c:0;for(;c<d;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,c){var d=a.length,e=0;if(typeof c.length=="number")for(var f=c.length;e<f;e++)a[d++]=c[e];else while(c[e]!==b)a[d++]=c[e++];a.length=d;return a},grep:function(a,b,c){var d=[],e;c=!!c;for(var f=0,g=a.length;f<g;f++)e=!!b(a[f],f),c!==e&&d.push(a[f]);return d},map:function(a,c,d){var f,g,h=[],i=0,j=a.length,k=a instanceof e||j!==b&&typeof j=="number"&&(j>0&&a[0]&&a[j-1]||j===0||e.isArray(a));if(k)for(;i<j;i++)f=c(a[i],i,d),f!=null&&(h[h.length]=f);else for(g in a)f=c(a[g],g,d),f!=null&&(h[h.length]=f);return h.concat.apply([],h)},guid:1,proxy:function(a,c){if(typeof c=="string"){var d=a[c];c=a,a=d}if(!e.isFunction(a))return b;var f=F.call(arguments,2),g=function(){return a.apply(c,f.concat(F.call(arguments)))};g.guid=a.guid=a.guid||g.guid||e.guid++;return g},access:function(a,c,d,f,g,h,i){var j,k=d==null,l=0,m=a.length;if(d&&typeof d=="object"){for(l in d)e.access(a,c,l,d[l],1,h,f);g=1}else if(f!==b){j=i===b&&e.isFunction(f),k&&(j?(j=c,c=function(a,b,c){return j.call(e(a),c)}):(c.call(a,f),c=null));if(c)for(;l<m;l++)c(a[l],d,j?f.call(a[l],l,c(a[l],d)):f,i);g=1}return g?a:k?c.call(a):m?c(a[0],d):h},now:function(){return(new Date).getTime()},uaMatch:function(a){a=a.toLowerCase();var b=r.exec(a)||s.exec(a)||t.exec(a)||a.indexOf("compatible")<0&&u.exec(a)||[];return{browser:b[1]||"",version:b[2]||"0"}},sub:function(){function a(b,c){return new a.fn.init(b,c)}e.extend(!0,a,this),a.superclass=this,a.fn=a.prototype=this(),a.fn.constructor=a,a.sub=this.sub,a.fn.init=function(d,f){f&&f instanceof e&&!(f instanceof a)&&(f=a(f));return e.fn.init.call(this,d,f,b)},a.fn.init.prototype=a.fn;var b=a(c);return a},browser:{}}),e.each("Boolean Number String Function Array Date RegExp Object".split(" "),function(a,b){I["[object "+b+"]"]=b.toLowerCase()}),z=e.uaMatch(y),z.browser&&(e.browser[z.browser]=!0,e.browser.version=z.version),e.browser.webkit&&(e.browser.safari=!0),j.test(" ")&&(k=/^[\s\xA0]+/,l=/[\s\xA0]+$/),h=e(c),c.addEventListener?B=function(){c.removeEventListener("DOMContentLoaded",B,!1),e.ready()}:c.attachEvent&&(B=function(){c.readyState==="complete"&&(c.detachEvent("onreadystatechange",B),e.ready())});return e}(),g={};f.Callbacks=function(a){a=a?g[a]||h(a):{};var c=[],d=[],e,i,j,k,l,m,n=function(b){var d,e,g,h,i;for(d=0,e=b.length;d<e;d++)g=b[d],h=f.type(g),h==="array"?n(g):h==="function"&&(!a.unique||!p.has(g))&&c.push(g)},o=function(b,f){f=f||[],e=!a.memory||[b,f],i=!0,j=!0,m=k||0,k=0,l=c.length;for(;c&&m<l;m++)if(c[m].apply(b,f)===!1&&a.stopOnFalse){e=!0;break}j=!1,c&&(a.once?e===!0?p.disable():c=[]:d&&d.length&&(e=d.shift(),p.fireWith(e[0],e[1])))},p={add:function(){if(c){var a=c.length;n(arguments),j?l=c.length:e&&e!==!0&&(k=a,o(e[0],e[1]))}return this},remove:function(){if(c){var b=arguments,d=0,e=b.length;for(;d<e;d++)for(var f=0;f<c.length;f++)if(b[d]===c[f]){j&&f<=l&&(l--,f<=m&&m--),c.splice(f--,1);if(a.unique)break}}return this},has:function(a){if(c){var b=0,d=c.length;for(;b<d;b++)if(a===c[b])return!0}return!1},empty:function(){c=[];return this},disable:function(){c=d=e=b;return this},disabled:function(){return!c},lock:function(){d=b,(!e||e===!0)&&p.disable();return this},locked:function(){return!d},fireWith:function(b,c){d&&(j?a.once||d.push([b,c]):(!a.once||!e)&&o(b,c));return this},fire:function(){p.fireWith(this,arguments);return this},fired:function(){return!!i}};return p};var i=[].slice;f.extend({Deferred:function(a){var b=f.Callbacks("once memory"),c=f.Callbacks("once memory"),d=f.Callbacks("memory"),e="pending",g={resolve:b,reject:c,notify:d},h={done:b.add,fail:c.add,progress:d.add,state:function(){return e},isResolved:b.fired,isRejected:c.fired,then:function(a,b,c){i.done(a).fail(b).progress(c);return this},always:function(){i.done.apply(i,arguments).fail.apply(i,arguments);return this},pipe:function(a,b,c){return f.Deferred(function(d){f.each({done:[a,"resolve"],fail:[b,"reject"],progress:[c,"notify"]},function(a,b){var c=b[0],e=b[1],g;f.isFunction(c)?i[a](function(){g=c.apply(this,arguments),g&&f.isFunction(g.promise)?g.promise().then(d.resolve,d.reject,d.notify):d[e+"With"](this===i?d:this,[g])}):i[a](d[e])})}).promise()},promise:function(a){if(a==null)a=h;else for(var b in h)a[b]=h[b];return a}},i=h.promise({}),j;for(j in g)i[j]=g[j].fire,i[j+"With"]=g[j].fireWith;i.done(function(){e="resolved"},c.disable,d.lock).fail(function(){e="rejected"},b.disable,d.lock),a&&a.call(i,i);return i},when:function(a){function m(a){return function(b){e[a]=arguments.length>1?i.call(arguments,0):b,j.notifyWith(k,e)}}function l(a){return function(c){b[a]=arguments.length>1?i.call(arguments,0):c,--g||j.resolveWith(j,b)}}var b=i.call(arguments,0),c=0,d=b.length,e=Array(d),g=d,h=d,j=d<=1&&a&&f.isFunction(a.promise)?a:f.Deferred(),k=j.promise();if(d>1){for(;c<d;c++)b[c]&&b[c].promise&&f.isFunction(b[c].promise)?b[c].promise().then(l(c),j.reject,m(c)):--g;g||j.resolveWith(j,b)}else j!==a&&j.resolveWith(j,d?[a]:[]);return k}}),f.support=function(){var b,d,e,g,h,i,j,k,l,m,n,o,p=c.createElement("div"),q=c.documentElement;p.setAttribute("className","t"),p.innerHTML=" <link/><table></table><a href='/a' style='top:1px;float:left;opacity:.55;'>a</a><input type='checkbox'/>",d=p.getElementsByTagName("*"),e=p.getElementsByTagName("a")[0];if(!d||!d.length||!e)return{};g=c.createElement("select"),h=g.appendChild(c.createElement("option")),i=p.getElementsByTagName("input")[0],b={leadingWhitespace:p.firstChild.nodeType===3,tbody:!p.getElementsByTagName("tbody").length,htmlSerialize:!!p.getElementsByTagName("link").length,style:/top/.test(e.getAttribute("style")),hrefNormalized:e.getAttribute("href")==="/a",opacity:/^0.55/.test(e.style.opacity),cssFloat:!!e.style.cssFloat,checkOn:i.value==="on",optSelected:h.selected,getSetAttribute:p.className!=="t",enctype:!!c.createElement("form").enctype,html5Clone:c.createElement("nav").cloneNode(!0).outerHTML!=="<:nav></:nav>",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,pixelMargin:!0},f.boxModel=b.boxModel=c.compatMode==="CSS1Compat",i.checked=!0,b.noCloneChecked=i.cloneNode(!0).checked,g.disabled=!0,b.optDisabled=!h.disabled;try{delete p.test}catch(r){b.deleteExpando=!1}!p.addEventListener&&p.attachEvent&&p.fireEvent&&(p.attachEvent("onclick",function(){b.noCloneEvent=!1}),p.cloneNode(!0).fireEvent("onclick")),i=c.createElement("input"),i.value="t",i.setAttribute("type","radio"),b.radioValue=i.value==="t",i.setAttribute("checked","checked"),i.setAttribute("name","t"),p.appendChild(i),j=c.createDocumentFragment(),j.appendChild(p.lastChild),b.checkClone=j.cloneNode(!0).cloneNode(!0).lastChild.checked,b.appendChecked=i.checked,j.removeChild(i),j.appendChild(p);if(p.attachEvent)for(n in{submit:1,change:1,focusin:1})m="on"+n,o=m in p,o||(p.setAttribute(m,"return;"),o=typeof p[m]=="function"),b[n+"Bubbles"]=o;j.removeChild(p),j=g=h=p=i=null,f(function(){var d,e,g,h,i,j,l,m,n,q,r,s,t,u=c.getElementsByTagName("body")[0];!u||(m=1,t="padding:0;margin:0;border:",r="position:absolute;top:0;left:0;width:1px;height:1px;",s=t+"0;visibility:hidden;",n="style='"+r+t+"5px solid #000;",q="<div "+n+"display:block;'><div style='"+t+"0;display:block;overflow:hidden;'></div></div>"+"<table "+n+"' cellpadding='0' cellspacing='0'>"+"<tr><td></td></tr></table>",d=c.createElement("div"),d.style.cssText=s+"width:0;height:0;position:static;top:0;margin-top:"+m+"px",u.insertBefore(d,u.firstChild),p=c.createElement("div"),d.appendChild(p),p.innerHTML="<table><tr><td style='"+t+"0;display:none'></td><td>t</td></tr></table>",k=p.getElementsByTagName("td"),o=k[0].offsetHeight===0,k[0].style.display="",k[1].style.display="none",b.reliableHiddenOffsets=o&&k[0].offsetHeight===0,a.getComputedStyle&&(p.innerHTML="",l=c.createElement("div"),l.style.width="0",l.style.marginRight="0",p.style.width="2px",p.appendChild(l),b.reliableMarginRight=(parseInt((a.getComputedStyle(l,null)||{marginRight:0}).marginRight,10)||0)===0),typeof p.style.zoom!="undefined"&&(p.innerHTML="",p.style.width=p.style.padding="1px",p.style.border=0,p.style.overflow="hidden",p.style.display="inline",p.style.zoom=1,b.inlineBlockNeedsLayout=p.offsetWidth===3,p.style.display="block",p.style.overflow="visible",p.innerHTML="<div style='width:5px;'></div>",b.shrinkWrapBlocks=p.offsetWidth!==3),p.style.cssText=r+s,p.innerHTML=q,e=p.firstChild,g=e.firstChild,i=e.nextSibling.firstChild.firstChild,j={doesNotAddBorder:g.offsetTop!==5,doesAddBorderForTableAndCells:i.offsetTop===5},g.style.position="fixed",g.style.top="20px",j.fixedPosition=g.offsetTop===20||g.offsetTop===15,g.style.position=g.style.top="",e.style.overflow="hidden",e.style.position="relative",j.subtractsBorderForOverflowNotVisible=g.offsetTop===-5,j.doesNotIncludeMarginInBodyOffset=u.offsetTop!==m,a.getComputedStyle&&(p.style.marginTop="1%",b.pixelMargin=(a.getComputedStyle(p,null)||{marginTop:0}).marginTop!=="1%"),typeof d.style.zoom!="undefined"&&(d.style.zoom=1),u.removeChild(d),l=p=d=null,f.extend(b,j))});return b}();var j=/^(?:\{.*\}|\[.*\])$/,k=/([A-Z])/g;f.extend({cache:{},uuid:0,expando:"jQuery"+(f.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){a=a.nodeType?f.cache[a[f.expando]]:a[f.expando];return!!a&&!m(a)},data:function(a,c,d,e){if(!!f.acceptData(a)){var g,h,i,j=f.expando,k=typeof c=="string",l=a.nodeType,m=l?f.cache:a,n=l?a[j]:a[j]&&j,o=c==="events";if((!n||!m[n]||!o&&!e&&!m[n].data)&&k&&d===b)return;n||(l?a[j]=n=++f.uuid:n=j),m[n]||(m[n]={},l||(m[n].toJSON=f.noop));if(typeof c=="object"||typeof c=="function")e?m[n]=f.extend(m[n],c):m[n].data=f.extend(m[n].data,c);g=h=m[n],e||(h.data||(h.data={}),h=h.data),d!==b&&(h[f.camelCase(c)]=d);if(o&&!h[c])return g.events;k?(i=h[c],i==null&&(i=h[f.camelCase(c)])):i=h;return i}},removeData:function(a,b,c){if(!!f.acceptData(a)){var d,e,g,h=f.expando,i=a.nodeType,j=i?f.cache:a,k=i?a[h]:h;if(!j[k])return;if(b){d=c?j[k]:j[k].data;if(d){f.isArray(b)||(b in d?b=[b]:(b=f.camelCase(b),b in d?b=[b]:b=b.split(" ")));for(e=0,g=b.length;e<g;e++)delete d[b[e]];if(!(c?m:f.isEmptyObject)(d))return}}if(!c){delete j[k].data;if(!m(j[k]))return}f.support.deleteExpando||!j.setInterval?delete j[k]:j[k]=null,i&&(f.support.deleteExpando?delete a[h]:a.removeAttribute?a.removeAttribute(h):a[h]=null)}},_data:function(a,b,c){return f.data(a,b,c,!0)},acceptData:function(a){if(a.nodeName){var b=f.noData[a.nodeName.toLowerCase()];if(b)return b!==!0&&a.getAttribute("classid")===b}return!0}}),f.fn.extend({data:function(a,c){var d,e,g,h,i,j=this[0],k=0,m=null;if(a===b){if(this.length){m=f.data(j);if(j.nodeType===1&&!f._data(j,"parsedAttrs")){g=j.attributes;for(i=g.length;k<i;k++)h=g[k].name,h.indexOf("data-")===0&&(h=f.camelCase(h.substring(5)),l(j,h,m[h]));f._data(j,"parsedAttrs",!0)}}return m}if(typeof a=="object")return this.each(function(){f.data(this,a)});d=a.split(".",2),d[1]=d[1]?"."+d[1]:"",e=d[1]+"!";return f.access(this,function(c){if(c===b){m=this.triggerHandler("getData"+e,[d[0]]),m===b&&j&&(m=f.data(j,a),m=l(j,a,m));return m===b&&d[1]?this.data(d[0]):m}d[1]=c,this.each(function(){var b=f(this);b.triggerHandler("setData"+e,d),f.data(this,a,c),b.triggerHandler("changeData"+e,d)})},null,c,arguments.length>1,null,!1)},removeData:function(a){return this.each(function(){f.removeData(this,a)})}}),f.extend({_mark:function(a,b){a&&(b=(b||"fx")+"mark",f._data(a,b,(f._data(a,b)||0)+1))},_unmark:function(a,b,c){a!==!0&&(c=b,b=a,a=!1);if(b){c=c||"fx";var d=c+"mark",e=a?0:(f._data(b,d)||1)-1;e?f._data(b,d,e):(f.removeData(b,d,!0),n(b,c,"mark"))}},queue:function(a,b,c){var d;if(a){b=(b||"fx")+"queue",d=f._data(a,b),c&&(!d||f.isArray(c)?d=f._data(a,b,f.makeArray(c)):d.push(c));return d||[]}},dequeue:function(a,b){b=b||"fx";var c=f.queue(a,b),d=c.shift(),e={};d==="inprogress"&&(d=c.shift()),d&&(b==="fx"&&c.unshift("inprogress"),f._data(a,b+".run",e),d.call(a,function(){f.dequeue(a,b)},e)),c.length||(f.removeData(a,b+"queue "+b+".run",!0),n(a,b,"queue"))}}),f.fn.extend({queue:function(a,c){var d=2;typeof a!="string"&&(c=a,a="fx",d--);if(arguments.length<d)return f.queue(this[0],a);return c===b?this:this.each(function(){var b=f.queue(this,a,c);a==="fx"&&b[0]!=="inprogress"&&f.dequeue(this,a)})},dequeue:function(a){return this.each(function(){f.dequeue(this,a)})},delay:function(a,b){a=f.fx?f.fx.speeds[a]||a:a,b=b||"fx";return this.queue(b,function(b,c){var d=setTimeout(b,a);c.stop=function(){clearTimeout(d)}})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,c){function m(){--h||d.resolveWith(e,[e])}typeof a!="string"&&(c=a,a=b),a=a||"fx";var d=f.Deferred(),e=this,g=e.length,h=1,i=a+"defer",j=a+"queue",k=a+"mark",l;while(g--)if(l=f.data(e[g],i,b,!0)||(f.data(e[g],j,b,!0)||f.data(e[g],k,b,!0))&&f.data(e[g],i,f.Callbacks("once memory"),!0))h++,l.add(m);m();return d.promise(c)}});var o=/[\n\t\r]/g,p=/\s+/,q=/\r/g,r=/^(?:button|input)$/i,s=/^(?:button|input|object|select|textarea)$/i,t=/^a(?:rea)?$/i,u=/^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,v=f.support.getSetAttribute,w,x,y;f.fn.extend({attr:function(a,b){return f.access(this,f.attr,a,b,arguments.length>1)},removeAttr:function(a){return this.each(function(){f.removeAttr(this,a)})},prop:function(a,b){return f.access(this,f.prop,a,b,arguments.length>1)},removeProp:function(a){a=f.propFix[a]||a;return this.each(function(){try{this[a]=b,delete this[a]}catch(c){}})},addClass:function(a){var b,c,d,e,g,h,i;if(f.isFunction(a))return this.each(function(b){f(this).addClass(a.call(this,b,this.className))});if(a&&typeof a=="string"){b=a.split(p);for(c=0,d=this.length;c<d;c++){e=this[c];if(e.nodeType===1)if(!e.className&&b.length===1)e.className=a;else{g=" "+e.className+" ";for(h=0,i=b.length;h<i;h++)~g.indexOf(" "+b[h]+" ")||(g+=b[h]+" ");e.className=f.trim(g)}}}return this},removeClass:function(a){var c,d,e,g,h,i,j;if(f.isFunction(a))return this.each(function(b){f(this).removeClass(a.call(this,b,this.className))});if(a&&typeof a=="string"||a===b){c=(a||"").split(p);for(d=0,e=this.length;d<e;d++){g=this[d];if(g.nodeType===1&&g.className)if(a){h=(" "+g.className+" ").replace(o," ");for(i=0,j=c.length;i<j;i++)h=h.replace(" "+c[i]+" "," ");g.className=f.trim(h)}else g.className=""}}return this},toggleClass:function(a,b){var c=typeof a,d=typeof b=="boolean";if(f.isFunction(a))return this.each(function(c){f(this).toggleClass(a.call(this,c,this.className,b),b)});return this.each(function(){if(c==="string"){var e,g=0,h=f(this),i=b,j=a.split(p);while(e=j[g++])i=d?i:!h.hasClass(e),h[i?"addClass":"removeClass"](e)}else if(c==="undefined"||c==="boolean")this.className&&f._data(this,"__className__",this.className),this.className=this.className||a===!1?"":f._data(this,"__className__")||""})},hasClass:function(a){var b=" "+a+" ",c=0,d=this.length;for(;c<d;c++)if(this[c].nodeType===1&&(" "+this[c].className+" ").replace(o," ").indexOf(b)>-1)return!0;return!1},val:function(a){var c,d,e,g=this[0];{if(!!arguments.length){e=f.isFunction(a);return this.each(function(d){var g=f(this),h;if(this.nodeType===1){e?h=a.call(this,d,g.val()):h=a,h==null?h="":typeof h=="number"?h+="":f.isArray(h)&&(h=f.map(h,function(a){return a==null?"":a+""})),c=f.valHooks[this.type]||f.valHooks[this.nodeName.toLowerCase()];if(!c||!("set"in c)||c.set(this,h,"value")===b)this.value=h}})}if(g){c=f.valHooks[g.type]||f.valHooks[g.nodeName.toLowerCase()];if(c&&"get"in c&&(d=c.get(g,"value"))!==b)return d;d=g.value;return typeof d=="string"?d.replace(q,""):d==null?"":d}}}}),f.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c,d,e,g=a.selectedIndex,h=[],i=a.options,j=a.type==="select-one";if(g<0)return null;c=j?g:0,d=j?g+1:i.length;for(;c<d;c++){e=i[c];if(e.selected&&(f.support.optDisabled?!e.disabled:e.getAttribute("disabled")===null)&&(!e.parentNode.disabled||!f.nodeName(e.parentNode,"optgroup"))){b=f(e).val();if(j)return b;h.push(b)}}if(j&&!h.length&&i.length)return f(i[g]).val();return h},set:function(a,b){var c=f.makeArray(b);f(a).find("option").each(function(){this.selected=f.inArray(f(this).val(),c)>=0}),c.length||(a.selectedIndex=-1);return c}}},attrFn:{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0},attr:function(a,c,d,e){var g,h,i,j=a.nodeType;if(!!a&&j!==3&&j!==8&&j!==2){if(e&&c in f.attrFn)return f(a)[c](d);if(typeof a.getAttribute=="undefined")return f.prop(a,c,d);i=j!==1||!f.isXMLDoc(a),i&&(c=c.toLowerCase(),h=f.attrHooks[c]||(u.test(c)?x:w));if(d!==b){if(d===null){f.removeAttr(a,c);return}if(h&&"set"in h&&i&&(g=h.set(a,d,c))!==b)return g;a.setAttribute(c,""+d);return d}if(h&&"get"in h&&i&&(g=h.get(a,c))!==null)return g;g=a.getAttribute(c);return g===null?b:g}},removeAttr:function(a,b){var c,d,e,g,h,i=0;if(b&&a.nodeType===1){d=b.toLowerCase().split(p),g=d.length;for(;i<g;i++)e=d[i],e&&(c=f.propFix[e]||e,h=u.test(e),h||f.attr(a,e,""),a.removeAttribute(v?e:c),h&&c in a&&(a[c]=!1))}},attrHooks:{type:{set:function(a,b){if(r.test(a.nodeName)&&a.parentNode)f.error("type property can't be changed");else if(!f.support.radioValue&&b==="radio"&&f.nodeName(a,"input")){var c=a.value;a.setAttribute("type",b),c&&(a.value=c);return b}}},value:{get:function(a,b){if(w&&f.nodeName(a,"button"))return w.get(a,b);return b in a?a.value:null},set:function(a,b,c){if(w&&f.nodeName(a,"button"))return w.set(a,b,c);a.value=b}}},propFix:{tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},prop:function(a,c,d){var e,g,h,i=a.nodeType;if(!!a&&i!==3&&i!==8&&i!==2){h=i!==1||!f.isXMLDoc(a),h&&(c=f.propFix[c]||c,g=f.propHooks[c]);return d!==b?g&&"set"in g&&(e=g.set(a,d,c))!==b?e:a[c]=d:g&&"get"in g&&(e=g.get(a,c))!==null?e:a[c]}},propHooks:{tabIndex:{get:function(a){var c=a.getAttributeNode("tabindex");return c&&c.specified?parseInt(c.value,10):s.test(a.nodeName)||t.test(a.nodeName)&&a.href?0:b}}}}),f.attrHooks.tabindex=f.propHooks.tabIndex,x={get:function(a,c){var d,e=f.prop(a,c);return e===!0||typeof e!="boolean"&&(d=a.getAttributeNode(c))&&d.nodeValue!==!1?c.toLowerCase():b},set:function(a,b,c){var d;b===!1?f.removeAttr(a,c):(d=f.propFix[c]||c,d in a&&(a[d]=!0),a.setAttribute(c,c.toLowerCase()));return c}},v||(y={name:!0,id:!0,coords:!0},w=f.valHooks.button={get:function(a,c){var d;d=a.getAttributeNode(c);return d&&(y[c]?d.nodeValue!=="":d.specified)?d.nodeValue:b},set:function(a,b,d){var e=a.getAttributeNode(d);e||(e=c.createAttribute(d),a.setAttributeNode(e));return e.nodeValue=b+""}},f.attrHooks.tabindex.set=w.set,f.each(["width","height"],function(a,b){f.attrHooks[b]=f.extend(f.attrHooks[b],{set:function(a,c){if(c===""){a.setAttribute(b,"auto");return c}}})}),f.attrHooks.contenteditable={get:w.get,set:function(a,b,c){b===""&&(b="false"),w.set(a,b,c)}}),f.support.hrefNormalized||f.each(["href","src","width","height"],function(a,c){f.attrHooks[c]=f.extend(f.attrHooks[c],{get:function(a){var d=a.getAttribute(c,2);return d===null?b:d}})}),f.support.style||(f.attrHooks.style={get:function(a){return a.style.cssText.toLowerCase()||b},set:function(a,b){return a.style.cssText=""+b}}),f.support.optSelected||(f.propHooks.selected=f.extend(f.propHooks.selected,{get:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex);return null}})),f.support.enctype||(f.propFix.enctype="encoding"),f.support.checkOn||f.each(["radio","checkbox"],function(){f.valHooks[this]={get:function(a){return a.getAttribute("value")===null?"on":a.value}}}),f.each(["radio","checkbox"],function(){f.valHooks[this]=f.extend(f.valHooks[this],{set:function(a,b){if(f.isArray(b))return a.checked=f.inArray(f(a).val(),b)>=0}})});var z=/^(?:textarea|input|select)$/i,A=/^([^\.]*)?(?:\.(.+))?$/,B=/(?:^|\s)hover(\.\S+)?\b/,C=/^key/,D=/^(?:mouse|contextmenu)|click/,E=/^(?:focusinfocus|focusoutblur)$/,F=/^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/,G=function(
+a){var b=F.exec(a);b&&(b[1]=(b[1]||"").toLowerCase(),b[3]=b[3]&&new RegExp("(?:^|\\s)"+b[3]+"(?:\\s|$)"));return b},H=function(a,b){var c=a.attributes||{};return(!b[1]||a.nodeName.toLowerCase()===b[1])&&(!b[2]||(c.id||{}).value===b[2])&&(!b[3]||b[3].test((c["class"]||{}).value))},I=function(a){return f.event.special.hover?a:a.replace(B,"mouseenter$1 mouseleave$1")};f.event={add:function(a,c,d,e,g){var h,i,j,k,l,m,n,o,p,q,r,s;if(!(a.nodeType===3||a.nodeType===8||!c||!d||!(h=f._data(a)))){d.handler&&(p=d,d=p.handler,g=p.selector),d.guid||(d.guid=f.guid++),j=h.events,j||(h.events=j={}),i=h.handle,i||(h.handle=i=function(a){return typeof f!="undefined"&&(!a||f.event.triggered!==a.type)?f.event.dispatch.apply(i.elem,arguments):b},i.elem=a),c=f.trim(I(c)).split(" ");for(k=0;k<c.length;k++){l=A.exec(c[k])||[],m=l[1],n=(l[2]||"").split(".").sort(),s=f.event.special[m]||{},m=(g?s.delegateType:s.bindType)||m,s=f.event.special[m]||{},o=f.extend({type:m,origType:l[1],data:e,handler:d,guid:d.guid,selector:g,quick:g&&G(g),namespace:n.join(".")},p),r=j[m];if(!r){r=j[m]=[],r.delegateCount=0;if(!s.setup||s.setup.call(a,e,n,i)===!1)a.addEventListener?a.addEventListener(m,i,!1):a.attachEvent&&a.attachEvent("on"+m,i)}s.add&&(s.add.call(a,o),o.handler.guid||(o.handler.guid=d.guid)),g?r.splice(r.delegateCount++,0,o):r.push(o),f.event.global[m]=!0}a=null}},global:{},remove:function(a,b,c,d,e){var g=f.hasData(a)&&f._data(a),h,i,j,k,l,m,n,o,p,q,r,s;if(!!g&&!!(o=g.events)){b=f.trim(I(b||"")).split(" ");for(h=0;h<b.length;h++){i=A.exec(b[h])||[],j=k=i[1],l=i[2];if(!j){for(j in o)f.event.remove(a,j+b[h],c,d,!0);continue}p=f.event.special[j]||{},j=(d?p.delegateType:p.bindType)||j,r=o[j]||[],m=r.length,l=l?new RegExp("(^|\\.)"+l.split(".").sort().join("\\.(?:.*\\.)?")+"(\\.|$)"):null;for(n=0;n<r.length;n++)s=r[n],(e||k===s.origType)&&(!c||c.guid===s.guid)&&(!l||l.test(s.namespace))&&(!d||d===s.selector||d==="**"&&s.selector)&&(r.splice(n--,1),s.selector&&r.delegateCount--,p.remove&&p.remove.call(a,s));r.length===0&&m!==r.length&&((!p.teardown||p.teardown.call(a,l)===!1)&&f.removeEvent(a,j,g.handle),delete o[j])}f.isEmptyObject(o)&&(q=g.handle,q&&(q.elem=null),f.removeData(a,["events","handle"],!0))}},customEvent:{getData:!0,setData:!0,changeData:!0},trigger:function(c,d,e,g){if(!e||e.nodeType!==3&&e.nodeType!==8){var h=c.type||c,i=[],j,k,l,m,n,o,p,q,r,s;if(E.test(h+f.event.triggered))return;h.indexOf("!")>=0&&(h=h.slice(0,-1),k=!0),h.indexOf(".")>=0&&(i=h.split("."),h=i.shift(),i.sort());if((!e||f.event.customEvent[h])&&!f.event.global[h])return;c=typeof c=="object"?c[f.expando]?c:new f.Event(h,c):new f.Event(h),c.type=h,c.isTrigger=!0,c.exclusive=k,c.namespace=i.join("."),c.namespace_re=c.namespace?new RegExp("(^|\\.)"+i.join("\\.(?:.*\\.)?")+"(\\.|$)"):null,o=h.indexOf(":")<0?"on"+h:"";if(!e){j=f.cache;for(l in j)j[l].events&&j[l].events[h]&&f.event.trigger(c,d,j[l].handle.elem,!0);return}c.result=b,c.target||(c.target=e),d=d!=null?f.makeArray(d):[],d.unshift(c),p=f.event.special[h]||{};if(p.trigger&&p.trigger.apply(e,d)===!1)return;r=[[e,p.bindType||h]];if(!g&&!p.noBubble&&!f.isWindow(e)){s=p.delegateType||h,m=E.test(s+h)?e:e.parentNode,n=null;for(;m;m=m.parentNode)r.push([m,s]),n=m;n&&n===e.ownerDocument&&r.push([n.defaultView||n.parentWindow||a,s])}for(l=0;l<r.length&&!c.isPropagationStopped();l++)m=r[l][0],c.type=r[l][1],q=(f._data(m,"events")||{})[c.type]&&f._data(m,"handle"),q&&q.apply(m,d),q=o&&m[o],q&&f.acceptData(m)&&q.apply(m,d)===!1&&c.preventDefault();c.type=h,!g&&!c.isDefaultPrevented()&&(!p._default||p._default.apply(e.ownerDocument,d)===!1)&&(h!=="click"||!f.nodeName(e,"a"))&&f.acceptData(e)&&o&&e[h]&&(h!=="focus"&&h!=="blur"||c.target.offsetWidth!==0)&&!f.isWindow(e)&&(n=e[o],n&&(e[o]=null),f.event.triggered=h,e[h](),f.event.triggered=b,n&&(e[o]=n));return c.result}},dispatch:function(c){c=f.event.fix(c||a.event);var d=(f._data(this,"events")||{})[c.type]||[],e=d.delegateCount,g=[].slice.call(arguments,0),h=!c.exclusive&&!c.namespace,i=f.event.special[c.type]||{},j=[],k,l,m,n,o,p,q,r,s,t,u;g[0]=c,c.delegateTarget=this;if(!i.preDispatch||i.preDispatch.call(this,c)!==!1){if(e&&(!c.button||c.type!=="click")){n=f(this),n.context=this.ownerDocument||this;for(m=c.target;m!=this;m=m.parentNode||this)if(m.disabled!==!0){p={},r=[],n[0]=m;for(k=0;k<e;k++)s=d[k],t=s.selector,p[t]===b&&(p[t]=s.quick?H(m,s.quick):n.is(t)),p[t]&&r.push(s);r.length&&j.push({elem:m,matches:r})}}d.length>e&&j.push({elem:this,matches:d.slice(e)});for(k=0;k<j.length&&!c.isPropagationStopped();k++){q=j[k],c.currentTarget=q.elem;for(l=0;l<q.matches.length&&!c.isImmediatePropagationStopped();l++){s=q.matches[l];if(h||!c.namespace&&!s.namespace||c.namespace_re&&c.namespace_re.test(s.namespace))c.data=s.data,c.handleObj=s,o=((f.event.special[s.origType]||{}).handle||s.handler).apply(q.elem,g),o!==b&&(c.result=o,o===!1&&(c.preventDefault(),c.stopPropagation()))}}i.postDispatch&&i.postDispatch.call(this,c);return c.result}},props:"attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(a,b){a.which==null&&(a.which=b.charCode!=null?b.charCode:b.keyCode);return a}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(a,d){var e,f,g,h=d.button,i=d.fromElement;a.pageX==null&&d.clientX!=null&&(e=a.target.ownerDocument||c,f=e.documentElement,g=e.body,a.pageX=d.clientX+(f&&f.scrollLeft||g&&g.scrollLeft||0)-(f&&f.clientLeft||g&&g.clientLeft||0),a.pageY=d.clientY+(f&&f.scrollTop||g&&g.scrollTop||0)-(f&&f.clientTop||g&&g.clientTop||0)),!a.relatedTarget&&i&&(a.relatedTarget=i===a.target?d.toElement:i),!a.which&&h!==b&&(a.which=h&1?1:h&2?3:h&4?2:0);return a}},fix:function(a){if(a[f.expando])return a;var d,e,g=a,h=f.event.fixHooks[a.type]||{},i=h.props?this.props.concat(h.props):this.props;a=f.Event(g);for(d=i.length;d;)e=i[--d],a[e]=g[e];a.target||(a.target=g.srcElement||c),a.target.nodeType===3&&(a.target=a.target.parentNode),a.metaKey===b&&(a.metaKey=a.ctrlKey);return h.filter?h.filter(a,g):a},special:{ready:{setup:f.bindReady},load:{noBubble:!0},focus:{delegateType:"focusin"},blur:{delegateType:"focusout"},beforeunload:{setup:function(a,b,c){f.isWindow(this)&&(this.onbeforeunload=c)},teardown:function(a,b){this.onbeforeunload===b&&(this.onbeforeunload=null)}}},simulate:function(a,b,c,d){var e=f.extend(new f.Event,c,{type:a,isSimulated:!0,originalEvent:{}});d?f.event.trigger(e,null,b):f.event.dispatch.call(b,e),e.isDefaultPrevented()&&c.preventDefault()}},f.event.handle=f.event.dispatch,f.removeEvent=c.removeEventListener?function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c,!1)}:function(a,b,c){a.detachEvent&&a.detachEvent("on"+b,c)},f.Event=function(a,b){if(!(this instanceof f.Event))return new f.Event(a,b);a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||a.returnValue===!1||a.getPreventDefault&&a.getPreventDefault()?K:J):this.type=a,b&&f.extend(this,b),this.timeStamp=a&&a.timeStamp||f.now(),this[f.expando]=!0},f.Event.prototype={preventDefault:function(){this.isDefaultPrevented=K;var a=this.originalEvent;!a||(a.preventDefault?a.preventDefault():a.returnValue=!1)},stopPropagation:function(){this.isPropagationStopped=K;var a=this.originalEvent;!a||(a.stopPropagation&&a.stopPropagation(),a.cancelBubble=!0)},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=K,this.stopPropagation()},isDefaultPrevented:J,isPropagationStopped:J,isImmediatePropagationStopped:J},f.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){f.event.special[a]={delegateType:b,bindType:b,handle:function(a){var c=this,d=a.relatedTarget,e=a.handleObj,g=e.selector,h;if(!d||d!==c&&!f.contains(c,d))a.type=e.origType,h=e.handler.apply(this,arguments),a.type=b;return h}}}),f.support.submitBubbles||(f.event.special.submit={setup:function(){if(f.nodeName(this,"form"))return!1;f.event.add(this,"click._submit keypress._submit",function(a){var c=a.target,d=f.nodeName(c,"input")||f.nodeName(c,"button")?c.form:b;d&&!d._submit_attached&&(f.event.add(d,"submit._submit",function(a){a._submit_bubble=!0}),d._submit_attached=!0)})},postDispatch:function(a){a._submit_bubble&&(delete a._submit_bubble,this.parentNode&&!a.isTrigger&&f.event.simulate("submit",this.parentNode,a,!0))},teardown:function(){if(f.nodeName(this,"form"))return!1;f.event.remove(this,"._submit")}}),f.support.changeBubbles||(f.event.special.change={setup:function(){if(z.test(this.nodeName)){if(this.type==="checkbox"||this.type==="radio")f.event.add(this,"propertychange._change",function(a){a.originalEvent.propertyName==="checked"&&(this._just_changed=!0)}),f.event.add(this,"click._change",function(a){this._just_changed&&!a.isTrigger&&(this._just_changed=!1,f.event.simulate("change",this,a,!0))});return!1}f.event.add(this,"beforeactivate._change",function(a){var b=a.target;z.test(b.nodeName)&&!b._change_attached&&(f.event.add(b,"change._change",function(a){this.parentNode&&!a.isSimulated&&!a.isTrigger&&f.event.simulate("change",this.parentNode,a,!0)}),b._change_attached=!0)})},handle:function(a){var b=a.target;if(this!==b||a.isSimulated||a.isTrigger||b.type!=="radio"&&b.type!=="checkbox")return a.handleObj.handler.apply(this,arguments)},teardown:function(){f.event.remove(this,"._change");return z.test(this.nodeName)}}),f.support.focusinBubbles||f.each({focus:"focusin",blur:"focusout"},function(a,b){var d=0,e=function(a){f.event.simulate(b,a.target,f.event.fix(a),!0)};f.event.special[b]={setup:function(){d++===0&&c.addEventListener(a,e,!0)},teardown:function(){--d===0&&c.removeEventListener(a,e,!0)}}}),f.fn.extend({on:function(a,c,d,e,g){var h,i;if(typeof a=="object"){typeof c!="string"&&(d=d||c,c=b);for(i in a)this.on(i,c,d,a[i],g);return this}d==null&&e==null?(e=c,d=c=b):e==null&&(typeof c=="string"?(e=d,d=b):(e=d,d=c,c=b));if(e===!1)e=J;else if(!e)return this;g===1&&(h=e,e=function(a){f().off(a);return h.apply(this,arguments)},e.guid=h.guid||(h.guid=f.guid++));return this.each(function(){f.event.add(this,a,e,d,c)})},one:function(a,b,c,d){return this.on(a,b,c,d,1)},off:function(a,c,d){if(a&&a.preventDefault&&a.handleObj){var e=a.handleObj;f(a.delegateTarget).off(e.namespace?e.origType+"."+e.namespace:e.origType,e.selector,e.handler);return this}if(typeof a=="object"){for(var g in a)this.off(g,c,a[g]);return this}if(c===!1||typeof c=="function")d=c,c=b;d===!1&&(d=J);return this.each(function(){f.event.remove(this,a,d,c)})},bind:function(a,b,c){return this.on(a,null,b,c)},unbind:function(a,b){return this.off(a,null,b)},live:function(a,b,c){f(this.context).on(a,this.selector,b,c);return this},die:function(a,b){f(this.context).off(a,this.selector||"**",b);return this},delegate:function(a,b,c,d){return this.on(b,a,c,d)},undelegate:function(a,b,c){return arguments.length==1?this.off(a,"**"):this.off(b,a,c)},trigger:function(a,b){return this.each(function(){f.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0])return f.event.trigger(a,b,this[0],!0)},toggle:function(a){var b=arguments,c=a.guid||f.guid++,d=0,e=function(c){var e=(f._data(this,"lastToggle"+a.guid)||0)%d;f._data(this,"lastToggle"+a.guid,e+1),c.preventDefault();return b[e].apply(this,arguments)||!1};e.guid=c;while(d<b.length)b[d++].guid=c;return this.click(e)},hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}}),f.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(a,b){f.fn[b]=function(a,c){c==null&&(c=a,a=null);return arguments.length>0?this.on(b,null,a,c):this.trigger(b)},f.attrFn&&(f.attrFn[b]=!0),C.test(b)&&(f.event.fixHooks[b]=f.event.keyHooks),D.test(b)&&(f.event.fixHooks[b]=f.event.mouseHooks)}),function(){function x(a,b,c,e,f,g){for(var h=0,i=e.length;h<i;h++){var j=e[h];if(j){var k=!1;j=j[a];while(j){if(j[d]===c){k=e[j.sizset];break}if(j.nodeType===1){g||(j[d]=c,j.sizset=h);if(typeof b!="string"){if(j===b){k=!0;break}}else if(m.filter(b,[j]).length>0){k=j;break}}j=j[a]}e[h]=k}}}function w(a,b,c,e,f,g){for(var h=0,i=e.length;h<i;h++){var j=e[h];if(j){var k=!1;j=j[a];while(j){if(j[d]===c){k=e[j.sizset];break}j.nodeType===1&&!g&&(j[d]=c,j.sizset=h);if(j.nodeName.toLowerCase()===b){k=j;break}j=j[a]}e[h]=k}}}var a=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,d="sizcache"+(Math.random()+"").replace(".",""),e=0,g=Object.prototype.toString,h=!1,i=!0,j=/\\/g,k=/\r\n/g,l=/\W/;[0,0].sort(function(){i=!1;return 0});var m=function(b,d,e,f){e=e||[],d=d||c;var h=d;if(d.nodeType!==1&&d.nodeType!==9)return[];if(!b||typeof b!="string")return e;var i,j,k,l,n,q,r,t,u=!0,v=m.isXML(d),w=[],x=b;do{a.exec(""),i=a.exec(x);if(i){x=i[3],w.push(i[1]);if(i[2]){l=i[3];break}}}while(i);if(w.length>1&&p.exec(b))if(w.length===2&&o.relative[w[0]])j=y(w[0]+w[1],d,f);else{j=o.relative[w[0]]?[d]:m(w.shift(),d);while(w.length)b=w.shift(),o.relative[b]&&(b+=w.shift()),j=y(b,j,f)}else{!f&&w.length>1&&d.nodeType===9&&!v&&o.match.ID.test(w[0])&&!o.match.ID.test(w[w.length-1])&&(n=m.find(w.shift(),d,v),d=n.expr?m.filter(n.expr,n.set)[0]:n.set[0]);if(d){n=f?{expr:w.pop(),set:s(f)}:m.find(w.pop(),w.length===1&&(w[0]==="~"||w[0]==="+")&&d.parentNode?d.parentNode:d,v),j=n.expr?m.filter(n.expr,n.set):n.set,w.length>0?k=s(j):u=!1;while(w.length)q=w.pop(),r=q,o.relative[q]?r=w.pop():q="",r==null&&(r=d),o.relative[q](k,r,v)}else k=w=[]}k||(k=j),k||m.error(q||b);if(g.call(k)==="[object Array]")if(!u)e.push.apply(e,k);else if(d&&d.nodeType===1)for(t=0;k[t]!=null;t++)k[t]&&(k[t]===!0||k[t].nodeType===1&&m.contains(d,k[t]))&&e.push(j[t]);else for(t=0;k[t]!=null;t++)k[t]&&k[t].nodeType===1&&e.push(j[t]);else s(k,e);l&&(m(l,h,e,f),m.uniqueSort(e));return e};m.uniqueSort=function(a){if(u){h=i,a.sort(u);if(h)for(var b=1;b<a.length;b++)a[b]===a[b-1]&&a.splice(b--,1)}return a},m.matches=function(a,b){return m(a,null,null,b)},m.matchesSelector=function(a,b){return m(b,null,null,[a]).length>0},m.find=function(a,b,c){var d,e,f,g,h,i;if(!a)return[];for(e=0,f=o.order.length;e<f;e++){h=o.order[e];if(g=o.leftMatch[h].exec(a)){i=g[1],g.splice(1,1);if(i.substr(i.length-1)!=="\\"){g[1]=(g[1]||"").replace(j,""),d=o.find[h](g,b,c);if(d!=null){a=a.replace(o.match[h],"");break}}}}d||(d=typeof b.getElementsByTagName!="undefined"?b.getElementsByTagName("*"):[]);return{set:d,expr:a}},m.filter=function(a,c,d,e){var f,g,h,i,j,k,l,n,p,q=a,r=[],s=c,t=c&&c[0]&&m.isXML(c[0]);while(a&&c.length){for(h in o.filter)if((f=o.leftMatch[h].exec(a))!=null&&f[2]){k=o.filter[h],l=f[1],g=!1,f.splice(1,1);if(l.substr(l.length-1)==="\\")continue;s===r&&(r=[]);if(o.preFilter[h]){f=o.preFilter[h](f,s,d,r,e,t);if(!f)g=i=!0;else if(f===!0)continue}if(f)for(n=0;(j=s[n])!=null;n++)j&&(i=k(j,f,n,s),p=e^i,d&&i!=null?p?g=!0:s[n]=!1:p&&(r.push(j),g=!0));if(i!==b){d||(s=r),a=a.replace(o.match[h],"");if(!g)return[];break}}if(a===q)if(g==null)m.error(a);else break;q=a}return s},m.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)};var n=m.getText=function(a){var b,c,d=a.nodeType,e="";if(d){if(d===1||d===9||d===11){if(typeof a.textContent=="string")return a.textContent;if(typeof a.innerText=="string")return a.innerText.replace(k,"");for(a=a.firstChild;a;a=a.nextSibling)e+=n(a)}else if(d===3||d===4)return a.nodeValue}else for(b=0;c=a[b];b++)c.nodeType!==8&&(e+=n(c));return e},o=m.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,CLASS:/\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/,TAG:/^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/},leftMatch:{},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(a){return a.getAttribute("href")},type:function(a){return a.getAttribute("type")}},relative:{"+":function(a,b){var c=typeof b=="string",d=c&&!l.test(b),e=c&&!d;d&&(b=b.toLowerCase());for(var f=0,g=a.length,h;f<g;f++)if(h=a[f]){while((h=h.previousSibling)&&h.nodeType!==1);a[f]=e||h&&h.nodeName.toLowerCase()===b?h||!1:h===b}e&&m.filter(b,a,!0)},">":function(a,b){var c,d=typeof b=="string",e=0,f=a.length;if(d&&!l.test(b)){b=b.toLowerCase();for(;e<f;e++){c=a[e];if(c){var g=c.parentNode;a[e]=g.nodeName.toLowerCase()===b?g:!1}}}else{for(;e<f;e++)c=a[e],c&&(a[e]=d?c.parentNode:c.parentNode===b);d&&m.filter(b,a,!0)}},"":function(a,b,c){var d,f=e++,g=x;typeof b=="string"&&!l.test(b)&&(b=b.toLowerCase(),d=b,g=w),g("parentNode",b,f,a,d,c)},"~":function(a,b,c){var d,f=e++,g=x;typeof b=="string"&&!l.test(b)&&(b=b.toLowerCase(),d=b,g=w),g("previousSibling",b,f,a,d,c)}},find:{ID:function(a,b,c){if(typeof b.getElementById!="undefined"&&!c){var d=b.getElementById(a[1]);return d&&d.parentNode?[d]:[]}},NAME:function(a,b){if(typeof b.getElementsByName!="undefined"){var c=[],d=b.getElementsByName(a[1]);for(var e=0,f=d.length;e<f;e++)d[e].getAttribute("name")===a[1]&&c.push(d[e]);return c.length===0?null:c}},TAG:function(a,b){if(typeof b.getElementsByTagName!="undefined")return b.getElementsByTagName(a[1])}},preFilter:{CLASS:function(a,b,c,d,e,f){a=" "+a[1].replace(j,"")+" ";if(f)return a;for(var g=0,h;(h=b[g])!=null;g++)h&&(e^(h.className&&(" "+h.className+" ").replace(/[\t\n\r]/g," ").indexOf(a)>=0)?c||d.push(h):c&&(b[g]=!1));return!1},ID:function(a){return a[1].replace(j,"")},TAG:function(a,b){return a[1].replace(j,"").toLowerCase()},CHILD:function(a){if(a[1]==="nth"){a[2]||m.error(a[0]),a[2]=a[2].replace(/^\+|\s*/g,"");var b=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(a[2]==="even"&&"2n"||a[2]==="odd"&&"2n+1"||!/\D/.test(a[2])&&"0n+"+a[2]||a[2]);a[2]=b[1]+(b[2]||1)-0,a[3]=b[3]-0}else a[2]&&m.error(a[0]);a[0]=e++;return a},ATTR:function(a,b,c,d,e,f){var g=a[1]=a[1].replace(j,"");!f&&o.attrMap[g]&&(a[1]=o.attrMap[g]),a[4]=(a[4]||a[5]||"").replace(j,""),a[2]==="~="&&(a[4]=" "+a[4]+" ");return a},PSEUDO:function(b,c,d,e,f){if(b[1]==="not")if((a.exec(b[3])||"").length>1||/^\w/.test(b[3]))b[3]=m(b[3],null,null,c);else{var g=m.filter(b[3],c,d,!0^f);d||e.push.apply(e,g);return!1}else if(o.match.POS.test(b[0])||o.match.CHILD.test(b[0]))return!0;return b},POS:function(a){a.unshift(!0);return a}},filters:{enabled:function(a){return a.disabled===!1&&a.type!=="hidden"},disabled:function(a){return a.disabled===!0},checked:function(a){return a.checked===!0},selected:function(a){a.parentNode&&a.parentNode.selectedIndex;return a.selected===!0},parent:function(a){return!!a.firstChild},empty:function(a){return!a.firstChild},has:function(a,b,c){return!!m(c[3],a).length},header:function(a){return/h\d/i.test(a.nodeName)},text:function(a){var b=a.getAttribute("type"),c=a.type;return a.nodeName.toLowerCase()==="input"&&"text"===c&&(b===c||b===null)},radio:function(a){return a.nodeName.toLowerCase()==="input"&&"radio"===a.type},checkbox:function(a){return a.nodeName.toLowerCase()==="input"&&"checkbox"===a.type},file:function(a){return a.nodeName.toLowerCase()==="input"&&"file"===a.type},password:function(a){return a.nodeName.toLowerCase()==="input"&&"password"===a.type},submit:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"submit"===a.type},image:function(a){return a.nodeName.toLowerCase()==="input"&&"image"===a.type},reset:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"reset"===a.type},button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&"button"===a.type||b==="button"},input:function(a){return/input|select|textarea|button/i.test(a.nodeName)},focus:function(a){return a===a.ownerDocument.activeElement}},setFilters:{first:function(a,b){return b===0},last:function(a,b,c,d){return b===d.length-1},even:function(a,b){return b%2===0},odd:function(a,b){return b%2===1},lt:function(a,b,c){return b<c[3]-0},gt:function(a,b,c){return b>c[3]-0},nth:function(a,b,c){return c[3]-0===b},eq:function(a,b,c){return c[3]-0===b}},filter:{PSEUDO:function(a,b,c,d){var e=b[1],f=o.filters[e];if(f)return f(a,c,b,d);if(e==="contains")return(a.textContent||a.innerText||n([a])||"").indexOf(b[3])>=0;if(e==="not"){var g=b[3];for(var h=0,i=g.length;h<i;h++)if(g[h]===a)return!1;return!0}m.error(e)},CHILD:function(a,b){var c,e,f,g,h,i,j,k=b[1],l=a;switch(k){case"only":case"first":while(l=l.previousSibling)if(l.nodeType===1)return!1;if(k==="first")return!0;l=a;case"last":while(l=l.nextSibling)if(l.nodeType===1)return!1;return!0;case"nth":c=b[2],e=b[3];if(c===1&&e===0)return!0;f=b[0],g=a.parentNode;if(g&&(g[d]!==f||!a.nodeIndex)){i=0;for(l=g.firstChild;l;l=l.nextSibling)l.nodeType===1&&(l.nodeIndex=++i);g[d]=f}j=a.nodeIndex-e;return c===0?j===0:j%c===0&&j/c>=0}},ID:function(a,b){return a.nodeType===1&&a.getAttribute("id")===b},TAG:function(a,b){return b==="*"&&a.nodeType===1||!!a.nodeName&&a.nodeName.toLowerCase()===b},CLASS:function(a,b){return(" "+(a.className||a.getAttribute("class"))+" ").indexOf(b)>-1},ATTR:function(a,b){var c=b[1],d=m.attr?m.attr(a,c):o.attrHandle[c]?o.attrHandle[c](a):a[c]!=null?a[c]:a.getAttribute(c),e=d+"",f=b[2],g=b[4];return d==null?f==="!=":!f&&m.attr?d!=null:f==="="?e===g:f==="*="?e.indexOf(g)>=0:f==="~="?(" "+e+" ").indexOf(g)>=0:g?f==="!="?e!==g:f==="^="?e.indexOf(g)===0:f==="$="?e.substr(e.length-g.length)===g:f==="|="?e===g||e.substr(0,g.length+1)===g+"-":!1:e&&d!==!1},POS:function(a,b,c,d){var e=b[2],f=o.setFilters[e];if(f)return f(a,c,b,d)}}},p=o.match.POS,q=function(a,b){return"\\"+(b-0+1)};for(var r in o.match)o.match[r]=new RegExp(o.match[r].source+/(?![^\[]*\])(?![^\(]*\))/.source),o.leftMatch[r]=new RegExp(/(^(?:.|\r|\n)*?)/.source+o.match[r].source.replace(/\\(\d+)/g,q));o.match.globalPOS=p;var s=function(a,b){a=Array.prototype.slice.call(a,0);if(b){b.push.apply(b,a);return b}return a};try{Array.prototype.slice.call(c.documentElement.childNodes,0)[0].nodeType}catch(t){s=function(a,b){var c=0,d=b||[];if(g.call(a)==="[object Array]")Array.prototype.push.apply(d,a);else if(typeof a.length=="number")for(var e=a.length;c<e;c++)d.push(a[c]);else for(;a[c];c++)d.push(a[c]);return d}}var u,v;c.documentElement.compareDocumentPosition?u=function(a,b){if(a===b){h=!0;return 0}if(!a.compareDocumentPosition||!b.compareDocumentPosition)return a.compareDocumentPosition?-1:1;return a.compareDocumentPosition(b)&4?-1:1}:(u=function(a,b){if(a===b){h=!0;return 0}if(a.sourceIndex&&b.sourceIndex)return a.sourceIndex-b.sourceIndex;var c,d,e=[],f=[],g=a.parentNode,i=b.parentNode,j=g;if(g===i)return v(a,b);if(!g)return-1;if(!i)return 1;while(j)e.unshift(j),j=j.parentNode;j=i;while(j)f.unshift(j),j=j.parentNode;c=e.length,d=f.length;for(var k=0;k<c&&k<d;k++)if(e[k]!==f[k])return v(e[k],f[k]);return k===c?v(a,f[k],-1):v(e[k],b,1)},v=function(a,b,c){if(a===b)return c;var d=a.nextSibling;while(d){if(d===b)return-1;d=d.nextSibling}return 1}),function(){var a=c.createElement("div"),d="script"+(new Date).getTime(),e=c.documentElement;a.innerHTML="<a name='"+d+"'/>",e.insertBefore(a,e.firstChild),c.getElementById(d)&&(o.find.ID=function(a,c,d){if(typeof c.getElementById!="undefined"&&!d){var e=c.getElementById(a[1]);return e?e.id===a[1]||typeof e.getAttributeNode!="undefined"&&e.getAttributeNode("id").nodeValue===a[1]?[e]:b:[]}},o.filter.ID=function(a,b){var c=typeof a.getAttributeNode!="undefined"&&a.getAttributeNode("id");return a.nodeType===1&&c&&c.nodeValue===b}),e.removeChild(a),e=a=null}(),function(){var a=c.createElement("div");a.appendChild(c.createComment("")),a.getElementsByTagName("*").length>0&&(o.find.TAG=function(a,b){var c=b.getElementsByTagName(a[1]);if(a[1]==="*"){var d=[];for(var e=0;c[e];e++)c[e].nodeType===1&&d.push(c[e]);c=d}return c}),a.innerHTML="<a href='#'></a>",a.firstChild&&typeof a.firstChild.getAttribute!="undefined"&&a.firstChild.getAttribute("href")!=="#"&&(o.attrHandle.href=function(a){return a.getAttribute("href",2)}),a=null}(),c.querySelectorAll&&function(){var a=m,b=c.createElement("div"),d="__sizzle__";b.innerHTML="<p class='TEST'></p>";if(!b.querySelectorAll||b.querySelectorAll(".TEST").length!==0){m=function(b,e,f,g){e=e||c;if(!g&&!m.isXML(e)){var h=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b);if(h&&(e.nodeType===1||e.nodeType===9)){if(h[1])return s(e.getElementsByTagName(b),f);if(h[2]&&o.find.CLASS&&e.getElementsByClassName)return s(e.getElementsByClassName(h[2]),f)}if(e.nodeType===9){if(b==="body"&&e.body)return s([e.body],f);if(h&&h[3]){var i=e.getElementById(h[3]);if(!i||!i.parentNode)return s([],f);if(i.id===h[3])return s([i],f)}try{return s(e.querySelectorAll(b),f)}catch(j){}}else if(e.nodeType===1&&e.nodeName.toLowerCase()!=="object"){var k=e,l=e.getAttribute("id"),n=l||d,p=e.parentNode,q=/^\s*[+~]/.test(b);l?n=n.replace(/'/g,"\\$&"):e.setAttribute("id",n),q&&p&&(e=e.parentNode);try{if(!q||p)return s(e.querySelectorAll("[id='"+n+"'] "+b),f)}catch(r){}finally{l||k.removeAttribute("id")}}}return a(b,e,f,g)};for(var e in a)m[e]=a[e];b=null}}(),function(){var a=c.documentElement,b=a.matchesSelector||a.mozMatchesSelector||a.webkitMatchesSelector||a.msMatchesSelector;if(b){var d=!b.call(c.createElement("div"),"div"),e=!1;try{b.call(c.documentElement,"[test!='']:sizzle")}catch(f){e=!0}m.matchesSelector=function(a,c){c=c.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!m.isXML(a))try{if(e||!o.match.PSEUDO.test(c)&&!/!=/.test(c)){var f=b.call(a,c);if(f||!d||a.document&&a.document.nodeType!==11)return f}}catch(g){}return m(c,null,null,[a]).length>0}}}(),function(){var a=c.createElement("div");a.innerHTML="<div class='test e'></div><div class='test'></div>";if(!!a.getElementsByClassName&&a.getElementsByClassName("e").length!==0){a.lastChild.className="e";if(a.getElementsByClassName("e").length===1)return;o.order.splice(1,0,"CLASS"),o.find.CLASS=function(a,b,c){if(typeof b.getElementsByClassName!="undefined"&&!c)return b.getElementsByClassName(a[1])},a=null}}(),c.documentElement.contains?m.contains=function(a,b){return a!==b&&(a.contains?a.contains(b):!0)}:c.documentElement.compareDocumentPosition?m.contains=function(a,b){return!!(a.compareDocumentPosition(b)&16)}:m.contains=function(){return!1},m.isXML=function(a){var b=(a?a.ownerDocument||a:0).documentElement;return b?b.nodeName!=="HTML":!1};var y=function(a,b,c){var d,e=[],f="",g=b.nodeType?[b]:b;while(d=o.match.PSEUDO.exec(a))f+=d[0],a=a.replace(o.match.PSEUDO,"");a=o.relative[a]?a+"*":a;for(var h=0,i=g.length;h<i;h++)m(a,g[h],e,c);return m.filter(f,e)};m.attr=f.attr,m.selectors.attrMap={},f.find=m,f.expr=m.selectors,f.expr[":"]=f.expr.filters,f.unique=m.uniqueSort,f.text=m.getText,f.isXMLDoc=m.isXML,f.contains=m.contains}();var L=/Until$/,M=/^(?:parents|prevUntil|prevAll)/,N=/,/,O=/^.[^:#\[\.,]*$/,P=Array.prototype.slice,Q=f.expr.match.globalPOS,R={children:!0,contents:!0,next:!0,prev:!0};f.fn.extend({find:function(a){var b=this,c,d;if(typeof a!="string")return f(a).filter(function(){for(c=0,d=b.length;c<d;c++)if(f.contains(b[c],this))return!0});var e=this.pushStack("","find",a),g,h,i;for(c=0,d=this.length;c<d;c++){g=e.length,f.find(a,this[c],e);if(c>0)for(h=g;h<e.length;h++)for(i=0;i<g;i++)if(e[i]===e[h]){e.splice(h--,1);break}}return e},has:function(a){var b=f(a);return this.filter(function(){for(var a=0,c=b.length;a<c;a++)if(f.contains(this,b[a]))return!0})},not:function(a){return this.pushStack(T(this,a,!1),"not",a)},filter:function(a){return this.pushStack(T(this,a,!0),"filter",a)},is:function(a){return!!a&&(typeof a=="string"?Q.test(a)?f(a,this.context).index(this[0])>=0:f.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c=[],d,e,g=this[0];if(f.isArray(a)){var h=1;while(g&&g.ownerDocument&&g!==b){for(d=0;d<a.length;d++)f(g).is(a[d])&&c.push({selector:a[d],elem:g,level:h});g=g.parentNode,h++}return c}var i=Q.test(a)||typeof a!="string"?f(a,b||this.context):0;for(d=0,e=this.length;d<e;d++){g=this[d];while(g){if(i?i.index(g)>-1:f.find.matchesSelector(g,a)){c.push(g);break}g=g.parentNode;if(!g||!g.ownerDocument||g===b||g.nodeType===11)break}}c=c.length>1?f.unique(c):c;return this.pushStack(c,"closest",a)},index:function(a){if(!a)return this[0]&&this[0].parentNode?this.prevAll().length:-1;if(typeof a=="string")return f.inArray(this[0],f(a));return f.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var c=typeof a=="string"?f(a,b):f.makeArray(a&&a.nodeType?[a]:a),d=f.merge(this.get(),c);return this.pushStack(S(c[0])||S(d[0])?d:f.unique(d))},andSelf:function(){return this.add(this.prevObject)}}),f.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return f.dir(a,"parentNode")},parentsUntil:function(a,b,c){return f.dir(a,"parentNode",c)},next:function(a){return f.nth(a,2,"nextSibling")},prev:function(a){return f.nth(a,2,"previousSibling")},nextAll:function(a){return f.dir(a,"nextSibling")},prevAll:function(a){return f.dir(a,"previousSibling")},nextUntil:function(a,b,c){return f.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return f.dir(a,"previousSibling",c)},siblings:function(a){return f.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return f.sibling(a.firstChild)},contents:function(a){return f.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:f.makeArray(a.childNodes)}},function(a,b){f.fn[a]=function(c,d){var e=f.map(this,b,c);L.test(a)||(d=c),d&&typeof d=="string"&&(e=f.filter(d,e)),e=this.length>1&&!R[a]?f.unique(e):e,(this.length>1||N.test(d))&&M.test(a)&&(e=e.reverse());return this.pushStack(e,a,P.call(arguments).join(","))}}),f.extend({filter:function(a,b,c){c&&(a=":not("+a+")");return b.length===1?f.find.matchesSelector(b[0],a)?[b[0]]:[]:f.find.matches(a,b)},dir:function(a,c,d){var e=[],g=a[c];while(g&&g.nodeType!==9&&(d===b||g.nodeType!==1||!f(g).is(d)))g.nodeType===1&&e.push(g),g=g[c];return e},nth:function(a,b,c,d){b=b||1;var e=0;for(;a;a=a[c])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var V="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",W=/ jQuery\d+="(?:\d+|null)"/g,X=/^\s+/,Y=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,Z=/<([\w:]+)/,$=/<tbody/i,_=/<|&#?\w+;/,ba=/<(?:script|style)/i,bb=/<(?:script|object|embed|option|style)/i,bc=new RegExp("<(?:"+V+")[\\s/>]","i"),bd=/checked\s*(?:[^=]|=\s*.checked.)/i,be=/\/(java|ecma)script/i,bf=/^\s*<!(?:\[CDATA\[|\-\-)/,bg={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,"",""]},bh=U(c);bg.optgroup=bg.option,bg.tbody=bg.tfoot=bg.colgroup=bg.caption=bg.thead,bg.th=bg.td,f.support.htmlSerialize||(bg._default=[1,"div<div>","</div>"]),f.fn.extend({text:function(a){return f.access(this,function(a){return a===b?f.text(this):this.empty().append((this[0]&&this[0].ownerDocument||c).createTextNode(a))},null,a,arguments.length)},wrapAll:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapAll(a.call(this,b))});if(this[0]){var b=f(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapInner(a.call(this,b))});return this.each(function(){var b=f(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=f.isFunction(a);return this.each(function(c){f(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){f.nodeName(this,"body")||f(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=f
+.clean(arguments);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,f.clean(arguments));return a}},remove:function(a,b){for(var c=0,d;(d=this[c])!=null;c++)if(!a||f.filter(a,[d]).length)!b&&d.nodeType===1&&(f.cleanData(d.getElementsByTagName("*")),f.cleanData([d])),d.parentNode&&d.parentNode.removeChild(d);return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&f.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return f.clone(this,a,b)})},html:function(a){return f.access(this,function(a){var c=this[0]||{},d=0,e=this.length;if(a===b)return c.nodeType===1?c.innerHTML.replace(W,""):null;if(typeof a=="string"&&!ba.test(a)&&(f.support.leadingWhitespace||!X.test(a))&&!bg[(Z.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Y,"<$1></$2>");try{for(;d<e;d++)c=this[d]||{},c.nodeType===1&&(f.cleanData(c.getElementsByTagName("*")),c.innerHTML=a);c=0}catch(g){}}c&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(a){if(this[0]&&this[0].parentNode){if(f.isFunction(a))return this.each(function(b){var c=f(this),d=c.html();c.replaceWith(a.call(this,b,d))});typeof a!="string"&&(a=f(a).detach());return this.each(function(){var b=this.nextSibling,c=this.parentNode;f(this).remove(),b?f(b).before(a):f(c).append(a)})}return this.length?this.pushStack(f(f.isFunction(a)?a():a),"replaceWith",a):this},detach:function(a){return this.remove(a,!0)},domManip:function(a,c,d){var e,g,h,i,j=a[0],k=[];if(!f.support.checkClone&&arguments.length===3&&typeof j=="string"&&bd.test(j))return this.each(function(){f(this).domManip(a,c,d,!0)});if(f.isFunction(j))return this.each(function(e){var g=f(this);a[0]=j.call(this,e,c?g.html():b),g.domManip(a,c,d)});if(this[0]){i=j&&j.parentNode,f.support.parentNode&&i&&i.nodeType===11&&i.childNodes.length===this.length?e={fragment:i}:e=f.buildFragment(a,this,k),h=e.fragment,h.childNodes.length===1?g=h=h.firstChild:g=h.firstChild;if(g){c=c&&f.nodeName(g,"tr");for(var l=0,m=this.length,n=m-1;l<m;l++)d.call(c?bi(this[l],g):this[l],e.cacheable||m>1&&l<n?f.clone(h,!0,!0):h)}k.length&&f.each(k,function(a,b){b.src?f.ajax({type:"GET",global:!1,url:b.src,async:!1,dataType:"script"}):f.globalEval((b.text||b.textContent||b.innerHTML||"").replace(bf,"/*$0*/")),b.parentNode&&b.parentNode.removeChild(b)})}return this}}),f.buildFragment=function(a,b,d){var e,g,h,i,j=a[0];b&&b[0]&&(i=b[0].ownerDocument||b[0]),i.createDocumentFragment||(i=c),a.length===1&&typeof j=="string"&&j.length<512&&i===c&&j.charAt(0)==="<"&&!bb.test(j)&&(f.support.checkClone||!bd.test(j))&&(f.support.html5Clone||!bc.test(j))&&(g=!0,h=f.fragments[j],h&&h!==1&&(e=h)),e||(e=i.createDocumentFragment(),f.clean(a,i,e,d)),g&&(f.fragments[j]=h?e:1);return{fragment:e,cacheable:g}},f.fragments={},f.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){f.fn[a]=function(c){var d=[],e=f(c),g=this.length===1&&this[0].parentNode;if(g&&g.nodeType===11&&g.childNodes.length===1&&e.length===1){e[b](this[0]);return this}for(var h=0,i=e.length;h<i;h++){var j=(h>0?this.clone(!0):this).get();f(e[h])[b](j),d=d.concat(j)}return this.pushStack(d,a,e.selector)}}),f.extend({clone:function(a,b,c){var d,e,g,h=f.support.html5Clone||f.isXMLDoc(a)||!bc.test("<"+a.nodeName+">")?a.cloneNode(!0):bo(a);if((!f.support.noCloneEvent||!f.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!f.isXMLDoc(a)){bk(a,h),d=bl(a),e=bl(h);for(g=0;d[g];++g)e[g]&&bk(d[g],e[g])}if(b){bj(a,h);if(c){d=bl(a),e=bl(h);for(g=0;d[g];++g)bj(d[g],e[g])}}d=e=null;return h},clean:function(a,b,d,e){var g,h,i,j=[];b=b||c,typeof b.createElement=="undefined"&&(b=b.ownerDocument||b[0]&&b[0].ownerDocument||c);for(var k=0,l;(l=a[k])!=null;k++){typeof l=="number"&&(l+="");if(!l)continue;if(typeof l=="string")if(!_.test(l))l=b.createTextNode(l);else{l=l.replace(Y,"<$1></$2>");var m=(Z.exec(l)||["",""])[1].toLowerCase(),n=bg[m]||bg._default,o=n[0],p=b.createElement("div"),q=bh.childNodes,r;b===c?bh.appendChild(p):U(b).appendChild(p),p.innerHTML=n[1]+l+n[2];while(o--)p=p.lastChild;if(!f.support.tbody){var s=$.test(l),t=m==="table"&&!s?p.firstChild&&p.firstChild.childNodes:n[1]==="<table>"&&!s?p.childNodes:[];for(i=t.length-1;i>=0;--i)f.nodeName(t[i],"tbody")&&!t[i].childNodes.length&&t[i].parentNode.removeChild(t[i])}!f.support.leadingWhitespace&&X.test(l)&&p.insertBefore(b.createTextNode(X.exec(l)[0]),p.firstChild),l=p.childNodes,p&&(p.parentNode.removeChild(p),q.length>0&&(r=q[q.length-1],r&&r.parentNode&&r.parentNode.removeChild(r)))}var u;if(!f.support.appendChecked)if(l[0]&&typeof (u=l.length)=="number")for(i=0;i<u;i++)bn(l[i]);else bn(l);l.nodeType?j.push(l):j=f.merge(j,l)}if(d){g=function(a){return!a.type||be.test(a.type)};for(k=0;j[k];k++){h=j[k];if(e&&f.nodeName(h,"script")&&(!h.type||be.test(h.type)))e.push(h.parentNode?h.parentNode.removeChild(h):h);else{if(h.nodeType===1){var v=f.grep(h.getElementsByTagName("script"),g);j.splice.apply(j,[k+1,0].concat(v))}d.appendChild(h)}}}return j},cleanData:function(a){var b,c,d=f.cache,e=f.event.special,g=f.support.deleteExpando;for(var h=0,i;(i=a[h])!=null;h++){if(i.nodeName&&f.noData[i.nodeName.toLowerCase()])continue;c=i[f.expando];if(c){b=d[c];if(b&&b.events){for(var j in b.events)e[j]?f.event.remove(i,j):f.removeEvent(i,j,b.handle);b.handle&&(b.handle.elem=null)}g?delete i[f.expando]:i.removeAttribute&&i.removeAttribute(f.expando),delete d[c]}}}});var bp=/alpha\([^)]*\)/i,bq=/opacity=([^)]*)/,br=/([A-Z]|^ms)/g,bs=/^[\-+]?(?:\d*\.)?\d+$/i,bt=/^-?(?:\d*\.)?\d+(?!px)[^\d\s]+$/i,bu=/^([\-+])=([\-+.\de]+)/,bv=/^margin/,bw={position:"absolute",visibility:"hidden",display:"block"},bx=["Top","Right","Bottom","Left"],by,bz,bA;f.fn.css=function(a,c){return f.access(this,function(a,c,d){return d!==b?f.style(a,c,d):f.css(a,c)},a,c,arguments.length>1)},f.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=by(a,"opacity");return c===""?"1":c}return a.style.opacity}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":f.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,d,e){if(!!a&&a.nodeType!==3&&a.nodeType!==8&&!!a.style){var g,h,i=f.camelCase(c),j=a.style,k=f.cssHooks[i];c=f.cssProps[i]||i;if(d===b){if(k&&"get"in k&&(g=k.get(a,!1,e))!==b)return g;return j[c]}h=typeof d,h==="string"&&(g=bu.exec(d))&&(d=+(g[1]+1)*+g[2]+parseFloat(f.css(a,c)),h="number");if(d==null||h==="number"&&isNaN(d))return;h==="number"&&!f.cssNumber[i]&&(d+="px");if(!k||!("set"in k)||(d=k.set(a,d))!==b)try{j[c]=d}catch(l){}}},css:function(a,c,d){var e,g;c=f.camelCase(c),g=f.cssHooks[c],c=f.cssProps[c]||c,c==="cssFloat"&&(c="float");if(g&&"get"in g&&(e=g.get(a,!0,d))!==b)return e;if(by)return by(a,c)},swap:function(a,b,c){var d={},e,f;for(f in b)d[f]=a.style[f],a.style[f]=b[f];e=c.call(a);for(f in b)a.style[f]=d[f];return e}}),f.curCSS=f.css,c.defaultView&&c.defaultView.getComputedStyle&&(bz=function(a,b){var c,d,e,g,h=a.style;b=b.replace(br,"-$1").toLowerCase(),(d=a.ownerDocument.defaultView)&&(e=d.getComputedStyle(a,null))&&(c=e.getPropertyValue(b),c===""&&!f.contains(a.ownerDocument.documentElement,a)&&(c=f.style(a,b))),!f.support.pixelMargin&&e&&bv.test(b)&&bt.test(c)&&(g=h.width,h.width=c,c=e.width,h.width=g);return c}),c.documentElement.currentStyle&&(bA=function(a,b){var c,d,e,f=a.currentStyle&&a.currentStyle[b],g=a.style;f==null&&g&&(e=g[b])&&(f=e),bt.test(f)&&(c=g.left,d=a.runtimeStyle&&a.runtimeStyle.left,d&&(a.runtimeStyle.left=a.currentStyle.left),g.left=b==="fontSize"?"1em":f,f=g.pixelLeft+"px",g.left=c,d&&(a.runtimeStyle.left=d));return f===""?"auto":f}),by=bz||bA,f.each(["height","width"],function(a,b){f.cssHooks[b]={get:function(a,c,d){if(c)return a.offsetWidth!==0?bB(a,b,d):f.swap(a,bw,function(){return bB(a,b,d)})},set:function(a,b){return bs.test(b)?b+"px":b}}}),f.support.opacity||(f.cssHooks.opacity={get:function(a,b){return bq.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=f.isNumeric(b)?"alpha(opacity="+b*100+")":"",g=d&&d.filter||c.filter||"";c.zoom=1;if(b>=1&&f.trim(g.replace(bp,""))===""){c.removeAttribute("filter");if(d&&!d.filter)return}c.filter=bp.test(g)?g.replace(bp,e):g+" "+e}}),f(function(){f.support.reliableMarginRight||(f.cssHooks.marginRight={get:function(a,b){return f.swap(a,{display:"inline-block"},function(){return b?by(a,"margin-right"):a.style.marginRight})}})}),f.expr&&f.expr.filters&&(f.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!f.support.reliableHiddenOffsets&&(a.style&&a.style.display||f.css(a,"display"))==="none"},f.expr.filters.visible=function(a){return!f.expr.filters.hidden(a)}),f.each({margin:"",padding:"",border:"Width"},function(a,b){f.cssHooks[a+b]={expand:function(c){var d,e=typeof c=="string"?c.split(" "):[c],f={};for(d=0;d<4;d++)f[a+bx[d]+b]=e[d]||e[d-2]||e[0];return f}}});var bC=/%20/g,bD=/\[\]$/,bE=/\r?\n/g,bF=/#.*$/,bG=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bH=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bI=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,bJ=/^(?:GET|HEAD)$/,bK=/^\/\//,bL=/\?/,bM=/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,bN=/^(?:select|textarea)/i,bO=/\s+/,bP=/([?&])_=[^&]*/,bQ=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,bR=f.fn.load,bS={},bT={},bU,bV,bW=["*/"]+["*"];try{bU=e.href}catch(bX){bU=c.createElement("a"),bU.href="",bU=bU.href}bV=bQ.exec(bU.toLowerCase())||[],f.fn.extend({load:function(a,c,d){if(typeof a!="string"&&bR)return bR.apply(this,arguments);if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var g=a.slice(e,a.length);a=a.slice(0,e)}var h="GET";c&&(f.isFunction(c)?(d=c,c=b):typeof c=="object"&&(c=f.param(c,f.ajaxSettings.traditional),h="POST"));var i=this;f.ajax({url:a,type:h,dataType:"html",data:c,complete:function(a,b,c){c=a.responseText,a.isResolved()&&(a.done(function(a){c=a}),i.html(g?f("<div>").append(c.replace(bM,"")).find(g):c)),d&&i.each(d,[c,b,a])}});return this},serialize:function(){return f.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?f.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||bN.test(this.nodeName)||bH.test(this.type))}).map(function(a,b){var c=f(this).val();return c==null?null:f.isArray(c)?f.map(c,function(a,c){return{name:b.name,value:a.replace(bE,"\r\n")}}):{name:b.name,value:c.replace(bE,"\r\n")}}).get()}}),f.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){f.fn[b]=function(a){return this.on(b,a)}}),f.each(["get","post"],function(a,c){f[c]=function(a,d,e,g){f.isFunction(d)&&(g=g||e,e=d,d=b);return f.ajax({type:c,url:a,data:d,success:e,dataType:g})}}),f.extend({getScript:function(a,c){return f.get(a,b,c,"script")},getJSON:function(a,b,c){return f.get(a,b,c,"json")},ajaxSetup:function(a,b){b?b$(a,f.ajaxSettings):(b=a,a=f.ajaxSettings),b$(a,b);return a},ajaxSettings:{url:bU,isLocal:bI.test(bV[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded; charset=UTF-8",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":bW},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":f.parseJSON,"text xml":f.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:bY(bS),ajaxTransport:bY(bT),ajax:function(a,c){function w(a,c,l,m){if(s!==2){s=2,q&&clearTimeout(q),p=b,n=m||"",v.readyState=a>0?4:0;var o,r,u,w=c,x=l?ca(d,v,l):b,y,z;if(a>=200&&a<300||a===304){if(d.ifModified){if(y=v.getResponseHeader("Last-Modified"))f.lastModified[k]=y;if(z=v.getResponseHeader("Etag"))f.etag[k]=z}if(a===304)w="notmodified",o=!0;else try{r=cb(d,x),w="success",o=!0}catch(A){w="parsererror",u=A}}else{u=w;if(!w||a)w="error",a<0&&(a=0)}v.status=a,v.statusText=""+(c||w),o?h.resolveWith(e,[r,w,v]):h.rejectWith(e,[v,w,u]),v.statusCode(j),j=b,t&&g.trigger("ajax"+(o?"Success":"Error"),[v,d,o?r:u]),i.fireWith(e,[v,w]),t&&(g.trigger("ajaxComplete",[v,d]),--f.active||f.event.trigger("ajaxStop"))}}typeof a=="object"&&(c=a,a=b),c=c||{};var d=f.ajaxSetup({},c),e=d.context||d,g=e!==d&&(e.nodeType||e instanceof f)?f(e):f.event,h=f.Deferred(),i=f.Callbacks("once memory"),j=d.statusCode||{},k,l={},m={},n,o,p,q,r,s=0,t,u,v={readyState:0,setRequestHeader:function(a,b){if(!s){var c=a.toLowerCase();a=m[c]=m[c]||a,l[a]=b}return this},getAllResponseHeaders:function(){return s===2?n:null},getResponseHeader:function(a){var c;if(s===2){if(!o){o={};while(c=bG.exec(n))o[c[1].toLowerCase()]=c[2]}c=o[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){s||(d.mimeType=a);return this},abort:function(a){a=a||"abort",p&&p.abort(a),w(0,a);return this}};h.promise(v),v.success=v.done,v.error=v.fail,v.complete=i.add,v.statusCode=function(a){if(a){var b;if(s<2)for(b in a)j[b]=[j[b],a[b]];else b=a[v.status],v.then(b,b)}return this},d.url=((a||d.url)+"").replace(bF,"").replace(bK,bV[1]+"//"),d.dataTypes=f.trim(d.dataType||"*").toLowerCase().split(bO),d.crossDomain==null&&(r=bQ.exec(d.url.toLowerCase()),d.crossDomain=!(!r||r[1]==bV[1]&&r[2]==bV[2]&&(r[3]||(r[1]==="http:"?80:443))==(bV[3]||(bV[1]==="http:"?80:443)))),d.data&&d.processData&&typeof d.data!="string"&&(d.data=f.param(d.data,d.traditional)),bZ(bS,d,c,v);if(s===2)return!1;t=d.global,d.type=d.type.toUpperCase(),d.hasContent=!bJ.test(d.type),t&&f.active++===0&&f.event.trigger("ajaxStart");if(!d.hasContent){d.data&&(d.url+=(bL.test(d.url)?"&":"?")+d.data,delete d.data),k=d.url;if(d.cache===!1){var x=f.now(),y=d.url.replace(bP,"$1_="+x);d.url=y+(y===d.url?(bL.test(d.url)?"&":"?")+"_="+x:"")}}(d.data&&d.hasContent&&d.contentType!==!1||c.contentType)&&v.setRequestHeader("Content-Type",d.contentType),d.ifModified&&(k=k||d.url,f.lastModified[k]&&v.setRequestHeader("If-Modified-Since",f.lastModified[k]),f.etag[k]&&v.setRequestHeader("If-None-Match",f.etag[k])),v.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+(d.dataTypes[0]!=="*"?", "+bW+"; q=0.01":""):d.accepts["*"]);for(u in d.headers)v.setRequestHeader(u,d.headers[u]);if(d.beforeSend&&(d.beforeSend.call(e,v,d)===!1||s===2)){v.abort();return!1}for(u in{success:1,error:1,complete:1})v[u](d[u]);p=bZ(bT,d,c,v);if(!p)w(-1,"No Transport");else{v.readyState=1,t&&g.trigger("ajaxSend",[v,d]),d.async&&d.timeout>0&&(q=setTimeout(function(){v.abort("timeout")},d.timeout));try{s=1,p.send(l,w)}catch(z){if(s<2)w(-1,z);else throw z}}return v},param:function(a,c){var d=[],e=function(a,b){b=f.isFunction(b)?b():b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=f.ajaxSettings.traditional);if(f.isArray(a)||a.jquery&&!f.isPlainObject(a))f.each(a,function(){e(this.name,this.value)});else for(var g in a)b_(g,a[g],c,e);return d.join("&").replace(bC,"+")}}),f.extend({active:0,lastModified:{},etag:{}});var cc=f.now(),cd=/(\=)\?(&|$)|\?\?/i;f.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return f.expando+"_"+cc++}}),f.ajaxPrefilter("json jsonp",function(b,c,d){var e=typeof b.data=="string"&&/^application\/x\-www\-form\-urlencoded/.test(b.contentType);if(b.dataTypes[0]==="jsonp"||b.jsonp!==!1&&(cd.test(b.url)||e&&cd.test(b.data))){var g,h=b.jsonpCallback=f.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2";b.jsonp!==!1&&(j=j.replace(cd,l),b.url===j&&(e&&(k=k.replace(cd,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},d.always(function(){a[h]=i,g&&f.isFunction(i)&&a[h](g[0])}),b.converters["script json"]=function(){g||f.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),f.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){f.globalEval(a);return a}}}),f.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),f.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.head||c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(c||!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var ce=a.ActiveXObject?function(){for(var a in cg)cg[a](0,1)}:!1,cf=0,cg;f.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&ch()||ci()}:ch,function(a){f.extend(f.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(f.ajaxSettings.xhr()),f.support.ajax&&f.ajaxTransport(function(c){if(!c.crossDomain||f.support.cors){var d;return{send:function(e,g){var h=c.xhr(),i,j;c.username?h.open(c.type,c.url,c.async,c.username,c.password):h.open(c.type,c.url,c.async);if(c.xhrFields)for(j in c.xhrFields)h[j]=c.xhrFields[j];c.mimeType&&h.overrideMimeType&&h.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(j in e)h.setRequestHeader(j,e[j])}catch(k){}h.send(c.hasContent&&c.data||null),d=function(a,e){var j,k,l,m,n;try{if(d&&(e||h.readyState===4)){d=b,i&&(h.onreadystatechange=f.noop,ce&&delete cg[i]);if(e)h.readyState!==4&&h.abort();else{j=h.status,l=h.getAllResponseHeaders(),m={},n=h.responseXML,n&&n.documentElement&&(m.xml=n);try{m.text=h.responseText}catch(a){}try{k=h.statusText}catch(o){k=""}!j&&c.isLocal&&!c.crossDomain?j=m.text?200:404:j===1223&&(j=204)}}}catch(p){e||g(-1,p)}m&&g(j,k,m,l)},!c.async||h.readyState===4?d():(i=++cf,ce&&(cg||(cg={},f(a).unload(ce)),cg[i]=d),h.onreadystatechange=d)},abort:function(){d&&d(0,1)}}}});var cj={},ck,cl,cm=/^(?:toggle|show|hide)$/,cn=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,co,cp=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]],cq;f.fn.extend({show:function(a,b,c){var d,e;if(a||a===0)return this.animate(ct("show",3),a,b,c);for(var g=0,h=this.length;g<h;g++)d=this[g],d.style&&(e=d.style.display,!f._data(d,"olddisplay")&&e==="none"&&(e=d.style.display=""),(e===""&&f.css(d,"display")==="none"||!f.contains(d.ownerDocument.documentElement,d))&&f._data(d,"olddisplay",cu(d.nodeName)));for(g=0;g<h;g++){d=this[g];if(d.style){e=d.style.display;if(e===""||e==="none")d.style.display=f._data(d,"olddisplay")||""}}return this},hide:function(a,b,c){if(a||a===0)return this.animate(ct("hide",3),a,b,c);var d,e,g=0,h=this.length;for(;g<h;g++)d=this[g],d.style&&(e=f.css(d,"display"),e!=="none"&&!f._data(d,"olddisplay")&&f._data(d,"olddisplay",e));for(g=0;g<h;g++)this[g].style&&(this[g].style.display="none");return this},_toggle:f.fn.toggle,toggle:function(a,b,c){var d=typeof a=="boolean";f.isFunction(a)&&f.isFunction(b)?this._toggle.apply(this,arguments):a==null||d?this.each(function(){var b=d?a:f(this).is(":hidden");f(this)[b?"show":"hide"]()}):this.animate(ct("toggle",3),a,b,c);return this},fadeTo:function(a,b,c,d){return this.filter(":hidden").css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){function g(){e.queue===!1&&f._mark(this);var b=f.extend({},e),c=this.nodeType===1,d=c&&f(this).is(":hidden"),g,h,i,j,k,l,m,n,o,p,q;b.animatedProperties={};for(i in a){g=f.camelCase(i),i!==g&&(a[g]=a[i],delete a[i]);if((k=f.cssHooks[g])&&"expand"in k){l=k.expand(a[g]),delete a[g];for(i in l)i in a||(a[i]=l[i])}}for(g in a){h=a[g],f.isArray(h)?(b.animatedProperties[g]=h[1],h=a[g]=h[0]):b.animatedProperties[g]=b.specialEasing&&b.specialEasing[g]||b.easing||"swing";if(h==="hide"&&d||h==="show"&&!d)return b.complete.call(this);c&&(g==="height"||g==="width")&&(b.overflow=[this.style.overflow,this.style.overflowX,this.style.overflowY],f.css(this,"display")==="inline"&&f.css(this,"float")==="none"&&(!f.support.inlineBlockNeedsLayout||cu(this.nodeName)==="inline"?this.style.display="inline-block":this.style.zoom=1))}b.overflow!=null&&(this.style.overflow="hidden");for(i in a)j=new f.fx(this,b,i),h=a[i],cm.test(h)?(q=f._data(this,"toggle"+i)||(h==="toggle"?d?"show":"hide":0),q?(f._data(this,"toggle"+i,q==="show"?"hide":"show"),j[q]()):j[h]()):(m=cn.exec(h),n=j.cur(),m?(o=parseFloat(m[2]),p=m[3]||(f.cssNumber[i]?"":"px"),p!=="px"&&(f.style(this,i,(o||1)+p),n=(o||1)/j.cur()*n,f.style(this,i,n+p)),m[1]&&(o=(m[1]==="-="?-1:1)*o+n),j.custom(n,o,p)):j.custom(n,h,""));return!0}var e=f.speed(b,c,d);if(f.isEmptyObject(a))return this.each(e.complete,[!1]);a=f.extend({},a);return e.queue===!1?this.each(g):this.queue(e.queue,g)},stop:function(a,c,d){typeof a!="string"&&(d=c,c=a,a=b),c&&a!==!1&&this.queue(a||"fx",[]);return this.each(function(){function h(a,b,c){var e=b[c];f.removeData(a,c,!0),e.stop(d)}var b,c=!1,e=f.timers,g=f._data(this);d||f._unmark(!0,this);if(a==null)for(b in g)g[b]&&g[b].stop&&b.indexOf(".run")===b.length-4&&h(this,g,b);else g[b=a+".run"]&&g[b].stop&&h(this,g,b);for(b=e.length;b--;)e[b].elem===this&&(a==null||e[b].queue===a)&&(d?e[b](!0):e[b].saveState(),c=!0,e.splice(b,1));(!d||!c)&&f.dequeue(this,a)})}}),f.each({slideDown:ct("show",1),slideUp:ct("hide",1),slideToggle:ct("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){f.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),f.extend({speed:function(a,b,c){var d=a&&typeof a=="object"?f.extend({},a):{complete:c||!c&&b||f.isFunction(a)&&a,duration:a,easing:c&&b||b&&!f.isFunction(b)&&b};d.duration=f.fx.off?0:typeof d.duration=="number"?d.duration:d.duration in f.fx.speeds?f.fx.speeds[d.duration]:f.fx.speeds._default;if(d.queue==null||d.queue===!0)d.queue="fx";d.old=d.complete,d.complete=function(a){f.isFunction(d.old)&&d.old.call(this),d.queue?f.dequeue(this,d.queue):a!==!1&&f._unmark(this)};return d},easing:{linear:function(a){return a},swing:function(a){return-Math.cos(a*Math.PI)/2+.5}},timers:[],fx:function(a,b,c){this.options=b,this.elem=a,this.prop=c,b.orig=b.orig||{}}}),f.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this),(f.fx.step[this.prop]||f.fx.step._default)(this)},cur:function(){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];var a,b=f.css(this.elem,this.prop);return isNaN(a=parseFloat(b))?!b||b==="auto"?0:b:a},custom:function(a,c,d){function h(a){return e.step(a)}var e=this,g=f.fx;this.startTime=cq||cr(),this.end=c,this.now=this.start=a,this.pos=this.state=0,this.unit=d||this.unit||(f.cssNumber[this.prop]?"":"px"),h.queue=this.options.queue,h.elem=this.elem,h.saveState=function(){f._data(e.elem,"fxshow"+e.prop)===b&&(e.options.hide?f._data(e.elem,"fxshow"+e.prop,e.start):e.options.show&&f._data(e.elem,"fxshow"+e.prop,e.end))},h()&&f.timers.push(h)&&!co&&(co=setInterval(g.tick,g.interval))},show:function(){var a=f._data(this.elem,"fxshow"+this.prop);this.options.orig[this.prop]=a||f.style(this.elem,this.prop),this.options.show=!0,a!==b?this.custom(this.cur(),a):this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur()),f(this.elem).show()},hide:function(){this.options.orig[this.prop]=f._data(this.elem,"fxshow"+this.prop)||f.style(this.elem,this.prop),this.options.hide=!0,this.custom(this.cur(),0)},step:function(a){var b,c,d,e=cq||cr(),g=!0,h=this.elem,i=this.options;if(a||e>=i.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),i.animatedProperties[this.prop]=!0;for(b in i.animatedProperties)i.animatedProperties[b]!==!0&&(g=!1);if(g){i.overflow!=null&&!f.support.shrinkWrapBlocks&&f.each(["","X","Y"],function(a,b){h.style["overflow"+b]=i.overflow[a]}),i.hide&&f(h).hide();if(i.hide||i.show)for(b in i.animatedProperties)f.style(h,b,i.orig[b]),f.removeData(h,"fxshow"+b,!0),f.removeData(h,"toggle"+b,!0);d=i.complete,d&&(i.complete=!1,d.call(h))}return!1}i.duration==Infinity?this.now=e:(c=e-this.startTime,this.state=c/i.duration,this.pos=f.easing[i.animatedProperties[this.prop]](this.state,c,0,1,i.duration),this.now=this.start+(this.end-this.start)*this.pos),this.update();return!0}},f.extend(f.fx,{tick:function(){var a,b=f.timers,c=0;for(;c<b.length;c++)a=b[c],!a()&&b[c]===a&&b.splice(c--,1);b.length||f.fx.stop()},interval:13,stop:function(){clearInterval(co),co=null},speeds:{slow:600,fast:200,_default:400},step:{opacity:function(a){f.style(a.elem,"opacity",a.now)},_default:function(a){a.elem.style&&a.elem.style[a.prop]!=null?a.elem.style[a.prop]=a.now+a.unit:a.elem[a.prop]=a.now}}}),f.each(cp.concat.apply([],cp),function(a,b){b.indexOf("margin")&&(f.fx.step[b]=function(a){f.style(a.elem,b,Math.max(0,a.now)+a.unit)})}),f.expr&&f.expr.filters&&(f.expr.filters.animated=function(a){return f.grep(f.timers,function(b){return a===b.elem}).length});var cv,cw=/^t(?:able|d|h)$/i,cx=/^(?:body|html)$/i;"getBoundingClientRect"in c.documentElement?cv=function(a,b,c,d){try{d=a.getBoundingClientRect()}catch(e){}if(!d||!f.contains(c,a))return d?{top:d.top,left:d.left}:{top:0,left:0};var g=b.body,h=cy(b),i=c.clientTop||g.clientTop||0,j=c.clientLeft||g.clientLeft||0,k=h.pageYOffset||f.support.boxModel&&c.scrollTop||g.scrollTop,l=h.pageXOffset||f.support.boxModel&&c.scrollLeft||g.scrollLeft,m=d.top+k-i,n=d.left+l-j;return{top:m,left:n}}:cv=function(a,b,c){var d,e=a.offsetParent,g=a,h=b.body,i=b.defaultView,j=i?i.getComputedStyle(a,null):a.currentStyle,k=a.offsetTop,l=a.offsetLeft;while((a=a.parentNode)&&a!==h&&a!==c){if(f.support.fixedPosition&&j.position==="fixed")break;d=i?i.getComputedStyle(a,null):a.currentStyle,k-=a.scrollTop,l-=a.scrollLeft,a===e&&(k+=a.offsetTop,l+=a.offsetLeft,f.support.doesNotAddBorder&&(!f.support.doesAddBorderForTableAndCells||!cw.test(a.nodeName))&&(k+=parseFloat(d.borderTopWidth)||0,l+=parseFloat(d.borderLeftWidth)||0),g=e,e=a.offsetParent),f.support.subtractsBorderForOverflowNotVisible&&d.overflow!=="visible"&&(k+=parseFloat(d.borderTopWidth)||0,l+=parseFloat(d.borderLeftWidth)||0),j=d}if(j.position==="relative"||j.position==="static")k+=h.offsetTop,l+=h.offsetLeft;f.support.fixedPosition&&j.position==="fixed"&&(k+=Math.max(c.scrollTop,h.scrollTop),l+=Math.max(c.scrollLeft,h.scrollLeft));return{top:k,left:l}},f.fn.offset=function(a){if(arguments.length)return a===b?this:this.each(function(b){f.offset.setOffset(this,a,b)});var c=this[0],d=c&&c.ownerDocument;if(!d)return null;if(c===d.body)return f.offset.bodyOffset(c);return cv(c,d,d.documentElement)},f.offset={bodyOffset:function(a){var b=a.offsetTop,c=a.offsetLeft;f.support.doesNotIncludeMarginInBodyOffset&&(b+=parseFloat(f.css(a,"marginTop"))||0,c+=parseFloat(f.css(a,"marginLeft"))||0);return{top:b,left:c}},setOffset:function(a,b,c){var d=f.css(a,"position");d==="static"&&(a.style.position="relative");var e=f(a),g=e.offset(),h=f.css(a,"top"),i=f.css(a,"left"),j=(d==="absolute"||d==="fixed")&&f.inArray("auto",[h,i])>-1,k={},l={},m,n;j?(l=e.position(),m=l.top,n=l.left):(m=parseFloat(h)||0,n=parseFloat(i)||0),f.isFunction(b)&&(b=b.call(a,c,g)),b.top!=null&&(k.top=b.top-g.top+m),b.left!=null&&(k.left=b.left-g.left+n),"using"in b?b.using.call(a,k):e.css(k)}},f.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),d=cx.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(f.css(a,"marginTop"))||0,c.left-=parseFloat(f.css(a,"marginLeft"))||0,d.top+=parseFloat(f.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(f.css(b[0],"borderLeftWidth"))||0;return{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&!cx.test(a.nodeName)&&f.css(a,"position")==="static")a=a.offsetParent;return a})}}),f.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(a,c){var d=/Y/.test(c);f.fn[a]=function(e){return f.access(this,function(a,e,g){var h=cy(a);if(g===b)return h?c in h?h[c]:f.support.boxModel&&h.document.documentElement[e]||h.document.body[e]:a[e];h?h.scrollTo(d?f(h).scrollLeft():g,d?g:f(h).scrollTop()):a[e]=g},a,e,arguments.length,null)}}),f.each({Height:"height",Width:"width"},function(a,c){var d="client"+a,e="scroll"+a,g="offset"+a;f.fn["inner"+a]=function(){var a=this[0];return a?a.style?parseFloat(f.css(a,c,"padding")):this[c]():null},f.fn["outer"+a]=function(a){var b=this[0];return b?b.style?parseFloat(f.css(b,c,a?"margin":"border")):this[c]():null},f.fn[c]=function(a){return f.access(this,function(a,c,h){var i,j,k,l;if(f.isWindow(a)){i=a.document,j=i.documentElement[d];return f.support.boxModel&&j||i.body&&i.body[d]||j}if(a.nodeType===9){i=a.documentElement;if(i[d]>=i[e])return i[d];return Math.max(a.body[e],i[e],a.body[g],i[g])}if(h===b){k=f.css(a,c),l=parseFloat(k);return f.isNumeric(l)?l:k}f(a).css(c,h)},c,a,arguments.length,null)}}),a.jQuery=a.$=f,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return f})})(window);
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/resources/web/lib/underscore-min.js b/src/ext/floodlight/src/main/resources/web/lib/underscore-min.js
new file mode 100644
index 0000000..5a0cb3b
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/lib/underscore-min.js
@@ -0,0 +1,32 @@
+// Underscore.js 1.3.3
+// (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.
+// Underscore is freely distributable under the MIT license.
+// Portions of Underscore are inspired or borrowed from Prototype,
+// Oliver Steele's Functional, and John Resig's Micro-Templating.
+// For all details and documentation:
+// http://documentcloud.github.com/underscore
+(function(){function r(a,c,d){if(a===c)return 0!==a||1/a==1/c;if(null==a||null==c)return a===c;a._chain&&(a=a._wrapped);c._chain&&(c=c._wrapped);if(a.isEqual&&b.isFunction(a.isEqual))return a.isEqual(c);if(c.isEqual&&b.isFunction(c.isEqual))return c.isEqual(a);var e=l.call(a);if(e!=l.call(c))return!1;switch(e){case "[object String]":return a==""+c;case "[object Number]":return a!=+a?c!=+c:0==a?1/a==1/c:a==+c;case "[object Date]":case "[object Boolean]":return+a==+c;case "[object RegExp]":return a.source==
+c.source&&a.global==c.global&&a.multiline==c.multiline&&a.ignoreCase==c.ignoreCase}if("object"!=typeof a||"object"!=typeof c)return!1;for(var f=d.length;f--;)if(d[f]==a)return!0;d.push(a);var f=0,g=!0;if("[object Array]"==e){if(f=a.length,g=f==c.length)for(;f--&&(g=f in a==f in c&&r(a[f],c[f],d)););}else{if("constructor"in a!="constructor"in c||a.constructor!=c.constructor)return!1;for(var h in a)if(b.has(a,h)&&(f++,!(g=b.has(c,h)&&r(a[h],c[h],d))))break;if(g){for(h in c)if(b.has(c,h)&&!f--)break;
+g=!f}}d.pop();return g}var s=this,I=s._,o={},k=Array.prototype,p=Object.prototype,i=k.slice,J=k.unshift,l=p.toString,K=p.hasOwnProperty,y=k.forEach,z=k.map,A=k.reduce,B=k.reduceRight,C=k.filter,D=k.every,E=k.some,q=k.indexOf,F=k.lastIndexOf,p=Array.isArray,L=Object.keys,t=Function.prototype.bind,b=function(a){return new m(a)};"undefined"!==typeof exports?("undefined"!==typeof module&&module.exports&&(exports=module.exports=b),exports._=b):s._=b;b.VERSION="1.3.3";var j=b.each=b.forEach=function(a,
+c,d){if(a!=null)if(y&&a.forEach===y)a.forEach(c,d);else if(a.length===+a.length)for(var e=0,f=a.length;e<f;e++){if(e in a&&c.call(d,a[e],e,a)===o)break}else for(e in a)if(b.has(a,e)&&c.call(d,a[e],e,a)===o)break};b.map=b.collect=function(a,c,b){var e=[];if(a==null)return e;if(z&&a.map===z)return a.map(c,b);j(a,function(a,g,h){e[e.length]=c.call(b,a,g,h)});if(a.length===+a.length)e.length=a.length;return e};b.reduce=b.foldl=b.inject=function(a,c,d,e){var f=arguments.length>2;a==null&&(a=[]);if(A&&
+a.reduce===A){e&&(c=b.bind(c,e));return f?a.reduce(c,d):a.reduce(c)}j(a,function(a,b,i){if(f)d=c.call(e,d,a,b,i);else{d=a;f=true}});if(!f)throw new TypeError("Reduce of empty array with no initial value");return d};b.reduceRight=b.foldr=function(a,c,d,e){var f=arguments.length>2;a==null&&(a=[]);if(B&&a.reduceRight===B){e&&(c=b.bind(c,e));return f?a.reduceRight(c,d):a.reduceRight(c)}var g=b.toArray(a).reverse();e&&!f&&(c=b.bind(c,e));return f?b.reduce(g,c,d,e):b.reduce(g,c)};b.find=b.detect=function(a,
+c,b){var e;G(a,function(a,g,h){if(c.call(b,a,g,h)){e=a;return true}});return e};b.filter=b.select=function(a,c,b){var e=[];if(a==null)return e;if(C&&a.filter===C)return a.filter(c,b);j(a,function(a,g,h){c.call(b,a,g,h)&&(e[e.length]=a)});return e};b.reject=function(a,c,b){var e=[];if(a==null)return e;j(a,function(a,g,h){c.call(b,a,g,h)||(e[e.length]=a)});return e};b.every=b.all=function(a,c,b){var e=true;if(a==null)return e;if(D&&a.every===D)return a.every(c,b);j(a,function(a,g,h){if(!(e=e&&c.call(b,
+a,g,h)))return o});return!!e};var G=b.some=b.any=function(a,c,d){c||(c=b.identity);var e=false;if(a==null)return e;if(E&&a.some===E)return a.some(c,d);j(a,function(a,b,h){if(e||(e=c.call(d,a,b,h)))return o});return!!e};b.include=b.contains=function(a,c){var b=false;if(a==null)return b;if(q&&a.indexOf===q)return a.indexOf(c)!=-1;return b=G(a,function(a){return a===c})};b.invoke=function(a,c){var d=i.call(arguments,2);return b.map(a,function(a){return(b.isFunction(c)?c||a:a[c]).apply(a,d)})};b.pluck=
+function(a,c){return b.map(a,function(a){return a[c]})};b.max=function(a,c,d){if(!c&&b.isArray(a)&&a[0]===+a[0])return Math.max.apply(Math,a);if(!c&&b.isEmpty(a))return-Infinity;var e={computed:-Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b>=e.computed&&(e={value:a,computed:b})});return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a)&&a[0]===+a[0])return Math.min.apply(Math,a);if(!c&&b.isEmpty(a))return Infinity;var e={computed:Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b<e.computed&&
+(e={value:a,computed:b})});return e.value};b.shuffle=function(a){var b=[],d;j(a,function(a,f){d=Math.floor(Math.random()*(f+1));b[f]=b[d];b[d]=a});return b};b.sortBy=function(a,c,d){var e=b.isFunction(c)?c:function(a){return a[c]};return b.pluck(b.map(a,function(a,b,c){return{value:a,criteria:e.call(d,a,b,c)}}).sort(function(a,b){var c=a.criteria,d=b.criteria;return c===void 0?1:d===void 0?-1:c<d?-1:c>d?1:0}),"value")};b.groupBy=function(a,c){var d={},e=b.isFunction(c)?c:function(a){return a[c]};
+j(a,function(a,b){var c=e(a,b);(d[c]||(d[c]=[])).push(a)});return d};b.sortedIndex=function(a,c,d){d||(d=b.identity);for(var e=0,f=a.length;e<f;){var g=e+f>>1;d(a[g])<d(c)?e=g+1:f=g}return e};b.toArray=function(a){return!a?[]:b.isArray(a)||b.isArguments(a)?i.call(a):a.toArray&&b.isFunction(a.toArray)?a.toArray():b.values(a)};b.size=function(a){return b.isArray(a)?a.length:b.keys(a).length};b.first=b.head=b.take=function(a,b,d){return b!=null&&!d?i.call(a,0,b):a[0]};b.initial=function(a,b,d){return i.call(a,
+0,a.length-(b==null||d?1:b))};b.last=function(a,b,d){return b!=null&&!d?i.call(a,Math.max(a.length-b,0)):a[a.length-1]};b.rest=b.tail=function(a,b,d){return i.call(a,b==null||d?1:b)};b.compact=function(a){return b.filter(a,function(a){return!!a})};b.flatten=function(a,c){return b.reduce(a,function(a,e){if(b.isArray(e))return a.concat(c?e:b.flatten(e));a[a.length]=e;return a},[])};b.without=function(a){return b.difference(a,i.call(arguments,1))};b.uniq=b.unique=function(a,c,d){var d=d?b.map(a,d):a,
+e=[];a.length<3&&(c=true);b.reduce(d,function(d,g,h){if(c?b.last(d)!==g||!d.length:!b.include(d,g)){d.push(g);e.push(a[h])}return d},[]);return e};b.union=function(){return b.uniq(b.flatten(arguments,true))};b.intersection=b.intersect=function(a){var c=i.call(arguments,1);return b.filter(b.uniq(a),function(a){return b.every(c,function(c){return b.indexOf(c,a)>=0})})};b.difference=function(a){var c=b.flatten(i.call(arguments,1),true);return b.filter(a,function(a){return!b.include(c,a)})};b.zip=function(){for(var a=
+i.call(arguments),c=b.max(b.pluck(a,"length")),d=Array(c),e=0;e<c;e++)d[e]=b.pluck(a,""+e);return d};b.indexOf=function(a,c,d){if(a==null)return-1;var e;if(d){d=b.sortedIndex(a,c);return a[d]===c?d:-1}if(q&&a.indexOf===q)return a.indexOf(c);d=0;for(e=a.length;d<e;d++)if(d in a&&a[d]===c)return d;return-1};b.lastIndexOf=function(a,b){if(a==null)return-1;if(F&&a.lastIndexOf===F)return a.lastIndexOf(b);for(var d=a.length;d--;)if(d in a&&a[d]===b)return d;return-1};b.range=function(a,b,d){if(arguments.length<=
+1){b=a||0;a=0}for(var d=arguments[2]||1,e=Math.max(Math.ceil((b-a)/d),0),f=0,g=Array(e);f<e;){g[f++]=a;a=a+d}return g};var H=function(){};b.bind=function(a,c){var d,e;if(a.bind===t&&t)return t.apply(a,i.call(arguments,1));if(!b.isFunction(a))throw new TypeError;e=i.call(arguments,2);return d=function(){if(!(this instanceof d))return a.apply(c,e.concat(i.call(arguments)));H.prototype=a.prototype;var b=new H,g=a.apply(b,e.concat(i.call(arguments)));return Object(g)===g?g:b}};b.bindAll=function(a){var c=
+i.call(arguments,1);c.length==0&&(c=b.functions(a));j(c,function(c){a[c]=b.bind(a[c],a)});return a};b.memoize=function(a,c){var d={};c||(c=b.identity);return function(){var e=c.apply(this,arguments);return b.has(d,e)?d[e]:d[e]=a.apply(this,arguments)}};b.delay=function(a,b){var d=i.call(arguments,2);return setTimeout(function(){return a.apply(null,d)},b)};b.defer=function(a){return b.delay.apply(b,[a,1].concat(i.call(arguments,1)))};b.throttle=function(a,c){var d,e,f,g,h,i,j=b.debounce(function(){h=
+g=false},c);return function(){d=this;e=arguments;f||(f=setTimeout(function(){f=null;h&&a.apply(d,e);j()},c));g?h=true:i=a.apply(d,e);j();g=true;return i}};b.debounce=function(a,b,d){var e;return function(){var f=this,g=arguments;d&&!e&&a.apply(f,g);clearTimeout(e);e=setTimeout(function(){e=null;d||a.apply(f,g)},b)}};b.once=function(a){var b=false,d;return function(){if(b)return d;b=true;return d=a.apply(this,arguments)}};b.wrap=function(a,b){return function(){var d=[a].concat(i.call(arguments,0));
+return b.apply(this,d)}};b.compose=function(){var a=arguments;return function(){for(var b=arguments,d=a.length-1;d>=0;d--)b=[a[d].apply(this,b)];return b[0]}};b.after=function(a,b){return a<=0?b():function(){if(--a<1)return b.apply(this,arguments)}};b.keys=L||function(a){if(a!==Object(a))throw new TypeError("Invalid object");var c=[],d;for(d in a)b.has(a,d)&&(c[c.length]=d);return c};b.values=function(a){return b.map(a,b.identity)};b.functions=b.methods=function(a){var c=[],d;for(d in a)b.isFunction(a[d])&&
+c.push(d);return c.sort()};b.extend=function(a){j(i.call(arguments,1),function(b){for(var d in b)a[d]=b[d]});return a};b.pick=function(a){var c={};j(b.flatten(i.call(arguments,1)),function(b){b in a&&(c[b]=a[b])});return c};b.defaults=function(a){j(i.call(arguments,1),function(b){for(var d in b)a[d]==null&&(a[d]=b[d])});return a};b.clone=function(a){return!b.isObject(a)?a:b.isArray(a)?a.slice():b.extend({},a)};b.tap=function(a,b){b(a);return a};b.isEqual=function(a,b){return r(a,b,[])};b.isEmpty=
+function(a){if(a==null)return true;if(b.isArray(a)||b.isString(a))return a.length===0;for(var c in a)if(b.has(a,c))return false;return true};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=p||function(a){return l.call(a)=="[object Array]"};b.isObject=function(a){return a===Object(a)};b.isArguments=function(a){return l.call(a)=="[object Arguments]"};b.isArguments(arguments)||(b.isArguments=function(a){return!(!a||!b.has(a,"callee"))});b.isFunction=function(a){return l.call(a)=="[object Function]"};
+b.isString=function(a){return l.call(a)=="[object String]"};b.isNumber=function(a){return l.call(a)=="[object Number]"};b.isFinite=function(a){return b.isNumber(a)&&isFinite(a)};b.isNaN=function(a){return a!==a};b.isBoolean=function(a){return a===true||a===false||l.call(a)=="[object Boolean]"};b.isDate=function(a){return l.call(a)=="[object Date]"};b.isRegExp=function(a){return l.call(a)=="[object RegExp]"};b.isNull=function(a){return a===null};b.isUndefined=function(a){return a===void 0};b.has=function(a,
+b){return K.call(a,b)};b.noConflict=function(){s._=I;return this};b.identity=function(a){return a};b.times=function(a,b,d){for(var e=0;e<a;e++)b.call(d,e)};b.escape=function(a){return(""+a).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'").replace(/\//g,"/")};b.result=function(a,c){if(a==null)return null;var d=a[c];return b.isFunction(d)?d.call(a):d};b.mixin=function(a){j(b.functions(a),function(c){M(c,b[c]=a[c])})};var N=0;b.uniqueId=
+function(a){var b=N++;return a?a+b:b};b.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var u=/.^/,n={"\\":"\\","'":"'",r:"\r",n:"\n",t:"\t",u2028:"\u2028",u2029:"\u2029"},v;for(v in n)n[n[v]]=v;var O=/\\|'|\r|\n|\t|\u2028|\u2029/g,P=/\\(\\|'|r|n|t|u2028|u2029)/g,w=function(a){return a.replace(P,function(a,b){return n[b]})};b.template=function(a,c,d){d=b.defaults(d||{},b.templateSettings);a="__p+='"+a.replace(O,function(a){return"\\"+n[a]}).replace(d.escape||
+u,function(a,b){return"'+\n_.escape("+w(b)+")+\n'"}).replace(d.interpolate||u,function(a,b){return"'+\n("+w(b)+")+\n'"}).replace(d.evaluate||u,function(a,b){return"';\n"+w(b)+"\n;__p+='"})+"';\n";d.variable||(a="with(obj||{}){\n"+a+"}\n");var a="var __p='';var print=function(){__p+=Array.prototype.join.call(arguments, '')};\n"+a+"return __p;\n",e=new Function(d.variable||"obj","_",a);if(c)return e(c,b);c=function(a){return e.call(this,a,b)};c.source="function("+(d.variable||"obj")+"){\n"+a+"}";return c};
+b.chain=function(a){return b(a).chain()};var m=function(a){this._wrapped=a};b.prototype=m.prototype;var x=function(a,c){return c?b(a).chain():a},M=function(a,c){m.prototype[a]=function(){var a=i.call(arguments);J.call(a,this._wrapped);return x(c.apply(b,a),this._chain)}};b.mixin(b);j("pop,push,reverse,shift,sort,splice,unshift".split(","),function(a){var b=k[a];m.prototype[a]=function(){var d=this._wrapped;b.apply(d,arguments);var e=d.length;(a=="shift"||a=="splice")&&e===0&&delete d[0];return x(d,
+this._chain)}});j(["concat","join","slice"],function(a){var b=k[a];m.prototype[a]=function(){return x(b.apply(this._wrapped,arguments),this._chain)}});m.prototype.chain=function(){this._chain=true;return this};m.prototype.value=function(){return this._wrapped}}).call(this);
diff --git a/src/ext/floodlight/src/main/resources/web/tpl/flow-list-item.html b/src/ext/floodlight/src/main/resources/web/tpl/flow-list-item.html
new file mode 100644
index 0000000..7c099c3
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/tpl/flow-list-item.html
@@ -0,0 +1 @@
+ <td><%= cookie %></td><td><%= priority %></td><td><%= matchHTML %></td><td><%= actionText %></td><td><%= packetCount %></td><td><%= byteCount %></td><td><%= durationSeconds %> s</td><td><%= idleTimeout %> s</td>
diff --git a/src/ext/floodlight/src/main/resources/web/tpl/flow-list.html b/src/ext/floodlight/src/main/resources/web/tpl/flow-list.html
new file mode 100644
index 0000000..21a30cf
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/tpl/flow-list.html
@@ -0,0 +1,19 @@
+<div class="page-header">
+ <h1>Flows (<%= nflows %>)</h1>
+</div>
+<table class="table table-striped flow-table">
+ <thead><tr><th>Cookie</th><th>Priority</th><th>Match</th><th>Action</th><th>Packets</th><th>Bytes</th><th>Age</th><th>Timeout</th></tr></thead>
+ <tbody>
+ <!-- flows will be inserted here by FlowListView:render -->
+ </tbody>
+</table>
+<!-- TODO implement pagination -->
+<!--
+<div class="pagination pagination-right"><ul>
+ <li><a href="">←</a></li>
+ <li class="active"><a href="">1</a></li>
+ <li><a href="">2</a></li>
+ <li><a href="">→</a>
+</ul></div>
+ -->
+
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/resources/web/tpl/header.html b/src/ext/floodlight/src/main/resources/web/tpl/header.html
new file mode 100644
index 0000000..78045ef
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/tpl/header.html
@@ -0,0 +1,26 @@
+<div class="navbar navbar-fixed-top">
+ <div class="navbar-inner">
+ <div class="container">
+ <a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ </a>
+ <img src="img/floodlight.png" style="float:left;">
+
+ <div class="nav-collapse">
+ <ul class="nav">
+ <li><a href="/">Dashboard</a></li>
+ <li><a href="/topology">Topology</a></li>
+ <li><a href="/switches">Switches</a></li>
+ <li><a href="/hosts">Hosts</a></li>
+ <!-- <li><a href="/vlans">VLANs</a></li> -->
+ </ul>
+ <form id="searchForm" class="navbar-search pull-right dropdown">
+ <input id="searchText" type="text" class="search-query dropdown-toggle"
+ placeholder="Search (try an IP or MAC address)">
+ </form>
+ </div> <!--/.nav-collapse -->
+ </div>
+ </div>
+</div>
diff --git a/src/ext/floodlight/src/main/resources/web/tpl/home.html b/src/ext/floodlight/src/main/resources/web/tpl/home.html
new file mode 100644
index 0000000..6abf494
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/tpl/home.html
@@ -0,0 +1,7 @@
+<div id="controller-status"></div>
+
+<div id="switch-list"></div>
+
+<div id="host-list"></div>
+
+<!-- <div id="vlan-list"></div> -->
diff --git a/src/ext/floodlight/src/main/resources/web/tpl/host-list-item.html b/src/ext/floodlight/src/main/resources/web/tpl/host-list-item.html
new file mode 100644
index 0000000..169477e
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/tpl/host-list-item.html
@@ -0,0 +1 @@
+ <td><a href="/host/<%= mac %>"><%= mac %></a></td><!-- <td><%= vlan %></td> --><td><%= ipv4 %></td><td><%= swport %></td><td><%= lastSeen %></td>
diff --git a/src/ext/floodlight/src/main/resources/web/tpl/host-list.html b/src/ext/floodlight/src/main/resources/web/tpl/host-list.html
new file mode 100644
index 0000000..bc1f364
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/tpl/host-list.html
@@ -0,0 +1,23 @@
+<div class="row">
+<div class="span12">
+<div class="page-header">
+ <h1>Hosts (<%= nhosts %>)</h1>
+</div>
+<table class="table table-striped host-table">
+ <thead><tr><th>MAC Address</th><!-- <th>VLAN</th> --><th>IP Address</th><th>Switch Port</th><th>Last Seen</th>
+ </tr></thead>
+ <tbody>
+ <!-- hosts will be inserted here by HostListView.render -->
+ </tbody>
+</table>
+<!-- TODO implement pagination -->
+<!--
+<div class="pagination pagination-right"><ul>
+ <li><a href="">←</a></li>
+ <li class="active"><a href="">1</a></li>
+ <li><a href="">2</a></li>
+ <li><a href="">→</a>
+</ul></div>
+-->
+</div>
+</div>
diff --git a/src/ext/floodlight/src/main/resources/web/tpl/host.html b/src/ext/floodlight/src/main/resources/web/tpl/host.html
new file mode 100644
index 0000000..985940f
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/tpl/host.html
@@ -0,0 +1,11 @@
+<div class="row">
+<div class="span12">
+<div class="page-header">
+ <h1>Host <%= id %></h1>
+</div>
+<p>
+<!-- VLAN: <a href="/vlan/<%= vlan %>"><%= vlan %></a><br> -->
+IP addresses: <%= ipv4 %><br>
+Attachment points: <%= swport %><br>
+Last seen: <%= lastSeen %>
+</p>
diff --git a/src/ext/floodlight/src/main/resources/web/tpl/port-list-item.html b/src/ext/floodlight/src/main/resources/web/tpl/port-list-item.html
new file mode 100644
index 0000000..bfc1978
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/tpl/port-list-item.html
@@ -0,0 +1 @@
+ <td><a id="<%= portNumber %>"><%= name %></a></td><td><%= status %></td><td><%= transmitBytes %></td><td><%= receiveBytes %></td><td><%= transmitPackets %></td><td><%= receivePackets %></td><td><%= dropped %></td><td><%= errors %></td>
diff --git a/src/ext/floodlight/src/main/resources/web/tpl/port-list.html b/src/ext/floodlight/src/main/resources/web/tpl/port-list.html
new file mode 100644
index 0000000..9ceb6c3
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/tpl/port-list.html
@@ -0,0 +1,19 @@
+<div class="page-header">
+ <h1>Ports (<%= nports %>)</h1>
+</div>
+<table class="table table-striped port-table">
+ <thead><tr><th>#</th><th>Link Status</th><th>TX Bytes</th><th>RX Bytes</th><th>TX Pkts</th><th>RX Pkts</th><th>Dropped</th><th>Errors</th></tr></thead>
+ <tbody>
+ <!-- ports will be inserted here by PortListView:render -->
+ </tbody>
+</table>
+<!-- TODO implement pagination -->
+<!--
+<div class="pagination pagination-right"><ul>
+ <li><a href="">←</a></li>
+ <li class="active"><a href="">1</a></li>
+ <li><a href="">2</a></li>
+ <li><a href="">→</a>
+</ul></div>
+-->
+
\ No newline at end of file
diff --git a/src/ext/floodlight/src/main/resources/web/tpl/status.html b/src/ext/floodlight/src/main/resources/web/tpl/status.html
new file mode 100644
index 0000000..5a76869
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/tpl/status.html
@@ -0,0 +1,13 @@
+<div class="row">
+<div class="span12">
+<div class="page-header">
+ <h1>Controller Status</h1>
+</div>
+<table class="status-table">
+ <tr><td class="status-head">Hostname:<td><%= host %>:<%= ofport %></td></tr>
+ <tr><td class="status-head">Healthy:<td><%= healthy %></td>
+ <tr><td class="status-head">Uptime:<td><%= uptime %><td>
+ <tr><td class="status-head">JVM memory bloat:<td><%= free %> free out of <%= total %></td>
+ <Tr><td class="status-head">Modules loaded:<td><%= moduleText %></td>
+</div>
+</div>
diff --git a/src/ext/floodlight/src/main/resources/web/tpl/switch-list-item.html b/src/ext/floodlight/src/main/resources/web/tpl/switch-list-item.html
new file mode 100644
index 0000000..7ce0262
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/tpl/switch-list-item.html
@@ -0,0 +1 @@
+ <td><a href="/switch/<%= id %>"><%= id %></a></td><td><%= inetAddress %></td><td><%= manufacturerDescription %><td><%= packetCount %></td><td><%= byteCount %></td><td><%= flowCount %></td><td><%= connectedSince %></td>
diff --git a/src/ext/floodlight/src/main/resources/web/tpl/switch-list.html b/src/ext/floodlight/src/main/resources/web/tpl/switch-list.html
new file mode 100644
index 0000000..e7dac09
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/tpl/switch-list.html
@@ -0,0 +1,22 @@
+<div class="row">
+<div class="span12">
+<div class="page-header">
+ <h1>Switches (<%= nswitches %>)</h1>
+</div>
+<table class="table table-striped switch-table">
+ <thead><tr><th>DPID</th><th>IP Address</th><th>Vendor</th><th>Packets</th><th>Bytes</th><th>Flows</th><th>Connected Since</th></tr></thead>
+ <tbody>
+ <!-- switches will be inserted here by SwitchListView:render -->
+ </tbody>
+</table>
+<!-- TODO implement pagination -->
+<!--
+<div class="pagination pagination-right"><ul>
+ <li><a href="">←</a></li>
+ <li class="active"><a href="">1</a></li>
+ <li><a href="">2</a></li>
+ <li><a href="">→</a>
+</ul></div>
+-->
+</div>
+</div>
diff --git a/src/ext/floodlight/src/main/resources/web/tpl/switch.html b/src/ext/floodlight/src/main/resources/web/tpl/switch.html
new file mode 100644
index 0000000..af89797
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/tpl/switch.html
@@ -0,0 +1,18 @@
+<div class="row">
+<div class="span12">
+<div class="page-header">
+ <h1>Switch <%= id %> <%= inetAddress %></h1>
+</div>
+<p>Connected since <%= connectedSince %><br>
+<%= manufacturerDescription %><br>
+<%= hardwareDescription %><br>
+<%= softwareDescription %><br>
+S/N: <%= serialNumber %><br>
+</p>
+
+<div id="port-list"></div> <!-- TODO would be nice to make this collapsible -->
+
+<div id="flow-list"></div>
+
+</div>
+</div>
diff --git a/src/ext/floodlight/src/main/resources/web/tpl/topology.html b/src/ext/floodlight/src/main/resources/web/tpl/topology.html
new file mode 100644
index 0000000..ce77cc7
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/tpl/topology.html
@@ -0,0 +1,8 @@
+<div class="row">
+<div class="span12">
+<div class="page-header">
+ <h1>Network Topology</h1>
+</div>
+<div id="topology-graph"></div>
+</div>
+</div>
diff --git a/src/ext/floodlight/src/main/resources/web/tpl/vlan-list-item.html b/src/ext/floodlight/src/main/resources/web/tpl/vlan-list-item.html
new file mode 100644
index 0000000..55a2f35
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/tpl/vlan-list-item.html
@@ -0,0 +1 @@
+ <tr><td><a href="/vlan/<%= id %>"><%= id %></a></td><td><%= name %></td><td><%= nhosts %></td></tr>
diff --git a/src/ext/floodlight/src/main/resources/web/tpl/vlan.html b/src/ext/floodlight/src/main/resources/web/tpl/vlan.html
new file mode 100644
index 0000000..edcd408
--- /dev/null
+++ b/src/ext/floodlight/src/main/resources/web/tpl/vlan.html
@@ -0,0 +1 @@
+<p>A VLAN probably has a list of ports and hosts.</p>
diff --git a/src/ext/floodlight/src/main/thrift/packetstreamer.thrift b/src/ext/floodlight/src/main/thrift/packetstreamer.thrift
new file mode 100644
index 0000000..827dd85
--- /dev/null
+++ b/src/ext/floodlight/src/main/thrift/packetstreamer.thrift
@@ -0,0 +1,88 @@
+#
+# Interface definition for packetstreamer service
+#
+
+namespace java net.floodlightcontroller.packetstreamer.thrift
+namespace cpp net.floodlightcontroller.packetstreamer
+namespace py packetstreamer
+namespace php packetstreamer
+namespace perl packetstreamer
+
+const string VERSION = "0.1.0"
+
+#
+# data structures
+#
+
+/**
+ * OFMessage type
+ **/
+enum OFMessageType {
+ HELLO = 0,
+ ERROR = 1,
+ ECHO_REQUEST = 2,
+ ECHO_REPLY = 3,
+ VENDOR = 4,
+ FEATURES_REQUEST = 5,
+ FEATURES_REPLY = 6,
+ GET_CONFIG_REQUEST = 7,
+ GET_CONFIG_REPLY = 8,
+ SET_CONFIG = 9,
+ PACKET_IN = 10,
+ FLOW_REMOVED = 11,
+ PORT_STATUS = 12,
+ PACKET_OUT = 13,
+ FLOW_MOD = 14,
+ PORT_MOD = 15,
+ STATS_REQUEST = 16,
+ STATS_REPLY = 17,
+ BARRIER_REQUEST = 18,
+ BARRIER_REPLY = 19,
+}
+
+/**
+ * A struct that defines switch port tuple
+ */
+struct SwitchPortTuple {
+ 1: i64 dpid,
+ 2: i16 port,
+}
+
+struct Packet {
+ 1: OFMessageType messageType,
+ 2: SwitchPortTuple swPortTuple,
+ 3: binary data,
+}
+
+struct Message {
+ 1: list<string> sessionIDs,
+ 2: Packet packet,
+}
+
+/**
+ * Packetstreamer API
+ */
+service PacketStreamer {
+
+ /**
+ * Synchronous method to get packets for a given sessionid
+ */
+ list<binary> getPackets(1:string sessionid),
+
+ /**
+ * Synchronous method to publish a packet.
+ * It ensure the order that the packets are pushed
+ */
+ i32 pushMessageSync(1:Message packet),
+
+ /**
+ * Asynchronous method to publish a packet.
+ * Order is not guaranteed.
+ */
+ oneway void pushMessageAsync(1:Message packet)
+
+ /**
+ * Terminate a session
+ */
+ void terminateSession(1:string sessionid)
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/core/internal/ControllerTest.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/core/internal/ControllerTest.java
new file mode 100644
index 0000000..fb063df
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/core/internal/ControllerTest.java
@@ -0,0 +1,1250 @@
+/**
+* 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.*;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import net.floodlightcontroller.core.FloodlightProvider;
+import net.floodlightcontroller.core.FloodlightContext;
+import net.floodlightcontroller.core.IFloodlightProviderService;
+import net.floodlightcontroller.core.IHAListener;
+import net.floodlightcontroller.core.IFloodlightProviderService.Role;
+import net.floodlightcontroller.core.IOFMessageFilterManagerService;
+import net.floodlightcontroller.core.IOFMessageListener;
+import net.floodlightcontroller.core.IListener.Command;
+import net.floodlightcontroller.core.IOFSwitch;
+import net.floodlightcontroller.core.IOFSwitchListener;
+import net.floodlightcontroller.core.OFMessageFilterManager;
+import net.floodlightcontroller.core.internal.Controller.IUpdate;
+import net.floodlightcontroller.core.internal.Controller.SwitchUpdate;
+import net.floodlightcontroller.core.internal.Controller.SwitchUpdateType;
+import net.floodlightcontroller.core.internal.OFChannelState.HandshakeState;
+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.packet.ARP;
+import net.floodlightcontroller.packet.Ethernet;
+import net.floodlightcontroller.packet.IPacket;
+import net.floodlightcontroller.packet.IPv4;
+import net.floodlightcontroller.perfmon.IPktInProcessingTimeService;
+import net.floodlightcontroller.perfmon.PktInProcessingTime;
+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 org.easymock.Capture;
+import org.easymock.EasyMock;
+import org.jboss.netty.channel.Channel;
+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.OFPacketIn;
+import org.openflow.protocol.OFPacketOut;
+import org.openflow.protocol.OFPhysicalPort;
+import org.openflow.protocol.OFPort;
+import org.openflow.protocol.OFPortStatus;
+import org.openflow.protocol.OFStatisticsReply;
+import org.openflow.protocol.OFType;
+import org.openflow.protocol.OFPacketIn.OFPacketInReason;
+import org.openflow.protocol.OFPortStatus.OFPortReason;
+import org.openflow.protocol.OFVendor;
+import org.openflow.protocol.action.OFAction;
+import org.openflow.protocol.action.OFActionOutput;
+import org.openflow.protocol.factory.BasicFactory;
+import org.openflow.protocol.statistics.OFFlowStatisticsReply;
+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;
+
+/**
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class ControllerTest extends FloodlightTestCase {
+
+ private Controller controller;
+ private MockThreadPoolService tp;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ FloodlightModuleContext fmc = new FloodlightModuleContext();
+
+ FloodlightProvider cm = new FloodlightProvider();
+ 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);
+
+ CounterStore cs = new CounterStore();
+ fmc.addService(ICounterStoreService.class, cs);
+
+ PktInProcessingTime ppt = new PktInProcessingTime();
+ fmc.addService(IPktInProcessingTimeService.class, ppt);
+
+ tp = new MockThreadPoolService();
+ fmc.addService(IThreadPoolService.class, tp);
+
+ ppt.init(fmc);
+ restApi.init(fmc);
+ memstorage.init(fmc);
+ cm.init(fmc);
+ tp.init(fmc);
+ ppt.startUp(fmc);
+ restApi.startUp(fmc);
+ memstorage.startUp(fmc);
+ cm.startUp(fmc);
+ tp.startUp(fmc);
+ }
+
+ public Controller getController() {
+ return controller;
+ }
+
+ protected 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;
+ }
+
+ /* Set the mock expectations for sw when sw is passed to addSwitch */
+ protected void setupSwitchForAddSwitch(IOFSwitch sw, long dpid) {
+ String dpidString = HexString.toHexString(dpid);
+
+ expect(sw.getId()).andReturn(dpid).anyTimes();
+ expect(sw.getStringId()).andReturn(dpidString).anyTimes();
+ expect(sw.getConnectedSince()).andReturn(new Date());
+ Channel channel = createMock(Channel.class);
+ expect(sw.getChannel()).andReturn(channel);
+ expect(channel.getRemoteAddress()).andReturn(null);
+
+ expect(sw.getCapabilities()).andReturn(0).anyTimes();
+ expect(sw.getBuffers()).andReturn(0).anyTimes();
+ expect(sw.getTables()).andReturn((byte)0).anyTimes();
+ expect(sw.getActions()).andReturn(0).anyTimes();
+ expect(sw.getPorts()).andReturn(new ArrayList<OFPhysicalPort>()).anyTimes();
+ }
+
+ /**
+ * Run the controller's main loop so that updates are processed
+ */
+ protected class ControllerRunThread extends Thread {
+ public void run() {
+ controller.openFlowPort = 0; // Don't listen
+ controller.run();
+ }
+ }
+
+ /**
+ * Verify that a listener that throws an exception halts further
+ * execution, and verify that the Commands STOP and CONTINUE are honored.
+ * @throws Exception
+ */
+ @Test
+ public void testHandleMessages() throws Exception {
+ Controller controller = getController();
+ controller.removeOFMessageListeners(OFType.PACKET_IN);
+
+ IOFSwitch sw = createMock(IOFSwitch.class);
+ expect(sw.getStringId()).andReturn("00:00:00:00:00:00:00").anyTimes();
+
+ // Build our test packet
+ 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) new BasicFactory().getMessage(OFType.PACKET_IN))
+ .setBufferId(-1)
+ .setInPort((short) 1)
+ .setPacketData(testPacketSerialized)
+ .setReason(OFPacketInReason.NO_MATCH)
+ .setTotalLength((short) testPacketSerialized.length);
+
+ IOFMessageListener test1 = createMock(IOFMessageListener.class);
+ expect(test1.getName()).andReturn("test1").anyTimes();
+ expect(test1.isCallbackOrderingPrereq((OFType)anyObject(), (String)anyObject())).andReturn(false).anyTimes();
+ expect(test1.isCallbackOrderingPostreq((OFType)anyObject(), (String)anyObject())).andReturn(false).anyTimes();
+ expect(test1.receive(eq(sw), eq(pi), isA(FloodlightContext.class))).andThrow(new RuntimeException("This is NOT an error! We are testing exception catching."));
+ IOFMessageListener test2 = createMock(IOFMessageListener.class);
+ expect(test2.getName()).andReturn("test2").anyTimes();
+ expect(test2.isCallbackOrderingPrereq((OFType)anyObject(), (String)anyObject())).andReturn(false).anyTimes();
+ expect(test2.isCallbackOrderingPostreq((OFType)anyObject(), (String)anyObject())).andReturn(false).anyTimes();
+ // expect no calls to test2.receive() since test1.receive() threw an exception
+
+ replay(test1, test2, sw);
+ controller.addOFMessageListener(OFType.PACKET_IN, test1);
+ controller.addOFMessageListener(OFType.PACKET_IN, test2);
+ try {
+ controller.handleMessage(sw, pi, null);
+ } catch (RuntimeException e) {
+ assertEquals(e.getMessage().startsWith("This is NOT an error!"), true);
+ }
+ verify(test1, test2, sw);
+
+ // verify STOP works
+ reset(test1, test2, sw);
+ expect(test1.receive(eq(sw), eq(pi), isA(FloodlightContext.class))).andReturn(Command.STOP);
+ //expect(test1.getId()).andReturn(0).anyTimes();
+ expect(sw.getStringId()).andReturn("00:00:00:00:00:00:00").anyTimes();
+ replay(test1, test2, sw);
+ controller.handleMessage(sw, pi, null);
+ verify(test1, test2, sw);
+ }
+
+ 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());
+ }
+
+ @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) new BasicFactory().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 testAddSwitch() throws Exception {
+ controller.activeSwitches = new ConcurrentHashMap<Long, IOFSwitch>();
+
+ //OFSwitchImpl oldsw = createMock(OFSwitchImpl.class);
+ OFSwitchImpl oldsw = new OFSwitchImpl();
+ OFFeaturesReply featuresReply = new OFFeaturesReply();
+ featuresReply.setDatapathId(0L);
+ featuresReply.setPorts(new ArrayList<OFPhysicalPort>());
+ oldsw.setFeaturesReply(featuresReply);
+ //expect(oldsw.getId()).andReturn(0L).anyTimes();
+ //expect(oldsw.asyncRemoveSwitchLock()).andReturn(rwlock.writeLock()).anyTimes();
+ //oldsw.setConnected(false);
+ //expect(oldsw.getStringId()).andReturn("00:00:00:00:00:00:00").anyTimes();
+
+ Channel channel = createNiceMock(Channel.class);
+ //expect(oldsw.getChannel()).andReturn(channel);
+ oldsw.setChannel(channel);
+ expect(channel.close()).andReturn(null);
+
+ IOFSwitch newsw = createMock(IOFSwitch.class);
+ expect(newsw.getId()).andReturn(0L).anyTimes();
+ expect(newsw.getStringId()).andReturn("00:00:00:00:00:00:00").anyTimes();
+ expect(newsw.getConnectedSince()).andReturn(new Date());
+ Channel channel2 = createMock(Channel.class);
+ expect(newsw.getChannel()).andReturn(channel2);
+ expect(channel2.getRemoteAddress()).andReturn(null);
+ expect(newsw.getPorts()).andReturn(new ArrayList<OFPhysicalPort>());
+ expect(newsw.getCapabilities()).andReturn(0).anyTimes();
+ expect(newsw.getBuffers()).andReturn(0).anyTimes();
+ expect(newsw.getTables()).andReturn((byte)0).anyTimes();
+ expect(newsw.getActions()).andReturn(0).anyTimes();
+ controller.activeSwitches.put(0L, oldsw);
+ replay(newsw, channel, channel2);
+
+ controller.addSwitch(newsw);
+
+ verify(newsw, channel, channel2);
+ }
+
+ @Test
+ public void testUpdateQueue() throws Exception {
+ class DummySwitchListener implements IOFSwitchListener {
+ public int nAdded;
+ public int nRemoved;
+ public int nPortChanged;
+ public DummySwitchListener() {
+ nAdded = 0;
+ nRemoved = 0;
+ nPortChanged = 0;
+ }
+ public synchronized void addedSwitch(IOFSwitch sw) {
+ nAdded++;
+ notifyAll();
+ }
+ public synchronized void removedSwitch(IOFSwitch sw) {
+ nRemoved++;
+ notifyAll();
+ }
+ public String getName() {
+ return "dummy";
+ }
+ @Override
+ public void switchPortChanged(Long switchId) {
+ nPortChanged++;
+ notifyAll();
+ }
+ }
+ DummySwitchListener switchListener = new DummySwitchListener();
+ IOFSwitch sw = createMock(IOFSwitch.class);
+ ControllerRunThread t = new ControllerRunThread();
+ t.start();
+
+ controller.addOFSwitchListener(switchListener);
+ synchronized(switchListener) {
+ controller.updates.put(controller.new SwitchUpdate(sw,
+ Controller.SwitchUpdateType.ADDED));
+ switchListener.wait(500);
+ assertTrue("IOFSwitchListener.addedSwitch() was not called",
+ switchListener.nAdded == 1);
+ controller.updates.put(controller.new SwitchUpdate(sw,
+ Controller.SwitchUpdateType.REMOVED));
+ switchListener.wait(500);
+ assertTrue("IOFSwitchListener.removedSwitch() was not called",
+ switchListener.nRemoved == 1);
+ controller.updates.put(controller.new SwitchUpdate(sw,
+ Controller.SwitchUpdateType.PORTCHANGED));
+ switchListener.wait(500);
+ assertTrue("IOFSwitchListener.switchPortChanged() was not called",
+ switchListener.nPortChanged == 1);
+ }
+ }
+
+
+ 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 void roleChanged(Role oldRole, Role newRole) {
+ // ignore
+ }
+
+ @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);
+
+ }
+ }
+ long waitTimeout = 250; // ms
+ 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);
+ ControllerRunThread t = new ControllerRunThread();
+ t.start();
+
+ synchronized(listener) {
+ // Insert a first entry
+ controller.storageSource.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");
+ listener.wait(waitTimeout);
+ listener.do_assert(1, expectedCurMap, expectedAddedMap, expectedRemovedMap);
+
+ // Add an interface that we want to ignore.
+ controller.storageSource.insertRow(Controller.CONTROLLER_INTERFACE_TABLE_NAME,
+ getFakeControllerIPRow("row2", "c1", "Ethernet", 1, "1.1.1.2"));
+ listener.wait(waitTimeout); // TODO: do a different check. This call will have to wait for the timeout
+ assertTrue("controllerNodeIPsChanged() should not have been called here",
+ listener.nCalled == 1);
+
+ // Add another entry
+ controller.storageSource.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");
+ listener.wait(waitTimeout);
+ listener.do_assert(2, expectedCurMap, expectedAddedMap, expectedRemovedMap);
+
+
+ // Update an entry
+ controller.storageSource.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");
+ listener.wait(waitTimeout);
+ listener.do_assert(3, expectedCurMap, expectedAddedMap, expectedRemovedMap);
+
+ // Delete an entry
+ controller.storageSource.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");
+ listener.wait(waitTimeout);
+ listener.do_assert(4, expectedCurMap, expectedAddedMap, expectedRemovedMap);
+ }
+ }
+
+ @Test
+ public void testGetControllerNodeIPs() {
+ HashMap<String,String> expectedCurMap = new HashMap<String, String>();
+
+ controller.storageSource.insertRow(Controller.CONTROLLER_INTERFACE_TABLE_NAME,
+ getFakeControllerIPRow("row1", "c1", "Ethernet", 0, "1.1.1.1"));
+ controller.storageSource.insertRow(Controller.CONTROLLER_INTERFACE_TABLE_NAME,
+ getFakeControllerIPRow("row2", "c1", "Ethernet", 1, "1.1.1.2"));
+ controller.storageSource.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());
+ }
+
+ @Test
+ public void testSetRoleNull() {
+ try {
+ controller.setRole(null);
+ fail("Should have thrown an Exception");
+ }
+ catch (NullPointerException e) {
+ //exptected
+ }
+ }
+
+ @Test
+ public void testSetRole() {
+ controller.connectedSwitches.add(new OFSwitchImpl());
+ RoleChanger roleChanger = createMock(RoleChanger.class);
+ roleChanger.submitRequest(controller.connectedSwitches, Role.SLAVE);
+ controller.roleChanger = roleChanger;
+
+ assertEquals("Check that update queue is empty", 0,
+ controller.updates.size());
+
+ replay(roleChanger);
+ controller.setRole(Role.SLAVE);
+ verify(roleChanger);
+
+ Controller.IUpdate upd = controller.updates.poll();
+ assertNotNull("Check that update queue has an update", upd);
+ assertTrue("Check that update is HARoleUpdate",
+ upd instanceof Controller.HARoleUpdate);
+ Controller.HARoleUpdate roleUpd = (Controller.HARoleUpdate)upd;
+ assertSame(null, roleUpd.oldRole);
+ assertSame(Role.SLAVE, roleUpd.newRole);
+ }
+
+ @Test
+ public void testCheckSwitchReady() {
+ OFChannelState state = new OFChannelState();
+ Controller.OFChannelHandler chdlr = controller.new OFChannelHandler(state);
+ chdlr.sw = createMock(OFSwitchImpl.class);
+
+ // Wrong current state
+ // Should not go to READY
+ state.hsState = OFChannelState.HandshakeState.HELLO;
+ state.hasDescription = true;
+ state.hasGetConfigReply = true;
+ replay(chdlr.sw); // nothing called on sw
+ chdlr.checkSwitchReady();
+ verify(chdlr.sw);
+ assertSame(OFChannelState.HandshakeState.HELLO, state.hsState);
+ reset(chdlr.sw);
+
+ // Have only config reply
+ state.hsState = OFChannelState.HandshakeState.FEATURES_REPLY;
+ state.hasDescription = false;
+ state.hasGetConfigReply = true;
+ replay(chdlr.sw);
+ chdlr.checkSwitchReady();
+ verify(chdlr.sw);
+ assertSame(OFChannelState.HandshakeState.FEATURES_REPLY, state.hsState);
+ assertTrue(controller.connectedSwitches.isEmpty());
+ assertTrue(controller.activeSwitches.isEmpty());
+ reset(chdlr.sw);
+
+ // Have only desc reply
+ state.hsState = OFChannelState.HandshakeState.FEATURES_REPLY;
+ state.hasDescription = true;
+ state.hasGetConfigReply = false;
+ replay(chdlr.sw);
+ chdlr.checkSwitchReady();
+ verify(chdlr.sw);
+ assertSame(OFChannelState.HandshakeState.FEATURES_REPLY, state.hsState);
+ assertTrue(controller.connectedSwitches.isEmpty());
+ assertTrue(controller.activeSwitches.isEmpty());
+ reset(chdlr.sw);
+
+ //////////////////////////////////////////
+ // Finally, everything is right. Should advance to READY
+ //////////////////////////////////////////
+ controller.roleChanger = createMock(RoleChanger.class);
+ state.hsState = OFChannelState.HandshakeState.FEATURES_REPLY;
+ state.hasDescription = true;
+ state.hasGetConfigReply = true;
+ // Role support disabled. Switch should be promoted to active switch
+ // list.
+ setupSwitchForAddSwitch(chdlr.sw, 0L);
+ chdlr.sw.clearAllFlowMods();
+ replay(controller.roleChanger, chdlr.sw);
+ chdlr.checkSwitchReady();
+ verify(controller.roleChanger, chdlr.sw);
+ assertSame(OFChannelState.HandshakeState.READY, state.hsState);
+ assertSame(chdlr.sw, controller.activeSwitches.get(0L));
+ assertTrue(controller.connectedSwitches.contains(chdlr.sw));
+ assertTrue(state.firstRoleReplyReceived);
+ reset(chdlr.sw);
+ reset(controller.roleChanger);
+ controller.connectedSwitches.clear();
+ controller.activeSwitches.clear();
+
+
+ // Role support enabled.
+ state.hsState = OFChannelState.HandshakeState.FEATURES_REPLY;
+ controller.role = Role.MASTER;
+ Capture<Collection<OFSwitchImpl>> swListCapture =
+ new Capture<Collection<OFSwitchImpl>>();
+ controller.roleChanger.submitRequest(capture(swListCapture),
+ same(Role.MASTER));
+ replay(controller.roleChanger, chdlr.sw);
+ chdlr.checkSwitchReady();
+ verify(controller.roleChanger, chdlr.sw);
+ assertSame(OFChannelState.HandshakeState.READY, state.hsState);
+ assertTrue(controller.activeSwitches.isEmpty());
+ assertTrue(controller.connectedSwitches.contains(chdlr.sw));
+ assertTrue(state.firstRoleReplyReceived);
+ Collection<OFSwitchImpl> swList = swListCapture.getValue();
+ assertEquals(1, swList.size());
+ assertTrue("swList must contain this switch", swList.contains(chdlr.sw));
+ }
+
+
+ @Test
+ public void testChannelDisconnected() throws Exception {
+ OFChannelState state = new OFChannelState();
+ state.hsState = OFChannelState.HandshakeState.READY;
+ Controller.OFChannelHandler chdlr = controller.new OFChannelHandler(state);
+ chdlr.sw = createMock(OFSwitchImpl.class);
+
+ // Switch is active
+ expect(chdlr.sw.getId()).andReturn(0L).anyTimes();
+ expect(chdlr.sw.getStringId()).andReturn("00:00:00:00:00:00:00:00")
+ .anyTimes();
+ chdlr.sw.cancelAllStatisticsReplies();
+ chdlr.sw.setConnected(false);
+ expect(chdlr.sw.isConnected()).andReturn(true);
+
+ controller.connectedSwitches.add(chdlr.sw);
+ controller.activeSwitches.put(0L, chdlr.sw);
+
+ replay(chdlr.sw);
+ chdlr.channelDisconnected(null, null);
+ verify(chdlr.sw);
+
+ // Switch is connected but not active
+ reset(chdlr.sw);
+ expect(chdlr.sw.getId()).andReturn(0L).anyTimes();
+ chdlr.sw.setConnected(false);
+ replay(chdlr.sw);
+ chdlr.channelDisconnected(null, null);
+ verify(chdlr.sw);
+
+ // Not in ready state
+ state.hsState = HandshakeState.START;
+ reset(chdlr.sw);
+ replay(chdlr.sw);
+ chdlr.channelDisconnected(null, null);
+ verify(chdlr.sw);
+
+ // Switch is null
+ state.hsState = HandshakeState.READY;
+ chdlr.sw = null;
+ chdlr.channelDisconnected(null, null);
+ }
+
+ /*
+ @Test
+ public void testRoleChangeForSerialFailoverSwitch() throws Exception {
+ OFSwitchImpl newsw = createMock(OFSwitchImpl.class);
+ expect(newsw.getId()).andReturn(0L).anyTimes();
+ expect(newsw.getStringId()).andReturn("00:00:00:00:00:00:00").anyTimes();
+ Channel channel2 = createMock(Channel.class);
+ expect(newsw.getChannel()).andReturn(channel2);
+
+ // newsw.role is null because the switch does not support
+ // role request messages
+ expect(newsw.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE))
+ .andReturn(false);
+ // switch is connected
+ controller.connectedSwitches.add(newsw);
+
+ // the switch should get disconnected when role is changed to SLAVE
+ expect(channel2.close()).andReturn(null);
+
+ replay(newsw, channel2);
+ controller.setRole(Role.SLAVE);
+ verify(newsw, channel2);
+ }
+ */
+
+ @Test
+ public void testRoleNotSupportedError() throws Exception {
+ int xid = 424242;
+ OFChannelState state = new OFChannelState();
+ state.hsState = HandshakeState.READY;
+ Controller.OFChannelHandler chdlr = controller.new OFChannelHandler(state);
+ chdlr.sw = createMock(OFSwitchImpl.class);
+ Channel ch = createMock(Channel.class);
+
+ // the error returned when role request message is not supported by sw
+ OFError msg = new OFError();
+ msg.setType(OFType.ERROR);
+ msg.setXid(xid);
+ msg.setErrorType(OFErrorType.OFPET_BAD_REQUEST);
+ msg.setErrorCode(OFBadRequestCode.OFPBRC_BAD_VENDOR);
+
+ // the switch connection should get disconnected when the controller is
+ // in SLAVE mode and the switch does not support role-request messages
+ state.firstRoleReplyReceived = false;
+ controller.role = Role.SLAVE;
+ expect(chdlr.sw.checkFirstPendingRoleRequestXid(xid)).andReturn(true);
+ chdlr.sw.deliverRoleRequestNotSupported(xid);
+ expect(chdlr.sw.getChannel()).andReturn(ch).anyTimes();
+ expect(ch.close()).andReturn(null);
+
+ replay(ch, chdlr.sw);
+ chdlr.processOFMessage(msg);
+ verify(ch, chdlr.sw);
+ assertTrue("state.firstRoleReplyReceived must be true",
+ state.firstRoleReplyReceived);
+ assertTrue("activeSwitches must be empty",
+ controller.activeSwitches.isEmpty());
+ reset(ch, chdlr.sw);
+
+
+ // a different error message - should also reject role request
+ msg.setErrorType(OFErrorType.OFPET_BAD_REQUEST);
+ msg.setErrorCode(OFBadRequestCode.OFPBRC_EPERM);
+ state.firstRoleReplyReceived = false;
+ controller.role = Role.SLAVE;
+ expect(chdlr.sw.checkFirstPendingRoleRequestXid(xid)).andReturn(true);
+ chdlr.sw.deliverRoleRequestNotSupported(xid);
+ expect(chdlr.sw.getChannel()).andReturn(ch).anyTimes();
+ expect(ch.close()).andReturn(null);
+ replay(ch, chdlr.sw);
+
+ chdlr.processOFMessage(msg);
+ verify(ch, chdlr.sw);
+ assertTrue("state.firstRoleReplyReceived must be True even with EPERM",
+ state.firstRoleReplyReceived);
+ assertTrue("activeSwitches must be empty",
+ controller.activeSwitches.isEmpty());
+ reset(ch, chdlr.sw);
+
+
+ // We are MASTER, the switch should be added to the list of active
+ // switches.
+ state.firstRoleReplyReceived = false;
+ controller.role = Role.MASTER;
+ expect(chdlr.sw.checkFirstPendingRoleRequestXid(xid)).andReturn(true);
+ chdlr.sw.deliverRoleRequestNotSupported(xid);
+ setupSwitchForAddSwitch(chdlr.sw, 0L);
+ chdlr.sw.clearAllFlowMods();
+ replay(ch, chdlr.sw);
+
+ chdlr.processOFMessage(msg);
+ verify(ch, chdlr.sw);
+ assertTrue("state.firstRoleReplyReceived must be true",
+ state.firstRoleReplyReceived);
+ assertSame("activeSwitches must contain this switch",
+ chdlr.sw, controller.activeSwitches.get(0L));
+ reset(ch, chdlr.sw);
+
+ }
+
+
+ @Test
+ public void testVendorMessageUnknown() throws Exception {
+ // Check behavior with an unknown vendor id
+ OFChannelState state = new OFChannelState();
+ state.hsState = HandshakeState.READY;
+ Controller.OFChannelHandler chdlr = controller.new OFChannelHandler(state);
+ OFVendor msg = new OFVendor();
+ msg.setVendor(0);
+ chdlr.processOFMessage(msg);
+ }
+
+
+ // Helper function.
+ protected Controller.OFChannelHandler getChannelHandlerForRoleReplyTest() {
+ OFChannelState state = new OFChannelState();
+ state.hsState = HandshakeState.READY;
+ Controller.OFChannelHandler chdlr = controller.new OFChannelHandler(state);
+ chdlr.sw = createMock(OFSwitchImpl.class);
+ return chdlr;
+ }
+
+ // Helper function
+ protected OFVendor getRoleReplyMsgForRoleReplyTest(int xid, int nicira_role) {
+ OFVendor msg = new OFVendor();
+ msg.setXid(xid);
+ msg.setVendor(OFNiciraVendorData.NX_VENDOR_ID);
+ OFRoleReplyVendorData roleReplyVendorData =
+ new OFRoleReplyVendorData(OFRoleReplyVendorData.NXT_ROLE_REPLY);
+ msg.setVendorData(roleReplyVendorData);
+ roleReplyVendorData.setRole(nicira_role);
+ return msg;
+ }
+
+ /** invalid role in role reply */
+ @Test
+ public void testNiciraRoleReplyInvalidRole()
+ throws Exception {
+ int xid = 424242;
+ Controller.OFChannelHandler chdlr = getChannelHandlerForRoleReplyTest();
+ Channel ch = createMock(Channel.class);
+ expect(chdlr.sw.getChannel()).andReturn(ch);
+ expect(ch.close()).andReturn(null);
+ OFVendor msg = getRoleReplyMsgForRoleReplyTest(xid, 232323);
+ replay(chdlr.sw, ch);
+ chdlr.processOFMessage(msg);
+ verify(chdlr.sw, ch);
+ }
+
+ /** First role reply message received: transition from slave to master */
+ @Test
+ public void testNiciraRoleReplySlave2MasterFristTime()
+ throws Exception {
+ int xid = 424242;
+ Controller.OFChannelHandler chdlr = getChannelHandlerForRoleReplyTest();
+ OFVendor msg = getRoleReplyMsgForRoleReplyTest(xid,
+ OFRoleReplyVendorData.NX_ROLE_MASTER);
+
+ chdlr.sw.deliverRoleReply(xid, Role.MASTER);
+ expect(chdlr.sw.isActive()).andReturn(true);
+ setupSwitchForAddSwitch(chdlr.sw, 1L);
+ chdlr.sw.clearAllFlowMods();
+ chdlr.state.firstRoleReplyReceived = false;
+ replay(chdlr.sw);
+ chdlr.processOFMessage(msg);
+ verify(chdlr.sw);
+ assertTrue("state.firstRoleReplyReceived must be true",
+ chdlr.state.firstRoleReplyReceived);
+ assertSame("activeSwitches must contain this switch",
+ chdlr.sw, controller.activeSwitches.get(1L));
+ }
+
+
+ /** Not first role reply message received: transition from slave to master */
+ @Test
+ public void testNiciraRoleReplySlave2MasterNotFristTime()
+ throws Exception {
+ int xid = 424242;
+ Controller.OFChannelHandler chdlr = getChannelHandlerForRoleReplyTest();
+ OFVendor msg = getRoleReplyMsgForRoleReplyTest(xid,
+ OFRoleReplyVendorData.NX_ROLE_MASTER);
+
+ chdlr.sw.deliverRoleReply(xid, Role.MASTER);
+ expect(chdlr.sw.isActive()).andReturn(true);
+ setupSwitchForAddSwitch(chdlr.sw, 1L);
+ chdlr.state.firstRoleReplyReceived = true;
+ // Flow table shouldn't be wipe
+ replay(chdlr.sw);
+ chdlr.processOFMessage(msg);
+ verify(chdlr.sw);
+ assertTrue("state.firstRoleReplyReceived must be true",
+ chdlr.state.firstRoleReplyReceived);
+ assertSame("activeSwitches must contain this switch",
+ chdlr.sw, controller.activeSwitches.get(1L));
+ }
+
+ /** transition from slave to equal */
+ @Test
+ public void testNiciraRoleReplySlave2Equal()
+ throws Exception {
+ int xid = 424242;
+ Controller.OFChannelHandler chdlr = getChannelHandlerForRoleReplyTest();
+ OFVendor msg = getRoleReplyMsgForRoleReplyTest(xid,
+ OFRoleReplyVendorData.NX_ROLE_OTHER);
+
+ chdlr.sw.deliverRoleReply(xid, Role.EQUAL);
+ expect(chdlr.sw.isActive()).andReturn(true);
+ setupSwitchForAddSwitch(chdlr.sw, 1L);
+ chdlr.sw.clearAllFlowMods();
+ chdlr.state.firstRoleReplyReceived = false;
+ replay(chdlr.sw);
+ chdlr.processOFMessage(msg);
+ verify(chdlr.sw);
+ assertTrue("state.firstRoleReplyReceived must be true",
+ chdlr.state.firstRoleReplyReceived);
+ assertSame("activeSwitches must contain this switch",
+ chdlr.sw, controller.activeSwitches.get(1L));
+ };
+
+ @Test
+ /** Slave2Slave transition ==> no change */
+ public void testNiciraRoleReplySlave2Slave() throws Exception{
+ int xid = 424242;
+ Controller.OFChannelHandler chdlr = getChannelHandlerForRoleReplyTest();
+ OFVendor msg = getRoleReplyMsgForRoleReplyTest(xid,
+ OFRoleReplyVendorData.NX_ROLE_SLAVE);
+
+ chdlr.sw.deliverRoleReply(xid, Role.SLAVE);
+ expect(chdlr.sw.getId()).andReturn(1L).anyTimes();
+ expect(chdlr.sw.getStringId()).andReturn("00:00:00:00:00:00:00:01")
+ .anyTimes();
+ expect(chdlr.sw.isActive()).andReturn(false);
+ // don't add switch to activeSwitches ==> slave2slave
+ chdlr.state.firstRoleReplyReceived = false;
+ replay(chdlr.sw);
+ chdlr.processOFMessage(msg);
+ verify(chdlr.sw);
+ assertTrue("state.firstRoleReplyReceived must be true",
+ chdlr.state.firstRoleReplyReceived);
+ assertTrue("activeSwitches must be empty",
+ controller.activeSwitches.isEmpty());
+ }
+
+ @Test
+ /** Equal2Master transition ==> no change */
+ public void testNiciraRoleReplyEqual2Master() throws Exception{
+ int xid = 424242;
+ Controller.OFChannelHandler chdlr = getChannelHandlerForRoleReplyTest();
+ OFVendor msg = getRoleReplyMsgForRoleReplyTest(xid,
+ OFRoleReplyVendorData.NX_ROLE_MASTER);
+
+ chdlr.sw.deliverRoleReply(xid, Role.MASTER);
+ expect(chdlr.sw.getId()).andReturn(1L).anyTimes();
+ expect(chdlr.sw.getStringId()).andReturn("00:00:00:00:00:00:00:01")
+ .anyTimes();
+ expect(chdlr.sw.isActive()).andReturn(true);
+ controller.activeSwitches.put(1L, chdlr.sw);
+ chdlr.state.firstRoleReplyReceived = false;
+ replay(chdlr.sw);
+ chdlr.processOFMessage(msg);
+ verify(chdlr.sw);
+ assertTrue("state.firstRoleReplyReceived must be true",
+ chdlr.state.firstRoleReplyReceived);
+ assertSame("activeSwitches must contain this switch",
+ chdlr.sw, controller.activeSwitches.get(1L));
+ }
+
+ @Test
+ public void testNiciraRoleReplyMaster2Slave()
+ throws Exception {
+ int xid = 424242;
+ Controller.OFChannelHandler chdlr = getChannelHandlerForRoleReplyTest();
+ OFVendor msg = getRoleReplyMsgForRoleReplyTest(xid,
+ OFRoleReplyVendorData.NX_ROLE_SLAVE);
+
+ chdlr.sw.deliverRoleReply(xid, Role.SLAVE);
+ expect(chdlr.sw.getId()).andReturn(1L).anyTimes();
+ expect(chdlr.sw.getStringId()).andReturn("00:00:00:00:00:00:00:01")
+ .anyTimes();
+ controller.activeSwitches.put(1L, chdlr.sw);
+ expect(chdlr.sw.isActive()).andReturn(false);
+ expect(chdlr.sw.isConnected()).andReturn(true);
+ chdlr.sw.cancelAllStatisticsReplies();
+ chdlr.state.firstRoleReplyReceived = false;
+ replay(chdlr.sw);
+ chdlr.processOFMessage(msg);
+ verify(chdlr.sw);
+ assertTrue("state.firstRoleReplyReceived must be true",
+ chdlr.state.firstRoleReplyReceived);
+ assertTrue("activeSwitches must be empty",
+ controller.activeSwitches.isEmpty());
+ }
+
+ /**
+ * Tests that you can't remove a switch from the active
+ * switch list.
+ * @throws Exception
+ */
+ @Test
+ public void testRemoveActiveSwitch() {
+ IOFSwitch sw = EasyMock.createNiceMock(IOFSwitch.class);
+ boolean exceptionThrown = false;
+ expect(sw.getId()).andReturn(1L).anyTimes();
+ replay(sw);
+ getController().activeSwitches.put(sw.getId(), sw);
+ try {
+ getController().getSwitches().remove(1L);
+ } catch (UnsupportedOperationException e) {
+ exceptionThrown = true;
+ }
+ assertTrue(exceptionThrown);
+ verify(sw);
+ }
+
+ public void verifyPortChangedUpdateInQueue(IOFSwitch sw) throws Exception {
+ assertEquals(1, controller.updates.size());
+ IUpdate update = controller.updates.take();
+ assertEquals(true, update instanceof SwitchUpdate);
+ SwitchUpdate swUpdate = (SwitchUpdate)update;
+ assertEquals(sw, swUpdate.sw);
+ assertEquals(SwitchUpdateType.PORTCHANGED, swUpdate.switchUpdateType);
+ }
+
+ /*
+ * Test handlePortStatus()
+ * TODO: test correct updateStorage behavior!
+ */
+ @Test
+ public void testHandlePortStatus() throws Exception {
+ IOFSwitch sw = createMock(IOFSwitch.class);
+ OFPhysicalPort port = new OFPhysicalPort();
+ port.setName("myPortName1");
+ port.setPortNumber((short)42);
+
+ OFPortStatus ofps = new OFPortStatus();
+ ofps.setDesc(port);
+
+ ofps.setReason((byte)OFPortReason.OFPPR_ADD.ordinal());
+ sw.setPort(port);
+ expectLastCall().once();
+ replay(sw);
+ controller.handlePortStatusMessage(sw, ofps, false);
+ verify(sw);
+ verifyPortChangedUpdateInQueue(sw);
+ reset(sw);
+
+ ofps.setReason((byte)OFPortReason.OFPPR_MODIFY.ordinal());
+ sw.setPort(port);
+ expectLastCall().once();
+ replay(sw);
+ controller.handlePortStatusMessage(sw, ofps, false);
+ verify(sw);
+ verifyPortChangedUpdateInQueue(sw);
+ reset(sw);
+
+ ofps.setReason((byte)OFPortReason.OFPPR_DELETE.ordinal());
+ sw.deletePort(port.getPortNumber());
+ expectLastCall().once();
+ replay(sw);
+ controller.handlePortStatusMessage(sw, ofps, false);
+ verify(sw);
+ verifyPortChangedUpdateInQueue(sw);
+ reset(sw);
+ }
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/core/internal/OFSwitchImplTest.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/core/internal/OFSwitchImplTest.java
new file mode 100644
index 0000000..758cd05
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/core/internal/OFSwitchImplTest.java
@@ -0,0 +1,236 @@
+package net.floodlightcontroller.core.internal;
+
+import static org.easymock.EasyMock.*;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.util.List;
+
+import net.floodlightcontroller.core.IFloodlightProviderService.Role;
+import net.floodlightcontroller.core.IOFSwitch;
+import net.floodlightcontroller.core.internal.OFSwitchImpl.PendingRoleRequestEntry;
+import net.floodlightcontroller.core.test.MockFloodlightProvider;
+import net.floodlightcontroller.test.FloodlightTestCase;
+
+import org.easymock.Capture;
+import org.jboss.netty.channel.Channel;
+import org.junit.Before;
+import org.junit.Test;
+import org.openflow.protocol.OFMessage;
+import org.openflow.protocol.OFType;
+import org.openflow.protocol.OFVendor;
+import org.openflow.protocol.vendor.OFVendorData;
+import org.openflow.vendor.nicira.OFNiciraVendorData;
+import org.openflow.vendor.nicira.OFRoleRequestVendorData;
+import org.openflow.vendor.nicira.OFRoleVendorData;
+
+public class OFSwitchImplTest extends FloodlightTestCase {
+ protected OFSwitchImpl sw;
+
+
+ @Before
+ public void setUp() throws Exception {
+ sw = new OFSwitchImpl();
+ Channel ch = createMock(Channel.class);
+ SocketAddress sa = new InetSocketAddress(42);
+ expect(ch.getRemoteAddress()).andReturn(sa).anyTimes();
+ sw.setChannel(ch);
+ MockFloodlightProvider floodlightProvider = new MockFloodlightProvider();
+ sw.setFloodlightProvider(floodlightProvider);
+ }
+
+
+ public void doSendNxRoleRequest(Role role, int nx_role) throws Exception {
+ long cookie = System.nanoTime();
+
+ // verify that the correct OFMessage is sent
+ Capture<List<OFMessage>> msgCapture = new Capture<List<OFMessage>>();
+ expect(sw.channel.write(capture(msgCapture))).andReturn(null);
+ replay(sw.channel);
+ int xid = sw.sendNxRoleRequest(role, cookie);
+ verify(sw.channel);
+ List<OFMessage> msgList = msgCapture.getValue();
+ assertEquals(1, msgList.size());
+ OFMessage msg = msgList.get(0);
+ assertEquals("Transaction Ids must match", xid, msg.getXid());
+ assertTrue("Message must be an OFVendor type", msg instanceof OFVendor);
+ assertEquals(OFType.VENDOR, msg.getType());
+ OFVendor vendorMsg = (OFVendor)msg;
+ assertEquals("Vendor message must be vendor Nicira",
+ OFNiciraVendorData.NX_VENDOR_ID, vendorMsg.getVendor());
+ OFVendorData vendorData = vendorMsg.getVendorData();
+ assertTrue("Vendor Data must be an OFRoleRequestVendorData",
+ vendorData instanceof OFRoleRequestVendorData);
+ OFRoleRequestVendorData roleRequest = (OFRoleRequestVendorData)vendorData;
+ assertEquals(nx_role, roleRequest.getRole());
+
+ // Now verify that we've added the pending request correctly
+ // to the pending queue
+ assertEquals(1, sw.pendingRoleRequests.size());
+ PendingRoleRequestEntry pendingRoleRequest = sw.pendingRoleRequests.poll();
+ assertEquals(msg.getXid(), pendingRoleRequest.xid);
+ assertEquals(role, pendingRoleRequest.role);
+ assertEquals(cookie, pendingRoleRequest.cookie);
+ reset(sw.channel);
+ }
+
+ @Test
+ public void testSendNxRoleRequest() throws Exception {
+ doSendNxRoleRequest(Role.MASTER, OFRoleVendorData.NX_ROLE_MASTER);
+ doSendNxRoleRequest(Role.SLAVE, OFRoleVendorData.NX_ROLE_SLAVE);
+ doSendNxRoleRequest(Role.EQUAL, OFRoleVendorData.NX_ROLE_OTHER);
+ }
+
+
+ @Test
+ public void testDeliverRoleReplyOk() {
+ // test normal case
+ PendingRoleRequestEntry pending = new PendingRoleRequestEntry(
+ (int)System.currentTimeMillis(), // arbitrary xid
+ Role.MASTER,
+ System.nanoTime() // arbitrary cookie
+ );
+ sw.pendingRoleRequests.add(pending);
+ replay(sw.channel);
+ sw.deliverRoleReply(pending.xid, pending.role);
+ verify(sw.channel);
+ assertEquals(true, sw.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE));
+ assertEquals(pending.role, sw.role);
+ assertEquals(0, sw.pendingRoleRequests.size());
+ }
+
+ @Test
+ public void testDeliverRoleReplyOkRepeated() {
+ // test normal case. Not the first role reply
+ PendingRoleRequestEntry pending = new PendingRoleRequestEntry(
+ (int)System.currentTimeMillis(), // arbitrary xid
+ Role.MASTER,
+ System.nanoTime() // arbitrary cookie
+ );
+ sw.setAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE, true);
+ sw.pendingRoleRequests.add(pending);
+ replay(sw.channel);
+ sw.deliverRoleReply(pending.xid, pending.role);
+ verify(sw.channel);
+ assertEquals(true, sw.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE));
+ assertEquals(pending.role, sw.role);
+ assertEquals(0, sw.pendingRoleRequests.size());
+ }
+
+ @Test
+ public void testDeliverRoleReplyNonePending() {
+ // nothing pending
+ expect(sw.channel.close()).andReturn(null);
+ replay(sw.channel);
+ sw.deliverRoleReply(1, Role.MASTER);
+ verify(sw.channel);
+ assertEquals(0, sw.pendingRoleRequests.size());
+ }
+
+ @Test
+ public void testDeliverRoleReplyWrongXid() {
+ // wrong xid received
+ PendingRoleRequestEntry pending = new PendingRoleRequestEntry(
+ (int)System.currentTimeMillis(), // arbitrary xid
+ Role.MASTER,
+ System.nanoTime() // arbitrary cookie
+ );
+ sw.pendingRoleRequests.add(pending);
+ expect(sw.channel.close()).andReturn(null);
+ replay(sw.channel);
+ sw.deliverRoleReply(pending.xid+1, pending.role);
+ verify(sw.channel);
+ assertEquals(null, sw.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE));
+ assertEquals(0, sw.pendingRoleRequests.size());
+ }
+
+ @Test
+ public void testDeliverRoleReplyWrongRole() {
+ // correct xid but incorrect role received
+ PendingRoleRequestEntry pending = new PendingRoleRequestEntry(
+ (int)System.currentTimeMillis(), // arbitrary xid
+ Role.MASTER,
+ System.nanoTime() // arbitrary cookie
+ );
+ sw.pendingRoleRequests.add(pending);
+ expect(sw.channel.close()).andReturn(null);
+ replay(sw.channel);
+ sw.deliverRoleReply(pending.xid, Role.SLAVE);
+ verify(sw.channel);
+ assertEquals(null, sw.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE));
+ assertEquals(0, sw.pendingRoleRequests.size());
+ }
+
+ @Test
+ public void testCheckFirstPendingRoleRequestXid() {
+ PendingRoleRequestEntry pending = new PendingRoleRequestEntry(
+ 54321, Role.MASTER, 232323);
+ replay(sw.channel); // we don't expect any invocations
+ sw.pendingRoleRequests.add(pending);
+ assertEquals(true, sw.checkFirstPendingRoleRequestXid(54321));
+ assertEquals(false, sw.checkFirstPendingRoleRequestXid(0));
+ sw.pendingRoleRequests.clear();
+ assertEquals(false, sw.checkFirstPendingRoleRequestXid(54321));
+ verify(sw.channel);
+ }
+
+ @Test
+ public void testCheckFirstPendingRoleRequestCookie() {
+ PendingRoleRequestEntry pending = new PendingRoleRequestEntry(
+ 54321, Role.MASTER, 232323);
+ replay(sw.channel); // we don't expect any invocations
+ sw.pendingRoleRequests.add(pending);
+ assertEquals(true, sw.checkFirstPendingRoleRequestCookie(232323));
+ assertEquals(false, sw.checkFirstPendingRoleRequestCookie(0));
+ sw.pendingRoleRequests.clear();
+ assertEquals(false, sw.checkFirstPendingRoleRequestCookie(232323));
+ verify(sw.channel);
+ }
+
+ @Test
+ public void testDeliverRoleRequestNotSupported () {
+ // normal case. xid is pending
+ PendingRoleRequestEntry pending = new PendingRoleRequestEntry(
+ (int)System.currentTimeMillis(), // arbitrary xid
+ Role.MASTER,
+ System.nanoTime() // arbitrary cookie
+ );
+ sw.role = Role.SLAVE;
+ sw.pendingRoleRequests.add(pending);
+ replay(sw.channel);
+ sw.deliverRoleRequestNotSupported(pending.xid);
+ verify(sw.channel);
+ assertEquals(false, sw.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE));
+ assertEquals(null, sw.role);
+ assertEquals(0, sw.pendingRoleRequests.size());
+ }
+
+ @Test
+ public void testDeliverRoleRequestNotSupportedNonePending() {
+ // nothing pending
+ sw.role = Role.SLAVE;
+ expect(sw.channel.close()).andReturn(null);
+ replay(sw.channel);
+ sw.deliverRoleRequestNotSupported(1);
+ verify(sw.channel);
+ assertEquals(null, sw.role);
+ assertEquals(0, sw.pendingRoleRequests.size());
+ }
+
+ @Test
+ public void testDeliverRoleRequestNotSupportedWrongXid() {
+ // wrong xid received
+ PendingRoleRequestEntry pending = new PendingRoleRequestEntry(
+ (int)System.currentTimeMillis(), // arbitrary xid
+ Role.MASTER,
+ System.nanoTime() // arbitrary cookie
+ );
+ sw.role = Role.SLAVE;
+ sw.pendingRoleRequests.add(pending);
+ expect(sw.channel.close()).andReturn(null);
+ replay(sw.channel);
+ sw.deliverRoleRequestNotSupported(pending.xid+1);
+ verify(sw.channel);
+ assertEquals(null, sw.role);
+ assertEquals(0, sw.pendingRoleRequests.size());
+ }
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/core/internal/RoleChangerTest.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/core/internal/RoleChangerTest.java
new file mode 100644
index 0000000..991afff
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/core/internal/RoleChangerTest.java
@@ -0,0 +1,233 @@
+package net.floodlightcontroller.core.internal;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.LinkedList;
+import net.floodlightcontroller.core.IOFSwitch;
+import net.floodlightcontroller.core.IFloodlightProviderService.Role;
+import net.floodlightcontroller.core.internal.RoleChanger.RoleChangeTask;
+
+import org.easymock.EasyMock;
+import org.jboss.netty.channel.Channel;
+import org.junit.Before;
+import org.junit.Test;
+
+public class RoleChangerTest {
+ public RoleChanger roleChanger;
+
+ @Before
+ public void setUp() throws Exception {
+ roleChanger = new RoleChanger();
+ }
+
+ /**
+ * Send a role request for SLAVE to a switch that doesn't support it.
+ * The connection should be closed.
+ */
+ @Test
+ public void testSendRoleRequestSlaveNotSupported() {
+ LinkedList<OFSwitchImpl> switches = new LinkedList<OFSwitchImpl>();
+
+ // a switch that doesn't support role requests
+ OFSwitchImpl sw1 = EasyMock.createMock(OFSwitchImpl.class);
+ Channel channel1 = createMock(Channel.class);
+ expect(sw1.getChannel()).andReturn(channel1);
+ // No support for NX_ROLE
+ expect(sw1.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE))
+ .andReturn(false);
+ expect(channel1.close()).andReturn(null);
+ switches.add(sw1);
+
+ replay(sw1, channel1);
+ roleChanger.sendRoleRequest(switches, Role.SLAVE, 123456);
+ verify(sw1, channel1);
+
+ // sendRoleRequest needs to remove the switch from the list since
+ // it closed its connection
+ assertTrue(switches.isEmpty());
+ }
+
+ /**
+ * Send a role request for MASTER to a switch that doesn't support it.
+ * The connection should be closed.
+ */
+ @Test
+ public void testSendRoleRequestMasterNotSupported() {
+ LinkedList<OFSwitchImpl> switches = new LinkedList<OFSwitchImpl>();
+
+ // a switch that doesn't support role requests
+ OFSwitchImpl sw1 = EasyMock.createMock(OFSwitchImpl.class);
+ // No support for NX_ROLE
+ expect(sw1.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE))
+ .andReturn(false);
+ switches.add(sw1);
+
+ replay(sw1);
+ roleChanger.sendRoleRequest(switches, Role.MASTER, 123456);
+ verify(sw1);
+
+ assertEquals(1, switches.size());
+ }
+
+ /**
+ * Send a role request a switch that supports it and one that
+ * hasn't had a role request send to it yet
+ */
+ @Test
+ public void testSendRoleRequestErrorHandling () throws Exception {
+ LinkedList<OFSwitchImpl> switches = new LinkedList<OFSwitchImpl>();
+
+ // a switch that supports role requests
+ OFSwitchImpl sw1 = EasyMock.createMock(OFSwitchImpl.class);
+ // No support for NX_ROLE
+ expect(sw1.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE))
+ .andReturn(true);
+ expect(sw1.sendNxRoleRequest(Role.MASTER, 123456))
+ .andThrow(new IOException()).once();
+ Channel channel1 = createMock(Channel.class);
+ expect(sw1.getChannel()).andReturn(channel1);
+ expect(channel1.close()).andReturn(null);
+ switches.add(sw1);
+
+ replay(sw1);
+ roleChanger.sendRoleRequest(switches, Role.MASTER, 123456);
+ verify(sw1);
+
+ assertTrue(switches.isEmpty());
+ }
+
+ /**
+ * Check error handling
+ * hasn't had a role request send to it yet
+ */
+ @Test
+ public void testSendRoleRequestSupported() throws Exception {
+ LinkedList<OFSwitchImpl> switches = new LinkedList<OFSwitchImpl>();
+
+ // a switch that supports role requests
+ OFSwitchImpl sw1 = EasyMock.createMock(OFSwitchImpl.class);
+ // No support for NX_ROLE
+ expect(sw1.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE))
+ .andReturn(true);
+ expect(sw1.sendNxRoleRequest(Role.MASTER, 123456)).andReturn(1).once();
+ switches.add(sw1);
+
+ // a switch for which we don't have SUPPORTS_NX_ROLE yet
+ OFSwitchImpl sw2 = EasyMock.createMock(OFSwitchImpl.class);
+ // No support for NX_ROLE
+ expect(sw2.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE))
+ .andReturn(null);
+ expect(sw2.sendNxRoleRequest(Role.MASTER, 123456)).andReturn(1).once();
+ switches.add(sw2);
+
+
+ replay(sw1, sw2);
+ roleChanger.sendRoleRequest(switches, Role.MASTER, 123456);
+ verify(sw1, sw2);
+
+ assertEquals(2, switches.size());
+ }
+
+ @Test
+ public void testVerifyRoleReplyReceived() {
+ LinkedList<OFSwitchImpl> switches = new LinkedList<OFSwitchImpl>();
+
+ // Add a switch that has received a role reply
+ OFSwitchImpl sw1 = EasyMock.createMock(OFSwitchImpl.class);
+ expect(sw1.checkFirstPendingRoleRequestCookie(123456))
+ .andReturn(false).once();
+ switches.add(sw1);
+
+ // Add a switch that has not yet received a role reply
+ OFSwitchImpl sw2 = EasyMock.createMock(OFSwitchImpl.class);
+ expect(sw2.checkFirstPendingRoleRequestCookie(123456))
+ .andReturn(true).once();
+ Channel channel2 = createMock(Channel.class);
+ expect(sw2.getChannel()).andReturn(channel2);
+ expect(channel2.close()).andReturn(null);
+ switches.add(sw2);
+
+
+ replay(sw1, sw2);
+ roleChanger.verifyRoleReplyReceived(switches, 123456);
+ verify(sw1, sw2);
+
+ assertEquals(2, switches.size());
+ }
+
+ @Test
+ public void testRoleChangeTask() {
+ @SuppressWarnings("unchecked")
+ Collection<OFSwitchImpl> switches =
+ EasyMock.createMock(Collection.class);
+ long now = System.nanoTime();
+ long dt1 = 10 * 1000*1000*1000L;
+ long dt2 = 20 * 1000*1000*1000L;
+ long dt3 = 15 * 1000*1000*1000L;
+ RoleChangeTask t1 = new RoleChangeTask(switches, null, now+dt1);
+ RoleChangeTask t2 = new RoleChangeTask(switches, null, now+dt2);
+ RoleChangeTask t3 = new RoleChangeTask(switches, null, now+dt3);
+
+ // FIXME: cannot test comparison against self. grrr
+ //assertTrue( t1.compareTo(t1) <= 0 );
+ assertTrue( t1.compareTo(t2) < 0 );
+ assertTrue( t1.compareTo(t3) < 0 );
+
+ assertTrue( t2.compareTo(t1) > 0 );
+ //assertTrue( t2.compareTo(t2) <= 0 );
+ assertTrue( t2.compareTo(t3) > 0 );
+ }
+
+ @Test
+ public void testSubmitRequest() throws Exception {
+ LinkedList<OFSwitchImpl> switches = new LinkedList<OFSwitchImpl>();
+ roleChanger.timeout = 500*1000*1000; // 500 ms
+
+ // a switch that supports role requests
+ OFSwitchImpl sw1 = EasyMock.createStrictMock(OFSwitchImpl.class);
+ // No support for NX_ROLE
+ expect(sw1.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE))
+ .andReturn(true);
+ expect(sw1.sendNxRoleRequest(EasyMock.same(Role.MASTER), EasyMock.anyLong()))
+ .andReturn(1);
+ expect(sw1.getAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE))
+ .andReturn(true);
+ expect(sw1.sendNxRoleRequest(EasyMock.same(Role.SLAVE), EasyMock.anyLong()))
+ .andReturn(1);
+ // The following calls happen for timeout handling:
+ expect(sw1.checkFirstPendingRoleRequestCookie(EasyMock.anyLong()))
+ .andReturn(false);
+ expect(sw1.checkFirstPendingRoleRequestCookie(EasyMock.anyLong()))
+ .andReturn(false);
+ switches.add(sw1);
+
+
+ replay(sw1);
+ roleChanger.submitRequest(switches, Role.MASTER);
+ roleChanger.submitRequest(switches, Role.SLAVE);
+ // Wait until role request has been sent.
+ // TODO: need to get rid of this sleep somehow
+ Thread.sleep(100);
+ // Now there should be exactly one timeout task pending
+ assertEquals(2, roleChanger.pendingTasks.size());
+ // Make sure it's indeed a timeout task
+ assertSame(RoleChanger.RoleChangeTask.Type.TIMEOUT,
+ roleChanger.pendingTasks.peek().type);
+ // Check that RoleChanger indeed made a copy of switches collection
+ assertNotSame(switches, roleChanger.pendingTasks.peek().switches);
+
+ // Wait until the timeout triggers
+ // TODO: get rid of this sleep too.
+ Thread.sleep(500);
+ assertEquals(0, roleChanger.pendingTasks.size());
+ verify(sw1);
+
+ }
+
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/core/module/FloodlightTestModuleLoader.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/core/module/FloodlightTestModuleLoader.java
new file mode 100644
index 0000000..2ba838e
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/core/module/FloodlightTestModuleLoader.java
@@ -0,0 +1,182 @@
+package net.floodlightcontroller.core.module;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+
+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;
+
+ 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);
+ }
+
+ 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) {
+ addDefaultModules(modules);
+ Collection<String> modulesAsString = new ArrayList<String>();
+ for (Class<? extends IFloodlightModule> m : modules) {
+ modulesAsString.add(m.getCanonicalName());
+ }
+
+ try {
+ fmc = loadModulesFromList(modulesAsString, null, mockedServices);
+ } catch (FloodlightModuleException e) {
+ log.error(e.getMessage());
+ }
+ }
+
+ /**
+ * 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/ext/floodlight/src/test/java/net/floodlightcontroller/core/test/MockFloodlightProvider.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/core/test/MockFloodlightProvider.java
new file mode 100644
index 0000000..e83fc58
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/core/test/MockFloodlightProvider.java
@@ -0,0 +1,350 @@
+/**
+* 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.test;
+
+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.Map;
+import java.util.Map.Entry;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import net.floodlightcontroller.core.FloodlightContext;
+import net.floodlightcontroller.core.IFloodlightProviderService;
+import net.floodlightcontroller.core.IHAListener;
+import net.floodlightcontroller.core.IInfoProvider;
+import net.floodlightcontroller.core.IOFMessageListener;
+import net.floodlightcontroller.core.IOFSwitch;
+import net.floodlightcontroller.core.IOFSwitchFilter;
+import net.floodlightcontroller.core.IOFSwitchListener;
+import net.floodlightcontroller.core.IListener.Command;
+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.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;
+
+/**
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class MockFloodlightProvider implements IFloodlightModule, IFloodlightProviderService {
+ protected static Logger log = LoggerFactory.getLogger(MockFloodlightProvider.class);
+ protected ConcurrentMap<OFType, ListenerDispatcher<OFType,IOFMessageListener>> listeners;
+ protected List<IOFSwitchListener> switchListeners;
+ protected List<IHAListener> haListeners;
+ protected Map<Long, IOFSwitch> switches;
+ protected BasicFactory factory;
+
+ /**
+ *
+ */
+ public MockFloodlightProvider() {
+ listeners = new ConcurrentHashMap<OFType, ListenerDispatcher<OFType,
+ IOFMessageListener>>();
+ switches = new ConcurrentHashMap<Long, IOFSwitch>();
+ switchListeners = new CopyOnWriteArrayList<IOFSwitchListener>();
+ haListeners = new CopyOnWriteArrayList<IHAListener>();
+ factory = new BasicFactory();
+ }
+
+ @Override
+ public synchronized void addOFMessageListener(OFType type,
+ IOFMessageListener listener) {
+ ListenerDispatcher<OFType, IOFMessageListener> ldd =
+ listeners.get(type);
+ if (ldd == null) {
+ ldd = new ListenerDispatcher<OFType, IOFMessageListener>();
+ listeners.put(type, ldd);
+ }
+ ldd.addListener(type, listener);
+ }
+
+ @Override
+ public synchronized void removeOFMessageListener(OFType type,
+ IOFMessageListener listener) {
+ ListenerDispatcher<OFType, IOFMessageListener> ldd =
+ listeners.get(type);
+ if (ldd != null) {
+ ldd.removeListener(listener);
+ }
+ }
+
+ /**
+ * @return the listeners
+ */
+ 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);
+ }
+
+ public void clearListeners() {
+ this.listeners.clear();
+ }
+
+ @Override
+ public Map<Long, IOFSwitch> getSwitches() {
+ return this.switches;
+ }
+
+ 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());
+ }
+
+ public void dispatchMessage(IOFSwitch sw, OFMessage msg, FloodlightContext bc) {
+ List<IOFMessageListener> theListeners = listeners.get(msg.getType()).getOrderedListeners();
+ if (theListeners != null) {
+ Command result = Command.CONTINUE;
+ Iterator<IOFMessageListener> it = theListeners.iterator();
+ if (OFType.PACKET_IN.equals(msg.getType())) {
+ OFPacketIn pi = (OFPacketIn)msg;
+ Ethernet eth = new Ethernet();
+ eth.deserialize(pi.getPacketData(), 0, pi.getPacketData().length);
+ IFloodlightProviderService.bcStore.put(bc,
+ IFloodlightProviderService.CONTEXT_PI_PAYLOAD,
+ eth);
+ }
+ while (it.hasNext() && !Command.STOP.equals(result)) {
+ result = it.next().receive(sw, msg, bc);
+ }
+ }
+ }
+
+ public void handleOutgoingMessage(IOFSwitch sw, OFMessage m, FloodlightContext bc) {
+ List<IOFMessageListener> msgListeners = null;
+ if (listeners.containsKey(m.getType())) {
+ msgListeners = listeners.get(m.getType()).getOrderedListeners();
+ }
+
+ if (msgListeners != null) {
+ for (IOFMessageListener listener : msgListeners) {
+ if (listener instanceof IOFSwitchFilter) {
+ if (!((IOFSwitchFilter)listener).isInterested(sw)) {
+ continue;
+ }
+ }
+ if (Command.STOP.equals(listener.receive(sw, m, bc))) {
+ break;
+ }
+ }
+ }
+ }
+
+ public void handleOutgoingMessages(IOFSwitch sw, List<OFMessage> msglist, FloodlightContext bc) {
+ for (OFMessage m:msglist) {
+ handleOutgoingMessage(sw, m, bc);
+ }
+ }
+
+ /**
+ * @return the switchListeners
+ */
+ public List<IOFSwitchListener> getSwitchListeners() {
+ return switchListeners;
+ }
+
+ 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();
+ }
+
+ @Override
+ public Collection<Class<? extends IFloodlightService>> getModuleServices() {
+ Collection<Class<? extends IFloodlightService>> services =
+ new ArrayList<Class<? extends IFloodlightService>>(1);
+ services.add(IFloodlightProviderService.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(IFloodlightProviderService.class, this);
+ return m;
+ }
+
+ @Override
+ public Collection<Class<? extends IFloodlightService>>
+ getModuleDependencies() {
+ return null;
+ }
+
+ @Override
+ public void init(FloodlightModuleContext context)
+ throws FloodlightModuleException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void startUp(FloodlightModuleContext context) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void addInfoProvider(String type, IInfoProvider provider) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void removeInfoProvider(String type, IInfoProvider provider) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public Map<String, Object> getControllerInfo(String type) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public void addHAListener(IHAListener listener) {
+ haListeners.add(listener);
+ }
+
+ @Override
+ public void removeHAListener(IHAListener listener) {
+ haListeners.remove(listener);
+ }
+
+ @Override
+ public Role getRole() {
+ return null;
+ }
+
+ @Override
+ public void setRole(Role role) {
+
+ }
+
+ /**
+ * Dispatches a new role change notification
+ * @param oldRole
+ * @param newRole
+ */
+ public void dispatchRoleChanged(Role oldRole, Role newRole) {
+ for (IHAListener rl : haListeners) {
+ rl.roleChanged(oldRole, newRole);
+ }
+ }
+
+ @Override
+ public String getControllerId() {
+ return "localhost";
+ }
+
+ @Override
+ public Map<String, String> getControllerNodeIPs() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public long getSystemStartTime() {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+ private void logListeners() {
+ for (Map.Entry<OFType,
+ ListenerDispatcher<OFType,
+ IOFMessageListener>> entry
+ : listeners.entrySet()) {
+
+ OFType type = entry.getKey();
+ ListenerDispatcher<OFType, IOFMessageListener> ldd =
+ entry.getValue();
+
+ StringBuffer sb = new StringBuffer();
+ sb.append("OFListeners for ");
+ sb.append(type);
+ sb.append(": ");
+ for (IOFMessageListener l : ldd.getOrderedListeners()) {
+ sb.append(l.getName());
+ sb.append(",");
+ }
+ log.debug(sb.toString());
+ }
+ }
+
+ @Override
+ public void setAlwaysClearFlowsOnSwAdd(boolean value) {
+ // TODO Auto-generated method stub
+
+ }
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/core/test/MockScheduledExecutor.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/core/test/MockScheduledExecutor.java
new file mode 100644
index 0000000..733ff1a
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/core/test/MockScheduledExecutor.java
@@ -0,0 +1,252 @@
+/**
+* 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.test;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.Delayed;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+public class MockScheduledExecutor implements ScheduledExecutorService {
+ ScheduledExecutorService ses = null;
+
+ public static class MockFuture<T> implements Future<T>,ScheduledFuture<T>{
+ T result;
+ ExecutionException e;
+
+ /**
+ * @param result
+ */
+ public MockFuture(T result) {
+ super();
+ this.result = result;
+ }
+
+ /**
+ * @param result
+ */
+ public MockFuture(ExecutionException e) {
+ super();
+ this.e = e;
+ }
+
+ @Override
+ public boolean cancel(boolean mayInterruptIfRunning) {
+ return false;
+ }
+
+ @Override
+ public T get() throws InterruptedException, ExecutionException {
+ if (e != null) throw e;
+ return result;
+ }
+
+ @Override
+ public T get(long timeout, TimeUnit unit) throws InterruptedException,
+ ExecutionException, TimeoutException {
+ if (e != null) throw e;
+ return result;
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return false;
+ }
+
+ @Override
+ public boolean isDone() {
+ return true;
+ }
+
+ @Override
+ public long getDelay(TimeUnit arg0) {
+ return 0;
+ }
+
+ @Override
+ public int compareTo(Delayed arg0) {
+ return 0;
+ }
+ }
+
+ @Override
+ public boolean awaitTermination(long arg0, TimeUnit arg1)
+ throws InterruptedException {
+ return false;
+ }
+
+ @Override
+ public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> arg0)
+ throws InterruptedException {
+ List<Future<T>> rlist = new ArrayList<Future<T>>();
+ for (Callable<T> arg : arg0) {
+ try {
+ rlist.add(new MockFuture<T>(arg.call()));
+ } catch (Exception e) {
+ rlist.add(new MockFuture<T>(new ExecutionException(e)));
+ }
+ }
+ return rlist;
+ }
+
+ @Override
+ public <T> List<Future<T>> invokeAll(
+ Collection<? extends Callable<T>> arg0, long arg1, TimeUnit arg2)
+ throws InterruptedException {
+ return this.invokeAll(arg0);
+ }
+
+ @Override
+ public <T> T invokeAny(Collection<? extends Callable<T>> arg0)
+ throws InterruptedException, ExecutionException {
+ for (Callable<T> arg : arg0) {
+ try {
+ return arg.call();
+ } catch (Exception e) {
+
+ }
+ }
+ throw new ExecutionException(new Exception("no task completed successfully"));
+ }
+
+ @Override
+ public <T> T invokeAny(Collection<? extends Callable<T>> arg0, long arg1,
+ TimeUnit arg2) throws InterruptedException, ExecutionException,
+ TimeoutException {
+ return invokeAny(arg0);
+ }
+
+ @Override
+ public boolean isShutdown() {
+ if (ses != null)
+ return ses.isShutdown();
+
+ return false;
+ }
+
+ @Override
+ public boolean isTerminated() {
+ if (ses != null)
+ return ses.isTerminated();
+
+ return false;
+ }
+
+ @Override
+ public void shutdown() {
+ if (ses != null)
+ ses.shutdown();
+ }
+
+ @Override
+ public List<Runnable> shutdownNow() {
+ if (ses != null)
+ return ses.shutdownNow();
+ return null;
+ }
+
+ @Override
+ public <T> Future<T> submit(Callable<T> arg0) {
+ try {
+ return new MockFuture<T>(arg0.call());
+ } catch (Exception e) {
+ return new MockFuture<T>(new ExecutionException(e));
+ }
+ }
+
+ @Override
+ public Future<?> submit(Runnable arg0) {
+ try {
+ arg0.run();
+ return new MockFuture<Object>(null);
+ } catch (Exception e) {
+ return new MockFuture<Object>(new ExecutionException(e));
+ }
+ }
+
+ @Override
+ public <T> Future<T> submit(Runnable arg0, T arg1) {
+ try {
+ arg0.run();
+ return new MockFuture<T>((T)null);
+ } catch (Exception e) {
+ return new MockFuture<T>(new ExecutionException(e));
+ }
+ }
+
+ @Override
+ public void execute(Runnable arg0) {
+ arg0.run();
+ }
+
+ @Override
+ public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) {
+ if (ses == null)
+ ses = Executors.newScheduledThreadPool(1);
+ try {
+ return ses.schedule(command, delay, unit);
+ } catch (Exception e) {
+ return new MockFuture<Object>(new ExecutionException(e));
+ }
+ }
+
+ @Override
+ public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay,
+ TimeUnit unit) {
+ if (ses == null)
+ ses = Executors.newScheduledThreadPool(1);
+ try {
+ return ses.schedule(callable, delay, unit);
+ } catch (Exception e) {
+ return new MockFuture<V>(new ExecutionException(e));
+ }
+ }
+
+ @Override
+ public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay,
+ long period, TimeUnit unit) {
+ if (ses == null)
+ ses = Executors.newScheduledThreadPool(1);
+ try {
+ return ses.scheduleAtFixedRate(command, initialDelay, period, unit);
+ } catch (Exception e) {
+ return new MockFuture<Object>(new ExecutionException(e));
+ }
+ }
+
+ @Override
+ public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay,
+ long delay, TimeUnit unit) {
+ if (ses == null)
+ ses = Executors.newScheduledThreadPool(1);
+ try {
+ return ses.scheduleWithFixedDelay(command, initialDelay, delay, unit);
+ } catch (Exception e) {
+ return new MockFuture<Object>(new ExecutionException(e));
+ }
+ }
+
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/core/test/MockThreadPoolService.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/core/test/MockThreadPoolService.java
new file mode 100644
index 0000000..67bee30
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/core/test/MockThreadPoolService.java
@@ -0,0 +1,67 @@
+package net.floodlightcontroller.core.test;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ScheduledExecutorService;
+
+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.threadpool.IThreadPoolService;
+
+public class MockThreadPoolService implements IFloodlightModule, IThreadPoolService {
+
+ protected ScheduledExecutorService mockExecutor = new MockScheduledExecutor();
+
+ /**
+ * Return a mock executor that will simply execute each task
+ * synchronously once.
+ */
+ @Override
+ public ScheduledExecutorService getScheduledExecutor() {
+ return mockExecutor;
+ }
+
+ // IFloodlightModule
+
+ @Override
+ public Collection<Class<? extends IFloodlightService>> getModuleServices() {
+ Collection<Class<? extends IFloodlightService>> l =
+ new ArrayList<Class<? extends IFloodlightService>>();
+ l.add(IThreadPoolService.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(IThreadPoolService.class, this);
+ // We are the class that implements the service
+ return m;
+ }
+
+ @Override
+ public Collection<Class<? extends IFloodlightService>>
+ getModuleDependencies() {
+ // No dependencies
+ return null;
+ }
+
+ @Override
+ public void init(FloodlightModuleContext context)
+ throws FloodlightModuleException {
+ }
+
+ @Override
+ public void startUp(FloodlightModuleContext context) {
+ // no-op
+ }
+
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/core/test/PacketFactory.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/core/test/PacketFactory.java
new file mode 100644
index 0000000..4d9ca32
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/core/test/PacketFactory.java
@@ -0,0 +1,147 @@
+package net.floodlightcontroller.core.test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+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.
+ * This is used in testing.
+ * @author alexreimers
+ *
+ */
+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 = new BasicFactory();
+
+ /**
+ * 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) {
+ byte[] serializedPacket = DhcpDiscoveryRequestEthernet(hostMac).serialize();
+ return (((OFPacketIn)OFMessageFactory
+ .getMessage(OFType.PACKET_IN))
+ .setBufferId(OFPacketOut.BUFFER_ID_NONE)
+ .setInPort((short) 1)
+ .setPacketData(serializedPacket)
+ .setReason(OFPacketInReason.NO_MATCH)
+ .setTotalLength((short)serializedPacket.length));
+ }
+
+ /**
+ * 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) {
+ List<DHCPOption> optionList = new ArrayList<DHCPOption>();
+
+ byte[] requestValue = new byte[4];
+ requestValue[0] = requestValue[1] = requestValue[2] = requestValue[3] = 0;
+ 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 =
+ 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 =
+ 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,
+ clientIdValue, 1, 6);
+ DHCPOption clientIdOption =
+ new DHCPOption()
+ .setCode(DHCP.DHCPOptionCode.OptionCode_ClientID.
+ getValue())
+ .setLength((byte)7)
+ .setData(clientIdValue);
+
+ 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())
+ .setDestinationMACAddress(broadcastMac)
+ .setEtherType(Ethernet.TYPE_IPv4)
+ .setPayload(
+ new IPv4()
+ .setVersion((byte)4)
+ .setDiffServ((byte)0)
+ .setIdentification((short)100)
+ .setFlags((byte)0)
+ .setFragmentOffset((short)0)
+ .setTtl((byte)250)
+ .setProtocol(IPv4.PROTOCOL_UDP)
+ .setChecksum((short)0)
+ .setSourceAddress(0)
+ .setDestinationAddress(broadcastIp)
+ .setPayload(
+ new UDP()
+ .setSourcePort(UDP.DHCP_CLIENT_PORT)
+ .setDestinationPort(UDP.DHCP_SERVER_PORT)
+ .setChecksum((short)0)
+ .setPayload(
+ new DHCP()
+ .setOpCode(DHCP.OPCODE_REQUEST)
+ .setHardwareType(DHCP.HWTYPE_ETHERNET)
+ .setHardwareAddressLength((byte)6)
+ .setHops((byte)0)
+ .setTransactionId(0x00003d1d)
+ .setSeconds((short)0)
+ .setFlags((short)0)
+ .setClientIPAddress(0)
+ .setYourIPAddress(0)
+ .setServerIPAddress(0)
+ .setGatewayIPAddress(0)
+ .setClientHardwareAddress(hostMac.toBytes())
+ .setOptions(optionList))));
+
+ return requestPacket;
+ }
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/core/util/AppCookieTest.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/core/util/AppCookieTest.java
new file mode 100644
index 0000000..2c77d6a
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/core/util/AppCookieTest.java
@@ -0,0 +1,38 @@
+/**
+* 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.util;
+
+import junit.framework.TestCase;
+import net.floodlightcontroller.test.FloodlightTestCase;
+
+
+public class AppCookieTest extends FloodlightTestCase {
+ public void testAppCookie(){
+ int appID = 12;
+ int user = 12345;
+ long cookie = AppCookie.makeCookie(appID, user);
+ TestCase.assertEquals(appID, AppCookie.extractApp(cookie));
+ TestCase.assertEquals(user, AppCookie.extractUser(cookie));
+
+ // now ensure that we don't exceed our size
+ cookie = AppCookie.makeCookie(appID + 0x10000, user);
+ TestCase.assertEquals(appID, AppCookie.extractApp(cookie));
+ TestCase.assertEquals(user, AppCookie.extractUser(cookie));
+
+ }
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/core/util/MessageDispatcherTest.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/core/util/MessageDispatcherTest.java
new file mode 100644
index 0000000..0829aa4
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/core/util/MessageDispatcherTest.java
@@ -0,0 +1,153 @@
+/**
+* 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.util;
+
+import static org.easymock.EasyMock.createNiceMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+import org.junit.Test;
+import org.openflow.protocol.OFType;
+
+import net.floodlightcontroller.core.IOFMessageListener;
+import net.floodlightcontroller.test.FloodlightTestCase;
+
+public class MessageDispatcherTest extends FloodlightTestCase {
+
+ IOFMessageListener createLMock(String name) {
+ IOFMessageListener mock = createNiceMock(IOFMessageListener.class);
+ expect(mock.getName()).andReturn(name).anyTimes();
+ return mock;
+ }
+
+ void addPrereqs(IOFMessageListener mock, String... deps) {
+ for (String dep : deps) {
+ expect(mock.isCallbackOrderingPrereq(OFType.PACKET_IN, dep)).andReturn(true).anyTimes();
+ }
+ }
+
+ void testOrdering(ArrayList<IOFMessageListener> inputListeners) {
+ ListenerDispatcher<OFType, IOFMessageListener> ld =
+ new ListenerDispatcher<OFType, IOFMessageListener>();
+
+ for (IOFMessageListener l : inputListeners) {
+ ld.addListener(OFType.PACKET_IN, l);
+ }
+ for (IOFMessageListener l : inputListeners) {
+ verify(l);
+ }
+
+ List<IOFMessageListener> result = ld.getOrderedListeners();
+ System.out.print("Ordering: ");
+ for (IOFMessageListener l : result) {
+ System.out.print(l.getName());
+ System.out.print(",");
+ }
+ System.out.print("\n");
+
+ for (int ind_i = 0; ind_i < result.size(); ind_i++) {
+ IOFMessageListener i = result.get(ind_i);
+ for (int ind_j = ind_i+1; ind_j < result.size(); ind_j++) {
+ IOFMessageListener j = result.get(ind_j);
+
+ boolean orderwrong =
+ (i.isCallbackOrderingPrereq(OFType.PACKET_IN, j.getName()) ||
+ j.isCallbackOrderingPostreq(OFType.PACKET_IN, i.getName()));
+ assertFalse("Invalid order: " +
+ ind_i + " (" + i.getName() + ") " +
+ ind_j + " (" + j.getName() + ") ", orderwrong);
+ }
+ }
+ }
+
+ void randomTestOrdering(ArrayList<IOFMessageListener> mocks) {
+ Random rand = new Random(0);
+ ArrayList<IOFMessageListener> random =
+ new ArrayList<IOFMessageListener>();
+ random.addAll(mocks);
+ for (int i = 0; i < 20; i++) {
+ for (int j = 0; j < random.size(); j++) {
+ int ind = rand.nextInt(mocks.size()-1);
+ IOFMessageListener tmp = random.get(j);
+ random.set(j, random.get(ind));
+ random.set(ind, tmp);
+ }
+ testOrdering(random);
+ }
+ }
+
+ @Test
+ public void testCallbackOrderingSimple() throws Exception {
+ ArrayList<IOFMessageListener> mocks =
+ new ArrayList<IOFMessageListener>();
+ for (int i = 0; i < 10; i++) {
+ mocks.add(createLMock(""+i));
+ }
+ for (int i = 1; i < 10; i++) {
+ addPrereqs(mocks.get(i), ""+(i-1));
+ }
+ for (IOFMessageListener l : mocks) {
+ replay(l);
+ }
+ randomTestOrdering(mocks);
+ }
+
+ @Test
+ public void testCallbackOrderingPartial() throws Exception {
+ ArrayList<IOFMessageListener> mocks =
+ new ArrayList<IOFMessageListener>();
+ for (int i = 0; i < 10; i++) {
+ mocks.add(createLMock(""+i));
+ }
+ for (int i = 1; i < 5; i++) {
+ addPrereqs(mocks.get(i), ""+(i-1));
+ }
+ for (int i = 6; i < 10; i++) {
+ addPrereqs(mocks.get(i), ""+(i-1));
+ }
+ for (IOFMessageListener l : mocks) {
+ replay(l);
+ }
+ randomTestOrdering(mocks);
+ }
+
+
+ @Test
+ public void testCallbackOrderingPartial2() throws Exception {
+ ArrayList<IOFMessageListener> mocks =
+ new ArrayList<IOFMessageListener>();
+ for (int i = 0; i < 10; i++) {
+ mocks.add(createLMock(""+i));
+ }
+ for (int i = 2; i < 5; i++) {
+ addPrereqs(mocks.get(i), ""+(i-1));
+ }
+ for (int i = 6; i < 9; i++) {
+ addPrereqs(mocks.get(i), ""+(i-1));
+ }
+ for (IOFMessageListener l : mocks) {
+ replay(l);
+ }
+ randomTestOrdering(mocks);
+ }
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/core/util/SingletonTaskTest.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/core/util/SingletonTaskTest.java
new file mode 100644
index 0000000..010c651
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/core/util/SingletonTaskTest.java
@@ -0,0 +1,288 @@
+/**
+* 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.util;
+
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import net.floodlightcontroller.test.FloodlightTestCase;
+
+public class SingletonTaskTest extends FloodlightTestCase {
+
+ public int ran = 0;
+ public int finished = 0;
+ public long time = 0;
+
+ @Before
+ public void setup() {
+ ran = 0;
+ finished = 0;
+ time = 0;
+ }
+
+ @Test
+ public void testBasic() throws InterruptedException {
+ ScheduledExecutorService ses =
+ Executors.newSingleThreadScheduledExecutor();
+
+ SingletonTask st1 = new SingletonTask(ses, new Runnable() {
+ @Override
+ public void run() {
+ ran += 1;
+ }
+ });
+ st1.reschedule(0, null);
+ ses.shutdown();
+ ses.awaitTermination(5, TimeUnit.SECONDS);
+
+ assertEquals("Check that task ran", 1, ran);
+ }
+
+ @Test
+ public void testDelay() throws InterruptedException {
+ ScheduledExecutorService ses =
+ Executors.newSingleThreadScheduledExecutor();
+
+ SingletonTask st1 = new SingletonTask(ses, new Runnable() {
+ @Override
+ public void run() {
+ ran += 1;
+ time = System.nanoTime();
+ }
+ });
+ long start = System.nanoTime();
+ st1.reschedule(10, TimeUnit.MILLISECONDS);
+ assertFalse("Check that task hasn't run yet", ran > 0);
+
+ ses.shutdown();
+ 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
+ public void testReschedule() throws InterruptedException {
+ ScheduledExecutorService ses =
+ Executors.newSingleThreadScheduledExecutor();
+
+ final Object tc = this;
+ SingletonTask st1 = new SingletonTask(ses, new Runnable() {
+ @Override
+ public void run() {
+ synchronized (tc) {
+ ran += 1;
+ }
+ 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);
+ st1.reschedule(20, TimeUnit.MILLISECONDS);
+ Thread.sleep(5);
+ assertFalse("Check that task hasn't run yet", ran > 0);
+ st1.reschedule(20, TimeUnit.MILLISECONDS);
+ Thread.sleep(5);
+ assertFalse("Check that task hasn't run yet", ran > 0);
+ st1.reschedule(20, TimeUnit.MILLISECONDS);
+ Thread.sleep(5);
+ assertFalse("Check that task hasn't run yet", ran > 0);
+ st1.reschedule(20, TimeUnit.MILLISECONDS);
+ Thread.sleep(5);
+ assertFalse("Check that task hasn't run yet", ran > 0);
+ st1.reschedule(20, TimeUnit.MILLISECONDS);
+ Thread.sleep(5);
+ assertFalse("Check that task hasn't run yet", ran > 0);
+ st1.reschedule(20, TimeUnit.MILLISECONDS);
+ Thread.sleep(5);
+ assertFalse("Check that task hasn't run yet", ran > 0);
+ st1.reschedule(20, TimeUnit.MILLISECONDS);
+ Thread.sleep(5);
+ assertFalse("Check that task hasn't run yet", ran > 0);
+
+ ses.shutdown();
+ 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
+ public void testConcurrentAddDelay() throws InterruptedException {
+ ScheduledExecutorService ses =
+ Executors.newSingleThreadScheduledExecutor();
+
+ final Object tc = this;
+ SingletonTask st1 = new SingletonTask(ses, new Runnable() {
+ @Override
+ public void run() {
+ synchronized (tc) {
+ ran += 1;
+ }
+ try {
+ Thread.sleep(50);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ synchronized (tc) {
+ finished += 1;
+ time = System.nanoTime();
+ }
+ }
+ });
+
+ long start = System.nanoTime();
+ st1.reschedule(5, TimeUnit.MILLISECONDS);
+ Thread.sleep(20);
+ assertEquals("Check that task started", 1, ran);
+ assertEquals("Check that task not finished", 0, finished);
+ st1.reschedule(75, TimeUnit.MILLISECONDS);
+ assertTrue("Check task running state true", st1.context.taskRunning);
+ assertTrue("Check task should run state true", st1.context.taskShouldRun);
+ assertEquals("Check that task started", 1, ran);
+ assertEquals("Check that task not finished", 0, finished);
+
+ Thread.sleep(150);
+
+ assertTrue("Check task running state false", !st1.context.taskRunning);
+ assertTrue("Check task should run state false", !st1.context.taskShouldRun);
+ 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);
+ }
+
+ @Test
+ public void testConcurrentAddDelay2() throws InterruptedException {
+ ScheduledExecutorService ses =
+ Executors.newSingleThreadScheduledExecutor();
+
+ final Object tc = this;
+ SingletonTask st1 = new SingletonTask(ses, new Runnable() {
+ @Override
+ public void run() {
+ synchronized (tc) {
+ ran += 1;
+ }
+ try {
+ Thread.sleep(50);
+ } catch (InterruptedException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ synchronized (tc) {
+ finished += 1;
+ time = System.nanoTime();
+ }
+ }
+ });
+
+ long start = System.nanoTime();
+ st1.reschedule(5, TimeUnit.MILLISECONDS);
+ Thread.sleep(20);
+ assertEquals("Check that task started", 1, ran);
+ assertEquals("Check that task not finished", 0, finished);
+ st1.reschedule(25, TimeUnit.MILLISECONDS);
+ assertTrue("Check task running state true", st1.context.taskRunning);
+ assertTrue("Check task should run state true", st1.context.taskShouldRun);
+ assertEquals("Check that task started", 1, ran);
+ assertEquals("Check that task not finished", 0, finished);
+
+ Thread.sleep(150);
+
+ assertTrue("Check task running state false", !st1.context.taskRunning);
+ assertTrue("Check task should run state false", !st1.context.taskShouldRun);
+ 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);
+ }
+
+
+ @Test
+ public void testConcurrentAddNoDelay() throws InterruptedException {
+ ScheduledExecutorService ses =
+ Executors.newSingleThreadScheduledExecutor();
+
+ final Object tc = this;
+ SingletonTask st1 = new SingletonTask(ses, new Runnable() {
+ @Override
+ public void run() {
+ synchronized (tc) {
+ ran += 1;
+ }
+ try {
+ Thread.sleep(50);
+ } catch (InterruptedException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ synchronized (tc) {
+ finished += 1;
+ time = System.nanoTime();
+ }
+ }
+ });
+
+ long start = System.nanoTime();
+ st1.reschedule(0, null);
+ Thread.sleep(20);
+ assertEquals("Check that task started", 1, ran);
+ assertEquals("Check that task not finished", 0, finished);
+ st1.reschedule(0, null);
+ assertTrue("Check task running state true", st1.context.taskRunning);
+ assertTrue("Check task should run state true", st1.context.taskShouldRun);
+ assertEquals("Check that task started", 1, ran);
+ assertEquals("Check that task not finished", 0, finished);
+
+ Thread.sleep(150);
+
+ assertTrue("Check task running state false", !st1.context.taskRunning);
+ assertTrue("Check task should run state false", !st1.context.taskShouldRun);
+ 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/ext/floodlight/src/test/java/net/floodlightcontroller/devicemanager/internal/DeviceManagerImplTest.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/devicemanager/internal/DeviceManagerImplTest.java
new file mode 100644
index 0000000..e266d4a
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/devicemanager/internal/DeviceManagerImplTest.java
@@ -0,0 +1,1756 @@
+/**
+ * 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.devicemanager.internal;
+
+
+import static org.easymock.EasyMock.*;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Date;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import static org.easymock.EasyMock.expectLastCall;
+import net.floodlightcontroller.core.IFloodlightProviderService;
+import net.floodlightcontroller.core.IOFSwitch;
+import net.floodlightcontroller.core.module.FloodlightModuleContext;
+import net.floodlightcontroller.core.test.MockFloodlightProvider;
+import net.floodlightcontroller.core.test.MockThreadPoolService;
+import net.floodlightcontroller.devicemanager.IDeviceListener;
+import net.floodlightcontroller.devicemanager.IDevice;
+import net.floodlightcontroller.devicemanager.IEntityClass;
+import net.floodlightcontroller.devicemanager.IEntityClassifierService;
+import net.floodlightcontroller.devicemanager.SwitchPort;
+import net.floodlightcontroller.devicemanager.IDeviceService;
+import net.floodlightcontroller.devicemanager.SwitchPort.ErrorStatus;
+import net.floodlightcontroller.devicemanager.internal.DeviceManagerImpl.ClassState;
+import net.floodlightcontroller.devicemanager.test.MockEntityClassifier;
+import net.floodlightcontroller.devicemanager.test.MockEntityClassifierMac;
+import net.floodlightcontroller.devicemanager.test.MockFlexEntityClassifier;
+import net.floodlightcontroller.flowcache.FlowReconcileManager;
+import net.floodlightcontroller.flowcache.IFlowReconcileService;
+import net.floodlightcontroller.packet.ARP;
+import net.floodlightcontroller.packet.Ethernet;
+import net.floodlightcontroller.packet.IPacket;
+import net.floodlightcontroller.packet.IPv4;
+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.topology.ITopologyService;
+import static org.junit.Assert.*;
+
+import org.easymock.EasyMock;
+import org.junit.Before;
+import org.junit.Test;
+import org.openflow.protocol.OFPacketIn;
+import org.openflow.protocol.OFPhysicalPort;
+import org.openflow.protocol.OFType;
+import org.openflow.protocol.OFPacketIn.OFPacketInReason;
+import org.openflow.util.HexString;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class DeviceManagerImplTest extends FloodlightTestCase {
+
+ protected static Logger logger =
+ LoggerFactory.getLogger(DeviceManagerImplTest.class);
+
+ protected OFPacketIn packetIn_1, packetIn_2, packetIn_3;
+ protected IPacket testARPReplyPacket_1, testARPReplyPacket_2,
+ testARPReplyPacket_3;
+ protected IPacket testARPReqPacket_1, testARPReqPacket_2;
+ protected byte[] testARPReplyPacket_1_Srld, testARPReplyPacket_2_Srld;
+ private byte[] testARPReplyPacket_3_Serialized;
+ MockFloodlightProvider mockFloodlightProvider;
+ DeviceManagerImpl deviceManager;
+ MemoryStorageSource storageSource;
+ FlowReconcileManager flowReconcileMgr;
+
+ private IOFSwitch makeSwitchMock(long id) {
+ IOFSwitch mockSwitch = createMock(IOFSwitch.class);
+ expect(mockSwitch.getId()).andReturn(id).anyTimes();
+ expect(mockSwitch.getStringId()).
+ andReturn(HexString.toHexString(id, 6)).anyTimes();
+ expect(mockSwitch.getPort(anyShort())).
+ andReturn(new OFPhysicalPort()).anyTimes();
+ expect(mockSwitch.portEnabled(isA(OFPhysicalPort.class))).
+ andReturn(true).anyTimes();
+ return mockSwitch;
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+
+ FloodlightModuleContext fmc = new FloodlightModuleContext();
+ RestApiServer restApi = new RestApiServer();
+ MockThreadPoolService tp = new MockThreadPoolService();
+ ITopologyService topology = createMock(ITopologyService.class);
+ fmc.addService(IThreadPoolService.class, tp);
+ mockFloodlightProvider = getMockFloodlightProvider();
+ deviceManager = new DeviceManagerImpl();
+ flowReconcileMgr = new FlowReconcileManager();
+ DefaultEntityClassifier entityClassifier = new DefaultEntityClassifier();
+ fmc.addService(IDeviceService.class, deviceManager);
+ storageSource = new MemoryStorageSource();
+ fmc.addService(IStorageSourceService.class, storageSource);
+ fmc.addService(IFloodlightProviderService.class, mockFloodlightProvider);
+ fmc.addService(IRestApiService.class, restApi);
+ fmc.addService(IFlowReconcileService.class, flowReconcileMgr);
+ fmc.addService(IEntityClassifierService.class, entityClassifier);
+ fmc.addService(ITopologyService.class, topology);
+ tp.init(fmc);
+ restApi.init(fmc);
+ storageSource.init(fmc);
+ deviceManager.init(fmc);
+ flowReconcileMgr.init(fmc);
+ entityClassifier.init(fmc);
+ storageSource.startUp(fmc);
+ deviceManager.startUp(fmc);
+ flowReconcileMgr.startUp(fmc);
+ tp.startUp(fmc);
+ entityClassifier.startUp(fmc);
+
+ reset(topology);
+ topology.addListener(deviceManager);
+ 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);
+ mockFloodlightProvider.setSwitches(switches);
+
+ replay(mockSwitch1, mockSwitch5, mockSwitch10, mockSwitch50);
+
+ // Build our test packet
+ this.testARPReplyPacket_1 = new Ethernet()
+ .setSourceMACAddress("00:44:33:22:11:01")
+ .setDestinationMACAddress("00:11:22:33:44:55")
+ .setEtherType(Ethernet.TYPE_ARP)
+ .setVlanID((short)5)
+ .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:01"))
+ .setSenderProtocolAddress(IPv4.toIPv4AddressBytes("192.168.1.1"))
+ .setTargetHardwareAddress(Ethernet.toMACAddress("00:11:22:33:44:55"))
+ .setTargetProtocolAddress(IPv4.toIPv4AddressBytes("192.168.1.2")));
+ this.testARPReplyPacket_1_Srld = testARPReplyPacket_1.serialize();
+
+ // Another test packet with a different source IP
+ this.testARPReplyPacket_2 = new Ethernet()
+ .setSourceMACAddress("00:44:33:22:11:01")
+ .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:01"))
+ .setSenderProtocolAddress(IPv4.toIPv4AddressBytes("192.168.1.1"))
+ .setTargetHardwareAddress(Ethernet.toMACAddress("00:11:22:33:44:55"))
+ .setTargetProtocolAddress(IPv4.toIPv4AddressBytes("192.168.1.2")));
+ this.testARPReplyPacket_2_Srld = testARPReplyPacket_2.serialize();
+
+ this.testARPReplyPacket_3 = new Ethernet()
+ .setSourceMACAddress("00:44:33:22:11:01")
+ .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:01"))
+ .setSenderProtocolAddress(IPv4.toIPv4AddressBytes("192.168.1.3"))
+ .setTargetHardwareAddress(Ethernet.toMACAddress("00:11:22:33:44:55"))
+ .setTargetProtocolAddress(IPv4.toIPv4AddressBytes("192.168.1.2")));
+ this.testARPReplyPacket_3_Serialized = testARPReplyPacket_3.serialize();
+
+ // Build the PacketIn
+ this.packetIn_1 = ((OFPacketIn) mockFloodlightProvider.
+ getOFMessageFactory().getMessage(OFType.PACKET_IN))
+ .setBufferId(-1)
+ .setInPort((short) 1)
+ .setPacketData(this.testARPReplyPacket_1_Srld)
+ .setReason(OFPacketInReason.NO_MATCH)
+ .setTotalLength((short) this.testARPReplyPacket_1_Srld.length);
+
+ // Build the PacketIn
+ this.packetIn_2 = ((OFPacketIn) mockFloodlightProvider.
+ getOFMessageFactory().getMessage(OFType.PACKET_IN))
+ .setBufferId(-1)
+ .setInPort((short) 1)
+ .setPacketData(this.testARPReplyPacket_2_Srld)
+ .setReason(OFPacketInReason.NO_MATCH)
+ .setTotalLength((short) this.testARPReplyPacket_2_Srld.length);
+
+ // Build the PacketIn
+ this.packetIn_3 = ((OFPacketIn) mockFloodlightProvider.
+ getOFMessageFactory().getMessage(OFType.PACKET_IN))
+ .setBufferId(-1)
+ .setInPort((short) 1)
+ .setPacketData(this.testARPReplyPacket_3_Serialized)
+ .setReason(OFPacketInReason.NO_MATCH)
+ .setTotalLength((short) this.testARPReplyPacket_3_Serialized.length);
+ }
+
+
+
+
+
+ @Test
+ public void testLastSeen() throws Exception {
+ Calendar c = Calendar.getInstance();
+ Date d1 = c.getTime();
+ Entity entity1 = new Entity(1L, null, null, null, null, d1);
+ c.add(Calendar.SECOND, 1);
+ Entity entity2 = new Entity(1L, null, 1, null, null, c.getTime());
+
+ IDevice d = deviceManager.learnDeviceByEntity(entity2);
+ assertEquals(c.getTime(), d.getLastSeen());
+ d = deviceManager.learnDeviceByEntity(entity1);
+ assertEquals(c.getTime(), d.getLastSeen());
+
+ deviceManager.startUp(null);
+ d = deviceManager.learnDeviceByEntity(entity1);
+ assertEquals(d1, d.getLastSeen());
+ d = deviceManager.learnDeviceByEntity(entity2);
+ assertEquals(c.getTime(), d.getLastSeen());
+ }
+
+ @Test
+ public void testEntityLearning() throws Exception {
+ IDeviceListener mockListener =
+ createStrictMock(IDeviceListener.class);
+
+ deviceManager.addListener(mockListener);
+ deviceManager.entityClassifier= new MockEntityClassifier();
+ deviceManager.startUp(null);
+
+ ITopologyService mockTopology = createMock(ITopologyService.class);
+ expect(mockTopology.getL2DomainId(anyLong())).
+ andReturn(1L).anyTimes();
+ expect(mockTopology.isBroadcastDomainPort(anyLong(), anyShort())).
+ andReturn(false).anyTimes();
+
+ expect(mockTopology.isAttachmentPointPort(anyLong(),
+ anyShort())).andReturn(true).anyTimes();
+ expect(mockTopology.isConsistent(10L, (short)1, 10L, (short)1)).
+ andReturn(true).anyTimes();
+ expect(mockTopology.isConsistent(1L, (short)1, 1L, (short)1)).
+ andReturn(true).anyTimes();
+ expect(mockTopology.isConsistent(50L, (short)3, 50L, (short)3)).
+ andReturn(true).anyTimes();
+
+ Date topologyUpdateTime = new Date();
+ expect(mockTopology.getLastUpdateTime()).andReturn(topologyUpdateTime).
+ anyTimes();
+
+ deviceManager.topology = mockTopology;
+
+ Entity entity1 = new Entity(1L, null, null, 1L, 1, new Date());
+ Entity entity2 = new Entity(1L, null, null, 10L, 1, new Date());
+ Entity entity3 = new Entity(1L, null, 1, 10L, 1, new Date());
+ Entity entity4 = new Entity(1L, null, 1, 1L, 1, new Date());
+ Entity entity5 = new Entity(2L, (short)4, 1, 5L, 2, new Date());
+ Entity entity6 = new Entity(2L, (short)4, 1, 50L, 3, new Date());
+ Entity entity7 = new Entity(2L, (short)4, 2, 50L, 3, new Date());
+
+ mockListener.deviceAdded(isA(IDevice.class));
+ replay(mockListener, mockTopology);
+
+ Device d1 = deviceManager.learnDeviceByEntity(entity1);
+ assertSame(d1, deviceManager.learnDeviceByEntity(entity1));
+ assertSame(d1, deviceManager.findDeviceByEntity(entity1));
+ assertEquals(DefaultEntityClassifier.entityClass ,
+ d1.entityClass);
+ assertArrayEquals(new Short[] { -1 }, d1.getVlanId());
+ assertArrayEquals(new Integer[] { }, d1.getIPv4Addresses());
+
+ assertEquals(1, deviceManager.getAllDevices().size());
+ verify(mockListener);
+
+ reset(mockListener);
+ mockListener.deviceAdded(isA(IDevice.class));
+ replay(mockListener);
+
+ Device d2 = deviceManager.learnDeviceByEntity(entity2);
+ assertFalse(d1.equals(d2));
+ assertNotSame(d1, d2);
+ assertNotSame(d1.getDeviceKey(), d2.getDeviceKey());
+ assertEquals(MockEntityClassifier.testEC, d2.entityClass);
+ assertArrayEquals(new Short[] { -1 }, d2.getVlanId());
+ assertArrayEquals(new Integer[] { }, d2.getIPv4Addresses());
+
+ assertEquals(2, deviceManager.getAllDevices().size());
+ verify(mockListener);
+
+ reset(mockListener);
+ mockListener.deviceIPV4AddrChanged(isA(IDevice.class));
+ replay(mockListener);
+
+ Device d3 = deviceManager.learnDeviceByEntity(entity3);
+ assertNotSame(d2, d3);
+ assertEquals(d2.getDeviceKey(), d3.getDeviceKey());
+ assertEquals(MockEntityClassifier.testEC, d3.entityClass);
+ assertArrayEquals(new Integer[] { 1 },
+ d3.getIPv4Addresses());
+ assertArrayEquals(new SwitchPort[] { new SwitchPort(10L, 1) },
+ d3.getAttachmentPoints());
+ assertArrayEquals(new SwitchPort[] { new SwitchPort(10L, 1) },
+ d3.getAttachmentPoints(true));
+ assertArrayEquals(new Short[] { -1 },
+ d3.getVlanId());
+
+ assertEquals(2, deviceManager.getAllDevices().size());
+ verify(mockListener);
+
+ reset(mockListener);
+ mockListener.deviceIPV4AddrChanged(isA(IDevice.class));
+ replay(mockListener);
+
+ Device d4 = deviceManager.learnDeviceByEntity(entity4);
+ assertNotSame(d1, d4);
+ assertEquals(d1.getDeviceKey(), d4.getDeviceKey());
+ assertEquals(DefaultEntityClassifier.entityClass, d4.entityClass);
+ assertArrayEquals(new Integer[] { 1 },
+ d4.getIPv4Addresses());
+ assertArrayEquals(new SwitchPort[] { new SwitchPort(1L, 1) },
+ d4.getAttachmentPoints());
+ assertArrayEquals(new Short[] { -1 },
+ d4.getVlanId());
+
+ assertEquals(2, deviceManager.getAllDevices().size());
+ verify(mockListener);
+
+ reset(mockListener);
+ mockListener.deviceAdded((isA(IDevice.class)));
+ replay(mockListener);
+
+ Device d5 = deviceManager.learnDeviceByEntity(entity5);
+ assertArrayEquals(new SwitchPort[] { new SwitchPort(5L, 2) },
+ d5.getAttachmentPoints());
+ assertArrayEquals(new Short[] { (short) 4 },
+ d5.getVlanId());
+ assertEquals(2L, d5.getMACAddress());
+ assertEquals("00:00:00:00:00:02", d5.getMACAddressString());
+ verify(mockListener);
+
+ reset(mockListener);
+ mockListener.deviceAdded(isA(IDevice.class));
+ replay(mockListener);
+
+ Device d6 = deviceManager.learnDeviceByEntity(entity6);
+ assertArrayEquals(new SwitchPort[] { new SwitchPort(50L, 3) },
+ d6.getAttachmentPoints());
+ assertArrayEquals(new Short[] { (short) 4 },
+ d6.getVlanId());
+
+ assertEquals(4, deviceManager.getAllDevices().size());
+ verify(mockListener);
+
+ reset(mockListener);
+ mockListener.deviceIPV4AddrChanged(isA(IDevice.class));
+ replay(mockListener);
+
+ Device d7 = deviceManager.learnDeviceByEntity(entity7);
+ assertNotSame(d6, d7);
+ assertEquals(d6.getDeviceKey(), d7.getDeviceKey());
+ assertArrayEquals(new SwitchPort[] { new SwitchPort(50L, 3) },
+ d7.getAttachmentPoints());
+ assertArrayEquals(new Short[] { (short) 4 },
+ d7.getVlanId());
+
+ assertEquals(4, deviceManager.getAllDevices().size());
+ verify(mockListener);
+
+
+ reset(mockListener);
+ replay(mockListener);
+
+ reset(deviceManager.topology);
+ deviceManager.topology.addListener(deviceManager);
+ expectLastCall().times(1);
+ replay(deviceManager.topology);
+
+ deviceManager.entityClassifier = new MockEntityClassifierMac();
+ deviceManager.startUp(null);
+ Entity entityNoClass = new Entity(5L, (short)1, 5, -1L, 1, new Date());
+ assertEquals(null, deviceManager.learnDeviceByEntity(entityNoClass));
+
+ verify(mockListener);
+ }
+
+
+ @Test
+ public void testAttachmentPointLearning() throws Exception {
+ IDeviceListener mockListener =
+ createStrictMock(IDeviceListener.class);
+
+ deviceManager.addListener(mockListener);
+
+ ITopologyService mockTopology = createMock(ITopologyService.class);
+ expect(mockTopology.getL2DomainId(1L)).
+ andReturn(1L).anyTimes();
+ expect(mockTopology.getL2DomainId(5L)).
+ andReturn(1L).anyTimes();
+ expect(mockTopology.getL2DomainId(10L)).
+ andReturn(10L).anyTimes();
+ expect(mockTopology.getL2DomainId(50L)).
+ andReturn(10L).anyTimes();
+ expect(mockTopology.isBroadcastDomainPort(anyLong(), anyShort())).
+ andReturn(false).anyTimes();
+ expect(mockTopology.isInSameBroadcastDomain(anyLong(), anyShort(),
+ anyLong(), anyShort())).andReturn(false).anyTimes();
+
+ expect(mockTopology.isAttachmentPointPort(anyLong(),
+ anyShort())).andReturn(true).anyTimes();
+ expect(mockTopology.isConsistent(1L, (short)1, 5L, (short)1)).
+ andReturn(false).anyTimes();
+ expect(mockTopology.isConsistent(5L, (short)1, 10L, (short)1)).
+ andReturn(false).anyTimes();
+ expect(mockTopology.isConsistent(10L, (short)1, 50L, (short)1)).
+ andReturn(false).anyTimes();
+
+ Date topologyUpdateTime = new Date();
+ expect(mockTopology.getLastUpdateTime()).andReturn(topologyUpdateTime).
+ anyTimes();
+
+ replay(mockTopology);
+
+ deviceManager.topology = mockTopology;
+
+ Calendar c = Calendar.getInstance();
+ Entity entity1 = new Entity(1L, null, 1, 1L, 1, c.getTime());
+ Entity entity0 = new Entity(1L, null, null, null, null, c.getTime());
+ c.add(Calendar.SECOND, 1);
+ Entity entity2 = new Entity(1L, null, null, 5L, 1, c.getTime());
+ c.add(Calendar.SECOND, 1);
+ Entity entity3 = new Entity(1L, null, null, 10L, 1, c.getTime());
+ c.add(Calendar.SECOND, 1);
+ Entity entity4 = new Entity(1L, null, null, 50L, 1, c.getTime());
+
+ IDevice d;
+ SwitchPort[] aps;
+ Integer[] ips;
+
+ mockListener.deviceAdded(isA(IDevice.class));
+ replay(mockListener);
+
+ deviceManager.learnDeviceByEntity(entity1);
+ d = deviceManager.learnDeviceByEntity(entity0);
+ assertEquals(1, deviceManager.getAllDevices().size());
+ aps = d.getAttachmentPoints();
+ assertArrayEquals(new SwitchPort[] { new SwitchPort(1L, 1) }, aps);
+ ips = d.getIPv4Addresses();
+ assertArrayEquals(new Integer[] { 1 }, ips);
+ verify(mockListener);
+
+ reset(mockListener);
+ mockListener.deviceMoved((isA(IDevice.class)));
+ replay(mockListener);
+
+ d = deviceManager.learnDeviceByEntity(entity2);
+ assertEquals(1, deviceManager.getAllDevices().size());
+ aps = d.getAttachmentPoints();
+
+ assertArrayEquals(new SwitchPort[] { new SwitchPort(5L, 1) }, aps);
+ ips = d.getIPv4Addresses();
+ assertArrayEquals(new Integer[] { 1 }, ips);
+ verify(mockListener);
+
+ reset(mockListener);
+ mockListener.deviceMoved((isA(IDevice.class)));
+ replay(mockListener);
+
+ d = deviceManager.learnDeviceByEntity(entity3);
+ assertEquals(1, deviceManager.getAllDevices().size());
+ aps = d.getAttachmentPoints();
+ assertArrayEquals(new SwitchPort[] {new SwitchPort(5L, 1), new SwitchPort(10L, 1)}, aps);
+ ips = d.getIPv4Addresses();
+ assertArrayEquals(new Integer[] { 1 }, ips);
+ verify(mockListener);
+
+ reset(mockListener);
+ mockListener.deviceMoved((isA(IDevice.class)));
+ replay(mockListener);
+
+ d = deviceManager.learnDeviceByEntity(entity4);
+ assertEquals(1, deviceManager.getAllDevices().size());
+ aps = d.getAttachmentPoints();
+ assertArrayEquals(new SwitchPort[] { new SwitchPort(5L, 1),
+ new SwitchPort(50L, 1) }, aps);
+ ips = d.getIPv4Addresses();
+ assertArrayEquals(new Integer[] { 1 }, ips);
+ verify(mockListener);
+ }
+
+ @Test
+ public void testAttachmentPointSuppression() throws Exception {
+ IDeviceListener mockListener =
+ createStrictMock(IDeviceListener.class);
+
+ deviceManager.addListener(mockListener);
+
+ ITopologyService mockTopology = createMock(ITopologyService.class);
+ expect(mockTopology.getL2DomainId(1L)).
+ andReturn(1L).anyTimes();
+ expect(mockTopology.getL2DomainId(5L)).
+ andReturn(1L).anyTimes();
+ expect(mockTopology.getL2DomainId(10L)).
+ andReturn(10L).anyTimes();
+ expect(mockTopology.getL2DomainId(50L)).
+ andReturn(10L).anyTimes();
+ expect(mockTopology.isBroadcastDomainPort(anyLong(), anyShort())).
+ andReturn(false).anyTimes();
+ expect(mockTopology.isInSameBroadcastDomain(anyLong(), anyShort(),
+ anyLong(), anyShort())).andReturn(false).anyTimes();
+
+ expect(mockTopology.isAttachmentPointPort(anyLong(),
+ anyShort())).andReturn(true).anyTimes();
+ expect(mockTopology.isConsistent(5L, (short)1, 50L, (short)1)).
+ andReturn(false).anyTimes();
+
+ Date topologyUpdateTime = new Date();
+ expect(mockTopology.getLastUpdateTime()).andReturn(topologyUpdateTime).
+ anyTimes();
+
+ replay(mockTopology);
+
+ deviceManager.topology = mockTopology;
+ // suppress (1L, 1) and (10L, 1)
+ deviceManager.addSuppressAPs(1L, (short)1);
+ deviceManager.addSuppressAPs(10L, (short)1);
+
+ Calendar c = Calendar.getInstance();
+ Entity entity1 = new Entity(1L, null, 1, 1L, 1, c.getTime());
+ Entity entity0 = new Entity(1L, null, null, null, null, c.getTime());
+ c.add(Calendar.SECOND, 1);
+ Entity entity2 = new Entity(1L, null, null, 5L, 1, c.getTime());
+ c.add(Calendar.SECOND, 1);
+ Entity entity3 = new Entity(1L, null, null, 10L, 1, c.getTime());
+ c.add(Calendar.SECOND, 1);
+ Entity entity4 = new Entity(1L, null, null, 50L, 1, c.getTime());
+
+ IDevice d;
+ SwitchPort[] aps;
+ Integer[] ips;
+
+ mockListener.deviceAdded(isA(IDevice.class));
+ replay(mockListener);
+
+ deviceManager.learnDeviceByEntity(entity1);
+ d = deviceManager.learnDeviceByEntity(entity0);
+ assertEquals(1, deviceManager.getAllDevices().size());
+ aps = d.getAttachmentPoints();
+ assertEquals(aps.length, 0);
+ ips = d.getIPv4Addresses();
+ assertArrayEquals(new Integer[] { 1 }, ips);
+ verify(mockListener);
+
+ reset(mockListener);
+ mockListener.deviceMoved((isA(IDevice.class)));
+ replay(mockListener);
+
+ d = deviceManager.learnDeviceByEntity(entity2);
+ assertEquals(1, deviceManager.getAllDevices().size());
+ aps = d.getAttachmentPoints();
+ assertArrayEquals(new SwitchPort[] { new SwitchPort(5L, 1) }, aps);
+ ips = d.getIPv4Addresses();
+ assertArrayEquals(new Integer[] { 1 }, ips);
+ verify(mockListener);
+
+ reset(mockListener);
+ mockListener.deviceMoved((isA(IDevice.class)));
+ replay(mockListener);
+
+ d = deviceManager.learnDeviceByEntity(entity3);
+ assertEquals(1, deviceManager.getAllDevices().size());
+ aps = d.getAttachmentPoints();
+ assertArrayEquals(new SwitchPort[] { new SwitchPort(5L, 1) }, aps);
+ ips = d.getIPv4Addresses();
+ assertArrayEquals(new Integer[] { 1 }, ips);
+ //verify(mockListener); // There is no device movement here; no not needed.
+
+ reset(mockListener);
+ mockListener.deviceMoved((isA(IDevice.class)));
+ replay(mockListener);
+
+ d = deviceManager.learnDeviceByEntity(entity4);
+ assertEquals(1, deviceManager.getAllDevices().size());
+ aps = d.getAttachmentPoints();
+ assertArrayEquals(new SwitchPort[] { new SwitchPort(5L, 1),
+ new SwitchPort(50L, 1) }, aps);
+ ips = d.getIPv4Addresses();
+ assertArrayEquals(new Integer[] { 1 }, ips);
+ verify(mockListener);
+ }
+
+ @Test
+ public void testBDAttachmentPointLearning() throws Exception {
+ ITopologyService mockTopology = createMock(ITopologyService.class);
+ expect(mockTopology.getL2DomainId(anyLong())).
+ andReturn(1L).anyTimes();
+ expect(mockTopology.isAttachmentPointPort(anyLong(), anyShort())).
+ andReturn(true).anyTimes();
+ expect(mockTopology.isBroadcastDomainPort(1L, (short)1)).
+ andReturn(false).anyTimes();
+ expect(mockTopology.isBroadcastDomainPort(1L, (short)2)).
+ andReturn(true).anyTimes();
+ expect(mockTopology.isInSameBroadcastDomain(1L, (short)1,
+ 1L, (short)2)).andReturn(true).anyTimes();
+ expect(mockTopology.isInSameBroadcastDomain(1L, (short)2,
+ 1L, (short)1)).andReturn(true).anyTimes();
+ expect(mockTopology.isConsistent(anyLong(), anyShort(), anyLong(), anyShort())).andReturn(false).anyTimes();
+
+ Date topologyUpdateTime = new Date();
+ expect(mockTopology.getLastUpdateTime()).andReturn(topologyUpdateTime).
+ anyTimes();
+
+ replay(mockTopology);
+
+ deviceManager.topology = mockTopology;
+
+ Calendar c = Calendar.getInstance();
+ Entity entity1 = new Entity(1L, null, 1, 1L, 1, c.getTime());
+ c.add(Calendar.MILLISECOND,
+ (int)AttachmentPoint.OPENFLOW_TO_EXTERNAL_TIMEOUT/ 2);
+ Entity entity2 = new Entity(1L, null, null, 1L, 2, c.getTime());
+ c.add(Calendar.MILLISECOND,
+ (int)AttachmentPoint.OPENFLOW_TO_EXTERNAL_TIMEOUT / 2 + 1);
+ Entity entity3 = new Entity(1L, null, null, 1L, 2, c.getTime());
+
+ IDevice d;
+ SwitchPort[] aps;
+
+ d = deviceManager.learnDeviceByEntity(entity1);
+ assertEquals(1, deviceManager.getAllDevices().size());
+ aps = d.getAttachmentPoints();
+ assertArrayEquals(new SwitchPort[] { new SwitchPort(1L, 1) }, aps);
+
+ // this timestamp is too soon; don't switch
+ d = deviceManager.learnDeviceByEntity(entity2);
+ assertEquals(1, deviceManager.getAllDevices().size());
+ aps = d.getAttachmentPoints();
+ assertArrayEquals(new SwitchPort[] { new SwitchPort(1L, 1) }, aps);
+
+ // it should switch when we learn with a timestamp after the
+ // timeout
+ d = deviceManager.learnDeviceByEntity(entity3);
+ assertEquals(1, deviceManager.getAllDevices().size());
+ aps = d.getAttachmentPoints();
+ assertArrayEquals(new SwitchPort[] { new SwitchPort(1L, 2) }, aps);
+ }
+
+
+ @Test
+ public void testPacketIn() throws Exception {
+ byte[] dataLayerSource =
+ ((Ethernet)this.testARPReplyPacket_1).getSourceMACAddress();
+
+ // Mock up our expected behavior
+ ITopologyService mockTopology = createMock(ITopologyService.class);
+ deviceManager.topology = mockTopology;
+ expect(mockTopology.isAttachmentPointPort(EasyMock.anyLong(),
+ EasyMock.anyShort())).
+ andReturn(true).anyTimes();
+ expect(mockTopology.isConsistent(EasyMock.anyLong(),
+ EasyMock.anyShort(),
+ EasyMock.anyLong(),
+ EasyMock.anyShort())).andReturn(false).
+ anyTimes();
+ expect(mockTopology.getL2DomainId(EasyMock.anyLong())).andReturn(1L).anyTimes();
+ replay(mockTopology);
+
+ Date currentDate = new Date();
+
+ // build our expected Device
+ Integer ipaddr = IPv4.toIPv4Address("192.168.1.1");
+ Device device =
+ new Device(deviceManager,
+ new Long(deviceManager.deviceKeyCounter),
+ new Entity(Ethernet.toLong(dataLayerSource),
+ (short)5,
+ ipaddr,
+ 1L,
+ 1,
+ currentDate),
+ DefaultEntityClassifier.entityClass);
+
+
+
+
+ // Get the listener and trigger the packet in
+ IOFSwitch switch1 = mockFloodlightProvider.getSwitches().get(1L);
+ mockFloodlightProvider.dispatchMessage(switch1, this.packetIn_1);
+
+ // Verify the replay matched our expectations
+ // verify(mockTopology);
+
+ // Verify the device
+ Device rdevice = (Device)
+ deviceManager.findDevice(Ethernet.toLong(dataLayerSource),
+ (short)5, null, null, null);
+
+ assertEquals(device, rdevice);
+ assertEquals(new Short((short)5), rdevice.getVlanId()[0]);
+
+ Device result = null;
+ Iterator<? extends IDevice> dstiter =
+ deviceManager.queryClassDevices(device, null, null, ipaddr,
+ null, null);
+ if (dstiter.hasNext()) {
+ result = (Device)dstiter.next();
+ }
+
+ assertEquals(device, result);
+
+ device =
+ new Device(device,
+ new Entity(Ethernet.toLong(dataLayerSource),
+ (short)5,
+ ipaddr,
+ 5L,
+ 2,
+ currentDate));
+
+ reset(mockTopology);
+ expect(mockTopology.isAttachmentPointPort(anyLong(),
+ anyShort())).
+ andReturn(true).
+ anyTimes();
+ expect(mockTopology.isConsistent(EasyMock.anyLong(),
+ EasyMock.anyShort(),
+ EasyMock.anyLong(),
+ EasyMock.anyShort())).andReturn(false).
+ anyTimes();
+ expect(mockTopology.isBroadcastDomainPort(EasyMock.anyLong(),
+ EasyMock.anyShort()))
+ .andReturn(false)
+ .anyTimes();
+ expect(mockTopology.getL2DomainId(1L)).andReturn(1L).anyTimes();
+ expect(mockTopology.getL2DomainId(5L)).andReturn(1L).anyTimes();
+ expect(mockTopology.isInSameBroadcastDomain(1L, (short)1, 5L, (short)2)).
+ andReturn(false).anyTimes();
+
+ // Start recording the replay on the mocks
+ replay(mockTopology);
+ // Get the listener and trigger the packet in
+ IOFSwitch switch5 = mockFloodlightProvider.getSwitches().get(5L);
+ mockFloodlightProvider.
+ dispatchMessage(switch5, this.packetIn_1.setInPort((short)2));
+
+ // Verify the replay matched our expectations
+ verify(mockTopology);
+
+ // Verify the device
+ rdevice = (Device)
+ deviceManager.findDevice(Ethernet.toLong(dataLayerSource),
+ (short)5, null, null, null);
+ assertEquals(device, rdevice);
+ }
+
+
+ /**
+ * Note: Entity expiration does not result in device moved notification.
+ * @throws Exception
+ */
+ public void doTestEntityExpiration() throws Exception {
+ IDeviceListener mockListener =
+ createStrictMock(IDeviceListener.class);
+ mockListener.deviceIPV4AddrChanged(isA(IDevice.class));
+
+ ITopologyService mockTopology = createMock(ITopologyService.class);
+ expect(mockTopology.isAttachmentPointPort(anyLong(),
+ anyShort())).
+ andReturn(true).anyTimes();
+
+ expect(mockTopology.isBroadcastDomainPort(1L, (short)1)).andReturn(false).anyTimes();
+ expect(mockTopology.isBroadcastDomainPort(5L, (short)1)).andReturn(false).anyTimes();
+ expect(mockTopology.getL2DomainId(1L)).andReturn(1L).anyTimes();
+ expect(mockTopology.getL2DomainId(5L)).andReturn(5L).anyTimes();
+ expect(mockTopology.isConsistent(1L, (short)1, 5L, (short)1)).
+ andReturn(false).anyTimes();
+
+ Date topologyUpdateTime = new Date();
+ expect(mockTopology.getLastUpdateTime()).andReturn(topologyUpdateTime).
+ anyTimes();
+
+ replay(mockTopology);
+ deviceManager.topology = mockTopology;
+
+ Calendar c = Calendar.getInstance();
+ Entity entity1 = new Entity(1L, null, 2, 1L, 1, c.getTime());
+ c.add(Calendar.MILLISECOND, -DeviceManagerImpl.ENTITY_TIMEOUT-1);
+ Entity entity2 = new Entity(1L, null, 1, 5L, 1, c.getTime());
+
+ deviceManager.learnDeviceByEntity(entity1);
+ IDevice d = deviceManager.learnDeviceByEntity(entity2);
+ assertArrayEquals(new Integer[] { 1, 2 }, d.getIPv4Addresses());
+ assertArrayEquals(new SwitchPort[] { new SwitchPort(1L, 1),
+ new SwitchPort(5L, 1)},
+ d.getAttachmentPoints());
+ Iterator<? extends IDevice> diter =
+ deviceManager.queryClassDevices(d, null, null, 1, null, null);
+ assertTrue(diter.hasNext());
+ assertEquals(d.getDeviceKey(), diter.next().getDeviceKey());
+ diter = deviceManager.queryClassDevices(d, null, null, 2, null, null);
+ assertTrue(diter.hasNext());
+ assertEquals(d.getDeviceKey(), diter.next().getDeviceKey());
+
+
+ deviceManager.addListener(mockListener);
+ replay(mockListener);
+ deviceManager.entityCleanupTask.reschedule(0, null);
+
+ d = deviceManager.getDevice(d.getDeviceKey());
+ assertArrayEquals(new Integer[] { 2 }, d.getIPv4Addresses());
+
+ // Attachment points are not removed, previous ones are still valid.
+ assertArrayEquals(new SwitchPort[] { new SwitchPort(1L, 1),
+ new SwitchPort(5L, 1) },
+ d.getAttachmentPoints());
+ diter = deviceManager.queryClassDevices(d, null, null, 2, null, null);
+ assertTrue(diter.hasNext());
+ assertEquals(d.getDeviceKey(), diter.next().getDeviceKey());
+ diter = deviceManager.queryClassDevices(d, null, null, 1, null, null);
+ assertFalse(diter.hasNext());
+
+ d = deviceManager.findDevice(1L, null, null, null, null);
+ assertArrayEquals(new Integer[] { 2 }, d.getIPv4Addresses());
+
+ // Attachment points are not removed, previous ones are still valid.
+ assertArrayEquals(new SwitchPort[] { new SwitchPort(1L, 1),
+ new SwitchPort(5L, 1) },
+ d.getAttachmentPoints());
+
+ verify(mockListener);
+ }
+
+ public void doTestDeviceExpiration() throws Exception {
+ IDeviceListener mockListener =
+ createStrictMock(IDeviceListener.class);
+ mockListener.deviceRemoved(isA(IDevice.class));
+
+ Calendar c = Calendar.getInstance();
+ c.add(Calendar.MILLISECOND, -DeviceManagerImpl.ENTITY_TIMEOUT-1);
+ Entity entity1 = new Entity(1L, null, 1, 1L, 1, c.getTime());
+ Entity entity2 = new Entity(1L, null, 2, 5L, 1, c.getTime());
+
+ ITopologyService mockTopology = createMock(ITopologyService.class);
+ deviceManager.topology = mockTopology;
+
+ expect(mockTopology.isAttachmentPointPort(EasyMock.anyLong(),
+ EasyMock.anyShort())).
+ andReturn(true).
+ anyTimes();
+ expect(mockTopology.getL2DomainId(1L)).andReturn(1L).anyTimes();
+ expect(mockTopology.getL2DomainId(5L)).andReturn(1L).anyTimes();
+ expect(mockTopology.isConsistent(EasyMock.anyLong(),
+ EasyMock.anyShort(),
+ EasyMock.anyLong(),
+ EasyMock.anyShort())).andReturn(false).
+ anyTimes();
+ expect(mockTopology.isBroadcastDomainPort(EasyMock.anyLong(),
+ EasyMock.anyShort())).
+ andReturn(false).anyTimes();
+ replay(mockTopology);
+
+ IDevice d = deviceManager.learnDeviceByEntity(entity2);
+ d = deviceManager.learnDeviceByEntity(entity1);
+ assertArrayEquals(new Integer[] { 1, 2 }, d.getIPv4Addresses());
+
+ deviceManager.addListener(mockListener);
+ replay(mockListener);
+ deviceManager.entityCleanupTask.reschedule(0, null);
+
+ IDevice r = deviceManager.getDevice(d.getDeviceKey());
+ assertNull(r);
+ Iterator<? extends IDevice> diter =
+ deviceManager.queryClassDevices(d, null, null, 1, null, null);
+ assertFalse(diter.hasNext());
+
+ r = deviceManager.findDevice(1L, null, null, null, null);
+ assertNull(r);
+
+ verify(mockListener);
+ }
+
+ /*
+ * A ConcurrentHashMap for devices (deviceMap) that can be used to test
+ * code that specially handles concurrent modification situations. In
+ * particular, we overwrite values() and will replace / remove all the
+ * elements returned by values.
+ *
+ * The remove flag in the constructor specifies if devices returned by
+ * values() should be removed or replaced.
+ */
+ protected static class ConcurrentlyModifiedDeviceMap
+ extends ConcurrentHashMap<Long, Device> {
+ private static final long serialVersionUID = 7784938535441180562L;
+ protected boolean remove;
+ public ConcurrentlyModifiedDeviceMap(boolean remove) {
+ super();
+ this.remove = remove;
+ }
+
+ @Override
+ public Collection<Device> values() {
+ // Get the values from the real map and copy them since
+ // the collection returned by values can reflect changed
+ Collection<Device> devs = new ArrayList<Device>(super.values());
+ for (Device d: devs) {
+ if (remove) {
+ // We remove the device from the underlying map
+ super.remove(d.getDeviceKey());
+ } else {
+ super.remove(d.getDeviceKey());
+ // We add a different Device instance with the same
+ // key to the map. We'll do some hackery so the device
+ // is different enough to compare differently in equals
+ // but otherwise looks the same.
+ // It's ugly but it works.
+ Entity[] curEntities = new Entity[d.getEntities().length];
+ int i = 0;
+ // clone entities
+ for (Entity e: d.getEntities()) {
+ curEntities[i] = new Entity (e.macAddress,
+ e.vlan,
+ e.ipv4Address,
+ e.switchDPID,
+ e.switchPort,
+ e.lastSeenTimestamp);
+ if (e.vlan == null)
+ curEntities[i].vlan = (short)1;
+ else
+ curEntities[i].vlan = (short)((e.vlan + 1 % 4095)+1);
+ i++;
+ }
+ Device newDevice = new Device(d, curEntities[0]);
+ newDevice.entities = curEntities;
+ assertEquals(false, newDevice.equals(d));
+ super.put(newDevice.getDeviceKey(), newDevice);
+ }
+ }
+ return devs;
+ }
+ }
+
+ @Test
+ public void testEntityExpiration() throws Exception {
+ doTestEntityExpiration();
+ }
+
+ @Test
+ public void testDeviceExpiration() throws Exception {
+ doTestDeviceExpiration();
+ }
+
+ /* Test correct entity cleanup behavior when a concurrent modification
+ * occurs.
+ */
+ @Test
+ public void testEntityExpirationConcurrentModification() throws Exception {
+ deviceManager.deviceMap = new ConcurrentlyModifiedDeviceMap(false);
+ doTestEntityExpiration();
+ }
+
+ /* Test correct entity cleanup behavior when a concurrent remove
+ * occurs.
+ */
+ @Test
+ public void testDeviceExpirationConcurrentRemove() throws Exception {
+ deviceManager.deviceMap = new ConcurrentlyModifiedDeviceMap(true);
+ doTestDeviceExpiration();
+ }
+
+ /* Test correct entity cleanup behavior when a concurrent modification
+ * occurs.
+ */
+ @Test
+ public void testDeviceExpirationConcurrentModification() throws Exception {
+ deviceManager.deviceMap = new ConcurrentlyModifiedDeviceMap(false);
+ doTestDeviceExpiration();
+ }
+
+
+ @Test
+ public void testAttachmentPointFlapping() throws Exception {
+ Calendar c = Calendar.getInstance();
+
+ ITopologyService mockTopology = createMock(ITopologyService.class);
+ expect(mockTopology.isAttachmentPointPort(anyLong(),
+ anyShort())).andReturn(true).anyTimes();
+ expect(mockTopology.isBroadcastDomainPort(anyLong(),
+ anyShort())).
+ andReturn(false).anyTimes();
+ expect(mockTopology.isInSameBroadcastDomain(anyLong(), anyShort(),
+ anyLong(), anyShort())).andReturn(false).anyTimes();
+ expect(mockTopology.getL2DomainId(anyLong())).
+ andReturn(1L).anyTimes();
+ expect(mockTopology.isConsistent(1L, (short)1, 1L, (short)1)).
+ andReturn(true).anyTimes();
+ expect(mockTopology.isConsistent(1L, (short)1, 5L, (short)1)).
+ andReturn(false).anyTimes();
+ expect(mockTopology.isConsistent(1L, (short)1, 10L, (short)1)).
+ andReturn(false).anyTimes();
+ expect(mockTopology.isConsistent(5L, (short)1, 10L, (short)1)).
+ andReturn(false).anyTimes();
+ expect(mockTopology.isConsistent(10L, (short)1, 1L, (short)1)).
+ andReturn(false).anyTimes();
+ expect(mockTopology.isConsistent(5L, (short)1, 1L, (short)1)).
+ andReturn(false).anyTimes();
+ expect(mockTopology.isConsistent(10L, (short)1, 5L, (short)1)).
+ andReturn(false).anyTimes();
+
+ Date topologyUpdateTime = new Date();
+ expect(mockTopology.getLastUpdateTime()).andReturn(topologyUpdateTime).
+ anyTimes();
+
+
+ replay(mockTopology);
+ deviceManager.topology = mockTopology;
+
+ Entity entity1 = new Entity(1L, null, null, 1L, 1, c.getTime());
+ Entity entity1a = new Entity(1L, null, 1, 1L, 1, c.getTime());
+ Entity entity2 = new Entity(1L, null, null, 5L, 1, c.getTime());
+ Entity entity3 = new Entity(1L, null, null, 10L, 1, c.getTime());
+ entity1.setLastSeenTimestamp(c.getTime());
+ c.add(Calendar.MILLISECOND, Entity.ACTIVITY_TIMEOUT/2);
+ entity1a.setLastSeenTimestamp(c.getTime());
+ c.add(Calendar.MILLISECOND, 1);
+ entity2.setLastSeenTimestamp(c.getTime());
+ c.add(Calendar.MILLISECOND, 1);
+ entity3.setLastSeenTimestamp(c.getTime());
+
+
+
+ IDevice d;
+ d = deviceManager.learnDeviceByEntity(entity1);
+ d = deviceManager.learnDeviceByEntity(entity1a);
+ d = deviceManager.learnDeviceByEntity(entity2);
+ d = deviceManager.learnDeviceByEntity(entity3);
+
+ // all entities are active, so entity3 should win
+ assertArrayEquals(new SwitchPort[] { new SwitchPort(10L, 1) },
+ d.getAttachmentPoints());
+
+ assertArrayEquals(new SwitchPort[] { new SwitchPort(10L, 1),},
+ d.getAttachmentPoints(true));
+
+ c.add(Calendar.MILLISECOND, Entity.ACTIVITY_TIMEOUT/4);
+ entity1.setLastSeenTimestamp(c.getTime());
+ d = deviceManager.learnDeviceByEntity(entity1);
+
+ // all are still active; entity3 should still win
+ assertArrayEquals(new SwitchPort[] { new SwitchPort(1L, 1) },
+ d.getAttachmentPoints());
+ assertArrayEquals(new SwitchPort[] { new SwitchPort(1L, 1),
+ new SwitchPort(5L, 1,
+ ErrorStatus.DUPLICATE_DEVICE),
+ new SwitchPort(10L, 1,
+ ErrorStatus.DUPLICATE_DEVICE) },
+ d.getAttachmentPoints(true));
+
+ c.add(Calendar.MILLISECOND, Entity.ACTIVITY_TIMEOUT+2000);
+ entity1.setLastSeenTimestamp(c.getTime());
+ d = deviceManager.learnDeviceByEntity(entity1);
+
+ assertEquals(entity1.getActiveSince(), entity1.getLastSeenTimestamp());
+ // entity1 should now be the only active entity
+ assertArrayEquals(new SwitchPort[] { new SwitchPort(1L, 1) },
+ d.getAttachmentPoints());
+ assertArrayEquals(new SwitchPort[] { new SwitchPort(1L, 1) },
+ d.getAttachmentPoints(true));
+ }
+
+
+ @Test
+ public void testAttachmentPointFlappingTwoCluster() throws Exception {
+ Calendar c = Calendar.getInstance();
+
+ ITopologyService mockTopology = createMock(ITopologyService.class);
+ expect(mockTopology.isAttachmentPointPort(anyLong(),
+ anyShort())).andReturn(true).anyTimes();
+ expect(mockTopology.isBroadcastDomainPort(anyLong(),
+ anyShort())).
+ andReturn(false).anyTimes();
+ expect(mockTopology.isInSameBroadcastDomain(anyLong(), anyShort(),
+ anyLong(), anyShort())).andReturn(false).anyTimes();
+ expect(mockTopology.getL2DomainId(1L)).
+ andReturn(1L).anyTimes();
+ expect(mockTopology.getL2DomainId(5L)).
+ andReturn(5L).anyTimes();
+ expect(mockTopology.isConsistent(1L, (short)1, 1L, (short)2)).
+ andReturn(false).anyTimes();
+ expect(mockTopology.isConsistent(1L, (short)2, 5L, (short)1)).
+ andReturn(false).anyTimes();
+ expect(mockTopology.isConsistent(5L, (short)1, 5L, (short)2)).
+ andReturn(false).anyTimes();
+ expect(mockTopology.isConsistent(1L, (short)2, 1L, (short)1)).
+ andReturn(false).anyTimes();
+ expect(mockTopology.isConsistent(1L, (short)1, 5L, (short)1)).
+ andReturn(false).anyTimes();
+ expect(mockTopology.isConsistent(1L, (short)1, 5L, (short)2)).
+ andReturn(false).anyTimes();
+ expect(mockTopology.isConsistent(5L, (short)2, 5L, (short)1)).
+ andReturn(false).anyTimes();
+
+ Date topologyUpdateTime = new Date();
+ expect(mockTopology.getLastUpdateTime()).andReturn(topologyUpdateTime).
+ anyTimes();
+
+ replay(mockTopology);
+ deviceManager.topology = mockTopology;
+
+ Entity entity1 = new Entity(1L, null, null, 1L, 1, c.getTime());
+ Entity entity2 = new Entity(1L, null, null, 1L, 2, c.getTime());
+ Entity entity3 = new Entity(1L, null, null, 5L, 1, c.getTime());
+ Entity entity4 = new Entity(1L, null, null, 5L, 2, c.getTime());
+ entity1.setLastSeenTimestamp(c.getTime());
+ c.add(Calendar.MILLISECOND, Entity.ACTIVITY_TIMEOUT/2);
+ c.add(Calendar.MILLISECOND, 1);
+ entity2.setLastSeenTimestamp(c.getTime());
+ c.add(Calendar.MILLISECOND, 1);
+ entity3.setLastSeenTimestamp(c.getTime());
+ c.add(Calendar.MILLISECOND, 1);
+ entity4.setLastSeenTimestamp(c.getTime());
+
+ deviceManager.learnDeviceByEntity(entity1);
+ deviceManager.learnDeviceByEntity(entity2);
+ deviceManager.learnDeviceByEntity(entity3);
+ IDevice d = deviceManager.learnDeviceByEntity(entity4);
+
+ // all entities are active, so entities 2,4 should win
+ assertArrayEquals(new SwitchPort[] { new SwitchPort(1L, 2),
+ new SwitchPort(5L, 2) },
+ d.getAttachmentPoints());
+ assertArrayEquals(new SwitchPort[] { new SwitchPort(1L, 2),
+ new SwitchPort(5L, 2)},
+ d.getAttachmentPoints(true));
+
+ c.add(Calendar.MILLISECOND, 1);
+ entity1.setLastSeenTimestamp(c.getTime());
+ d = deviceManager.learnDeviceByEntity(entity1);
+
+ // all entities are active, so entities 2,4 should win
+ assertArrayEquals(new SwitchPort[] { new SwitchPort(1L, 1),
+ new SwitchPort(5L, 2) },
+ d.getAttachmentPoints());
+ assertArrayEquals(new SwitchPort[] { new SwitchPort(1L, 1),
+ new SwitchPort(5L, 2),
+ new SwitchPort(1L, 2, ErrorStatus.DUPLICATE_DEVICE)},
+ d.getAttachmentPoints(true));
+
+ c.add(Calendar.MILLISECOND, Entity.ACTIVITY_TIMEOUT+1);
+ entity1.setLastSeenTimestamp(c.getTime());
+ d = deviceManager.learnDeviceByEntity(entity1);
+
+ // entities 3,4 are still in conflict, but 1 should be resolved
+ assertArrayEquals(new SwitchPort[] { new SwitchPort(1L, 1),
+ new SwitchPort(5L, 2) },
+ d.getAttachmentPoints());
+ assertArrayEquals(new SwitchPort[] { new SwitchPort(1L, 1),
+ new SwitchPort(5L, 2)},
+ d.getAttachmentPoints(true));
+
+ entity3.setLastSeenTimestamp(c.getTime());
+ d = deviceManager.learnDeviceByEntity(entity3);
+
+ // no conflicts, 1 and 3 will win
+ assertArrayEquals(new SwitchPort[] { new SwitchPort(1L, 1),
+ new SwitchPort(5L, 1) },
+ d.getAttachmentPoints());
+ assertArrayEquals(new SwitchPort[] { new SwitchPort(1L, 1),
+ new SwitchPort(5L, 1) },
+ d.getAttachmentPoints(true));
+
+ }
+
+ protected void doTestDeviceQuery() throws Exception {
+ Entity entity1 = new Entity(1L, (short)1, 1, 1L, 1, new Date());
+ Entity entity2 = new Entity(2L, (short)2, 2, 1L, 2, new Date());
+ Entity entity3 = new Entity(3L, (short)3, 3, 5L, 1, new Date());
+ Entity entity4 = new Entity(4L, (short)4, 3, 5L, 2, new Date());
+ Entity entity5 = new Entity(1L, (short)4, 3, 5L, 2, new Date());
+
+ deviceManager.learnDeviceByEntity(entity1);
+ deviceManager.learnDeviceByEntity(entity2);
+ deviceManager.learnDeviceByEntity(entity3);
+ deviceManager.learnDeviceByEntity(entity4);
+
+ Iterator<? extends IDevice> iter =
+ deviceManager.queryDevices(null, (short)1, 1, null, null);
+ int count = 0;
+ while (iter.hasNext()) {
+ count += 1;
+ iter.next();
+ }
+ assertEquals(1, count);
+
+ iter = deviceManager.queryDevices(null, (short)3, 3, null, null);
+ count = 0;
+ while (iter.hasNext()) {
+ count += 1;
+ iter.next();
+ }
+ assertEquals(1, count);
+
+ iter = deviceManager.queryDevices(null, (short)1, 3, null, null);
+ count = 0;
+ while (iter.hasNext()) {
+ count += 1;
+ iter.next();
+ }
+ assertEquals(0, count);
+
+ deviceManager.learnDeviceByEntity(entity5);
+ iter = deviceManager.queryDevices(null, (short)4, 3, null, null);
+ count = 0;
+ while (iter.hasNext()) {
+ count += 1;
+ iter.next();
+ }
+ assertEquals(2, count);
+ }
+
+ @Test
+ public void testDeviceIndex() throws Exception {
+ EnumSet<IDeviceService.DeviceField> indexFields =
+ EnumSet.noneOf(IDeviceService.DeviceField.class);
+ indexFields.add(IDeviceService.DeviceField.IPV4);
+ indexFields.add(IDeviceService.DeviceField.VLAN);
+ deviceManager.addIndex(false, indexFields);
+
+ ITopologyService mockTopology = createMock(ITopologyService.class);
+ deviceManager.topology = mockTopology;
+ expect(mockTopology.isAttachmentPointPort(anyLong(),
+ anyShort())).
+ andReturn(true).anyTimes();
+ expect(mockTopology.getL2DomainId(EasyMock.anyLong())).andReturn(1L).anyTimes();
+ replay(mockTopology);
+ doTestDeviceQuery();
+ }
+
+ @Test
+ public void testDeviceQuery() throws Exception {
+ ITopologyService mockTopology = createMock(ITopologyService.class);
+ deviceManager.topology = mockTopology;
+ expect(mockTopology.isAttachmentPointPort(anyLong(),
+ anyShort())).
+ andReturn(true).anyTimes();
+ expect(mockTopology.getL2DomainId(EasyMock.anyLong())).andReturn(1L).anyTimes();
+ replay(mockTopology);
+
+ doTestDeviceQuery();
+ }
+
+ protected void doTestDeviceClassQuery() throws Exception {
+ Entity entity1 = new Entity(1L, (short)1, 1, 1L, 1, new Date());
+ Entity entity2 = new Entity(2L, (short)2, 2, 1L, 2, new Date());
+ Entity entity3 = new Entity(3L, (short)3, 3, 5L, 1, new Date());
+ Entity entity4 = new Entity(4L, (short)4, 3, 5L, 2, new Date());
+ Entity entity5 = new Entity(1L, (short)4, 3, 5L, 2, new Date());
+
+ IDevice d = deviceManager.learnDeviceByEntity(entity1);
+ deviceManager.learnDeviceByEntity(entity2);
+ deviceManager.learnDeviceByEntity(entity3);
+ deviceManager.learnDeviceByEntity(entity4);
+
+ Iterator<? extends IDevice> iter =
+ deviceManager.queryClassDevices(d, null,
+ (short)1, 1, null, null);
+ int count = 0;
+ while (iter.hasNext()) {
+ count += 1;
+ iter.next();
+ }
+ assertEquals(1, count);
+
+ iter = deviceManager.queryClassDevices(d, null,
+ (short)3, 3, null, null);
+ count = 0;
+ while (iter.hasNext()) {
+ count += 1;
+ iter.next();
+ }
+ assertEquals(1, count);
+
+ iter = deviceManager.queryClassDevices(d, null,
+ (short)1, 3, null, null);
+ count = 0;
+ while (iter.hasNext()) {
+ count += 1;
+ iter.next();
+ }
+ assertEquals(0, count);
+
+ deviceManager.learnDeviceByEntity(entity5);
+ iter = deviceManager.queryClassDevices(d, null,
+ (short)4, 3, null, null);
+ count = 0;
+ while (iter.hasNext()) {
+ count += 1;
+ iter.next();
+ }
+ assertEquals(2, count);
+ }
+
+ @Test
+ public void testDeviceClassIndex() throws Exception {
+ EnumSet<IDeviceService.DeviceField> indexFields =
+ EnumSet.noneOf(IDeviceService.DeviceField.class);
+ indexFields.add(IDeviceService.DeviceField.IPV4);
+ indexFields.add(IDeviceService.DeviceField.VLAN);
+ deviceManager.addIndex(true, indexFields);
+
+ ITopologyService mockTopology = createMock(ITopologyService.class);
+ deviceManager.topology = mockTopology;
+ expect(mockTopology.isAttachmentPointPort(anyLong(),
+ anyShort())).
+ andReturn(true).anyTimes();
+ expect(mockTopology.getL2DomainId(EasyMock.anyLong())).andReturn(1L).anyTimes();
+ replay(mockTopology);
+
+ doTestDeviceClassQuery();
+ }
+
+ @Test
+ public void testDeviceClassQuery() throws Exception {
+ ITopologyService mockTopology = createMock(ITopologyService.class);
+ deviceManager.topology = mockTopology;
+ expect(mockTopology.isAttachmentPointPort(anyLong(),
+ anyShort())).
+ andReturn(true).anyTimes();
+ expect(mockTopology.getL2DomainId(EasyMock.anyLong())).andReturn(1L).anyTimes();
+ replay(mockTopology);
+
+ doTestDeviceClassQuery();
+ }
+
+ @Test
+ public void testFindDevice() {
+ boolean exceptionCaught;
+ deviceManager.entityClassifier= new MockEntityClassifierMac();
+ deviceManager.startUp(null);
+
+ ITopologyService mockTopology = createMock(ITopologyService.class);
+ deviceManager.topology = mockTopology;
+ expect(mockTopology.isAttachmentPointPort(anyLong(),
+ anyShort())).
+ andReturn(true).anyTimes();
+ expect(mockTopology.getL2DomainId(EasyMock.anyLong())).andReturn(1L).anyTimes();
+ replay(mockTopology);
+
+ Entity entity1 = new Entity(1L, (short)1, 1, 1L, 1, new Date());
+ Entity entity2 = new Entity(2L, (short)2, 2, 1L, 2, new Date());
+ Entity entity2b = new Entity(22L, (short)2, 2, 1L, 2, new Date());
+
+ Entity entity3 = new Entity(3L, (short)1, 3, 2L, 1, new Date());
+ Entity entity4 = new Entity(4L, (short)2, 4, 2L, 2, new Date());
+
+ Entity entity5 = new Entity(5L, (short)1, 5, 3L, 1, new Date());
+
+
+ IDevice d1 = deviceManager.learnDeviceByEntity(entity1);
+ IDevice d2 = deviceManager.learnDeviceByEntity(entity2);
+ IDevice d3 = deviceManager.learnDeviceByEntity(entity3);
+ IDevice d4 = deviceManager.learnDeviceByEntity(entity4);
+ IDevice d5 = deviceManager.learnDeviceByEntity(entity5);
+
+ // Make sure the entity classifier worked as expected
+ assertEquals(MockEntityClassifierMac.testECMac1, d1.getEntityClass());
+ assertEquals(MockEntityClassifierMac.testECMac1, d2.getEntityClass());
+ assertEquals(MockEntityClassifierMac.testECMac2, d3.getEntityClass());
+ assertEquals(MockEntityClassifierMac.testECMac2, d4.getEntityClass());
+ assertEquals(DefaultEntityClassifier.entityClass,
+ d5.getEntityClass());
+
+ // Look up the device using findDevice() which uses only the primary
+ // index
+ assertEquals(d1, deviceManager.findDevice(entity1.getMacAddress(),
+ entity1.getVlan(),
+ entity1.getIpv4Address(),
+ entity1.getSwitchDPID(),
+ entity1.getSwitchPort()));
+ // port changed. Device will be found through class index
+ assertEquals(d1, deviceManager.findDevice(entity1.getMacAddress(),
+ entity1.getVlan(),
+ entity1.getIpv4Address(),
+ entity1.getSwitchDPID(),
+ entity1.getSwitchPort()+1));
+ // VLAN changed. No device matches
+ assertEquals(null, deviceManager.findDevice(entity1.getMacAddress(),
+ (short)42,
+ entity1.getIpv4Address(),
+ entity1.getSwitchDPID(),
+ entity1.getSwitchPort()));
+ assertEquals(null, deviceManager.findDevice(entity1.getMacAddress(),
+ null,
+ entity1.getIpv4Address(),
+ entity1.getSwitchDPID(),
+ entity1.getSwitchPort()));
+ assertEquals(d2, deviceManager.findDeviceByEntity(entity2));
+ assertEquals(null, deviceManager.findDeviceByEntity(entity2b));
+ assertEquals(d3, deviceManager.findDevice(entity3.getMacAddress(),
+ entity3.getVlan(),
+ entity3.getIpv4Address(),
+ entity3.getSwitchDPID(),
+ entity3.getSwitchPort()));
+ // switch and port not set. throws exception
+ exceptionCaught = false;
+ try {
+ assertEquals(null, deviceManager.findDevice(entity3.getMacAddress(),
+ entity3.getVlan(),
+ entity3.getIpv4Address(),
+ null,
+ null));
+ }
+ catch (IllegalArgumentException e) {
+ exceptionCaught = true;
+ }
+ if (!exceptionCaught)
+ fail("findDevice() did not throw IllegalArgumentException");
+ assertEquals(d4, deviceManager.findDeviceByEntity(entity4));
+ assertEquals(d5, deviceManager.findDevice(entity5.getMacAddress(),
+ entity5.getVlan(),
+ entity5.getIpv4Address(),
+ entity5.getSwitchDPID(),
+ entity5.getSwitchPort()));
+ // switch and port not set. throws exception (swith/port are key
+ // fields of IEntityClassifier but not d5.entityClass
+ exceptionCaught = false;
+ try {
+ assertEquals(d5, deviceManager.findDevice(entity5.getMacAddress(),
+ entity5.getVlan(),
+ entity5.getIpv4Address(),
+ null,
+ null));
+ }
+ catch (IllegalArgumentException e) {
+ exceptionCaught = true;
+ }
+ if (!exceptionCaught)
+ fail("findDevice() did not throw IllegalArgumentException");
+
+
+ Entity entityNoClass = new Entity(5L, (short)1, 5, -1L, 1, new Date());
+ assertEquals(null, deviceManager.findDeviceByEntity(entityNoClass));
+
+
+ // Now look up destination devices
+ assertEquals(d1, deviceManager.findDestDevice(d2,
+ entity1.getMacAddress(),
+ entity1.getVlan(),
+ entity1.getIpv4Address()));
+ assertEquals(d1, deviceManager.findDestDevice(d2,
+ entity1.getMacAddress(),
+ entity1.getVlan(),
+ null));
+ assertEquals(null, deviceManager.findDestDevice(d2,
+ entity1.getMacAddress(),
+ (short) -1,
+ 0));
+ }
+
+ @Test
+ public void testGetIPv4Addresses() {
+ // Looks like Date is only 1s granularity
+
+ ITopologyService mockTopology = createMock(ITopologyService.class);
+ deviceManager.topology = mockTopology;
+ expect(mockTopology.isAttachmentPointPort(anyLong(),
+ anyShort())).
+ andReturn(true).anyTimes();
+ expect(mockTopology.getL2DomainId(anyLong())).andReturn(1L).anyTimes();
+ expect(mockTopology.isConsistent(EasyMock.anyLong(),
+ EasyMock.anyShort(),
+ EasyMock.anyLong(),
+ EasyMock.anyShort()))
+ .andReturn(false)
+ .anyTimes();
+ expect(mockTopology.isBroadcastDomainPort(EasyMock.anyLong(),
+ EasyMock.anyShort()))
+ .andReturn(false)
+ .anyTimes();
+ expect(mockTopology.isInSameBroadcastDomain(EasyMock.anyLong(),
+ EasyMock.anyShort(),
+ EasyMock.anyLong(),
+ EasyMock.anyShort())).
+ andReturn(false).anyTimes();
+ replay(mockTopology);
+
+ Entity e1 = new Entity(1L, (short)1, null, null, null, new Date(2000));
+ Device d1 = deviceManager.learnDeviceByEntity(e1);
+ assertArrayEquals(new Integer[0], d1.getIPv4Addresses());
+
+
+ Entity e2 = new Entity(2L, (short)2, 2, null, null, new Date(2000));
+ Device d2 = deviceManager.learnDeviceByEntity(e2);
+ d2 = deviceManager.learnDeviceByEntity(e2);
+ assertArrayEquals(new Integer[] { 2 }, d2.getIPv4Addresses());
+ // More than one entity
+ Entity e2b = new Entity(2L, (short)2, null, 2L, 2, new Date(3000));
+ d2 = deviceManager.learnDeviceByEntity(e2b);
+ assertEquals(2, d2.entities.length);
+ assertArrayEquals(new Integer[] { 2 }, d2.getIPv4Addresses());
+ // and now add an entity with an IP
+ Entity e2c = new Entity(2L, (short)2, 2, 2L, 3, new Date(3000));
+ d2 = deviceManager.learnDeviceByEntity(e2c);
+ assertArrayEquals(new Integer[] { 2 }, d2.getIPv4Addresses());
+ assertEquals(3, d2.entities.length);
+
+ // Other devices with different IPs shouldn't interfere
+ Entity e3 = new Entity(3L, (short)3, 3, null, null, new Date(4000));
+ Entity e3b = new Entity(3L, (short)3, 3, 3L, 3, new Date(4400));
+ Device d3 = deviceManager.learnDeviceByEntity(e3);
+ d3 = deviceManager.learnDeviceByEntity(e3b);
+ assertArrayEquals(new Integer[] { 2 }, d2.getIPv4Addresses());
+ assertArrayEquals(new Integer[] { 3 }, d3.getIPv4Addresses());
+
+ // Add another IP to d3
+ Entity e3c = new Entity(3L, (short)3, 33, 3L, 3, new Date(4400));
+ d3 = deviceManager.learnDeviceByEntity(e3c);
+ Integer[] ips = d3.getIPv4Addresses();
+ Arrays.sort(ips);
+ assertArrayEquals(new Integer[] { 3, 33 }, ips);
+
+ // Add another device that also claims IP2 but is older than e2
+ Entity e4 = new Entity(4L, (short)4, 2, null, null, new Date(1000));
+ Entity e4b = new Entity(4L, (short)4, null, 4L, 4, new Date(1000));
+ Device d4 = deviceManager.learnDeviceByEntity(e4);
+ assertArrayEquals(new Integer[] { 2 }, d2.getIPv4Addresses());
+ assertArrayEquals(new Integer[0], d4.getIPv4Addresses());
+ // add another entity to d4
+ d4 = deviceManager.learnDeviceByEntity(e4b);
+ assertArrayEquals(new Integer[0], d4.getIPv4Addresses());
+
+ // Make e4 and e4a newer
+ Entity e4c = new Entity(4L, (short)4, 2, null, null, new Date(5000));
+ Entity e4d = new Entity(4L, (short)4, null, 4L, 5, new Date(5000));
+ d4 = deviceManager.learnDeviceByEntity(e4c);
+ d4 = deviceManager.learnDeviceByEntity(e4d);
+ assertArrayEquals(new Integer[0], d2.getIPv4Addresses());
+ // FIXME: d4 should not return IP4
+ assertArrayEquals(new Integer[] { 2 }, d4.getIPv4Addresses());
+
+ // Add another newer entity to d2 but with different IP
+ Entity e2d = new Entity(2L, (short)2, 22, 4L, 6, new Date(6000));
+ d2 = deviceManager.learnDeviceByEntity(e2d);
+ assertArrayEquals(new Integer[] { 22 }, d2.getIPv4Addresses());
+ assertArrayEquals(new Integer[] { 2 }, d4.getIPv4Addresses());
+
+ // new IP for d2,d4 but with same timestamp. Both devices get the IP
+ Entity e2e = new Entity(2L, (short)2, 42, 2L, 4, new Date(7000));
+ d2 = deviceManager.learnDeviceByEntity(e2e);
+ ips= d2.getIPv4Addresses();
+ Arrays.sort(ips);
+ assertArrayEquals(new Integer[] { 22, 42 }, ips);
+ Entity e4e = new Entity(4L, (short)4, 42, 4L, 7, new Date(7000));
+ d4 = deviceManager.learnDeviceByEntity(e4e);
+ ips= d4.getIPv4Addresses();
+ Arrays.sort(ips);
+ assertArrayEquals(new Integer[] { 2, 42 }, ips);
+
+ // add a couple more IPs
+ Entity e2f = new Entity(2L, (short)2, 4242, 2L, 5, new Date(8000));
+ d2 = deviceManager.learnDeviceByEntity(e2f);
+ ips= d2.getIPv4Addresses();
+ Arrays.sort(ips);
+ assertArrayEquals(new Integer[] { 22, 42, 4242 }, ips);
+ Entity e4f = new Entity(4L, (short)4, 4242, 4L, 8, new Date(9000));
+ d4 = deviceManager.learnDeviceByEntity(e4f);
+ ips= d4.getIPv4Addresses();
+ Arrays.sort(ips);
+ assertArrayEquals(new Integer[] { 2, 42, 4242 }, ips);
+ }
+
+ // TODO: this test should really go into a separate class that collects
+ // unit tests for Device
+ @Test
+ public void testGetSwitchPortVlanId() {
+ Entity entity1 = new Entity(1L, (short)1, null, 10L, 1, new Date());
+ Entity entity2 = new Entity(1L, null, null, 10L, 1, new Date());
+ Entity entity3 = new Entity(1L, (short)3, null, 1L, 1, new Date());
+ Entity entity4 = new Entity(1L, (short)42, null, 1L, 1, new Date());
+ Entity[] entities = new Entity[] { entity1, entity2,
+ entity3, entity4
+ };
+ Device d = new Device(null,1L, null, null, Arrays.asList(entities), null);
+ SwitchPort swp1x1 = new SwitchPort(1L, 1);
+ SwitchPort swp1x2 = new SwitchPort(1L, 2);
+ SwitchPort swp2x1 = new SwitchPort(2L, 1);
+ SwitchPort swp10x1 = new SwitchPort(10L, 1);
+ assertArrayEquals(new Short[] { -1, 1},
+ d.getSwitchPortVlanIds(swp10x1));
+ assertArrayEquals(new Short[] { 3, 42},
+ d.getSwitchPortVlanIds(swp1x1));
+ assertArrayEquals(new Short[0],
+ d.getSwitchPortVlanIds(swp1x2));
+ assertArrayEquals(new Short[0],
+ d.getSwitchPortVlanIds(swp2x1));
+ }
+
+ @Test
+ public void testReclassifyDevice() {
+ MockFlexEntityClassifier flexClassifier =
+ new MockFlexEntityClassifier();
+ deviceManager.entityClassifier= flexClassifier;
+ deviceManager.startUp(null);
+
+ ITopologyService mockTopology = createMock(ITopologyService.class);
+ deviceManager.topology = mockTopology;
+ expect(mockTopology.isAttachmentPointPort(anyLong(),
+ anyShort())).
+ andReturn(true).anyTimes();
+ expect(mockTopology.getL2DomainId(anyLong())).andReturn(1L).anyTimes();
+ expect(mockTopology.isConsistent(EasyMock.anyLong(),
+ EasyMock.anyShort(),
+ EasyMock.anyLong(),
+ EasyMock.anyShort()))
+ .andReturn(false)
+ .anyTimes();
+ expect(mockTopology.isBroadcastDomainPort(EasyMock.anyLong(),
+ EasyMock.anyShort()))
+ .andReturn(false)
+ .anyTimes();
+ replay(mockTopology);
+
+ //flexClassifier.createTestEntityClass("Class1");
+
+ Entity entity1 = new Entity(1L, (short)1, 1, 1L, 1, new Date());
+ Entity entity1b = new Entity(1L, (short)2, 1, 1L, 1, new Date());
+ Entity entity2 = new Entity(2L, (short)1, 2, 2L, 2, new Date());
+ Entity entity2b = new Entity(2L, (short)2, 2, 2L, 2, new Date());
+
+
+ Device d1 = deviceManager.learnDeviceByEntity(entity1);
+ Device d2 = deviceManager.learnDeviceByEntity(entity2);
+ Device d1b = deviceManager.learnDeviceByEntity(entity1b);
+ Device d2b = deviceManager.learnDeviceByEntity(entity2b);
+
+ d1 = deviceManager.getDeviceIteratorForQuery(entity1.getMacAddress(),
+ entity1.getVlan(), entity1.getIpv4Address(),
+ entity1.getSwitchDPID(), entity1.getSwitchPort())
+ .next();
+ d1b = deviceManager.getDeviceIteratorForQuery(entity1b.getMacAddress(),
+ entity1b.getVlan(), entity1b.getIpv4Address(),
+ entity1b.getSwitchDPID(), entity1b.getSwitchPort()).next();
+
+ assertEquals(d1, d1b);
+
+ d2 = deviceManager.getDeviceIteratorForQuery(entity2.getMacAddress(),
+ entity2.getVlan(), entity2.getIpv4Address(),
+ entity2.getSwitchDPID(), entity2.getSwitchPort()).next();
+ d2b = deviceManager.getDeviceIteratorForQuery(entity2b.getMacAddress(),
+ entity2b.getVlan(), entity2b.getIpv4Address(),
+ entity2b.getSwitchDPID(), entity2b.getSwitchPort()).next();
+ assertEquals(d2, d2b);
+
+ IEntityClass eC1 = flexClassifier.createTestEntityClass("C1");
+ IEntityClass eC2 = flexClassifier.createTestEntityClass("C2");
+
+ flexClassifier.addVlanEntities((short)1, eC1);
+ flexClassifier.addVlanEntities((short)2, eC1);
+
+ deviceManager.reclassifyDevice(d1);
+ deviceManager.reclassifyDevice(d2);
+
+ d1 = deviceManager.deviceMap.get(
+ deviceManager.primaryIndex.findByEntity(entity1));
+ d1b = deviceManager.deviceMap.get(
+ deviceManager.primaryIndex.findByEntity(entity1b));
+
+ assertEquals(d1, d1b);
+
+ d2 = deviceManager.deviceMap.get(
+ deviceManager.primaryIndex.findByEntity(entity2));
+ d2b = deviceManager.deviceMap.get(
+ deviceManager.primaryIndex.findByEntity(entity2b));
+
+ assertEquals(d2, d2b);
+
+ flexClassifier.addVlanEntities((short)1, eC2);
+
+ deviceManager.reclassifyDevice(d1);
+ deviceManager.reclassifyDevice(d2);
+ d1 = deviceManager.deviceMap.get(
+ deviceManager.primaryIndex.findByEntity(entity1));
+ d1b = deviceManager.deviceMap.get(
+ deviceManager.primaryIndex.findByEntity(entity1b));
+ d2 = deviceManager.deviceMap.get(
+ deviceManager.primaryIndex.findByEntity(entity2));
+ d2b = deviceManager.deviceMap.get(
+ deviceManager.primaryIndex.findByEntity(entity2b));
+
+ assertNotSame(d1, d1b);
+
+ assertNotSame(d2, d2b);
+
+ flexClassifier.addVlanEntities((short)1, eC1);
+ deviceManager.reclassifyDevice(d1);
+ deviceManager.reclassifyDevice(d2);
+ ClassState classState = deviceManager.classStateMap.get(eC1.getName());
+
+ Long deviceKey1 = null;
+ Long deviceKey1b = null;
+ Long deviceKey2 = null;
+ Long deviceKey2b = null;
+
+ deviceKey1 =
+ classState.classIndex.findByEntity(entity1);
+ deviceKey1b =
+ classState.classIndex.findByEntity(entity1b);
+ deviceKey2 =
+ classState.classIndex.findByEntity(entity2);
+ deviceKey2b =
+ classState.classIndex.findByEntity(entity2b);
+
+ assertEquals(deviceKey1, deviceKey1b);
+
+ assertEquals(deviceKey2, deviceKey2b);
+
+
+ }
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/devicemanager/internal/DeviceUniqueIndexTest.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/devicemanager/internal/DeviceUniqueIndexTest.java
new file mode 100644
index 0000000..2a6ee9d
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/devicemanager/internal/DeviceUniqueIndexTest.java
@@ -0,0 +1,167 @@
+/**
+* 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.devicemanager.internal;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.EnumSet;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.Iterator;
+
+import org.junit.Test;
+import net.floodlightcontroller.devicemanager.IDeviceService.DeviceField;
+import junit.framework.TestCase;
+
+/**
+ *
+ * @author gregor
+ *
+ */
+public class DeviceUniqueIndexTest extends TestCase {
+ protected Entity e1a;
+ protected Entity e1b;
+ protected Device d1;
+ protected Entity e2;
+ protected Entity e2alt;
+ protected Entity e3;
+ protected Entity e4;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ e1a = new Entity(1L, (short)1, 1, 1L, 1, new Date());
+ e1b = new Entity(1L, (short)2, 1, 1L, 1, new Date());
+ List<Entity> d1Entities = new ArrayList<Entity>(2);
+ d1Entities.add(e1a);
+ d1Entities.add(e1b);
+ d1 = new Device(null, Long.valueOf(1), null, null, d1Entities, null);
+
+ // e2 and e2 alt match in MAC and VLAN
+ e2 = new Entity(2L, (short)2, 2, 2L, 2, new Date());
+ e2alt = new Entity(2, (short)2, null, null, null, null);
+
+ // IP is null
+ e3 = new Entity(3L, (short)3, null, 3L, 3, new Date());
+
+ // IP and switch and port are null
+ e4 = new Entity(4L, (short)4, null, null, null, new Date());
+ }
+
+ /*
+ * Checks that the iterator it returns the elements in the Set expected
+ * Doesn't check how often an element is returned as long it's at least
+ * once
+ */
+ protected void verifyIterator(Set<Long> expected, Iterator<Long> it) {
+ HashSet<Long> actual = new HashSet<Long>();
+ while (it.hasNext()) {
+ actual.add(it.next());
+ }
+ assertEquals(expected, actual);
+ }
+
+ @Test
+ public void testDeviceUniqueIndex() {
+ DeviceUniqueIndex idx1 = new DeviceUniqueIndex(
+ EnumSet.of(DeviceField.MAC,
+ DeviceField.VLAN));
+
+ idx1.updateIndex(d1, d1.getDeviceKey());
+ idx1.updateIndex(e2, 2L);
+
+ //-------------
+ // Test findByEntity lookups
+ assertEquals(Long.valueOf(1L), idx1.findByEntity(e1a));
+ assertEquals(Long.valueOf(1L), idx1.findByEntity(e1b));
+ assertEquals(Long.valueOf(2L), idx1.findByEntity(e2));
+ // we didn't add e2alt but since they key fields are the same we
+ // should find it
+ assertEquals(Long.valueOf(2L), idx1.findByEntity(e2alt));
+ assertEquals(null, idx1.findByEntity(e3));
+ assertEquals(null, idx1.findByEntity(e4));
+
+ //-------------
+ // Test getAll()
+ HashSet<Long> expectedKeys = new HashSet<Long>();
+ expectedKeys.add(1L);
+ expectedKeys.add(2L);
+ verifyIterator(expectedKeys, idx1.getAll());
+
+
+ //-------------
+ // Test queryByEntity()
+ verifyIterator(Collections.<Long>singleton(1L),
+ idx1.queryByEntity(e1a));
+ verifyIterator(Collections.<Long>singleton(1L),
+ idx1.queryByEntity(e1b));
+ verifyIterator(Collections.<Long>singleton(2L),
+ idx1.queryByEntity(e2));
+ verifyIterator(Collections.<Long>singleton(2L),
+ idx1.queryByEntity(e2alt));
+ assertEquals(false, idx1.queryByEntity(e3).hasNext());
+ assertEquals(false, idx1.queryByEntity(e3).hasNext());
+
+
+ //-------------
+ // Test removal
+ idx1.removeEntity(e1a, 42L); // No-op. e1a isn't mapped to this key
+ assertEquals(Long.valueOf(1L), idx1.findByEntity(e1a));
+ idx1.removeEntity(e1a, 1L);
+ assertEquals(null, idx1.findByEntity(e1a));
+ assertEquals(Long.valueOf(1L), idx1.findByEntity(e1b));
+ assertEquals(Long.valueOf(2L), idx1.findByEntity(e2));
+ idx1.removeEntity(e2);
+ assertEquals(null, idx1.findByEntity(e2));
+ assertEquals(Long.valueOf(1L), idx1.findByEntity(e1b));
+
+
+ //-------------
+ // Test null keys
+ DeviceUniqueIndex idx2 = new DeviceUniqueIndex(
+ EnumSet.of(DeviceField.IPV4,
+ DeviceField.SWITCH));
+ // only one key field is null
+ idx2.updateIndex(e3, 3L);
+ assertEquals(Long.valueOf(3L), idx2.findByEntity(e3));
+ e3.ipv4Address = 3;
+ assertEquals(null, idx2.findByEntity(e3));
+ // all key fields are null
+ idx2.updateIndex(e4, 4L);
+ assertEquals(null, idx2.findByEntity(e4));
+ Device d4 = new Device(null, 4L, null, null, Collections.<Entity>singleton(e4), null);
+ idx2.updateIndex(d4, 4L);
+ assertEquals(null, idx2.findByEntity(e4));
+
+
+
+ //-------------
+ // entity already exists with different deviceKey
+ DeviceUniqueIndex idx3 = new DeviceUniqueIndex(
+ EnumSet.of(DeviceField.MAC,
+ DeviceField.VLAN));
+ idx3.updateIndex(e1a, 42L);
+ assertEquals(false, idx3.updateIndex(d1, 1L));
+ // TODO: shouldn't this fail as well so that the behavior
+ // is consistent?
+ idx3.updateIndex(e1a, 1L);
+ // anyways. We can now add d1 ;-)
+ assertEquals(true, idx3.updateIndex(d1, 1L));
+ }
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/devicemanager/test/MockDevice.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/devicemanager/test/MockDevice.java
new file mode 100644
index 0000000..5460ea3
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/devicemanager/test/MockDevice.java
@@ -0,0 +1,91 @@
+/**
+* Copyright 2011,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.devicemanager.test;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.TreeSet;
+
+import net.floodlightcontroller.devicemanager.IEntityClass;
+import net.floodlightcontroller.devicemanager.SwitchPort;
+import net.floodlightcontroller.devicemanager.internal.AttachmentPoint;
+import net.floodlightcontroller.devicemanager.internal.Device;
+import net.floodlightcontroller.devicemanager.internal.DeviceManagerImpl;
+import net.floodlightcontroller.devicemanager.internal.Entity;
+
+/**
+ * This mock device removes the dependency on topology and a parent device
+ * manager and simply assumes all its entities are current and correct
+ */
+public class MockDevice extends Device {
+
+ public MockDevice(DeviceManagerImpl deviceManager,
+ Long deviceKey,
+ Entity entity,
+ IEntityClass entityClass) {
+ super(deviceManager, deviceKey, entity, entityClass);
+ }
+
+ public MockDevice(Device device, Entity newEntity) {
+ super(device, newEntity);
+ }
+
+ public MockDevice(DeviceManagerImpl deviceManager, Long deviceKey,
+ List<AttachmentPoint> aps,
+ List<AttachmentPoint> trueAPs,
+ Collection<Entity> entities,
+ IEntityClass entityClass) {
+ super(deviceManager, deviceKey, aps, trueAPs, entities, entityClass);
+ }
+
+ @Override
+ public Integer[] getIPv4Addresses() {
+ TreeSet<Integer> vals = new TreeSet<Integer>();
+ for (Entity e : entities) {
+ if (e.getIpv4Address() == null) continue;
+ vals.add(e.getIpv4Address());
+ }
+
+ return vals.toArray(new Integer[vals.size()]);
+ }
+
+ @Override
+ public SwitchPort[] getAttachmentPoints() {
+ ArrayList<SwitchPort> vals =
+ new ArrayList<SwitchPort>(entities.length);
+ for (Entity e : entities) {
+ if (e.getSwitchDPID() != null &&
+ e.getSwitchPort() != null &&
+ deviceManager.isValidAttachmentPoint(e.getSwitchDPID(), e.getSwitchPort())) {
+ SwitchPort sp = new SwitchPort(e.getSwitchDPID(),
+ e.getSwitchPort());
+ vals.add(sp);
+ }
+ }
+ return vals.toArray(new SwitchPort[vals.size()]);
+ }
+
+ @Override
+ public String toString() {
+ String rv = "MockDevice[entities=+";
+ rv += entities.toString();
+ rv += "]";
+ return rv;
+ }
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/devicemanager/test/MockDeviceManager.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/devicemanager/test/MockDeviceManager.java
new file mode 100644
index 0000000..8b539f6
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/devicemanager/test/MockDeviceManager.java
@@ -0,0 +1,103 @@
+package net.floodlightcontroller.devicemanager.test;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.Set;
+
+import net.floodlightcontroller.devicemanager.IDevice;
+import net.floodlightcontroller.devicemanager.IDeviceListener;
+import net.floodlightcontroller.devicemanager.IEntityClass;
+import net.floodlightcontroller.devicemanager.IEntityClassifierService;
+import net.floodlightcontroller.devicemanager.internal.AttachmentPoint;
+import net.floodlightcontroller.devicemanager.internal.Device;
+import net.floodlightcontroller.devicemanager.internal.DeviceManagerImpl;
+import net.floodlightcontroller.devicemanager.internal.Entity;
+
+/**
+ * Mock device manager useful for unit tests
+ * @author readams
+ */
+public class MockDeviceManager extends DeviceManagerImpl {
+ /**
+ * Set a new IEntityClassifier
+ * Use this as a quick way to use a particular entity classifier in a
+ * single test without having to setup the full FloodlightModuleContext
+ * again.
+ * @param ecs
+ */
+ public void setEntityClassifier(IEntityClassifierService ecs) {
+ this.entityClassifier = ecs;
+ this.startUp(null);
+ }
+
+ /**
+ * Learn a device using the given characteristics.
+ * @param macAddress the MAC
+ * @param vlan the VLAN (can be null)
+ * @param ipv4Address the IP (can be null)
+ * @param switchDPID the attachment point switch DPID (can be null)
+ * @param switchPort the attachment point switch port (can be null)
+ * @param processUpdates if false, will not send updates. Note that this
+ * method is not thread safe if this is false
+ * @return the device, either new or not
+ */
+ public IDevice learnEntity(long macAddress, Short vlan,
+ Integer ipv4Address, Long switchDPID,
+ Integer switchPort,
+ boolean processUpdates) {
+ Set<IDeviceListener> listeners = deviceListeners;
+ if (!processUpdates) {
+ deviceListeners = Collections.<IDeviceListener>emptySet();
+ }
+
+ if (vlan != null && vlan.shortValue() <= 0)
+ vlan = null;
+ if (ipv4Address != null && ipv4Address == 0)
+ ipv4Address = null;
+ IDevice res = learnDeviceByEntity(new Entity(macAddress, vlan,
+ ipv4Address, switchDPID,
+ switchPort, new Date()));
+ deviceListeners = listeners;
+ return res;
+ }
+
+ /**
+ * Learn a device using the given characteristics.
+ * @param macAddress the MAC
+ * @param vlan the VLAN (can be null)
+ * @param ipv4Address the IP (can be null)
+ * @param switchDPID the attachment point switch DPID (can be null)
+ * @param switchPort the attachment point switch port (can be null)
+ * @return the device, either new or not
+ */
+ public IDevice learnEntity(long macAddress, Short vlan,
+ Integer ipv4Address, Long switchDPID,
+ Integer switchPort) {
+ return learnEntity(macAddress, vlan, ipv4Address,
+ switchDPID, switchPort, true);
+ }
+
+ @Override
+ protected Device allocateDevice(Long deviceKey,
+ Entity entity,
+ IEntityClass entityClass) {
+ return new MockDevice(this, deviceKey, entity, entityClass);
+ }
+
+ @Override
+ protected Device allocateDevice(Long deviceKey,
+ List<AttachmentPoint> aps,
+ List<AttachmentPoint> trueAPs,
+ Collection<Entity> entities,
+ IEntityClass entityClass) {
+ return new MockDevice(this, deviceKey, aps, trueAPs, entities, entityClass);
+ }
+
+ @Override
+ protected Device allocateDevice(Device device,
+ Entity entity) {
+ return new MockDevice(device, entity);
+ }
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/devicemanager/test/MockEntityClassifier.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/devicemanager/test/MockEntityClassifier.java
new file mode 100644
index 0000000..679dc9a
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/devicemanager/test/MockEntityClassifier.java
@@ -0,0 +1,47 @@
+package net.floodlightcontroller.devicemanager.test;
+
+import static net.floodlightcontroller.devicemanager.IDeviceService.DeviceField.MAC;
+import static net.floodlightcontroller.devicemanager.IDeviceService.DeviceField.PORT;
+import static net.floodlightcontroller.devicemanager.IDeviceService.DeviceField.SWITCH;
+import static net.floodlightcontroller.devicemanager.IDeviceService.DeviceField.VLAN;
+
+import java.util.EnumSet;
+
+import net.floodlightcontroller.devicemanager.IDeviceService;
+import net.floodlightcontroller.devicemanager.IEntityClass;
+import net.floodlightcontroller.devicemanager.IDeviceService.DeviceField;
+import net.floodlightcontroller.devicemanager.internal.DefaultEntityClassifier;
+import net.floodlightcontroller.devicemanager.internal.Entity;
+
+/** A simple IEntityClassifier. Useful for tests that need IEntityClassifiers
+ * and IEntityClass'es with switch and/or port key fields
+ */
+public class MockEntityClassifier extends DefaultEntityClassifier {
+ public static class TestEntityClass implements IEntityClass {
+ @Override
+ public EnumSet<DeviceField> getKeyFields() {
+ return EnumSet.of(MAC, VLAN, SWITCH, PORT);
+ }
+
+ @Override
+ public String getName() {
+ return "TestEntityClass";
+ }
+ }
+ public static IEntityClass testEC =
+ new MockEntityClassifier.TestEntityClass();
+
+ @Override
+ public IEntityClass classifyEntity(Entity entity) {
+ if (entity.getSwitchDPID() >= 10L) {
+ return testEC;
+ }
+ return DefaultEntityClassifier.entityClass;
+ }
+
+ @Override
+ public EnumSet<IDeviceService.DeviceField> getKeyFields() {
+ return EnumSet.of(MAC, VLAN, SWITCH, PORT);
+ }
+
+}
\ No newline at end of file
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/devicemanager/test/MockEntityClassifierMac.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/devicemanager/test/MockEntityClassifierMac.java
new file mode 100644
index 0000000..398b6c0
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/devicemanager/test/MockEntityClassifierMac.java
@@ -0,0 +1,57 @@
+package net.floodlightcontroller.devicemanager.test;
+
+import static net.floodlightcontroller.devicemanager.IDeviceService.DeviceField.MAC;
+import static net.floodlightcontroller.devicemanager.IDeviceService.DeviceField.PORT;
+import static net.floodlightcontroller.devicemanager.IDeviceService.DeviceField.SWITCH;
+import static net.floodlightcontroller.devicemanager.IDeviceService.DeviceField.VLAN;
+
+import java.util.EnumSet;
+
+import net.floodlightcontroller.devicemanager.IDeviceService;
+import net.floodlightcontroller.devicemanager.IEntityClass;
+import net.floodlightcontroller.devicemanager.IDeviceService.DeviceField;
+import net.floodlightcontroller.devicemanager.internal.DefaultEntityClassifier;
+import net.floodlightcontroller.devicemanager.internal.Entity;
+
+/** A simple IEntityClassifier. Useful for tests that need an IEntityClassifier
+ * with switch/port as key fields.
+ */
+public class MockEntityClassifierMac extends DefaultEntityClassifier {
+ public static class TestEntityClassMac implements IEntityClass {
+ protected String name;
+ public TestEntityClassMac(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public EnumSet<DeviceField> getKeyFields() {
+ return EnumSet.of(MAC, VLAN);
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+ }
+ public static IEntityClass testECMac1 =
+ new MockEntityClassifierMac.TestEntityClassMac("testECMac1");
+ public static IEntityClass testECMac2 =
+ new MockEntityClassifierMac.TestEntityClassMac("testECMac2");
+
+ @Override
+ public IEntityClass classifyEntity(Entity entity) {
+ if (entity.getSwitchDPID() == 1L) {
+ return testECMac1;
+ } else if (entity.getSwitchDPID() == 2L) {
+ return testECMac2;
+ } else if (entity.getSwitchDPID() == -1L) {
+ return null;
+ }
+ return DefaultEntityClassifier.entityClass;
+ }
+
+ @Override
+ public EnumSet<IDeviceService.DeviceField> getKeyFields() {
+ return EnumSet.of(MAC, VLAN, SWITCH, PORT);
+ }
+}
\ No newline at end of file
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/devicemanager/test/MockFlexEntityClassifier.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/devicemanager/test/MockFlexEntityClassifier.java
new file mode 100644
index 0000000..2224551
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/devicemanager/test/MockFlexEntityClassifier.java
@@ -0,0 +1,78 @@
+/**
+ *
+ */
+package net.floodlightcontroller.devicemanager.test;
+
+import static net.floodlightcontroller.devicemanager.IDeviceService.DeviceField.MAC;
+import static net.floodlightcontroller.devicemanager.IDeviceService.DeviceField.PORT;
+import static net.floodlightcontroller.devicemanager.IDeviceService.DeviceField.SWITCH;
+import static net.floodlightcontroller.devicemanager.IDeviceService.DeviceField.VLAN;
+
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.Map;
+
+import net.floodlightcontroller.devicemanager.IDeviceService;
+import net.floodlightcontroller.devicemanager.IEntityClass;
+import net.floodlightcontroller.devicemanager.IDeviceService.DeviceField;
+import net.floodlightcontroller.devicemanager.internal.DefaultEntityClassifier;
+import net.floodlightcontroller.devicemanager.internal.Entity;
+
+/**
+ * Extension to simple entity classifier to help in unit tests to provide table
+ * based multiple entity classification mock for reclassification tests
+ *
+ */
+public class MockFlexEntityClassifier extends DefaultEntityClassifier {
+ Map <Long, IEntityClass> switchEntities;
+ Map <Short, IEntityClass> vlanEntities;
+
+ public static class TestEntityClass implements IEntityClass {
+ String name;
+ public TestEntityClass(String name) {
+ this.name = name;
+ }
+ @Override
+ public EnumSet<DeviceField> getKeyFields() {
+ return EnumSet.of(MAC);
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+ }
+ public static IEntityClass defaultClass = new TestEntityClass("default");
+ public MockFlexEntityClassifier() {
+ switchEntities = new HashMap<Long, IEntityClass> ();
+ vlanEntities = new HashMap<Short, IEntityClass> ();
+ }
+ public IEntityClass createTestEntityClass(String name) {
+ return new TestEntityClass(name);
+ }
+
+ public void addSwitchEntity(Long dpid, IEntityClass entityClass) {
+ switchEntities.put(dpid, entityClass);
+ }
+ public void removeSwitchEntity(Long dpid) {
+ switchEntities.remove(dpid);
+ }
+ public void addVlanEntities(Short vlan, IEntityClass entityClass) {
+ vlanEntities.put(vlan, entityClass);
+ }
+ public void removeVlanEntities(Short vlan) {
+ vlanEntities.remove(vlan);
+ }
+ @Override
+ public IEntityClass classifyEntity(Entity entity) {
+ if (switchEntities.containsKey(entity.getSwitchDPID()))
+ return switchEntities.get(entity.getSwitchDPID());
+ if (vlanEntities.containsKey(entity.getVlan()))
+ return vlanEntities.get(entity.getVlan());
+ return defaultClass;
+ }
+ @Override
+ public EnumSet<IDeviceService.DeviceField> getKeyFields() {
+ return EnumSet.of(MAC, VLAN, SWITCH, PORT);
+ }
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/firewall/FirewallTest.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/firewall/FirewallTest.java
new file mode 100644
index 0000000..959bb49
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/firewall/FirewallTest.java
@@ -0,0 +1,513 @@
+package net.floodlightcontroller.firewall;
+
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import net.floodlightcontroller.core.FloodlightContext;
+import net.floodlightcontroller.core.IFloodlightProviderService;
+import net.floodlightcontroller.core.IOFSwitch;
+import net.floodlightcontroller.core.module.FloodlightModuleContext;
+import net.floodlightcontroller.core.module.FloodlightModuleException;
+import net.floodlightcontroller.core.test.MockFloodlightProvider;
+import net.floodlightcontroller.packet.ARP;
+import net.floodlightcontroller.packet.Data;
+import net.floodlightcontroller.packet.Ethernet;
+import net.floodlightcontroller.packet.IPacket;
+import net.floodlightcontroller.packet.IPv4;
+import net.floodlightcontroller.packet.TCP;
+import net.floodlightcontroller.packet.UDP;
+import net.floodlightcontroller.restserver.IRestApiService;
+import net.floodlightcontroller.restserver.RestApiServer;
+import net.floodlightcontroller.routing.IRoutingDecision;
+import net.floodlightcontroller.storage.IStorageSourceService;
+import net.floodlightcontroller.storage.memory.MemoryStorageSource;
+import net.floodlightcontroller.test.FloodlightTestCase;
+import net.floodlightcontroller.util.MACAddress;
+
+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.OFType;
+import org.openflow.util.HexString;
+
+/**
+ * Unit test for stateless firewall implemented as a Google Summer of Code project.
+ *
+ * @author Amer Tahir
+ */
+public class FirewallTest extends FloodlightTestCase {
+ protected MockFloodlightProvider mockFloodlightProvider;
+ protected FloodlightContext cntx;
+ protected OFPacketIn packetIn;
+ protected IOFSwitch sw;
+ protected IPacket tcpPacket;
+ protected IPacket broadcastARPPacket;
+ protected IPacket ARPReplyPacket;
+ protected IPacket broadcastIPPacket;
+ protected IPacket tcpPacketReply;
+ protected IPacket broadcastMalformedPacket;
+ private Firewall firewall;
+ public static String TestSwitch1DPID = "00:00:00:00:00:00:00:01";
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ cntx = new FloodlightContext();
+ mockFloodlightProvider = getMockFloodlightProvider();
+ firewall = new Firewall();
+ IStorageSourceService storageService = new MemoryStorageSource();
+ RestApiServer restApi = new RestApiServer();
+
+ // Mock switches
+ long dpid = HexString.toLong(TestSwitch1DPID);
+ sw = EasyMock.createNiceMock(IOFSwitch.class);
+ expect(sw.getId()).andReturn(dpid).anyTimes();
+ expect(sw.getStringId()).andReturn(TestSwitch1DPID).anyTimes();
+ replay(sw);
+ // Load the switch map
+ Map<Long, IOFSwitch> switches = new HashMap<Long, IOFSwitch>();
+ switches.put(dpid, sw);
+ mockFloodlightProvider.setSwitches(switches);
+
+ FloodlightModuleContext fmc = new FloodlightModuleContext();
+ fmc.addService(IFloodlightProviderService.class,
+ mockFloodlightProvider);
+ fmc.addService(IFirewallService.class, firewall);
+ fmc.addService(IStorageSourceService.class, storageService);
+ fmc.addService(IRestApiService.class, restApi);
+
+ try {
+ restApi.init(fmc);
+ } catch (FloodlightModuleException e) {
+ e.printStackTrace();
+ }
+
+ firewall.init(fmc);
+ firewall.startUp(fmc);
+
+ // Build our test packet
+ this.tcpPacket = new Ethernet()
+ .setDestinationMACAddress("00:11:22:33:44:55")
+ .setSourceMACAddress("00:44:33:22:11:00")
+ .setVlanID((short) 42)
+ .setEtherType(Ethernet.TYPE_IPv4)
+ .setPayload(
+ new IPv4()
+ .setTtl((byte) 128)
+ .setSourceAddress("192.168.1.1")
+ .setDestinationAddress("192.168.1.2")
+ .setPayload(new TCP()
+ .setSourcePort((short) 81)
+ .setDestinationPort((short) 80)
+ .setPayload(new Data(new byte[] {0x01}))));
+
+ // Build a broadcast ARP packet
+ this.broadcastARPPacket = new Ethernet()
+ .setDestinationMACAddress("FF:FF:FF:FF:FF:FF")
+ .setSourceMACAddress("00:44:33:22:11:00")
+ .setVlanID((short) 42)
+ .setEtherType(Ethernet.TYPE_ARP)
+ .setPayload(
+ new ARP()
+ .setHardwareType(ARP.HW_TYPE_ETHERNET)
+ .setProtocolType(ARP.PROTO_TYPE_IP)
+ .setOpCode(ARP.OP_REQUEST)
+ .setHardwareAddressLength((byte)6)
+ .setProtocolAddressLength((byte)4)
+ .setSenderHardwareAddress(Ethernet.toMACAddress("00:44:33:22:11:00"))
+ .setSenderProtocolAddress(IPv4.toIPv4Address("192.168.1.1"))
+ .setTargetHardwareAddress(Ethernet.toMACAddress("00:00:00:00:00:00"))
+ .setTargetProtocolAddress(IPv4.toIPv4Address("192.168.1.2"))
+ .setPayload(new Data(new byte[] {0x01})));
+
+ // Build a ARP packet
+ this.ARPReplyPacket = new Ethernet()
+ .setDestinationMACAddress("00:44:33:22:11:00")
+ .setSourceMACAddress("00:11:22:33:44:55")
+ .setVlanID((short) 42)
+ .setEtherType(Ethernet.TYPE_ARP)
+ .setPayload(
+ new ARP()
+ .setHardwareType(ARP.HW_TYPE_ETHERNET)
+ .setProtocolType(ARP.PROTO_TYPE_IP)
+ .setOpCode(ARP.OP_REQUEST)
+ .setHardwareAddressLength((byte)6)
+ .setProtocolAddressLength((byte)4)
+ .setSenderHardwareAddress(Ethernet.toMACAddress("00:11:22:33:44:55"))
+ .setSenderProtocolAddress(IPv4.toIPv4Address("192.168.1.2"))
+ .setTargetHardwareAddress(Ethernet.toMACAddress("00:44:33:22:11:00"))
+ .setTargetProtocolAddress(IPv4.toIPv4Address("192.168.1.1"))
+ .setPayload(new Data(new byte[] {0x01})));
+
+ // Build a broadcast IP packet
+ this.broadcastIPPacket = new Ethernet()
+ .setDestinationMACAddress("FF:FF:FF:FF:FF:FF")
+ .setSourceMACAddress("00:44:33:22:11:00")
+ .setVlanID((short) 42)
+ .setEtherType(Ethernet.TYPE_IPv4)
+ .setPayload(
+ new IPv4()
+ .setTtl((byte) 128)
+ .setSourceAddress("192.168.1.1")
+ .setDestinationAddress("192.168.1.255")
+ .setPayload(new UDP()
+ .setSourcePort((short) 5000)
+ .setDestinationPort((short) 5001)
+ .setPayload(new Data(new byte[] {0x01}))));
+
+ // Build a malformed broadcast packet
+ this.broadcastMalformedPacket = new Ethernet()
+ .setDestinationMACAddress("FF:FF:FF:FF:FF:FF")
+ .setSourceMACAddress("00:44:33:22:11:00")
+ .setVlanID((short) 42)
+ .setEtherType(Ethernet.TYPE_IPv4)
+ .setPayload(
+ new IPv4()
+ .setTtl((byte) 128)
+ .setSourceAddress("192.168.1.1")
+ .setDestinationAddress("192.168.1.2")
+ .setPayload(new UDP()
+ .setSourcePort((short) 5000)
+ .setDestinationPort((short) 5001)
+ .setPayload(new Data(new byte[] {0x01}))));
+
+ this.tcpPacketReply = new Ethernet()
+ .setDestinationMACAddress("00:44:33:22:11:00")
+ .setSourceMACAddress("00:11:22:33:44:55")
+ .setVlanID((short) 42)
+ .setEtherType(Ethernet.TYPE_IPv4)
+ .setPayload(
+ new IPv4()
+ .setTtl((byte) 128)
+ .setSourceAddress("192.168.1.2")
+ .setDestinationAddress("192.168.1.1")
+ .setPayload(new TCP()
+ .setSourcePort((short) 80)
+ .setDestinationPort((short) 81)
+ .setPayload(new Data(new byte[] {0x02}))));
+ }
+
+ protected void setPacketIn(IPacket packet) {
+ byte[] serializedPacket = packet.serialize();
+ // Build the PacketIn
+ this.packetIn = ((OFPacketIn) mockFloodlightProvider.getOFMessageFactory().getMessage(OFType.PACKET_IN))
+ .setBufferId(-1)
+ .setInPort((short) 1)
+ .setPacketData(serializedPacket)
+ .setReason(OFPacketInReason.NO_MATCH)
+ .setTotalLength((short) serializedPacket.length);
+
+ // Add the packet to the context store
+ IFloodlightProviderService.bcStore.
+ put(cntx,
+ IFloodlightProviderService.CONTEXT_PI_PAYLOAD,
+ (Ethernet)packet);
+ }
+
+ @Test
+ public void testNoRules() throws Exception {
+ // enable firewall first
+ firewall.enableFirewall(true);
+ // simulate a packet-in event
+ this.setPacketIn(tcpPacket);
+ firewall.receive(sw, this.packetIn, cntx);
+ verify(sw);
+
+ assertEquals(0, firewall.rules.size());
+
+ IRoutingDecision decision = IRoutingDecision.rtStore.get(cntx, IRoutingDecision.CONTEXT_DECISION);
+ // no rules to match, so firewall should deny
+ assertEquals(decision.getRoutingAction(), IRoutingDecision.RoutingAction.DROP);
+ }
+
+ @Test
+ public void testReadRulesFromStorage() throws Exception {
+ // add 2 rules first
+ FirewallRule rule = new FirewallRule();
+ rule.in_port = 2;
+ rule.dl_src = MACAddress.valueOf("00:00:00:00:00:01").toLong();
+ rule.dl_dst = MACAddress.valueOf("00:00:00:00:00:02").toLong();
+ rule.priority = 1;
+ rule.action = FirewallRule.FirewallAction.DENY;
+ firewall.addRule(rule);
+ rule = new FirewallRule();
+ rule.in_port = 3;
+ rule.dl_src = MACAddress.valueOf("00:00:00:00:00:02").toLong();
+ rule.dl_dst = MACAddress.valueOf("00:00:00:00:00:01").toLong();
+ rule.nw_proto = IPv4.PROTOCOL_TCP;
+ rule.wildcard_nw_proto = false;
+ rule.tp_dst = 80;
+ rule.priority = 2;
+ rule.action = FirewallRule.FirewallAction.ALLOW;
+ firewall.addRule(rule);
+
+ List<FirewallRule> rules = firewall.readRulesFromStorage();
+ // verify rule 1
+ FirewallRule r = rules.get(0);
+ assertEquals(r.in_port, 2);
+ assertEquals(r.priority, 1);
+ assertEquals(r.dl_src, MACAddress.valueOf("00:00:00:00:00:01").toLong());
+ assertEquals(r.dl_dst, MACAddress.valueOf("00:00:00:00:00:02").toLong());
+ assertEquals(r.action, FirewallRule.FirewallAction.DENY);
+ // verify rule 2
+ r = rules.get(1);
+ assertEquals(r.in_port, 3);
+ assertEquals(r.priority, 2);
+ assertEquals(r.dl_src, MACAddress.valueOf("00:00:00:00:00:02").toLong());
+ assertEquals(r.dl_dst, MACAddress.valueOf("00:00:00:00:00:01").toLong());
+ assertEquals(r.nw_proto, IPv4.PROTOCOL_TCP);
+ assertEquals(r.tp_dst, 80);
+ assertEquals(r.wildcard_nw_proto, false);
+ assertEquals(r.action, FirewallRule.FirewallAction.ALLOW);
+ }
+
+ @Test
+ public void testRuleInsertionIntoStorage() throws Exception {
+ // add TCP rule
+ FirewallRule rule = new FirewallRule();
+ rule.nw_proto = IPv4.PROTOCOL_TCP;
+ rule.wildcard_nw_proto = false;
+ rule.priority = 1;
+ firewall.addRule(rule);
+
+ List<Map<String, Object>> rulesFromStorage = firewall.getStorageRules();
+ assertEquals(1, rulesFromStorage.size());
+ assertEquals(Integer.parseInt((String)rulesFromStorage.get(0).get("ruleid")), rule.ruleid);
+ }
+
+ @Test
+ public void testRuleDeletion() throws Exception {
+ // add TCP rule
+ FirewallRule rule = new FirewallRule();
+ rule.nw_proto = IPv4.PROTOCOL_TCP;
+ rule.wildcard_nw_proto = false;
+ rule.priority = 1;
+ firewall.addRule(rule);
+ int rid = rule.ruleid;
+
+ List<Map<String, Object>> rulesFromStorage = firewall.getStorageRules();
+ assertEquals(1, rulesFromStorage.size());
+ assertEquals(Integer.parseInt((String)rulesFromStorage.get(0).get("ruleid")), rid);
+
+ // delete rule
+ firewall.deleteRule(rid);
+ rulesFromStorage = firewall.getStorageRules();
+ assertEquals(0, rulesFromStorage.size());
+ }
+
+ @Test
+ public void testFirewallDisabled() throws Exception {
+ // firewall isn't enabled by default
+ // so, it shouldn't make any decision
+
+ // add TCP rule
+ FirewallRule rule = new FirewallRule();
+ rule.nw_proto = IPv4.PROTOCOL_TCP;
+ rule.wildcard_nw_proto = false;
+ rule.priority = 1;
+ firewall.addRule(rule);
+
+ this.setPacketIn(tcpPacket);
+ firewall.receive(sw, this.packetIn, cntx);
+ verify(sw);
+
+ assertEquals(1, firewall.rules.size());
+
+ IRoutingDecision decision = IRoutingDecision.rtStore.get(cntx, IRoutingDecision.CONTEXT_DECISION);
+ assertNull(decision);
+ }
+
+ @Test
+ public void testSimpleAllowRule() throws Exception {
+ // enable firewall first
+ firewall.enableFirewall(true);
+
+ // add TCP rule
+ FirewallRule rule = new FirewallRule();
+ rule.dl_type = Ethernet.TYPE_IPv4;
+ rule.wildcard_dl_type = false;
+ rule.nw_proto = IPv4.PROTOCOL_TCP;
+ rule.wildcard_nw_proto = false;
+ // source is IP 192.168.1.2
+ rule.nw_src_prefix = IPv4.toIPv4Address("192.168.1.2");
+ rule.wildcard_nw_src = false;
+ // dest is network 192.168.1.0/24
+ rule.nw_dst_prefix = IPv4.toIPv4Address("192.168.1.0");
+ rule.nw_dst_maskbits = 24;
+ rule.wildcard_nw_dst = false;
+ rule.priority = 1;
+ firewall.addRule(rule);
+
+ // simulate a packet-in events
+
+ this.setPacketIn(tcpPacketReply);
+ firewall.receive(sw, this.packetIn, cntx);
+ verify(sw);
+
+ IRoutingDecision decision = IRoutingDecision.rtStore.get(cntx, IRoutingDecision.CONTEXT_DECISION);
+ assertEquals(decision.getRoutingAction(), IRoutingDecision.RoutingAction.FORWARD_OR_FLOOD);
+
+ // clear decision
+ IRoutingDecision.rtStore.remove(cntx, IRoutingDecision.CONTEXT_DECISION);
+
+ this.setPacketIn(tcpPacket);
+ firewall.receive(sw, this.packetIn, cntx);
+ verify(sw);
+
+ decision = IRoutingDecision.rtStore.get(cntx, IRoutingDecision.CONTEXT_DECISION);
+ assertEquals(decision.getRoutingAction(), IRoutingDecision.RoutingAction.DROP);
+ }
+
+ @Test
+ public void testOverlappingRules() throws Exception {
+ firewall.enableFirewall(true);
+
+ // add TCP port 80 (destination only) allow rule
+ FirewallRule rule = new FirewallRule();
+ rule.dl_type = Ethernet.TYPE_IPv4;
+ rule.wildcard_dl_type = false;
+ rule.nw_proto = IPv4.PROTOCOL_TCP;
+ rule.wildcard_nw_proto = false;
+ rule.tp_dst = 80;
+ rule.priority = 1;
+ firewall.addRule(rule);
+
+ // add block all rule
+ rule = new FirewallRule();
+ rule.action = FirewallRule.FirewallAction.DENY;
+ rule.priority = 2;
+ firewall.addRule(rule);
+
+ assertEquals(2, firewall.rules.size());
+
+ // packet destined to TCP port 80 - should be allowed
+
+ this.setPacketIn(tcpPacket);
+ firewall.receive(sw, this.packetIn, cntx);
+ verify(sw);
+
+ IRoutingDecision decision = IRoutingDecision.rtStore.get(cntx, IRoutingDecision.CONTEXT_DECISION);
+ assertEquals(decision.getRoutingAction(), IRoutingDecision.RoutingAction.FORWARD_OR_FLOOD);
+
+ // clear decision
+ IRoutingDecision.rtStore.remove(cntx, IRoutingDecision.CONTEXT_DECISION);
+
+ // packet destined for port 81 - should be denied
+
+ this.setPacketIn(tcpPacketReply);
+ firewall.receive(sw, this.packetIn, cntx);
+ verify(sw);
+
+ decision = IRoutingDecision.rtStore.get(cntx, IRoutingDecision.CONTEXT_DECISION);
+ assertEquals(decision.getRoutingAction(), IRoutingDecision.RoutingAction.DROP);
+ }
+
+ @Test
+ public void testARP() throws Exception {
+ // enable firewall first
+ firewall.enableFirewall(true);
+
+ // no rules inserted so all traffic other than broadcast and ARP-request-broadcast should be blocked
+
+ // simulate an ARP broadcast packet-in event
+
+ this.setPacketIn(broadcastARPPacket);
+ firewall.receive(sw, this.packetIn, cntx);
+ verify(sw);
+
+ // broadcast-ARP traffic should be allowed
+ IRoutingDecision decision = IRoutingDecision.rtStore.get(cntx, IRoutingDecision.CONTEXT_DECISION);
+ assertEquals(IRoutingDecision.RoutingAction.MULTICAST, decision.getRoutingAction());
+
+ // clear decision
+ IRoutingDecision.rtStore.remove(cntx, IRoutingDecision.CONTEXT_DECISION);
+
+ // simulate an ARP reply packet-in event
+
+ this.setPacketIn(ARPReplyPacket);
+ firewall.receive(sw, this.packetIn, cntx);
+ verify(sw);
+
+ // ARP reply traffic should be denied
+ decision = IRoutingDecision.rtStore.get(cntx, IRoutingDecision.CONTEXT_DECISION);
+ assertEquals(decision.getRoutingAction(), IRoutingDecision.RoutingAction.DROP);
+ }
+
+ @Test
+ public void testIPBroadcast() throws Exception {
+ // enable firewall first
+ firewall.enableFirewall(true);
+
+ // set subnet mask for IP broadcast
+ firewall.setSubnetMask("255.255.255.0");
+
+ // no rules inserted so all traffic other than broadcast and ARP-request-broadcast should be blocked
+
+ // simulate a packet-in event
+
+ this.setPacketIn(broadcastIPPacket);
+ firewall.receive(sw, this.packetIn, cntx);
+ verify(sw);
+
+ // broadcast traffic should be allowed
+ IRoutingDecision decision = IRoutingDecision.rtStore.get(cntx, IRoutingDecision.CONTEXT_DECISION);
+ assertEquals(IRoutingDecision.RoutingAction.MULTICAST, decision.getRoutingAction());
+ }
+
+ @Test
+ public void testMalformedIPBroadcast() throws Exception {
+ // enable firewall first
+ firewall.enableFirewall(true);
+
+ // no rules inserted so all traffic other than broadcast and ARP-request-broadcast should be blocked
+
+ // simulate a packet-in event
+
+ this.setPacketIn(broadcastMalformedPacket);
+ firewall.receive(sw, this.packetIn, cntx);
+ verify(sw);
+
+ // malformed broadcast traffic should NOT be allowed
+ IRoutingDecision decision = IRoutingDecision.rtStore.get(cntx, IRoutingDecision.CONTEXT_DECISION);
+ assertEquals(decision.getRoutingAction(), IRoutingDecision.RoutingAction.DROP);
+ }
+
+ @Test
+ public void testLayer2Rule() throws Exception {
+ // enable firewall first
+ firewall.enableFirewall(true);
+
+ // add L2 rule
+ FirewallRule rule = new FirewallRule();
+ rule.dl_src = MACAddress.valueOf("00:44:33:22:11:00").toLong();
+ rule.wildcard_dl_src = false;
+ rule.dl_dst = MACAddress.valueOf("00:11:22:33:44:55").toLong();
+ rule.wildcard_dl_dst = false;
+ rule.priority = 1;
+ firewall.addRule(rule);
+
+ // add TCP deny all rule
+ rule = new FirewallRule();
+ rule.nw_proto = IPv4.PROTOCOL_TCP;
+ rule.wildcard_nw_proto = false;
+ rule.priority = 2;
+ rule.action = FirewallRule.FirewallAction.DENY;
+ firewall.addRule(rule);
+
+ // simulate a packet-in event
+
+ this.setPacketIn(tcpPacket);
+ firewall.receive(sw, this.packetIn, cntx);
+ verify(sw);
+
+ IRoutingDecision decision = IRoutingDecision.rtStore.get(cntx, IRoutingDecision.CONTEXT_DECISION);
+ assertEquals(decision.getRoutingAction(), IRoutingDecision.RoutingAction.FORWARD_OR_FLOOD);
+ }
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/flowcache/FlowReconcileMgrTest.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/flowcache/FlowReconcileMgrTest.java
new file mode 100644
index 0000000..0427828
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/flowcache/FlowReconcileMgrTest.java
@@ -0,0 +1,500 @@
+package net.floodlightcontroller.flowcache;
+
+import static org.easymock.EasyMock.*;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.ListIterator;
+
+import net.floodlightcontroller.core.IListener.Command;
+import net.floodlightcontroller.core.module.FloodlightModuleContext;
+import net.floodlightcontroller.core.test.MockFloodlightProvider;
+import net.floodlightcontroller.core.test.MockThreadPoolService;
+import net.floodlightcontroller.counter.ICounterStoreService;
+import net.floodlightcontroller.counter.SimpleCounter;
+import net.floodlightcontroller.counter.CounterValue.CounterType;
+import net.floodlightcontroller.flowcache.IFlowReconcileListener;
+import net.floodlightcontroller.flowcache.OFMatchReconcile;
+import net.floodlightcontroller.test.FloodlightTestCase;
+import net.floodlightcontroller.threadpool.IThreadPoolService;
+
+import org.easymock.EasyMock;
+import org.easymock.IAnswer;
+import org.junit.Before;
+import org.junit.Test;
+import org.openflow.protocol.OFStatisticsRequest;
+import org.openflow.protocol.OFType;
+
+public class FlowReconcileMgrTest extends FloodlightTestCase {
+
+ protected MockFloodlightProvider mockFloodlightProvider;
+ protected FlowReconcileManager flowReconcileMgr;
+ protected MockThreadPoolService threadPool;
+ protected ICounterStoreService counterStore;
+ protected FloodlightModuleContext fmc;
+
+ OFStatisticsRequest ofStatsRequest;
+
+ protected int NUM_FLOWS_PER_THREAD = 100;
+ protected int NUM_THREADS = 100;
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+
+ fmc = new FloodlightModuleContext();
+ flowReconcileMgr = new FlowReconcileManager();
+ threadPool = new MockThreadPoolService();
+ counterStore = createMock(ICounterStoreService.class);
+
+ fmc.addService(ICounterStoreService.class, counterStore);
+ fmc.addService(IThreadPoolService.class, threadPool);
+
+ threadPool.init(fmc);
+ flowReconcileMgr.init(fmc);
+
+ threadPool.startUp(fmc);
+ flowReconcileMgr.startUp(fmc);
+ }
+
+ /** Verify pipeline listener registration and ordering
+ *
+ * @throws Exception
+ */
+ @SuppressWarnings("unchecked")
+ @Test
+ public void testFlowReconcilePipeLine() throws Exception {
+ flowReconcileMgr.flowReconcileEnabled = true;
+
+ IFlowReconcileListener r1 =
+ EasyMock.createNiceMock(IFlowReconcileListener.class);
+ IFlowReconcileListener r2 =
+ EasyMock.createNiceMock(IFlowReconcileListener.class);
+ IFlowReconcileListener r3 =
+ EasyMock.createNiceMock(IFlowReconcileListener.class);
+
+ expect(r1.getName()).andReturn("r1").anyTimes();
+ expect(r2.getName()).andReturn("r2").anyTimes();
+ expect(r3.getName()).andReturn("r3").anyTimes();
+
+ // Set the listeners' order: r1 -> r2 -> r3
+ expect(r1.isCallbackOrderingPrereq((OFType)anyObject(),
+ (String)anyObject())).andReturn(false).anyTimes();
+ expect(r1.isCallbackOrderingPostreq((OFType)anyObject(),
+ (String)anyObject())).andReturn(false).anyTimes();
+ expect(r2.isCallbackOrderingPrereq((OFType)anyObject(),
+ eq("r1"))).andReturn(true).anyTimes();
+ expect(r2.isCallbackOrderingPrereq((OFType)anyObject(),
+ eq("r3"))).andReturn(false).anyTimes();
+ expect(r2.isCallbackOrderingPostreq((OFType)anyObject(),
+ eq("r1"))).andReturn(false).anyTimes();
+ expect(r2.isCallbackOrderingPostreq((OFType)anyObject(),
+ eq("r3"))).andReturn(true).anyTimes();
+ expect(r3.isCallbackOrderingPrereq((OFType)anyObject(),
+ eq("r1"))).andReturn(false).anyTimes();
+ expect(r3.isCallbackOrderingPrereq((OFType)anyObject(),
+ eq("r2"))).andReturn(true).anyTimes();
+ expect(r3.isCallbackOrderingPostreq((OFType)anyObject(),
+ (String)anyObject())).andReturn(false).anyTimes();
+
+ expect(r1.reconcileFlows((ArrayList<OFMatchReconcile>)anyObject())).
+ andThrow(new RuntimeException("This is NOT an error! " +
+ "We are testing exception catching."));
+
+ SimpleCounter cnt = (SimpleCounter)SimpleCounter.createCounter(
+ new Date(),
+ CounterType.LONG);
+ cnt.increment();
+ expect(counterStore.getCounter(
+ flowReconcileMgr.controllerPktInCounterName))
+ .andReturn(cnt)
+ .anyTimes();
+
+ replay(r1, r2, r3, counterStore);
+ flowReconcileMgr.clearFlowReconcileListeners();
+ flowReconcileMgr.addFlowReconcileListener(r1);
+ flowReconcileMgr.addFlowReconcileListener(r2);
+ flowReconcileMgr.addFlowReconcileListener(r3);
+
+ int pre_flowReconcileThreadRunCount =
+ flowReconcileMgr.flowReconcileThreadRunCount;
+ Date startTime = new Date();
+ OFMatchReconcile ofmRcIn = new OFMatchReconcile();
+ try {
+ flowReconcileMgr.reconcileFlow(ofmRcIn);
+ flowReconcileMgr.doReconcile();
+ } catch (RuntimeException e) {
+ assertEquals(e.getMessage()
+ .startsWith("This is NOT an error!"), true);
+ }
+
+ verify(r1, r2, r3);
+
+ // verify STOP works
+ reset(r1, r2, r3);
+
+ // restart reconcileThread since it exited due to previous runtime
+ // exception.
+ flowReconcileMgr.startUp(fmc);
+ expect(r1.reconcileFlows((ArrayList<OFMatchReconcile>)anyObject()))
+ .andReturn(Command.STOP).times(1);
+ expect(r2.reconcileFlows((ArrayList<OFMatchReconcile>)anyObject()));
+ expectLastCall().andAnswer(new IAnswer<Object>() {
+ public Object answer() {
+ fail("Unexpected call");
+ return Command.STOP;
+ }
+ }).anyTimes();
+
+ pre_flowReconcileThreadRunCount =
+ flowReconcileMgr.flowReconcileThreadRunCount;
+ startTime = new Date();
+ replay(r1, r2, r3);
+ flowReconcileMgr.reconcileFlow(ofmRcIn);
+ while (flowReconcileMgr.flowReconcileThreadRunCount <=
+ pre_flowReconcileThreadRunCount) {
+ Thread.sleep(10);
+ Date currTime = new Date();
+ assertTrue((currTime.getTime() - startTime.getTime()) < 1000);
+ }
+ verify(r1, r2, r3);
+
+ // verify CONTINUE works
+ reset(r1, r2, r3);
+ expect(r1.reconcileFlows((ArrayList<OFMatchReconcile>)anyObject()))
+ .andReturn(Command.CONTINUE).times(1);
+ expect(r2.reconcileFlows((ArrayList<OFMatchReconcile>)anyObject()))
+ .andReturn(Command.STOP).times(1);
+ expect(r3.reconcileFlows((ArrayList<OFMatchReconcile>)anyObject()));
+ expectLastCall().andAnswer(new IAnswer<Object>() {
+ public Object answer() {
+ fail("Unexpected call");
+ return Command.STOP;
+ }
+ }).anyTimes();
+
+ pre_flowReconcileThreadRunCount =
+ flowReconcileMgr.flowReconcileThreadRunCount;
+ startTime = new Date();
+
+ replay(r1, r2, r3);
+ flowReconcileMgr.reconcileFlow(ofmRcIn);
+ while (flowReconcileMgr.flowReconcileThreadRunCount <=
+ pre_flowReconcileThreadRunCount) {
+ Thread.sleep(10);
+ Date currTime = new Date();
+ assertTrue((currTime.getTime() - startTime.getTime()) < 1000);
+ }
+ verify(r1, r2, r3);
+
+ // verify CONTINUE works
+ reset(r1, r2, r3);
+ expect(r1.reconcileFlows((ArrayList<OFMatchReconcile>)anyObject()))
+ .andReturn(Command.CONTINUE).times(1);
+ expect(r2.reconcileFlows((ArrayList<OFMatchReconcile>)anyObject()))
+ .andReturn(Command.CONTINUE).times(1);
+ expect(r3.reconcileFlows((ArrayList<OFMatchReconcile>)anyObject()))
+ .andReturn(Command.STOP).times(1);
+
+ pre_flowReconcileThreadRunCount =
+ flowReconcileMgr.flowReconcileThreadRunCount;
+ startTime = new Date();
+
+ replay(r1, r2, r3);
+ flowReconcileMgr.reconcileFlow(ofmRcIn);
+ while (flowReconcileMgr.flowReconcileThreadRunCount <=
+ pre_flowReconcileThreadRunCount) {
+ Thread.sleep(10);
+ Date currTime = new Date();
+ assertTrue((currTime.getTime() - startTime.getTime()) < 1000);
+ }
+ verify(r1, r2, r3);
+
+ // Verify removeFlowReconcileListener
+ flowReconcileMgr.removeFlowReconcileListener(r1);
+ reset(r1, r2, r3);
+ expect(r1.reconcileFlows((ArrayList<OFMatchReconcile>)anyObject()));
+ expectLastCall().andAnswer(new IAnswer<Object>() {
+ public Object answer() {
+ fail("Unexpected call to a listener that is " +
+ "removed from the chain.");
+ return Command.STOP;
+ }
+ }).anyTimes();
+ expect(r2.reconcileFlows((ArrayList<OFMatchReconcile>)anyObject()))
+ .andReturn(Command.CONTINUE).times(1);
+ expect(r3.reconcileFlows((ArrayList<OFMatchReconcile>)anyObject()))
+ .andReturn(Command.STOP).times(1);
+
+ pre_flowReconcileThreadRunCount =
+ flowReconcileMgr.flowReconcileThreadRunCount;
+ startTime = new Date();
+ replay(r1, r2, r3);
+ flowReconcileMgr.reconcileFlow(ofmRcIn);
+ while (flowReconcileMgr.flowReconcileThreadRunCount <=
+ pre_flowReconcileThreadRunCount) {
+ Thread.sleep(10);
+ Date currTime = new Date();
+ assertTrue((currTime.getTime() - startTime.getTime()) < 1000);
+ }
+ verify(r1, r2, r3);
+ }
+
+ @Test
+ public void testGetPktInRate() {
+ internalTestGetPktInRate(CounterType.LONG);
+ internalTestGetPktInRate(CounterType.DOUBLE);
+ }
+
+ protected void internalTestGetPktInRate(CounterType type) {
+ Date currentTime = new Date();
+ SimpleCounter newCnt = (SimpleCounter)SimpleCounter.createCounter(
+ currentTime, type);
+ newCnt.increment(currentTime, 1);
+
+ // Set the lastCounter time in the future of the current time
+ Date lastCounterTime = new Date(currentTime.getTime() + 1000);
+ flowReconcileMgr.lastPacketInCounter =
+ (SimpleCounter)SimpleCounter.createCounter(
+ lastCounterTime, type);
+ flowReconcileMgr.lastPacketInCounter.increment(lastCounterTime, 1);
+
+ assertEquals(FlowReconcileManager.MAX_SYSTEM_LOAD_PER_SECOND,
+ flowReconcileMgr.getPktInRate(newCnt, new Date()));
+
+ // Verify the rate == 0 time difference is zero.
+ lastCounterTime = new Date(currentTime.getTime() - 1000);
+ flowReconcileMgr.lastPacketInCounter.increment(lastCounterTime, 1);
+ assertEquals(0, flowReconcileMgr.getPktInRate(newCnt, lastCounterTime));
+
+ /** verify the computation is correct.
+ * new = 2000, old = 1000, Tdiff = 1 second.
+ * rate should be 1000/second
+ */
+ newCnt = (SimpleCounter)SimpleCounter.createCounter(
+ currentTime, type);
+ newCnt.increment(currentTime, 2000);
+
+ lastCounterTime = new Date(currentTime.getTime() - 1000);
+ flowReconcileMgr.lastPacketInCounter =
+ (SimpleCounter)SimpleCounter.createCounter(
+ lastCounterTime, type);
+ flowReconcileMgr.lastPacketInCounter.increment(lastCounterTime, 1000);
+ assertEquals(1000, flowReconcileMgr.getPktInRate(newCnt, currentTime));
+
+ /** verify the computation is correct.
+ * new = 2,000,000, old = 1,000,000, Tdiff = 2 second.
+ * rate should be 1000/second
+ */
+ newCnt = (SimpleCounter)SimpleCounter.createCounter(
+ currentTime, type);
+ newCnt.increment(currentTime, 2000000);
+
+ lastCounterTime = new Date(currentTime.getTime() - 2000);
+ flowReconcileMgr.lastPacketInCounter =
+ (SimpleCounter)SimpleCounter.createCounter(
+ lastCounterTime, type);
+ flowReconcileMgr.lastPacketInCounter.increment(lastCounterTime,
+ 1000000);
+ assertEquals(500000, flowReconcileMgr.getPktInRate(newCnt,
+ currentTime));
+ }
+
+ @Test
+ public void testGetCurrentCapacity() throws Exception {
+ // Disable the reconcile thread.
+ flowReconcileMgr.flowReconcileEnabled = false;
+
+ int minFlows = FlowReconcileManager.MIN_FLOW_RECONCILE_PER_SECOND *
+ FlowReconcileManager.FLOW_RECONCILE_DELAY_MILLISEC / 1000;
+
+ /** Verify the initial state, when packetIn counter has not
+ * been created.
+ */
+ expect(counterStore.getCounter(
+ flowReconcileMgr.controllerPktInCounterName))
+ .andReturn(null)
+ .times(1);
+
+ replay(counterStore);
+ assertEquals(minFlows, flowReconcileMgr.getCurrentCapacity());
+ verify(counterStore);
+
+ /** Verify the initial state, when lastPacketInCounter is null */
+ reset(counterStore);
+ Date currentTime = new Date();
+ SimpleCounter newCnt = (SimpleCounter)SimpleCounter.createCounter(
+ currentTime, CounterType.LONG);
+
+ expect(counterStore.getCounter(
+ flowReconcileMgr.controllerPktInCounterName))
+ .andReturn(newCnt)
+ .times(1);
+ long initPktInCount = 10000;
+ newCnt.increment(currentTime, initPktInCount);
+
+ replay(counterStore);
+ assertEquals(minFlows, flowReconcileMgr.getCurrentCapacity());
+ verify(counterStore);
+
+ /** Now the lastPacketInCounter has been set.
+ * lastCounter = 100,000 and newCounter = 300,000, t = 1 second
+ * packetInRate = 200,000/sec.
+ * capacity should be 500k - 200k = 300k
+ */
+ reset(counterStore);
+ newCnt = (SimpleCounter)SimpleCounter.createCounter(
+ currentTime, CounterType.LONG);
+ currentTime = new Date(currentTime.getTime() + 200);
+ long nextPktInCount = 30000;
+ newCnt.increment(currentTime, nextPktInCount);
+
+ expect(counterStore.getCounter(
+ flowReconcileMgr.controllerPktInCounterName))
+ .andReturn(newCnt)
+ .times(1);
+
+ replay(counterStore);
+ // Wait for 1 second so that enough elapsed time to compute capacity.
+ Thread.sleep(1000);
+ int capacity = flowReconcileMgr.getCurrentCapacity();
+ verify(counterStore);
+ long expectedCap = (FlowReconcileManager.MAX_SYSTEM_LOAD_PER_SECOND -
+ (nextPktInCount - initPktInCount)) *
+ FlowReconcileManager.FLOW_RECONCILE_DELAY_MILLISEC / 1000;
+ assertEquals(expectedCap, capacity);
+ }
+
+ private class FlowReconcileWorker implements Runnable {
+ @Override
+ public void run() {
+ OFMatchReconcile ofmRc = new OFMatchReconcile();
+ // push large number of flows to be reconciled.
+ for (int i = 0; i < NUM_FLOWS_PER_THREAD; i++) {
+ flowReconcileMgr.reconcileFlow(ofmRc);
+ }
+ }
+ }
+
+ /** Verify the flows are sent to the reconcile pipeline in order.
+ */
+ @SuppressWarnings("unchecked")
+ @Test
+ public void testQueueFlowsOrder() {
+ flowReconcileMgr.flowReconcileEnabled = false;
+
+ IFlowReconcileListener r1 =
+ EasyMock.createNiceMock(IFlowReconcileListener.class);
+
+ expect(r1.getName()).andReturn("r1").anyTimes();
+
+ // Set the listeners' order: r1 -> r2 -> r3
+ expect(r1.isCallbackOrderingPrereq((OFType)anyObject(),
+ (String)anyObject())).andReturn(false).anyTimes();
+ expect(r1.isCallbackOrderingPostreq((OFType)anyObject(),
+ (String)anyObject())).andReturn(false).anyTimes();
+
+ expect(r1.reconcileFlows((ArrayList<OFMatchReconcile>)anyObject()))
+ .andAnswer(new IAnswer<Command>() {
+ @Override
+ public Command answer() throws Throwable {
+ ArrayList<OFMatchReconcile> ofmList =
+ (ArrayList<OFMatchReconcile>)EasyMock.
+ getCurrentArguments()[0];
+ ListIterator<OFMatchReconcile> lit = ofmList.listIterator();
+ int index = 0;
+ while (lit.hasNext()) {
+ OFMatchReconcile ofm = lit.next();
+ assertEquals(index++, ofm.cookie);
+ }
+ return Command.STOP;
+ }
+ }).times(1);
+
+ SimpleCounter cnt = (SimpleCounter)SimpleCounter.createCounter(
+ new Date(),
+ CounterType.LONG);
+ cnt.increment();
+ expect(counterStore.getCounter(
+ flowReconcileMgr.controllerPktInCounterName))
+ .andReturn(cnt)
+ .anyTimes();
+
+ replay(r1, counterStore);
+ flowReconcileMgr.clearFlowReconcileListeners();
+ flowReconcileMgr.addFlowReconcileListener(r1);
+
+ OFMatchReconcile ofmRcIn = new OFMatchReconcile();
+ int index = 0;
+ for (index = 0; index < 10; index++) {
+ ofmRcIn.cookie = index;
+ flowReconcileMgr.reconcileFlow(ofmRcIn);
+ }
+ flowReconcileMgr.flowReconcileEnabled = true;
+ flowReconcileMgr.doReconcile();
+
+ verify(r1);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Test
+ public void testQueueFlowsByManyThreads() {
+ // Disable the reconcile thread so that the queue won't be emptied.
+ flowQueueTest(false);
+
+ // Enable the reconcile thread. The queue should be empty.
+ Date currentTime = new Date();
+ SimpleCounter newCnt = (SimpleCounter)SimpleCounter.createCounter(
+ currentTime, CounterType.LONG);
+
+ expect(counterStore.getCounter(
+ flowReconcileMgr.controllerPktInCounterName))
+ .andReturn(newCnt)
+ .anyTimes();
+ long initPktInCount = 10000;
+ newCnt.increment(currentTime, initPktInCount);
+
+ IFlowReconcileListener r1 =
+ EasyMock.createNiceMock(IFlowReconcileListener.class);
+
+ expect(r1.getName()).andReturn("r1").anyTimes();
+
+ // Set the listeners' order: r1 -> r2 -> r3
+ expect(r1.isCallbackOrderingPrereq((OFType)anyObject(),
+ (String)anyObject())).andReturn(false).anyTimes();
+ expect(r1.isCallbackOrderingPostreq((OFType)anyObject(),
+ (String)anyObject())).andReturn(false).anyTimes();
+
+ expect(r1.reconcileFlows((ArrayList<OFMatchReconcile>)anyObject()))
+ .andReturn(Command.CONTINUE).anyTimes();
+
+ flowReconcileMgr.clearFlowReconcileListeners();
+ replay(r1, counterStore);
+ flowQueueTest(true);
+ verify(r1, counterStore);
+ }
+
+ protected void flowQueueTest(boolean enableReconcileThread) {
+ flowReconcileMgr.flowReconcileEnabled = enableReconcileThread;
+
+ // Simulate flow
+ for (int i = 0; i < NUM_THREADS; i++) {
+ Runnable worker = this.new FlowReconcileWorker();
+ Thread t = new Thread(worker);
+ t.start();
+ }
+
+ Date startTime = new Date();
+ int totalFlows = NUM_THREADS * NUM_FLOWS_PER_THREAD;
+ if (enableReconcileThread) {
+ totalFlows = 0;
+ }
+ while (flowReconcileMgr.flowQueue.size() != totalFlows) {
+ Date currTime = new Date();
+ assertTrue((currTime.getTime() - startTime.getTime()) < 2000);
+ }
+
+ // Make sure all flows are in the queue.
+ assertEquals(totalFlows, flowReconcileMgr.flowQueue.size());
+ }
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/forwarding/ForwardingTest.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/forwarding/ForwardingTest.java
new file mode 100644
index 0000000..7a37589
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/forwarding/ForwardingTest.java
@@ -0,0 +1,520 @@
+/**
+* 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;
+
+import static org.easymock.EasyMock.*;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import net.floodlightcontroller.core.FloodlightContext;
+import net.floodlightcontroller.core.IFloodlightProviderService;
+import net.floodlightcontroller.core.IOFSwitch;
+import net.floodlightcontroller.core.module.FloodlightModuleContext;
+import net.floodlightcontroller.core.test.MockFloodlightProvider;
+import net.floodlightcontroller.core.test.MockThreadPoolService;
+import net.floodlightcontroller.devicemanager.internal.DefaultEntityClassifier;
+import net.floodlightcontroller.devicemanager.test.MockDeviceManager;
+import net.floodlightcontroller.counter.CounterStore;
+import net.floodlightcontroller.counter.ICounterStoreService;
+import net.floodlightcontroller.devicemanager.IDevice;
+import net.floodlightcontroller.devicemanager.IDeviceService;
+import net.floodlightcontroller.devicemanager.IEntityClassifierService;
+import net.floodlightcontroller.packet.Data;
+import net.floodlightcontroller.packet.Ethernet;
+import net.floodlightcontroller.packet.IPacket;
+import net.floodlightcontroller.packet.IPv4;
+import net.floodlightcontroller.packet.UDP;
+import net.floodlightcontroller.routing.IRoutingService;
+import net.floodlightcontroller.routing.Route;
+import net.floodlightcontroller.test.FloodlightTestCase;
+import net.floodlightcontroller.threadpool.IThreadPoolService;
+import net.floodlightcontroller.topology.ITopologyListener;
+import net.floodlightcontroller.topology.ITopologyService;
+import net.floodlightcontroller.topology.NodePortTuple;
+import net.floodlightcontroller.flowcache.FlowReconcileManager;
+import net.floodlightcontroller.flowcache.IFlowReconcileService;
+import net.floodlightcontroller.forwarding.Forwarding;
+
+import org.easymock.Capture;
+import org.easymock.CaptureType;
+import org.easymock.EasyMock;
+import org.junit.Test;
+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.OFPacketOut;
+import org.openflow.protocol.OFPort;
+import org.openflow.protocol.OFType;
+import org.openflow.protocol.OFPacketIn.OFPacketInReason;
+import org.openflow.protocol.action.OFAction;
+import org.openflow.protocol.action.OFActionOutput;
+import org.openflow.util.HexString;
+
+public class ForwardingTest extends FloodlightTestCase {
+ protected MockFloodlightProvider mockFloodlightProvider;
+ protected FloodlightContext cntx;
+ protected MockDeviceManager deviceManager;
+ protected IRoutingService routingEngine;
+ protected Forwarding forwarding;
+ protected FlowReconcileManager flowReconcileMgr;
+ protected ITopologyService topology;
+ protected MockThreadPoolService threadPool;
+ protected IOFSwitch sw1, sw2;
+ protected OFFeaturesReply swFeatures;
+ protected IDevice srcDevice, dstDevice1, dstDevice2;
+ protected OFPacketIn packetIn;
+ protected OFPacketOut packetOut;
+ protected OFPacketOut packetOutFlooded;
+ protected IPacket testPacket;
+ protected byte[] testPacketSerialized;
+ protected int expected_wildcards;
+ protected Date currentDate;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ cntx = new FloodlightContext();
+
+ // Module loader setup
+ /*
+ Collection<Class<? extends IFloodlightModule>> mods = new ArrayList<Class<? extends IFloodlightModule>>();
+ Collection<IFloodlightService> mockedServices = new ArrayList<IFloodlightService>();
+ mods.add(Forwarding.class);
+ routingEngine = createMock(IRoutingService.class);
+ topology = createMock(ITopologyService.class);
+ mockedServices.add(routingEngine);
+ mockedServices.add(topology);
+ FloodlightTestModuleLoader fml = new FloodlightTestModuleLoader();
+ fml.setupModules(mods, mockedServices);
+ mockFloodlightProvider =
+ (MockFloodlightProvider) fml.getModuleByName(MockFloodlightProvider.class);
+ deviceManager =
+ (MockDeviceManager) fml.getModuleByName(MockDeviceManager.class);
+ threadPool =
+ (MockThreadPoolService) fml.getModuleByName(MockThreadPoolService.class);
+ forwarding =
+ (Forwarding) fml.getModuleByName(Forwarding.class);
+ */
+ mockFloodlightProvider = getMockFloodlightProvider();
+ forwarding = new Forwarding();
+ threadPool = new MockThreadPoolService();
+ deviceManager = new MockDeviceManager();
+ flowReconcileMgr = new FlowReconcileManager();
+ routingEngine = createMock(IRoutingService.class);
+ topology = createMock(ITopologyService.class);
+ DefaultEntityClassifier entityClassifier = new DefaultEntityClassifier();
+
+
+ FloodlightModuleContext fmc = new FloodlightModuleContext();
+ fmc.addService(IFloodlightProviderService.class,
+ mockFloodlightProvider);
+ fmc.addService(IThreadPoolService.class, threadPool);
+ fmc.addService(ITopologyService.class, topology);
+ fmc.addService(IRoutingService.class, routingEngine);
+ fmc.addService(ICounterStoreService.class, new CounterStore());
+ fmc.addService(IDeviceService.class, deviceManager);
+ fmc.addService(IFlowReconcileService.class, flowReconcileMgr);
+ fmc.addService(IEntityClassifierService.class, entityClassifier);
+
+ topology.addListener(anyObject(ITopologyListener.class));
+ expectLastCall().anyTimes();
+ replay(topology);
+ threadPool.init(fmc);
+ forwarding.init(fmc);
+ deviceManager.init(fmc);
+ flowReconcileMgr.init(fmc);
+ entityClassifier.init(fmc);
+ threadPool.startUp(fmc);
+ deviceManager.startUp(fmc);
+ forwarding.startUp(fmc);
+ flowReconcileMgr.startUp(fmc);
+ entityClassifier.startUp(fmc);
+ verify(topology);
+
+ swFeatures = new OFFeaturesReply();
+ swFeatures.setBuffers(1000);
+ // Mock switches
+ sw1 = EasyMock.createMock(IOFSwitch.class);
+ expect(sw1.getId()).andReturn(1L).anyTimes();
+ expect(sw1.getBuffers()).andReturn(swFeatures.getBuffers()).anyTimes();
+ expect(sw1.getStringId())
+ .andReturn(HexString.toHexString(1L)).anyTimes();
+
+ sw2 = EasyMock.createMock(IOFSwitch.class);
+ expect(sw2.getId()).andReturn(2L).anyTimes();
+ expect(sw2.getBuffers()).andReturn(swFeatures.getBuffers()).anyTimes();
+ expect(sw2.getStringId())
+ .andReturn(HexString.toHexString(2L)).anyTimes();
+
+ //fastWilcards mocked as this constant
+ int fastWildcards =
+ OFMatch.OFPFW_IN_PORT |
+ OFMatch.OFPFW_NW_PROTO |
+ OFMatch.OFPFW_TP_SRC |
+ OFMatch.OFPFW_TP_DST |
+ OFMatch.OFPFW_NW_SRC_ALL |
+ OFMatch.OFPFW_NW_DST_ALL |
+ OFMatch.OFPFW_NW_TOS;
+
+ expect(sw1.getAttribute(IOFSwitch.PROP_FASTWILDCARDS)).andReturn((Integer)fastWildcards).anyTimes();
+ expect(sw1.hasAttribute(IOFSwitch.PROP_SUPPORTS_OFPP_TABLE)).andReturn(true).anyTimes();
+
+ expect(sw2.getAttribute(IOFSwitch.PROP_FASTWILDCARDS)).andReturn((Integer)fastWildcards).anyTimes();
+ expect(sw2.hasAttribute(IOFSwitch.PROP_SUPPORTS_OFPP_TABLE)).andReturn(true).anyTimes();
+
+ // Load the switch map
+ Map<Long, IOFSwitch> switches = new HashMap<Long, IOFSwitch>();
+ switches.put(1L, sw1);
+ switches.put(2L, sw2);
+ mockFloodlightProvider.setSwitches(switches);
+
+ // Build test packet
+ testPacket = new Ethernet()
+ .setDestinationMACAddress("00:11:22:33:44:55")
+ .setSourceMACAddress("00:44:33:22:11:00")
+ .setEtherType(Ethernet.TYPE_IPv4)
+ .setPayload(
+ new IPv4()
+ .setTtl((byte) 128)
+ .setSourceAddress("192.168.1.1")
+ .setDestinationAddress("192.168.1.2")
+ .setPayload(new UDP()
+ .setSourcePort((short) 5000)
+ .setDestinationPort((short) 5001)
+ .setPayload(new Data(new byte[] {0x01}))));
+
+
+
+ currentDate = new Date();
+
+ // Mock Packet-in
+ testPacketSerialized = testPacket.serialize();
+ packetIn =
+ ((OFPacketIn) mockFloodlightProvider.getOFMessageFactory().
+ getMessage(OFType.PACKET_IN))
+ .setBufferId(-1)
+ .setInPort((short) 1)
+ .setPacketData(testPacketSerialized)
+ .setReason(OFPacketInReason.NO_MATCH)
+ .setTotalLength((short) testPacketSerialized.length);
+
+ // Mock Packet-out
+ packetOut =
+ (OFPacketOut) mockFloodlightProvider.getOFMessageFactory().
+ getMessage(OFType.PACKET_OUT);
+ packetOut.setBufferId(this.packetIn.getBufferId())
+ .setInPort(this.packetIn.getInPort());
+ List<OFAction> poactions = new ArrayList<OFAction>();
+ poactions.add(new OFActionOutput((short) 3, (short) 0xffff));
+ packetOut.setActions(poactions)
+ .setActionsLength((short) OFActionOutput.MINIMUM_LENGTH)
+ .setPacketData(testPacketSerialized)
+ .setLengthU(OFPacketOut.MINIMUM_LENGTH+
+ packetOut.getActionsLength()+
+ testPacketSerialized.length);
+
+ // Mock Packet-out with OFPP_FLOOD action
+ packetOutFlooded =
+ (OFPacketOut) mockFloodlightProvider.getOFMessageFactory().
+ getMessage(OFType.PACKET_OUT);
+ packetOutFlooded.setBufferId(this.packetIn.getBufferId())
+ .setInPort(this.packetIn.getInPort());
+ poactions = new ArrayList<OFAction>();
+ poactions.add(new OFActionOutput(OFPort.OFPP_FLOOD.getValue(),
+ (short) 0xffff));
+ packetOutFlooded.setActions(poactions)
+ .setActionsLength((short) OFActionOutput.MINIMUM_LENGTH)
+ .setPacketData(testPacketSerialized)
+ .setLengthU(OFPacketOut.MINIMUM_LENGTH+
+ packetOutFlooded.getActionsLength()+
+ testPacketSerialized.length);
+
+ expected_wildcards = fastWildcards;
+ expected_wildcards &= ~OFMatch.OFPFW_IN_PORT &
+ ~OFMatch.OFPFW_DL_VLAN &
+ ~OFMatch.OFPFW_DL_SRC &
+ ~OFMatch.OFPFW_DL_DST;
+ expected_wildcards &= ~OFMatch.OFPFW_NW_SRC_MASK &
+ ~OFMatch.OFPFW_NW_DST_MASK;
+
+ IFloodlightProviderService.bcStore.
+ put(cntx,
+ IFloodlightProviderService.CONTEXT_PI_PAYLOAD,
+ (Ethernet)testPacket);
+ }
+
+ enum DestDeviceToLearn { NONE, DEVICE1 ,DEVICE2 };
+ public void learnDevices(DestDeviceToLearn destDeviceToLearn) {
+ // Build src and dest devices
+ byte[] dataLayerSource = ((Ethernet)testPacket).getSourceMACAddress();
+ byte[] dataLayerDest =
+ ((Ethernet)testPacket).getDestinationMACAddress();
+ int networkSource =
+ ((IPv4)((Ethernet)testPacket).getPayload()).
+ getSourceAddress();
+ int networkDest =
+ ((IPv4)((Ethernet)testPacket).getPayload()).
+ getDestinationAddress();
+
+ reset(topology);
+ expect(topology.isAttachmentPointPort(1L, (short)1))
+ .andReturn(true)
+ .anyTimes();
+ expect(topology.isAttachmentPointPort(2L, (short)3))
+ .andReturn(true)
+ .anyTimes();
+ expect(topology.isAttachmentPointPort(1L, (short)3))
+ .andReturn(true)
+ .anyTimes();
+ replay(topology);
+
+ srcDevice =
+ deviceManager.learnEntity(Ethernet.toLong(dataLayerSource),
+ null, networkSource,
+ 1L, 1);
+ IDeviceService.fcStore. put(cntx,
+ IDeviceService.CONTEXT_SRC_DEVICE,
+ srcDevice);
+ if (destDeviceToLearn == DestDeviceToLearn.DEVICE1) {
+ dstDevice1 =
+ deviceManager.learnEntity(Ethernet.toLong(dataLayerDest),
+ null, networkDest,
+ 2L, 3);
+ IDeviceService.fcStore.put(cntx,
+ IDeviceService.CONTEXT_DST_DEVICE,
+ dstDevice1);
+ }
+ if (destDeviceToLearn == DestDeviceToLearn.DEVICE2) {
+ dstDevice2 =
+ deviceManager.learnEntity(Ethernet.toLong(dataLayerDest),
+ null, networkDest,
+ 1L, 3);
+ IDeviceService.fcStore.put(cntx,
+ IDeviceService.CONTEXT_DST_DEVICE,
+ dstDevice2);
+ }
+ verify(topology);
+ }
+
+ @Test
+ public void testForwardMultiSwitchPath() throws Exception {
+ learnDevices(DestDeviceToLearn.DEVICE1);
+
+ Capture<OFMessage> wc1 = new Capture<OFMessage>(CaptureType.ALL);
+ Capture<OFMessage> wc2 = new Capture<OFMessage>(CaptureType.ALL);
+ Capture<FloodlightContext> bc1 =
+ new Capture<FloodlightContext>(CaptureType.ALL);
+ Capture<FloodlightContext> bc2 =
+ new Capture<FloodlightContext>(CaptureType.ALL);
+
+
+ Route route = new Route(1L, 2L);
+ List<NodePortTuple> nptList = new ArrayList<NodePortTuple>();
+ nptList.add(new NodePortTuple(1L, (short)1));
+ nptList.add(new NodePortTuple(1L, (short)3));
+ nptList.add(new NodePortTuple(2L, (short)1));
+ nptList.add(new NodePortTuple(2L, (short)3));
+ route.setPath(nptList);
+ expect(routingEngine.getRoute(1L, (short)1, 2L, (short)3)).andReturn(route).atLeastOnce();
+
+ // Expected Flow-mods
+ OFMatch match = new OFMatch();
+ match.loadFromPacket(testPacketSerialized, (short) 1);
+ OFActionOutput action = new OFActionOutput((short)3, (short)0xffff);
+ List<OFAction> actions = new ArrayList<OFAction>();
+ actions.add(action);
+
+ OFFlowMod fm1 =
+ (OFFlowMod) mockFloodlightProvider.getOFMessageFactory().
+ getMessage(OFType.FLOW_MOD);
+ fm1.setIdleTimeout((short)5)
+ .setMatch(match.clone()
+ .setWildcards(expected_wildcards))
+ .setActions(actions)
+ .setBufferId(OFPacketOut.BUFFER_ID_NONE)
+ .setCookie(2L << 52)
+ .setLengthU(OFFlowMod.MINIMUM_LENGTH+OFActionOutput.MINIMUM_LENGTH);
+ OFFlowMod fm2 = fm1.clone();
+ ((OFActionOutput)fm2.getActions().get(0)).setPort((short) 3);
+
+ sw1.write(capture(wc1), capture(bc1));
+ expectLastCall().anyTimes();
+ sw2.write(capture(wc2), capture(bc2));
+ expectLastCall().anyTimes();
+
+ reset(topology);
+ expect(topology.getL2DomainId(1L)).andReturn(1L).anyTimes();
+ expect(topology.getL2DomainId(2L)).andReturn(1L).anyTimes();
+ expect(topology.isAttachmentPointPort(1L, (short)1)).andReturn(true).anyTimes();
+ expect(topology.isAttachmentPointPort(2L, (short)3)).andReturn(true).anyTimes();
+ expect(topology.isIncomingBroadcastAllowed(anyLong(), anyShort())).andReturn(true).anyTimes();
+
+ // Reset mocks, trigger the packet in, and validate results
+ replay(sw1, sw2, routingEngine, topology);
+ forwarding.receive(sw1, this.packetIn, cntx);
+ verify(sw1, sw2, routingEngine);
+
+ assertTrue(wc1.hasCaptured()); // wc1 should get packetout + flowmod.
+ assertTrue(wc2.hasCaptured()); // wc2 should be a flowmod.
+
+ List<OFMessage> msglist = wc1.getValues();
+
+ for (OFMessage m: msglist) {
+ if (m instanceof OFFlowMod)
+ assertEquals(fm1, m);
+ else if (m instanceof OFPacketOut)
+ assertEquals(packetOut, m);
+ }
+
+ OFMessage m = wc2.getValue();
+ assert (m instanceof OFFlowMod);
+ assertTrue(m.equals(fm2));
+ }
+
+ @Test
+ public void testForwardSingleSwitchPath() throws Exception {
+ learnDevices(DestDeviceToLearn.DEVICE2);
+
+ Route route = new Route(1L, 1L);
+ route.getPath().add(new NodePortTuple(1L, (short)1));
+ route.getPath().add(new NodePortTuple(1L, (short)3));
+ expect(routingEngine.getRoute(1L, (short)1, 1L, (short)3)).andReturn(route).atLeastOnce();
+
+ // Expected Flow-mods
+ OFMatch match = new OFMatch();
+ match.loadFromPacket(testPacketSerialized, (short) 1);
+ OFActionOutput action = new OFActionOutput((short)3, (short)0xffff);
+ List<OFAction> actions = new ArrayList<OFAction>();
+ actions.add(action);
+
+ OFFlowMod fm1 =
+ (OFFlowMod) mockFloodlightProvider.getOFMessageFactory().
+ getMessage(OFType.FLOW_MOD);
+ fm1.setIdleTimeout((short)5)
+ .setMatch(match.clone()
+ .setWildcards(expected_wildcards))
+ .setActions(actions)
+ .setBufferId(OFPacketOut.BUFFER_ID_NONE)
+ .setCookie(2L << 52)
+ .setLengthU(OFFlowMod.MINIMUM_LENGTH +
+ OFActionOutput.MINIMUM_LENGTH);
+
+ // Record expected packet-outs/flow-mods
+ sw1.write(fm1, cntx);
+ sw1.write(packetOut, cntx);
+
+ reset(topology);
+ expect(topology.isIncomingBroadcastAllowed(anyLong(), anyShort())).andReturn(true).anyTimes();
+ expect(topology.getL2DomainId(1L)).andReturn(1L).anyTimes();
+ expect(topology.isAttachmentPointPort(1L, (short)1)).andReturn(true).anyTimes();
+ expect(topology.isAttachmentPointPort(1L, (short)3)).andReturn(true).anyTimes();
+
+ // Reset mocks, trigger the packet in, and validate results
+ replay(sw1, sw2, routingEngine, topology);
+ forwarding.receive(sw1, this.packetIn, cntx);
+ verify(sw1, sw2, routingEngine);
+ }
+
+ @Test
+ public void testFlowModDampening() throws Exception {
+ learnDevices(DestDeviceToLearn.DEVICE2);
+
+ reset(topology);
+ expect(topology.isAttachmentPointPort(EasyMock.anyLong(), EasyMock.anyShort()))
+ .andReturn(true).anyTimes();
+ expect(topology.getL2DomainId(1L)).andReturn(1L).anyTimes();
+ replay(topology);
+
+
+ Route route = new Route(1L, 1L);
+ route.getPath().add(new NodePortTuple(1L, (short)1));
+ route.getPath().add(new NodePortTuple(1L, (short)3));
+ expect(routingEngine.getRoute(1L, (short)1, 1L, (short)3)).andReturn(route).atLeastOnce();
+
+ // Expected Flow-mods
+ OFMatch match = new OFMatch();
+ match.loadFromPacket(testPacketSerialized, (short) 1);
+ OFActionOutput action = new OFActionOutput((short)3, (short)0xffff);
+ List<OFAction> actions = new ArrayList<OFAction>();
+ actions.add(action);
+
+ OFFlowMod fm1 =
+ (OFFlowMod) mockFloodlightProvider.getOFMessageFactory().
+ getMessage(OFType.FLOW_MOD);
+ fm1.setIdleTimeout((short)5)
+ .setMatch(match.clone()
+ .setWildcards(expected_wildcards))
+ .setActions(actions)
+ .setBufferId(OFPacketOut.BUFFER_ID_NONE)
+ .setCookie(2L << 52)
+ .setLengthU(OFFlowMod.MINIMUM_LENGTH +
+ OFActionOutput.MINIMUM_LENGTH);
+
+ // Record expected packet-outs/flow-mods
+ // We will inject the packet_in 3 times and expect 1 flow mod and
+ // 3 packet outs due to flow mod dampening
+ sw1.write(fm1, cntx);
+ expectLastCall().once();
+ sw1.write(packetOut, cntx);
+ expectLastCall().times(3);
+
+ reset(topology);
+ expect(topology.isIncomingBroadcastAllowed(anyLong(), anyShort())).andReturn(true).anyTimes();
+ expect(topology.getL2DomainId(1L)).andReturn(1L).anyTimes();
+ expect(topology.isAttachmentPointPort(1L, (short)1)).andReturn(true).anyTimes();
+ expect(topology.isAttachmentPointPort(1L, (short)3)).andReturn(true).anyTimes();
+
+ // Reset mocks, trigger the packet in, and validate results
+ replay(sw1, routingEngine, topology);
+ forwarding.receive(sw1, this.packetIn, cntx);
+ forwarding.receive(sw1, this.packetIn, cntx);
+ forwarding.receive(sw1, this.packetIn, cntx);
+ verify(sw1, routingEngine);
+ }
+
+ @Test
+ public void testForwardNoPath() throws Exception {
+ learnDevices(DestDeviceToLearn.NONE);
+
+ // Set no destination attachment point or route
+ // expect no Flow-mod but expect the packet to be flooded
+
+ // Reset mocks, trigger the packet in, and validate results
+ reset(topology);
+ expect(topology.isIncomingBroadcastAllowed(1L, (short)1)).andReturn(true).anyTimes();
+ expect(topology.isAttachmentPointPort(EasyMock.anyLong(),
+ EasyMock.anyShort()))
+ .andReturn(true)
+ .anyTimes();
+ expect(sw1.hasAttribute(IOFSwitch.PROP_SUPPORTS_OFPP_FLOOD))
+ .andReturn(true).anyTimes();
+ sw1.write(packetOutFlooded, cntx);
+ expectLastCall().once();
+ replay(sw1, sw2, routingEngine, topology);
+ forwarding.receive(sw1, this.packetIn, cntx);
+ verify(sw1, sw2, routingEngine);
+ }
+
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/hub/HubTest.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/hub/HubTest.java
new file mode 100644
index 0000000..b4a215c
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/hub/HubTest.java
@@ -0,0 +1,167 @@
+/**
+* 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.hub;
+
+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 java.util.Arrays;
+
+import net.floodlightcontroller.core.FloodlightContext;
+import net.floodlightcontroller.core.IOFMessageListener;
+import net.floodlightcontroller.core.IOFSwitch;
+import net.floodlightcontroller.core.test.MockFloodlightProvider;
+import net.floodlightcontroller.packet.Data;
+import net.floodlightcontroller.packet.Ethernet;
+import net.floodlightcontroller.packet.IPacket;
+import net.floodlightcontroller.packet.IPv4;
+import net.floodlightcontroller.packet.UDP;
+import net.floodlightcontroller.test.FloodlightTestCase;
+
+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;
+
+/**
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class HubTest extends FloodlightTestCase {
+ protected OFPacketIn packetIn;
+ protected IPacket testPacket;
+ protected byte[] testPacketSerialized;
+ private MockFloodlightProvider mockFloodlightProvider;
+ private Hub hub;
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+
+ mockFloodlightProvider = getMockFloodlightProvider();
+ hub = new Hub();
+ mockFloodlightProvider.addOFMessageListener(OFType.PACKET_IN, hub);
+ hub.setFloodlightProvider(mockFloodlightProvider);
+
+ // Build our test packet
+ this.testPacket = new Ethernet()
+ .setDestinationMACAddress("00:11:22:33:44:55")
+ .setSourceMACAddress("00:44:33:22:11:00")
+ .setEtherType(Ethernet.TYPE_IPv4)
+ .setPayload(
+ new IPv4()
+ .setTtl((byte) 128)
+ .setSourceAddress("192.168.1.1")
+ .setDestinationAddress("192.168.1.2")
+ .setPayload(new UDP()
+ .setSourcePort((short) 5000)
+ .setDestinationPort((short) 5001)
+ .setPayload(new Data(new byte[] {0x01}))));
+ this.testPacketSerialized = testPacket.serialize();
+
+ // Build the PacketIn
+ this.packetIn = ((OFPacketIn) mockFloodlightProvider.getOFMessageFactory().getMessage(OFType.PACKET_IN))
+ .setBufferId(-1)
+ .setInPort((short) 1)
+ .setPacketData(this.testPacketSerialized)
+ .setReason(OFPacketInReason.NO_MATCH)
+ .setTotalLength((short) this.testPacketSerialized.length);
+ }
+
+ @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);
+
+ Capture<OFMessage> wc1 = new Capture<OFMessage>(CaptureType.ALL);
+ Capture<FloodlightContext> bc1 = new Capture<FloodlightContext>(CaptureType.ALL);
+
+ mockSwitch.write(capture(wc1), capture(bc1));
+
+ // Start recording the replay on the mocks
+ replay(mockSwitch);
+ // Get the listener and trigger the packet in
+ IOFMessageListener listener = mockFloodlightProvider.getListeners().get(
+ OFType.PACKET_IN).get(0);
+ listener.receive(mockSwitch, this.packetIn,
+ parseAndAnnotate(this.packetIn));
+
+ // Verify the replay matched our expectations
+ verify(mockSwitch);
+
+ assertTrue(wc1.hasCaptured());
+ OFMessage m = wc1.getValue();
+ assert(m.equals(po));
+ }
+
+ @Test
+ public void testFloodBufferId() throws Exception {
+ MockFloodlightProvider mockFloodlightProvider = getMockFloodlightProvider();
+ this.packetIn.setBufferId(10);
+
+ // 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());
+
+ // 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));
+
+ // Start recording the replay on the mocks
+ replay(mockSwitch);
+ // Get the listener and trigger the packet in
+ IOFMessageListener listener = mockFloodlightProvider.getListeners().get(
+ OFType.PACKET_IN).get(0);
+ listener.receive(mockSwitch, this.packetIn,
+ parseAndAnnotate(this.packetIn));
+
+ // Verify the replay matched our expectations
+ verify(mockSwitch);
+
+ assertTrue(wc1.hasCaptured());
+ OFMessage m = wc1.getValue();
+ assert(m.equals(po));
+ }
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/learningswitch/LearningSwitchTest.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/learningswitch/LearningSwitchTest.java
new file mode 100644
index 0000000..a68a1b8
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/learningswitch/LearningSwitchTest.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.learningswitch;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+
+import net.floodlightcontroller.core.IOFMessageListener;
+import net.floodlightcontroller.core.IOFSwitch;
+import net.floodlightcontroller.core.module.FloodlightTestModuleLoader;
+import net.floodlightcontroller.core.module.IFloodlightModule;
+import net.floodlightcontroller.core.test.MockFloodlightProvider;
+import net.floodlightcontroller.packet.Data;
+import net.floodlightcontroller.packet.Ethernet;
+import net.floodlightcontroller.packet.IPacket;
+import net.floodlightcontroller.packet.IPv4;
+import net.floodlightcontroller.packet.UDP;
+import net.floodlightcontroller.test.FloodlightTestCase;
+
+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.OFPacketIn.OFPacketInReason;
+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;
+
+/**
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class LearningSwitchTest extends FloodlightTestCase {
+ protected OFPacketIn packetIn;
+ protected IPacket testPacket;
+ protected byte[] testPacketSerialized;
+ protected IPacket broadcastPacket;
+ protected byte[] broadcastPacketSerialized;
+ protected IPacket testPacketReply;
+ protected byte[] testPacketReplySerialized;
+ private LearningSwitch learningSwitch;
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ FloodlightTestModuleLoader fml = new FloodlightTestModuleLoader();
+ Collection<Class<? extends IFloodlightModule>> mods
+ = new ArrayList<Class<? extends IFloodlightModule>>();
+ mods.add(LearningSwitch.class);
+ fml.setupModules(mods, null);
+ learningSwitch = (LearningSwitch) fml.getModuleByName(LearningSwitch.class);
+ mockFloodlightProvider =
+ (MockFloodlightProvider) fml.getModuleByName(MockFloodlightProvider.class);
+
+ // Build our test packet
+ this.testPacket = new Ethernet()
+ .setDestinationMACAddress("00:11:22:33:44:55")
+ .setSourceMACAddress("00:44:33:22:11:00")
+ .setVlanID((short) 42)
+ .setEtherType(Ethernet.TYPE_IPv4)
+ .setPayload(
+ new IPv4()
+ .setTtl((byte) 128)
+ .setSourceAddress("192.168.1.1")
+ .setDestinationAddress("192.168.1.2")
+ .setPayload(new UDP()
+ .setSourcePort((short) 5000)
+ .setDestinationPort((short) 5001)
+ .setPayload(new Data(new byte[] {0x01}))));
+ this.testPacketSerialized = testPacket.serialize();
+ // Build a broadcast packet
+ this.broadcastPacket = new Ethernet()
+ .setDestinationMACAddress("FF:FF:FF:FF:FF:FF")
+ .setSourceMACAddress("00:44:33:22:11:00")
+ .setVlanID((short) 42)
+ .setEtherType(Ethernet.TYPE_IPv4)
+ .setPayload(
+ new IPv4()
+ .setTtl((byte) 128)
+ .setSourceAddress("192.168.1.1")
+ .setDestinationAddress("192.168.255.255")
+ .setPayload(new UDP()
+ .setSourcePort((short) 5000)
+ .setDestinationPort((short) 5001)
+ .setPayload(new Data(new byte[] {0x01}))));
+
+ this.broadcastPacketSerialized = broadcastPacket.serialize();
+ this.testPacketReply = new Ethernet()
+ .setDestinationMACAddress("00:44:33:22:11:00")
+ .setSourceMACAddress("00:11:22:33:44:55")
+ .setVlanID((short) 42)
+ .setEtherType(Ethernet.TYPE_IPv4)
+ .setPayload(
+ new IPv4()
+ .setTtl((byte) 128)
+ .setSourceAddress("192.168.1.2")
+ .setDestinationAddress("192.168.1.1")
+ .setPayload(new UDP()
+ .setSourcePort((short) 5001)
+ .setDestinationPort((short) 5000)
+ .setPayload(new Data(new byte[] {0x02}))));
+ this.testPacketReplySerialized = testPacketReply.serialize();
+
+ // Build the PacketIn
+ this.packetIn = ((OFPacketIn) mockFloodlightProvider.getOFMessageFactory().getMessage(OFType.PACKET_IN))
+ .setBufferId(-1)
+ .setInPort((short) 1)
+ .setPacketData(this.testPacketSerialized)
+ .setReason(OFPacketInReason.NO_MATCH)
+ .setTotalLength((short) this.testPacketSerialized.length);
+ }
+
+ @Test
+ public void testFlood() throws Exception {
+ // build our expected flooded packetOut
+ OFPacketOut po = new OFPacketOut()
+ .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);
+ expect(mockSwitch.getStringId()).andReturn("00:11:22:33:44:55:66:77").anyTimes();
+ mockSwitch.write(po, null);
+
+ // Start recording the replay on the mocks
+ replay(mockSwitch);
+ // Get the listener and trigger the packet in
+ IOFMessageListener listener = mockFloodlightProvider.getListeners().get(
+ OFType.PACKET_IN).get(0);
+ // Make sure it's the right listener
+ listener.receive(mockSwitch, this.packetIn, parseAndAnnotate(this.packetIn));
+
+ // Verify the replay matched our expectations
+ short result = learningSwitch.getFromPortMap(mockSwitch, Ethernet.toLong(Ethernet.toMACAddress("00:44:33:22:11:00")), (short) 42).shortValue();
+ verify(mockSwitch);
+
+ // Verify the MAC table inside the switch
+ assertEquals(1, result);
+ }
+
+ @Test
+ public void testFlowMod() throws Exception {
+ // tweak the test packet in since we need a bufferId
+ this.packetIn.setBufferId(50);
+
+ // build expected flow mods
+ OFMessage fm1 = ((OFFlowMod) mockFloodlightProvider.getOFMessageFactory().getMessage(OFType.FLOW_MOD))
+ .setActions(Arrays.asList(new OFAction[] {
+ new OFActionOutput().setPort((short) 2).setMaxLength((short) -1)}))
+ .setBufferId(50)
+ .setCommand(OFFlowMod.OFPFC_ADD)
+ .setIdleTimeout((short) 5)
+ .setMatch(new OFMatch()
+ .loadFromPacket(testPacketSerialized, (short) 1)
+ .setWildcards(OFMatch.OFPFW_NW_PROTO | OFMatch.OFPFW_TP_SRC | OFMatch.OFPFW_TP_DST
+ | OFMatch.OFPFW_NW_TOS))
+ .setOutPort(OFPort.OFPP_NONE.getValue())
+ .setCookie(1L << 52)
+ .setPriority((short) 100)
+ .setFlags((short)(1 << 0))
+ .setLengthU(OFFlowMod.MINIMUM_LENGTH+OFActionOutput.MINIMUM_LENGTH);
+ OFMessage fm2 = ((OFFlowMod) mockFloodlightProvider.getOFMessageFactory().getMessage(OFType.FLOW_MOD))
+ .setActions(Arrays.asList(new OFAction[] {
+ new OFActionOutput().setPort((short) 1).setMaxLength((short) -1)}))
+ .setBufferId(-1)
+ .setCommand(OFFlowMod.OFPFC_ADD)
+ .setIdleTimeout((short) 5)
+ .setMatch(new OFMatch()
+ .loadFromPacket(testPacketReplySerialized, (short) 2)
+ .setWildcards(OFMatch.OFPFW_NW_PROTO | OFMatch.OFPFW_TP_SRC | OFMatch.OFPFW_TP_DST
+ | OFMatch.OFPFW_NW_TOS))
+ .setOutPort(OFPort.OFPP_NONE.getValue())
+ .setCookie(1L << 52)
+ .setPriority((short) 100)
+ .setFlags((short)(1 << 0))
+ .setLengthU(OFFlowMod.MINIMUM_LENGTH+OFActionOutput.MINIMUM_LENGTH);
+
+ // Mock up our expected behavior
+ IOFSwitch mockSwitch = createMock(IOFSwitch.class);
+ expect(mockSwitch.getId()).andReturn(1L).anyTimes();
+ expect(mockSwitch.getAttribute(IOFSwitch.PROP_FASTWILDCARDS)).andReturn((Integer) (OFMatch.OFPFW_IN_PORT | OFMatch.OFPFW_NW_PROTO
+ | OFMatch.OFPFW_TP_SRC | OFMatch.OFPFW_TP_DST | OFMatch.OFPFW_NW_SRC_ALL
+ | OFMatch.OFPFW_NW_DST_ALL | OFMatch.OFPFW_NW_TOS));
+ mockSwitch.write(fm1, null);
+ mockSwitch.write(fm2, null);
+
+ // Start recording the replay on the mocks
+ replay(mockSwitch);
+
+ // Populate the MAC table
+ learningSwitch.addToPortMap(mockSwitch,
+ Ethernet.toLong(Ethernet.toMACAddress("00:11:22:33:44:55")), (short) 42, (short) 2);
+
+ // Get the listener and trigger the packet in
+ IOFMessageListener listener = mockFloodlightProvider.getListeners().get(
+ OFType.PACKET_IN).get(0);
+ listener.receive(mockSwitch, this.packetIn, parseAndAnnotate(this.packetIn));
+
+ // Verify the replay matched our expectations
+ short result = learningSwitch.getFromPortMap(mockSwitch, Ethernet.toLong(Ethernet.toMACAddress("00:44:33:22:11:00")), (short) 42).shortValue();
+ verify(mockSwitch);
+
+ // Verify the MAC table inside the switch
+ assertEquals(1, result);
+ }
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/linkdiscovery/internal/LinkDiscoveryManagerTest.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/linkdiscovery/internal/LinkDiscoveryManagerTest.java
new file mode 100644
index 0000000..6e99acd
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/linkdiscovery/internal/LinkDiscoveryManagerTest.java
@@ -0,0 +1,432 @@
+/**
+* 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.linkdiscovery.internal;
+
+import static org.easymock.EasyMock.*;
+
+import java.util.Collections;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import net.floodlightcontroller.core.IFloodlightProviderService;
+import net.floodlightcontroller.core.IFloodlightProviderService.Role;
+import net.floodlightcontroller.core.IOFSwitch;
+import net.floodlightcontroller.core.module.FloodlightModuleContext;
+import net.floodlightcontroller.core.test.MockThreadPoolService;
+import net.floodlightcontroller.linkdiscovery.ILinkDiscoveryListener;
+import net.floodlightcontroller.linkdiscovery.ILinkDiscoveryService;
+import net.floodlightcontroller.linkdiscovery.LinkInfo;
+import net.floodlightcontroller.linkdiscovery.internal.LinkDiscoveryManager;
+import net.floodlightcontroller.restserver.IRestApiService;
+import net.floodlightcontroller.restserver.RestApiServer;
+import net.floodlightcontroller.routing.IRoutingService;
+import net.floodlightcontroller.routing.Link;
+import net.floodlightcontroller.storage.IStorageSourceService;
+import net.floodlightcontroller.storage.memory.MemoryStorageSource;
+import net.floodlightcontroller.test.FloodlightTestCase;
+import net.floodlightcontroller.threadpool.IThreadPoolService;
+import net.floodlightcontroller.topology.ITopologyService;
+import net.floodlightcontroller.topology.NodePortTuple;
+import net.floodlightcontroller.topology.TopologyManager;
+
+/**
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class LinkDiscoveryManagerTest extends FloodlightTestCase {
+
+ private TestLinkDiscoveryManager ldm;
+ protected static Logger log = LoggerFactory.getLogger(LinkDiscoveryManagerTest.class);
+
+ public class TestLinkDiscoveryManager extends LinkDiscoveryManager {
+ public boolean isSendLLDPsCalled = false;
+ public boolean isClearLinksCalled = false;
+
+ @Override
+ protected void discoverOnAllPorts() {
+ isSendLLDPsCalled = true;
+ super.discoverOnAllPorts();
+ }
+
+ public void reset() {
+ isSendLLDPsCalled = false;
+ isClearLinksCalled = false;
+ }
+
+ @Override
+ protected void clearAllLinks() {
+ isClearLinksCalled = true;
+ super.clearAllLinks();
+ }
+ }
+
+ public LinkDiscoveryManager getTopology() {
+ return ldm;
+ }
+
+ public IOFSwitch createMockSwitch(Long id) {
+ IOFSwitch mockSwitch = createNiceMock(IOFSwitch.class);
+ expect(mockSwitch.getId()).andReturn(id).anyTimes();
+ return mockSwitch;
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ FloodlightModuleContext cntx = new FloodlightModuleContext();
+ ldm = new TestLinkDiscoveryManager();
+ TopologyManager routingEngine = new TopologyManager();
+ ldm.linkDiscoveryAware = new ArrayList<ILinkDiscoveryListener>();
+ MockThreadPoolService tp = new MockThreadPoolService();
+ RestApiServer restApi = new RestApiServer();
+ cntx.addService(IRestApiService.class, restApi);
+ cntx.addService(IThreadPoolService.class, tp);
+ cntx.addService(IRoutingService.class, routingEngine);
+ cntx.addService(ILinkDiscoveryService.class, ldm);
+ cntx.addService(ITopologyService.class, ldm);
+ cntx.addService(IStorageSourceService.class, new MemoryStorageSource());
+ cntx.addService(IFloodlightProviderService.class, getMockFloodlightProvider());
+ restApi.init(cntx);
+ tp.init(cntx);
+ routingEngine.init(cntx);
+ ldm.init(cntx);
+ restApi.startUp(cntx);
+ tp.startUp(cntx);
+ routingEngine.startUp(cntx);
+ ldm.startUp(cntx);
+
+ IOFSwitch sw1 = createMockSwitch(1L);
+ IOFSwitch sw2 = createMockSwitch(2L);
+ Map<Long, IOFSwitch> switches = new HashMap<Long, IOFSwitch>();
+ switches.put(1L, sw1);
+ switches.put(2L, sw2);
+ getMockFloodlightProvider().setSwitches(switches);
+ replay(sw1, sw2);
+ }
+
+ @Test
+ public void testAddOrUpdateLink() throws Exception {
+ LinkDiscoveryManager topology = getTopology();
+
+ Link lt = new Link(1L, 2, 2L, 1);
+ LinkInfo info = new LinkInfo(System.currentTimeMillis(),
+ System.currentTimeMillis(), null,
+ 0, 0);
+ topology.addOrUpdateLink(lt, info);
+
+
+ NodePortTuple srcNpt = new NodePortTuple(1L, 2);
+ NodePortTuple dstNpt = new NodePortTuple(2L, 1);
+
+ // check invariants hold
+ assertNotNull(topology.switchLinks.get(lt.getSrc()));
+ assertTrue(topology.switchLinks.get(lt.getSrc()).contains(lt));
+ assertNotNull(topology.portLinks.get(srcNpt));
+ assertTrue(topology.portLinks.get(srcNpt).contains(lt));
+ assertNotNull(topology.portLinks.get(dstNpt));
+ assertTrue(topology.portLinks.get(dstNpt).contains(lt));
+ assertTrue(topology.links.containsKey(lt));
+ }
+
+ @Test
+ public void testDeleteLink() throws Exception {
+ LinkDiscoveryManager topology = getTopology();
+
+ Link lt = new Link(1L, 2, 2L, 1);
+ LinkInfo info = new LinkInfo(System.currentTimeMillis(),
+ System.currentTimeMillis(), null,
+ 0, 0);
+ topology.addOrUpdateLink(lt, info);
+ topology.deleteLinks(Collections.singletonList(lt), "Test");
+
+ // check invariants hold
+ assertNull(topology.switchLinks.get(lt.getSrc()));
+ assertNull(topology.switchLinks.get(lt.getDst()));
+ assertNull(topology.portLinks.get(lt.getSrc()));
+ assertNull(topology.portLinks.get(lt.getDst()));
+ assertTrue(topology.links.isEmpty());
+ }
+
+ @Test
+ public void testAddOrUpdateLinkToSelf() throws Exception {
+ LinkDiscoveryManager topology = getTopology();
+
+ Link lt = new Link(1L, 2, 2L, 3);
+ NodePortTuple srcNpt = new NodePortTuple(1L, 2);
+ NodePortTuple dstNpt = new NodePortTuple(2L, 3);
+
+ LinkInfo info = new LinkInfo(System.currentTimeMillis(),
+ System.currentTimeMillis(), null,
+ 0, 0);
+ topology.addOrUpdateLink(lt, info);
+
+ // check invariants hold
+ assertNotNull(topology.switchLinks.get(lt.getSrc()));
+ assertTrue(topology.switchLinks.get(lt.getSrc()).contains(lt));
+ assertNotNull(topology.portLinks.get(srcNpt));
+ assertTrue(topology.portLinks.get(srcNpt).contains(lt));
+ assertNotNull(topology.portLinks.get(dstNpt));
+ assertTrue(topology.portLinks.get(dstNpt).contains(lt));
+ assertTrue(topology.links.containsKey(lt));
+ }
+
+ @Test
+ public void testDeleteLinkToSelf() throws Exception {
+ LinkDiscoveryManager topology = getTopology();
+
+ Link lt = new Link(1L, 2, 1L, 3);
+ NodePortTuple srcNpt = new NodePortTuple(1L, 2);
+ NodePortTuple dstNpt = new NodePortTuple(2L, 3);
+
+ LinkInfo info = new LinkInfo(System.currentTimeMillis(),
+ System.currentTimeMillis(), null,
+ 0, 0);
+ topology.addOrUpdateLink(lt, info);
+ topology.deleteLinks(Collections.singletonList(lt), "Test to self");
+
+ // check invariants hold
+ assertNull(topology.switchLinks.get(lt.getSrc()));
+ assertNull(topology.switchLinks.get(lt.getDst()));
+ assertNull(topology.portLinks.get(srcNpt));
+ assertNull(topology.portLinks.get(dstNpt));
+ assertTrue(topology.links.isEmpty());
+ }
+
+ @Test
+ public void testRemovedSwitch() {
+ LinkDiscoveryManager topology = getTopology();
+
+ Link lt = new Link(1L, 2, 2L, 1);
+ NodePortTuple srcNpt = new NodePortTuple(1L, 2);
+ NodePortTuple dstNpt = new NodePortTuple(2L, 1);
+ LinkInfo info = new LinkInfo(System.currentTimeMillis(),
+ System.currentTimeMillis(), null,
+ 0, 0);
+ topology.addOrUpdateLink(lt, info);
+
+ IOFSwitch sw1 = getMockFloodlightProvider().getSwitches().get(1L);
+ IOFSwitch sw2 = getMockFloodlightProvider().getSwitches().get(2L);
+ // Mock up our expected behavior
+ topology.removedSwitch(sw1);
+ verify(sw1, sw2);
+
+ // check invariants hold
+ assertNull(topology.switchLinks.get(lt.getSrc()));
+ assertNull(topology.switchLinks.get(lt.getDst()));
+ assertNull(topology.portLinks.get(srcNpt));
+ assertNull(topology.portLinks.get(dstNpt));
+ assertTrue(topology.links.isEmpty());
+ }
+
+ @Test
+ public void testRemovedSwitchSelf() {
+ LinkDiscoveryManager topology = getTopology();
+ IOFSwitch sw1 = createMockSwitch(1L);
+ replay(sw1);
+ Link lt = new Link(1L, 2, 1L, 3);
+ LinkInfo info = new LinkInfo(System.currentTimeMillis(),
+ System.currentTimeMillis(), null,
+ 0, 0);
+ topology.addOrUpdateLink(lt, info);
+
+ // Mock up our expected behavior
+ topology.removedSwitch(sw1);
+
+ verify(sw1);
+ // check invariants hold
+ assertNull(topology.switchLinks.get(lt.getSrc()));
+ assertNull(topology.portLinks.get(lt.getSrc()));
+ assertNull(topology.portLinks.get(lt.getDst()));
+ assertTrue(topology.links.isEmpty());
+ }
+
+ @Test
+ public void testAddUpdateLinks() throws Exception {
+ LinkDiscoveryManager topology = getTopology();
+
+ Link lt = new Link(1L, 1, 2L, 1);
+ NodePortTuple srcNpt = new NodePortTuple(1L, 1);
+ NodePortTuple dstNpt = new NodePortTuple(2L, 1);
+
+ LinkInfo info;
+
+ info = new LinkInfo(System.currentTimeMillis() - 40000,
+ System.currentTimeMillis() - 40000, null,
+ 0, 0);
+ topology.addOrUpdateLink(lt, info);
+
+ // check invariants hold
+ assertNotNull(topology.switchLinks.get(lt.getSrc()));
+ assertTrue(topology.switchLinks.get(lt.getSrc()).contains(lt));
+ assertNotNull(topology.portLinks.get(srcNpt));
+ assertTrue(topology.portLinks.get(srcNpt).contains(lt));
+ assertNotNull(topology.portLinks.get(dstNpt));
+ assertTrue(topology.portLinks.get(dstNpt).contains(lt));
+ assertTrue(topology.links.containsKey(lt));
+ assertTrue(topology.portBroadcastDomainLinks.get(srcNpt) == null ||
+ topology.portBroadcastDomainLinks.get(srcNpt).contains(lt) == false);
+ assertTrue(topology.portBroadcastDomainLinks.get(dstNpt) == null ||
+ topology.portBroadcastDomainLinks.get(dstNpt).contains(lt) == false);
+
+ topology.timeoutLinks();
+
+
+ info = new LinkInfo(System.currentTimeMillis(),/* firstseen */
+ null,/* unicast */
+ System.currentTimeMillis(), 0, 0);
+ topology.addOrUpdateLink(lt, info);
+ assertTrue(topology.links.get(lt).getUnicastValidTime() == null);
+ assertTrue(topology.links.get(lt).getMulticastValidTime() != null);
+ assertTrue(topology.portBroadcastDomainLinks.get(srcNpt).contains(lt));
+ assertTrue(topology.portBroadcastDomainLinks.get(dstNpt).contains(lt));
+
+
+ // Add a link info based on info that woudld be obtained from unicast LLDP
+ // Setting the unicast LLDP reception time to be 40 seconds old, so we can use
+ // this to test timeout after this test. Although the info is initialized
+ // with LT_OPENFLOW_LINK, the link property should be changed to LT_NON_OPENFLOW
+ // by the addOrUpdateLink method.
+ info = new LinkInfo(System.currentTimeMillis() - 40000,
+ System.currentTimeMillis() - 40000, null, 0, 0);
+ topology.addOrUpdateLink(lt, info);
+ assertTrue(topology.portBroadcastDomainLinks.get(srcNpt) == null ||
+ topology.portBroadcastDomainLinks.get(srcNpt).contains(lt) == false);
+ assertTrue(topology.portBroadcastDomainLinks.get(dstNpt) == null ||
+ topology.portBroadcastDomainLinks.get(dstNpt).contains(lt) == false);
+
+ // Expect to timeout the unicast Valid Time, but not the multicast Valid time
+ // So the link type should go back to non-openflow link.
+ topology.timeoutLinks();
+ assertTrue(topology.links.get(lt).getUnicastValidTime() == null);
+ assertTrue(topology.links.get(lt).getMulticastValidTime() != null);
+ assertTrue(topology.portBroadcastDomainLinks.get(srcNpt).contains(lt));
+ assertTrue(topology.portBroadcastDomainLinks.get(dstNpt).contains(lt));
+
+ // Set the multicastValidTime to be old and see if that also times out.
+ info = new LinkInfo(System.currentTimeMillis() - 40000,
+ null, System.currentTimeMillis() - 40000, 0, 0);
+ topology.addOrUpdateLink(lt, info);
+ topology.timeoutLinks();
+ assertTrue(topology.links.get(lt) == null);
+ assertTrue(topology.portBroadcastDomainLinks.get(srcNpt) == null ||
+ topology.portBroadcastDomainLinks.get(srcNpt).contains(lt) == false);
+ assertTrue(topology.portBroadcastDomainLinks.get(dstNpt) == null ||
+ topology.portBroadcastDomainLinks.get(dstNpt).contains(lt) == false);
+
+
+ // Test again only with multicast LLDP
+ info = new LinkInfo(System.currentTimeMillis() - 40000,
+ null, System.currentTimeMillis() - 40000, 0, 0);
+ topology.addOrUpdateLink(lt, info);
+ assertTrue(topology.links.get(lt).getUnicastValidTime() == null);
+ assertTrue(topology.links.get(lt).getMulticastValidTime() != null);
+ assertTrue(topology.portBroadcastDomainLinks.get(srcNpt).contains(lt));
+ assertTrue(topology.portBroadcastDomainLinks.get(dstNpt).contains(lt));
+
+ // Call timeout and check if link is no longer present.
+ topology.timeoutLinks();
+ assertTrue(topology.links.get(lt) == null);
+ assertTrue(topology.portBroadcastDomainLinks.get(srcNpt) == null ||
+ topology.portBroadcastDomainLinks.get(srcNpt).contains(lt) == false);
+ assertTrue(topology.portBroadcastDomainLinks.get(dstNpt) == null ||
+ topology.portBroadcastDomainLinks.get(dstNpt).contains(lt) == false);
+
+ // Start clean and see if loops are also added.
+ lt = new Link(1L, 1, 1L, 2);
+ srcNpt = new NodePortTuple(1L, 1);
+ dstNpt = new NodePortTuple(1L, 2);
+ info = new LinkInfo(System.currentTimeMillis() - 40000,
+ null, System.currentTimeMillis() - 40000, 0, 0);
+ topology.addOrUpdateLink(lt, info);
+ assertTrue(topology.portBroadcastDomainLinks.get(srcNpt).contains(lt));
+ assertTrue(topology.portBroadcastDomainLinks.get(dstNpt).contains(lt));
+
+
+ // Start clean and see if loops are also added.
+ lt = new Link(1L, 1, 1L, 3);
+ srcNpt = new NodePortTuple(1L, 1);
+ dstNpt = new NodePortTuple(1L, 3);
+ info = new LinkInfo(System.currentTimeMillis() - 40000,
+ null, System.currentTimeMillis() - 40000, 0, 0);
+ topology.addOrUpdateLink(lt, info);
+ assertTrue(topology.portBroadcastDomainLinks.get(srcNpt).contains(lt));
+ assertTrue(topology.portBroadcastDomainLinks.get(dstNpt).contains(lt));
+
+
+ // Start clean and see if loops are also added.
+ lt = new Link(1L, 4, 1L, 5);
+ srcNpt = new NodePortTuple(1L, 4);
+ dstNpt = new NodePortTuple(1L, 5);
+ info = new LinkInfo(System.currentTimeMillis() - 40000,
+ null, System.currentTimeMillis() - 40000, 0, 0);
+ topology.addOrUpdateLink(lt, info);
+ assertTrue(topology.portBroadcastDomainLinks.get(srcNpt).contains(lt));
+ assertTrue(topology.portBroadcastDomainLinks.get(dstNpt).contains(lt));
+
+
+ // Start clean and see if loops are also added.
+ lt = new Link(1L, 3, 1L, 5);
+ srcNpt = new NodePortTuple(1L, 3);
+ dstNpt = new NodePortTuple(1L, 5);
+ info = new LinkInfo(System.currentTimeMillis() - 40000,
+ null, System.currentTimeMillis() - 40000, 0, 0);
+ topology.addOrUpdateLink(lt, info);
+ assertTrue(topology.portBroadcastDomainLinks.get(srcNpt).contains(lt));
+ assertTrue(topology.portBroadcastDomainLinks.get(dstNpt).contains(lt));
+ }
+
+ @Test
+ public void testHARoleChange() throws Exception {
+ LinkDiscoveryManager topology = getTopology();
+ IOFSwitch sw1 = createMockSwitch(1L);
+ IOFSwitch sw2 = createMockSwitch(2L);
+ replay(sw1, sw2);
+ Link lt = new Link(1L, 2, 2L, 1);
+ NodePortTuple srcNpt = new NodePortTuple(1L, 2);
+ NodePortTuple dstNpt = new NodePortTuple(2L, 1);
+ LinkInfo info = new LinkInfo(System.currentTimeMillis(),
+ System.currentTimeMillis(), null,
+ 0, 0);
+ topology.addOrUpdateLink(lt, info);
+
+ // check invariants hold
+ assertNotNull(topology.switchLinks.get(lt.getSrc()));
+ assertTrue(topology.switchLinks.get(lt.getSrc()).contains(lt));
+ assertNotNull(topology.portLinks.get(srcNpt));
+ assertTrue(topology.portLinks.get(srcNpt).contains(lt));
+ assertNotNull(topology.portLinks.get(dstNpt));
+ assertTrue(topology.portLinks.get(dstNpt).contains(lt));
+ assertTrue(topology.links.containsKey(lt));
+
+ // check that it clears from memory
+ getMockFloodlightProvider().dispatchRoleChanged(null, Role.SLAVE);
+ assertTrue(topology.switchLinks.isEmpty());
+ getMockFloodlightProvider().dispatchRoleChanged(Role.SLAVE, Role.MASTER);
+ // check that lldps were sent
+ assertTrue(ldm.isSendLLDPsCalled);
+ assertTrue(ldm.isClearLinksCalled);
+ ldm.reset();
+ }
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/packet/BSNTest.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/packet/BSNTest.java
new file mode 100644
index 0000000..d672fdf
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/packet/BSNTest.java
@@ -0,0 +1,80 @@
+/**
+* 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.packet;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Arrays;
+
+import org.junit.Test;
+
+/**
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ *
+ */
+public class BSNTest {
+ protected byte[] probePkt = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, // src mac
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, // dst mac
+ (byte) 0x89, 0x42, // BSN type
+ 0x20, 0x00, 0x06, 0x04, 0x00, 0x01, 0x00, 0x00, // BSN header
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // controller id
+ 0x00, 0x00, 0x00, 0x03, // sequence id
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, // src mac
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, // dst mac
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, // switch dpid
+ 0x00, 0x00, 0x00, 0x01 // port number
+ };
+
+ protected Ethernet getProbePacket() {
+ return (Ethernet) new Ethernet()
+ .setSourceMACAddress("00:00:00:00:00:04")
+ .setDestinationMACAddress("00:00:00:00:00:01")
+ .setEtherType(Ethernet.TYPE_BSN)
+ .setPayload(new BSN(BSN.BSN_TYPE_PROBE)
+ .setPayload(new BSNPROBE()
+ .setSequenceId(3)
+ .setSrcMac(new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x01})
+ .setDstMac(new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x04})
+ .setSrcSwDpid(0x06)
+ .setSrcPortNo(0x01)
+ )
+ );
+ }
+
+ @Test
+ public void testSerialize() throws Exception {
+ Ethernet pkt = getProbePacket();
+ byte[] serialized = pkt.serialize();
+ assertTrue(Arrays.equals(probePkt, serialized));
+ }
+
+ @Test
+ public void testDeserialize() throws Exception {
+ Ethernet pkt = (Ethernet) new Ethernet().deserialize(probePkt, 0, probePkt.length);
+ assertTrue(Arrays.equals(probePkt, pkt.serialize()));
+
+ Ethernet expected = getProbePacket();
+ assertEquals(expected, pkt);
+ }
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/packet/DHCPTest.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/packet/DHCPTest.java
new file mode 100644
index 0000000..b83ffa8
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/packet/DHCPTest.java
@@ -0,0 +1,595 @@
+/**
+* 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.packet;
+
+
+
+import java.util.Arrays;
+import java.util.ListIterator;
+
+import junit.framework.TestCase;
+
+
+/**
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ *
+ */
+public class DHCPTest extends TestCase {
+ public byte[] dhcpPacket = new byte[] {
+ (byte) 0x01, (byte) 0x01,
+ (byte) 0x06, (byte) 0x00, (byte) 0x66, (byte) 0xf2, (byte) 0x8a,
+ (byte) 0x11, (byte) 0x00, (byte) 0x06, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0xc0, (byte) 0x9f, (byte) 0x9f,
+ (byte) 0xfe, (byte) 0xd8, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x63,
+ (byte) 0x82, (byte) 0x53, (byte) 0x63, (byte) 0x35, (byte) 0x01,
+ (byte) 0x01, (byte) 0x37, (byte) 0x0a, (byte) 0x01, (byte) 0x1c,
+ (byte) 0x02, (byte) 0x03, (byte) 0x0f, (byte) 0x06, (byte) 0x0c,
+ (byte) 0x28, (byte) 0x29, (byte) 0x2a, (byte) 0xff, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00
+ };
+
+ public byte[] dhcpPacket2 = new byte[] { (byte) 0xff, (byte) 0xff,
+ (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x00,
+ (byte) 0xc0, (byte) 0x9f, (byte) 0x9e, (byte) 0x11, (byte) 0x84,
+ (byte) 0x08, (byte) 0x00, (byte) 0x45, (byte) 0x00, (byte) 0x02,
+ (byte) 0x40, (byte) 0x00, (byte) 0x04, (byte) 0x00, (byte) 0x00,
+ (byte) 0x14, (byte) 0x11, (byte) 0xa4, (byte) 0xaa, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0xff, (byte) 0xff,
+ (byte) 0xff, (byte) 0xff, (byte) 0x00, (byte) 0x44, (byte) 0x00,
+ (byte) 0x43, (byte) 0x02, (byte) 0x2c, (byte) 0xdd, (byte) 0x9d,
+ (byte) 0x01, (byte) 0x01, (byte) 0x06, (byte) 0x00, (byte) 0xa4,
+ (byte) 0x9e, (byte) 0x11, (byte) 0x84, (byte) 0x00, (byte) 0x40,
+ (byte) 0x80, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0xc0,
+ (byte) 0x9f, (byte) 0x9e, (byte) 0x11, (byte) 0x84, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x63, (byte) 0x82, (byte) 0x53, (byte) 0x63,
+ (byte) 0x35, (byte) 0x01, (byte) 0x01, (byte) 0x37, (byte) 0x18,
+ (byte) 0x01, (byte) 0x02, (byte) 0x03, (byte) 0x05, (byte) 0x06,
+ (byte) 0x0b, (byte) 0x0c, (byte) 0x0d, (byte) 0x0f, (byte) 0x10,
+ (byte) 0x11, (byte) 0x12, (byte) 0x2b, (byte) 0x36, (byte) 0x3c,
+ (byte) 0x43, (byte) 0x80, (byte) 0x81, (byte) 0x82, (byte) 0x83,
+ (byte) 0x84, (byte) 0x85, (byte) 0x86, (byte) 0x87, (byte) 0x39,
+ (byte) 0x02, (byte) 0x04, (byte) 0xec, (byte) 0x61, (byte) 0x11,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x5d, (byte) 0x02, (byte) 0x00,
+ (byte) 0x00, (byte) 0x5e, (byte) 0x03, (byte) 0x01, (byte) 0x02,
+ (byte) 0x01, (byte) 0x3c, (byte) 0x20, (byte) 0x50, (byte) 0x58,
+ (byte) 0x45, (byte) 0x43, (byte) 0x6c, (byte) 0x69, (byte) 0x65,
+ (byte) 0x6e, (byte) 0x74, (byte) 0x3a, (byte) 0x41, (byte) 0x72,
+ (byte) 0x63, (byte) 0x68, (byte) 0x3a, (byte) 0x30, (byte) 0x30,
+ (byte) 0x30, (byte) 0x30, (byte) 0x30, (byte) 0x3a, (byte) 0x55,
+ (byte) 0x4e, (byte) 0x44, (byte) 0x49, (byte) 0x3a, (byte) 0x30,
+ (byte) 0x30, (byte) 0x32, (byte) 0x30, (byte) 0x30, (byte) 0x31,
+ (byte) 0xff, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00 };
+
+ public byte[] dhcpPacket3 = new byte[] {
+ (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+ (byte) 0xff, (byte) 0x74, (byte) 0x44, (byte) 0x01, (byte) 0x72,
+ (byte) 0xd8, (byte) 0x41, (byte) 0x08, (byte) 0x00, (byte) 0x45,
+ (byte) 0x00, (byte) 0x01, (byte) 0x1f, (byte) 0x48, (byte) 0xcd,
+ (byte) 0x00, (byte) 0x00, (byte) 0x40, (byte) 0x11, (byte) 0x6f,
+ (byte) 0x6a, (byte) 0xc0, (byte) 0xa8, (byte) 0x00, (byte) 0xef,
+ (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x00,
+ (byte) 0x44, (byte) 0x00, (byte) 0x43, (byte) 0x01, (byte) 0x0b,
+ (byte) 0xb3, (byte) 0x0f, (byte) 0x01, (byte) 0x01, (byte) 0x06,
+ (byte) 0x00, (byte) 0x82, (byte) 0x88, (byte) 0xa6, (byte) 0xc9,
+ (byte) 0x00, (byte) 0x00, (byte) 0x80, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x74, (byte) 0x44, (byte) 0x01, (byte) 0x72, (byte) 0xd8,
+ (byte) 0x41, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x63, (byte) 0x82,
+ (byte) 0x53, (byte) 0x63, (byte) 0x35, (byte) 0x01, (byte) 0x01,
+ (byte) 0x32, (byte) 0x04, (byte) 0xc0, (byte) 0xa8, (byte) 0x0a,
+ (byte) 0xa9, (byte) 0x39, (byte) 0x02, (byte) 0x02, (byte) 0x40,
+ (byte) 0x37, (byte) 0x03, (byte) 0x01, (byte) 0x03, (byte) 0x06,
+ (byte) 0xff
+ };
+
+ public byte[] dhcpPacketPXE = new byte[] { (byte) 0xff, (byte) 0xff,
+ (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x00,
+ (byte) 0x19, (byte) 0xb9, (byte) 0xb0, (byte) 0x01, (byte) 0x44,
+ (byte) 0x08, (byte) 0x00, (byte) 0x45, (byte) 0x10, (byte) 0x01,
+ (byte) 0x48, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x80, (byte) 0x11, (byte) 0x2c, (byte) 0x98, (byte) 0x0a,
+ (byte) 0x00, (byte) 0x02, (byte) 0xfe, (byte) 0xff, (byte) 0xff,
+ (byte) 0xff, (byte) 0xff, (byte) 0x00, (byte) 0x43, (byte) 0x00,
+ (byte) 0x44, (byte) 0x01, (byte) 0x34, (byte) 0xa6, (byte) 0xf0,
+ (byte) 0x02, (byte) 0x01, (byte) 0x06, (byte) 0x00, (byte) 0xa0,
+ (byte) 0x9e, (byte) 0x0c, (byte) 0x13, (byte) 0x00, (byte) 0x04,
+ (byte) 0x80, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x0a, (byte) 0x00, (byte) 0x02, (byte) 0x14,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0xc0,
+ (byte) 0x9f, (byte) 0x9e, (byte) 0x0c, (byte) 0x13, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x78, (byte) 0x65,
+ (byte) 0x6e, (byte) 0x73, (byte) 0x65, (byte) 0x72, (byte) 0x76,
+ (byte) 0x65, (byte) 0x72, (byte) 0x5f, (byte) 0x35, (byte) 0x2e,
+ (byte) 0x36, (byte) 0x2f, (byte) 0x70, (byte) 0x78, (byte) 0x65,
+ (byte) 0x6c, (byte) 0x69, (byte) 0x6e, (byte) 0x75, (byte) 0x78,
+ (byte) 0x2e, (byte) 0x30, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x63, (byte) 0x82, (byte) 0x53, (byte) 0x63,
+ (byte) 0x35, (byte) 0x01, (byte) 0x02, (byte) 0x36, (byte) 0x04,
+ (byte) 0x0a, (byte) 0x00, (byte) 0x02, (byte) 0xfe, (byte) 0x33,
+ (byte) 0x04, (byte) 0x00, (byte) 0x00, (byte) 0xa8, (byte) 0xc0,
+ (byte) 0x01, (byte) 0x04, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+ (byte) 0x00, (byte) 0x03, (byte) 0x04, (byte) 0x0a, (byte) 0x00,
+ (byte) 0x02, (byte) 0xfe, (byte) 0x0c, (byte) 0x0d, (byte) 0x64,
+ (byte) 0x6e, (byte) 0x72, (byte) 0x63, (byte) 0x2d, (byte) 0x68,
+ (byte) 0x6f, (byte) 0x73, (byte) 0x74, (byte) 0x30, (byte) 0x30,
+ (byte) 0x32, (byte) 0x30, (byte) 0xff, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00
+ };
+
+ public byte[] dhcpPacketBadOption1 = new byte[] { (byte) 0xff, (byte) 0xff,
+ (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x00,
+ (byte) 0x19, (byte) 0xb9, (byte) 0xb0, (byte) 0x01, (byte) 0x44,
+ (byte) 0x08, (byte) 0x00, (byte) 0x45, (byte) 0x10, (byte) 0x01,
+ (byte) 0x48, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x80, (byte) 0x11, (byte) 0x2c, (byte) 0x98, (byte) 0x0a,
+ (byte) 0x00, (byte) 0x02, (byte) 0xfe, (byte) 0xff, (byte) 0xff,
+ (byte) 0xff, (byte) 0xff, (byte) 0x00, (byte) 0x44, (byte) 0x00,
+ (byte) 0x43, (byte) 0x01, (byte) 0x34, (byte) 0xa6, (byte) 0xf0,
+ (byte) 0x02, (byte) 0x01, (byte) 0x06, (byte) 0x00, (byte) 0xa0,
+ (byte) 0x9e, (byte) 0x0c, (byte) 0x13, (byte) 0x00, (byte) 0x04,
+ (byte) 0x80, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x0a, (byte) 0x00, (byte) 0x02, (byte) 0x14,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0xc0,
+ (byte) 0x9f, (byte) 0x9e, (byte) 0x0c, (byte) 0x13, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x78, (byte) 0x65,
+ (byte) 0x6e, (byte) 0x73, (byte) 0x65, (byte) 0x72, (byte) 0x76,
+ (byte) 0x65, (byte) 0x72, (byte) 0x5f, (byte) 0x35, (byte) 0x2e,
+ (byte) 0x36, (byte) 0x2f, (byte) 0x70, (byte) 0x78, (byte) 0x65,
+ (byte) 0x6c, (byte) 0x69, (byte) 0x6e, (byte) 0x75, (byte) 0x78,
+ (byte) 0x2e, (byte) 0x30, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x63, (byte) 0x82, (byte) 0x53, (byte) 0x63,
+ (byte) 0x35, (byte) 0x01, (byte) 0x01, (byte) 0x36, (byte) 0x04,
+ (byte) 0x0a, (byte) 0x00, (byte) 0x02, (byte) 0xfe, (byte) 0x33,
+ (byte) 0x04, (byte) 0x00, (byte) 0x00, (byte) 0xa8, (byte) 0xc0,
+ (byte) 0x01, (byte) 0x04, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+ (byte) 0x00, (byte) 0x03, (byte) 0x04, (byte) 0x0a, (byte) 0x00,
+ (byte) 0x02, (byte) 0xfe, (byte) 0x0c, (byte) 0x30, (byte) 0x64,
+ (byte) 0x6e, (byte) 0x72, (byte) 0x63, (byte) 0x2d, (byte) 0x68,
+ (byte) 0x6f, (byte) 0x73, (byte) 0x74, (byte) 0x30, (byte) 0x30,
+ (byte) 0x32, (byte) 0x30, (byte) 0xff, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00
+ };
+
+ public byte[] dhcpPacketBadHeader = new byte[] { (byte) 0xff, (byte) 0xff,
+ (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x00,
+ (byte) 0x19, (byte) 0xb9, (byte) 0xb0, (byte) 0x01, (byte) 0x44,
+ (byte) 0x08, (byte) 0x00, (byte) 0x45, (byte) 0x10, (byte) 0x01,
+ (byte) 0x48, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x80, (byte) 0x11, (byte) 0x2c, (byte) 0x98, (byte) 0x0a,
+ (byte) 0x00, (byte) 0x02, (byte) 0xfe, (byte) 0xff, (byte) 0xff,
+ (byte) 0xff, (byte) 0xff, (byte) 0x00, (byte) 0x44, (byte) 0x00,
+ (byte) 0x43, (byte) 0x01, (byte) 0x34, (byte) 0xa6, (byte) 0xf0,
+ (byte) 0x02, (byte) 0x01, (byte) 0x06, (byte) 0x00, (byte) 0xa0,
+ (byte) 0x9e, (byte) 0x0c, (byte) 0x13, (byte) 0x00, (byte) 0x04,
+ (byte) 0x80, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x0a, (byte) 0x00, (byte) 0x02, (byte) 0x14,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0xc0,
+ (byte) 0x9f, (byte) 0x9e, (byte) 0x0c, (byte) 0x13, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x78, (byte) 0x65,
+ (byte) 0x6e, (byte) 0x73, (byte) 0x65, (byte) 0x72, (byte) 0x76,
+ (byte) 0x65, (byte) 0x72, (byte) 0x5f, (byte) 0x35, (byte) 0x2e,
+ (byte) 0x36, (byte) 0x2f, (byte) 0x70, (byte) 0x78, (byte) 0x65,
+ (byte) 0x6c, (byte) 0x69, (byte) 0x6e, (byte) 0x75, (byte) 0x78,
+ (byte) 0x2e, (byte) 0x30, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00
+ };
+
+ private void resetChecksumsAndLengths(IPv4 ipv4, UDP udp) {
+ ipv4.setChecksum((short)0);
+ udp.setChecksum((short)0);
+ }
+
+ public void testSerialize() {
+ DHCP dhcp = new DHCP();
+ dhcp.deserialize(dhcpPacket, 0, dhcpPacket.length);
+ byte[] result = dhcp.serialize();
+ assertTrue(Arrays.equals(this.dhcpPacket, result));
+ }
+
+ public void testDeSerialize() {
+ Ethernet eth = new Ethernet();
+ eth.deserialize(dhcpPacket2, 0, dhcpPacket2.length);
+ assertTrue(eth.getPayload() instanceof IPv4);
+ IPv4 ipv4 = (IPv4) eth.getPayload();
+ assertTrue(ipv4.getPayload() instanceof UDP);
+ UDP udp = (UDP) ipv4.getPayload();
+ assertTrue(udp.getPayload() instanceof DHCP);
+ DHCP dhcp = (DHCP) udp.getPayload();
+ /** The invalid option in DHCP packet is dropped. Reset checksums and
+ * length field so that the serialize() function can re-compute them
+ */
+ resetChecksumsAndLengths(ipv4, udp);
+ assertEquals(DHCP.OPCODE_REQUEST, dhcp.getOpCode());
+ }
+
+ public void testDeSerializeReSerialize() {
+ Ethernet eth = new Ethernet();
+ eth.deserialize(dhcpPacket3, 0, dhcpPacket3.length);
+ assertTrue(eth.getPayload() instanceof IPv4);
+ IPv4 ipv4 = (IPv4) eth.getPayload();
+ assertTrue(ipv4.getPayload() instanceof UDP);
+
+ byte[] serializedPacket = eth.serialize();
+ Ethernet eth2 = new Ethernet();
+ eth2.deserialize(serializedPacket, 0, serializedPacket.length);
+ IPv4 ipv42 = (IPv4) eth2.getPayload();
+
+ short ipchecksum = ipv42.getChecksum();
+ ipv42.setChecksum((short) 0);
+ eth2.serialize();
+ assertEquals(ipchecksum, ipv42.getChecksum());
+ }
+
+ public void testDeSerializePXE() {
+ Ethernet eth = new Ethernet();
+ eth.deserialize(dhcpPacketPXE, 0, dhcpPacketPXE.length);
+ assertTrue(eth.getPayload() instanceof IPv4);
+ IPv4 ipv4 = (IPv4) eth.getPayload();
+ assertTrue(ipv4.getPayload() instanceof UDP);
+ UDP udp = (UDP) ipv4.getPayload();
+ assertTrue(udp.getPayload() instanceof DHCP);
+ DHCP dhcp = (DHCP) udp.getPayload();
+ /** The invalid option in DHCP packet is dropped. Reset checksums and
+ * length field so that the serialize() function can re-compute them
+ */
+ resetChecksumsAndLengths(ipv4, udp);
+
+ assertEquals(DHCP.OPCODE_REPLY, dhcp.getOpCode());
+ assertEquals("xenserver_5.6/pxelinux.0", dhcp.getBootFileName());
+
+ byte[] result = eth.serialize();
+ assertTrue(Arrays.equals(this.dhcpPacketPXE, result));
+ }
+
+ public void testDeSerializeBad1() {
+ Ethernet eth = new Ethernet();
+ eth.deserialize(dhcpPacketBadOption1, 0, dhcpPacketBadOption1.length);
+ assertTrue(eth.getPayload() instanceof IPv4);
+ IPv4 ipv4 = (IPv4) eth.getPayload();
+ assertTrue(ipv4.getPayload() instanceof UDP);
+ UDP udp = (UDP) ipv4.getPayload();
+ assertTrue(udp.getPayload() instanceof DHCP);
+ DHCP dhcp = (DHCP) udp.getPayload();
+ /** The invalid option in DHCP packet is dropped. Reset checksums and
+ * length field so that the serialize() function can re-compute them
+ */
+ resetChecksumsAndLengths(ipv4, udp);
+
+ assertEquals(DHCP.OPCODE_REPLY, dhcp.getOpCode());
+ ListIterator<DHCPOption> lit = dhcp.getOptions().listIterator();
+ // Expect 5 correct options and an END option.
+ assertEquals(dhcp.getOptions().size(), 6);
+ while (lit.hasNext()) {
+ DHCPOption option = lit.next();
+ assertFalse(option.code == (byte)0x0c);
+ }
+
+ byte[] result = eth.serialize();
+ // Since one option is badly formated, the result is different.
+ assertFalse(Arrays.equals(this.dhcpPacketPXE, result));
+ }
+
+ public void testDeSerializeBadHeader() {
+ Ethernet eth = new Ethernet();
+ eth.deserialize(dhcpPacketBadHeader, 0, dhcpPacketBadHeader.length);
+ assertTrue(eth.getPayload() instanceof IPv4);
+ IPv4 ipv4 = (IPv4) eth.getPayload();
+ assertTrue(ipv4.getPayload() instanceof UDP);
+ UDP udp = (UDP) ipv4.getPayload();
+ assertTrue(udp.getPayload() instanceof DHCP);
+ DHCP dhcp = (DHCP) udp.getPayload();
+
+ assertEquals(UDP.DHCP_CLIENT_PORT, udp.getSourcePort());
+ assertEquals(UDP.DHCP_SERVER_PORT, udp.getDestinationPort());
+
+ // should get invalid opCode of 0
+ assertEquals(0, dhcp.getOpCode());
+ }
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/packet/EthernetTest.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/packet/EthernetTest.java
new file mode 100644
index 0000000..f742f21
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/packet/EthernetTest.java
@@ -0,0 +1,75 @@
+/**
+* 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.packet;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Arrays;
+
+import org.junit.Test;
+
+/**
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ *
+ */
+public class EthernetTest {
+ @Test
+ public void testToMACAddress() {
+ byte[] address = new byte[] { 0x0, 0x11, 0x22, (byte) 0xff,
+ (byte) 0xee, (byte) 0xdd};
+ assertTrue(Arrays.equals(address, Ethernet
+ .toMACAddress("00:11:22:ff:ee:dd")));
+ assertTrue(Arrays.equals(address, Ethernet
+ .toMACAddress("00:11:22:FF:EE:DD")));
+ }
+
+ @Test
+ public void testSerialize() {
+ Ethernet ethernet = new Ethernet()
+ .setDestinationMACAddress("de:ad:be:ef:de:ad")
+ .setSourceMACAddress("be:ef:de:ad:be:ef")
+ .setEtherType((short) 0);
+ byte[] expected = new byte[] { (byte) 0xde, (byte) 0xad, (byte) 0xbe,
+ (byte) 0xef, (byte) 0xde, (byte) 0xad, (byte) 0xbe,
+ (byte) 0xef, (byte) 0xde, (byte) 0xad, (byte) 0xbe,
+ (byte) 0xef, 0x0, 0x0 };
+ assertTrue(Arrays.equals(expected, ethernet.serialize()));
+ }
+
+ @Test
+ public void testToLong() {
+ assertEquals(
+ 281474976710655L,
+ Ethernet.toLong(new byte[] { (byte) 0xff, (byte) 0xff,
+ (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff }));
+
+ assertEquals(
+ 1103823438081L,
+ Ethernet.toLong(new byte[] { (byte) 0x01, (byte) 0x01,
+ (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x01 }));
+
+ assertEquals(
+ 141289400074368L,
+ Ethernet.toLong(new byte[] { (byte) 0x80, (byte) 0x80,
+ (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80 }));
+ }
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/packet/ICMPTest.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/packet/ICMPTest.java
new file mode 100644
index 0000000..e554c4e
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/packet/ICMPTest.java
@@ -0,0 +1,67 @@
+/**
+* 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.packet;
+
+import static org.junit.Assert.assertTrue;
+
+import java.util.Arrays;
+
+import org.junit.Test;
+
+/**
+ * @author shudong.zhou@bigswitch.com
+ */
+public class ICMPTest {
+ private byte[] pktSerialized = new byte[] {
+ // (byte) 0xc8, 0x2a, 0x14, 0x2d, 0x35, (byte) 0xf1,
+ // 0x00, 0x0c, 0x29, 0x3b, (byte) 0x95, (byte) 0xf2, 0x08, 0x0,
+ 0x45, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x40, 0x00, 0x40, 0x01,
+ (byte) 0xa3, (byte) 0xcb,
+ (byte) 0xc0, (byte) 0xa8, (byte) 0x0a, (byte) 0xe7,
+ (byte) 0xc0, (byte) 0xa8, (byte) 0x0a, (byte) 0xdb,
+ 0x08, 0x00, 0x7f, 0x0a, 0x76, (byte) 0xf2, 0x00, 0x02,
+ 0x01, 0x01, 0x01 };
+ @Test
+ public void testSerialize() {
+ IPacket packet = new IPv4()
+ .setIdentification((short) 0)
+ .setFlags((byte) 0x02)
+ .setTtl((byte) 64)
+ .setSourceAddress("192.168.10.231")
+ .setDestinationAddress("192.168.10.219")
+ .setPayload(new ICMP()
+ .setIcmpType((byte) 8)
+ .setIcmpCode((byte) 0)
+ .setPayload(new Data(new byte[]
+ {0x76, (byte) 0xf2, 0x0, 0x2, 0x1, 0x1, 0x1}))
+ );
+ byte[] actual = packet.serialize();
+ assertTrue(Arrays.equals(pktSerialized, actual));
+ }
+
+ @Test
+ public void testDeserialize() {
+ IPacket packet = new IPv4();
+ packet.deserialize(pktSerialized, 0, pktSerialized.length);
+ byte[] pktSerialized1 = packet.serialize();
+ assertTrue(Arrays.equals(pktSerialized, pktSerialized1));
+ }
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/packet/IPv4Test.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/packet/IPv4Test.java
new file mode 100644
index 0000000..928a2de
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/packet/IPv4Test.java
@@ -0,0 +1,88 @@
+/**
+* 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.packet;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Arrays;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ *
+ */
+public class IPv4Test {
+ @Test
+ public void testToIPv4Address() {
+ int intIp = 0xc0a80001;
+ String stringIp = "192.168.0.1";
+ byte[] byteIp = new byte[] {(byte)192, (byte)168, (byte)0, (byte)1};
+ assertEquals(intIp, IPv4.toIPv4Address(stringIp));
+ assertEquals(intIp, IPv4.toIPv4Address(byteIp));
+ assertTrue(Arrays.equals(byteIp, IPv4.toIPv4AddressBytes(intIp)));
+ assertTrue(Arrays.equals(byteIp, IPv4.toIPv4AddressBytes(stringIp)));
+ }
+
+ @Test
+ public void testToIPv4AddressBytes() {
+ byte[] expected = new byte[] {(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff};
+ Assert.assertArrayEquals(expected, IPv4.toIPv4AddressBytes("255.255.255.255"));
+ expected = new byte[] {(byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80};
+ Assert.assertArrayEquals(expected, IPv4.toIPv4AddressBytes("128.128.128.128"));
+ expected = new byte[] {0x7f,0x7f,0x7f,0x7f};
+ Assert.assertArrayEquals(expected, IPv4.toIPv4AddressBytes("127.127.127.127"));
+ }
+
+ @Test
+ public void testSerialize() {
+ byte[] expected = new byte[] { 0x45, 0x00, 0x00, 0x14, 0x5e, 0x4e,
+ 0x00, 0x00, 0x3f, 0x06, 0x31, 0x2e, (byte) 0xac, 0x18,
+ 0x4a, (byte) 0xdf, (byte) 0xab, 0x40, 0x4a, 0x30 };
+ IPv4 packet = new IPv4()
+ .setIdentification((short) 24142)
+ .setTtl((byte) 63)
+ .setProtocol((byte) 0x06)
+ .setSourceAddress("172.24.74.223")
+ .setDestinationAddress("171.64.74.48");
+ byte[] actual = packet.serialize();
+ assertTrue(Arrays.equals(expected, actual));
+ }
+
+ @Test
+ public void testDeserialize() {
+ // A real TLSv1 packet
+ byte[] pktSerialized = new byte[] { 0x45, 0x00,
+ 0x00, 0x2e, 0x41, (byte) 0xbe, 0x40, 0x00, 0x40, 0x06,
+ (byte) 0xd4, (byte) 0xf0, (byte) 0xc0, (byte) 0xa8, 0x02, (byte) 0xdb, (byte) 0xd0, 0x55,
+ (byte) 0x90, 0x42, (byte) 0xd5, 0x48, 0x01, (byte) 0xbb, (byte) 0xe3, 0x50,
+ (byte) 0xb2, 0x2f, (byte) 0xfc, (byte) 0xf8, (byte) 0xa8, 0x2c, 0x50, 0x18,
+ (byte) 0xff, (byte) 0xff, 0x24, 0x3c, 0x00, 0x00, 0x14, 0x03,
+ 0x01, 0x00, 0x01, 0x01
+ };
+ IPv4 packet = new IPv4();
+ packet.deserialize(pktSerialized, 0, pktSerialized.length);
+ byte[] pktSerialized1 = packet.serialize();
+ assertTrue(Arrays.equals(pktSerialized, pktSerialized1));
+ }
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/packet/LLDPOrganizationalTLVTest.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/packet/LLDPOrganizationalTLVTest.java
new file mode 100644
index 0000000..88fb26f
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/packet/LLDPOrganizationalTLVTest.java
@@ -0,0 +1,95 @@
+/**
+ * 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.packet;
+
+import org.junit.Test;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+public class LLDPOrganizationalTLVTest {
+ private final byte[] expected = new byte[] {
+ // Type: 127, Length: 13
+ (byte) 254, 13,
+ // OpenFlow OUI: 00-26-E1
+ 0x0, 0x26, (byte)0xe1,
+ // SubType: 12
+ 0xc,
+ // Bytes in "ExtraInfo"
+ 0x45, 0x78, 0x74, 0x72, 0x61, 0x49, 0x6e, 0x66, 0x6f
+ };
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testShortOUI() {
+ LLDPOrganizationalTLV tlv = new LLDPOrganizationalTLV();
+ tlv.setOUI(new byte[2]);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testLongOUI() {
+ LLDPOrganizationalTLV tlv = new LLDPOrganizationalTLV();
+ tlv.setOUI(new byte[4]);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testLongInfoString() {
+ LLDPOrganizationalTLV tlv = new LLDPOrganizationalTLV();
+ tlv.setInfoString(new byte[LLDPOrganizationalTLV.MAX_INFOSTRING_LENGTH + 1]);
+ }
+
+ @Test
+ public void testMaxInfoString() {
+ LLDPOrganizationalTLV tlv = new LLDPOrganizationalTLV();
+ tlv.setInfoString(new byte[LLDPOrganizationalTLV.MAX_INFOSTRING_LENGTH]);
+ }
+
+ @Test
+ public void testInfoString() {
+ LLDPOrganizationalTLV tlv = new LLDPOrganizationalTLV();
+ tlv.setInfoString("ExtraInfo");
+ assertThat(tlv.getInfoString(), is("ExtraInfo".getBytes(Charset.forName("UTF-8"))));
+ }
+
+ @Test
+ public void testSerialize() {
+ LLDPOrganizationalTLV tlv = new LLDPOrganizationalTLV();
+ tlv.setLength((short) 13);
+ // OpenFlow OUI is 00-26-E1
+ tlv.setOUI(new byte[] {0x0, 0x26, (byte) 0xe1});
+ tlv.setSubType((byte) 12);
+ tlv.setInfoString("ExtraInfo".getBytes(Charset.forName("UTF-8")));
+
+ assertThat(tlv.getType(), is((byte)127));
+ assertThat(tlv.getLength(), is((short)13));
+ assertThat(tlv.getOUI(), is(new byte[] {0x0, 0x26, (byte) 0xe1}));
+ assertThat(tlv.getSubType(), is((byte)12));
+ assertThat(tlv.serialize(), is(expected));
+ }
+
+ @Test
+ public void testDeserialize() {
+ LLDPOrganizationalTLV tlv = new LLDPOrganizationalTLV();
+ tlv.deserialize(ByteBuffer.wrap(expected));
+
+ assertThat(tlv.getType(), is((byte)127));
+ assertThat(tlv.getLength(), is((short)13));
+ assertThat(tlv.getOUI(), is(new byte[] {0x0, 0x26, (byte) 0xe1}));
+ assertThat(tlv.getSubType(), is((byte)12));
+ assertThat(tlv.getInfoString(), is("ExtraInfo".getBytes(Charset.forName("UTF-8"))));
+ }
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/packet/LLDPTest.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/packet/LLDPTest.java
new file mode 100755
index 0000000..930c66a
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/packet/LLDPTest.java
@@ -0,0 +1,71 @@
+/**
+* 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.packet;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Arrays;
+
+import org.junit.Test;
+
+/**
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ *
+ */
+public class LLDPTest {
+ protected byte[] pkt = {0x01,0x23,0x20,0x00,0x00,0x01,0x00,0x12,(byte) 0xe2,0x78,0x67,0x78,(byte) 0x88,(byte) 0xcc,0x02,0x07,
+ 0x04,0x00,0x12,(byte) 0xe2,0x78,0x67,0x64,0x04,0x03,0x02,0x00,0x06,0x06,0x02,0x00,0x78,
+ 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};
+
+ protected IPacket getPacket() {
+ return new Ethernet()
+ .setPad(true)
+ .setDestinationMACAddress("01:23:20:00:00:01")
+ .setSourceMACAddress("00:12:e2:78:67:78")
+ .setEtherType(Ethernet.TYPE_LLDP)
+ .setPayload(
+ new LLDP()
+ .setChassisId(new LLDPTLV().setType((byte) 1).setLength((short) 7).setValue(new byte[] {0x04, 0x00, 0x12, (byte) 0xe2, 0x78, 0x67, 0x64}))
+ .setPortId(new LLDPTLV().setType((byte) 2).setLength((short) 3).setValue(new byte[] {0x02, 0x00, 0x06}))
+ .setTtl(new LLDPTLV().setType((byte) 3).setLength((short) 2).setValue(new byte[] {0x00, 0x78}))
+
+ );
+ }
+
+ @Test
+ public void testSerialize() throws Exception {
+ IPacket ethernet = getPacket();
+ assertTrue(Arrays.equals(pkt, ethernet.serialize()));
+ }
+
+ @Test
+ public void testDeserialize() throws Exception {
+ Ethernet ethernet = (Ethernet) new Ethernet().deserialize(pkt, 0, pkt.length);
+ ethernet.setPad(true);
+ assertTrue(Arrays.equals(pkt, ethernet.serialize()));
+
+ IPacket expected = getPacket();
+ assertEquals(expected, ethernet);
+ }
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/packet/PacketTest.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/packet/PacketTest.java
new file mode 100644
index 0000000..67bed71
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/packet/PacketTest.java
@@ -0,0 +1,126 @@
+package net.floodlightcontroller.packet;
+
+import static org.junit.Assert.*;
+import org.junit.Before;
+import org.junit.Test;
+
+public class PacketTest {
+ protected IPacket pkt1, pkt2, pkt3, pkt4;
+ protected IPacket dummyPkt;
+ protected IPacket[] packets;
+
+ @Before
+ public void setUp() {
+ this.pkt1 = new Ethernet()
+ .setDestinationMACAddress("00:11:22:33:44:55")
+ .setSourceMACAddress("00:44:33:22:11:00")
+ .setEtherType(Ethernet.TYPE_IPv4)
+ .setPayload(
+ new IPv4()
+ .setTtl((byte) 128)
+ .setSourceAddress("192.168.1.1")
+ .setDestinationAddress("192.168.1.2")
+ .setPayload(new UDP()
+ .setSourcePort((short) 5000)
+ .setDestinationPort((short) 5001)
+ .setPayload(new Data(new byte[] {0x01}))));
+
+ this.pkt2 = new Ethernet()
+ .setSourceMACAddress("00:44:33:22:11:01")
+ .setDestinationMACAddress("00:11:22:33:44:55")
+ .setEtherType(Ethernet.TYPE_ARP)
+ .setVlanID((short)5)
+ .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:01"))
+ .setSenderProtocolAddress(IPv4.toIPv4AddressBytes("192.168.1.1"))
+ .setTargetHardwareAddress(Ethernet.toMACAddress("00:11:22:33:44:55"))
+ .setTargetProtocolAddress(IPv4.toIPv4AddressBytes("192.168.1.2")));
+
+
+ this.pkt3 = new Ethernet()
+ .setSourceMACAddress("00:44:33:22:11:01")
+ .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:01"))
+ .setSenderProtocolAddress(IPv4.toIPv4AddressBytes("192.168.1.1"))
+ .setTargetHardwareAddress(Ethernet.toMACAddress("00:11:22:33:44:55"))
+ .setTargetProtocolAddress(IPv4.toIPv4AddressBytes("192.168.1.2")));
+
+ this.pkt4 = new Ethernet()
+ .setDestinationMACAddress("FF:FF:FF:FF:FF:FF")
+ .setSourceMACAddress("00:11:33:55:77:01")
+ .setEtherType(Ethernet.TYPE_IPv4)
+ .setPayload(
+ new IPv4()
+ .setTtl((byte) 128)
+ .setSourceAddress("192.168.10.1")
+ .setDestinationAddress("192.168.255.255")
+ .setPayload(new UDP()
+ .setSourcePort((short) 5000)
+ .setDestinationPort((short) 5001)
+ .setPayload(new Data(new byte[] {0x01}))));
+
+ this.dummyPkt = new IPv4()
+ .setTtl((byte) 32)
+ .setSourceAddress("1.2.3.4")
+ .setDestinationAddress("5.6.7.8");
+
+ this.packets = new IPacket[] { pkt1, pkt2, pkt3, pkt4 };
+ }
+
+ protected void doTestClone(IPacket pkt) {
+ if (pkt.getPayload() != null)
+ doTestClone(pkt.getPayload());
+ IPacket newPkt = (IPacket)pkt.clone();
+ assertSame(pkt.getClass(), newPkt.getClass());
+ assertNotSame(pkt, newPkt);
+ assertSame(pkt.getParent(), newPkt.getParent());
+ assertEquals(pkt, newPkt);
+ assertEquals(pkt.getPayload(), newPkt.getPayload());
+ if (pkt.getPayload() != null)
+ assertNotSame(pkt.getPayload(), newPkt.getPayload());
+
+ if (pkt instanceof Ethernet) {
+ Ethernet eth = (Ethernet)pkt;
+ Ethernet newEth = (Ethernet)newPkt;
+ newEth.setDestinationMACAddress(new byte[] { 1,2,3,4,5,6});
+ assertEquals(false, newEth.getDestinationMAC()
+ .equals(eth.getDestinationMAC()));
+ assertEquals(false, newPkt.equals(pkt));
+ }
+ if (pkt instanceof ARP) {
+ ARP arp = (ARP)pkt;
+ ARP newArp = (ARP)newPkt;
+ newArp.setSenderProtocolAddress(new byte[] {1,2,3,4});
+ assertEquals(false, newArp.getSenderProtocolAddress()
+ .equals(arp.getSenderProtocolAddress()));
+ assertEquals(false, newPkt.equals(pkt));
+ }
+
+ byte[] dummyData = dummyPkt.serialize().clone();
+ newPkt = (IPacket)pkt.clone();
+ newPkt.deserialize(dummyData, 0, dummyData.length);
+ assertEquals(false, newPkt.equals(pkt));
+ }
+
+ @Test
+ public void testClone() {
+ for (IPacket pkt: packets) {
+ doTestClone(pkt);
+ }
+ }
+
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/packet/TCPTest.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/packet/TCPTest.java
new file mode 100644
index 0000000..fcd5422
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/packet/TCPTest.java
@@ -0,0 +1,74 @@
+/**
+* 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.packet;
+
+import static org.junit.Assert.assertTrue;
+
+import java.util.Arrays;
+
+import org.junit.Test;
+
+/**
+ * @author shudong.zhou@bigswitch.com
+ */
+public class TCPTest {
+ private byte[] pktSerialized = new byte[] { 0x45, 0x20,
+ 0x00, 0x34, 0x1d, (byte) 0x85, 0x00, 0x00, 0x32, 0x06,
+ 0x31, 0x1e, 0x4a, 0x7d, 0x2d, 0x6d, (byte) 0xc0, (byte) 0xa8,
+ 0x01, 0x6f, 0x03, (byte) 0xe1, (byte) 0xc0, 0x32, (byte) 0xe3, (byte) 0xad,
+ (byte) 0xee, (byte) 0x88, (byte) 0xb7, (byte) 0xda, (byte) 0xd8, 0x24, (byte) 0x80, 0x10,
+ 0x01, 0x0b, 0x59, 0x33, 0x00, 0x00, 0x01, 0x01,
+ 0x08, 0x0a, 0x20, (byte) 0x9a, 0x41, 0x04, 0x07, 0x76,
+ 0x53, 0x1f};
+
+ @Test
+ public void testSerialize() {
+ IPacket packet = new IPv4()
+ .setDiffServ((byte) 0x20)
+ .setIdentification((short) 0x1d85)
+ .setFlags((byte) 0x00)
+ .setTtl((byte) 50)
+ .setSourceAddress("74.125.45.109")
+ .setDestinationAddress("192.168.1.111")
+ .setPayload(new TCP()
+ .setSourcePort((short) 993)
+ .setDestinationPort((short) 49202)
+ .setSequence(0xe3adee88)
+ .setAcknowledge(0xb7dad824)
+ .setDataOffset((byte) 8)
+ .setFlags((short) 0x10)
+ .setWindowSize((short) 267)
+ .setOptions(new byte[] {0x01, 0x01, 0x08, 0x0a, 0x20, (byte) 0x9a,
+ 0x41, 0x04, 0x07, 0x76, 0x53, 0x1f})
+ .setPayload(null)
+ );
+ byte[] actual = packet.serialize();
+ assertTrue(Arrays.equals(pktSerialized, actual));
+ }
+
+ @Test
+ public void testDeserialize() {
+ IPacket packet = new IPv4();
+ packet.deserialize(pktSerialized, 0, pktSerialized.length);
+ byte[] pktSerialized1 = packet.serialize();
+ assertTrue(Arrays.equals(pktSerialized, pktSerialized1));
+ }
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/packet/UDPTest.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/packet/UDPTest.java
new file mode 100644
index 0000000..bed42a2
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/packet/UDPTest.java
@@ -0,0 +1,55 @@
+/**
+* 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.packet;
+
+import static org.junit.Assert.assertTrue;
+
+import java.util.Arrays;
+
+import org.junit.Test;
+
+/**
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ *
+ */
+public class UDPTest {
+
+ @Test
+ public void testSerialize() {
+ byte[] expected = new byte[] { 0x45, 0x00, 0x00, 0x1d, 0x56, 0x23,
+ 0x00, 0x00, (byte) 0x80, 0x11, 0x48, 0x7f, (byte) 0xc0,
+ (byte) 0xa8, 0x01, 0x02, 0x0c, (byte) 0x81, (byte) 0xce, 0x02,
+ 0x17, (byte) 0xe1, 0x04, 0x5f, 0x00, 0x09, 0x46, 0x6e,
+ 0x01 };
+ IPacket packet = new IPv4()
+ .setIdentification((short) 22051)
+ .setTtl((byte) 128)
+ .setSourceAddress("192.168.1.2")
+ .setDestinationAddress("12.129.206.2")
+ .setPayload(new UDP()
+ .setSourcePort((short) 6113)
+ .setDestinationPort((short) 1119)
+ .setPayload(new Data(new byte[] {0x01}))
+ );
+ byte[] actual = packet.serialize();
+ assertTrue(Arrays.equals(expected, actual));
+ }
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/routing/RouteTest.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/routing/RouteTest.java
new file mode 100644
index 0000000..3bd0398
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/routing/RouteTest.java
@@ -0,0 +1,63 @@
+/**
+* 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.routing;
+
+import org.junit.Test;
+
+import net.floodlightcontroller.routing.Route;
+import net.floodlightcontroller.test.FloodlightTestCase;
+import net.floodlightcontroller.topology.NodePortTuple;
+
+/**
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public class RouteTest extends FloodlightTestCase {
+ @Test
+ public void testCloneable() throws Exception {
+ Route r1 = new Route(1L, 2L);
+ Route r2 = new Route(1L, 3L);
+
+ assertNotSame(r1, r2);
+ assertNotSame(r1.getId(), r2.getId());
+
+ r1 = new Route(1L, 3L);
+ r1.getPath().add(new NodePortTuple(1L, (short)1));
+ r1.getPath().add(new NodePortTuple(2L, (short)1));
+ r1.getPath().add(new NodePortTuple(2L, (short)2));
+ r1.getPath().add(new NodePortTuple(3L, (short)1));
+
+ r2.getPath().add(new NodePortTuple(1L, (short)1));
+ r2.getPath().add(new NodePortTuple(2L, (short)1));
+ r2.getPath().add(new NodePortTuple(2L, (short)2));
+ r2.getPath().add(new NodePortTuple(3L, (short)1));
+
+ assertEquals(r1, r2);
+
+ NodePortTuple temp = r2.getPath().remove(0);
+ assertNotSame(r1, r2);
+
+ r2.getPath().add(0, temp);
+ assertEquals(r1, r2);
+
+ r2.getPath().remove(1);
+ temp = new NodePortTuple(2L, (short)5);
+ r2.getPath().add(1, temp);
+ assertNotSame(r1, r2);
+ }
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/staticflowentry/StaticFlowTests.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/staticflowentry/StaticFlowTests.java
new file mode 100644
index 0000000..186fd69
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/staticflowentry/StaticFlowTests.java
@@ -0,0 +1,334 @@
+package net.floodlightcontroller.staticflowentry;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+
+import org.easymock.Capture;
+import org.easymock.CaptureType;
+import org.junit.Test;
+import org.openflow.protocol.OFFlowMod;
+import org.openflow.protocol.OFMatch;
+import org.openflow.protocol.OFMessage;
+import org.openflow.protocol.OFPort;
+import org.openflow.protocol.action.OFAction;
+import org.openflow.protocol.action.OFActionOutput;
+import org.openflow.util.HexString;
+
+
+import net.floodlightcontroller.core.FloodlightContext;
+import net.floodlightcontroller.core.IFloodlightProviderService.Role;
+import net.floodlightcontroller.core.IOFSwitch;
+import net.floodlightcontroller.core.module.FloodlightModuleContext;
+import net.floodlightcontroller.core.module.FloodlightModuleException;
+import net.floodlightcontroller.core.test.MockFloodlightProvider;
+import net.floodlightcontroller.test.FloodlightTestCase;
+import net.floodlightcontroller.restserver.RestApiServer;
+import net.floodlightcontroller.staticflowentry.StaticFlowEntryPusher;
+import net.floodlightcontroller.storage.IStorageSourceService;
+import net.floodlightcontroller.storage.memory.MemoryStorageSource;
+import static net.floodlightcontroller.staticflowentry.StaticFlowEntryPusher.*;
+import static org.easymock.EasyMock.*;
+
+public class StaticFlowTests extends FloodlightTestCase {
+
+ static String TestSwitch1DPID = "00:00:00:00:00:00:00:01";
+ static int TotalTestRules = 3;
+
+ /***
+ * Create TestRuleXXX and the corresponding FlowModXXX
+ * for X = 1..3
+ */
+ static Map<String,Object> TestRule1;
+ static OFFlowMod FlowMod1;
+ static {
+ FlowMod1 = new OFFlowMod();
+ TestRule1 = new HashMap<String,Object>();
+ TestRule1.put(COLUMN_NAME, "TestRule1");
+ TestRule1.put(COLUMN_SWITCH, TestSwitch1DPID);
+ // setup match
+ OFMatch match = new OFMatch();
+ TestRule1.put(COLUMN_DL_DST, "00:20:30:40:50:60");
+ match.fromString("dl_dst=00:20:30:40:50:60");
+ // setup actions
+ List<OFAction> actions = new LinkedList<OFAction>();
+ TestRule1.put(COLUMN_ACTIONS, "output=1");
+ actions.add(new OFActionOutput((short)1, (short) Short.MAX_VALUE));
+ // done
+ FlowMod1.setMatch(match);
+ FlowMod1.setActions(actions);
+ FlowMod1.setBufferId(-1);
+ FlowMod1.setOutPort(OFPort.OFPP_NONE.getValue());
+ FlowMod1.setPriority(Short.MAX_VALUE);
+ FlowMod1.setLengthU(OFFlowMod.MINIMUM_LENGTH + 8); // 8 bytes of actions
+ }
+
+ static Map<String,Object> TestRule2;
+ static OFFlowMod FlowMod2;
+
+ static {
+ FlowMod2 = new OFFlowMod();
+ TestRule2 = new HashMap<String,Object>();
+ TestRule2.put(COLUMN_NAME, "TestRule2");
+ TestRule2.put(COLUMN_SWITCH, TestSwitch1DPID);
+ // setup match
+ OFMatch match = new OFMatch();
+ TestRule2.put(COLUMN_NW_DST, "192.168.1.0/24");
+ match.fromString("nw_dst=192.168.1.0/24");
+ // setup actions
+ List<OFAction> actions = new LinkedList<OFAction>();
+ TestRule2.put(COLUMN_ACTIONS, "output=1");
+ actions.add(new OFActionOutput((short)1, (short) Short.MAX_VALUE));
+ // done
+ FlowMod2.setMatch(match);
+ FlowMod2.setActions(actions);
+ FlowMod2.setBufferId(-1);
+ FlowMod2.setOutPort(OFPort.OFPP_NONE.getValue());
+ FlowMod2.setPriority(Short.MAX_VALUE);
+ FlowMod2.setLengthU(OFFlowMod.MINIMUM_LENGTH + 8); // 8 bytes of actions
+
+ }
+
+
+ static Map<String,Object> TestRule3;
+ static OFFlowMod FlowMod3;
+ static {
+ FlowMod3 = new OFFlowMod();
+ TestRule3 = new HashMap<String,Object>();
+ TestRule3.put(COLUMN_NAME, "TestRule3");
+ TestRule3.put(COLUMN_SWITCH, TestSwitch1DPID);
+ // setup match
+ OFMatch match = new OFMatch();
+ TestRule3.put(COLUMN_DL_DST, "00:20:30:40:50:60");
+ TestRule3.put(COLUMN_DL_VLAN, 4096);
+ match.fromString("dl_dst=00:20:30:40:50:60,dl_vlan=4096");
+ // setup actions
+ TestRule3.put(COLUMN_ACTIONS, "output=controller");
+ List<OFAction> actions = new LinkedList<OFAction>();
+ actions.add(new OFActionOutput(OFPort.OFPP_CONTROLLER.getValue(), (short) Short.MAX_VALUE));
+ // done
+ FlowMod3.setMatch(match);
+ FlowMod3.setActions(actions);
+ FlowMod3.setBufferId(-1);
+ FlowMod3.setOutPort(OFPort.OFPP_NONE.getValue());
+ FlowMod3.setPriority(Short.MAX_VALUE);
+ FlowMod3.setLengthU(OFFlowMod.MINIMUM_LENGTH + 8); // 8 bytes of actions
+
+ }
+
+ private void verifyFlowMod(OFFlowMod testFlowMod,
+ OFFlowMod goodFlowMod) {
+ verifyMatch(testFlowMod, goodFlowMod);
+ verifyActions(testFlowMod, goodFlowMod);
+ // dont' bother testing the cookie; just copy it over
+ goodFlowMod.setCookie(testFlowMod.getCookie());
+ // .. so we can continue to use .equals()
+ assertEquals(goodFlowMod, testFlowMod);
+ }
+
+
+ private void verifyMatch(OFFlowMod testFlowMod, OFFlowMod goodFlowMod) {
+ assertEquals(goodFlowMod.getMatch(), testFlowMod.getMatch());
+ }
+
+
+ private void verifyActions(OFFlowMod testFlowMod, OFFlowMod goodFlowMod) {
+ List<OFAction> goodActions = goodFlowMod.getActions();
+ List<OFAction> testActions = testFlowMod.getActions();
+ assertNotNull(goodActions);
+ assertNotNull(testActions);
+ assertEquals(goodActions.size(), testActions.size());
+ // assumes actions are marshalled in same order; should be safe
+ for(int i = 0; i < goodActions.size(); i++) {
+ assertEquals(goodActions.get(i), testActions.get(i));
+ }
+
+ }
+
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ }
+
+ @Test
+ public void testStaticFlowPush() throws IOException {
+ StaticFlowEntryPusher staticFlowEntryPusher = new StaticFlowEntryPusher();
+ IStorageSourceService storage = createStorageWithFlowEntries();
+ long dpid = HexString.toLong(TestSwitch1DPID);
+
+ // Create a Switch and attach a switch
+ IOFSwitch mockSwitch = createNiceMock(IOFSwitch.class);
+ Capture<OFMessage> writeCapture = new Capture<OFMessage>(CaptureType.ALL);
+ Capture<FloodlightContext> contextCapture = new Capture<FloodlightContext>(CaptureType.ALL);
+ Capture<List<OFMessage>> writeCaptureList = new Capture<List<OFMessage>>(CaptureType.ALL);
+
+ //OFMessageSafeOutStream mockOutStream = createNiceMock(OFMessageSafeOutStream.class);
+ mockSwitch.write(capture(writeCapture), capture(contextCapture));
+ expectLastCall().anyTimes();
+ mockSwitch.write(capture(writeCaptureList), capture(contextCapture));
+ expectLastCall().anyTimes();
+ mockSwitch.flush();
+ expectLastCall().anyTimes();
+
+ staticFlowEntryPusher.setStorageSource(storage);
+
+ FloodlightModuleContext fmc = new FloodlightModuleContext();
+
+ MockFloodlightProvider mockFloodlightProvider = getMockFloodlightProvider();
+ Map<Long, IOFSwitch> switchMap = new HashMap<Long, IOFSwitch>();
+ switchMap.put(dpid, mockSwitch);
+ // NO ! expect(mockFloodlightProvider.getSwitches()).andReturn(switchMap).anyTimes();
+ mockFloodlightProvider.setSwitches(switchMap);
+ staticFlowEntryPusher.setFloodlightProvider(mockFloodlightProvider);
+ RestApiServer restApi = new RestApiServer();
+ try {
+ restApi.init(fmc);
+ } catch (FloodlightModuleException e) {
+ e.printStackTrace();
+ }
+ staticFlowEntryPusher.restApi = restApi;
+ staticFlowEntryPusher.startUp(null); // again, to hack unittest
+
+ // verify that flowpusher read all three entries from storage
+ assertEquals(TotalTestRules, staticFlowEntryPusher.countEntries());
+
+ // if someone calls mockSwitch.getOutputStream(), return mockOutStream instead
+ //expect(mockSwitch.getOutputStream()).andReturn(mockOutStream).anyTimes();
+
+ // if someone calls getId(), return this dpid instead
+ expect(mockSwitch.getId()).andReturn(dpid).anyTimes();
+ expect(mockSwitch.getStringId()).andReturn(TestSwitch1DPID).anyTimes();
+ replay(mockSwitch);
+
+ // hook the static pusher up to the fake switch
+ staticFlowEntryPusher.addedSwitch(mockSwitch);
+
+ verify(mockSwitch);
+
+ // Verify that the switch has gotten some flow_mods
+ assertEquals(true, writeCapture.hasCaptured());
+ assertEquals(TotalTestRules, writeCapture.getValues().size());
+
+ // Order assumes how things are stored in hash bucket;
+ // should be fixed because OFMessage.hashCode() is deterministic
+ OFFlowMod firstFlowMod = (OFFlowMod) writeCapture.getValues().get(2);
+ verifyFlowMod(firstFlowMod, FlowMod1);
+ OFFlowMod secondFlowMod = (OFFlowMod) writeCapture.getValues().get(1);
+ verifyFlowMod(secondFlowMod, FlowMod2);
+ OFFlowMod thirdFlowMod = (OFFlowMod) writeCapture.getValues().get(0);
+ verifyFlowMod(thirdFlowMod, FlowMod3);
+
+ writeCapture.reset();
+ contextCapture.reset();
+
+
+ // delete two rules and verify they've been removed
+ // this should invoke staticFlowPusher.rowsDeleted()
+ storage.deleteRow(StaticFlowEntryPusher.TABLE_NAME, "TestRule1");
+ storage.deleteRow(StaticFlowEntryPusher.TABLE_NAME, "TestRule2");
+
+ assertEquals(1, staticFlowEntryPusher.countEntries());
+ assertEquals(2, writeCapture.getValues().size());
+
+ OFFlowMod firstDelete = (OFFlowMod) writeCapture.getValues().get(0);
+ FlowMod1.setCommand(OFFlowMod.OFPFC_DELETE_STRICT);
+ verifyFlowMod(firstDelete, FlowMod1);
+
+ OFFlowMod secondDelete = (OFFlowMod) writeCapture.getValues().get(1);
+ FlowMod2.setCommand(OFFlowMod.OFPFC_DELETE_STRICT);
+ verifyFlowMod(secondDelete, FlowMod2);
+
+ // add rules back to make sure that staticFlowPusher.rowsInserted() works
+ writeCapture.reset();
+ FlowMod2.setCommand(OFFlowMod.OFPFC_ADD);
+ storage.insertRow(StaticFlowEntryPusher.TABLE_NAME, TestRule2);
+ assertEquals(2, staticFlowEntryPusher.countEntries());
+ assertEquals(1, writeCaptureList.getValues().size());
+ List<OFMessage> outList =
+ (List<OFMessage>) writeCaptureList.getValues().get(0);
+ assertEquals(1, outList.size());
+ OFFlowMod firstAdd = (OFFlowMod) outList.get(0);
+ verifyFlowMod(firstAdd, FlowMod2);
+ writeCapture.reset();
+ contextCapture.reset();
+ writeCaptureList.reset();
+
+ // now try an update, calling staticFlowPusher.rowUpdated()
+ TestRule3.put(COLUMN_DL_VLAN, 333);
+ storage.updateRow(StaticFlowEntryPusher.TABLE_NAME, TestRule3);
+ assertEquals(2, staticFlowEntryPusher.countEntries());
+ assertEquals(1, writeCaptureList.getValues().size());
+
+ outList = (List<OFMessage>) writeCaptureList.getValues().get(0);
+ assertEquals(2, outList.size());
+ OFFlowMod removeFlowMod = (OFFlowMod) outList.get(0);
+ FlowMod3.setCommand(OFFlowMod.OFPFC_DELETE_STRICT);
+ verifyFlowMod(removeFlowMod, FlowMod3);
+ FlowMod3.setCommand(OFFlowMod.OFPFC_ADD);
+ FlowMod3.getMatch().fromString("dl_dst=00:20:30:40:50:60,dl_vlan=333");
+ OFFlowMod updateFlowMod = (OFFlowMod) outList.get(1);
+ verifyFlowMod(updateFlowMod, FlowMod3);
+
+ }
+
+
+ IStorageSourceService createStorageWithFlowEntries() {
+ return populateStorageWithFlowEntries(new MemoryStorageSource());
+ }
+
+ IStorageSourceService populateStorageWithFlowEntries(IStorageSourceService storage) {
+ Set<String> indexedColumns = new HashSet<String>();
+ indexedColumns.add(COLUMN_NAME);
+ storage.createTable(StaticFlowEntryPusher.TABLE_NAME, indexedColumns);
+ storage.setTablePrimaryKeyName(StaticFlowEntryPusher.TABLE_NAME, COLUMN_NAME);
+
+ storage.insertRow(StaticFlowEntryPusher.TABLE_NAME, TestRule1);
+ storage.insertRow(StaticFlowEntryPusher.TABLE_NAME, TestRule2);
+ storage.insertRow(StaticFlowEntryPusher.TABLE_NAME, TestRule3);
+
+ return storage;
+ }
+
+ @Test
+ public void testHARoleChanged() throws IOException {
+ StaticFlowEntryPusher staticFlowEntryPusher = new StaticFlowEntryPusher();
+ IStorageSourceService storage = createStorageWithFlowEntries();
+ MockFloodlightProvider mfp = getMockFloodlightProvider();
+ staticFlowEntryPusher.setFloodlightProvider(mfp);
+ staticFlowEntryPusher.setStorageSource(storage);
+ RestApiServer restApi = new RestApiServer();
+ try {
+ FloodlightModuleContext fmc = new FloodlightModuleContext();
+ restApi.init(fmc);
+ } catch (FloodlightModuleException e) {
+ e.printStackTrace();
+ }
+ staticFlowEntryPusher.restApi = restApi;
+ staticFlowEntryPusher.startUp(null); // again, to hack unittest
+
+ assert(staticFlowEntryPusher.entry2dpid.containsValue(TestSwitch1DPID));
+ assert(staticFlowEntryPusher.entriesFromStorage.containsValue(FlowMod1));
+ assert(staticFlowEntryPusher.entriesFromStorage.containsValue(FlowMod2));
+ assert(staticFlowEntryPusher.entriesFromStorage.containsValue(FlowMod3));
+
+ // Send a notification that we've changed to slave
+ mfp.dispatchRoleChanged(null, Role.SLAVE);
+ // Make sure we've removed all our entries
+ assert(staticFlowEntryPusher.entry2dpid.isEmpty());
+ assert(staticFlowEntryPusher.entriesFromStorage.isEmpty());
+
+ // Send a notification that we've changed to master
+ mfp.dispatchRoleChanged(Role.SLAVE, Role.MASTER);
+ // Make sure we've learned the entries
+ assert(staticFlowEntryPusher.entry2dpid.containsValue(TestSwitch1DPID));
+ assert(staticFlowEntryPusher.entriesFromStorage.containsValue(FlowMod1));
+ assert(staticFlowEntryPusher.entriesFromStorage.containsValue(FlowMod2));
+ assert(staticFlowEntryPusher.entriesFromStorage.containsValue(FlowMod3));
+ }
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/storage/memory/tests/MemoryStorageTest.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/storage/memory/tests/MemoryStorageTest.java
new file mode 100644
index 0000000..c250066
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/storage/memory/tests/MemoryStorageTest.java
@@ -0,0 +1,41 @@
+/**
+* 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.storage.memory.tests;
+
+import net.floodlightcontroller.core.module.FloodlightModuleContext;
+import net.floodlightcontroller.restserver.IRestApiService;
+import net.floodlightcontroller.restserver.RestApiServer;
+import net.floodlightcontroller.storage.memory.MemoryStorageSource;
+import net.floodlightcontroller.storage.tests.StorageTest;
+import org.junit.Before;
+
+public class MemoryStorageTest extends StorageTest {
+
+ @Before
+ public void setUp() throws Exception {
+ storageSource = new MemoryStorageSource();
+ restApi = new RestApiServer();
+ FloodlightModuleContext fmc = new FloodlightModuleContext();
+ fmc.addService(IRestApiService.class, restApi);
+ restApi.init(fmc);
+ storageSource.init(fmc);
+ restApi.startUp(fmc);
+ storageSource.startUp(fmc);
+ super.setUp();
+ }
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/storage/tests/StorageTest.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/storage/tests/StorageTest.java
new file mode 100644
index 0000000..29cc15b
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/storage/tests/StorageTest.java
@@ -0,0 +1,743 @@
+/**
+* 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.storage.tests;
+
+import static org.easymock.EasyMock.*;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.TimeUnit;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import net.floodlightcontroller.restserver.RestApiServer;
+import net.floodlightcontroller.storage.CompoundPredicate;
+import net.floodlightcontroller.storage.IStorageExceptionHandler;
+import net.floodlightcontroller.storage.IPredicate;
+import net.floodlightcontroller.storage.IQuery;
+import net.floodlightcontroller.storage.IResultSet;
+import net.floodlightcontroller.storage.IRowMapper;
+import net.floodlightcontroller.storage.IStorageSourceListener;
+import net.floodlightcontroller.storage.NullValueStorageException;
+import net.floodlightcontroller.storage.OperatorPredicate;
+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 {
+
+ protected NoSqlStorageSource storageSource;
+ protected RestApiServer restApi;
+
+ protected String PERSON_TABLE_NAME = "Person";
+
+ protected String PERSON_SSN = "SSN";
+ protected String PERSON_FIRST_NAME = "FirstName";
+ protected String PERSON_LAST_NAME = "LastName";
+ protected String PERSON_AGE = "Age";
+ protected String PERSON_REGISTERED = "Registered";
+
+ protected String[] PERSON_COLUMN_LIST = {PERSON_SSN, PERSON_FIRST_NAME, PERSON_LAST_NAME, PERSON_AGE, PERSON_REGISTERED};
+
+ class Person {
+ private String ssn;
+ private String firstName;
+ private String lastName;
+ int age;
+ boolean registered;
+
+ public Person(String ssn, String firstName, String lastName, int age, boolean registered) {
+ this.ssn = ssn;
+ this.firstName = firstName;
+ this.lastName = lastName;
+ this.age = age;
+ this.registered = registered;
+ }
+
+ public String getSSN() {
+ return ssn;
+ }
+
+ public String getFirstName() {
+ return firstName;
+ }
+
+ public String getLastName() {
+ return lastName;
+
+ }
+
+ public int getAge() {
+ return age;
+ }
+
+ public boolean isRegistered() {
+ return registered;
+ }
+ }
+
+ class PersonRowMapper implements IRowMapper {
+ public Object mapRow(IResultSet resultSet) {
+ String ssn = resultSet.getString(PERSON_SSN);
+ String firstName = resultSet.getString(PERSON_FIRST_NAME);
+ String lastName = resultSet.getString(PERSON_LAST_NAME);
+ int age = resultSet.getInt(PERSON_AGE);
+ boolean registered = resultSet.getBoolean(PERSON_REGISTERED);
+ return new Person(ssn, firstName, lastName, age, registered);
+ }
+ }
+
+ Object[][] PERSON_INIT_DATA = {
+ {"111-11-1111", "John", "Smith", 40, true},
+ {"222-22-2222", "Jim", "White", 24, false},
+ {"333-33-3333", "Lisa", "Jones", 27, true},
+ {"444-44-4444", "Susan", "Jones", 14, false},
+ {"555-55-5555", "Jose", "Garcia", 31, true},
+ {"666-66-6666", "Abigail", "Johnson", 35, false},
+ {"777-77-7777", "Bjorn", "Borg", 55, true},
+ {"888-88-8888", "John", "McEnroe", 53, false}
+ };
+
+ Map<String,Object> createPersonRowValues(Object[] personData) {
+ Map<String,Object> rowValues = new HashMap<String,Object>();
+ for (int i = 0; i < PERSON_COLUMN_LIST.length; i++) {
+ rowValues.put(PERSON_COLUMN_LIST[i], personData[i]);
+ }
+ return rowValues;
+ }
+
+ public void insertPerson(Object[] personData) {
+ Map<String,Object> rowValues = createPersonRowValues(personData);
+ storageSource.insertRow(PERSON_TABLE_NAME, rowValues);
+ }
+
+ public void initPersons() {
+ for (Object[] row: PERSON_INIT_DATA) {
+ insertPerson(row);
+ }
+ }
+
+ public void setUp() throws Exception {
+ super.setUp();
+ Set<String> indexedColumnNames = new HashSet<String>();
+ indexedColumnNames.add(PERSON_LAST_NAME);
+ storageSource.setExceptionHandler(null);
+ storageSource.createTable(PERSON_TABLE_NAME, indexedColumnNames);
+ storageSource.setTablePrimaryKeyName(PERSON_TABLE_NAME, PERSON_SSN);
+ initPersons();
+ }
+
+ public void checkExpectedResults(IResultSet resultSet, String[] columnNameList, Object[][] expectedRowList) {
+ boolean nextResult;
+ for (Object[] expectedRow: expectedRowList) {
+ nextResult = resultSet.next();
+ assertEquals(nextResult,true);
+ assertEquals(expectedRow.length, columnNameList.length);
+ for (int i = 0; i < expectedRow.length; i++) {
+ Object expectedObject = expectedRow[i];
+ String columnName = columnNameList[i];
+ if (expectedObject instanceof Boolean)
+ assertEquals(((Boolean)expectedObject).booleanValue(), resultSet.getBoolean(columnName));
+ else if (expectedObject instanceof Byte)
+ assertEquals(((Byte)expectedObject).byteValue(), resultSet.getByte(columnName));
+ else if (expectedObject instanceof Short)
+ assertEquals(((Short)expectedObject).shortValue(), resultSet.getShort(columnName));
+ else if (expectedObject instanceof Integer)
+ assertEquals(((Integer)expectedObject).intValue(), resultSet.getInt(columnName));
+ else if (expectedObject instanceof Long)
+ assertEquals(((Long)expectedObject).longValue(), resultSet.getLong(columnName));
+ else if (expectedObject instanceof Float)
+ assertEquals(((Float)expectedObject).floatValue(), resultSet.getFloat(columnName), 0.00001);
+ else if (expectedObject instanceof Double)
+ assertEquals(((Double)expectedObject).doubleValue(), resultSet.getDouble(columnName), 0.00001);
+ else if (expectedObject instanceof byte[])
+ assertEquals((byte[])expectedObject, resultSet.getByteArray(columnName));
+ else if (expectedObject instanceof String)
+ assertEquals((String)expectedObject, resultSet.getString(columnName));
+ else
+ assertTrue("Unexpected column value type", false);
+ }
+ }
+ nextResult = resultSet.next();
+ assertEquals(nextResult,false);
+ resultSet.close();
+ }
+
+ @Test
+ public void testInsertRows() {
+ IResultSet resultSet = storageSource.executeQuery(PERSON_TABLE_NAME, null, null, new RowOrdering(PERSON_SSN));
+ checkExpectedResults(resultSet, PERSON_COLUMN_LIST, PERSON_INIT_DATA);
+ }
+
+ @Test
+ public void testOperatorQuery() {
+ Object[][] expectedResults = {
+ {"John", "Smith", 40},
+ {"Jim", "White", 24},
+ };
+ String[] columnList = {PERSON_FIRST_NAME,PERSON_LAST_NAME,PERSON_AGE};
+ IResultSet resultSet = storageSource.executeQuery(PERSON_TABLE_NAME, columnList,
+ new OperatorPredicate(PERSON_LAST_NAME, OperatorPredicate.Operator.GTE, "Sm"),
+ new RowOrdering(PERSON_SSN));
+ checkExpectedResults(resultSet, columnList, expectedResults);
+ }
+
+ @Test
+ public void testAndQuery() {
+ String[] columnList = {PERSON_FIRST_NAME,PERSON_LAST_NAME};
+ Object[][] expectedResults = {
+ {"Lisa", "Jones"},
+ {"Susan", "Jones"},
+ {"Jose", "Garcia"},
+ {"Abigail", "Johnson"},
+ {"John", "McEnroe"}
+ };
+ IResultSet resultSet = storageSource.executeQuery(PERSON_TABLE_NAME, columnList,
+ new CompoundPredicate(CompoundPredicate.Operator.AND, false,
+ new OperatorPredicate(PERSON_LAST_NAME, OperatorPredicate.Operator.GTE, "G"),
+ new OperatorPredicate(PERSON_LAST_NAME, OperatorPredicate.Operator.LT, "N")
+ ),
+ new RowOrdering(PERSON_SSN));
+ checkExpectedResults(resultSet, columnList, expectedResults);
+ }
+
+ @Test
+ public void testOrQuery() {
+ String[] columnList = {PERSON_FIRST_NAME,PERSON_LAST_NAME, PERSON_AGE};
+ Object[][] expectedResults = {
+ {"John", "Smith", 40},
+ {"Lisa", "Jones", 27},
+ {"Abigail", "Johnson", 35},
+ {"Bjorn", "Borg", 55},
+ {"John", "McEnroe", 53}
+ };
+ IResultSet resultSet = storageSource.executeQuery(PERSON_TABLE_NAME, columnList,
+ new CompoundPredicate(CompoundPredicate.Operator.OR, false,
+ new OperatorPredicate(PERSON_AGE, OperatorPredicate.Operator.GTE, 35),
+ new OperatorPredicate(PERSON_FIRST_NAME, OperatorPredicate.Operator.EQ, "Lisa")
+ ),
+ new RowOrdering(PERSON_SSN));
+ checkExpectedResults(resultSet, columnList, expectedResults);
+}
+
+ @Test
+ public void testCreateQuery() {
+ String[] columnList = {PERSON_FIRST_NAME,PERSON_LAST_NAME};
+ Object[][] expectedResults = {
+ {"Lisa", "Jones"},
+ {"Susan", "Jones"}
+ };
+ IPredicate predicate = new OperatorPredicate(PERSON_LAST_NAME, OperatorPredicate.Operator.EQ, "Jones");
+ IQuery query = storageSource.createQuery(PERSON_TABLE_NAME, columnList, predicate, new RowOrdering(PERSON_SSN));
+ IResultSet resultSet = storageSource.executeQuery(query);
+ checkExpectedResults(resultSet, columnList, expectedResults);
+ }
+
+ @Test
+ public void testQueryParameters() {
+ String[] columnList = {PERSON_FIRST_NAME,PERSON_LAST_NAME, PERSON_AGE};
+ Object[][] expectedResults = {
+ {"John", "Smith", 40},
+ {"Bjorn", "Borg", 55},
+ {"John", "McEnroe", 53}
+ };
+ IPredicate predicate = new OperatorPredicate(PERSON_AGE, OperatorPredicate.Operator.GTE, "?MinimumAge?");
+ IQuery query = storageSource.createQuery(PERSON_TABLE_NAME, columnList, predicate, new RowOrdering(PERSON_SSN));
+ query.setParameter("MinimumAge", 40);
+ IResultSet resultSet = storageSource.executeQuery(query);
+ checkExpectedResults(resultSet, columnList, expectedResults);
+ }
+
+ private void checkPerson(Person person, Object[] expectedValues) {
+ assertEquals(person.getSSN(), expectedValues[0]);
+ assertEquals(person.getFirstName(), expectedValues[1]);
+ assertEquals(person.getLastName(), expectedValues[2]);
+ assertEquals(person.getAge(), expectedValues[3]);
+ assertEquals(person.isRegistered(), expectedValues[4]);
+ }
+
+ @Test
+ public void testRowMapper() {
+ Object[][] expectedResults = {
+ PERSON_INIT_DATA[2],
+ PERSON_INIT_DATA[3]
+ };
+ IPredicate predicate = new OperatorPredicate(PERSON_LAST_NAME, OperatorPredicate.Operator.EQ, "Jones");
+ IRowMapper rowMapper = new PersonRowMapper();
+ Object[] personList = storageSource.executeQuery(PERSON_TABLE_NAME, null, predicate, new RowOrdering(PERSON_SSN), rowMapper);
+ assertEquals(personList.length, 2);
+ for (int i = 0; i < personList.length; i++)
+ checkPerson((Person)personList[i], expectedResults[i]);
+ }
+
+ @Test
+ public void testDeleteRowsDirect() {
+
+ storageSource.deleteRow(PERSON_TABLE_NAME, "111-11-1111");
+ storageSource.deleteRow(PERSON_TABLE_NAME, "222-22-2222");
+ storageSource.deleteRow(PERSON_TABLE_NAME, "333-33-3333");
+ storageSource.deleteRow(PERSON_TABLE_NAME, "444-44-4444");
+
+ Object[][] expectedResults = {
+ {"555-55-5555", "Jose", "Garcia", 31, true},
+ {"666-66-6666", "Abigail", "Johnson", 35, false},
+ {"777-77-7777", "Bjorn", "Borg", 55, true},
+ {"888-88-8888", "John", "McEnroe", 53, false}
+ };
+ IResultSet resultSet = storageSource.executeQuery(PERSON_TABLE_NAME, PERSON_COLUMN_LIST, null, new RowOrdering(PERSON_SSN));
+ checkExpectedResults(resultSet, PERSON_COLUMN_LIST, expectedResults);
+ }
+
+ @Test
+ public void testDeleteRowsFromResultSet() {
+ Object[][] expectedResults = {
+ {"555-55-5555", "Jose", "Garcia", 31, true},
+ {"666-66-6666", "Abigail", "Johnson", 35, false},
+ {"777-77-7777", "Bjorn", "Borg", 55, true},
+ {"888-88-8888", "John", "McEnroe", 53, false}
+ };
+
+ // Query once to delete the rows
+ IResultSet resultSet = storageSource.executeQuery(PERSON_TABLE_NAME, PERSON_COLUMN_LIST, null, new RowOrdering(PERSON_SSN));
+ for (int i = 0; i < 4; i++) {
+ resultSet.next();
+ resultSet.deleteRow();
+ }
+ resultSet.save();
+ resultSet.close();
+
+ // Now query again to verify that the rows were deleted
+ resultSet = storageSource.executeQuery(PERSON_TABLE_NAME, PERSON_COLUMN_LIST, null, new RowOrdering(PERSON_SSN));
+ checkExpectedResults(resultSet, PERSON_COLUMN_LIST, expectedResults);
+ }
+
+ @Test
+ public void testDeleteMatchingRows() {
+ Object[][] expectedResults = {
+ {"111-11-1111", "John", "Smith", 40, true},
+ {"777-77-7777", "Bjorn", "Borg", 55, true},
+ {"888-88-8888", "John", "McEnroe", 53, false}
+ };
+ storageSource.deleteMatchingRows(PERSON_TABLE_NAME, new OperatorPredicate(PERSON_AGE, OperatorPredicate.Operator.LT, 40));
+
+ // Now query again to verify that the rows were deleted
+ IResultSet resultSet = storageSource.executeQuery(PERSON_TABLE_NAME, PERSON_COLUMN_LIST, null, new RowOrdering(PERSON_SSN));
+ checkExpectedResults(resultSet, PERSON_COLUMN_LIST, expectedResults);
+
+ storageSource.deleteMatchingRows(PERSON_TABLE_NAME, null);
+
+ // Now query again to verify that all rows were deleted
+ resultSet = storageSource.executeQuery(PERSON_TABLE_NAME, PERSON_COLUMN_LIST, null, new RowOrdering(PERSON_SSN));
+ checkExpectedResults(resultSet, PERSON_COLUMN_LIST, new Object[0][]);
+ }
+
+ @Test
+ public void testUpdateRowsDirect() {
+
+ Object[][] expectedResults = {
+ {"777-77-7777", "Tennis", "Borg", 60, true},
+ {"888-88-8888", "Tennis", "McEnroe", 60, false}
+ };
+ Map<String,Object> updateValues = new HashMap<String,Object>();
+ updateValues.put(PERSON_FIRST_NAME, "Tennis");
+ updateValues.put(PERSON_AGE, 60);
+
+ IPredicate predicate = new OperatorPredicate(PERSON_AGE, OperatorPredicate.Operator.GT, 50);
+ IResultSet resultSet = storageSource.executeQuery(PERSON_TABLE_NAME, null, predicate, new RowOrdering(PERSON_SSN));
+ while (resultSet.next()) {
+ String key = resultSet.getString(PERSON_SSN);
+ storageSource.updateRow(PERSON_TABLE_NAME, key, updateValues);
+ }
+ resultSet.close();
+
+ resultSet = storageSource.executeQuery(PERSON_TABLE_NAME, PERSON_COLUMN_LIST, predicate, new RowOrdering(PERSON_SSN));
+ checkExpectedResults(resultSet, PERSON_COLUMN_LIST, expectedResults);
+ }
+
+ @Test
+ public void testUpdateRowsFromResultSet() {
+
+ Object[][] expectedResults = {
+ {"777-77-7777", "Tennis", "Borg", 60, true},
+ {"888-88-8888", "Tennis", "McEnroe", 60, false}
+ };
+
+ IPredicate predicate = new OperatorPredicate(PERSON_AGE, OperatorPredicate.Operator.GT, 50);
+ IResultSet resultSet = storageSource.executeQuery(PERSON_TABLE_NAME, null, predicate, null);
+ while (resultSet.next()) {
+ resultSet.setString(PERSON_FIRST_NAME, "Tennis");
+ resultSet.setInt(PERSON_AGE, 60);
+ }
+ resultSet.save();
+ resultSet.close();
+
+ resultSet = storageSource.executeQuery(PERSON_TABLE_NAME, PERSON_COLUMN_LIST, predicate, new RowOrdering(PERSON_SSN));
+ checkExpectedResults(resultSet, PERSON_COLUMN_LIST, expectedResults);
+ }
+
+ @Test
+ public void testNullValues() {
+
+ IPredicate predicate = new OperatorPredicate(PERSON_LAST_NAME, OperatorPredicate.Operator.EQ, "Jones");
+ IResultSet resultSet = storageSource.executeQuery(PERSON_TABLE_NAME, null, predicate, new RowOrdering(PERSON_SSN));
+ while (resultSet.next()) {
+ resultSet.setNull(PERSON_FIRST_NAME);
+ resultSet.setIntegerObject(PERSON_AGE, null);
+ }
+ resultSet.save();
+ resultSet.close();
+
+ resultSet = storageSource.executeQuery(PERSON_TABLE_NAME, null, predicate, new RowOrdering(PERSON_SSN));
+ int count = 0;
+ while (resultSet.next()) {
+ boolean checkNull = resultSet.isNull(PERSON_FIRST_NAME);
+ assertTrue(checkNull);
+ String s = resultSet.getString(PERSON_FIRST_NAME);
+ assertEquals(s, null);
+ checkNull = resultSet.isNull(PERSON_AGE);
+ assertTrue(checkNull);
+ Integer intObj = resultSet.getIntegerObject(PERSON_AGE);
+ assertEquals(intObj, null);
+ Short shortObj = resultSet.getShortObject(PERSON_AGE);
+ assertEquals(shortObj, null);
+ boolean excThrown = false;
+ try {
+ resultSet.getInt(PERSON_AGE);
+ }
+ catch (NullValueStorageException exc) {
+ excThrown = true;
+ }
+ assertTrue(excThrown);
+ count++;
+ }
+ resultSet.close();
+ assertEquals(count, 2);
+
+ predicate = new OperatorPredicate(PERSON_FIRST_NAME, OperatorPredicate.Operator.EQ, null);
+ resultSet = storageSource.executeQuery(PERSON_TABLE_NAME, null, predicate, new RowOrdering(PERSON_SSN));
+ count = 0;
+ while (resultSet.next()) {
+ boolean checkNull = resultSet.isNull(PERSON_FIRST_NAME);
+ assertTrue(checkNull);
+ count++;
+ checkNull = resultSet.isNull(PERSON_AGE);
+ assertTrue(checkNull);
+ }
+ resultSet.close();
+ assertEquals(count, 2);
+ }
+
+ @Test
+ public void testInsertNotification() {
+ // Set up the listener and record the expected notification
+ IStorageSourceListener mockListener = createNiceMock(IStorageSourceListener.class);
+ Set<Object> expectedKeys = new HashSet<Object>();
+ expectedKeys.add("999-99-9999");
+ mockListener.rowsModified(PERSON_TABLE_NAME, expectedKeys);
+
+ replay(mockListener);
+
+ // Now try it for real
+ storageSource.addListener(PERSON_TABLE_NAME, mockListener);
+
+ // Create a new person, which should trigger the listener
+ Object[] newPerson = {"999-99-9999", "Serena", "Williams", 22, true};
+ insertPerson(newPerson);
+
+ verify(mockListener);
+ }
+
+ @Test
+ public void testUpdateNotification() {
+ // Set up the listener and record the expected notification
+ IStorageSourceListener mockListener = createNiceMock(IStorageSourceListener.class);
+ Set<Object> expectedKeys = new HashSet<Object>();
+ expectedKeys.add("111-11-1111");
+ mockListener.rowsModified(PERSON_TABLE_NAME, expectedKeys);
+
+ replay(mockListener);
+
+ // Now try it for real
+ storageSource.addListener(PERSON_TABLE_NAME, mockListener);
+
+ // Create a new person, which should trigger the listener
+ Map<String,Object> updateValues = new HashMap<String,Object>();
+ updateValues.put(PERSON_FIRST_NAME, "Tennis");
+ storageSource.updateRow(PERSON_TABLE_NAME, "111-11-1111", updateValues);
+
+ verify(mockListener);
+ }
+
+ @Test
+ public void testDeleteNotification() {
+ IStorageSourceListener mockListener = createNiceMock(IStorageSourceListener.class);
+ Set<Object> expectedKeys = new HashSet<Object>();
+ expectedKeys.add("111-11-1111");
+ mockListener.rowsDeleted(PERSON_TABLE_NAME, expectedKeys);
+
+ replay(mockListener);
+
+ // Now try it for real
+ storageSource.addListener(PERSON_TABLE_NAME, mockListener);
+
+ // Create a new person, which should trigger the listener
+ storageSource.deleteRow(PERSON_TABLE_NAME, "111-11-1111");
+
+ verify(mockListener);
+ }
+
+ public void waitForFuture(Future<?> future) {
+ try
+ {
+ future.get(10, TimeUnit.SECONDS);
+ }
+ catch (InterruptedException exc)
+ {
+ fail("Async storage operation interrupted");
+ }
+ catch (ExecutionException exc) {
+ fail("Async storage operation failed");
+ }
+ catch (TimeoutException exc) {
+ fail("Async storage operation timed out");
+ }
+ }
+
+ @Test
+ public void testAsyncQuery1() {
+ Object[][] expectedResults = {
+ {"John", "Smith", 40},
+ {"Jim", "White", 24},
+ };
+ String[] columnList = {PERSON_FIRST_NAME,PERSON_LAST_NAME,PERSON_AGE};
+ IPredicate predicate = new OperatorPredicate(PERSON_LAST_NAME, OperatorPredicate.Operator.GTE, "Sm");
+ IQuery query = storageSource.createQuery(PERSON_TABLE_NAME, columnList, predicate, new RowOrdering(PERSON_SSN));
+ Future<IResultSet> future = storageSource.executeQueryAsync(query);
+ waitForFuture(future);
+ try {
+ IResultSet resultSet = future.get();
+ checkExpectedResults(resultSet, columnList, expectedResults);
+ }
+ catch (Exception e) {
+ fail("Exception thrown in async storage operation: " + e.toString());
+ }
+ }
+
+ @Test
+ public void testAsyncQuery2() {
+ Object[][] expectedResults = {
+ {"John", "Smith", 40},
+ {"Jim", "White", 24},
+ };
+ String[] columnList = {PERSON_FIRST_NAME,PERSON_LAST_NAME,PERSON_AGE};
+ IPredicate predicate = new OperatorPredicate(PERSON_LAST_NAME, OperatorPredicate.Operator.GTE, "Sm");
+ Future<IResultSet> future = storageSource.executeQueryAsync(PERSON_TABLE_NAME,
+ columnList, predicate, new RowOrdering(PERSON_SSN));
+ waitForFuture(future);
+ try {
+ IResultSet resultSet = future.get();
+ checkExpectedResults(resultSet, columnList, expectedResults);
+ }
+ catch (Exception e) {
+ fail("Exception thrown in async storage operation: " + e.toString());
+ }
+ }
+
+ @Test
+ public void testAsyncQuery3() {
+ Object[][] expectedResults = {
+ PERSON_INIT_DATA[2],
+ PERSON_INIT_DATA[3]
+ };
+ IPredicate predicate = new OperatorPredicate(PERSON_LAST_NAME, OperatorPredicate.Operator.EQ, "Jones");
+ IRowMapper rowMapper = new PersonRowMapper();
+ Future<Object[]> future = storageSource.executeQueryAsync(PERSON_TABLE_NAME,
+ null, predicate, new RowOrdering(PERSON_SSN), rowMapper);
+ waitForFuture(future);
+ try {
+ Object[] personList = future.get();
+ assertEquals(personList.length, 2);
+ for (int i = 0; i < personList.length; i++)
+ checkPerson((Person)personList[i], expectedResults[i]);
+ }
+ catch (Exception e) {
+ fail("Exception thrown in async storage operation: " + e.toString());
+ }
+ }
+
+ @Test
+ public void testAsyncException() {
+ class TestExceptionHandler implements IStorageExceptionHandler {
+ public int exceptionCount = 0;
+ @Override
+ public void handleException(Exception exception) {
+ exceptionCount++;
+ }
+ }
+ TestExceptionHandler exceptionHandler = new TestExceptionHandler();
+ storageSource.setExceptionHandler(exceptionHandler);
+
+ // Use an invalid table name, which should cause the storage API call to throw
+ // an exception, which should then be converted to an ExecutionException.
+ Future<IResultSet> future = storageSource.executeQueryAsync("InvalidTableName",
+ null, null, null);
+ try {
+ future.get(10, TimeUnit.SECONDS);
+ fail("Expected ExecutionException was not thrown");
+ }
+ catch (ExecutionException e) {
+ assertTrue(true);
+ }
+ catch (Exception e) {
+ fail("Exception thrown in async storage operation: " + e.toString());
+ }
+ assertEquals(exceptionHandler.exceptionCount, 1);
+ }
+
+ @Test
+ public void testAsyncInsertRow() {
+ Object[][] newPersonInfo = {{"999-99-9999", "Ellen", "Wilson", 40, true}};
+ Map<String,Object> rowValues = createPersonRowValues(newPersonInfo[0]);
+ Future<?> future = storageSource.insertRowAsync(PERSON_TABLE_NAME, rowValues);
+ waitForFuture(future);
+ try {
+ IResultSet resultSet = storageSource.executeQuery(PERSON_TABLE_NAME, null, null, new RowOrdering(PERSON_SSN));
+ Object[][] expectedPersons = Arrays.copyOf(PERSON_INIT_DATA, PERSON_INIT_DATA.length + newPersonInfo.length);
+ System.arraycopy(newPersonInfo, 0, expectedPersons, PERSON_INIT_DATA.length, newPersonInfo.length);
+ checkExpectedResults(resultSet, PERSON_COLUMN_LIST, expectedPersons);
+ }
+ catch (Exception e) {
+ fail("Exception thrown in async storage operation: " + e.toString());
+ }
+ }
+
+ @Test
+ public void testAsyncUpdateRow() {
+ Map<String,Object> updateValues = new HashMap<String,Object>();
+ updateValues.put(PERSON_SSN, "777-77-7777");
+ updateValues.put(PERSON_FIRST_NAME, "Tennis");
+ updateValues.put(PERSON_AGE, 60);
+
+ Future<?> future = storageSource.updateRowAsync(PERSON_TABLE_NAME, updateValues);
+ waitForFuture(future);
+
+ try {
+ IResultSet resultSet = storageSource.getRow(PERSON_TABLE_NAME, "777-77-7777");
+ Object[][] expectedPersons = {{"777-77-7777", "Tennis", "Borg", 60, true}};
+ checkExpectedResults(resultSet, PERSON_COLUMN_LIST, expectedPersons);
+ }
+ catch (Exception e) {
+ fail("Exception thrown in async storage operation: " + e.toString());
+ }
+ }
+
+ @Test
+ public void testAsyncUpdateRow2() {
+ Map<String,Object> updateValues = new HashMap<String,Object>();
+ updateValues.put(PERSON_FIRST_NAME, "Tennis");
+ updateValues.put(PERSON_AGE, 60);
+
+ Future<?> future = storageSource.updateRowAsync(PERSON_TABLE_NAME, "777-77-7777", updateValues);
+ waitForFuture(future);
+
+ try {
+ IResultSet resultSet = storageSource.getRow(PERSON_TABLE_NAME, "777-77-7777");
+ Object[][] expectedPersons = {{"777-77-7777", "Tennis", "Borg", 60, true}};
+ checkExpectedResults(resultSet, PERSON_COLUMN_LIST, expectedPersons);
+ }
+ catch (Exception e) {
+ fail("Exception thrown in async storage operation: " + e.toString());
+ }
+ }
+
+ @Test
+ public void testAsyncUpdateMatchingRows() {
+ Map<String,Object> updateValues = new HashMap<String,Object>();
+ updateValues.put(PERSON_FIRST_NAME, "Tennis");
+ updateValues.put(PERSON_AGE, 60);
+
+ IPredicate predicate = new OperatorPredicate(PERSON_SSN, OperatorPredicate.Operator.EQ, "777-77-7777");
+ Future<?> future = storageSource.updateMatchingRowsAsync(PERSON_TABLE_NAME, predicate, updateValues);
+ waitForFuture(future);
+ try {
+ IResultSet resultSet = storageSource.getRow(PERSON_TABLE_NAME, "777-77-7777");
+ Object[][] expectedPersons = {{"777-77-7777", "Tennis", "Borg", 60, true}};
+ checkExpectedResults(resultSet, PERSON_COLUMN_LIST, expectedPersons);
+ }
+ catch (Exception e) {
+ fail("Exception thrown in async storage operation: " + e.toString());
+ }
+ }
+
+ @Test
+ public void testAsyncDeleteRow() {
+ Future<?> future = storageSource.deleteRowAsync(PERSON_TABLE_NAME, "111-11-1111");
+ waitForFuture(future);
+ try {
+ IResultSet resultSet = storageSource.executeQuery(PERSON_TABLE_NAME, null, null, new RowOrdering(PERSON_SSN));
+ Object[][] expectedPersons = Arrays.copyOfRange(PERSON_INIT_DATA, 1, PERSON_INIT_DATA.length);
+ checkExpectedResults(resultSet, PERSON_COLUMN_LIST, expectedPersons);
+ }
+ catch (Exception e) {
+ fail("Exception thrown in async storage operation: " + e.toString());
+ }
+ }
+
+ @Test
+ public void testAsyncDeleteMatchingRows() {
+ Future<?> future = storageSource.deleteMatchingRowsAsync(PERSON_TABLE_NAME, null);
+ waitForFuture(future);
+ try {
+ IResultSet resultSet = storageSource.executeQuery(PERSON_TABLE_NAME, null, null, new RowOrdering(PERSON_SSN));
+ checkExpectedResults(resultSet, PERSON_COLUMN_LIST, new Object[0][]);
+ }
+ catch (Exception e) {
+ fail("Exception thrown in async storage operation: " + e.toString());
+ }
+ }
+
+ @Test
+ public void testAsyncSave() {
+ // Get a result set and make some changes to it
+ IResultSet resultSet = storageSource.executeQuery(PERSON_TABLE_NAME, null, null, new RowOrdering(PERSON_SSN));
+ resultSet.next();
+ resultSet.deleteRow();
+ resultSet.next();
+ resultSet.setString(PERSON_FIRST_NAME, "John");
+
+ Future<?> future = storageSource.saveAsync(resultSet);
+ waitForFuture(future);
+ try {
+ resultSet = storageSource.executeQuery(PERSON_TABLE_NAME, null, null, new RowOrdering(PERSON_SSN));
+ Object[][] expectedPersons = Arrays.copyOfRange(PERSON_INIT_DATA, 1, PERSON_INIT_DATA.length);
+ expectedPersons[0][1] = "John";
+ checkExpectedResults(resultSet, PERSON_COLUMN_LIST, expectedPersons);
+ }
+ catch (Exception e) {
+ fail("Exception thrown in async storage operation: " + e.toString());
+ }
+
+ }
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/test/FloodlightTestCase.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/test/FloodlightTestCase.java
new file mode 100644
index 0000000..b0e83cc
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/test/FloodlightTestCase.java
@@ -0,0 +1,95 @@
+/**
+* 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.test;
+
+import junit.framework.TestCase;
+import net.floodlightcontroller.core.FloodlightContext;
+import net.floodlightcontroller.core.IFloodlightProviderService;
+import net.floodlightcontroller.core.test.MockFloodlightProvider;
+import net.floodlightcontroller.devicemanager.IDevice;
+import net.floodlightcontroller.devicemanager.IDeviceService;
+import net.floodlightcontroller.packet.Ethernet;
+
+import org.junit.Test;
+import org.openflow.protocol.OFMessage;
+import org.openflow.protocol.OFPacketIn;
+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 {
+ protected MockFloodlightProvider mockFloodlightProvider;
+
+ public MockFloodlightProvider getMockFloodlightProvider() {
+ return mockFloodlightProvider;
+ }
+
+ public void setMockFloodlightProvider(MockFloodlightProvider mockFloodlightProvider) {
+ this.mockFloodlightProvider = mockFloodlightProvider;
+ }
+
+ public FloodlightContext parseAndAnnotate(OFMessage m,
+ IDevice srcDevice,
+ IDevice dstDevice) {
+ FloodlightContext bc = new FloodlightContext();
+ return parseAndAnnotate(bc, m, srcDevice, dstDevice);
+ }
+
+ public FloodlightContext parseAndAnnotate(OFMessage m) {
+ return parseAndAnnotate(m, null, null);
+ }
+
+ public FloodlightContext parseAndAnnotate(FloodlightContext bc,
+ OFMessage m,
+ IDevice srcDevice,
+ IDevice dstDevice) {
+ if (OFType.PACKET_IN.equals(m.getType())) {
+ OFPacketIn pi = (OFPacketIn)m;
+ Ethernet eth = new Ethernet();
+ eth.deserialize(pi.getPacketData(), 0, pi.getPacketData().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
+ public void setUp() throws Exception {
+ mockFloodlightProvider = new MockFloodlightProvider();
+ }
+
+ @Test
+ public void testSanity() throws Exception {
+ assertTrue(true);
+ }
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/topology/TopologyInstanceTest.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/topology/TopologyInstanceTest.java
new file mode 100644
index 0000000..e72179d
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/topology/TopologyInstanceTest.java
@@ -0,0 +1,518 @@
+package net.floodlightcontroller.topology;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import static org.junit.Assert.*;
+
+import net.floodlightcontroller.core.IFloodlightProviderService;
+import net.floodlightcontroller.core.module.FloodlightModuleContext;
+import net.floodlightcontroller.core.test.MockFloodlightProvider;
+import net.floodlightcontroller.core.test.MockThreadPoolService;
+import net.floodlightcontroller.linkdiscovery.ILinkDiscovery;
+import net.floodlightcontroller.threadpool.IThreadPoolService;
+import net.floodlightcontroller.topology.NodePortTuple;
+import net.floodlightcontroller.topology.TopologyInstance;
+import net.floodlightcontroller.topology.TopologyManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class TopologyInstanceTest {
+ protected static Logger log = LoggerFactory.getLogger(TopologyInstanceTest.class);
+ protected TopologyManager topologyManager;
+ protected FloodlightModuleContext fmc;
+ protected MockFloodlightProvider mockFloodlightProvider;
+
+ protected int DIRECT_LINK = 1;
+ protected int MULTIHOP_LINK = 2;
+ protected int TUNNEL_LINK = 3;
+
+ @Before
+ public void SetUp() throws Exception {
+ fmc = new FloodlightModuleContext();
+ mockFloodlightProvider = new MockFloodlightProvider();
+ fmc.addService(IFloodlightProviderService.class, mockFloodlightProvider);
+ MockThreadPoolService tp = new MockThreadPoolService();
+ topologyManager = new TopologyManager();
+ fmc.addService(IThreadPoolService.class, tp);
+ topologyManager.init(fmc);
+ tp.init(fmc);
+ tp.startUp(fmc);
+ }
+
+ protected void verifyClusters(int[][] clusters) {
+ verifyClusters(clusters, true);
+ }
+
+ protected void verifyClusters(int[][] clusters, boolean tunnelsEnabled) {
+ List<Long> verifiedSwitches = new ArrayList<Long>();
+
+ // Make sure the expected cluster arrays are sorted so we can
+ // use binarySearch to test for membership
+ for (int i = 0; i < clusters.length; i++)
+ Arrays.sort(clusters[i]);
+
+ TopologyInstance ti =
+ topologyManager.getCurrentInstance(tunnelsEnabled);
+ Set<Long> switches = ti.getSwitches();
+
+ for (long sw: switches) {
+ if (!verifiedSwitches.contains(sw)) {
+
+ int[] expectedCluster = null;
+
+ for (int j = 0; j < clusters.length; j++) {
+ if (Arrays.binarySearch(clusters[j], (int) sw) >= 0) {
+ expectedCluster = clusters[j];
+ break;
+ }
+ }
+ if (expectedCluster != null) {
+ Set<Long> cluster = ti.getSwitchesInOpenflowDomain(sw);
+ assertEquals(expectedCluster.length, cluster.size());
+ for (long sw2: cluster) {
+ assertTrue(Arrays.binarySearch(expectedCluster, (int)sw2) >= 0);
+ verifiedSwitches.add(sw2);
+ }
+ }
+ }
+ }
+ }
+
+ protected void
+ verifyExpectedBroadcastPortsInClusters(int [][][] ebp) {
+ verifyExpectedBroadcastPortsInClusters(ebp, true);
+ }
+
+ protected void
+ verifyExpectedBroadcastPortsInClusters(int [][][] ebp,
+ boolean tunnelsEnabled) {
+ NodePortTuple npt = null;
+ Set<NodePortTuple> expected = new HashSet<NodePortTuple>();
+ for(int i=0; i<ebp.length; ++i) {
+ int [][] nptList = ebp[i];
+ expected.clear();
+ for(int j=0; j<nptList.length; ++j) {
+ npt = new NodePortTuple((long)nptList[j][0], (short)nptList[j][1]);
+ expected.add(npt);
+ }
+ TopologyInstance ti = topologyManager.getCurrentInstance(tunnelsEnabled);
+ Set<NodePortTuple> computed = ti.getBroadcastNodePortsInCluster(npt.nodeId);
+ if (computed != null)
+ assertTrue(computed.equals(expected));
+ else if (computed == null)
+ assertTrue(expected.isEmpty());
+ }
+ }
+
+ public void createTopologyFromLinks(int [][] linkArray) throws Exception {
+ ILinkDiscovery.LinkType type = ILinkDiscovery.LinkType.DIRECT_LINK;
+
+ // Use topologymanager to write this test, it will make it a lot easier.
+ for (int i = 0; i < linkArray.length; i++) {
+ int [] r = linkArray[i];
+ if (r[4] == DIRECT_LINK)
+ type= ILinkDiscovery.LinkType.DIRECT_LINK;
+ else if (r[4] == MULTIHOP_LINK)
+ type= ILinkDiscovery.LinkType.MULTIHOP_LINK;
+ else if (r[4] == TUNNEL_LINK)
+ type = ILinkDiscovery.LinkType.TUNNEL;
+
+ topologyManager.addOrUpdateLink((long)r[0], (short)r[1], (long)r[2], (short)r[3], type);
+ }
+ topologyManager.createNewInstance();
+ }
+
+ public TopologyManager getTopologyManager() {
+ return topologyManager;
+ }
+
+ @Test
+ public void testClusters() throws Exception {
+ TopologyManager tm = getTopologyManager();
+ {
+ int [][] linkArray = {
+ {1, 1, 2, 1, DIRECT_LINK},
+ {2, 2, 3, 2, DIRECT_LINK},
+ {3, 1, 1, 2, DIRECT_LINK},
+ {2, 3, 4, 2, DIRECT_LINK},
+ {3, 3, 4, 1, DIRECT_LINK}
+ };
+ int [][] expectedClusters = {
+ {1,2,3},
+ {4}
+ };
+ //tm.recompute();
+ createTopologyFromLinks(linkArray);
+ verifyClusters(expectedClusters);
+ }
+
+ {
+ int [][] linkArray = {
+ {5, 3, 6, 1, DIRECT_LINK}
+ };
+ int [][] expectedClusters = {
+ {1,2,3},
+ {4},
+ {5},
+ {6}
+ };
+ createTopologyFromLinks(linkArray);
+
+ verifyClusters(expectedClusters);
+ }
+
+ {
+ int [][] linkArray = {
+ {6, 1, 5, 3, DIRECT_LINK}
+ };
+ int [][] expectedClusters = {
+ {1,2,3},
+ {4},
+ {5,6}
+ };
+ createTopologyFromLinks(linkArray);
+
+ verifyClusters(expectedClusters);
+ }
+
+ {
+ int [][] linkArray = {
+ {4, 2, 2, 3, DIRECT_LINK}
+ };
+ int [][] expectedClusters = {
+ {1,2,3,4},
+ {5,6}
+ };
+ createTopologyFromLinks(linkArray);
+
+ verifyClusters(expectedClusters);
+ }
+ {
+ int [][] linkArray = {
+ {4, 3, 5, 1, DIRECT_LINK}
+ };
+ int [][] expectedClusters = {
+ {1,2,3,4},
+ {5,6}
+ };
+ createTopologyFromLinks(linkArray);
+
+ verifyClusters(expectedClusters);
+ }
+ {
+ int [][] linkArray = {
+ {5, 2, 2, 4, DIRECT_LINK}
+ };
+ int [][] expectedClusters = {
+ {1,2,3,4,5,6}
+ };
+ createTopologyFromLinks(linkArray);
+
+ verifyClusters(expectedClusters);
+ }
+
+ //Test 2.
+ {
+ int [][] linkArray = {
+ {3, 2, 2, 2, DIRECT_LINK},
+ {2, 1, 1, 1, DIRECT_LINK},
+ {1, 2, 3, 1, DIRECT_LINK},
+ {4, 1, 3, 3, DIRECT_LINK},
+ {5, 1, 4, 3, DIRECT_LINK},
+ {2, 4, 5, 2, DIRECT_LINK}
+ };
+ int [][] expectedClusters = {
+ {1,2,3,4,5,6}
+ };
+ createTopologyFromLinks(linkArray);
+ verifyClusters(expectedClusters);
+ }
+
+ // Test 3. Remove links
+ {
+ tm.removeLink((long)5,(short)3,(long)6,(short)1);
+ tm.removeLink((long)6,(short)1,(long)5,(short)3);
+
+ int [][] expectedClusters = {
+ {1,2,3,4,5},
+ };
+ topologyManager.createNewInstance();
+ verifyClusters(expectedClusters);
+ }
+
+ // Remove Switch
+ {
+ tm.removeSwitch(4);
+ int [][] expectedClusters = {
+ {1,2,3,5},
+ };
+ topologyManager.createNewInstance();
+ verifyClusters(expectedClusters);
+ }
+ }
+
+ @Test
+ public void testLoopDetectionInSingleIsland() throws Exception {
+
+ int [][] linkArray = {
+ {1, 1, 2, 1, DIRECT_LINK},
+ {2, 1, 1, 1, DIRECT_LINK},
+ {1, 2, 3, 1, DIRECT_LINK},
+ {3, 1, 1, 2, DIRECT_LINK},
+ {2, 2, 3, 2, DIRECT_LINK},
+ {3, 2, 2, 2, DIRECT_LINK},
+ {3, 3, 4, 1, DIRECT_LINK},
+ {4, 1, 3, 3, DIRECT_LINK},
+ {4, 2, 6, 2, DIRECT_LINK},
+ {6, 2, 4, 2, DIRECT_LINK},
+ {4, 3, 5, 1, DIRECT_LINK},
+ {5, 1, 4, 3, DIRECT_LINK},
+ {5, 2, 6, 1, DIRECT_LINK},
+ {6, 1, 5, 2, DIRECT_LINK},
+
+ };
+ int [][] expectedClusters = {
+ {1, 2, 3, 4, 5, 6}
+ };
+ int [][][] expectedBroadcastPorts = {
+ {{1,1}, {2,1}, {1,2}, {3,1}, {3,3}, {4,1}, {4,3}, {5,1}, {4,2}, {6,2}},
+ };
+
+ createTopologyFromLinks(linkArray);
+ topologyManager.createNewInstance();
+ verifyClusters(expectedClusters);
+ verifyExpectedBroadcastPortsInClusters(expectedBroadcastPorts);
+ }
+
+ @Test
+ public void testTunnelLinkDeletion() throws Exception {
+
+ // +-------+ +-------+
+ // | | | |
+ // | 1 1|-------------|1 2 |
+ // | 2 | | 2 |
+ // +-------+ +-------+
+ // | |
+ // | |
+ // +-------+ |
+ // | 1 | |
+ // | 3 2|-----------------+
+ // | 3 |
+ // +-------+
+ //
+ //
+ // +-------+
+ // | 1 |
+ // | 4 2|----------------+
+ // | 3 | |
+ // +-------+ |
+ // | |
+ // | |
+ // +-------+ +-------+
+ // | 1 | | 2 |
+ // | 5 2|-------------|1 6 |
+ // | | | |
+ // +-------+ +-------+
+ {
+ int [][] linkArray = {
+ {1, 1, 2, 1, DIRECT_LINK},
+ {2, 1, 1, 1, DIRECT_LINK},
+ {1, 2, 3, 1, TUNNEL_LINK},
+ {3, 1, 1, 2, TUNNEL_LINK},
+ {2, 2, 3, 2, TUNNEL_LINK},
+ {3, 2, 2, 2, TUNNEL_LINK},
+
+ {4, 2, 6, 2, DIRECT_LINK},
+ {6, 2, 4, 2, DIRECT_LINK},
+ {4, 3, 5, 1, TUNNEL_LINK},
+ {5, 1, 4, 3, TUNNEL_LINK},
+ {5, 2, 6, 1, TUNNEL_LINK},
+ {6, 1, 5, 2, TUNNEL_LINK},
+
+ };
+
+ int [][] expectedClusters = {
+ {1, 2},
+ {4, 6},
+ };
+ int [][][] expectedBroadcastPorts = {
+ {{1,1}, {2,1}},
+ {{4,2}, {6,2}}
+ };
+
+ createTopologyFromLinks(linkArray);
+ topologyManager.createNewInstance();
+ verifyClusters(expectedClusters, false);
+ verifyExpectedBroadcastPortsInClusters(expectedBroadcastPorts, false);
+ }
+
+ // +-------+ +-------+
+ // | | TUNNEL | |
+ // | 1 1|-------------|1 2 |
+ // | 2 | | 2 |
+ // +-------+ +-------+
+ // | |
+ // | |
+ // +-------+ |
+ // | 1 | |
+ // | 3 2|-----------------+
+ // | 3 |
+ // +-------+
+ // |
+ // | TUNNEL
+ // |
+ // +-------+
+ // | 1 | TUNNEL
+ // | 4 2|----------------+
+ // | 3 | |
+ // +-------+ |
+ // | |
+ // | |
+ // +-------+ +-------+
+ // | 1 | | 2 |
+ // | 5 2|-------------|1 6 |
+ // | | | |
+ // +-------+ +-------+
+
+ {
+ int [][] linkArray = {
+ {3, 3, 4, 1, TUNNEL_LINK},
+ {4, 1, 3, 3, TUNNEL_LINK},
+
+ };
+ int [][] expectedClusters = {
+ {1, 2},
+ {4, 6},
+ {3},
+ {5},
+ };
+ int [][][] expectedBroadcastPorts = {
+ {{1,1}, {2,1}},
+ {{4,2}, {6,2}}
+ };
+
+ createTopologyFromLinks(linkArray);
+ topologyManager.createNewInstance();
+ verifyClusters(expectedClusters, false);
+ verifyExpectedBroadcastPortsInClusters(expectedBroadcastPorts, false);
+ }
+ }
+
+ @Test
+ public void testLoopDetectionWithIslands() throws Exception {
+
+ // +-------+ +-------+
+ // | | TUNNEL | |
+ // | 1 1|-------------|1 2 |
+ // | 2 | | 2 |
+ // +-------+ +-------+
+ // | |
+ // | |
+ // +-------+ |
+ // | 1 | |
+ // | 3 2|-----------------+
+ // | 3 |
+ // +-------+
+ //
+ //
+ // +-------+
+ // | 1 | TUNNEL
+ // | 4 2|----------------+
+ // | 3 | |
+ // +-------+ |
+ // | |
+ // | |
+ // +-------+ +-------+
+ // | 1 | | 2 |
+ // | 5 2|-------------|1 6 |
+ // | | | |
+ // +-------+ +-------+
+ {
+ int [][] linkArray = {
+ {1, 1, 2, 1, TUNNEL_LINK},
+ {2, 1, 1, 1, TUNNEL_LINK},
+ {1, 2, 3, 1, DIRECT_LINK},
+ {3, 1, 1, 2, DIRECT_LINK},
+ {2, 2, 3, 2, DIRECT_LINK},
+ {3, 2, 2, 2, DIRECT_LINK},
+
+ {4, 2, 6, 2, TUNNEL_LINK},
+ {6, 2, 4, 2, TUNNEL_LINK},
+ {4, 3, 5, 1, DIRECT_LINK},
+ {5, 1, 4, 3, DIRECT_LINK},
+ {5, 2, 6, 1, DIRECT_LINK},
+ {6, 1, 5, 2, DIRECT_LINK},
+
+ };
+
+ int [][] expectedClusters = {
+ {1, 2, 3},
+ {4, 5, 6}
+ };
+ int [][][] expectedBroadcastPorts = {
+ {{1,2}, {3,1}, {2,2}, {3,2}},
+ {{4,3}, {5,1}, {5,2}, {6,1}},
+ };
+
+ createTopologyFromLinks(linkArray);
+ topologyManager.createNewInstance();
+ verifyClusters(expectedClusters);
+ verifyExpectedBroadcastPortsInClusters(expectedBroadcastPorts);
+ }
+
+ // +-------+ +-------+
+ // | | TUNNEL | |
+ // | 1 1|-------------|1 2 |
+ // | 2 | | 2 |
+ // +-------+ +-------+
+ // | |
+ // | |
+ // +-------+ |
+ // | 1 | |
+ // | 3 2|-----------------+
+ // | 3 |
+ // +-------+
+ // |
+ // | TUNNEL
+ // |
+ // +-------+
+ // | 1 | TUNNEL
+ // | 4 2|----------------+
+ // | 3 | |
+ // +-------+ |
+ // | |
+ // | |
+ // +-------+ +-------+
+ // | 1 | | 2 |
+ // | 5 2|-------------|1 6 |
+ // | | | |
+ // +-------+ +-------+
+
+ {
+ int [][] linkArray = {
+ {3, 3, 4, 1, TUNNEL_LINK},
+ {4, 1, 3, 3, TUNNEL_LINK},
+
+ };
+ int [][] expectedClusters = {
+ {1, 2, 3},
+ {4, 5, 6}
+ };
+ int [][][] expectedBroadcastPorts = {
+ {{1,2}, {3,1}, {2,2}, {3,2}},
+ {{4,3}, {5,1}, {5,2}, {6,1}},
+ };
+
+ createTopologyFromLinks(linkArray);
+ topologyManager.createNewInstance();
+ verifyClusters(expectedClusters, false);
+ verifyExpectedBroadcastPortsInClusters(expectedBroadcastPorts);
+ }
+ }
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/topology/TopologyManagerTest.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/topology/TopologyManagerTest.java
new file mode 100644
index 0000000..113cecb
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/topology/TopologyManagerTest.java
@@ -0,0 +1,142 @@
+package net.floodlightcontroller.topology;
+
+import net.floodlightcontroller.core.IFloodlightProviderService;
+import net.floodlightcontroller.core.IFloodlightProviderService.Role;
+import net.floodlightcontroller.core.module.FloodlightModuleContext;
+import net.floodlightcontroller.core.test.MockThreadPoolService;
+import net.floodlightcontroller.linkdiscovery.ILinkDiscovery;
+import net.floodlightcontroller.test.FloodlightTestCase;
+import net.floodlightcontroller.threadpool.IThreadPoolService;
+import net.floodlightcontroller.topology.TopologyManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class TopologyManagerTest extends FloodlightTestCase {
+ protected static Logger log = LoggerFactory.getLogger(TopologyManagerTest.class);
+ TopologyManager tm;
+ FloodlightModuleContext fmc;
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ fmc = new FloodlightModuleContext();
+ fmc.addService(IFloodlightProviderService.class, getMockFloodlightProvider());
+ MockThreadPoolService tp = new MockThreadPoolService();
+ fmc.addService(IThreadPoolService.class, tp);
+ tm = new TopologyManager();
+ tp.init(fmc);
+ tm.init(fmc);
+ tp.startUp(fmc);
+ }
+
+ @Test
+ public void testBasic1() throws Exception {
+ tm.addOrUpdateLink((long)1, (short)1, (long)2, (short)1, ILinkDiscovery.LinkType.DIRECT_LINK);
+ assertTrue(tm.getSwitchPorts().size() == 2); // for two nodes.
+ assertTrue(tm.getSwitchPorts().get((long)1).size()==1);
+ assertTrue(tm.getSwitchPorts().get((long)2).size()==1);
+ assertTrue(tm.getSwitchPortLinks().size()==2);
+ assertTrue(tm.getPortBroadcastDomainLinks().size()==0);
+ assertTrue(tm.getTunnelPorts().size()==0);
+
+ tm.addOrUpdateLink((long)1, (short)2, (long)2, (short)2, ILinkDiscovery.LinkType.MULTIHOP_LINK);
+ assertTrue(tm.getSwitchPorts().size() == 2); // for two nodes.
+ assertTrue(tm.getSwitchPorts().get((long)1).size()==2);
+ assertTrue(tm.getSwitchPorts().get((long)2).size()==2);
+ assertTrue(tm.getSwitchPortLinks().size()==4);
+ assertTrue(tm.getPortBroadcastDomainLinks().size()==2);
+ assertTrue(tm.getTunnelPorts().size()==0);
+
+ tm.addOrUpdateLink((long)1, (short)3, (long)2, (short)3, ILinkDiscovery.LinkType.TUNNEL);
+ assertTrue(tm.getSwitchPorts().size() == 2); // for two nodes.
+ assertTrue(tm.getSwitchPorts().get((long)1).size()==3);
+ assertTrue(tm.getSwitchPorts().get((long)2).size()==3);
+ assertTrue(tm.getSwitchPortLinks().size()==6);
+ assertTrue(tm.getPortBroadcastDomainLinks().size()==2);
+ assertTrue(tm.getTunnelPorts().size()==2);
+
+ tm.removeLink((long)1, (short)2, (long)2, (short)2);
+ log.info("# of switchports. {}", tm.getSwitchPorts().get((long)1).size());
+ assertTrue(tm.getSwitchPorts().get((long)1).size()==2);
+ assertTrue(tm.getSwitchPorts().get((long)2).size()==2);
+ assertTrue(tm.getSwitchPorts().size() == 2); // for two nodes.
+ assertTrue(tm.getSwitchPortLinks().size()==4);
+ assertTrue(tm.getPortBroadcastDomainLinks().size()==0);
+ assertTrue(tm.getTunnelPorts().size()==2);
+
+ tm.removeLink((long)1, (short)1, (long)2, (short)1);
+ assertTrue(tm.getSwitchPorts().size() == 2); // for two nodes.
+ assertTrue(tm.getSwitchPorts().get((long)1).size()==1);
+ assertTrue(tm.getSwitchPorts().get((long)2).size()==1);
+ assertTrue(tm.getSwitchPortLinks().size()==2);
+ assertTrue(tm.getPortBroadcastDomainLinks().size()==0);
+ assertTrue(tm.getTunnelPorts().size()==2);
+
+ tm.removeLink((long)1, (short)3, (long)2, (short)3);
+ assertTrue(tm.getSwitchPorts().size() == 0);
+ assertTrue(tm.getSwitchPortLinks().size()==0);
+ assertTrue(tm.getPortBroadcastDomainLinks().size()==0);
+ assertTrue(tm.getTunnelPorts().size()==0);
+ }
+
+ @Test
+ public void testBasic2() throws Exception {
+ tm.addOrUpdateLink((long)1, (short)1, (long)2, (short)1, ILinkDiscovery.LinkType.DIRECT_LINK);
+ tm.addOrUpdateLink((long)2, (short)2, (long)3, (short)1, ILinkDiscovery.LinkType.MULTIHOP_LINK);
+ tm.addOrUpdateLink((long)3, (short)2, (long)1, (short)2, ILinkDiscovery.LinkType.TUNNEL);
+ assertTrue(tm.getSwitchPorts().size() == 3); // for two nodes.
+ assertTrue(tm.getSwitchPorts().get((long)1).size()==2);
+ assertTrue(tm.getSwitchPorts().get((long)2).size()==2);
+ assertTrue(tm.getSwitchPorts().get((long)3).size()==2);
+ assertTrue(tm.getSwitchPortLinks().size()==6);
+ assertTrue(tm.getPortBroadcastDomainLinks().size()==2);
+ assertTrue(tm.getTunnelPorts().size()==2);
+
+ tm.removeLink((long)1, (short)1, (long)2, (short)1);
+ assertTrue(tm.getSwitchPorts().size() == 3); // for two nodes.
+ assertTrue(tm.getSwitchPorts().get((long)1).size()==1);
+ assertTrue(tm.getSwitchPorts().get((long)2).size()==1);
+ assertTrue(tm.getSwitchPorts().get((long)3).size()==2);
+ assertTrue(tm.getSwitchPortLinks().size()==4);
+ assertTrue(tm.getPortBroadcastDomainLinks().size()==2);
+ assertTrue(tm.getTunnelPorts().size()==2);
+
+ // nonexistent link // no null pointer exceptions.
+ tm.removeLink((long)3, (short)1, (long)2, (short)2);
+ assertTrue(tm.getSwitchPorts().size() == 3); // for two nodes.
+ assertTrue(tm.getSwitchPorts().get((long)1).size()==1);
+ assertTrue(tm.getSwitchPorts().get((long)2).size()==1);
+ assertTrue(tm.getSwitchPorts().get((long)3).size()==2);
+ assertTrue(tm.getSwitchPortLinks().size()==4);
+ assertTrue(tm.getPortBroadcastDomainLinks().size()==2);
+ assertTrue(tm.getTunnelPorts().size()==2);
+
+ tm.removeLink((long)3, (short)2, (long)1, (short)2);
+ assertTrue(tm.getSwitchPorts().size() == 2); // for two nodes.
+ assertTrue(tm.getSwitchPorts().get((long)1)==null);
+ assertTrue(tm.getSwitchPorts().get((long)2).size()==1);
+ assertTrue(tm.getSwitchPorts().get((long)3).size()==1);
+ assertTrue(tm.getSwitchPortLinks().size()==2);
+ assertTrue(tm.getPortBroadcastDomainLinks().size()==2);
+ assertTrue(tm.getTunnelPorts().size()==0);
+
+ tm.removeLink((long)2, (short)2, (long)3, (short)1);
+ assertTrue(tm.getSwitchPorts().size() == 0); // for two nodes.
+ assertTrue(tm.getSwitchPortLinks().size()==0);
+ assertTrue(tm.getPortBroadcastDomainLinks().size()==0);
+ assertTrue(tm.getTunnelPorts().size()==0);
+ }
+
+ @Test
+ public void testHARoleChange() throws Exception {
+ testBasic2();
+ getMockFloodlightProvider().dispatchRoleChanged(null, Role.SLAVE);
+ assert(tm.switchPorts.isEmpty());
+ assert(tm.switchPortLinks.isEmpty());
+ assert(tm.portBroadcastDomainLinks.isEmpty());
+ assert(tm.tunnelLinks.isEmpty());
+ }
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/util/MACAddressTest.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/util/MACAddressTest.java
new file mode 100644
index 0000000..5669614
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/util/MACAddressTest.java
@@ -0,0 +1,85 @@
+package net.floodlightcontroller.util;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author Sho Shimizu (sho.shimizu@gmail.com)
+ */
+public class MACAddressTest {
+ @Test
+ public void testValueOf() {
+ MACAddress address = MACAddress.valueOf("00:01:02:03:04:05");
+ assertEquals(address,
+ MACAddress.valueOf(new byte[]{0x00, 0x01, 0x02, 0x03, 0x04, 0x05}));
+ assertEquals("00:01:02:03:04:05", address.toString());
+
+ address = MACAddress.valueOf("FF:FE:FD:10:20:30");
+ assertEquals(address,
+ MACAddress.valueOf(new byte[]{(byte) 0xFF, (byte) 0xFE, (byte) 0xFD, 0x10, 0x20, 0x30}));
+ assertEquals("FF:FE:FD:10:20:30", address.toString());
+
+ address = MACAddress.valueOf("00:11:22:aa:bb:cc");
+ assertEquals(address,
+ MACAddress.valueOf(new byte[]{0x00, 0x11, 0x22, (byte)0xaa, (byte)0xbb, (byte)0xcc}));
+ }
+
+ @Test(expected=NumberFormatException.class)
+ public void testIllegalFormat() {
+ MACAddress.valueOf("0T:00:01:02:03:04");
+ }
+
+ @Test(expected=IllegalArgumentException.class)
+ public void testLongStringFields() {
+ MACAddress.valueOf("00:01:02:03:04:05:06");
+ }
+
+ @Test(expected=IllegalArgumentException.class)
+ public void testShortStringFields() {
+ MACAddress.valueOf("00:01:02:03:04");
+ }
+
+ @Test(expected=IllegalArgumentException.class)
+ public void testLongByteFields() {
+ MACAddress.valueOf(new byte[]{0x01, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06});
+ }
+
+ @Test(expected=IllegalArgumentException.class)
+ public void testShortByteField() {
+ MACAddress.valueOf(new byte[]{0x01, 0x01, 0x02, 0x03, 0x04});
+ }
+
+ // Test data is imported from net.floodlightcontroller.packet.EthernetTest
+ @Test
+ public void testToLong() {
+ assertEquals(
+ 281474976710655L,
+ MACAddress.valueOf(new byte[]{(byte) 0xff, (byte) 0xff,
+ (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff}).toLong());
+
+ assertEquals(
+ 1103823438081L,
+ MACAddress.valueOf(new byte[] { (byte) 0x01, (byte) 0x01,
+ (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x01 }).toLong());
+
+ assertEquals(
+ 141289400074368L,
+ MACAddress.valueOf(new byte[] { (byte) 0x80, (byte) 0x80,
+ (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80 }).toLong());
+
+ }
+
+ @Test
+ public void testIsBroadcast() {
+ assertTrue(MACAddress.valueOf("FF:FF:FF:FF:FF:FF").isBroadcast());
+ assertFalse(MACAddress.valueOf("11:22:33:44:55:66").isBroadcast());
+ }
+
+ @Test
+ public void testIsMulticast() {
+ assertTrue(MACAddress.valueOf("01:80:C2:00:00:00").isMulticast());
+ assertFalse(MACAddress.valueOf("FF:FF:FF:FF:FF:FF").isMulticast());
+ assertFalse(MACAddress.valueOf("11:22:33:44:55:66").isBroadcast());
+ }
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/util/OFMessageDamperMockSwitch.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/util/OFMessageDamperMockSwitch.java
new file mode 100644
index 0000000..59e0a51
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/util/OFMessageDamperMockSwitch.java
@@ -0,0 +1,349 @@
+package net.floodlightcontroller.util;
+
+import static org.junit.Assert.*;
+import java.io.IOException;
+
+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.FloodlightContext;
+import net.floodlightcontroller.core.IOFMessageListener;
+import net.floodlightcontroller.core.IOFSwitch;
+import net.floodlightcontroller.core.IFloodlightProviderService.Role;
+
+import org.jboss.netty.channel.Channel;
+import org.openflow.protocol.OFFeaturesReply;
+import org.openflow.protocol.OFMessage;
+import org.openflow.protocol.OFPhysicalPort;
+import org.openflow.protocol.OFStatisticsRequest;
+import org.openflow.protocol.statistics.OFDescriptionStatistics;
+import org.openflow.protocol.statistics.OFStatistics;
+
+
+/**
+ * A mock implementation of IFOSwitch we use for {@link OFMessageDamper}
+ *
+ * We need to mock equals() and hashCode() but alas, EasyMock doesn't support
+ * this. Sigh. And of course this happens to be the interface with the most
+ * methods.
+ * @author gregor
+ *
+ */
+public class OFMessageDamperMockSwitch implements IOFSwitch {
+ OFMessage writtenMessage;
+ FloodlightContext writtenContext;
+
+ public OFMessageDamperMockSwitch() {
+ reset();
+ }
+
+ /* reset this mock. I.e., clear the stored message previously written */
+ public void reset() {
+ writtenMessage = null;
+ writtenContext = null;
+ }
+
+ /* assert that a message was written to this switch and that the
+ * written message and context matches the expected values
+ * @param expected
+ * @param expectedContext
+ */
+ public void assertMessageWasWritten(OFMessage expected,
+ FloodlightContext expectedContext) {
+ assertNotNull("No OFMessage was written", writtenMessage);
+ assertEquals(expected, writtenMessage);
+ assertEquals(expectedContext, writtenContext);
+ }
+
+ /*
+ * assert that no message was written
+ */
+ public void assertNoMessageWritten() {
+ assertNull("OFMessage was written but didn't expect one",
+ writtenMessage);
+ assertNull("There was a context but didn't expect one",
+ writtenContext);
+ }
+
+ /*
+ * use hashCode() and equals() from Object
+ */
+
+
+ //-------------------------------------------------------
+ // IOFSwitch: mocked methods
+ @Override
+ public void write(OFMessage m, FloodlightContext bc) throws IOException {
+ assertNull("write() called but already have message", writtenMessage);
+ assertNull("write() called but already have context", writtenContext);
+ writtenContext = bc;
+ writtenMessage = m;
+ }
+
+ //-------------------------------------------------------
+ // IOFSwitch: not-implemented methods
+ @Override
+ public void write(List<OFMessage> msglist, FloodlightContext bc)
+ throws IOException {
+ assertTrue("Unexpected method call", false);
+ }
+
+ @Override
+ public void disconnectOutputStream() {
+ assertTrue("Unexpected method call", false);
+ }
+
+ @Override
+ public Channel getChannel() {
+ assertTrue("Unexpected method call", false);
+ return null;
+ }
+
+ @Override
+ public void setFeaturesReply(OFFeaturesReply featuresReply) {
+ assertTrue("Unexpected method call", false);
+ }
+
+ @Override
+ public void setSwitchProperties(OFDescriptionStatistics description) {
+ assertTrue("Unexpected method call", false);
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public Collection<OFPhysicalPort> getEnabledPorts() {
+ assertTrue("Unexpected method call", false);
+ return null;
+ }
+
+ @Override
+ public Collection<Short> getEnabledPortNumbers() {
+ assertTrue("Unexpected method call", false);
+ return null;
+ }
+
+ @Override
+ public OFPhysicalPort getPort(short portNumber) {
+ assertTrue("Unexpected method call", false);
+ return null;
+ }
+
+ @Override
+ public OFPhysicalPort getPort(String portName) {
+ assertTrue("Unexpected method call", false);
+ return null;
+ }
+
+ @Override
+ public void setPort(OFPhysicalPort port) {
+ assertTrue("Unexpected method call", false);
+ }
+
+ @Override
+ public void deletePort(short portNumber) {
+ assertTrue("Unexpected method call", false);
+ }
+
+ @Override
+ public void deletePort(String portName) {
+ assertTrue("Unexpected method call", false);
+ }
+
+ @Override
+ public Collection<OFPhysicalPort> getPorts() {
+ assertTrue("Unexpected method call", false);
+ return null;
+ }
+
+ @Override
+ public boolean portEnabled(short portName) {
+ assertTrue("Unexpected method call", false);
+ return false;
+ }
+
+ @Override
+ public boolean portEnabled(String portName) {
+ assertTrue("Unexpected method call", false);
+ return false;
+ }
+
+ @Override
+ public boolean portEnabled(OFPhysicalPort port) {
+ assertTrue("Unexpected method call", false);
+ return false;
+ }
+
+ @Override
+ public long getId() {
+ assertTrue("Unexpected method call", false);
+ return 0;
+ }
+
+ @Override
+ public String getStringId() {
+ assertTrue("Unexpected method call", false);
+ return null;
+ }
+
+ @Override
+ public Map<Object, Object> getAttributes() {
+ assertTrue("Unexpected method call", false);
+ return null;
+ }
+
+ @Override
+ public Date getConnectedSince() {
+ assertTrue("Unexpected method call", false);
+ return null;
+ }
+
+ @Override
+ public int getNextTransactionId() {
+ assertTrue("Unexpected method call", false);
+ return 0;
+ }
+
+ @Override
+ public Future<List<OFStatistics>>
+ getStatistics(OFStatisticsRequest request) throws IOException {
+ assertTrue("Unexpected method call", false);
+ return null;
+ }
+
+ @Override
+ public boolean isConnected() {
+ assertTrue("Unexpected method call", false);
+ return false;
+ }
+
+ @Override
+ public void setConnected(boolean connected) {
+ assertTrue("Unexpected method call", false);
+ }
+
+ @Override
+ public Role getRole() {
+ assertTrue("Unexpected method call", false);
+ return null;
+ }
+
+ @Override
+ public boolean isActive() {
+ assertTrue("Unexpected method call", false);
+ return false;
+ }
+
+ @Override
+ public void deliverStatisticsReply(OFMessage reply) {
+ assertTrue("Unexpected method call", false);
+ }
+
+ @Override
+ public void cancelStatisticsReply(int transactionId) {
+ assertTrue("Unexpected method call", false);
+ }
+
+ @Override
+ public void cancelAllStatisticsReplies() {
+ assertTrue("Unexpected method call", false);
+ }
+
+ @Override
+ public boolean hasAttribute(String name) {
+ assertTrue("Unexpected method call", false);
+ return false;
+ }
+
+ @Override
+ public Object getAttribute(String name) {
+ assertTrue("Unexpected method call", false);
+ return null;
+ }
+
+ @Override
+ public void setAttribute(String name, Object value) {
+ assertTrue("Unexpected method call", false);
+ }
+
+ @Override
+ public Object removeAttribute(String name) {
+ assertTrue("Unexpected method call", false);
+ return null;
+ }
+
+ @Override
+ public void clearAllFlowMods() {
+ assertTrue("Unexpected method call", false);
+ }
+
+ @Override
+ public boolean updateBroadcastCache(Long entry, Short port) {
+ assertTrue("Unexpected method call", false);
+ return false;
+ }
+
+ @Override
+ public Map<Short, Long> getPortBroadcastHits() {
+ assertTrue("Unexpected method call", false);
+ return null;
+ }
+
+ @Override
+ public void sendStatsQuery(OFStatisticsRequest request, int xid,
+ IOFMessageListener caller)
+ throws IOException {
+ assertTrue("Unexpected method call", false);
+ }
+
+ @Override
+ public void flush() {
+ assertTrue("Unexpected method call", false);
+ }
+
+ @Override
+ public Future<OFFeaturesReply> getFeaturesReplyFromSwitch()
+ throws IOException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public void deliverOFFeaturesReply(OFMessage reply) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void cancelFeaturesReply(int transactionId) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public int getBuffers() {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+ @Override
+ public int getActions() {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+ @Override
+ public int getCapabilities() {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+ @Override
+ public byte getTables() {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+}
\ No newline at end of file
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/util/OFMessageDamperTest.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/util/OFMessageDamperTest.java
new file mode 100644
index 0000000..8cad756
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/util/OFMessageDamperTest.java
@@ -0,0 +1,150 @@
+package net.floodlightcontroller.util;
+
+import static org.junit.Assert.*;
+
+import net.floodlightcontroller.core.FloodlightContext;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.openflow.protocol.OFEchoRequest;
+import org.openflow.protocol.OFHello;
+import org.openflow.protocol.OFMessage;
+import org.openflow.protocol.OFType;
+import org.openflow.protocol.factory.BasicFactory;
+import org.openflow.protocol.factory.OFMessageFactory;
+
+import java.io.IOException;
+import java.util.EnumSet;
+
+public class OFMessageDamperTest {
+ OFMessageFactory factory;
+ OFMessageDamper damper;
+ FloodlightContext cntx;
+
+ OFMessageDamperMockSwitch sw1;
+ OFMessageDamperMockSwitch sw2;
+
+ OFEchoRequest echoRequst1;
+ OFEchoRequest echoRequst1Clone;
+ OFEchoRequest echoRequst2;
+ OFHello hello1;
+ OFHello hello2;
+
+
+
+ @Before
+ public void setUp() throws IOException {
+ factory = new BasicFactory();
+ cntx = new FloodlightContext();
+
+ sw1 = new OFMessageDamperMockSwitch();
+ sw2 = new OFMessageDamperMockSwitch();
+
+ echoRequst1 = (OFEchoRequest)factory.getMessage(OFType.ECHO_REQUEST);
+ echoRequst1.setPayload(new byte[] { 1 });
+ echoRequst1Clone = (OFEchoRequest)
+ factory.getMessage(OFType.ECHO_REQUEST);
+ echoRequst1Clone.setPayload(new byte[] { 1 });
+ echoRequst2 = (OFEchoRequest)factory.getMessage(OFType.ECHO_REQUEST);
+ echoRequst2.setPayload(new byte[] { 2 });
+
+ hello1 = (OFHello)factory.getMessage(OFType.HELLO);
+ hello1.setXid(1);
+ hello2 = (OFHello)factory.getMessage(OFType.HELLO);
+ hello2.setXid(2);
+
+ }
+
+ protected void doWrite(boolean expectWrite,
+ OFMessageDamperMockSwitch sw,
+ OFMessage msg,
+ FloodlightContext cntx) throws IOException {
+
+ boolean result;
+ sw.reset();
+ result = damper.write(sw, msg, cntx);
+
+ if (expectWrite) {
+ assertEquals(true, result);
+ sw.assertMessageWasWritten(msg, cntx);
+ } else {
+ assertEquals(false, result);
+ sw.assertNoMessageWritten();
+ }
+ }
+
+
+ @Test
+ public void testOneMessageType() throws IOException, InterruptedException {
+ int timeout = 50;
+ int sleepTime = 60;
+ damper = new OFMessageDamper(100,
+ EnumSet.of(OFType.ECHO_REQUEST),
+ timeout);
+
+
+
+ // echo requests should be dampened
+ doWrite(true, sw1, echoRequst1, cntx);
+ doWrite(false, sw1, echoRequst1, cntx);
+ doWrite(false, sw1, echoRequst1Clone, cntx);
+ doWrite(true, sw1, echoRequst2, cntx);
+ doWrite(false, sw1, echoRequst2, cntx);
+
+ // we don't dampen hellos. All should succeed
+ doWrite(true, sw1, hello1, cntx);
+ doWrite(true, sw1, hello1, cntx);
+ doWrite(true, sw1, hello1, cntx);
+
+ // echo request should also be dampened on sw2
+ doWrite(true, sw2, echoRequst1, cntx);
+ doWrite(false, sw2, echoRequst1, cntx);
+ doWrite(true, sw2, echoRequst2, cntx);
+
+
+ Thread.sleep(sleepTime);
+ doWrite(true, sw1, echoRequst1, cntx);
+ doWrite(true, sw2, echoRequst1, cntx);
+
+ }
+
+ @Test
+ public void testTwoMessageTypes() throws IOException, InterruptedException {
+ int timeout = 50;
+ int sleepTime = 60;
+ damper = new OFMessageDamper(100,
+ EnumSet.of(OFType.ECHO_REQUEST,
+ OFType.HELLO),
+ timeout);
+
+
+
+ // echo requests should be dampened
+ doWrite(true, sw1, echoRequst1, cntx);
+ doWrite(false, sw1, echoRequst1, cntx);
+ doWrite(false, sw1, echoRequst1Clone, cntx);
+ doWrite(true, sw1, echoRequst2, cntx);
+ doWrite(false, sw1, echoRequst2, cntx);
+
+ // hello should be dampened as well
+ doWrite(true, sw1, hello1, cntx);
+ doWrite(false, sw1, hello1, cntx);
+ doWrite(false, sw1, hello1, cntx);
+
+ doWrite(true, sw1, hello2, cntx);
+ doWrite(false, sw1, hello2, cntx);
+ doWrite(false, sw1, hello2, cntx);
+
+ // echo request should also be dampened on sw2
+ doWrite(true, sw2, echoRequst1, cntx);
+ doWrite(false, sw2, echoRequst1, cntx);
+ doWrite(true, sw2, echoRequst2, cntx);
+
+ Thread.sleep(sleepTime);
+ doWrite(true, sw1, echoRequst1, cntx);
+ doWrite(true, sw2, echoRequst1, cntx);
+ doWrite(true, sw1, hello1, cntx);
+ doWrite(true, sw1, hello2, cntx);
+ }
+
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/util/TimedCacheTest.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/util/TimedCacheTest.java
new file mode 100644
index 0000000..5e70641
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/util/TimedCacheTest.java
@@ -0,0 +1,90 @@
+package net.floodlightcontroller.util;
+
+import static org.junit.Assert.*;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class TimedCacheTest {
+ public static class CacheEntry {
+ public int key;
+
+ public CacheEntry(int key) {
+ this.key = key;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + key;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ CacheEntry other = (CacheEntry) obj;
+ if (key != other.key)
+ return false;
+ return true;
+ }
+ }
+
+ protected TimedCache<CacheEntry> cache;
+
+ @Before
+ public void setUp() {
+ //
+ }
+
+
+ @Test
+ public void testCaching() throws InterruptedException {
+ int timeout = 50;
+ int timeToSleep = 60;
+ cache = new TimedCache<TimedCacheTest.CacheEntry>(100, timeout);
+
+ CacheEntry e1a = new CacheEntry(1);
+ CacheEntry e1b = new CacheEntry(1);
+ CacheEntry e1c = new CacheEntry(1);
+ CacheEntry e2 = new CacheEntry(2);
+
+ assertEquals(false, cache.update(e1a));
+ assertEquals(true, cache.update(e1a));
+ assertEquals(true, cache.update(e1b));
+ assertEquals(true, cache.update(e1c));
+ assertEquals(false, cache.update(e2));
+ assertEquals(true, cache.update(e2));
+
+ Thread.sleep(timeToSleep);
+ assertEquals(false, cache.update(e1a));
+ assertEquals(false, cache.update(e2));
+ }
+
+ @Test
+ public void testCapacity() throws InterruptedException {
+ int timeout = 5000;
+ cache = new TimedCache<TimedCacheTest.CacheEntry>(2, timeout);
+
+ // Testing the capacity is tricky since the capacity can be
+ // exceeded for short amounts of time, so we try to flood the cache
+ // to make sure the first entry is expired
+ CacheEntry e1 = new CacheEntry(1);
+ for (int i=0; i < 100; i++) {
+ CacheEntry e = new CacheEntry(i);
+ cache.update(e);
+ }
+
+ // entry 1 should have been expired due to capacity limits
+ assertEquals(false, cache.update(e1));
+ }
+
+
+
+}
diff --git a/src/ext/floodlight/src/test/java/net/floodlightcontroller/virtualnetwork/VirtualNetworkFilterTest.java b/src/ext/floodlight/src/test/java/net/floodlightcontroller/virtualnetwork/VirtualNetworkFilterTest.java
new file mode 100644
index 0000000..32afe4e
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/net/floodlightcontroller/virtualnetwork/VirtualNetworkFilterTest.java
@@ -0,0 +1,375 @@
+package net.floodlightcontroller.virtualnetwork;
+
+import static org.easymock.EasyMock.*;
+
+import java.util.List;
+
+import org.easymock.EasyMock;
+import org.junit.Before;
+import org.junit.Test;
+import org.openflow.protocol.OFMatch;
+import org.openflow.protocol.OFPacketIn;
+import org.openflow.protocol.OFType;
+import org.openflow.protocol.OFPacketIn.OFPacketInReason;
+
+import net.floodlightcontroller.core.FloodlightContext;
+import net.floodlightcontroller.core.IFloodlightProviderService;
+import net.floodlightcontroller.core.IOFMessageListener;
+import net.floodlightcontroller.core.IListener.Command;
+import net.floodlightcontroller.core.IOFSwitch;
+import net.floodlightcontroller.core.module.FloodlightModuleContext;
+import net.floodlightcontroller.core.test.MockThreadPoolService;
+import net.floodlightcontroller.core.test.PacketFactory;
+import net.floodlightcontroller.devicemanager.IDeviceService;
+import net.floodlightcontroller.devicemanager.IEntityClassifierService;
+import net.floodlightcontroller.devicemanager.internal.DefaultEntityClassifier;
+import net.floodlightcontroller.devicemanager.test.MockDeviceManager;
+import net.floodlightcontroller.flowcache.FlowReconcileManager;
+import net.floodlightcontroller.flowcache.IFlowReconcileService;
+import net.floodlightcontroller.packet.Data;
+import net.floodlightcontroller.packet.Ethernet;
+import net.floodlightcontroller.packet.IPacket;
+import net.floodlightcontroller.packet.IPv4;
+import net.floodlightcontroller.packet.UDP;
+import net.floodlightcontroller.restserver.IRestApiService;
+import net.floodlightcontroller.restserver.RestApiServer;
+import net.floodlightcontroller.test.FloodlightTestCase;
+import net.floodlightcontroller.threadpool.IThreadPoolService;
+import net.floodlightcontroller.topology.ITopologyService;
+import net.floodlightcontroller.util.MACAddress;
+import net.floodlightcontroller.virtualnetwork.VirtualNetworkFilter;
+
+public class VirtualNetworkFilterTest extends FloodlightTestCase {
+ protected VirtualNetworkFilter vns;
+ protected MockDeviceManager deviceService;
+
+ protected static String guid1 = "guid1";
+ protected static String net1 = "net1";
+ protected static String gw1 = "1.1.1.1";
+ protected static String guid2 = "guid2";
+ protected static String net2 = "net2";
+ protected static String guid3 = "guid3";
+ protected static String net3 = "net3";
+ protected static String gw2 = "2.2.2.2";
+
+ protected static MACAddress mac1 =
+ new MACAddress(Ethernet.toMACAddress("00:11:22:33:44:55"));
+ protected static MACAddress mac2 =
+ new MACAddress(Ethernet.toMACAddress("00:11:22:33:44:66"));
+ protected static MACAddress mac3 =
+ new MACAddress(Ethernet.toMACAddress("00:11:22:33:44:77"));
+ protected static MACAddress mac4 =
+ new MACAddress(Ethernet.toMACAddress("00:11:22:33:44:88"));
+ protected static String hostPort1 = "port1";
+ protected static String hostPort2 = "port2";
+ protected static String hostPort3 = "port3";
+ protected static String hostPort4 = "port4";
+
+ // For testing forwarding behavior
+ protected IOFSwitch sw1;
+ protected FloodlightContext cntx;
+ protected OFPacketIn mac1ToMac2PacketIn;
+ protected IPacket mac1ToMac2PacketIntestPacket;
+ protected byte[] mac1ToMac2PacketIntestPacketSerialized;
+ protected OFPacketIn mac1ToMac4PacketIn;
+ protected IPacket mac1ToMac4PacketIntestPacket;
+ protected byte[] mac1ToMac4PacketIntestPacketSerialized;
+ protected OFPacketIn mac1ToGwPacketIn;
+ protected IPacket mac1ToGwPacketIntestPacket;
+ protected byte[] mac1ToGwPacketIntestPacketSerialized;
+ protected OFPacketIn packetInDHCPDiscoveryRequest;
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+
+ // Module loading stuff
+ FloodlightModuleContext fmc = new FloodlightModuleContext();
+ RestApiServer restApi = new RestApiServer();
+ deviceService = new MockDeviceManager();
+ FlowReconcileManager frm = new FlowReconcileManager();
+ MockThreadPoolService tps = new MockThreadPoolService();
+ ITopologyService topology = createMock(ITopologyService.class);
+ vns = new VirtualNetworkFilter();
+ DefaultEntityClassifier entityClassifier = new DefaultEntityClassifier();
+ fmc.addService(IRestApiService.class, restApi);
+ fmc.addService(IFloodlightProviderService.class, getMockFloodlightProvider());
+ fmc.addService(IDeviceService.class, deviceService);
+ fmc.addService(IFlowReconcileService.class, frm);
+ fmc.addService(IThreadPoolService.class, tps);
+ fmc.addService(IEntityClassifierService.class, entityClassifier);
+ fmc.addService(ITopologyService.class, topology);
+ tps.init(fmc);
+ frm.init(fmc);
+ deviceService.init(fmc);
+ restApi.init(fmc);
+ getMockFloodlightProvider().init(fmc);
+ entityClassifier.init(fmc);
+ tps.startUp(fmc);
+ vns.init(fmc);
+ frm.startUp(fmc);
+ deviceService.startUp(fmc);
+ restApi.startUp(fmc);
+ getMockFloodlightProvider().startUp(fmc);
+ vns.startUp(fmc);
+ entityClassifier.startUp(fmc);
+
+ topology.addListener(deviceService);
+ expectLastCall().times(1);
+ replay(topology);
+ // Mock switches
+ //fastWilcards mocked as this constant
+ int fastWildcards =
+ OFMatch.OFPFW_IN_PORT |
+ OFMatch.OFPFW_NW_PROTO |
+ OFMatch.OFPFW_TP_SRC |
+ OFMatch.OFPFW_TP_DST |
+ OFMatch.OFPFW_NW_SRC_ALL |
+ OFMatch.OFPFW_NW_DST_ALL |
+ OFMatch.OFPFW_NW_TOS;
+ sw1 = EasyMock.createNiceMock(IOFSwitch.class);
+ expect(sw1.getId()).andReturn(1L).anyTimes();
+ expect(sw1.getAttribute(IOFSwitch.PROP_FASTWILDCARDS)).andReturn((Integer)fastWildcards).anyTimes();
+ expect(sw1.hasAttribute(IOFSwitch.PROP_SUPPORTS_OFPP_TABLE)).andReturn(true).anyTimes();
+ replay(sw1);
+
+ // Mock packets
+ // Mock from MAC1 -> MAC2
+ mac1ToMac2PacketIntestPacket = new Ethernet()
+ .setDestinationMACAddress(mac2.toBytes())
+ .setSourceMACAddress(mac1.toBytes())
+ .setEtherType(Ethernet.TYPE_IPv4)
+ .setPayload(
+ new IPv4()
+ .setTtl((byte) 128)
+ .setSourceAddress("192.168.1.1")
+ .setDestinationAddress("192.168.1.2")
+ .setPayload(new UDP()
+ .setSourcePort((short) 5000)
+ .setDestinationPort((short) 5001)
+ .setPayload(new Data(new byte[] {0x01}))));
+ mac1ToMac2PacketIntestPacketSerialized = mac1ToMac2PacketIntestPacket.serialize();
+ mac1ToMac2PacketIn =
+ ((OFPacketIn) mockFloodlightProvider.getOFMessageFactory().
+ getMessage(OFType.PACKET_IN))
+ .setBufferId(-1)
+ .setInPort((short) 1)
+ .setPacketData(mac1ToMac2PacketIntestPacketSerialized)
+ .setReason(OFPacketInReason.NO_MATCH)
+ .setTotalLength((short) mac1ToMac2PacketIntestPacketSerialized.length);
+
+ // Mock from MAC1 -> MAC4
+ mac1ToMac4PacketIntestPacket = new Ethernet()
+ .setDestinationMACAddress(mac4.toBytes())
+ .setSourceMACAddress(mac1.toBytes())
+ .setEtherType(Ethernet.TYPE_IPv4)
+ .setPayload(
+ new IPv4()
+ .setTtl((byte) 128)
+ .setSourceAddress("192.168.1.1")
+ .setDestinationAddress("192.168.1.2")
+ .setPayload(new UDP()
+ .setSourcePort((short) 5000)
+ .setDestinationPort((short) 5001)
+ .setPayload(new Data(new byte[] {0x01}))));
+ mac1ToMac4PacketIntestPacketSerialized = mac1ToMac4PacketIntestPacket.serialize();
+ mac1ToMac4PacketIn =
+ ((OFPacketIn) mockFloodlightProvider.getOFMessageFactory().
+ getMessage(OFType.PACKET_IN))
+ .setBufferId(-1)
+ .setInPort((short) 1)
+ .setPacketData(mac1ToMac4PacketIntestPacketSerialized)
+ .setReason(OFPacketInReason.NO_MATCH)
+ .setTotalLength((short) mac1ToMac4PacketIntestPacketSerialized.length);
+
+ // Mock from MAC1 to gateway1
+ mac1ToGwPacketIntestPacket = new Ethernet()
+ .setDestinationMACAddress("00:11:33:33:44:55") // mac shouldn't matter, can't be other host
+ .setSourceMACAddress(mac1.toBytes())
+ .setEtherType(Ethernet.TYPE_IPv4)
+ .setPayload(
+ new IPv4()
+ .setTtl((byte) 128)
+ .setSourceAddress("192.168.1.1")
+ .setDestinationAddress(gw1)
+ .setPayload(new UDP()
+ .setSourcePort((short) 5000)
+ .setDestinationPort((short) 5001)
+ .setPayload(new Data(new byte[] {0x01}))));
+ mac1ToGwPacketIntestPacketSerialized = mac1ToGwPacketIntestPacket.serialize();
+ mac1ToGwPacketIn =
+ ((OFPacketIn) mockFloodlightProvider.getOFMessageFactory().
+ getMessage(OFType.PACKET_IN))
+ .setBufferId(-1)
+ .setInPort((short) 1)
+ .setPacketData(mac1ToGwPacketIntestPacketSerialized)
+ .setReason(OFPacketInReason.NO_MATCH)
+ .setTotalLength((short) mac1ToGwPacketIntestPacketSerialized.length);
+ }
+
+ @Test
+ public void testCreateNetwork() {
+ // Test creating a network with all parameters
+ vns.createNetwork(guid1, net1, IPv4.toIPv4Address(gw1));
+ assertTrue(vns.gatewayToGuid.get(IPv4.toIPv4Address(gw1)).contains(guid1));
+ assertTrue(vns.nameToGuid.get(net1).equals(guid1));
+ assertTrue(vns.guidToGateway.get(guid1).equals(IPv4.toIPv4Address(gw1)));
+ assertTrue(vns.vNetsByGuid.get(guid1).name.equals(net1));
+ assertTrue(vns.vNetsByGuid.get(guid1).guid.equals(guid1));
+ assertTrue(vns.vNetsByGuid.get(guid1).gateway.equals(gw1));
+ assertTrue(vns.vNetsByGuid.get(guid1).hosts.size()==0);
+
+ // Test creating network without a gateway
+ vns.createNetwork(guid2, net2, null);
+ assertTrue(vns.nameToGuid.get(net2).equals(guid2));
+ assertTrue(vns.guidToGateway.get(guid2) == null);
+ assertTrue(vns.gatewayToGuid.get(IPv4.toIPv4Address(gw1)).size() == 1);
+ assertTrue(vns.vNetsByGuid.get(guid2).name.equals(net2));
+ assertTrue(vns.vNetsByGuid.get(guid2).guid.equals(guid2));
+ assertTrue(vns.vNetsByGuid.get(guid2).gateway == null);
+ assertTrue(vns.vNetsByGuid.get(guid2).hosts.size()==0);
+
+ // Test creating a network that shares the gateway with net1
+ vns.createNetwork(guid3, net3, IPv4.toIPv4Address(gw1));
+ assertTrue(vns.gatewayToGuid.get(IPv4.toIPv4Address(gw1)).contains(guid1));
+ assertTrue(vns.gatewayToGuid.get(IPv4.toIPv4Address(gw1)).contains(guid3));
+ assertTrue(vns.gatewayToGuid.get(IPv4.toIPv4Address(gw1)).size() == 2);
+ assertTrue(vns.nameToGuid.get(net3).equals(guid3));
+ assertTrue(vns.guidToGateway.get(guid3).equals(IPv4.toIPv4Address(gw1)));
+ assertTrue(vns.vNetsByGuid.get(guid3).name.equals(net3));
+ assertTrue(vns.vNetsByGuid.get(guid3).guid.equals(guid3));
+ assertTrue(vns.vNetsByGuid.get(guid3).gateway.equals(gw1));
+ assertTrue(vns.vNetsByGuid.get(guid3).hosts.size()==0);
+
+ }
+
+ @Test
+ public void testModifyNetwork() {
+ // Create some networks
+
+ testCreateNetwork();
+ // Modify net2 to add a gateway
+ vns.createNetwork(guid2, net2, IPv4.toIPv4Address(gw1));
+ assertTrue(vns.nameToGuid.get(net2).equals(guid2));
+ assertTrue(vns.guidToGateway.get(guid2).equals(IPv4.toIPv4Address(gw1)));
+ assertTrue(vns.gatewayToGuid.get(IPv4.toIPv4Address(gw1)).contains(guid1));
+ assertTrue(vns.gatewayToGuid.get(IPv4.toIPv4Address(gw1)).contains(guid2));
+ assertTrue(vns.gatewayToGuid.get(IPv4.toIPv4Address(gw1)).contains(guid3));
+ assertTrue(vns.gatewayToGuid.get(IPv4.toIPv4Address(gw1)).size() == 3);
+ // Modify net2 to change it's name
+ vns.createNetwork(guid2, "newnet2", null);
+ // Make sure the gateway is still there
+ assertTrue(vns.gatewayToGuid.get(IPv4.toIPv4Address(gw1)).contains(guid2));
+ assertTrue(vns.vNetsByGuid.get(guid2).gateway.equals(gw1));
+ // make sure the new name mapping was learned
+ assertTrue(vns.nameToGuid.get("newnet2").equals(guid2));
+ assertTrue(vns.vNetsByGuid.get(guid2).name.equals("newnet2"));
+ // and the old one was deleted
+ assertFalse(vns.nameToGuid.containsKey(net2));
+ }
+
+ @Test
+ public void testDeleteNetwork() {
+ testModifyNetwork();
+ // Delete newnet2
+ vns.deleteNetwork(guid2);
+ assertTrue(vns.gatewayToGuid.get(IPv4.toIPv4Address(gw1)).contains(guid1));
+ assertTrue(vns.gatewayToGuid.get(IPv4.toIPv4Address(gw1)).contains(guid3));
+ assertTrue(vns.gatewayToGuid.get(IPv4.toIPv4Address(gw1)).size() == 2);
+ assertFalse(vns.nameToGuid.containsKey(net2));
+ assertFalse(vns.guidToGateway.containsKey(net2));
+ assertTrue(vns.vNetsByGuid.get(guid2)==null);
+ }
+
+ @Test
+ public void testAddHost() {
+ testModifyNetwork();
+ vns.addHost(mac1, guid1, hostPort1);
+ assertTrue(vns.macToGuid.get(mac1).equals(guid1));
+ assertTrue(vns.portToMac.get(hostPort1).equals(mac1));
+ assertTrue(vns.vNetsByGuid.get(guid1).hosts.contains(mac1));
+ vns.addHost(mac2, guid1, hostPort2);
+ assertTrue(vns.macToGuid.get(mac2).equals(guid1));
+ assertTrue(vns.portToMac.get(hostPort2).equals(mac2));
+ assertTrue(vns.vNetsByGuid.get(guid1).hosts.contains(mac2));
+ vns.addHost(mac3, guid3, hostPort3);
+ vns.addHost(mac4, guid3, hostPort4);
+ assertTrue(vns.vNetsByGuid.get(guid3).hosts.contains(mac4));
+ }
+
+ @Test
+ public void testDeleteHost() {
+ testAddHost();
+
+ String host1Guid = vns.macToGuid.get(mac1);
+ vns.deleteHost(mac1, null);
+ assertFalse(vns.macToGuid.containsKey(mac1));
+ assertFalse(vns.portToMac.containsKey(hostPort1));
+ assertFalse(vns.vNetsByGuid.get(host1Guid).hosts.contains(mac1));
+
+ String host2Guid = vns.macToGuid.get(vns.portToMac.get(hostPort2));
+ vns.deleteHost(null, hostPort2);
+ assertFalse(vns.macToGuid.containsKey(mac2));
+ assertFalse(vns.portToMac.containsKey(hostPort2));
+ assertFalse(vns.vNetsByGuid.get(host2Guid).hosts.contains(mac2));
+
+ String host3Guid = vns.macToGuid.get(mac3);
+ vns.deleteHost(mac3, hostPort3);
+ assertFalse(vns.macToGuid.containsKey(mac3));
+ assertFalse(vns.portToMac.containsKey(hostPort3));
+ assertFalse(vns.vNetsByGuid.get(host3Guid).hosts.contains(mac3));
+
+ }
+
+ @Test
+ public void testForwarding() {
+ testAddHost();
+ // make sure mac1 can communicate with mac2
+ IOFMessageListener listener = getVirtualNetworkListener();
+ cntx = new FloodlightContext();
+ IFloodlightProviderService.bcStore.put(cntx,
+ IFloodlightProviderService.CONTEXT_PI_PAYLOAD,
+ (Ethernet)mac1ToMac2PacketIntestPacket);
+ Command ret = listener.receive(sw1, mac1ToMac2PacketIn, cntx);
+ assertTrue(ret == Command.CONTINUE);
+ // make sure mac1 can't communicate with mac4
+ cntx = new FloodlightContext();
+ IFloodlightProviderService.bcStore.put(cntx,
+ IFloodlightProviderService.CONTEXT_PI_PAYLOAD,
+ (Ethernet)mac1ToMac4PacketIntestPacket);
+ ret = listener.receive(sw1, mac1ToMac4PacketIn, cntx);
+ assertTrue(ret == Command.STOP);
+ }
+
+ @Test
+ public void testDefaultGateway() {
+ testAddHost();
+ IOFMessageListener listener = getVirtualNetworkListener();
+ cntx = new FloodlightContext();
+ IFloodlightProviderService.bcStore.put(cntx,
+ IFloodlightProviderService.CONTEXT_PI_PAYLOAD,
+ (Ethernet)mac1ToGwPacketIntestPacket);
+ deviceService.learnEntity(((Ethernet)mac1ToGwPacketIntestPacket).getDestinationMAC().toLong(),
+ null, IPv4.toIPv4Address(gw1), null, null);
+ Command ret = listener.receive(sw1, mac1ToGwPacketIn, cntx);
+ assertTrue(ret == Command.CONTINUE);
+ }
+
+ @Test
+ public void testDhcp() {
+ IOFMessageListener listener = getVirtualNetworkListener();
+ Ethernet dhcpPacket = PacketFactory.DhcpDiscoveryRequestEthernet(mac1);
+ OFPacketIn dhcpPacketOf = PacketFactory.DhcpDiscoveryRequestOFPacketIn(mac1);
+ cntx = new FloodlightContext();
+ IFloodlightProviderService.bcStore.put(cntx,
+ IFloodlightProviderService.CONTEXT_PI_PAYLOAD,
+ dhcpPacket);
+ Command ret = listener.receive(sw1, dhcpPacketOf, cntx);
+ assertTrue(ret == Command.CONTINUE);
+ }
+
+ protected IOFMessageListener getVirtualNetworkListener() {
+ List<IOFMessageListener> listeners = mockFloodlightProvider.getListeners().get(OFType.PACKET_IN);
+ return listeners.get(listeners.indexOf(vns));
+ }
+}
diff --git a/src/ext/floodlight/src/test/java/org/openflow/protocol/BasicFactoryTest.java b/src/ext/floodlight/src/test/java/org/openflow/protocol/BasicFactoryTest.java
new file mode 100644
index 0000000..6825008
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/org/openflow/protocol/BasicFactoryTest.java
@@ -0,0 +1,81 @@
+/**
+* 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 org.jboss.netty.buffer.ChannelBuffer;
+import org.jboss.netty.buffer.ChannelBuffers;
+import org.openflow.protocol.factory.BasicFactory;
+import org.openflow.protocol.factory.MessageParseException;
+import org.openflow.util.U16;
+
+import junit.framework.TestCase;
+
+public class BasicFactoryTest extends TestCase {
+
+ public void testCreateAndParse() throws MessageParseException {
+ BasicFactory factory = new BasicFactory();
+ 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 = new BasicFactory();
+ 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 = new BasicFactory();
+ 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());
+ }
+ }
+
+}
diff --git a/src/ext/floodlight/src/test/java/org/openflow/protocol/OFActionTypeTest.java b/src/ext/floodlight/src/test/java/org/openflow/protocol/OFActionTypeTest.java
new file mode 100644
index 0000000..ed8386c
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/org/openflow/protocol/OFActionTypeTest.java
@@ -0,0 +1,37 @@
+/**
+* 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/ext/floodlight/src/test/java/org/openflow/protocol/OFBarrierReplyTest.java b/src/ext/floodlight/src/test/java/org/openflow/protocol/OFBarrierReplyTest.java
new file mode 100644
index 0000000..7e447bb
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/org/openflow/protocol/OFBarrierReplyTest.java
@@ -0,0 +1,36 @@
+/**
+* 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/ext/floodlight/src/test/java/org/openflow/protocol/OFBarrierRequestTest.java b/src/ext/floodlight/src/test/java/org/openflow/protocol/OFBarrierRequestTest.java
new file mode 100644
index 0000000..3aa9cb4
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/org/openflow/protocol/OFBarrierRequestTest.java
@@ -0,0 +1,36 @@
+/**
+* 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/ext/floodlight/src/test/java/org/openflow/protocol/OFErrorTest.java b/src/ext/floodlight/src/test/java/org/openflow/protocol/OFErrorTest.java
new file mode 100644
index 0000000..45d5257
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/org/openflow/protocol/OFErrorTest.java
@@ -0,0 +1,88 @@
+/**
+* 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((short) 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((short) 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((short) 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 = new BasicFactory();
+ 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/ext/floodlight/src/test/java/org/openflow/protocol/OFFeaturesReplyTest.java b/src/ext/floodlight/src/test/java/org/openflow/protocol/OFFeaturesReplyTest.java
new file mode 100644
index 0000000..62e491a
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/org/openflow/protocol/OFFeaturesReplyTest.java
@@ -0,0 +1,62 @@
+/**
+* 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/ext/floodlight/src/test/java/org/openflow/protocol/OFFlowRemovedTest.java b/src/ext/floodlight/src/test/java/org/openflow/protocol/OFFlowRemovedTest.java
new file mode 100644
index 0000000..11746e7
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/org/openflow/protocol/OFFlowRemovedTest.java
@@ -0,0 +1,43 @@
+/**
+* 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/ext/floodlight/src/test/java/org/openflow/protocol/OFGetConfigReplyTest.java b/src/ext/floodlight/src/test/java/org/openflow/protocol/OFGetConfigReplyTest.java
new file mode 100644
index 0000000..c1f1f67
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/org/openflow/protocol/OFGetConfigReplyTest.java
@@ -0,0 +1,38 @@
+/**
+* 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/ext/floodlight/src/test/java/org/openflow/protocol/OFGetConfigRequestTest.java b/src/ext/floodlight/src/test/java/org/openflow/protocol/OFGetConfigRequestTest.java
new file mode 100644
index 0000000..94d9036
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/org/openflow/protocol/OFGetConfigRequestTest.java
@@ -0,0 +1,36 @@
+/**
+* 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/ext/floodlight/src/test/java/org/openflow/protocol/OFMatchTest.java b/src/ext/floodlight/src/test/java/org/openflow/protocol/OFMatchTest.java
new file mode 100644
index 0000000..fd7863a
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/org/openflow/protocol/OFMatchTest.java
@@ -0,0 +1,92 @@
+/**
+* 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/ext/floodlight/src/test/java/org/openflow/protocol/OFMessageContextStoreTest.java b/src/ext/floodlight/src/test/java/org/openflow/protocol/OFMessageContextStoreTest.java
new file mode 100644
index 0000000..60a9e73
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/org/openflow/protocol/OFMessageContextStoreTest.java
@@ -0,0 +1,31 @@
+/**
+* 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/ext/floodlight/src/test/java/org/openflow/protocol/OFPortConfigTest.java b/src/ext/floodlight/src/test/java/org/openflow/protocol/OFPortConfigTest.java
new file mode 100644
index 0000000..9b115eb
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/org/openflow/protocol/OFPortConfigTest.java
@@ -0,0 +1,39 @@
+/**
+* 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/ext/floodlight/src/test/java/org/openflow/protocol/OFPortStatusTest.java b/src/ext/floodlight/src/test/java/org/openflow/protocol/OFPortStatusTest.java
new file mode 100644
index 0000000..4fab64e
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/org/openflow/protocol/OFPortStatusTest.java
@@ -0,0 +1,44 @@
+/**
+* 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/ext/floodlight/src/test/java/org/openflow/protocol/OFSetConfigTest.java b/src/ext/floodlight/src/test/java/org/openflow/protocol/OFSetConfigTest.java
new file mode 100644
index 0000000..2a9e86f
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/org/openflow/protocol/OFSetConfigTest.java
@@ -0,0 +1,38 @@
+/**
+* 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/ext/floodlight/src/test/java/org/openflow/protocol/OFStatisticsReplyTest.java b/src/ext/floodlight/src/test/java/org/openflow/protocol/OFStatisticsReplyTest.java
new file mode 100644
index 0000000..50bac8f
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/org/openflow/protocol/OFStatisticsReplyTest.java
@@ -0,0 +1,77 @@
+/**
+* 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 = new BasicFactory();
+ 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/ext/floodlight/src/test/java/org/openflow/protocol/OFStatisticsRequestTest.java b/src/ext/floodlight/src/test/java/org/openflow/protocol/OFStatisticsRequestTest.java
new file mode 100644
index 0000000..74af6f4
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/org/openflow/protocol/OFStatisticsRequestTest.java
@@ -0,0 +1,79 @@
+/**
+* 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 = new BasicFactory();
+ 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 = new BasicFactory();
+ 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/ext/floodlight/src/test/java/org/openflow/protocol/OFStatisticsTypeTest.java b/src/ext/floodlight/src/test/java/org/openflow/protocol/OFStatisticsTypeTest.java
new file mode 100644
index 0000000..d44ef7f
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/org/openflow/protocol/OFStatisticsTypeTest.java
@@ -0,0 +1,37 @@
+/**
+* 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/ext/floodlight/src/test/java/org/openflow/protocol/OFTypeTest.java b/src/ext/floodlight/src/test/java/org/openflow/protocol/OFTypeTest.java
new file mode 100644
index 0000000..c6bf0a3
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/org/openflow/protocol/OFTypeTest.java
@@ -0,0 +1,39 @@
+/**
+* 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/ext/floodlight/src/test/java/org/openflow/protocol/OFVendorTest.java b/src/ext/floodlight/src/test/java/org/openflow/protocol/OFVendorTest.java
new file mode 100644
index 0000000..b85a915
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/org/openflow/protocol/OFVendorTest.java
@@ -0,0 +1,215 @@
+/**
+* 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;
+
+ public int getLength() {
+ return 4;
+ }
+
+ public void readFrom(ChannelBuffer data, int length) {
+ dataType = data.readInt();
+ }
+
+ 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;
+ }
+
+ public int getLength() {
+ return 8;
+ }
+
+ public void readFrom(ChannelBuffer data, int length) {
+ super.readFrom(data, length);
+ flags = data.readShort();
+ value = data.readShort();
+
+ }
+ public void writeTo(ChannelBuffer data) {
+ super.writeTo(data);
+ data.writeShort(flags);
+ data.writeShort(value);
+ }
+
+ public static Instantiable<OFVendorData> getInstantiable() {
+ return new Instantiable<OFVendorData>() {
+ 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;
+ }
+
+ public int getLength() {
+ return 12;
+ }
+
+ public void readFrom(ChannelBuffer data, int length) {
+ super.readFrom(data, length);
+ type = data.readShort();
+ subtype = data.readShort();
+
+ }
+ public void writeTo(ChannelBuffer data) {
+ super.writeTo(data);
+ data.writeShort(type);
+ data.writeShort(subtype);
+ }
+
+ public static Instantiable<OFVendorData> getInstantiable() {
+ return new Instantiable<OFVendorData>() {
+ 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(new BasicFactory());
+ 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/ext/floodlight/src/test/java/org/openflow/util/HexStringTest.java b/src/ext/floodlight/src/test/java/org/openflow/util/HexStringTest.java
new file mode 100644
index 0000000..a8f8ba4
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/org/openflow/util/HexStringTest.java
@@ -0,0 +1,88 @@
+/**
+* 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/ext/floodlight/src/test/java/org/openflow/util/OFTestCase.java b/src/ext/floodlight/src/test/java/org/openflow/util/OFTestCase.java
new file mode 100644
index 0000000..5132c2c
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/org/openflow/util/OFTestCase.java
@@ -0,0 +1,36 @@
+/**
+* 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 = new BasicFactory();
+ }
+
+ public void test() throws Exception {
+ }
+}
diff --git a/src/ext/floodlight/src/test/java/org/openflow/util/U16Test.java b/src/ext/floodlight/src/test/java/org/openflow/util/U16Test.java
new file mode 100644
index 0000000..ba87e7b
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/org/openflow/util/U16Test.java
@@ -0,0 +1,33 @@
+/**
+* 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/ext/floodlight/src/test/java/org/openflow/util/U32Test.java b/src/ext/floodlight/src/test/java/org/openflow/util/U32Test.java
new file mode 100644
index 0000000..223c103
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/org/openflow/util/U32Test.java
@@ -0,0 +1,32 @@
+/**
+* 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/ext/floodlight/src/test/java/org/openflow/util/U64Test.java b/src/ext/floodlight/src/test/java/org/openflow/util/U64Test.java
new file mode 100644
index 0000000..0a97e30
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/org/openflow/util/U64Test.java
@@ -0,0 +1,34 @@
+/**
+* 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/ext/floodlight/src/test/java/org/openflow/util/U8Test.java b/src/ext/floodlight/src/test/java/org/openflow/util/U8Test.java
new file mode 100644
index 0000000..2c06c4d
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/org/openflow/util/U8Test.java
@@ -0,0 +1,32 @@
+/**
+* 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/ext/floodlight/src/test/java/org/openflow/util/UnsignedTest.java b/src/ext/floodlight/src/test/java/org/openflow/util/UnsignedTest.java
new file mode 100644
index 0000000..7cdf739
--- /dev/null
+++ b/src/ext/floodlight/src/test/java/org/openflow/util/UnsignedTest.java
@@ -0,0 +1,83 @@
+/**
+* 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);
+ }
+ }
+}