X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=blobdiff_plain;f=test%2Ftools%2Fodl-mdsal-clustering-tests%2Fclustering-performance-test%2Fflow_config_blaster.py;h=d7e5bb9932a7c59a6232bed388e0dd111a0fc86b;hb=072f6e3a8d1bdf8f4c663843589c22d93ba07791;hp=219d33ea56fcdcfde133304e5e3be2bc13b0492a;hpb=5dadab61ae1438dc9405d5826e3937cc0cbefee0;p=integration%2Ftest.git diff --git a/test/tools/odl-mdsal-clustering-tests/clustering-performance-test/flow_config_blaster.py b/test/tools/odl-mdsal-clustering-tests/clustering-performance-test/flow_config_blaster.py index 219d33ea56..d7e5bb9932 100755 --- a/test/tools/odl-mdsal-clustering-tests/clustering-performance-test/flow_config_blaster.py +++ b/test/tools/odl-mdsal-clustering-tests/clustering-performance-test/flow_config_blaster.py @@ -54,6 +54,7 @@ class FlowConfigBlaster(object): FLWURL = "restconf/config/opendaylight-inventory:nodes/node/openflow:%d/table/0/flow/%d" TBLURL = "restconf/config/opendaylight-inventory:nodes/node/openflow:%d/table/0" INVURL = 'restconf/operational/opendaylight-inventory:nodes' + TIMEOUT = 10 flows = {} @@ -178,8 +179,8 @@ class FlowConfigBlaster(object): if flow_mod_template: self.flow_mode_template = flow_mod_template - self.post_url_template = 'http://' + self.host + ":" + self.port + '/' + self.TBLURL - self.del_url_template = 'http://' + self.host + ":" + self.port + '/' + self.FLWURL + self.post_url_template = 'http://%s:' + self.port + '/' + self.TBLURL + self.del_url_template = 'http://%s:' + self.port + '/' + self.FLWURL self.stats = self.FcbStats() self.total_ok_flows = 0 @@ -202,13 +203,16 @@ class FlowConfigBlaster(object): for openflow nodes :return: None """ - inventory_url = 'http://' + self.host + ":" + self.port + '/' + self.INVURL + hosts = self.host.split(",") + host = hosts[0] + inventory_url = 'http://' + host + ":" + self.port + '/' + self.INVURL nodes = self.nnodes if not self.auth: - r = session.get(inventory_url, headers=self.getheaders, stream=False) + r = session.get(inventory_url, headers=self.getheaders, stream=False, timeout=self.TIMEOUT) else: - r = session.get(inventory_url, headers=self.getheaders, stream=False, auth=('admin', 'admin')) + r = session.get(inventory_url, headers=self.getheaders, stream=False, auth=('admin', 'admin'), + timeout=self.TIMEOUT) if r.status_code == 200: try: @@ -224,7 +228,7 @@ class FlowConfigBlaster(object): return nodes - def create_flow_from_template(self, flow_id, ipaddr): + def create_flow_from_template(self, flow_id, ipaddr, node_id): """ Create a new flow instance from the flow template specified during FlowConfigBlaster instantiation. Flow templates are json-compatible @@ -241,7 +245,7 @@ class FlowConfigBlaster(object): flow['match']['ipv4-destination'] = '%s/32' % str(netaddr.IPAddress(ipaddr)) return flow - def post_flows(self, session, node, flow_list): + def post_flows(self, session, node, flow_list, flow_count): """ Performs a RESTCONF post of flows passed in the 'flow_list' parameters :param session: 'requests' session on which to perform the POST @@ -249,20 +253,43 @@ class FlowConfigBlaster(object): :param flow_list: List of flows (in dictionary form) to POST :return: status code from the POST operation """ - fmod = dict(self.flow_mode_template) - fmod['flow'] = flow_list - flow_data = json.dumps(fmod) - # print flow_data - flow_url = self.post_url_template % node + flow_data = self.convert_to_json(flow_list, node) + + hosts = self.host.split(",") + host = hosts[flow_count % len(hosts)] + flow_url = self.assemble_post_url(host, node) # print flow_url if not self.auth: - r = session.post(flow_url, data=flow_data, headers=self.putheaders, stream=False) + r = session.post(flow_url, data=flow_data, headers=self.putheaders, stream=False, timeout=self.TIMEOUT) else: - r = session.post(flow_url, data=flow_data, headers=self.putheaders, stream=False, auth=('admin', 'admin')) + r = session.post(flow_url, data=flow_data, headers=self.putheaders, stream=False, auth=('admin', 'admin'), + timeout=self.TIMEOUT) return r.status_code + def assemble_post_url(self, host, node): + """ + Creates url pointing to config dataStore: /nodes/node//table/ + :param host: ip address or host name pointing to controller + :param node: id of node (without protocol prefix and colon) + :return: url suitable for sending a flow to controller via POST method + """ + return self.post_url_template % (host, node) + + def convert_to_json(self, flow_list, node_id=None): + """ + Dumps flows to json form. + :param flow_list: list of flows in json friendly structure + :param node_id: node identifier of corresponding node + :return: string containing plain json + """ + fmod = dict(self.flow_mode_template) + fmod['flow'] = flow_list + flow_data = json.dumps(fmod) + # print flow_data + return flow_data + def add_flows(self, start_flow_id, tid): """ Adds flows into the ODL config data store. This function is executed by @@ -294,11 +321,11 @@ class FlowConfigBlaster(object): for i in range(self.fpr): flow_id = tid * (self.ncycles * self.nflows) + nflows + start_flow_id + self.startflow self.flows[tid][flow_id] = node_id - flow_list.append(self.create_flow_from_template(flow_id, self.ip_addr.increment())) + flow_list.append(self.create_flow_from_template(flow_id, self.ip_addr.increment(), node_id)) nflows += 1 if nflows >= self.nflows: break - sts = self.post_flows(s, node_id, flow_list) + sts = self.post_flows(s, node_id, flow_list, nflows) try: rqst_stats[sts] += 1 flow_stats[sts] += len(flow_list) @@ -323,7 +350,7 @@ class FlowConfigBlaster(object): with self.cond: self.cond.notifyAll() - def delete_flow(self, session, node, flow_id): + def delete_flow(self, session, node, flow_id, flow_count): """ Deletes a single flow from the ODL config data store using RESTCONF :param session: 'requests' session on which to perform the POST @@ -331,12 +358,16 @@ class FlowConfigBlaster(object): :param flow_id: ID of the to-be-deleted flow :return: status code from the DELETE operation """ - flow_url = self.del_url_template % (node, flow_id) + + hosts = self.host.split(",") + host = hosts[flow_count % len(hosts)] + flow_url = self.del_url_template % (host, node, flow_id) + # print flow_url if not self.auth: - r = session.delete(flow_url, headers=self.getheaders) + r = session.delete(flow_url, headers=self.getheaders, timeout=self.TIMEOUT) else: - r = session.delete(flow_url, headers=self.getheaders, auth=('admin', 'admin')) + r = session.delete(flow_url, headers=self.getheaders, auth=('admin', 'admin'), timeout=self.TIMEOUT) return r.status_code @@ -344,14 +375,13 @@ class FlowConfigBlaster(object): """ Deletes flow from the ODL config space that have been added using the 'add_flows()' function. This function is executed by a worker thread - :param start_flow_id - the ID of the first flow. Each Blaster thread + :param start_flow - the ID of the first flow. Each Blaster thread deletes a different set of flows :param tid: Thread ID - used to id the Blaster thread when statistics for the thread are printed out :return: """ - """ - """ + rqst_stats = {200: 0, 204: 0} s = requests.Session() @@ -363,7 +393,7 @@ class FlowConfigBlaster(object): with Timer() as t: for flow in range(self.nflows): flow_id = tid * (self.ncycles * self.nflows) + flow + start_flow + self.startflow - sts = self.delete_flow(s, self.flows[tid][flow_id], flow_id) + sts = self.delete_flow(s, self.flows[tid][flow_id], flow_id, flow) try: rqst_stats[sts] += 1 except KeyError: @@ -410,9 +440,8 @@ class FlowConfigBlaster(object): # Wait for all threads to finish and measure the execution time with Timer() as t: - while self.threads_done < self.nthreads: - with self.cond: - self.cond.wait() + for thread in threads: + thread.join() with self.print_lock: print '\n*** Test summary:' @@ -515,6 +544,56 @@ example_flow_mod_json = '''{ ] }''' + +def create_arguments_parser(): + """ + Shorthand to arg parser on library level in order to access and eventually enhance in ancestors. + :return: argument parser supporting config blaster arguments and parameters + """ + my_parser = argparse.ArgumentParser(description='Flow programming performance test: First adds and then' + ' deletes flows into the config tree, as specified by' + ' optional parameters.') + + my_parser.add_argument('--host', default='127.0.0.1', + help='Host where odl controller is running (default is 127.0.0.1). ' + 'Specify a comma-separated list of hosts to perform round-robin load-balancing.') + my_parser.add_argument('--port', default='8181', + help='Port on which odl\'s RESTCONF is listening (default is 8181)') + my_parser.add_argument('--cycles', type=int, default=1, + help='Number of flow add/delete cycles; default 1. Both Flow Adds and Flow Deletes are ' + 'performed in cycles. worker threads are started in each cycle and the cycle ' + 'ends when all threads finish. Another cycle is started when the previous cycle ' + 'finished.') + my_parser.add_argument('--threads', type=int, default=1, + help='Number of request worker threads to start in each cycle; default=1. ' + 'Each thread will add/delete flows.') + my_parser.add_argument('--flows', type=int, default=10, + help='Number of flows that will be added/deleted by each worker thread in each cycle; ' + 'default 10') + my_parser.add_argument('--fpr', type=int, default=1, + help='Flows-per-Request - number of flows (batch size) sent in each HTTP request; ' + 'default 1') + my_parser.add_argument('--nodes', type=int, default=16, + help='Number of nodes if mininet is not connected; default=16. If mininet is connected, ' + 'flows will be evenly distributed (programmed) into connected nodes.') + my_parser.add_argument('--delay', type=int, default=0, + help='Time (in seconds) to wait between the add and delete cycles; default=0') + my_parser.add_argument('--delete', dest='delete', action='store_true', default=True, + help='Delete all added flows one by one, benchmark delete ' + 'performance.') + my_parser.add_argument('--no-delete', dest='delete', action='store_false', + help='Do not perform the delete cycle.') + my_parser.add_argument('--auth', dest='auth', action='store_true', default=False, + help="Use the ODL default username/password 'admin'/'admin' to authenticate access to REST; " + 'default: no authentication') + my_parser.add_argument('--startflow', type=int, default=0, + help='The starting Flow ID; default=0') + my_parser.add_argument('--file', default='', + help='File from which to read the JSON flow template; default: no file, use a built in ' + 'template.') + return my_parser + + if __name__ == "__main__": ############################################################################ # This program executes the base performance test. The test adds flows into @@ -522,45 +601,8 @@ if __name__ == "__main__": # to the FlowConfigBlaster class and drives its main functions: adding and # deleting flows from the controller's config data store ############################################################################ - parser = argparse.ArgumentParser(description='Flow programming performance test: First adds and then deletes flows ' - 'into the config tree, as specified by optional parameters.') - - parser.add_argument('--host', default='127.0.0.1', - help='Host where odl controller is running (default is 127.0.0.1)') - parser.add_argument('--port', default='8181', - help='Port on which odl\'s RESTCONF is listening (default is 8181)') - parser.add_argument('--cycles', type=int, default=1, - help='Number of flow add/delete cycles; default 1. Both Flow Adds and Flow Deletes are ' - 'performed in cycles. worker threads are started in each cycle and the cycle ' - 'ends when all threads finish. Another cycle is started when the previous cycle finished.') - parser.add_argument('--threads', type=int, default=1, - help='Number of request worker threads to start in each cycle; default=1. ' - 'Each thread will add/delete flows.') - parser.add_argument('--flows', type=int, default=10, - help='Number of flows that will be added/deleted by each worker thread in each cycle; ' - 'default 10') - parser.add_argument('--fpr', type=int, default=1, - help='Flows-per-Request - number of flows (batch size) sent in each HTTP request; ' - 'default 1') - parser.add_argument('--nodes', type=int, default=16, - help='Number of nodes if mininet is not connected; default=16. If mininet is connected, ' - 'flows will be evenly distributed (programmed) into connected nodes.') - parser.add_argument('--delay', type=int, default=0, - help='Time (in seconds) to wait between the add and delete cycles; default=0') - parser.add_argument('--delete', dest='delete', action='store_true', default=True, - help='Delete all added flows one by one, benchmark delete ' - 'performance.') - parser.add_argument('--no-delete', dest='delete', action='store_false', - help='Do not perform the delete cycle.') - parser.add_argument('--auth', dest='auth', action='store_true', default=False, - help="Use the ODL default username/password 'admin'/'admin' to authenticate access to REST; " - 'default: no authentication') - parser.add_argument('--startflow', type=int, default=0, - help='The starting Flow ID; default=0') - parser.add_argument('--file', default='', - help='File from which to read the JSON flow template; default: no file, use a built in ' - 'template.') + parser = create_arguments_parser() in_args = parser.parse_args() if in_args.file != '':