Test improvements and fixes for image creation
[samplevnf.git] / VNFs / DPPD-PROX / helper-scripts / openstackrapid / runrapid.py
1 #!/usr/bin/python
2
3 ##
4 ## Copyright (c) 2010-2017 Intel Corporation
5 ##
6 ## Licensed under the Apache License, Version 2.0 (the "License");
7 ## you may not use this file except in compliance with the License.
8 ## You may obtain a copy of the License at
9 ##
10 ##     http://www.apache.org/licenses/LICENSE-2.0
11 ##
12 ## Unless required by applicable law or agreed to in writing, software
13 ## distributed under the License is distributed on an "AS IS" BASIS,
14 ## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 ## See the License for the specific language governing permissions and
16 ## limitations under the License.
17 ##
18
19 from __future__ import print_function
20
21 import os
22 import stat
23 import sys
24 import time
25 import subprocess
26 import getopt
27 import re
28 import logging
29 from logging.handlers import RotatingFileHandler
30 from logging import handlers
31 from prox_ctrl import prox_ctrl
32 import ConfigParser
33 import ast
34 import atexit
35 import csv
36
37 version="18.10.15"
38 env = "rapid" #Default string for environment
39 test = "basicrapid" #Default string for test
40 loglevel="DEBUG" # sets log level for writing to file
41 runtime=10 # time in seconds for 1 test run
42 configonly = False # IF True, the system will upload all the necessary config fiels to the VMs, but not start PROX and the actual testing
43
44 def usage():
45         print("usage: runrapid    [--version] [-v]")
46         print("                   [--env ENVIRONMENT_NAME]")
47         print("                   [--test TEST_NAME]")
48         print("                   [--runtime TIME_FOR_TEST]")
49         print("                   [--configonly False|True]")
50         print("                   [--log DEBUG|INFO|WARNING|ERROR|CRITICAL]")
51         print("                   [-h] [--help]")
52         print("")
53         print("Command-line interface to runrapid")
54         print("")
55         print("optional arguments:")
56         print("  -v,  --version                 Show program's version number and exit")
57         print("  --env ENVIRONMENT_NAME         Parameters will be read from ENVIRONMENT_NAME.env Default is %s."%env)
58         print("  --test TEST_NAME               Test cases will be read from TEST_NAME.test Default is %s."%test)
59         print("  --runtime                      Specify time in seconds for 1 test run")
60         print("  --configonly                   If True, only upload all config files to the VMs, do not run the tests. Default is %s."%configonly)
61         print("  --log                          Specify logging level for log file output, screen output level is hard coded")
62         print("  -h, --help                     Show help message and exit.")
63         print("")
64
65 try:
66         opts, args = getopt.getopt(sys.argv[1:], "vh", ["version","help", "env=", "test=","runtime=","configonly=","log="])
67 except getopt.GetoptError as err:
68         print("===========================================")
69         print(str(err))
70         print("===========================================")
71         usage()
72         sys.exit(2)
73 if args:
74         usage()
75         sys.exit(2)
76 for opt, arg in opts:
77         if opt in ("-h", "--help"):
78                 usage()
79                 sys.exit()
80         if opt in ("-v", "--version"):
81                 print("Rapid Automated Performance Indication for Dataplane "+version)
82                 sys.exit()
83         if opt in ("--env"):
84                 env = arg
85                 print ("Using '"+env+"' as name for the environment")
86         if opt in ("--test"):
87                 test = arg
88                 print ("Using '"+test+".test' for test case definition")
89         if opt in ("--runtime"):
90                 runtime = arg
91                 print ("Runtime: "+ runtime)
92         if opt in ("--configonly"):
93                 configonly = arg
94                 print ("configonly: "+ configonly)
95         if opt in ("--log"):
96                 loglevel = arg
97                 print ("Log level: "+ loglevel)
98
99 class bcolors:
100     HEADER = '\033[95m'
101     OKBLUE = '\033[94m'
102     OKGREEN = '\033[92m'
103     WARNING = '\033[93m'
104     FAIL = '\033[91m'
105     ENDC = '\033[0m'
106     BOLD = '\033[1m'
107     UNDERLINE = '\033[4m'
108
109 # create formatters
110 screen_formatter = logging.Formatter("%(message)s")
111 file_formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")
112
113 # get a top-level logger,
114 # set its log level,
115 # BUT PREVENT IT from propagating messages to the root logger
116 #
117 log = logging.getLogger()
118 numeric_level = getattr(logging, loglevel.upper(), None)
119 if not isinstance(numeric_level, int):
120     raise ValueError('Invalid log level: %s' % loglevel)
121 log.setLevel(numeric_level)
122 log.propagate = 0
123
124 # create a console handler
125 # and set its log level to the command-line option 
126
127 console_handler = logging.StreamHandler(sys.stdout)
128 console_handler.setLevel(logging.INFO)
129 console_handler.setFormatter(screen_formatter)
130
131 # create a file handler
132 # and set its log level to DEBUG
133 #
134 log_file = 'RUN' +env+'.'+test+'.log'
135 data_file = 'RUN' +env+'.'+test+'.csv'
136 file_handler = logging.handlers.RotatingFileHandler(log_file, backupCount=10)
137 #file_handler = log.handlers.TimedRotatingFileHandler(log_file, 'D', 1, 5)
138 file_handler.setLevel(numeric_level)
139 file_handler.setFormatter(file_formatter)
140
141 # add handlers to the logger
142 #
143 log.addHandler(file_handler)
144 log.addHandler(console_handler)
145
146 # Check if log exists and should therefore be rolled
147 needRoll = os.path.isfile(log_file)
148
149
150 # This is a stale log, so roll it
151 if needRoll:    
152     # Add timestamp
153     log.debug('\n---------\nLog closed on %s.\n---------\n' % time.asctime())
154
155     # Roll over on application start
156     log.handlers[0].doRollover()
157
158 # Add timestamp
159 log.debug('\n---------\nLog started on %s.\n---------\n' % time.asctime())
160
161 log.debug("runrapid.py version: "+version)
162 #========================================================================
163 def connect_socket(client):
164         attempts = 1
165         log.debug("Trying to connect to PROX (just launched) on %s, attempt: %d" % (client.ip(), attempts))
166         sock = None
167         while True:
168                 sock = client.prox_sock()
169                 if sock is not None:
170                         break
171                 attempts += 1
172                 if attempts > 20:
173                         log.exception("Failed to connect to PROX on %s after %d attempts" % (client.ip(), attempts))
174                         raise Exception("Failed to connect to PROX on %s after %d attempts" % (client.ip(), attempts))
175                 time.sleep(2)
176                 log.debug("Trying to connect to PROX (just launched) on %s, attempt: %d" % (client.ip(), attempts))
177         log.info("Connected to PROX on %s" % client.ip())
178         return sock
179
180 def connect_client(client):
181         attempts = 1
182         log.debug("Trying to connect to VM which was just launched on %s, attempt: %d" % (client.ip(), attempts))
183         while True:
184                 try:
185                         client.connect()
186                         break
187                 except RuntimeWarning, ex:
188                         attempts += 1
189                         if attempts > 20:
190                                 log.exception("Failed to connect to VM after %d attempts:\n%s" % (attempts, ex))
191                                 raise Exception("Failed to connect to VM after %d attempts:\n%s" % (attempts, ex))
192                         time.sleep(2)
193                         log.debug("Trying to connect to VM which was just launched on %s, attempt: %d" % (client.ip(), attempts))
194         log.debug("Connected to VM on %s" % client.ip())
195
196 def run_iteration(gensock,sutsock):
197         sleep_time = 3
198         # Sleep_time is needed to be able to do accurate measurements to check for packet loss. We need to make this time large enough so that we do not take the first measurement while some packets from the previous tests migth still be in flight
199         time.sleep(sleep_time)
200         abs_old_rx, abs_old_tx, abs_old_drop, abs_old_tsc, abs_tsc_hz = gensock.core_stats(genstatcores)
201         gensock.start(gencores)
202         time.sleep(sleep_time)
203         if sutsock!='none':
204                 old_sut_rx, old_sut_tx, old_sut_drop, old_sut_tsc, sut_tsc_hz = sutsock.core_stats(sutstatcores)
205         old_rx, old_tx, old_drop, old_tsc, tsc_hz = gensock.core_stats(genstatcores)
206         time.sleep(float(runtime))
207         lat_min, lat_max, lat_avg = gensock.lat_stats(latcores)
208         # Get statistics after some execution time
209         new_rx, new_tx, new_drop, new_tsc, tsc_hz = gensock.core_stats(genstatcores)
210         if sutsock!='none':
211                 new_sut_rx, new_sut_tx, new_sut_drop, new_sut_tsc, sut_tsc_hz = sutsock.core_stats(sutstatcores)
212         #Stop generating
213         gensock.stop(gencores)
214         time.sleep(sleep_time)
215         abs_new_rx, abs_new_tx, abs_new_drop, abs_new_tsc, abs_tsc_hz = gensock.core_stats(genstatcores)
216         drop = new_drop-old_drop # drop is all packets dropped by all tasks. This includes packets dropped at the generator task + packets dropped by the nop task. In steady state, this equals to the number of packets received by this VM
217         rx = new_rx - old_rx     # rx is all packets received by the nop task = all packets received in the gen VM
218         tx = new_tx - old_tx     # tx is all generated packets actually accepted by the interface
219         abs_dropped = (abs_new_tx - abs_old_tx) - (abs_new_rx - abs_old_rx)
220         tsc = new_tsc - old_tsc  # time difference between the 2 measurements, expressed in cycles.
221         pps_req_tx = (tx+drop-rx)*tsc_hz*1.0/(tsc*1000000)
222         pps_tx = tx*tsc_hz*1.0/(tsc*1000000)
223         pps_rx = rx*tsc_hz*1.0/(tsc*1000000)
224         if sutsock!='none':
225                 sut_rx = new_sut_rx - old_sut_rx
226                 sut_tx = new_sut_tx - old_sut_tx
227                 sut_tsc = new_sut_tsc - old_sut_tsc
228                 pps_sut_tx = sut_tx*sut_tsc_hz*1.0/(sut_tsc*1000000)
229                 pps_sut_tx_str = '{:>9.3f}'.format(pps_sut_tx)
230         else:
231                 pps_sut_tx = 0
232                 pps_sut_tx_str = 'NO MEAS.'
233         if (tx == 0):
234                 log.critical("TX = 0. Test interrupted since no packet has been sent.")
235                 raise Exception("TX = 0")
236         return(pps_req_tx,pps_tx,pps_sut_tx_str,pps_rx,lat_avg,lat_max,abs_dropped,(abs_new_tx - abs_old_tx))
237
238 def new_speed(speed,minspeed,maxspeed,success):
239         # Following calculates the ratio for the new speed to be applied
240         # On the Y axis, we will find the ratio, a number between 0 and 1
241         # On the x axis, we find the % of dropped packets, a number between 0 and 100
242         # 2 lines are drawn and we take the minumun of these lines to calculate the ratio
243         # One line goes through (0,y0) and (p,q)
244         # The second line goes through (p,q) and (100,y100)
245 #       y0=0.99
246 #       y100=0.1
247 #       p=1
248 #       q=.99
249 #       ratio = min((q-y0)/p*drop_rate+y0,(q-y100)/(p-100)*drop_rate+q-p*(q-y100)/(p-100))
250 #       return (int(speed*ratio*100)+0.5)/100.0
251         if success:
252                 minspeed = speed
253         else:
254                 maxspeed = speed
255         newspeed = (maxspeed+minspeed)/2.0
256         return (newspeed,minspeed,maxspeed)
257
258 def get_pps(speed,size):
259         return (speed * 100.0 / (8*(size+24)))
260
261 def run_speedtest(gensock,sutsock):
262         log.info("+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+")
263         log.info("| Generator is sending UDP (1 flow) packets (64 bytes) to SUT. SUT sends packets back                                                                                          |")
264         log.info("+--------+--------------------+----------------+----------------+----------------+----------------+----------------+----------------+----------------+------------+------------+")
265         log.info("| Test   |  Speed requested   | Sent to NIC    |  Sent by Gen   | Forward by SUT |  Rec. by Gen   |  Avg. Latency  |  Max. Latency  |  Packets Lost  | Loss Ratio | Result     |")
266         log.info("+--------+--------------------+----------------+----------------+----------------+----------------+----------------+----------------+----------------+------------+------------+")
267         maxspeed = speed = STARTSPEED
268         minspeed = 0
269         size=60
270         attempts = 0
271         endpps_sut_tx_str = 'NO_RESULTS'
272         gensock.set_size(gencores,0,size) # This is setting the frame size
273         gensock.set_value(gencores,0,16,(size-14),2) # 18 is the difference between the frame size and IP size = size of (MAC addresses, ethertype and FCS)
274         gensock.set_value(gencores,0,38,(size-34),2) # 38 is the difference between the frame size and UDP size = 18 + size of IP header (=20)
275         # This will only work when using sending UDP packets. For different protocols and ethernet types, we would need a different calculation
276         while (maxspeed-minspeed > ACCURACY):
277                 attempts += 1
278                 print('Measurement ongoing at speed: ' + str(round(speed,2)) + '%      ',end='\r')
279                 sys.stdout.flush()
280                 # Start generating packets at requested speed (in % of a 10Gb/s link)
281                 gensock.speed(speed, gencores)
282                 time.sleep(1)
283                 # Get statistics now that the generation is stable and NO ARP messages any more
284                 pps_req_tx,pps_tx,pps_sut_tx_str,pps_rx,lat_avg,lat_max, abs_dropped, abs_tx = run_iteration(gensock,sutsock)
285                 drop_rate = 100.0*abs_dropped/abs_tx
286                 if ((get_pps(speed,size) - pps_tx)/get_pps(speed,size))<0.001 and ((drop_rate < DROP_RATE_TRESHOLD) or (abs_dropped==DROP_RATE_TRESHOLD ==0)) and (lat_avg< LAT_AVG_TRESHOLD) and (lat_max < LAT_MAX_TRESHOLD):
287                         log.info('|{:>7}'.format(str(attempts))+" | " + '{:>5.1f}'.format(speed) + '% ' +'{:>6.3f}'.format(get_pps(speed,size)) + ' Mpps | '+ '{:>9.3f}'.format(pps_req_tx)+' Mpps | '+ '{:>9.3f}'.format(pps_tx) +' Mpps | ' + '{:>9}'.format(pps_sut_tx_str) +' Mpps | '+ '{:>9.3f}'.format(pps_rx)+' Mpps | '+ '{:>9.0f}'.format(lat_avg)+' us   | '+  '{:>9.0f}'.format(lat_max)+' us   | '+ '{:>14d}'.format(abs_dropped)+ ' |''{:>9.2f}'.format(drop_rate)+ '%  | SUCCESS    |')
288                         endspeed = speed
289                         endpps_req_tx = pps_req_tx
290                         endpps_tx = pps_tx
291                         endpps_sut_tx_str = pps_sut_tx_str
292                         endpps_rx = pps_rx
293                         endlat_avg = lat_avg
294                         endlat_max = lat_max
295                         endabs_dropped = abs_dropped
296                         enddrop_rate = drop_rate
297                         success = True 
298                 else:
299                         abs_drop_rate_prefix = bcolors.ENDC
300                         if ((abs_dropped>0) and (DROP_RATE_TRESHOLD ==0)):
301                                 abs_drop_rate_prefix = bcolors.FAIL
302                         if (drop_rate < DROP_RATE_TRESHOLD):
303                                 drop_rate_prefix = bcolors.ENDC
304                         else:
305                                 drop_rate_prefix = bcolors.FAIL
306                         if (lat_avg< LAT_AVG_TRESHOLD):
307                                 lat_avg_prefix = bcolors.ENDC
308                         else:
309                                 lat_avg_prefix = bcolors.FAIL
310                         if (lat_max< LAT_MAX_TRESHOLD):
311                                 lat_max_prefix = bcolors.ENDC
312                         else:
313                                 lat_max_prefix = bcolors.FAIL
314                         if (((get_pps(speed,size) - pps_tx)/get_pps(speed,size))<0.001):
315                                 speed_prefix = bcolors.ENDC
316                         else:
317                                 speed_prefix = bcolors.FAIL
318                         log.info('|{:>7}'.format(str(attempts))+" | " + '{:>5.1f}'.format(speed) + '% '+speed_prefix +'{:>6.3f}'.format(get_pps(speed,size)) + ' Mpps | '+ '{:>9.3f}'.format(pps_req_tx)+' Mpps | '+ bcolors.ENDC + '{:>9.3f}'.format(pps_tx) +' Mpps | ' + '{:>9}'.format(pps_sut_tx_str) +' Mpps | '+ '{:>9.3f}'.format(pps_rx)+' Mpps | '+lat_avg_prefix+ '{:>9.0f}'.format(lat_avg)+' us   | '+lat_max_prefix+ '{:>9.0f}'.format(lat_max)+' us   | '+ abs_drop_rate_prefix + '{:>14d}'.format(abs_dropped)+drop_rate_prefix+ ' |''{:>9.2f}'.format(drop_rate)+bcolors.ENDC+ '%  | FAILED     |')
319                         success = False 
320                 speed,minspeed,maxspeed = new_speed(speed,minspeed,maxspeed,success)
321         if endpps_sut_tx_str <>  'NO_RESULTS':
322                 log.info("+--------+--------------------+----------------+----------------+----------------+----------------+----------------+----------------+----------------+------------+------------+")
323                 log.info('|{:>7}'.format('END')+" | " + '{:>5.1f}'.format(endspeed) + '% ' +'{:>6.3f}'.format(get_pps(endspeed,size)) + ' Mpps | '+ '{:>9.3f}'.format(endpps_req_tx)+' Mpps | '+ '{:>9.3f}'.format(endpps_tx) +' Mpps | ' + '{:>9}'.format(endpps_sut_tx_str) +' Mpps | '+ '{:>9.3f}'.format(endpps_rx)+' Mpps | '+ '{:>9.0f}'.format(endlat_avg)+' us   | '+ '{:>9.0f}'.format(endlat_max)+' us   | '+'{:>14d}'.format(endabs_dropped)+ ' |''{:>9.2f}'.format(enddrop_rate)+ '%  | SUCCESS    |')
324                 log.info("+--------+--------------------+----------------+----------------+----------------+----------------+----------------+----------------+----------------+------------+------------+")
325         else:
326                 log.info('| Speed 0 or close to 0')
327
328 def run_flowtest(gensock,sutsock):
329         log.info("+-----------------------------------------------------------------------------------------------------------------------------------------------------------------+")
330         log.info("| UDP, 64 bytes, different number of flows by randomizing SRC & DST UDP port                                                                                      |")
331         log.info("+--------+--------------------+----------------+----------------+----------------+----------------+----------------+----------------+----------------+------------+")
332         log.info("| Flows  |  Speed requested   | Sent to NIC    |  Sent by Gen   | Forward by SUT |  Rec. by Gen   |  Avg. Latency  |  Max. Latency  |  Packets Lost  | Loss Ratio |")
333         log.info("+--------+--------------------+----------------+----------------+----------------+----------------+----------------+----------------+----------------+------------+")
334         size=60
335         # To generate a desired number of flows, PROX will randomize the bits in source and destination ports, as specified by the bit masks in the flows variable. 
336         flows={128:['1000000000000XXX','100000000000XXXX'],1024:['10000000000XXXXX','10000000000XXXXX'],8192:['1000000000XXXXXX','100000000XXXXXXX'],65535:['10000000XXXXXXXX','10000000XXXXXXXX'],524280:['1000000XXXXXXXXX','100000XXXXXXXXXX']}
337 #       flows={524280:['1000000XXXXXXXXX','100000XXXXXXXXXX']}
338         gensock.set_size(gencores,0,size) # This is setting the frame size
339         gensock.set_value(gencores,0,16,(size-14),2) # 18 is the difference between the frame size and IP size = size of (MAC addresses, ethertype and FCS)
340         gensock.set_value(gencores,0,38,(size-34),2) # 38 is the difference between the frame size and UDP size = 18 + size of IP header (=20)
341         # This will only work when using sending UDP packets. For different protocls and ehternet types, we would need a differnt calculation
342         for flow_number in sorted(flows.iterkeys()):
343                 gensock.reset_stats()
344                 if sutsock!='none':
345                         sutsock.reset_stats()
346                 source_port,destination_port = flows[flow_number]
347                 gensock.set_random(gencores,0,34,source_port,2)
348                 gensock.set_random(gencores,0,36,destination_port,2)
349                 endpps_sut_tx_str = 'NO_RESULTS'
350                 maxspeed = speed = STARTSPEED
351                 minspeed = 0
352                 while (maxspeed-minspeed > ACCURACY):
353                         print(str(flow_number)+' flows: Measurement ongoing at speed: ' + str(round(speed,2)) + '%      ',end='\r')
354                         sys.stdout.flush()
355                         # Start generating packets at requested speed (in % of a 10Gb/s link)
356                         gensock.speed(speed, gencores)
357                         time.sleep(1)
358                         # Get statistics now that the generation is stable and NO ARP messages any more
359                         pps_req_tx,pps_tx,pps_sut_tx_str,pps_rx,lat_avg,lat_max, abs_dropped, abs_tx = run_iteration(gensock,sutsock)
360                         drop_rate = 100.0*abs_dropped/abs_tx
361                         if ((get_pps(speed,size) - pps_tx)/get_pps(speed,size))<0.001 and ((drop_rate < DROP_RATE_TRESHOLD) or (abs_dropped==DROP_RATE_TRESHOLD ==0)) and (lat_avg< LAT_AVG_TRESHOLD) and (lat_max < LAT_MAX_TRESHOLD):
362                                 endspeed = speed
363                                 endpps_req_tx = pps_req_tx
364                                 endpps_tx = pps_tx
365                                 endpps_sut_tx_str = pps_sut_tx_str
366                                 endpps_rx = pps_rx
367                                 endlat_avg = lat_avg 
368                                 endlat_max = lat_max 
369                                 endabs_dropped = abs_dropped
370                                 enddrop_rate = drop_rate
371                                 success = True
372                         else:
373                                 success = False 
374                         speed,minspeed,maxspeed = new_speed(speed,minspeed,maxspeed,success)
375                 if endpps_sut_tx_str <>  'NO_RESULTS':
376                         log.info('|{:>7}'.format(str(flow_number))+" | " + '{:>5.1f}'.format(endspeed) + '% ' +'{:>6.3f}'.format(get_pps(endspeed,size)) + ' Mpps | '+ '{:>9.3f}'.format(endpps_req_tx)+' Mpps | '+ '{:>9.3f}'.format(endpps_tx) +' Mpps | ' + '{:>9}'.format(endpps_sut_tx_str) +' Mpps | '+ '{:>9.3f}'.format(endpps_rx)+' Mpps | '+ '{:>9.0f}'.format(endlat_avg)+' us   | '+ '{:>9.0f}'.format(endlat_max)+' us   | '+ '{:>14d}'.format(endabs_dropped)+ ' |'+'{:>9.2f}'.format(enddrop_rate)+ '%  |')
377                         log.info("+--------+--------------------+----------------+----------------+----------------+----------------+----------------+----------------+----------------+------------+")
378                         writer.writerow({'flow':flow_number,'size':(size+4),'endspeed':endspeed,'endspeedpps':get_pps(endspeed,size),'endpps_req_tx':endpps_req_tx,'endpps_tx':endpps_tx,'endpps_sut_tx_str':endpps_sut_tx_str,'endpps_rx':endpps_rx,'endlat_avg':endlat_avg,'endlat_max':endlat_max,'endabs_dropped':endabs_dropped,'enddrop_rate':enddrop_rate})
379                 else:
380                         log.info('|{:>7}'.format(str(flow_number))+" | Speed 0 or close to 0")
381
382 def run_sizetest(gensock,sutsock):
383         log.info("+-----------------------------------------------------------------------------------------------------------------------------------------------------------------+")
384         log.info("| UDP, 1 flow, different packet sizes                                                                                                                             |")
385         log.info("+--------+--------------------+----------------+----------------+----------------+----------------+----------------+----------------+----------------+------------+")
386         log.info("| Pktsize|  Speed requested   | Sent to NIC    |  Sent by Gen   | Forward by SUT |  Rec. by Gen   |  Avg. Latency  |  Max. Latency  |  Packets Lost  | Loss Ratio |")
387         log.info("+--------+--------------------+----------------+----------------+----------------+----------------+----------------+----------------+----------------+------------+")
388         # PROX will use different packet sizes as defined in sizes[]
389         sizes=[1496,1020,508,252,124,60]
390 #       sizes=[1020,508,252,124,60]
391 #       sizes=[124,60]
392         for size in sizes:
393                 gensock.reset_stats()
394                 if sutsock!='none':
395                         sutsock.reset_stats()
396                 gensock.set_size(gencores,0,size) # This is setting the frame size
397                 gensock.set_value(gencores,0,16,(size-14),2) # 18 is the difference between the frame size and IP size = size of (MAC addresses, ethertype and FCS)
398                 gensock.set_value(gencores,0,38,(size-34),2) # 38 is the difference between the frame size and UDP size = 18 + size of IP header (=20)
399                 # This will only work when using sending UDP packets. For different protocls and ehternet types, we would need a differnt calculation
400                 endpps_sut_tx_str = 'NO_RESULTS'
401                 maxspeed = speed = STARTSPEED
402                 minspeed = 0
403                 while (maxspeed-minspeed > ACCURACY):
404                         print(str(size+4)+' bytes: Measurement ongoing at speed: ' + str(round(speed,2)) + '%      ',end='\r')
405                         sys.stdout.flush()
406                         # Start generating packets at requested speed (in % of a 10Gb/s link)
407                         gensock.speed(speed, gencores)
408                         # Get statistics now that the generation is stable and NO ARP messages any more
409                         pps_req_tx,pps_tx,pps_sut_tx_str,pps_rx,lat_avg,lat_max, abs_dropped, abs_tx = run_iteration(gensock,sutsock)
410                         drop_rate = 100.0*abs_dropped/abs_tx
411                         if ((get_pps(speed,size) - pps_tx)/get_pps(speed,size))<0.001 and ((drop_rate < DROP_RATE_TRESHOLD) or (abs_dropped==DROP_RATE_TRESHOLD ==0)) and (lat_avg< LAT_AVG_TRESHOLD) and (lat_max < LAT_MAX_TRESHOLD):
412                                 endspeed = speed
413                                 endpps_req_tx = pps_req_tx
414                                 endpps_tx = pps_tx
415                                 endpps_sut_tx_str = pps_sut_tx_str
416                                 endpps_rx = pps_rx
417                                 endlat_avg = lat_avg 
418                                 endlat_max = lat_max 
419                                 endabs_dropped = abs_dropped
420                                 enddrop_rate = drop_rate
421                                 success = True
422                         else:
423                                 success = False 
424                         speed,minspeed,maxspeed = new_speed(speed,minspeed,maxspeed,success)
425                 if endpps_sut_tx_str <>  'NO_RESULTS':
426                         log.info('|{:>7}'.format(size+4)+" | " + '{:>5.1f}'.format(endspeed) + '% ' +'{:>6.3f}'.format(get_pps(endspeed,size)) + ' Mpps | '+ '{:>9.3f}'.format(endpps_req_tx)+' Mpps | '+ '{:>9.3f}'.format(endpps_tx) +' Mpps | ' + '{:>9}'.format(endpps_sut_tx_str) +' Mpps | '+ '{:>9.3f}'.format(endpps_rx)+' Mpps | '+ '{:>9.0f}'.format(endlat_avg)+' us   | '+'{:>9.0f}'.format(endlat_max)+' us   | '+ '{:>14d}'.format(endabs_dropped)+ ' |'+'{:>9.2f}'.format(enddrop_rate)+ '%  |')
427                         log.info("+--------+--------------------+----------------+----------------+----------------+----------------+----------------+----------------+----------------+------------+")
428                         writer.writerow({'flow':'1','size':(size+4),'endspeed':endspeed,'endspeedpps':get_pps(endspeed,size),'endpps_req_tx':endpps_req_tx,'endpps_tx':endpps_tx,'endpps_sut_tx_str':endpps_sut_tx_str,'endpps_rx':endpps_rx,'endlat_avg':endlat_avg,'endlat_max':endlat_max,'endabs_dropped':endabs_dropped,'enddrop_rate':enddrop_rate})
429                 else:
430                         log.debug('|{:>7}'.format(str(size))+" | Speed 0 or close to 0")
431
432
433 def run_irqtest(sock):
434         log.info("+----------------------------------------------------------------------------------------------------------------------------")
435         log.info("| Measuring time probably spent dealing with an interrupt. Interrupting DPDK cores for more than 50us might be problematic   ")
436         log.info("| and result in packet loss. The first row shows the interrupted time buckets: first number is the bucket between 0us and    ")
437         log.info("| that number expressed in us and so on. The numbers in the other rows show how many times per second, the program was       ")
438         log.info("| interrupted for a time as specified by its bucket. '0' is printed when there are no interrupts in this bucket throughout   ")
439         log.info("| the duration of the test. This is to avoid rounding errors in the case of 0.0                                              ") 
440         log.info("+----------------------------------------------------------------------------------------------------------------------------")
441         sys.stdout.flush()
442         buckets=sock.show_irq_buckets(1)
443         print('Measurement ongoing ... ',end='\r')
444         sock.stop(irqcores)
445         old_irq = [[0 for x in range(len(buckets)+1)] for y in range(len(irqcores)+1)] 
446         irq = [[0 for x in range(len(buckets)+1)] for y in range(len(irqcores)+1)]
447         irq[0][0] = 'bucket us' 
448         for j,bucket in enumerate(buckets,start=1):
449                 irq[0][j] = '<'+ bucket
450         irq[0][-1] = '>'+ buckets [-2]
451         sock.start(irqcores)
452         time.sleep(2)
453         for j,bucket in enumerate(buckets,start=1):
454                 for i,irqcore in enumerate(irqcores,start=1):
455                         old_irq[i][j] = sock.irq_stats(irqcore,j-1)
456         time.sleep(float(runtime))
457         sock.stop(irqcores)
458         for i,irqcore in enumerate(irqcores,start=1):
459                 irq[i][0]='core %s '%irqcore
460                 for j,bucket in enumerate(buckets,start=1):
461                         diff =  sock.irq_stats(irqcore,j-1) - old_irq[i][j]
462                         if diff == 0:
463                                 irq[i][j] = '0'
464                         else:
465                                 irq[i][j] = str(round(diff/float(runtime), 2))
466         log.info('\n'.join([''.join(['{:>12}'.format(item) for item in row]) for row in irq]))
467
468 def run_impairtest(gensock,sutsock,speed):
469         log.info("+-----------------------------------------------------------------------------------------------------------------------------------------------------------------+")
470         log.info("| Generator is sending UDP (1 flow) packets (64 bytes) to SUT via GW dropping and delaying packets. SUT sends packets back. Use ctrl-c to stop the test           |")
471         log.info("+--------+--------------------+----------------+----------------+----------------+----------------+----------------+----------------+----------------+------------+")
472         log.info("| Test   |  Speed requested   | Sent to NIC    |  Sent by Gen   | Forward by SUT |  Rec. by Gen   |  Avg. Latency  |  Max. Latency  |  Packets Lost  | Loss Ratio |")
473         log.info("+--------+--------------------+----------------+----------------+----------------+----------------+----------------+----------------+----------------+------------+")
474         size=60
475         attempts = 0
476         gensock.set_size(gencores,0,size) # This is setting the frame size
477         gensock.set_value(gencores,0,16,(size-14),2) # 18 is the difference between the frame size and IP size = size of (MAC addresses, ethertype and FCS)
478         gensock.set_value(gencores,0,38,(size-34),2) # 38 is the difference between the frame size and UDP size = 18 + size of IP header (=20)
479         # This will only work when using sending UDP packets. For different protocols and ethernet types, we would need a different calculation
480         gensock.speed(speed, gencores)
481         while True:
482                 attempts += 1
483                 print('Measurement ongoing at speed: ' + str(round(speed,2)) + '%      ',end='\r')
484                 sys.stdout.flush()
485                 time.sleep(1)
486                 # Get statistics now that the generation is stable and NO ARP messages any more
487                 pps_req_tx,pps_tx,pps_sut_tx_str,pps_rx,lat_avg,lat_max, abs_dropped, abs_tx = run_iteration(gensock,sutsock)
488                 drop_rate = 100.0*abs_dropped/abs_tx
489                 log.info('|{:>7}'.format(str(attempts))+" | " + '{:>5.1f}'.format(speed) + '% ' +'{:>6.3f}'.format(get_pps(speed,size)) + ' Mpps | '+ '{:>9.3f}'.format(pps_req_tx)+' Mpps | '+ '{:>9.3f}'.format(pps_tx) +' Mpps | ' + '{:>9}'.format(pps_sut_tx_str) +' Mpps | '+ '{:>9.3f}'.format(pps_rx)+' Mpps | '+ '{:>9.0f}'.format(lat_avg)+' us   | '+ '{:>9.0f}'.format(lat_max)+' us   | '+ '{:>14d}'.format(abs_dropped)+ ' |''{:>9.2f}'.format(drop_rate)+ '%  |')
490
491 def init_test():
492 # Running at low speed to make sure the ARP messages can get through.
493 # If not doing this, the ARP message could be dropped by a switch in overload and then the test will not give proper results
494 # Note hoever that if we would run the test steps during a very long time, the ARP would expire in the switch.
495 # PROX will send a new ARP request every seconds so chances are very low that they will all fail to get through
496         socks[0].speed(0.01, gencores)
497         socks[0].start(genstatcores)
498         time.sleep(2)
499         socks[0].stop(gencores)
500
501 global sutstatcores
502 global genstatcores
503 global latcores
504 global gencores
505 global irqcores
506 global DROP_RATE_TRESHOLD
507 global LAT_AVG_TRESHOLD
508 global LAT_MAX_TRESHOLD
509 global ACCURACY
510 global STARTSPEED
511 global required_number_of_test_machines
512 clients =[]
513 socks =[]
514 vmDPIP =[]
515 vmAdminIP =[]
516 vmDPmac =[]
517 hexDPIP =[]
518 config_file =[]
519 script_control =[]
520
521 testconfig = ConfigParser.RawConfigParser()
522 testconfig.read(test+'.test')
523 required_number_of_test_machines = testconfig.get('DEFAULT', 'total_number_of_test_machines')
524 DROP_RATE_TRESHOLD = float(testconfig.get('DEFAULT', 'drop_rate_treshold'))
525 LAT_AVG_TRESHOLD = float(testconfig.get('DEFAULT', 'lat_avg_treshold'))
526 LAT_MAX_TRESHOLD = float(testconfig.get('DEFAULT', 'lat_max_treshold'))
527 ACCURACY = float(testconfig.get('DEFAULT', 'accuracy'))
528 STARTSPEED = float(testconfig.get('DEFAULT', 'startspeed'))
529 config = ConfigParser.RawConfigParser()
530 config.read(env+'.env')
531 key = config.get('OpenStack', 'key')
532 total_number_of_machines = config.get('rapid', 'total_number_of_machines')
533 if int(required_number_of_test_machines) > int(total_number_of_machines):
534         log.exception("Not enough VMs for this test: %s needed and only %s available" % (required_number_of_test_machines,total_number_of_machines))
535         raise Exception("Not enough VMs for this test: %s needed and only %s available" % (required_number_of_test_machines,total_number_of_machines))
536 for vm in range(1, int(total_number_of_machines)+1):
537         vmAdminIP.append(config.get('M%d'%vm, 'admin_ip'))
538         vmDPmac.append(config.get('M%d'%vm, 'dp_mac'))
539         vmDPIP.append(config.get('M%d'%vm, 'dp_ip'))
540         ip = vmDPIP[-1].split('.')
541         hexDPIP.append(hex(int(ip[0]))[2:].zfill(2) + ' ' + hex(int(ip[1]))[2:].zfill(2) + ' ' + hex(int(ip[2]))[2:].zfill(2) + ' ' + hex(int(ip[3]))[2:].zfill(2))
542 machine_index = []
543 for vm in range(1, int(required_number_of_test_machines)+1):
544         machine_index.append(int(testconfig.get('TestM%d'%vm, 'machine_index'))-1)
545 for vm in range(1, int(required_number_of_test_machines)+1):
546         config_file.append(testconfig.get('TestM%d'%vm, 'config_file'))
547         script_control.append(testconfig.get('TestM%d'%vm, 'script_control'))
548         group1cores=testconfig.get('TestM%d'%vm, 'group1cores')
549         if group1cores <> 'not_used':
550                 group1cores=ast.literal_eval(group1cores)
551         group2cores=testconfig.get('TestM%d'%vm, 'group2cores')
552         if group2cores <> 'not_used':
553                 group2cores=ast.literal_eval(group2cores)
554         group3cores=testconfig.get('TestM%d'%vm, 'group3cores')
555         if group3cores <> 'not_used':
556                 group3cores=ast.literal_eval(group3cores)
557         with open("parameters%d.lua"%vm, "w") as f:
558                 f.write('name="%s"\n'% testconfig.get('TestM%d'%vm, 'name'))
559                 f.write('local_ip="%s"\n'% vmDPIP[machine_index[vm-1]])
560                 f.write('local_hex_ip="%s"\n'% hexDPIP[machine_index[vm-1]])
561                 gwVM = testconfig.get('TestM%d'%vm, 'gw_vm')
562                 if gwVM <> 'not_used':
563                         gwVMindex = int(gwVM)-1
564                         f.write('gw_ip="%s"\n'% vmDPIP[machine_index[gwVMindex]])
565                         f.write('gw_hex_ip="%s"\n'% hexDPIP[machine_index[gwVMindex]])
566                 destVM = testconfig.get('TestM%d'%vm, 'dest_vm')
567                 if destVM <> 'not_used':
568                         destVMindex = int(destVM)-1
569                         f.write('dest_ip="%s"\n'% vmDPIP[machine_index[destVMindex]])
570                         f.write('dest_hex_ip="%s"\n'% hexDPIP[machine_index[destVMindex]])
571                         f.write('dest_hex_mac="%s"\n'% vmDPmac[machine_index[destVMindex]].replace(':',' '))
572                 if group1cores <> 'not_used':
573                         f.write('group1="%s"\n'% ','.join(map(str, group1cores)))
574                 if group2cores <> 'not_used':
575                         f.write('group2="%s"\n'% ','.join(map(str, group2cores)))
576                 if group3cores <> 'not_used':
577                         f.write('group3="%s"\n'% ','.join(map(str, group3cores)))
578         if re.match('(l2){0,1}gen.*\.cfg',config_file[-1]):
579                 gencores = group1cores
580                 latcores = group2cores
581                 genstatcores = group3cores
582         elif config_file[-1] == 'gen_gw.cfg':
583                 gencores = group1cores
584                 latcores = group2cores
585                 genstatcores = group3cores
586         elif  re.match('(l2){0,1}swap.*\.cfg',config_file[-1]):
587                 sutstatcores = group1cores
588         elif config_file[-1] == 'secgw2.cfg':
589                 sutstatcores = group1cores
590         elif config_file[-1] == 'irq.cfg':
591                 irqcores = group1cores
592         f.close
593 #####################################################################################
594 def exit_handler():
595         log.debug ('exit cleanup')
596         for sock in socks:
597                 sock.quit()
598         for client in clients:
599                 client.close()
600         sys.exit(0)
601
602 atexit.register(exit_handler)
603
604 for vm in range(0, int(required_number_of_test_machines)):
605         clients.append(prox_ctrl(vmAdminIP[machine_index[vm]], key+'.pem','root'))
606         connect_client(clients[-1])
607 # Creating script to bind the right network interface to the poll mode driver
608         devbindfile = "devbindvm%d.sh"%(vm+1)
609         with open("devbind.sh") as f:
610                 newText=f.read().replace('MACADDRESS', vmDPmac[machine_index[vm]])
611                 with open(devbindfile, "w") as f:
612                         f.write(newText)
613         st = os.stat(devbindfile)
614         os.chmod(devbindfile, st.st_mode | stat.S_IEXEC)
615         clients[-1].scp_put('./%s'%devbindfile, '/root/devbind.sh')
616         cmd = '/root/devbind.sh'
617         clients[-1].run_cmd(cmd)
618         log.debug("devbind.sh running on VM%d"%(vm+1))
619         clients[-1].scp_put('./%s'%config_file[vm], '/root/%s'%config_file[vm])
620         clients[-1].scp_put('./parameters%d.lua'%(vm+1), '/root/parameters.lua')
621         log.debug("Starting PROX on VM%d"%(vm+1))
622         if script_control[vm] == 'true':
623                 cmd = '/root/prox/build/prox -e -t -o cli -f /root/%s'%config_file[vm]
624         else:
625                 cmd = '/root/prox/build/prox -t -o cli -f /root/%s'%config_file[vm]
626         if configonly == False:
627                 clients[-1].fork_cmd(cmd, 'PROX Testing on TestM%d'%(vm+1))
628                 socks.append(connect_socket(clients[-1]))
629 if configonly:
630         sys.exit()
631 init_code = testconfig.get('DEFAULT', 'init_code')
632 if init_code <> 'not_used':
633         eval(init_code)
634 ####################################################
635 # Run test cases
636 # Best to run the flow test at the end since otherwise the tests coming after might be influenced by the big number of entries in the switch flow tables
637 ####################################################
638 number_of_tests = testconfig.get('DEFAULT', 'number_of_tests')
639 data_file = 'RUN' +env+'.'+test+'.csv'
640 data_csv_file = open(data_file,'w')
641 with data_csv_file:
642         fieldnames = ['flow','size','endspeed','endspeedpps','endpps_req_tx','endpps_tx','endpps_sut_tx_str','endpps_rx','endlat_avg','endlat_max','endabs_dropped','enddrop_rate']
643         writer = csv.DictWriter(data_csv_file, fieldnames=fieldnames)
644         writer.writeheader()
645         for vm in range(1, int(number_of_tests)+1):
646                 cmd=testconfig.get('test%d'%vm,'cmd')
647                 eval(cmd)
648 ####################################################