blob: 68c5df53c940e19813648b3e5b0c087a8d4de925 [file] [log] [blame]
Umesh Krishnaswamy345ee992012-12-13 20:29:48 -08001#! /usr/bin/python
2"""
3circuitpusher utilizes floodlight rest APIs to create a bidirectional circuit,
4i.e., permanent flow entry, on all switches in route between two devices based
5on IP addresses with specified priority.
6
7Notes:
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
29import os
30import sys
31import subprocess
32import json
33import argparse
34import io
35import 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
42parser = argparse.ArgumentParser(description='Circuit Pusher')
43parser.add_argument('--controller', dest='controllerRestIp', action='store', default='localhost:8080', help='controller IP:RESTport, e.g., localhost:8080 or A.B.C.D:8080')
44parser.add_argument('--add', dest='action', action='store_const', const='add', default='add', help='action: add, delete')
45parser.add_argument('--delete', dest='action', action='store_const', const='delete', default='add', help='action: add, delete')
46parser.add_argument('--type', dest='type', action='store', default='ip', help='valid types: ip')
47parser.add_argument('--src', dest='srcAddress', action='store', default='0.0.0.0', help='source address: if type=ip, A.B.C.D')
48parser.add_argument('--dst', dest='dstAddress', action='store', default='0.0.0.0', help='destination address: if type=ip, A.B.C.D')
49parser.add_argument('--name', dest='circuitName', action='store', default='circuit-1', help='name for circuit, e.g., circuit-1')
50
51args = parser.parse_args()
52print args
53
54controllerRestIp = args.controllerRestIp
55
56# first check if a local file exists, which needs to be updated after add/delete
57if os.path.exists('./circuits.json'):
58 circuitDb = open('./circuits.json','r')
59 lines = circuitDb.readlines()
60 circuitDb.close()
61else:
62 lines={}
63
64if 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
158elif 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