- Added capability to read flow templates from files
authorJan Medved <jmedved@cisco.com>
Mon, 13 Oct 2014 01:39:16 +0000 (18:39 -0700)
committerJan Medved <jmedved@cisco.com>
Mon, 13 Oct 2014 01:43:40 +0000 (18:43 -0700)
- Added 'measured rate', which computes the flow programming rate as
  total_flows / total-elapsed-time from threads start to finish

- Small reorg of the code to get it ready for Floodlight flow config blaster

- README updates

- Helptext updates.

Change-Id: I1a286c790a05f4654f097b3d94f7f929c2ca1b8c
Signed-off-by: Jan Medved <jmedved@cisco.com>
test/tools/odl-mdsal-clustering-tests/clustering-performance-test/README
test/tools/odl-mdsal-clustering-tests/clustering-performance-test/config_cleanup.py [changed mode: 0644->0755]
test/tools/odl-mdsal-clustering-tests/clustering-performance-test/flow_add_delete_test.py
test/tools/odl-mdsal-clustering-tests/clustering-performance-test/flow_config_blaster.py
test/tools/odl-mdsal-clustering-tests/clustering-performance-test/inventory_crawler.py [changed mode: 0644->0755]
test/tools/odl-mdsal-clustering-tests/clustering-performance-test/multi-blaster.sh [new file with mode: 0755]
test/tools/odl-mdsal-clustering-tests/clustering-performance-test/nicira-ext-all.json [new file with mode: 0644]

index b0ce2ae3ef0bf07cf724195bd76982868b98cc75..cd5161b84a15493f56a026240a24f8e39f292708 100644 (file)
@@ -85,11 +85,14 @@ The Flow Config Blaster:
 To see the command line options, type:
   > ./flow_config_blaster.py --help
 
-usage: flow_config_blaster.py [-h] [--host HOST] [--port PORT] [--flows FLOWS]
+usage: flow_config_blaster.py [-h] [--host HOST] [--port PORT]
                               [--cycles CYCLES] [--threads THREADS]
-                              [--nodes NODES] [--delay DELAY] [--delete]
-                              [--no-delete] [--no-auth] [--auth]
-                              [--startflow STARTFLOW]
+                              [--flows FLOWS] [--nodes NODES] [--delay DELAY]
+                              [--delete] [--no-delete] [--auth]
+                              [--startflow STARTFLOW] [--file FILE]
+
+Flow programming performance test: First adds and then deletes flows into the
+config tree, as specified by optional parameters.
 
 optional arguments:
   -h, --help            show this help message and exit
@@ -97,25 +100,31 @@ optional arguments:
                         127.0.0.1)
   --port PORT           Port on which odl's RESTCONF is listening (default is
                         8181)
-  --flows FLOWS         Number of flow add/delete requests to send in each
-                        cycle; default 10
-  --cycles CYCLES       Number of flow add/delete cycles to send in each
-                        thread; default 1
-  --threads THREADS     Number of request worker threads, default=1. Each
-                        thread will add/delete flows.
+  --cycles CYCLES       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.
+  --threads THREADS     Number of request worker threads to start in each
+                        cycle; default=1. Each thread will add/delete <FLOWS>
+                        flows.
+  --flows FLOWS         Number of flows that will be added/deleted by each
+                        worker thread in each cycle; default 10
   --nodes NODES         Number of nodes if mininet is not connected;
                         default=16. If mininet is connected, flows will be
                         evenly distributed (programmed) into connected nodes.
-  --delay DELAY         Time to wait between the add and delete cycles;
-                        default=0
+  --delay DELAY         Time (in seconds) to wait between the add and delete
+                        cycles; default=0
   --delete              Delete all added flows one by one, benchmark delete
                         performance.
   --no-delete           Do not perform the delete cycle.
-  --no-auth             Do not use authenticated access to REST (default)
-  --auth                Use authenticated access to REST (username: 'admin',
-                        password: 'admin').
+  --auth                Use the ODL default username/password 'admin'/'admin'
+                        to authenticate access to REST; default: no
+                        authentication
   --startflow STARTFLOW
                         The starting Flow ID; default=0
+  --file FILE           File from which to read the JSON flow template;
+                        default: no file, use a built in template.
 
 NOTE: The 'startflow' command line parameter is used with multiple 
 flow_config_blasters blasting flows at the same ODL instance. With Python's
@@ -131,7 +140,8 @@ network, flow_config_blaster will evenly distribute flows across the network.
 If ODL is not connected to a network, flows are only stored in the config 
 data store (i.e. nodes that may connect at some point in the future are in 
 effect "preconfigured"). The not-connected mode can be used to test the 
-performance of the data store and the REST subsystems. The 'nodes' parameter determines how many nodes are used in the non-connected mode.
+performance of the data store and the REST subsystems. The 'nodes' parameter
+determines how many nodes are used in the non-connected mode.
 
 Examples:
 ---------
