From 8316a4653de4dbc5b2771a160cf8fd41ee743577 Mon Sep 17 00:00:00 2001 From: Jan Medved Date: Sun, 6 Mar 2016 16:13:27 -0800 Subject: [PATCH] Fixed flow_config_blaster Floodlight edition Fixed measurements of time for stats collection in flow_add_delete_test Fixed PIP-8 violations (at least the fixable ones) Change-Id: I01a361a7245fe479fbb65eea95bea93808d787a8 Signed-off-by: Jan Medved --- .../flow_add_delete_test.py | 34 +++++-- .../flow_config_blaster.py | 40 ++++++-- .../flow_config_blaster_fle.py | 92 ++++++++++++------- 3 files changed, 116 insertions(+), 50 deletions(-) diff --git a/tools/odl-mdsal-clustering-tests/clustering-performance-test/flow_add_delete_test.py b/tools/odl-mdsal-clustering-tests/clustering-performance-test/flow_add_delete_test.py index ecbcbb971d..cc36ed93a7 100755 --- a/tools/odl-mdsal-clustering-tests/clustering-performance-test/flow_add_delete_test.py +++ b/tools/odl-mdsal-clustering-tests/clustering-performance-test/flow_add_delete_test.py @@ -13,6 +13,22 @@ __license__ = "New-style BSD" __email__ = "jmedved@cisco.com" +class Timer(object): + def __init__(self, verbose=False): + self.verbose = verbose + + def __enter__(self): + self.start = time.time() + return self + + def __exit__(self, *args): + self.end = time.time() + self.secs = self.end - self.start + self.msecs = self.secs * 1000 # millisecs + if self.verbose: + print ("elapsed time: %f ms" % self.msecs) + + def wait_for_stats(crawler, exp_found, timeout, delay): """ Waits for the ODL stats manager to catch up. Polls ODL inventory every @@ -28,16 +44,18 @@ def wait_for_stats(crawler, exp_found, timeout, delay): """ total_delay = 0 print 'Waiting for stats to catch up:' - while True: - crawler.crawl_inventory() - print ' %d, %d' % (crawler.reported_flows, crawler.found_flows) - if crawler.found_flows == exp_found or total_delay > timeout: - break - total_delay += delay - time.sleep(delay) + + with Timer() as t: + while True: + crawler.crawl_inventory() + print ' %d, %d' % (crawler.reported_flows, crawler.found_flows) + if crawler.found_flows == exp_found or total_delay > timeout: + break + total_delay += delay + time.sleep(delay) if total_delay < timeout: - print 'Stats collected in %d seconds.' % total_delay + print 'Stats collected in %d seconds.' % t.secs else: print 'Stats collection did not finish in %d seconds. Aborting...' % total_delay diff --git a/tools/odl-mdsal-clustering-tests/clustering-performance-test/flow_config_blaster.py b/tools/odl-mdsal-clustering-tests/clustering-performance-test/flow_config_blaster.py index ddf3bb1239..8b38b14b8f 100755 --- a/tools/odl-mdsal-clustering-tests/clustering-performance-test/flow_config_blaster.py +++ b/tools/odl-mdsal-clustering-tests/clustering-performance-test/flow_config_blaster.py @@ -120,6 +120,15 @@ class FlowConfigBlaster(object): """ Calculates the stats for RESTCONF request and flow programming throughput, and aggregates statistics across all Blaster threads. + + Args: + rqst_stats: Request statistics dictionary + flow_stats: Flow statistcis dictionary + elapsed_time: Elapsed time for the test + + Returns: Rates (requests/sec) for successfully finished requests, + the total number of requests, sucessfully installed flow and + the total number of flows """ ok_rqsts = rqst_stats[200] + rqst_stats[204] total_rqsts = sum(rqst_stats.values()) @@ -236,13 +245,18 @@ class FlowConfigBlaster(object): FlowConfigBlaster instantiation. Flow templates are json-compatible dictionaries that MUST contain elements for flow cookie, flow name, flow id and the destination IPv4 address in the flow match field. - :param flow_id: Id for the new flow to create - :param ipaddr: IP Address to put into the flow's match - :return: The newly created flow instance + + Args: + flow_id: Id for the new flow to create + ipaddr: IP Address to put into the flow's match + node_id: ID of the node where to create the flow + + Returns: The flow that gas been created from the template + """ flow = copy.deepcopy(self.flow_mode_template['flow'][0]) flow['cookie'] = flow_id - flow['flow-name'] = 'TestFlow-%d' % flow_id + flow['flow-name'] = self.create_flow_name(flow_id) flow['id'] = str(flow_id) flow['match']['ipv4-destination'] = '%s/32' % str(netaddr.IPAddress(ipaddr)) return flow @@ -253,6 +267,8 @@ class FlowConfigBlaster(object): :param session: 'requests' session on which to perform the POST :param node: The ID of the openflow node to which to post the flows :param flow_list: List of flows (in dictionary form) to POST + :param flow_count: Flow counter for round-robin host load balancing + :return: status code from the POST operation """ flow_data = self.convert_to_json(flow_list, node) @@ -355,10 +371,14 @@ class FlowConfigBlaster(object): 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 - :param node: Id of the openflow node from which to delete the flow - :param flow_id: ID of the to-be-deleted flow - :return: status code from the DELETE operation + Args: + session: 'requests' session on which to perform the POST + node: Id of the openflow node from which to delete the flow + flow_id: ID of the to-be-deleted flow + flow_count: Index of the flow being processed (for round-robin LB) + + Returns: status code from the DELETE operation + """ hosts = self.host.split(",") @@ -477,6 +497,9 @@ class FlowConfigBlaster(object): def get_ok_rqsts(self): return self.total_ok_rqsts + def create_flow_name(self, flow_id): + return 'TestFlow-%d' % flow_id + def get_json_from_file(filename): """ @@ -498,6 +521,7 @@ def get_json_from_file(filename): return None + ############################################################################### # This is an example of what the content of a JSON flow mode template should # look like. Cut & paste to create a custom template. "id" and "ipv4-destination" diff --git a/tools/odl-mdsal-clustering-tests/clustering-performance-test/flow_config_blaster_fle.py b/tools/odl-mdsal-clustering-tests/clustering-performance-test/flow_config_blaster_fle.py index 3c3535d5f3..b54cf5cce5 100755 --- a/tools/odl-mdsal-clustering-tests/clustering-performance-test/flow_config_blaster_fle.py +++ b/tools/odl-mdsal-clustering-tests/clustering-performance-test/flow_config_blaster_fle.py @@ -1,9 +1,10 @@ #!/usr/bin/python from flow_config_blaster import FlowConfigBlaster import argparse -import netaddr import time import json +import copy +import requests __author__ = "Jan Medved" @@ -21,17 +22,17 @@ class FlowConfigBlasterFLE(FlowConfigBlaster): "name": "flow-mod", "cookie": "0", "priority": "32768", - "ether-type": "2048", - "dst-ip": "10.0.0.1/32", + "eth_type": "2048", + "ipv4_dst": "10.0.0.1/32", "active": "true", "actions": "output=flood" } def __init__(self, host, port, ncycles, nthreads, nnodes, nflows, startflow): - FlowConfigBlaster.__init__(self, host, port, ncycles, nthreads, nnodes, nflows, startflow, False, '') + FlowConfigBlaster.__init__(self, host, port, ncycles, nthreads, 1, nnodes, nflows, startflow, False) - # Create the service URL - self.url = 'http://' + self.host + ":" + self.port + '/wm/staticflowentrypusher/json' + def create_floodlight_url(self, host): + return 'http://' + host + ":" + self.port + '/wm/staticflowpusher/json' def get_num_nodes(self, session): """ @@ -52,41 +53,60 @@ class FlowConfigBlasterFLE(FlowConfigBlaster): return nodes - def post_flows(self, session, node, flow_id, ipaddr): + def post_flows(self, session, node, flow_list, flow_count): """ - Adds a flow. Overrides the add_flow method in FlowConfigBlaster. - :param session: - :param node: - :param flow_id: - :param ipaddr: - :return: + Performs a RESTCONF post of flows passed in the 'flow_list' parameters + :param session: 'requests' session on which to perform the POST + :param node: The ID of the openflow node to which to post the flows + :param flow_list: List of flows (in dictionary form) to POST + :param flow_count: Number of flows in flow_list (must be 1) + :return: status code from the POST operation """ - self.flow['switch'] = "00:00:00:00:00:00:00:%s" % '{0:02x}'.format(node) - self.flow['name'] = 'TestFlow-%d' % flow_id - self.flow['cookie'] = str(flow_id) - self.flow['dst-ip'] = "%s/32" % str(netaddr.IPAddress(ipaddr)) - - flow_data = json.dumps(self.flow) - # print flow_data - # print flow_url - - r = session.post(self.url, data=flow_data, headers=self.putheaders, stream=False) + flow = copy.deepcopy(self.flow) + flow['switch'] = "00:00:00:00:00:00:00:%s" % '{0:02x}'.format(node) + flow['name'] = flow_list[0]['flow-name'] + flow['table'] = flow_list[0]['table_id'] + flow['cookie'] = flow_list[0]['cookie'] + # flow['cookie_mask'] = flow_list[0]['cookie_mask'] + flow['idle_timeout'] = flow_list[0]['idle-timeout'] + flow['hard_timeout'] = flow_list[0]['hard-timeout'] + flow['ipv4_dst'] = flow_list[0]['match']['ipv4-destination'] + + flow_data = json.dumps(flow) + + hosts = self.host.split(",") + host = hosts[flow_count % len(hosts)] + flow_url = self.create_floodlight_url(host) + + r = session.post(flow_url, data=flow_data, headers=self.putheaders, stream=False) return r.status_code - def delete_flow(self, session, node, flow_id): + def delete_flow(self, session, node, flow_id, flow_count): """ - Deletes a flow. Overrides the delete_flow method in FlowConfigBlaster. - :param session: - :param node: - :param flow_id: - :return: + Deletes a single flow from the ODL config data store using RESTCONF + :param session: 'requests' session on which to perform the POST + :param node: Id of the openflow node from which to delete the flow + :param flow_id: ID of the to-be-deleted flow + :param flow_count: Flow counter for round-robin of delete operations + :return: status code from the DELETE operation """ - f = {'name': 'TestFlow-%d' % flow_id} - flow_data = json.dumps(f) - r = session.delete(self.url, data=flow_data, headers=self.getheaders) + hosts = self.host.split(",") + host = hosts[flow_count % len(hosts)] + flow_url = self.create_floodlight_url(host) + flow_data = json.dumps({'name': self.create_flow_name(flow_id)}) + + r = session.delete(flow_url, data=flow_data, headers=self.getheaders) return r.status_code + def clear_all_flows(self): + clear_url = 'http://' + self.host + ":" + self.port + '/wm/staticflowpusher/clear/all/json' + r = requests.get(clear_url) + if r.status_code == 200: + print "All flows cleared before the test" + else: + print "Failed to clear flows from the controller, your results may vary" + if __name__ == "__main__": @@ -122,11 +142,13 @@ if __name__ == "__main__": fct = FlowConfigBlasterFLE(in_args.host, in_args.port, in_args.cycles, in_args.threads, in_args.nodes, in_args.flows, in_args.startflow) + fct.clear_all_flows() + # Run through , where are started in each cycle and are added from each thread fct.add_blaster() - print '\n*** Total flows added: %s' % fct.get_total_flows() - print ' HTTP[OK] results: %d\n' % fct.get_ok_flows() + print '\n*** Total flows added: %s' % fct.get_ok_flows() + print ' HTTP[OK] results: %d\n' % fct.get_ok_rqsts() if in_args.delay > 0: print '*** Waiting for %d seconds before the delete cycle ***\n' % in_args.delay @@ -136,3 +158,5 @@ if __name__ == "__main__": # deleted in each thread if in_args.delete: fct.delete_blaster() + print '\n*** Total flows deleted: %s' % fct.get_ok_flows() + print ' HTTP[OK] results: %d\n' % fct.get_ok_rqsts() -- 2.36.6