3 # Copyright 2015-2017 Intel Corporation.
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
17 """VSPERF main script.
36 from tabulate import tabulate
37 from conf import merge_spec
38 from conf import settings
39 import core.component_factory as component_factory
40 from core.loader import Loader
41 from testcases import PerformanceTestCase
42 from testcases import IntegrationTestCase
43 from tools import tasks
44 from tools import networkcard
45 from tools import functions
46 from tools.pkt_gen import trafficgen
47 from tools.opnfvdashboard import opnfvdashboard
48 sys.dont_write_bytecode = True
51 'debug': logging.DEBUG,
53 'warning': logging.WARNING,
54 'error': logging.ERROR,
55 'critical': logging.CRITICAL
58 _CURR_DIR = os.path.dirname(os.path.realpath(__file__))
60 _TEMPLATE_RST = {'head' : os.path.join(_CURR_DIR, 'tools/report/report_head.rst'),
61 'foot' : os.path.join(_CURR_DIR, 'tools/report/report_foot.rst'),
62 'final' : 'test_report.rst',
63 'tmp' : os.path.join(_CURR_DIR, 'tools/report/report_tmp_caption.rst')
66 _TEMPLATE_MATRIX = "Performance Matrix\n------------------\n\n"\
67 "The following performance matrix was generated with the results of all the\n"\
68 "currently run tests. The metric used for comparison is {}.\n\n{}\n\n"
70 _LOGGER = logging.getLogger()
72 def parse_param_string(values):
74 Parse and split a single '--test-params' argument.
76 This expects either 'x=y', 'x=y,z' or 'x' (implicit true)
77 values. For multiple overrides use a ; separated list for
78 e.g. --test-params 'x=z; y=(a,b)'
85 for param, _, value in re.findall('([^;=]+)(=([^;]+))?', values):
90 # values are passed inside string from CLI, so we must retype them accordingly
92 results[param] = ast.literal_eval(value)
94 # for backward compatibility, we have to accept strings without quotes
95 _LOGGER.warning("Adding missing quotes around string value: %s = %s",
97 results[param] = str(value)
103 def parse_arguments():
105 Parse command line arguments.
107 class _SplitTestParamsAction(argparse.Action):
109 Parse and split '--test-params' arguments.
111 This expects either a single list of ; separated overrides
112 as 'x=y', 'x=y,z' or 'x' (implicit true) values.
113 e.g. --test-params 'x=z; y=(a,b)'
114 Or a list of these ; separated lists with overrides for
116 e.g. --test-params "['x=z; y=(a,b)','x=z']"
118 def __call__(self, parser, namespace, values, option_string=None):
121 input_list = ast.literal_eval(values)
123 for test_params in input_list:
124 parameter_list.append(parse_param_string(test_params))
126 parameter_list = parse_param_string(values)
127 results = {'_PARAMS_LIST':parameter_list}
128 setattr(namespace, self.dest, results)
130 class _ValidateFileAction(argparse.Action):
131 """Validate a file can be read from before using it.
133 def __call__(self, parser, namespace, values, option_string=None):
134 if not os.path.isfile(values):
135 raise argparse.ArgumentTypeError(
136 'the path \'%s\' is not a valid path' % values)
137 elif not os.access(values, os.R_OK):
138 raise argparse.ArgumentTypeError(
139 'the path \'%s\' is not accessible' % values)
141 setattr(namespace, self.dest, values)
143 class _ValidateDirAction(argparse.Action):
144 """Validate a directory can be written to before using it.
146 def __call__(self, parser, namespace, values, option_string=None):
147 if not os.path.isdir(values):
148 raise argparse.ArgumentTypeError(
149 'the path \'%s\' is not a valid path' % values)
150 elif not os.access(values, os.W_OK):
151 raise argparse.ArgumentTypeError(
152 'the path \'%s\' is not accessible' % values)
154 setattr(namespace, self.dest, values)
156 def list_logging_levels():
157 """Give a summary of all available logging levels.
159 :return: List of verbosity level names in decreasing order of
162 return sorted(VERBOSITY_LEVELS.keys(),
163 key=lambda x: VERBOSITY_LEVELS[x])
165 parser = argparse.ArgumentParser(prog=__file__, formatter_class=
166 argparse.ArgumentDefaultsHelpFormatter)
167 parser.add_argument('--version', action='version', version='%(prog)s 0.2')
168 parser.add_argument('--list', '--list-tests', action='store_true',
169 help='list all tests and exit')
170 parser.add_argument('--list-trafficgens', action='store_true',
171 help='list all traffic generators and exit')
172 parser.add_argument('--list-collectors', action='store_true',
173 help='list all system metrics loggers and exit')
174 parser.add_argument('--list-vswitches', action='store_true',
175 help='list all system vswitches and exit')
176 parser.add_argument('--list-fwdapps', action='store_true',
177 help='list all system forwarding applications and exit')
178 parser.add_argument('--list-vnfs', action='store_true',
179 help='list all system vnfs and exit')
180 parser.add_argument('--list-loadgens', action='store_true',
181 help='list all background load generators')
182 parser.add_argument('--list-settings', action='store_true',
183 help='list effective settings configuration and exit')
184 parser.add_argument('exact_test_name', nargs='*', help='Exact names of\
185 tests to run. E.g "vsperf phy2phy_tput phy2phy_cont"\
186 runs only the two tests with those exact names.\
187 To run all tests omit both positional args and --tests arg.')
189 group = parser.add_argument_group('test selection options')
190 group.add_argument('-m', '--mode', help='vsperf mode of operation;\
191 Values: "normal" - execute vSwitch, VNF and traffic generator;\
192 "trafficgen" - execute only traffic generator; "trafficgen-off" \
193 - execute vSwitch and VNF; trafficgen-pause - execute vSwitch \
194 and VNF but pause before traffic transmission ', default='normal')
196 group.add_argument('-f', '--test-spec', help='test specification file')
197 group.add_argument('-d', '--test-dir', help='directory containing tests')
198 group.add_argument('-t', '--tests', help='Comma-separated list of terms \
199 indicating tests to run. e.g. "RFC2544,!p2p" - run all tests whose\
200 name contains RFC2544 less those containing "p2p"; "!back2back" - \
201 run all tests except those containing back2back')
202 group.add_argument('--verbosity', choices=list_logging_levels(),
204 group.add_argument('--integration', action='store_true', help='execute integration tests')
205 group.add_argument('--trafficgen', help='traffic generator to use')
206 group.add_argument('--vswitch', help='vswitch implementation to use')
207 group.add_argument('--fwdapp', help='packet forwarding application to use')
208 group.add_argument('--vnf', help='vnf to use')
209 group.add_argument('--loadgen', help='loadgen to use')
210 group.add_argument('--sysmetrics', help='system metrics logger to use')
211 group = parser.add_argument_group('test behavior options')
212 group.add_argument('--xunit', action='store_true',
213 help='enable xUnit-formatted output')
214 group.add_argument('--xunit-dir', action=_ValidateDirAction,
215 help='output directory of xUnit-formatted output')
216 group.add_argument('--load-env', action='store_true',
217 help='enable loading of settings from the environment')
218 group.add_argument('--conf-file', action=_ValidateFileAction,
219 help='settings file')
220 group.add_argument('--test-params', action=_SplitTestParamsAction,
221 help='csv list of test parameters: key=val; e.g. '
222 'TRAFFICGEN_PKT_SIZES=(64,128);TRAFFICGEN_DURATION=30; '
223 'GUEST_LOOPBACK=["l2fwd"] ...'
224 ' or a list of csv lists of test parameters: key=val; e.g. '
225 '[\'TRAFFICGEN_DURATION=10;TRAFFICGEN_PKT_SIZES=(128,)\','
226 '\'TRAFFICGEN_DURATION=10;TRAFFICGEN_PKT_SIZES=(64,)\']')
227 group.add_argument('--opnfvpod', help='name of POD in opnfv')
228 group.add_argument('--matrix', help='enable performance matrix analysis',
229 action='store_true', default=False)
231 args = vars(parser.parse_args())
236 def configure_logging(level):
237 """Configure logging.
239 log_file_default = os.path.join(
240 settings.getValue('LOG_DIR'), settings.getValue('LOG_FILE_DEFAULT'))
241 log_file_host_cmds = os.path.join(
242 settings.getValue('LOG_DIR'), settings.getValue('LOG_FILE_HOST_CMDS'))
243 log_file_traffic_gen = os.path.join(
244 settings.getValue('LOG_DIR'),
245 settings.getValue('LOG_FILE_TRAFFIC_GEN'))
247 _LOGGER.setLevel(logging.DEBUG)
249 stream_logger = logging.StreamHandler(sys.stdout)
250 stream_logger.setLevel(VERBOSITY_LEVELS[level])
251 stream_logger.setFormatter(logging.Formatter(
252 '[%(levelname)-5s] %(asctime)s : (%(name)s) - %(message)s'))
253 _LOGGER.addHandler(stream_logger)
255 file_logger = logging.FileHandler(filename=log_file_default)
256 file_logger.setLevel(logging.DEBUG)
257 _LOGGER.addHandler(file_logger)
259 class CommandFilter(logging.Filter):
260 """Filter out strings beginning with 'cmd :'"""
261 def filter(self, record):
262 return record.getMessage().startswith(tasks.CMD_PREFIX)
264 class TrafficGenCommandFilter(logging.Filter):
265 """Filter out strings beginning with 'gencmd :'"""
266 def filter(self, record):
267 return record.getMessage().startswith(trafficgen.CMD_PREFIX)
269 cmd_logger = logging.FileHandler(filename=log_file_host_cmds)
270 cmd_logger.setLevel(logging.DEBUG)
271 cmd_logger.addFilter(CommandFilter())
272 _LOGGER.addHandler(cmd_logger)
274 gen_logger = logging.FileHandler(filename=log_file_traffic_gen)
275 gen_logger.setLevel(logging.DEBUG)
276 gen_logger.addFilter(TrafficGenCommandFilter())
277 _LOGGER.addHandler(gen_logger)
280 def apply_filter(tests, tc_filter):
281 """Allow a subset of tests to be conveniently selected
283 :param tests: The list of Tests from which to select.
284 :param tc_filter: A case-insensitive string of comma-separated terms
285 indicating the Tests to select.
286 e.g. 'RFC' - select all tests whose name contains 'RFC'
287 e.g. 'RFC,burst' - select all tests whose name contains 'RFC' or
289 e.g. 'RFC,burst,!p2p' - select all tests whose name contains 'RFC'
290 or 'burst' and from these remove any containing 'p2p'.
291 e.g. '' - empty string selects all tests.
292 :return: A list of the selected Tests.
294 # if negative filter is first we have to start with full list of tests
295 if tc_filter.strip()[0] == '!':
299 if tc_filter is None:
302 for term in [x.strip() for x in tc_filter.lower().split(",")]:
303 if not term or term[0] != '!':
304 # Add matching tests from 'tests' into results
305 result.extend([test for test in tests \
306 if test['Name'].lower().find(term) >= 0])
308 # Term begins with '!' so we remove matching tests
309 result = [test for test in result \
310 if test['Name'].lower().find(term[1:]) < 0]
315 def check_and_set_locale():
316 """ Function will check locale settings. In case, that it isn't configured
317 properly, then default values specified by DEFAULT_LOCALE will be used.
320 system_locale = locale.getdefaultlocale()
321 if None in system_locale:
322 os.environ['LC_ALL'] = settings.getValue('DEFAULT_LOCALE')
323 _LOGGER.warning("Locale was not properly configured. Default values were set. Old locale: %s, New locale: %s",
324 system_locale, locale.getdefaultlocale())
326 def get_vswitch_names(rst_files):
327 """ Function will return a list of vSwitches detected in given ``rst_files``.
329 vswitch_names = set()
332 output = subprocess.check_output(['grep', '-h', '^* vSwitch'] + rst_files).decode().splitlines()
334 match = re.search(r'^\* vSwitch: ([^,]+)', str(line))
336 vswitch_names.add(match.group(1))
338 if len(vswitch_names):
339 return list(vswitch_names)
341 except subprocess.CalledProcessError:
342 _LOGGER.warning('Cannot detect vSwitches used during testing.')
344 # fallback to the default value
348 """ Function will return a Jenkins job ID environment variable.
352 build_tag = os.environ['BUILD_TAG']
355 _LOGGER.warning('Cannot detect Jenkins job ID')
360 def generate_final_report():
361 """ Function will check if partial test results are available
362 and generates final report in rst format.
365 path = settings.getValue('RESULTS_PATH')
366 # check if there are any results in rst format
367 rst_results = glob.glob(os.path.join(path, 'result*rst'))
368 pkt_processors = get_vswitch_names(rst_results)
371 test_report = os.path.join(path, '{}_{}'.format('_'.join(pkt_processors), _TEMPLATE_RST['final']))
372 # create report caption directly - it is not worth to execute jinja machinery
373 report_caption = '{}\n{} {}\n{}\n\n'.format(
374 '============================================================',
375 'Performance report for',
376 ', '.join(pkt_processors),
377 '============================================================')
379 with open(_TEMPLATE_RST['tmp'], 'w') as file_:
380 file_.write(report_caption)
382 retval = subprocess.call('cat {} {} {} {} > {}'.format(_TEMPLATE_RST['tmp'], _TEMPLATE_RST['head'],
383 ' '.join(rst_results), _TEMPLATE_RST['foot'],
384 test_report), shell=True)
385 if retval == 0 and os.path.isfile(test_report):
386 _LOGGER.info('Overall test report written to "%s"', test_report)
388 _LOGGER.error('Generation of overall test report has failed.')
390 # remove temporary file
391 os.remove(_TEMPLATE_RST['tmp'])
393 except subprocess.CalledProcessError:
394 _LOGGER.error('Generatrion of overall test report has failed.')
397 def generate_performance_matrix(selected_tests, results_path):
399 Loads the results of all the currently run tests, compares them
400 based on the MATRIX_METRIC, outputs and saves the generated table.
401 :selected_tests: list of currently run test
402 :results_path: directory path to the results of current tests
404 _LOGGER.info('Performance Matrix:')
407 for test in selected_tests:
408 test_name = test.get('Name', '<Name not set>')
409 test_deployment = test.get('Deployment', '<Deployment not set>')
410 test_list.append({'test_name':test_name, 'test_deployment':test_deployment, 'csv_data':False})
414 all_params = settings.getValue('_PARAMS_LIST')
415 for i in range(len(selected_tests)):
417 if isinstance(all_params, list):
419 if i >= len(all_params):
420 list_index = len(all_params) - 1
421 if settings.getValue('CUMULATIVE_PARAMS') and (i > 0):
422 test_params.update(all_params[list_index])
424 test_params = all_params[list_index]
426 test_params = all_params
427 settings.setValue('TEST_PARAMS', test_params)
428 test['test_params'] = copy.deepcopy(test_params)
430 with open("{}/result_{}_{}_{}.csv".format(results_path, str(i),
431 test['test_name'], test['test_deployment'])) as csvfile:
432 reader = list(csv.DictReader(csvfile))
433 test['csv_data'] = reader[0]
434 # pylint: disable=broad-except
435 except (Exception) as ex:
436 _LOGGER.error("Result file not found: %s", ex)
438 metric = settings.getValue('MATRIX_METRIC')
440 output_header = ("ID", "Name", metric, "Change [%]", "Parameters, "\
441 "CUMULATIVE_PARAMS = {}".format(settings.getValue('CUMULATIVE_PARAMS')))
442 if not test_list[0]['csv_data'] or float(test_list[0]['csv_data'][metric]) == 0:
443 _LOGGER.error("Incorrect format of test results")
445 for i, test in enumerate(test_list):
447 change[i] = float(test['csv_data'][metric])/\
448 (float(test_list[0]['csv_data'][metric]) / 100) - 100
449 output.append([i, test['test_name'], float(test['csv_data'][metric]),
450 change[i], str(test['test_params'])[1:-1]])
453 output.append([i, test['test_name'], "Test Failed", 0, test['test_params']])
454 print(tabulate(output, headers=output_header, tablefmt="grid", floatfmt="0.3f"))
455 with open(results_path + '/result_performance_matrix.rst', 'w+') as output_file:
456 output_file.write(_TEMPLATE_MATRIX.format(metric, tabulate(output, headers=output_header,
457 tablefmt="rst", floatfmt="0.3f")))
458 _LOGGER.info('Performance matrix written to: "%s/result_performance_matrix.rst"', results_path)
460 def enable_sriov(nic_list):
461 """ Enable SRIOV for given enhanced PCI IDs
463 :param nic_list: A list of enhanced PCI IDs
465 # detect if sriov is required
468 if networkcard.is_sriov_nic(nic):
469 tmp_nic = nic.split('|')
470 if tmp_nic[0] in sriov_nic:
471 if int(tmp_nic[1][2:]) > sriov_nic[tmp_nic[0]]:
472 sriov_nic[tmp_nic[0]] = int(tmp_nic[1][2:])
474 sriov_nic.update({tmp_nic[0] : int(tmp_nic[1][2:])})
476 # sriov is required for some NICs
478 for nic in sriov_nic:
479 # check if SRIOV is supported and enough virt interfaces are available
480 if not networkcard.is_sriov_supported(nic) \
481 or networkcard.get_sriov_numvfs(nic) <= sriov_nic[nic]:
482 # if not, enable and set appropriate number of VFs
483 if not networkcard.set_sriov_numvfs(nic, sriov_nic[nic] + 1):
484 raise RuntimeError('SRIOV cannot be enabled for NIC {}'.format(nic))
486 _LOGGER.debug("SRIOV enabled for NIC %s", nic)
488 # ensure that path to the bind tool is valid
489 functions.settings_update_paths()
491 # WORKAROUND: it has been observed with IXGBE(VF) driver,
492 # that NIC doesn't correclty dispatch traffic to VFs based
493 # on their MAC address. Unbind and bind to the same driver
495 networkcard.reinit_vfs(nic)
497 # After SRIOV is enabled it takes some time until network drivers
498 # properly initialize all cards.
499 # Wait also in case, that SRIOV was already configured as it can be
500 # configured automatically just before vsperf execution.
508 def disable_sriov(nic_list):
509 """ Disable SRIOV for given PCI IDs
511 :param nic_list: A list of enhanced PCI IDs
514 if networkcard.is_sriov_nic(nic):
515 if not networkcard.set_sriov_numvfs(nic.split('|')[0], 0):
516 raise RuntimeError('SRIOV cannot be disabled for NIC {}'.format(nic))
518 _LOGGER.debug("SRIOV disabled for NIC %s", nic.split('|')[0])
521 def handle_list_options(args):
522 """ Process --list cli arguments if needed
524 :param args: A dictionary with all CLI arguments
526 if args['list_trafficgens']:
527 print(Loader().get_trafficgens_printable())
530 if args['list_collectors']:
531 print(Loader().get_collectors_printable())
534 if args['list_vswitches']:
535 print(Loader().get_vswitches_printable())
538 if args['list_vnfs']:
539 print(Loader().get_vnfs_printable())
542 if args['list_fwdapps']:
543 print(Loader().get_pktfwds_printable())
546 if args['list_loadgens']:
547 print(Loader().get_loadgens_printable())
550 if args['list_settings']:
559 def list_testcases(args):
560 """ Print list of testcases requested by --list CLI argument
562 :param args: A dictionary with all CLI arguments
565 if args['integration']:
566 testcases = settings.getValue('INTEGRATION_TESTS')
568 testcases = settings.getValue('PERFORMANCE_TESTS')
570 print("Available Tests:")
571 print("================")
573 for test in testcases:
574 description = functions.format_description(test['Description'], 70)
575 if len(test['Name']) < 40:
576 print('* {:40} {}'.format('{}:'.format(test['Name']), description[0]))
578 print('* {}'.format('{}:'.format(test['Name'])))
579 print(' {:40} {}'.format('', description[0]))
580 for i in range(1, len(description)):
581 print(' {:40} {}'.format('', description[i]))
584 def vsperf_finalize():
585 """ Clean up before exit
587 # remove directory if no result files were created
589 results_path = settings.getValue('RESULTS_PATH')
590 if os.path.exists(results_path):
591 files_list = os.listdir(results_path)
593 _LOGGER.info("Removing empty result directory: " + results_path)
594 shutil.rmtree(results_path)
595 except AttributeError:
596 # skip it if parameter doesn't exist
599 # disable SRIOV if needed
601 if settings.getValue('SRIOV_ENABLED'):
602 disable_sriov(settings.getValue('WHITELIST_NICS_ORIG'))
603 except AttributeError:
604 # skip it if parameter doesn't exist
608 class MockTestCase(unittest.TestCase):
609 """Allow use of xmlrunner to generate Jenkins compatible output without
610 using xmlrunner to actually run tests.
613 suite = unittest.TestSuite()
614 suite.addTest(MockTestCase('Test1 passed ', True, 'Test1'))
615 suite.addTest(MockTestCase('Test2 failed because...', False, 'Test2'))
616 xmlrunner.XMLTestRunner(...).run(suite)
619 def __init__(self, msg, is_pass, test_name):
622 self.is_pass = is_pass
624 #dynamically create a test method with the right name
625 #but point the method at our generic test method
626 setattr(MockTestCase, test_name, self.generic_test)
628 super(MockTestCase, self).__init__(test_name)
630 def generic_test(self):
631 """Provide a generic function that raises or not based
632 on how self.is_pass was set in the constructor"""
633 self.assertTrue(self.is_pass, self.msg)
635 # pylint: disable=too-many-locals, too-many-branches, too-many-statements
639 args = parse_arguments()
643 settings.load_from_dir(os.path.join(_CURR_DIR, 'conf'))
645 # Load non performance/integration tests
646 if args['integration']:
647 settings.load_from_dir(os.path.join(_CURR_DIR, 'conf/integration'))
649 # load command line parameters first in case there are settings files
651 settings.load_from_dict(args)
653 if args['conf_file']:
654 settings.load_from_file(args['conf_file'])
657 settings.load_from_env()
659 # reload command line parameters since these should take higher priority
660 # than both a settings file and environment variables
661 settings.load_from_dict(args)
663 settings.setValue('mode', args['mode'])
665 # update paths to trafficgens if required
666 if settings.getValue('mode') == 'trafficgen':
667 functions.settings_update_paths()
669 # if required, handle list-* operations
670 handle_list_options(args)
672 configure_logging(settings.getValue('VERBOSITY'))
674 # check and fix locale
675 check_and_set_locale()
677 # configure trafficgens
678 if args['trafficgen']:
679 trafficgens = Loader().get_trafficgens()
680 if args['trafficgen'] not in trafficgens:
681 _LOGGER.error('There are no trafficgens matching \'%s\' found in'
682 ' \'%s\'. Exiting...', args['trafficgen'],
683 settings.getValue('TRAFFICGEN_DIR'))
686 # configuration validity checks
688 vswitch_none = args['vswitch'].strip().lower() == 'none'
690 settings.setValue('VSWITCH', 'none')
692 vswitches = Loader().get_vswitches()
693 if args['vswitch'] not in vswitches:
694 _LOGGER.error('There are no vswitches matching \'%s\' found in'
695 ' \'%s\'. Exiting...', args['vswitch'],
696 settings.getValue('VSWITCH_DIR'))
700 settings.setValue('PKTFWD', args['fwdapp'])
701 fwdapps = Loader().get_pktfwds()
702 if args['fwdapp'] not in fwdapps:
703 _LOGGER.error('There are no forwarding application'
704 ' matching \'%s\' found in'
705 ' \'%s\'. Exiting...', args['fwdapp'],
706 settings.getValue('PKTFWD_DIR'))
710 vnfs = Loader().get_vnfs()
711 if args['vnf'] not in vnfs:
712 _LOGGER.error('there are no vnfs matching \'%s\' found in'
713 ' \'%s\'. exiting...', args['vnf'],
714 settings.getValue('VNF_DIR'))
718 loadgens = Loader().get_loadgens()
719 if args['loadgen'] not in loadgens:
720 _LOGGER.error('There are no loadgens matching \'%s\' found in'
721 ' \'%s\'. Exiting...', args['loadgen'],
722 settings.getValue('LOADGEN_DIR'))
725 if args['exact_test_name'] and args['tests']:
726 _LOGGER.error("Cannot specify tests with both positional args and --test.")
729 # modify NIC configuration to decode enhanced PCI IDs
730 wl_nics_orig = list(networkcard.check_pci(pci) for pci in settings.getValue('WHITELIST_NICS'))
731 settings.setValue('WHITELIST_NICS_ORIG', wl_nics_orig)
733 # sriov handling is performed on checked/expanded PCI IDs
734 settings.setValue('SRIOV_ENABLED', enable_sriov(wl_nics_orig))
737 for nic in wl_nics_orig:
738 tmp_nic = networkcard.get_nic_info(nic)
740 nic_list.append({'pci' : tmp_nic,
741 'type' : 'vf' if networkcard.get_sriov_pf(tmp_nic) else 'pf',
742 'mac' : networkcard.get_mac(tmp_nic),
743 'driver' : networkcard.get_driver(tmp_nic),
744 'device' : networkcard.get_device_name(tmp_nic)})
747 raise RuntimeError("Invalid network card PCI ID: '{}'".format(nic))
749 settings.setValue('NICS', nic_list)
750 # for backward compatibility
751 settings.setValue('WHITELIST_NICS', list(nic['pci'] for nic in nic_list))
753 # generate results directory name
754 date = datetime.datetime.fromtimestamp(time.time())
755 results_dir = "results_" + date.strftime('%Y-%m-%d_%H-%M-%S')
756 results_path = os.path.join(settings.getValue('LOG_DIR'), results_dir)
757 settings.setValue('RESULTS_PATH', results_path)
759 # create results directory
760 if not os.path.exists(results_path):
761 _LOGGER.info("Creating result directory: " + results_path)
762 os.makedirs(results_path)
763 # pylint: disable=too-many-nested-blocks
764 if settings.getValue('mode') == 'trafficgen':
765 # execute only traffic generator
766 _LOGGER.debug("Executing traffic generator:")
768 # set traffic details, so they can be passed to traffic ctl
769 traffic = copy.deepcopy(settings.getValue('TRAFFIC'))
770 traffic = functions.check_traffic(traffic)
772 traffic_ctl = component_factory.create_traffic(
773 traffic['traffic_type'],
774 loader.get_trafficgen_class())
776 traffic_ctl.send_traffic(traffic)
777 _LOGGER.debug("Traffic Results:")
778 traffic_ctl.print_results()
780 # write results into CSV file
781 result_file = os.path.join(results_path, "result.csv")
782 PerformanceTestCase.write_result_to_file(traffic_ctl.get_results(), result_file)
785 if args['integration']:
786 testcases = settings.getValue('INTEGRATION_TESTS')
788 testcases = settings.getValue('PERFORMANCE_TESTS')
790 if args['exact_test_name']:
791 exact_names = args['exact_test_name']
792 # positional args => exact matches only
794 for test_name in exact_names:
795 for test in testcases:
796 if test['Name'] == test_name:
797 selected_tests.append(test)
799 # --tests => apply filter to select requested tests
800 selected_tests = apply_filter(testcases, args['tests'])
802 # Default - run all tests
803 selected_tests = testcases
805 if not len(selected_tests):
806 _LOGGER.error("No tests matched --tests option or positional args. Done.")
811 # Add pylint exception: Redefinition of test type from
812 # testcases.integration.IntegrationTestCase to testcases.performance.PerformanceTestCase
813 # pylint: disable=redefined-variable-type
814 suite = unittest.TestSuite()
815 settings_snapshot = copy.deepcopy(settings.__dict__)
817 for i, cfg in enumerate(selected_tests):
818 settings.setValue('_TEST_INDEX', i)
819 test_name = cfg.get('Name', '<Name not set>')
821 test_params = settings.getValue('_PARAMS_LIST')
822 if isinstance(test_params, list):
824 if i >= len(test_params):
825 list_index = len(test_params) - 1
826 test_params = test_params[list_index]
827 if settings.getValue('CUMULATIVE_PARAMS'):
828 test_params = merge_spec(settings.getValue('TEST_PARAMS'), test_params)
829 settings.setValue('TEST_PARAMS', test_params)
831 if args['integration']:
832 test = IntegrationTestCase(cfg)
834 test = PerformanceTestCase(cfg)
837 suite.addTest(MockTestCase('', True, test.name))
839 # pylint: disable=broad-except
840 except (Exception) as ex:
841 _LOGGER.exception("Failed to run test: %s", test_name)
842 suite.addTest(MockTestCase(str(ex), False, test_name))
843 _LOGGER.info("Continuing with next test...")
845 if not settings.getValue('CUMULATIVE_PARAMS'):
846 settings.restore_from_dict(settings_snapshot)
848 settings.restore_from_dict(settings_snapshot)
851 # Generate and printout Performance Matrix
853 generate_performance_matrix(selected_tests, results_path)
855 # generate final rst report with results of all executed TCs
856 generate_final_report()
860 if settings.getValue('XUNIT'):
861 xmlrunner.XMLTestRunner(
862 output=settings.getValue('XUNIT_DIR'), outsuffix="",
863 verbosity=0).run(suite)
866 pod_name = args['opnfvpod']
867 installer_name = str(settings.getValue('OPNFV_INSTALLER')).lower()
868 opnfv_url = settings.getValue('OPNFV_URL')
869 pkg_list = settings.getValue('PACKAGE_LIST')
871 int_data = {'pod': pod_name,
872 'build_tag': get_build_tag(),
873 'installer': installer_name,
874 'pkg_list': pkg_list,
876 # pass vswitch name from configuration to be used for failed
877 # TCs; In case of successful TCs it is safer to use vswitch
878 # name from CSV as TC can override global configuration
879 'vswitch': str(settings.getValue('VSWITCH')).lower()}
880 tc_names = [tc['Name'] for tc in selected_tests]
881 opnfvdashboard.results2opnfv_dashboard(tc_names, results_path, int_data)
883 # cleanup before exit
886 if __name__ == "__main__":