diff --git a/apps/circuitpusher/circuitpusher.py b/apps/circuitpusher/circuitpusher.py new file mode 100755 index 0000000000000000000000000000000000000000..68c5df53c940e19813648b3e5b0c087a8d4de925 --- /dev/null +++ b/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/main/java/net/floodlightcontroller/topology/web/RouteResource.java b/src/main/java/net/floodlightcontroller/topology/web/RouteResource.java new file mode 100644 index 0000000000000000000000000000000000000000..70e406fcc25946a4c1275c2e351c6c4513838932 --- /dev/null +++ b/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/main/java/net/floodlightcontroller/topology/web/TopologyWebRoutable.java b/src/main/java/net/floodlightcontroller/topology/web/TopologyWebRoutable.java index 7335f261d95ef8f037d8ab8612816bd6a8a8a296..1c15d8a65dde9c7b8e678e0c2325fbb0ffc43135 100644 --- a/src/main/java/net/floodlightcontroller/topology/web/TopologyWebRoutable.java +++ b/src/main/java/net/floodlightcontroller/topology/web/TopologyWebRoutable.java @@ -18,6 +18,7 @@ public class TopologyWebRoutable implements RestletRoutable { router.attach("/switchclusters/json", SwitchClustersResource.class); router.attach("/broadcastdomainports/json", BroadcastDomainResource.class); router.attach("/blockedports/json", BlockedPortsResource.class); + router.attach("/route/{src-dpid}/{src-port}/{dst-dpid}/{dst-port}/json", RouteResource.class); return router; }