Step 1: Move vm scripts to the right place
[integration/test.git] / test / tools / odl-mdsal-clustering-tests / clustering-performance-test / flow_config_blaster.py
index 219d33ea56fcdcfde133304e5e3be2bc13b0492a..d7e5bb9932a7c59a6232bed388e0dd111a0fc86b 100755 (executable)
@@ -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/<node-id>/table/<table-id>
+        :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. <THREADS> 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> 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. <THREADS> 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> 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 != '':