@@ -163,9 +173,14 @@ add/delete cycles type:
 
    NOTE: Both Add and Delete are performed in 10 cycles. 5 worker threads 
    are started in each cycle and the cycle ends when all threads finish. 
-   Another cycle is started when the previous cycle finished. Cycles are
-   useful to determine performance degradation with increasing number of
-   flows in the datastore and in the network.
+   Cycles are useful to determine performance degradation with increasing
+   number of flows in the datastore and in the network.
+
+To  put and then delete 1000 flows with nicira match and action extensions,
+type:
+   >./flow_config_blaster.py --flows=1000 --auth --file=./nicira-ext-all.json
+
+   NOTE: json for flow adds will be taken from the file 'nicira-ext-all.json'
 
 
 
index 1bb13d6be74efb219702f4d2e86e78c83006e785..a541c8fa844dc926e5f4818e26218a9862af87e2 100755 (executable)
@@ -7,12 +7,11 @@ __email__ = "jmedved@cisco.com"
 
 import argparse
 import time
-from flow_config_blaster import FlowConfigBlaster
+from flow_config_blaster import FlowConfigBlaster, get_json_from_file
 from inventory_crawler import InventoryCrawler
 from config_cleanup import cleanup_config
 
 
-
 if __name__ == "__main__":
 
     JSON_FLOW_MOD1 = '''{
@@ -55,7 +54,6 @@ if __name__ == "__main__":
         ]
     }'''
 
-
     parser = argparse.ArgumentParser(description='Flow programming performance test: First adds and then deletes flows '
                                                  'into the config tree, as specified by optional parameters.')
 
@@ -86,14 +84,22 @@ if __name__ == "__main__":
                         help="Use authenticated access to REST (username: 'admin', password: 'admin'); default=False")
     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.')
 
     in_args = parser.parse_args()
 
     # Initialize
+    if in_args.file != '':
+        flow_template = get_json_from_file(in_args.file)
+    else:
+        flow_template = JSON_FLOW_MOD1
+
     ic = InventoryCrawler(in_args.host, in_args.port, 0, 'operational', in_args.auth, False)
 
     fct = FlowConfigBlaster(in_args.host, in_args.port, in_args.cycles, in_args.threads, in_args.nodes,
-                            in_args.flows, in_args.startflow, in_args.auth, JSON_FLOW_MOD1)
+                            in_args.flows, in_args.startflow, in_args.auth, flow_template)
 
     # Get baseline stats
     ic.crawl_inventory()
@@ -118,7 +124,7 @@ if __name__ == "__main__":
     print 'Waiting for stats to catch up:'
     while True:
         ic.crawl_inventory()
-        print '   %d, %d' %(ic.reported_flows, ic.found_flows)
+        print '   %d, %d' % (ic.reported_flows, ic.found_flows)
         if ic.found_flows == exp_found or total_delay > in_args.timeout:
             break
         total_delay += in_args.delay
@@ -135,8 +141,8 @@ if __name__ == "__main__":
         print '\nDeleting all flows in bulk:\n   ',
         cleanup_config(in_args.host, in_args.port, in_args.auth)
     else:
-       print '\nDeleting flows one by one\n   ',
-       fct.delete_blaster()
+        print '\nDeleting flows one by one\n   ',
+        fct.delete_blaster()
 
     # Wait for stats to catch up
     total_delay = 0
@@ -147,7 +153,7 @@ if __name__ == "__main__":
         if ic.found_flows == found or total_delay > in_args.timeout:
             break
         total_delay += in_args.delay
-        print '   %d, %d' %(ic.reported_flows, ic.found_flows)
+        print '   %d, %d' % (ic.reported_flows, ic.found_flows)
         time.sleep(in_args.delay)
 
     if total_delay < in_args.timeout:
index 54d2c6ff10b6d2611c35d608fddb629033520c81..9a1ae165f69a16f9bb98d574b305d1825b6912ca 100755 (executable)
@@ -65,13 +65,16 @@ class FlowConfigBlaster(object):
         self.nflows = nflows
         self.startflow = startflow
         self.auth = auth
+
         self.json_template = json_template
+        self.url_template = 'http://' + self.host + ":" + self.port + '/' + self.FLWURL
 
         self.ok_rate = Counter(0.0)
         self.total_rate = Counter(0.0)
 
         self.ip_addr = Counter(int(netaddr.IPAddress('10.0.0.1')) + startflow)
 
+
         self.print_lock = threading.Lock()
         self.cond = threading.Condition()
         self.threads_done = 0
@@ -108,14 +111,14 @@ class FlowConfigBlaster(object):
         return nodes
 
 
