16 "ethernet-match": {"ethernet-type": {"type": 2048}},
17 "ipv4-destination": "10.0.20.0/24",
22 odl_node_url = "/restconf/config/opendaylight-inventory:nodes/node/"
26 def __init__(self, verbose=False):
27 self.verbose = verbose
30 self.start = time.time()
33 def __exit__(self, *args):
34 self.end = time.time()
35 self.secs = self.end - self.start
36 self.msecs = self.secs * 1000 # millisecs
38 print("elapsed time: %f ms" % self.msecs)
41 class Counter(object):
42 def __init__(self, start=0):
43 self.lock = threading.Lock()
46 def increment(self, value=1):
56 def _prepare_post(cntl, method, flows, template=None):
57 """Creates a POST http requests to configure a flow in configuration datastore.
60 :param cntl: controller's ip address or hostname
62 :param method: determines http request method
64 :param flows: list of flow details
66 :param template: flow template to be to be filled
69 :returns req: http request object
72 for dev_id, ip in flows:
73 flow = copy.deepcopy(template)
75 flow["match"]["ipv4-destination"] = "%s/32" % str(netaddr.IPAddress(ip))
76 flow_list.append(flow)
77 body = {"flow": flow_list}
78 url = "http://" + cntl + ":8181" + odl_node_url + dev_id + "/table/0"
79 req_data = json.dumps(body)
80 req = requests.Request(
83 headers={"Content-Type": "application/json"},
85 auth=("admin", "admin"),
90 def _prepare_delete(cntl, method, flows, template=None):
91 """Creates a DELETE http requests to configure a flow in configuration datastore.
94 :param cntl: controller's ip address or hostname
96 :param method: determines http request method
98 :param flows: list of flow details
100 :param template: flow template to be to be filled
103 :returns req: http request object
105 dev_id, flow_id = flows[0]
115 req = requests.Request(
118 headers={"Content-Type": "application/json"},
120 auth=("admin", "admin"),
125 def _wt_request_sender(
136 """The funcion sends http requests.
138 Runs in the working thread. It reads out flow details from the queue and sends apropriate http requests
142 :param thread_id: thread id
144 :param preparefnc: function to preparesthe http request
146 :param inqueue: input queue, flow details are comming from here
148 :param exitevent: event to notify working thread that parent (task executor) stopped filling the input queue
150 :param controllers: a list of controllers' ip addresses or hostnames
152 :param restport: restconf port
154 :param template: flow template used for creating flow content
156 :param outqueue: queue where the results should be put
158 :param method: method derermines the type of http request
161 nothing, results must be put into the output queue
163 ses = requests.Session()
164 cntl = controllers[0]
165 counter = [0 for i in range(600)]
170 flowlist = inqueue.get(timeout=1)
172 if exitevent.is_set() and inqueue.empty():
175 req = preparefnc(cntl, method, flowlist, template=template)
176 # prep = ses.prepare_request(req)
179 rsp = ses.send(prep, timeout=5)
180 except requests.exceptions.Timeout:
183 counter[rsp.status_code] += 1
185 for i, v in enumerate(counter):
191 def get_device_ids(controller="127.0.0.1", port=8181):
192 """Returns a list of switch ids"""
195 url="http://{0}:{1}/restconf/operational/opendaylight-inventory:nodes".format(
198 auth=("admin", "admin"),
200 if rsp.status_code != 200:
203 devices = json.loads(rsp.content)["nodes"]["node"]
204 ids = [d["id"] for d in devices]
210 def get_flow_ids(controller="127.0.0.1", port=8181):
211 """Returns a list of flow ids"""
213 device_ids = get_device_ids(controller, port)
214 for device_id in device_ids:
216 url="http://{0}:{1}/restconf/operational/opendaylight-inventory:nodes/node/%s/table/0".format(
220 auth=("admin", "admin"),
222 if rsp.status_code != 200:
225 flows = json.loads(rsp.content)["flow-node-inventory:table"][0]["flow"]
235 parser = argparse.ArgumentParser(
236 description="Flow programming performance test: First adds and then deletes flows "
237 "into the config tree, as specified by optional parameters."
243 help="Host where ODL controller is running (default is 127.0.0.1)",
248 help="Port on which ODL's RESTCONF is listening (default is 8181)",
254 help="Number of request worker threads to start in each cycle; default=1. "
255 "Each thread will add/delete <FLOWS> flows.",
261 help="Number of flows that will be added/deleted in total, default 10",
264 "--fpr", type=int, default=1, help="Number of flows per REST request, default 1"
270 help="The maximum time (seconds) to wait between the add and delete cycles; default=100",
277 help="Delete all added flows one by one, benchmark delete " "performance.",
284 help="Delete all flows in bulk; default=False",
289 help='Stores add and delete flow rest api rate; default=""',
292 in_args = parser.parse_args(*argv)
296 base_dev_ids = get_device_ids(controller=in_args.host)
297 base_flow_ids = get_flow_ids(controller=in_args.host)
299 ip_addr = Counter(int(netaddr.IPAddress("10.0.0.1")))
301 preparefnc = _prepare_post
303 base_num_flows = len(base_flow_ids)
306 print(" devices:", len(base_dev_ids))
307 print(" flows :", base_num_flows)
309 # lets fill the queue for workers
313 sendqueue = Queue.Queue()
314 dev_id = random.choice(base_dev_ids)
315 for i in range(in_args.flows):
316 dst_ip = ip_addr.increment()
317 flow_list.append((dev_id, dst_ip))
318 flow_details.append((dev_id, dst_ip))
320 if nflows == in_args.fpr:
321 sendqueue.put(flow_list)
324 dev_id = random.choice(base_dev_ids)
327 resultqueue = Queue.Queue()
329 exitevent = threading.Event()
334 for i in range(int(in_args.threads)):
335 thr = threading.Thread(
336 target=_wt_request_sender,
337 args=(i, preparefnc),
339 "inqueue": sendqueue,
340 "exitevent": exitevent,
341 "controllers": [in_args.host],
342 "restport": in_args.port,
343 "template": flow_template,
344 "outqueue": resultqueue,
354 # waitng for reqults and sum them up
357 # reading partial resutls from sender thread
358 part_result = resultqueue.get()
359 for k, v in part_result.iteritems():
365 print("Added", in_args.flows, "flows in", tmr.secs, "seconds", result)
366 add_details = {"duration": tmr.secs, "flows": len(flow_details)}
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 + in_args.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))
384 "... monitoring aborted after %d rounds, elapsed time %d\n\n"
388 if in_args.no_delete:
392 time.sleep(in_args.timeout)
394 print("Flows to be removed: %d" % (len(flow_details)))
395 # lets fill the queue for workers
396 sendqueue = Queue.Queue()
397 for fld in flow_details:
401 resultqueue = Queue.Queue()
403 exitevent = threading.Event()
406 preparefnc = _prepare_delete
408 if in_args.bulk_delete:
409 url = "http://" + in_args.host + ":" + "8181"
410 url += "/restconf/config/opendaylight-inventory:nodes"
411 rsp = requests.delete(
413 headers={"Content-Type": "application/json"},
414 auth=("admin", "admin"),
416 result = {rsp.status_code: 1}
419 for i in range(int(in_args.threads)):
420 thr = threading.Thread(
421 target=_wt_request_sender,
422 args=(i, preparefnc),
424 "inqueue": sendqueue,
425 "exitevent": exitevent,
426 "controllers": [in_args.host],
427 "restport": in_args.port,
429 "outqueue": resultqueue,
439 # waitng for reqults and sum them up
442 # reading partial resutls from sender thread
443 part_result = resultqueue.get()
444 for k, v in part_result.iteritems():
450 print("Removed", len(flow_details), "flows in", tmr.secs, "seconds", result)
451 del_details = {"duration": tmr.secs, "flows": len(flow_details)}
453 print("\n\nStats monitoring ...")
456 for i in range(rounds):
457 reported_flows = len(get_flow_ids(controller=in_args.host))
458 expected_flows = base_num_flows
459 print("Reported Flows: %d/%d" % ((reported_flows, expected_flows)))
460 if reported_flows <= expected_flows:
465 print("... monitoring finished in +%d seconds\n\n" % (t.secs))
468 "... monitoring aborted after %d rounds, elapsed time %d\n\n"
472 if in_args.outfile != "":
473 addrate = add_details["flows"] / add_details["duration"]
474 delrate = del_details["flows"] / del_details["duration"]
475 print("addrate", addrate)
476 print("delrate", delrate)
478 with open(in_args.outfile, "wt") as fd:
479 fd.write("AddRate,DeleteRate\n")
480 fd.write("{0},{1}\n".format(addrate, delrate))
483 if __name__ == "__main__":