Removal of interfaces used by the packetcable-consumer bundle that has neither been...
[packetcable.git] / packetcable-client / flow_config_perf_pcmm.py
1 __author__ = "Jan Medved"
2 __copyright__ = "Copyright(c) 2014, Cisco Systems, Inc."
3 __license__ = "New-style BSD"
4 __email__ = "jmedved@cisco.com"
5
6 from random import randrange
7 import json
8 import argparse
9 import requests
10 import time
11 import threading
12 import re
13
14 class Counter(object):
15     def __init__(self, start=0):
16         self.lock = threading.Lock()
17         self.value = start
18     def increment(self, value=1):
19         self.lock.acquire()
20         try:
21             self.value = self.value + value
22         finally:
23             self.lock.release()
24
25
26 class Timer(object):
27     def __init__(self, verbose=False):
28         self.verbose = verbose
29
30     def __enter__(self):
31         self.start = time.time()
32         return self
33
34     def __exit__(self, *args):
35         self.end = time.time()
36         self.secs = self.end - self.start
37         self.msecs = self.secs * 1000  # millisecs
38         if self.verbose:
39             print ("elapsed time: %f ms" % self.msecs)
40
41
42 putheaders = {'content-type': 'application/json'}
43 getheaders = {'Accept': 'application/json'}
44 # ODL IP:port
45 # We fist delete all existing service functions
46 DELURL  = "restconf/config/opendaylight-inventory:nodes/node/openflow:%d/table/0/flow/%d"
47 GETURL  = "restconf/config/opendaylight-inventory:nodes/node/openflow:%d/table/0/flow/%d"
48 # Incremental PUT. This URL is for a list element
49 PUTURL  = "restconf/config/opendaylight-inventory:nodes/node/openflow:%d/table/0/flow/%d"
50
51 INVURL = 'restconf/operational/opendaylight-inventory:nodes'
52 N1T0_URL = 'restconf/operational/opendaylight-inventory:nodes/node/openflow:1/table/0'
53
54
55 print_lock = threading.Lock()
56 threads_done = 0
57
58 JSON_FLOW_MOD1 = '''{
59     "flow-node-inventory:flow": [
60         {
61             "flow-node-inventory:cookie": %d,
62             "flow-node-inventory:cookie_mask": 65535,
63             "flow-node-inventory:flow-name": "%s",
64             "flow-node-inventory:hard-timeout": %d,
65             "flow-node-inventory:id": "%s",
66             "flow-node-inventory:idle-timeout": %d,
67             "flow-node-inventory:installHw": false,
68             "flow-node-inventory:instructions": {
69                 "flow-node-inventory:instruction": [
70                     {
71                         "flow-node-inventory:apply-actions": {
72                             "flow-node-inventory:action": [
73                                 {
74                                     "flow-node-inventory:dec-nw-ttl": {},
75                                     "flow-node-inventory:order": 0
76                                 }
77                             ]
78                         },
79                         "flow-node-inventory:order": 0
80                     }
81                 ]
82             },
83             "flow-node-inventory:match": {
84                 "flow-node-inventory:metadata": {
85                     "flow-node-inventory:metadata": %d
86                 }
87             },
88             "flow-node-inventory:priority": 2,
89             "flow-node-inventory:strict": false,
90             "flow-node-inventory:table_id": 0
91         }
92     ]
93 }'''
94
95 add_ok_rate = Counter(0.0)
96 add_total_rate = Counter(0.0)
97 del_ok_rate = Counter(0.0)
98 del_total_rate = Counter(0.0)
99
100 flows = {}
101
102 def add_flow(session, url_template, res, tid, node, flow_id, metadata):
103     flow_data = JSON_FLOW_MOD1 % (tid + flow_id, 'TestFlow-%d' % flow_id, 65000,
104                                   str(flow_id), 65000, metadata)
105     flow_url = url_template % (node, flow_id)
106     r = session.put(flow_url, data=flow_data, headers=putheaders, stream=False )
107
108     try:
109         res[r.status_code] += 1
110     except(KeyError):
111         res[r.status_code] = 1
112
113
114 def delete_flow(session, url_template, res, tid, node, flow_id):
115     flow_url = url_template % (node, flow_id)
116     r = session.delete(flow_url, headers=getheaders)
117     try:
118         res[r.status_code] += 1
119     except(KeyError):
120         res[r.status_code] = 1
121
122
123 def get_num_nodes(session, inventory_url, default_nodes):
124     """
125     Determines the number of OF nodes in the connected mininet network. If
126     mininet is not connected, the default number of flows is 16
127     """
128     nodes = default_nodes
129     r = session.get(inventory_url, headers=getheaders, stream=False )
130     if (r.status_code == 200):
131         try:
132             inv = json.loads(r.content)['nodes']['node']
133             nn = 0
134             for n in range(len(inv)):
135                 if re.search('openflow', inv[n]['id']) != None:
136                     nn = nn + 1
137             if nn != 0:
138                 nodes = nn
139         except(KeyError):
140             pass
141
142     return nodes
143
144 def add_flows(put_url, nnodes, nflows, start_flow, tid, cond):
145     """
146     The function that add flows into the ODL config space.
147     """
148     global threads_done
149
150     add_res = {}
151     add_res[200] = 0
152
153     s = requests.Session()
154
155     nnodes = get_num_nodes(s, inv_url, nnodes)
156
157     with print_lock:
158         print '    Thread %d:\n        Adding %d flows on %d nodes' % (tid, nflows, nnodes)
159
160     with Timer() as t:
161         for flow in range(nflows):
162             node_id = randrange(1, nnodes+1)
163             flow_id = tid*100000 + flow + start_flow
164             flows[tid][flow_id] = node_id
165             add_flow(s, put_url, add_res, tid, node_id, flow_id, flow*2+1)
166
167     add_time = t.secs
168     add_ok_rate_t = add_res[200]/add_time
169     add_total_rate_t = sum(add_res.values())/add_time
170
171     add_ok_rate.increment(add_ok_rate_t)
172     add_total_rate.increment(add_total_rate_t)
173
174     with print_lock:
175         print '    Thread %d: ' % tid
176         print '        Add time: %.2f,' % add_time
177         print '        Add success rate:  %.2f, Add total rate: %.2f' % \
178                         (add_ok_rate_t, add_total_rate_t)
179         print '        Add Results: ',
180         print add_res
181         threads_done = threads_done + 1
182
183     s.close()
184
185     with cond:
186         cond.notifyAll()
187
188
189 def delete_flows(del_url, nnodes, nflows, start_flow, tid, cond):
190     """
191     The function that deletes flow from the ODL config space that have been
192     added using the 'add_flows()' function.
193     """
194     global threads_done
195
196     del_res = {}
197     del_res[200] = 0
198
199     s = requests.Session()
200     nnodes = get_num_nodes(s, inv_url, nnodes)
201
202     with print_lock:
203         print 'Thread %d: Deleting %d flows on %d nodes' % (tid, nflows, nnodes)
204
205     with Timer() as t:
206         for flow in range(nflows):
207             flow_id = tid*100000 + flow + start_flow
208             delete_flow(s, del_url, del_res, 100, flows[tid][flow_id], flow_id)
209
210     del_time = t.secs
211
212     del_ok_rate_t = del_res[200]/del_time
213     del_total_rate_t = sum(del_res.values())/del_time
214
215     del_ok_rate.increment(del_ok_rate_t)
216     del_total_rate.increment(del_total_rate_t)
217
218     with print_lock:
219         print '    Thread %d: ' % tid
220         print '        Delete time: %.2f,' % del_time
221         print '        Delete success rate:  %.2f, Delete total rate: %.2f' % \
222                         (del_ok_rate_t, del_total_rate_t)
223         print '        Delete Results: ',
224         print del_res
225         threads_done = threads_done + 1
226
227     s.close()
228
229     with cond:
230         cond.notifyAll()
231
232
233 def driver(function, ncycles, nthreads, nnodes, nflows, url, cond, ok_rate, total_rate):
234     """
235     The top-level driver function that drives the execution of the flow-add and
236     flow-delete tests.
237     """
238     global threads_done
239
240     for c in range(ncycles):
241         with print_lock:
242             print '\nCycle %d:' % c
243
244         threads = []
245         for i in range(nthreads):
246             t = threading.Thread(target=function,
247                                  args=(url, nnodes, nflows, c*nflows, i, cond))
248             threads.append(t)
249             t.start()
250
251         # Wait for all threads to finish
252         while threads_done < in_args.nthreads:
253             with cond:
254                 cond.wait()
255
256         with print_lock:
257              print '    Overall success rate:  %.2f, Overall rate: %.2f' % \
258                             (ok_rate.value, total_rate.value)
259              threads_done = 0
260
261         ok_rate.value = 0
262         total_rate.value = 0
263
264
265 if __name__ == "__main__":
266     parser = argparse.ArgumentParser(description='Flow programming performance test: '
267                                      'First adds and then deletes flows into '
268                                      'the config tree, as specified by optional parameters.')
269     parser.add_argument('--odlhost', default='127.0.0.1', help='Host where '
270                         'odl controller is running (default is 127.0.0.1)')
271     parser.add_argument('--odlport', default='8080', help='Port on '
272                         'which odl\'s RESTCONF is listening (default is 8080)')
273     parser.add_argument('--nflows', type=int, default=10, help='Number of '
274                         'flow add/delete requests to send in  each cycle; default 10')
275     parser.add_argument('--ncycles', type=int, default=1, help='Number of '
276                         'flow add/delete cycles to send in each thread; default 1')
277     parser.add_argument('--nthreads', type=int, default=1,
278                         help='Number of request worker threads, default=1. '
279                         'Each thread will add/delete nflows.')
280     parser.add_argument('--nnodes', type=int, default=16,
281                         help='Number of nodes if mininet is not connected, default=16. '
282                         'If mininet is connected, flows will be evenly distributed '
283                         '(programmed) into connected nodes.')
284     parser.add_argument('--delete', dest='delete', action='store_true', default=True,
285                         help='Delete all added flows one by one, benchmark delete '
286                         'performance.')
287     parser.add_argument('--no-delete', dest='delete', action='store_false',
288                         help='Add flows and leave them in the config data store.')
289
290     in_args = parser.parse_args()
291
292     put_url = 'http://' + in_args.odlhost + ":" + in_args.odlport + '/' + PUTURL
293     del_url = 'http://' + in_args.odlhost + ":" + in_args.odlport + '/' + DELURL
294     get_url = 'http://' + in_args.odlhost + ":" + in_args.odlport + '/' + GETURL
295     inv_url = 'http://' + in_args.odlhost + ":" + in_args.odlport + '/' + INVURL
296
297     cond = threading.Condition()
298
299     # Initialize the flows array
300     for i in range(in_args.nthreads):
301         flows[i] = {}
302
303     # Run through ncycles, where nthreads are started in each cycles and
304     # nflows added from each thread
305     driver(add_flows, in_args.ncycles, in_args.nthreads, in_args.nnodes, \
306            in_args.nflows, put_url, cond, add_ok_rate, add_total_rate)
307
308
309     # Run through ncycles, where nthreads are started in each cycles and
310     # nflows added from each thread
311     if in_args.delete == True:
312         driver(delete_flows, in_args.ncycles, in_args.nthreads, in_args.nnodes, \
313                in_args.nflows, del_url, cond, del_ok_rate, del_total_rate)