-    def add_flow(self, session, url_template, tid, node, flow_id, ipaddr):
+    def add_flow(self, session, tid, node, flow_id, ipaddr):
         """
         Adds a single flow to the config data store via REST
         """
         flow_data = self.json_template % (tid + flow_id, 'TestFlow-%d' % flow_id, 65000,
-                                      str(flow_id), 65000, str(netaddr.IPAddress(ipaddr)))
+                                          str(flow_id), 65000, str(netaddr.IPAddress(ipaddr)))
         # print flow_data
-        flow_url = url_template % (node, flow_id)
+        flow_url = self.url_template % (node, flow_id)
         # print flow_url
 
         if not self.auth:
@@ -131,8 +134,6 @@ class FlowConfigBlaster(object):
         Adds flows into the ODL config space. This function is executed by a worker thread
         """
 
-        put_url = 'http://' + self.host + ":" + self.port + '/' + self.FLWURL
-
         add_res = {200: 0}
 
         s = requests.Session()
@@ -147,7 +148,7 @@ class FlowConfigBlaster(object):
                 node_id = randrange(1, n_nodes + 1)
                 flow_id = tid * (self.ncycles * self.nflows) + flow + start_flow + self.startflow
                 self.flows[tid][flow_id] = node_id
-                sts = self.add_flow(s, put_url, tid, node_id, flow_id, self.ip_addr.increment())
+                sts = self.add_flow(s, tid, node_id, flow_id, self.ip_addr.increment())
                 try:
                     add_res[sts] += 1
                 except KeyError:
@@ -175,8 +176,17 @@ class FlowConfigBlaster(object):
             self.cond.notifyAll()
 
 
-    def delete_flow(self, session, url_template, node, flow_id):
-        flow_url = url_template % (node, flow_id)
+    def delete_flow(self, session, node, flow_id):
+        """
+        Deletes a single flow from the ODL config data store via REST
+
+        :param session:
+        :param url_template:
+        :param node:
+        :param flow_id:
+        :return:
+        """
+        flow_url = self.url_template % (node, flow_id)
 
         if not self.auth:
             r = session.delete(flow_url, headers=self.getheaders)
@@ -191,8 +201,6 @@ 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
         """
-        del_url = 'http://' + self.host + ":" + self.port + '/' + self.FLWURL
-
         del_res = {200: 0}
 
         s = requests.Session()
@@ -204,7 +212,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, del_url, self.flows[tid][flow_id], flow_id)
+                sts = self.delete_flow(s, self.flows[tid][flow_id], flow_id)
                 try:
                     del_res[sts] += 1
                 except KeyError:
@@ -248,14 +256,18 @@ class FlowConfigBlaster(object):
                 threads.append(t)
                 t.start()
 
-            # Wait for all threads to finish
-            while self.threads_done < self.nthreads:
-                with self.cond:
-                    self.cond.wait()
+            # 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()
 
             with self.print_lock:
-                print '    Overall success rate:  %.2f, Overall rate: %.2f' % (
-                    self.ok_rate.value, self.total_rate.value)
+                print '    Total success rate: %.2f, Total rate: %.2f' % (
+                      self.ok_rate.value, self.total_rate.value)
+                measured_rate = self.nthreads * self.nflows * self.ncycles / t.secs
+                print '    Measured rate:      %.2f (%.2f%% of Total success rate)' % \
+                      (measured_rate, measured_rate / self.total_rate.value * 100)
                 self.threads_done = 0
 
             self.ok_rate.value = 0
@@ -275,6 +287,12 @@ class FlowConfigBlaster(object):
         return self.ok_total
 
 
+def get_json_from_file(filename):
+    with open(filename, 'r') as f:
+        read_data = f.read()
+    return read_data
+
+
 if __name__ == "__main__":
 
     JSON_FLOW_MOD1 = '''{
@@ -317,7 +335,6 @@ if __name__ == "__main__":
         ]
     }'''
 
-
     parser = argparse.ArgumentParser(description='Flow programming performance test: First adds and then deletes flows '
                                                  'into the config tree, as specified by optional parameters.')
 
@@ -325,34 +342,44 @@ if __name__ == "__main__":
                         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('--flows', type=int, default=10,
-                        help='Number of flow add/delete requests to send in each cycle; default 10')
     parser.add_argument('--cycles', type=int, default=1,
-                        help='Number of flow add/delete cycles to send in each thread; 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, default=1. '
-                             'Each thread will add/delete nflows.')
+                        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('--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 to wait between the add and delete cycles; 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('--no-auth', dest='auth', action='store_false', default=False,
-                        help="Do not use authenticated access to REST (default)")
-    parser.add_argument('--auth', dest='auth', action='store_true',
-                        help="Use authenticated access to REST (username: 'admin', password: 'admin').")
+    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.')
 
     in_args = parser.parse_args()
 
