1 __author__ = "Jan Medved"
2 __copyright__ = "Copyright(c) 2014, Cisco Systems, Inc."
3 __license__ = "New-style BSD"
4 __email__ = "jmedved@cisco.com"
6 from random import randrange
14 class Counter(object):
15 def __init__(self, start=0):
16 self.lock = threading.Lock()
18 def increment(self, value=1):
21 self.value = self.value + value
27 def __init__(self, verbose=False):
28 self.verbose = verbose
31 self.start = time.time()
34 def __exit__(self, *args):
35 self.end = time.time()
36 self.secs = self.end - self.start
37 self.msecs = self.secs * 1000 # millisecs
39 print ("elapsed time: %f ms" % self.msecs)
42 putheaders = {'content-type': 'application/json'}
43 getheaders = {'Accept': 'application/json'}
45 # We fist delete all existing service functions
46 DELURL = "restconf/config/opendaylight-inventory:nodes/node/openflow:%d/table/0/flow/%d"
47 GETURL = "restconf/config/opendaylight-inventory:nodes/node/openflow:%d/table/0/flow/%d"
48 # Incremental PUT. This URL is for a list element
49 PUTURL = "restconf/config/opendaylight-inventory:nodes/node/openflow:%d/table/0/flow/%d"
51 INVURL = 'restconf/operational/opendaylight-inventory:nodes'
52 N1T0_URL = 'restconf/operational/opendaylight-inventory:nodes/node/openflow:1/table/0'
55 print_lock = threading.Lock()
59 "flow-node-inventory:flow": [
61 "flow-node-inventory:cookie": %d,
62 "flow-node-inventory:cookie_mask": 65535,
63 "flow-node-inventory:flow-name": "%s",
64 "flow-node-inventory:hard-timeout": %d,
65 "flow-node-inventory:id": "%s",
66 "flow-node-inventory:idle-timeout": %d,
67 "flow-node-inventory:installHw": false,
68 "flow-node-inventory:instructions": {
69 "flow-node-inventory:instruction": [
71 "flow-node-inventory:apply-actions": {
72 "flow-node-inventory:action": [
74 "flow-node-inventory:dec-nw-ttl": {},
75 "flow-node-inventory:order": 0
79 "flow-node-inventory:order": 0
83 "flow-node-inventory:match": {
84 "flow-node-inventory:metadata": {
85 "flow-node-inventory:metadata": %d
88 "flow-node-inventory:priority": 2,
89 "flow-node-inventory:strict": false,
90 "flow-node-inventory:table_id": 0
95 add_ok_rate = Counter(0.0)
96 add_total_rate = Counter(0.0)
97 del_ok_rate = Counter(0.0)
98 del_total_rate = Counter(0.0)
102 def add_flow(session, url_template, res, tid, node, flow_id, metadata):
103 flow_data = JSON_FLOW_MOD1 % (tid + flow_id, 'TestFlow-%d' % flow_id, 65000,
104 str(flow_id), 65000, metadata)
105 flow_url = url_template % (node, flow_id)
106 r = session.put(flow_url, data=flow_data, headers=putheaders, stream=False )
109 res[r.status_code] += 1
111 res[r.status_code] = 1
114 def delete_flow(session, url_template, res, tid, node, flow_id):
115 flow_url = url_template % (node, flow_id)
116 r = session.delete(flow_url, headers=getheaders)
118 res[r.status_code] += 1
120 res[r.status_code] = 1
123 def get_num_nodes(session, inventory_url, default_nodes):
125 Determines the number of OF nodes in the connected mininet network. If
126 mininet is not connected, the default number of flows is 16
128 nodes = default_nodes
129 r = session.get(inventory_url, headers=getheaders, stream=False )
130 if (r.status_code == 200):
132 inv = json.loads(r.content)['nodes']['node']
134 for n in range(len(inv)):
135 if re.search('openflow', inv[n]['id']) != None:
144 def add_flows(put_url, nnodes, nflows, start_flow, tid, cond):
146 The function that add flows into the ODL config space.
153 s = requests.Session()
155 nnodes = get_num_nodes(s, inv_url, nnodes)
158 print ' Thread %d:\n Adding %d flows on %d nodes' % (tid, nflows, nnodes)
161 for flow in range(nflows):
162 node_id = randrange(1, nnodes+1)
163 flow_id = tid*100000 + flow + start_flow
164 flows[tid][flow_id] = node_id
165 add_flow(s, put_url, add_res, tid, node_id, flow_id, flow*2+1)
168 add_ok_rate_t = add_res[200]/add_time
169 add_total_rate_t = sum(add_res.values())/add_time
171 add_ok_rate.increment(add_ok_rate_t)
172 add_total_rate.increment(add_total_rate_t)
175 print ' Thread %d: ' % tid
176 print ' Add time: %.2f,' % add_time
177 print ' Add success rate: %.2f, Add total rate: %.2f' % \
178 (add_ok_rate_t, add_total_rate_t)
179 print ' Add Results: ',
181 threads_done = threads_done + 1
189 def delete_flows(del_url, nnodes, nflows, start_flow, tid, cond):
191 The function that deletes flow from the ODL config space that have been
192 added using the 'add_flows()' function.
199 s = requests.Session()
200 nnodes = get_num_nodes(s, inv_url, nnodes)
203 print 'Thread %d: Deleting %d flows on %d nodes' % (tid, nflows, nnodes)
206 for flow in range(nflows):
207 flow_id = tid*100000 + flow + start_flow
208 delete_flow(s, del_url, del_res, 100, flows[tid][flow_id], flow_id)
212 del_ok_rate_t = del_res[200]/del_time
213 del_total_rate_t = sum(del_res.values())/del_time
215 del_ok_rate.increment(del_ok_rate_t)
216 del_total_rate.increment(del_total_rate_t)
219 print ' Thread %d: ' % tid
220 print ' Delete time: %.2f,' % del_time
221 print ' Delete success rate: %.2f, Delete total rate: %.2f' % \
222 (del_ok_rate_t, del_total_rate_t)
223 print ' Delete Results: ',
225 threads_done = threads_done + 1
233 def driver(function, ncycles, nthreads, nnodes, nflows, url, cond, ok_rate, total_rate):
235 The top-level driver function that drives the execution of the flow-add and
240 for c in range(ncycles):
242 print '\nCycle %d:' % c
245 for i in range(nthreads):
246 t = threading.Thread(target=function,
247 args=(url, nnodes, nflows, c*nflows, i, cond))
251 # Wait for all threads to finish
252 while threads_done < in_args.nthreads:
257 print ' Overall success rate: %.2f, Overall rate: %.2f' % \
258 (ok_rate.value, total_rate.value)
265 if __name__ == "__main__":
266 parser = argparse.ArgumentParser(description='Flow programming performance test: '
267 'First adds and then deletes flows into '
268 'the config tree, as specified by optional parameters.')
269 parser.add_argument('--odlhost', default='127.0.0.1', help='Host where '
270 'odl controller is running (default is 127.0.0.1)')
271 parser.add_argument('--odlport', default='8080', help='Port on '
272 'which odl\'s RESTCONF is listening (default is 8080)')
273 parser.add_argument('--nflows', type=int, default=10, help='Number of '
274 'flow add/delete requests to send in each cycle; default 10')
275 parser.add_argument('--ncycles', type=int, default=1, help='Number of '
276 'flow add/delete cycles to send in each thread; default 1')
277 parser.add_argument('--nthreads', type=int, default=1,
278 help='Number of request worker threads, default=1. '
279 'Each thread will add/delete nflows.')
280 parser.add_argument('--nnodes', type=int, default=16,
281 help='Number of nodes if mininet is not connected, default=16. '
282 'If mininet is connected, flows will be evenly distributed '
283 '(programmed) into connected nodes.')
284 parser.add_argument('--delete', dest='delete', action='store_true', default=True,
285 help='Delete all added flows one by one, benchmark delete '
287 parser.add_argument('--no-delete', dest='delete', action='store_false',
288 help='Add flows and leave them in the config data store.')
290 in_args = parser.parse_args()
292 put_url = 'http://' + in_args.odlhost + ":" + in_args.odlport + '/' + PUTURL
293 del_url = 'http://' + in_args.odlhost + ":" + in_args.odlport + '/' + DELURL
294 get_url = 'http://' + in_args.odlhost + ":" + in_args.odlport + '/' + GETURL
295 inv_url = 'http://' + in_args.odlhost + ":" + in_args.odlport + '/' + INVURL
297 cond = threading.Condition()
299 # Initialize the flows array
300 for i in range(in_args.nthreads):
303 # Run through ncycles, where nthreads are started in each cycles and
304 # nflows added from each thread
305 driver(add_flows, in_args.ncycles, in_args.nthreads, in_args.nnodes, \
306 in_args.nflows, put_url, cond, add_ok_rate, add_total_rate)
309 # Run through ncycles, where nthreads are started in each cycles and
310 # nflows added from each thread
311 if in_args.delete == True:
312 driver(delete_flows, in_args.ncycles, in_args.nthreads, in_args.nnodes, \
313 in_args.nflows, del_url, cond, del_ok_rate, del_total_rate)