21 "ipv4-destination": "10.0.20.0/24"
26 odl_node_url = '/restconf/config/opendaylight-inventory:nodes/node/'
30 def __init__(self, verbose=False):
31 self.verbose = verbose
34 self.start = time.time()
37 def __exit__(self, *args):
38 self.end = time.time()
39 self.secs = self.end - self.start
40 self.msecs = self.secs * 1000 # millisecs
42 print("elapsed time: %f ms" % self.msecs)
45 class Counter(object):
46 def __init__(self, start=0):
47 self.lock = threading.Lock()
50 def increment(self, value=1):
60 def _prepare_post(cntl, method, flows, template=None):
61 """Creates a POST http requests to configure a flow in configuration datastore.
64 :param cntl: controller's ip address or hostname
66 :param method: determines http request method
68 :param flows: list of flow details
70 :param template: flow template to be to be filled
73 :returns req: http request object
76 for dev_id, ip in (flows):
77 flow = copy.deepcopy(template)
79 flow["match"]["ipv4-destination"] = '%s/32' % str(netaddr.IPAddress(ip))
80 flow_list.append(flow)
81 body = {"flow": flow_list}
82 url = 'http://' + cntl + ':8181' + odl_node_url + dev_id + '/table/0'
83 req_data = json.dumps(body)
84 req = requests.Request(method, url, headers={'Content-Type': 'application/json'},
85 data=req_data, auth=('admin', 'admin'))
89 def _prepare_delete(cntl, method, flows, template=None):
90 """Creates a DELETE http requests to configure a flow in configuration datastore.
93 :param cntl: controller's ip address or hostname
95 :param method: determines http request method
97 :param flows: list of flow details
99 :param template: flow template to be to be filled
102 :returns req: http request object
104 dev_id, flow_id = flows[0]
105 url = 'http://' + cntl + ':8181' + odl_node_url + dev_id + '/table/0/flow/' + str(flow_id)
106 req = requests.Request(method, url, headers={'Content-Type': 'application/json'},
107 data=None, auth=('admin', 'admin'))
111 def _wt_request_sender(thread_id, preparefnc, inqueue=None, exitevent=None, controllers=[], restport='',
112 template=None, outqueue=None, method=None):
113 """The funcion sends http requests.
115 Runs in the working thread. It reads out flow details from the queue and sends apropriate http requests
119 :param thread_id: thread id
121 :param preparefnc: function to preparesthe http request
123 :param inqueue: input queue, flow details are comming from here
125 :param exitevent: event to notify working thread that parent (task executor) stopped filling the input queue
127 :param controllers: a list of controllers' ip addresses or hostnames
129 :param restport: restconf port
131 :param template: flow template used for creating flow content
133 :param outqueue: queue where the results should be put
135 :param method: method derermines the type of http request
138 nothing, results must be put into the output queue
140 ses = requests.Session()
141 cntl = controllers[0]
142 counter = [0 for i in range(600)]
147 flowlist = inqueue.get(timeout=1)
149 if exitevent.is_set() and inqueue.empty():
152 req = preparefnc(cntl, method, flowlist, template=template)
153 # prep = ses.prepare_request(req)
156 rsp = ses.send(prep, timeout=5)
157 except requests.exceptions.Timeout:
160 counter[rsp.status_code] += 1
162 for i, v in enumerate(counter):
168 def get_device_ids(controller='127.0.0.1', port=8181):
169 """Returns a list of switch ids"""
171 rsp = requests.get(url='http://{0}:{1}/restconf/operational/opendaylight-inventory:nodes'
172 .format(controller, port), auth=('admin', 'admin'))
173 if rsp.status_code != 200:
176 devices = json.loads(rsp.content)['nodes']['node']
177 ids = [d['id'] for d in devices]
183 def get_flow_ids(controller='127.0.0.1', port=8181):
184 """Returns a list of flow ids"""
186 device_ids = get_device_ids(controller, port)
187 for device_id in device_ids:
188 rsp = requests.get(url='http://{0}:{1}/restconf/operational/opendaylight-inventory:nodes/node/%s/table/0'
189 .format(controller, port) % device_id, auth=('admin', 'admin'))
190 if rsp.status_code != 200:
193 flows = json.loads(rsp.content)['flow-node-inventory:table'][0]['flow']
203 parser = argparse.ArgumentParser(description='Flow programming performance test: First adds and then deletes flows '
204 'into the config tree, as specified by optional parameters.')
206 parser.add_argument('--host', default='127.0.0.1',
207 help='Host where ODL controller is running (default is 127.0.0.1)')
208 parser.add_argument('--port', default='8181',
209 help='Port on which ODL\'s RESTCONF is listening (default is 8181)')
210 parser.add_argument('--threads', type=int, default=1,
211 help='Number of request worker threads to start in each cycle; default=1. '
212 'Each thread will add/delete <FLOWS> flows.')
213 parser.add_argument('--flows', type=int, default=10,
214 help='Number of flows that will be added/deleted in total, default 10')
215 parser.add_argument('--fpr', type=int, default=1,
216 help='Number of flows per REST request, default 1')
217 parser.add_argument('--timeout', type=int, default=100,
218 help='The maximum time (seconds) to wait between the add and delete cycles; default=100')
219 parser.add_argument('--no-delete', dest='no_delete', action='store_true', default=False,
220 help='Delete all added flows one by one, benchmark delete '
222 parser.add_argument('--bulk-delete', dest='bulk_delete', action='store_true', default=False,
223 help='Delete all flows in bulk; default=False')
224 parser.add_argument('--outfile', default='', help='Stores add and delete flow rest api rate; default=""')
226 in_args = parser.parse_args(*argv)
230 base_dev_ids = get_device_ids(controller=in_args.host)
231 base_flow_ids = get_flow_ids(controller=in_args.host)
233 ip_addr = Counter(int(netaddr.IPAddress('10.0.0.1')))
235 preparefnc = _prepare_post
237 base_num_flows = len(base_flow_ids)
240 print " devices:", len(base_dev_ids)
241 print " flows :", base_num_flows
243 # lets fill the queue for workers
247 sendqueue = Queue.Queue()
248 dev_id = random.choice(base_dev_ids)
249 for i in range(in_args.flows):
250 dst_ip = ip_addr.increment()
251 flow_list.append((dev_id, dst_ip))
252 flow_details.append((dev_id, dst_ip))
254 if nflows == in_args.fpr:
255 sendqueue.put(flow_list)
258 dev_id = random.choice(base_dev_ids)
261 resultqueue = Queue.Queue()
263 exitevent = threading.Event()
268 for i in range(int(in_args.threads)):
269 thr = threading.Thread(target=_wt_request_sender, args=(i, preparefnc),
270 kwargs={"inqueue": sendqueue, "exitevent": exitevent,
271 "controllers": [in_args.host], "restport": in_args.port,
272 "template": flow_template, "outqueue": resultqueue, "method": "POST"})
279 # waitng for reqults and sum them up
282 # reading partial resutls from sender thread
283 part_result = resultqueue.get()
284 for k, v in part_result.iteritems():
290 print "Added", in_args.flows, "flows in", tmr.secs, "seconds", result
291 add_details = {"duration": tmr.secs, "flows": len(flow_details)}
293 # lets print some stats
294 print "\n\nStats monitoring ..."
297 for i in range(rounds):
298 reported_flows = len(get_flow_ids(controller=in_args.host))
299 expected_flows = base_num_flows + in_args.flows
300 print "Reported Flows: %d/%d" % (reported_flows, expected_flows)
301 if reported_flows >= expected_flows:
306 print "... monitoring finished in +%d seconds\n\n" % t.secs
308 print "... monitoring aborted after %d rounds, elapsed time %d\n\n" % (rounds, t.secs)
310 if in_args.no_delete:
314 time.sleep(in_args.timeout)
316 print "Flows to be removed: %d" % len(flow_details)
317 # lets fill the queue for workers
318 sendqueue = Queue.Queue()
319 for fld in flow_details:
323 resultqueue = Queue.Queue()
325 exitevent = threading.Event()
328 preparefnc = _prepare_delete
330 if in_args.bulk_delete:
331 url = 'http://' + in_args.host + ':' + '8181'
332 url += '/restconf/config/opendaylight-inventory:nodes'
333 rsp = requests.delete(url, headers={'Content-Type': 'application/json'}, auth=('admin', 'admin'))
334 result = {rsp.status_code: 1}
337 for i in range(int(in_args.threads)):
338 thr = threading.Thread(target=_wt_request_sender, args=(i, preparefnc),
339 kwargs={"inqueue": sendqueue, "exitevent": exitevent,
340 "controllers": [in_args.host], "restport": in_args.port,
341 "template": None, "outqueue": resultqueue, "method": "DELETE"})
348 # waitng for reqults and sum them up
351 # reading partial resutls from sender thread
352 part_result = resultqueue.get()
353 for k, v in part_result.iteritems():
359 print "Removed", len(flow_details), "flows in", tmr.secs, "seconds", result
360 del_details = {"duration": tmr.secs, "flows": len(flow_details)}
362 # # lets print some stats
363 # print "\n\nSome stats monitoring ...."
364 # for i in range(100):
365 # print get_flow_simple_stats(controller=in_args.host)
367 # print "... monitoring finished\n\n"
368 # lets print some stats
369 print "\n\nStats monitoring ..."
372 for i in range(rounds):
373 reported_flows = len(get_flow_ids(controller=in_args.host))
374 expected_flows = base_num_flows
375 print "Reported Flows: %d/%d" % (reported_flows, expected_flows)
376 if reported_flows <= expected_flows:
381 print "... monitoring finished in +%d seconds\n\n" % t.secs
383 print "... monitoring aborted after %d rounds, elapsed time %d\n\n" % (rounds, t.secs)
385 if in_args.outfile != "":
386 addrate = add_details['flows'] / add_details['duration']
387 delrate = del_details['flows'] / del_details['duration']
388 print "addrate", addrate
389 print "delrate", delrate
391 with open(in_args.outfile, "wt") as fd:
392 fd.write("AddRate,DeleteRate\n")
393 fd.write("{0},{1}\n".format(addrate, delrate))
395 if __name__ == "__main__":