+    if in_args.file != '':
+        flow_template = get_json_from_file(in_args.file)
+    else:
+        flow_template = JSON_FLOW_MOD1
+
     fct = FlowConfigBlaster(in_args.host, in_args.port, in_args.cycles, in_args.threads, in_args.nodes,
-                            in_args.flows, in_args.startflow, in_args.auth, JSON_FLOW_MOD1)
+                            in_args.flows, in_args.startflow, in_args.auth, flow_template)
 
     # Run through <cycles>, where <threads> are started in each cycle and <flows> are added from each thread
     fct.add_blaster()
diff --git a/test/tools/odl-mdsal-clustering-tests/clustering-performance-test/multi-blaster.sh b/test/tools/odl-mdsal-clustering-tests/clustering-performance-test/multi-blaster.sh
new file mode 100755 (executable)
index 0000000..f4296a1
--- /dev/null
@@ -0,0 +1,18 @@
+#!/usr/bin/env bash
+
+echo "Starting Blaster 1:"
+./flow_config_blaster.py  --flows=1000 --threads=5 --auth --no-delete &
+
+echo "Starting Blaster 2:"
+./flow_config_blaster.py  --flows=1000 --threads=5 --auth --no-delete --startflow=5000 &
+
+echo "Starting Blaster 3:"
+./flow_config_blaster.py  --flows=1000 --threads=5 --auth --no-delete --startflow=10000 &
+
+echo "Starting Blaster 4:"
+./flow_config_blaster.py  --flows=1000 --threads=5 --auth --no-delete --startflow=15000 &
+
+echo "Starting Blaster 5:"
+./flow_config_blaster.py  --flows=1000 --threads=5 --auth --no-delete --startflow=20000 &
+
+echo "Done."
diff --git a/test/tools/odl-mdsal-clustering-tests/clustering-performance-test/nicira-ext-all.json b/test/tools/odl-mdsal-clustering-tests/clustering-performance-test/nicira-ext-all.json
new file mode 100644 (file)
index 0000000..b48aecd
--- /dev/null
@@ -0,0 +1,83 @@
+{
+    "flow-node-inventory:flow": [
+        {
+            "flow-node-inventory:cookie": %d,
+            "flow-node-inventory:cookie_mask": 65535,
+            "flow-node-inventory:flow-name": "%s",
+            "flow-node-inventory:hard-timeout": %d,
+            "flow-node-inventory:id": "%s",
+            "flow-node-inventory:idle-timeout": %d,
+            "flow-node-inventory:installHw": false,
+            "flow-node-inventory:instructions": {
+                "flow-node-inventory:instruction": [
+                    {
+                        "flow-node-inventory:apply-actions": {
+                            "flow-node-inventory:action": [
+                                {
+                                    "flow-node-inventory:dec-nw-ttl": {},
+                                    "flow-node-inventory:order": 0
+                                },
+                                {
+                                    "openflowplugin-extension-nicira-action:nx-reg-load": {
+                                        "dst": {
+                                            "end": 31,
+                                            "of-eth-src": "",
+                                            "start": 0
+                                        },
+                                        "value": 3232249877
+                                    },
+                                    "order": 2
+                                },
+                                {
+                                    "openflowplugin-extension-nicira-action:nx-reg-load": {
+                                        "dst": {
+                                            "end": 31,
+                                            "nx-tun-ipv4-dst": "",
+                                            "start": 0
+                                        },
+                                        "value": 3232249877
+                                    },
+                                    "order": 1
+                                },
+                                {
+                                    "openflowplugin-extension-nicira-action:nx-reg-load": {
+                                        "dst": {
+                                            "end": 31,
+                                            "of-eth-dst": "",
+                                            "start": 0
+                                        },
+                                        "value": 3232249877
+                                    },
+                                    "order": 3
+                                }
+                            ]
+                        },
+                        "flow-node-inventory:order": 0
+                    }
+                ]
+            },
+            "flow-node-inventory:match": {
+                "flow-node-inventory:ipv4-destination": "%s/32",
+                "flow-node-inventory:ethernet-match": {
+                    "flow-node-inventory:ethernet-type": {
+                        "flow-node-inventory:type": 2048
+                    }
+                },
+                "openflowplugin-extension-general:extension-list": [
+                    {
+                        "extension": {
+                            "openflowplugin-extension-nicira-match:nxm-nx-reg": {
+                                "reg": "nicira-match:nxm-nx-reg0",
+                                "value": 42
+                            }
+                        },
+                        "extension-key": "openflowplugin-extension-nicira-match:nxm-nx-reg0-key"
+                    }
+                ]
+            },
+            "flow-node-inventory:priority": 2,
+            "flow-node-inventory:strict": false,
+            "flow-node-inventory:table_id": 0
+        }
+    ]
+}