Umesh Krishnaswamy | 345ee99 | 2012-12-13 20:29:48 -0800 | [diff] [blame] | 1 | #! /usr/bin/python |
| 2 | """ |
| 3 | circuitpusher utilizes floodlight rest APIs to create a bidirectional circuit, |
| 4 | i.e., permanent flow entry, on all switches in route between two devices based |
| 5 | on IP addresses with specified priority. |
| 6 | |
| 7 | Notes: |
| 8 | 1. The circuit pusher currently only creates circuit with two IP end points |
| 9 | 2. Prior to sending restAPI requests to the circuit pusher, the specified end |
| 10 | points must already been known to the controller (i.e., already have sent |
| 11 | packets on the network, easy way to assure this is to do a ping (to any |
| 12 | target) from the two hosts. |
| 13 | 3. The current supported command syntax format is: |
| 14 | a) circuitpusher.py --controller={IP}:{rest port} --type ip --src {IP} --dst {IP} --add --name {circuit-name} |
| 15 | |
| 16 | adds a new circuit between src and dst devices Currently ip circuit is supported. ARP is automatically supported. |
| 17 | |
| 18 | Currently a simple circuit record storage is provided in a text file circuits.json in the working directory. |
| 19 | The file is not protected and does not clean itself between controller restarts. The file is needed for correct operation |
| 20 | and the user should make sure deleting the file when floodlight controller is restarted. |
| 21 | |
| 22 | b) circuitpusher.py --controller={IP}:{rest port} --delete --name {circuit-name} |
| 23 | |
| 24 | deletes a created circuit (as recorded in circuits.json) using the previously given name |
| 25 | |
| 26 | @author kcwang |
| 27 | """ |
| 28 | |
| 29 | import os |
| 30 | import sys |
| 31 | import subprocess |
| 32 | import json |
| 33 | import argparse |
| 34 | import io |
| 35 | import time |
| 36 | |
| 37 | # parse circuit options. Currently supports add and delete actions. |
| 38 | # Syntax: |
| 39 | # circuitpusher --controller {IP:REST_PORT} --add --name {CIRCUIT_NAME} --type ip --src {IP} --dst {IP} |
| 40 | # circuitpusher --controller {IP:REST_PORT} --delete --name {CIRCUIT_NAME} |
| 41 | |
| 42 | parser = argparse.ArgumentParser(description='Circuit Pusher') |
| 43 | 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') |
| 44 | parser.add_argument('--add', dest='action', action='store_const', const='add', default='add', help='action: add, delete') |
| 45 | parser.add_argument('--delete', dest='action', action='store_const', const='delete', default='add', help='action: add, delete') |
| 46 | parser.add_argument('--type', dest='type', action='store', default='ip', help='valid types: ip') |
| 47 | parser.add_argument('--src', dest='srcAddress', action='store', default='0.0.0.0', help='source address: if type=ip, A.B.C.D') |
| 48 | parser.add_argument('--dst', dest='dstAddress', action='store', default='0.0.0.0', help='destination address: if type=ip, A.B.C.D') |
| 49 | parser.add_argument('--name', dest='circuitName', action='store', default='circuit-1', help='name for circuit, e.g., circuit-1') |
| 50 | |
| 51 | args = parser.parse_args() |
| 52 | print args |
| 53 | |
| 54 | controllerRestIp = args.controllerRestIp |
| 55 | |
| 56 | # first check if a local file exists, which needs to be updated after add/delete |
| 57 | if os.path.exists('./circuits.json'): |
| 58 | circuitDb = open('./circuits.json','r') |
| 59 | lines = circuitDb.readlines() |
| 60 | circuitDb.close() |
| 61 | else: |
| 62 | lines={} |
| 63 | |
| 64 | if args.action=='add': |
| 65 | |
| 66 | circuitDb = open('./circuits.json','a') |
| 67 | |
| 68 | for line in lines: |
| 69 | data = json.loads(line) |
| 70 | if data['name']==(args.circuitName): |
| 71 | print "Circuit %s exists already. Use new name to create." % args.circuitName |
| 72 | sys.exit() |
| 73 | else: |
| 74 | circuitExists = False |
| 75 | |
| 76 | # retrieve source and destination device attachment points |
| 77 | # using DeviceManager rest API |
| 78 | |
| 79 | command = "curl -s http://%s/wm/device/?ipv4=%s" % (args.controllerRestIp, args.srcAddress) |
| 80 | result = os.popen(command).read() |
| 81 | parsedResult = json.loads(result) |
| 82 | print command+"\n" |
| 83 | sourceSwitch = parsedResult[0]['attachmentPoint'][0]['switchDPID'] |
| 84 | sourcePort = parsedResult[0]['attachmentPoint'][0]['port'] |
| 85 | |
| 86 | command = "curl -s http://%s/wm/device/?ipv4=%s" % (args.controllerRestIp, args.dstAddress) |
| 87 | result = os.popen(command).read() |
| 88 | parsedResult = json.loads(result) |
| 89 | print command+"\n" |
| 90 | destSwitch = parsedResult[0]['attachmentPoint'][0]['switchDPID'] |
| 91 | destPort = parsedResult[0]['attachmentPoint'][0]['port'] |
| 92 | |
| 93 | print "Creating circuit:" |
| 94 | print "from source device at switch %s port %s" % (sourceSwitch,sourcePort) |
| 95 | print "to destination device at switch %s port %s"% (destSwitch,destPort) |
| 96 | |
| 97 | # retrieving route from source to destination |
| 98 | # using Routing rest API |
| 99 | |
| 100 | command = "curl -s http://%s/wm/topology/route/%s/%s/%s/%s/json" % (controllerRestIp, sourceSwitch, sourcePort, destSwitch, destPort) |
| 101 | |
| 102 | result = os.popen(command).read() |
| 103 | parsedResult = json.loads(result) |
| 104 | |
| 105 | print command+"\n" |
| 106 | print result+"\n" |
| 107 | |
| 108 | for i in range(len(parsedResult)): |
| 109 | if i % 2 == 0: |
| 110 | ap1Dpid = parsedResult[i]['switch'] |
| 111 | ap1Port = parsedResult[i]['port'] |
| 112 | print ap1Dpid, ap1Port |
| 113 | |
| 114 | else: |
| 115 | ap2Dpid = parsedResult[i]['switch'] |
| 116 | ap2Port = parsedResult[i]['port'] |
| 117 | print ap2Dpid, ap2Port |
| 118 | |
| 119 | # send one flow mod per pair of APs in route |
| 120 | # using StaticFlowPusher rest API |
| 121 | |
| 122 | # IMPORTANT NOTE: current Floodlight StaticflowEntryPusher |
| 123 | # assumes all flow entries to have unique name across all switches |
| 124 | # this will most possibly be relaxed later, but for now we |
| 125 | # encode each flow entry's name with both switch dpid, user |
| 126 | # specified name, and flow type (f: forward, r: reverse, farp/rarp: arp) |
| 127 | |
| 128 | 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) |
| 129 | result = os.popen(command).read() |
| 130 | print command |
| 131 | |
| 132 | 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) |
| 133 | result = os.popen(command).read() |
| 134 | print command |
| 135 | |
| 136 | |
| 137 | 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) |
| 138 | result = os.popen(command).read() |
| 139 | print command |
| 140 | |
| 141 | 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) |
| 142 | result = os.popen(command).read() |
| 143 | print command |
| 144 | |
| 145 | # store created circuit attributes in local ./circuits.json |
| 146 | datetime = time.asctime() |
| 147 | circuitParams = {'name':args.circuitName, 'Dpid':ap1Dpid, 'inPort':ap1Port, 'outPort':ap2Port, 'datetime':datetime} |
| 148 | str = json.dumps(circuitParams) |
| 149 | circuitDb.write(str+"\n") |
| 150 | |
| 151 | # confirm successful circuit creation |
| 152 | # using controller rest API |
| 153 | |
| 154 | command="curl -s http://%s/wm/core/switch/all/flow/json| python -mjson.tool" % (controllerRestIp) |
| 155 | result = os.popen(command).read() |
| 156 | print command + "\n" + result |
| 157 | |
| 158 | elif args.action=='delete': |
| 159 | |
| 160 | circuitDb = open('./circuits.json','w') |
| 161 | |
| 162 | # removing previously created flow from switches |
| 163 | # using StaticFlowPusher rest API |
| 164 | # currently, circuitpusher records created circuits in local file ./circuits.db |
| 165 | # with circuit name and list of switches |
| 166 | |
| 167 | circuitExists = False |
| 168 | |
| 169 | for line in lines: |
| 170 | data = json.loads(line) |
| 171 | if data['name']==(args.circuitName): |
| 172 | circuitExists = True |
| 173 | |
| 174 | sw = data['Dpid'] |
| 175 | print data, sw |
| 176 | |
| 177 | command = "curl -X DELETE -d '{\"name\":\"%s\", \"switch\":\"%s\"}' http://%s/wm/staticflowentrypusher/json" % (sw+"."+args.circuitName+".f", sw, controllerRestIp) |
| 178 | result = os.popen(command).read() |
| 179 | print command, result |
| 180 | |
| 181 | command = "curl -X DELETE -d '{\"name\":\"%s\", \"switch\":\"%s\"}' http://%s/wm/staticflowentrypusher/json" % (sw+"."+args.circuitName+".farp", sw, controllerRestIp) |
| 182 | result = os.popen(command).read() |
| 183 | print command, result |
| 184 | |
| 185 | command = "curl -X DELETE -d '{\"name\":\"%s\", \"switch\":\"%s\"}' http://%s/wm/staticflowentrypusher/json" % (sw+"."+args.circuitName+".r", sw, controllerRestIp) |
| 186 | result = os.popen(command).read() |
| 187 | print command, result |
| 188 | |
| 189 | command = "curl -X DELETE -d '{\"name\":\"%s\", \"switch\":\"%s\"}' http://%s/wm/staticflowentrypusher/json" % (sw+"."+args.circuitName+".rarp", sw, controllerRestIp) |
| 190 | result = os.popen(command).read() |
| 191 | print command, result |
| 192 | |
| 193 | else: |
| 194 | circuitDb.write(line) |
| 195 | |
| 196 | circuitDb.close() |
| 197 | |
| 198 | if not circuitExists: |
| 199 | print "specified circuit does not exist" |
| 200 | sys.exit() |
| 201 | |