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() +