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
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
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:
---------
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'
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 = '''{
]
}'''
-
parser = argparse.ArgumentParser(description='Flow programming performance test: First adds and then deletes flows '
'into the config tree, as specified by optional parameters.')
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()
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
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
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:
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
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:
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()
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:
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)
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()
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:
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
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 = '''{
]
}'''
-
parser = argparse.ArgumentParser(description='Flow programming performance test: First adds and then deletes flows '
'into the config tree, as specified by optional parameters.')
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()
--- /dev/null
+#!/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."
--- /dev/null
+{
+ "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
+ }
+ ]
+}