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.
35 from conf import settings
36 import core.component_factory as component_factory
37 from core.loader import Loader
38 from testcases import PerformanceTestCase
39 from testcases import IntegrationTestCase
40 from tools import tasks
41 from tools import networkcard
42 from tools import functions
43 from tools.pkt_gen import trafficgen
44 from tools.opnfvdashboard import opnfvdashboard
46 sys.dont_write_bytecode = True
49 'debug': logging.DEBUG,
51 'warning': logging.WARNING,
52 'error': logging.ERROR,
53 'critical': logging.CRITICAL
56 _CURR_DIR = os.path.dirname(os.path.realpath(__file__))
58 _TEMPLATE_RST = {'head' : os.path.join(_CURR_DIR, 'tools/report/report_head.rst'),
59 'foot' : os.path.join(_CURR_DIR, 'tools/report/report_foot.rst'),
60 'final' : 'test_report.rst',
61 'tmp' : os.path.join(_CURR_DIR, 'tools/report/report_tmp_caption.rst')
65 _LOGGER = logging.getLogger()
67 def parse_arguments():
69 Parse command line arguments.
71 class _SplitTestParamsAction(argparse.Action):
73 Parse and split the '--test-params' argument.
75 This expects either 'x=y', 'x=y,z' or 'x' (implicit true)
76 values. For multiple overrides use a ; separated list for
77 e.g. --test-params 'x=z; y=(a,b)'
79 def __call__(self, parser, namespace, values, option_string=None):
82 for param, _, value in re.findall('([^;=]+)(=([^;]+))?', values):
87 # values are passed inside string from CLI, so we must retype them accordingly
89 results[param] = ast.literal_eval(value)
91 # for backward compatibility, we have to accept strings without quotes
92 _LOGGER.warning("Adding missing quotes around string value: %s = %s",
94 results[param] = str(value)
98 setattr(namespace, self.dest, results)
100 class _ValidateFileAction(argparse.Action):
101 """Validate a file can be read from before using it.
103 def __call__(self, parser, namespace, values, option_string=None):
104 if not os.path.isfile(values):
105 raise argparse.ArgumentTypeError(
106 'the path \'%s\' is not a valid path' % values)
107 elif not os.access(values, os.R_OK):
108 raise argparse.ArgumentTypeError(
109 'the path \'%s\' is not accessible' % values)
111 setattr(namespace, self.dest, values)
113 class _ValidateDirAction(argparse.Action):
114 """Validate a directory can be written to before using it.
116 def __call__(self, parser, namespace, values, option_string=None):
117 if not os.path.isdir(values):
118 raise argparse.ArgumentTypeError(
119 'the path \'%s\' is not a valid path' % values)
120 elif not os.access(values, os.W_OK):
121 raise argparse.ArgumentTypeError(
122 'the path \'%s\' is not accessible' % values)
124 setattr(namespace, self.dest, values)
126 def list_logging_levels():
127 """Give a summary of all available logging levels.
129 :return: List of verbosity level names in decreasing order of
132 return sorted(VERBOSITY_LEVELS.keys(),
133 key=lambda x: VERBOSITY_LEVELS[x])
135 parser = argparse.ArgumentParser(prog=__file__, formatter_class=
136 argparse.ArgumentDefaultsHelpFormatter)
137 parser.add_argument('--version', action='version', version='%(prog)s 0.2')
138 parser.add_argument('--list', '--list-tests', action='store_true',
139 help='list all tests and exit')
140 parser.add_argument('--list-trafficgens', action='store_true',
141 help='list all traffic generators and exit')
142 parser.add_argument('--list-collectors', action='store_true',
143 help='list all system metrics loggers and exit')
144 parser.add_argument('--list-vswitches', action='store_true',
145 help='list all system vswitches and exit')
146 parser.add_argument('--list-fwdapps', action='store_true',
147 help='list all system forwarding applications and exit')
148 parser.add_argument('--list-vnfs', action='store_true',
149 help='list all system vnfs and exit')
150 parser.add_argument('--list-loadgens', action='store_true',
151 help='list all background load generators')
152 parser.add_argument('--list-settings', action='store_true',
153 help='list effective settings configuration and exit')
154 parser.add_argument('exact_test_name', nargs='*', help='Exact names of\
155 tests to run. E.g "vsperf phy2phy_tput phy2phy_cont"\
156 runs only the two tests with those exact names.\
157 To run all tests omit both positional args and --tests arg.')
159 group = parser.add_argument_group('test selection options')
160 group.add_argument('-m', '--mode', help='vsperf mode of operation;\
161 Values: "normal" - execute vSwitch, VNF and traffic generator;\
162 "trafficgen" - execute only traffic generator; "trafficgen-off" \
163 - execute vSwitch and VNF; trafficgen-pause - execute vSwitch \
164 and VNF but pause before traffic transmission ', default='normal')
166 group.add_argument('-f', '--test-spec', help='test specification file')
167 group.add_argument('-d', '--test-dir', help='directory containing tests')
168 group.add_argument('-t', '--tests', help='Comma-separated list of terms \
169 indicating tests to run. e.g. "RFC2544,!p2p" - run all tests whose\
170 name contains RFC2544 less those containing "p2p"; "!back2back" - \
171 run all tests except those containing back2back')
172 group.add_argument('--verbosity', choices=list_logging_levels(),
174 group.add_argument('--integration', action='store_true', help='execute integration tests')
175 group.add_argument('--trafficgen', help='traffic generator to use')
176 group.add_argument('--vswitch', help='vswitch implementation to use')
177 group.add_argument('--fwdapp', help='packet forwarding application to use')
178 group.add_argument('--vnf', help='vnf to use')
179 group.add_argument('--loadgen', help='loadgen to use')
180 group.add_argument('--sysmetrics', help='system metrics logger to use')
181 group = parser.add_argument_group('test behavior options')
182 group.add_argument('--xunit', action='store_true',
183 help='enable xUnit-formatted output')
184 group.add_argument('--xunit-dir', action=_ValidateDirAction,
185 help='output directory of xUnit-formatted output')
186 group.add_argument('--load-env', action='store_true',
187 help='enable loading of settings from the environment')
188 group.add_argument('--conf-file', action=_ValidateFileAction,
189 help='settings file')
190 group.add_argument('--test-params', action=_SplitTestParamsAction,
191 help='csv list of test parameters: key=val; e.g. '
192 'TRAFFICGEN_PKT_SIZES=(64,128);TRAFICGEN_DURATION=30; '
193 'GUEST_LOOPBACK=["l2fwd"] ...')
194 group.add_argument('--opnfvpod', help='name of POD in opnfv')
196 args = vars(parser.parse_args())
201 def configure_logging(level):
202 """Configure logging.
204 log_file_default = os.path.join(
205 settings.getValue('LOG_DIR'), settings.getValue('LOG_FILE_DEFAULT'))
206 log_file_host_cmds = os.path.join(
207 settings.getValue('LOG_DIR'), settings.getValue('LOG_FILE_HOST_CMDS'))
208 log_file_traffic_gen = os.path.join(
209 settings.getValue('LOG_DIR'),
210 settings.getValue('LOG_FILE_TRAFFIC_GEN'))
212 _LOGGER.setLevel(logging.DEBUG)
214 stream_logger = logging.StreamHandler(sys.stdout)
215 stream_logger.setLevel(VERBOSITY_LEVELS[level])
216 stream_logger.setFormatter(logging.Formatter(
217 '[%(levelname)-5s] %(asctime)s : (%(name)s) - %(message)s'))
218 _LOGGER.addHandler(stream_logger)
220 file_logger = logging.FileHandler(filename=log_file_default)
221 file_logger.setLevel(logging.DEBUG)
222 _LOGGER.addHandler(file_logger)
224 class CommandFilter(logging.Filter):
225 """Filter out strings beginning with 'cmd :'"""
226 def filter(self, record):
227 return record.getMessage().startswith(tasks.CMD_PREFIX)
229 class TrafficGenCommandFilter(logging.Filter):
230 """Filter out strings beginning with 'gencmd :'"""
231 def filter(self, record):
232 return record.getMessage().startswith(trafficgen.CMD_PREFIX)
234 cmd_logger = logging.FileHandler(filename=log_file_host_cmds)
235 cmd_logger.setLevel(logging.DEBUG)
236 cmd_logger.addFilter(CommandFilter())
237 _LOGGER.addHandler(cmd_logger)
239 gen_logger = logging.FileHandler(filename=log_file_traffic_gen)
240 gen_logger.setLevel(logging.DEBUG)
241 gen_logger.addFilter(TrafficGenCommandFilter())
242 _LOGGER.addHandler(gen_logger)
245 def apply_filter(tests, tc_filter):
246 """Allow a subset of tests to be conveniently selected
248 :param tests: The list of Tests from which to select.
249 :param tc_filter: A case-insensitive string of comma-separated terms
250 indicating the Tests to select.
251 e.g. 'RFC' - select all tests whose name contains 'RFC'
252 e.g. 'RFC,burst' - select all tests whose name contains 'RFC' or
254 e.g. 'RFC,burst,!p2p' - select all tests whose name contains 'RFC'
255 or 'burst' and from these remove any containing 'p2p'.
256 e.g. '' - empty string selects all tests.
257 :return: A list of the selected Tests.
259 # if negative filter is first we have to start with full list of tests
260 if tc_filter.strip()[0] == '!':
264 if tc_filter is None:
267 for term in [x.strip() for x in tc_filter.lower().split(",")]:
268 if not term or term[0] != '!':
269 # Add matching tests from 'tests' into results
270 result.extend([test for test in tests \
271 if test['Name'].lower().find(term) >= 0])
273 # Term begins with '!' so we remove matching tests
274 result = [test for test in result \
275 if test['Name'].lower().find(term[1:]) < 0]
280 def check_and_set_locale():
281 """ Function will check locale settings. In case, that it isn't configured
282 properly, then default values specified by DEFAULT_LOCALE will be used.
285 system_locale = locale.getdefaultlocale()
286 if None in system_locale:
287 os.environ['LC_ALL'] = settings.getValue('DEFAULT_LOCALE')
288 _LOGGER.warning("Locale was not properly configured. Default values were set. Old locale: %s, New locale: %s",
289 system_locale, locale.getdefaultlocale())
291 def get_vswitch_names(rst_files):
292 """ Function will return a list of vSwitches detected in given ``rst_files``.
294 vswitch_names = set()
297 output = subprocess.check_output(['grep', '-h', '^* vSwitch'] + rst_files).decode().splitlines()
299 match = re.search(r'^\* vSwitch: ([^,]+)', str(line))
301 vswitch_names.add(match.group(1))
303 if len(vswitch_names):
304 return list(vswitch_names)
306 except subprocess.CalledProcessError:
307 _LOGGER.warning('Cannot detect vSwitches used during testing.')
309 # fallback to the default value
313 """ Function will return a Jenkins job ID environment variable.
317 build_tag = os.environ['BUILD_TAG']
320 _LOGGER.warning('Cannot detect Jenkins job ID')
325 def generate_final_report():
326 """ Function will check if partial test results are available
327 and generates final report in rst format.
330 path = settings.getValue('RESULTS_PATH')
331 # check if there are any results in rst format
332 rst_results = glob.glob(os.path.join(path, 'result*rst'))
333 pkt_processors = get_vswitch_names(rst_results)
336 test_report = os.path.join(path, '{}_{}'.format('_'.join(pkt_processors), _TEMPLATE_RST['final']))
337 # create report caption directly - it is not worth to execute jinja machinery
338 report_caption = '{}\n{} {}\n{}\n\n'.format(
339 '============================================================',
340 'Performance report for',
341 ', '.join(pkt_processors),
342 '============================================================')
344 with open(_TEMPLATE_RST['tmp'], 'w') as file_:
345 file_.write(report_caption)
347 retval = subprocess.call('cat {} {} {} {} > {}'.format(_TEMPLATE_RST['tmp'], _TEMPLATE_RST['head'],
348 ' '.join(rst_results), _TEMPLATE_RST['foot'],
349 test_report), shell=True)
350 if retval == 0 and os.path.isfile(test_report):
351 _LOGGER.info('Overall test report written to "%s"', test_report)
353 _LOGGER.error('Generation of overall test report has failed.')
355 # remove temporary file
356 os.remove(_TEMPLATE_RST['tmp'])
358 except subprocess.CalledProcessError:
359 _LOGGER.error('Generatrion of overall test report has failed.')
362 def enable_sriov(nic_list):
363 """ Enable SRIOV for given enhanced PCI IDs
365 :param nic_list: A list of enhanced PCI IDs
367 # detect if sriov is required
370 if networkcard.is_sriov_nic(nic):
371 tmp_nic = nic.split('|')
372 if tmp_nic[0] in sriov_nic:
373 if int(tmp_nic[1][2:]) > sriov_nic[tmp_nic[0]]:
374 sriov_nic[tmp_nic[0]] = int(tmp_nic[1][2:])
376 sriov_nic.update({tmp_nic[0] : int(tmp_nic[1][2:])})
378 # sriov is required for some NICs
380 for nic in sriov_nic:
381 # check if SRIOV is supported and enough virt interfaces are available
382 if not networkcard.is_sriov_supported(nic) \
383 or networkcard.get_sriov_numvfs(nic) <= sriov_nic[nic]:
384 # if not, enable and set appropriate number of VFs
385 if not networkcard.set_sriov_numvfs(nic, sriov_nic[nic] + 1):
386 raise RuntimeError('SRIOV cannot be enabled for NIC {}'.format(nic))
388 _LOGGER.debug("SRIOV enabled for NIC %s", nic)
390 # WORKAROUND: it has been observed with IXGBE(VF) driver,
391 # that NIC doesn't correclty dispatch traffic to VFs based
392 # on their MAC address. Unbind and bind to the same driver
394 networkcard.reinit_vfs(nic)
396 # After SRIOV is enabled it takes some time until network drivers
397 # properly initialize all cards.
398 # Wait also in case, that SRIOV was already configured as it can be
399 # configured automatically just before vsperf execution.
407 def disable_sriov(nic_list):
408 """ Disable SRIOV for given PCI IDs
410 :param nic_list: A list of enhanced PCI IDs
413 if networkcard.is_sriov_nic(nic):
414 if not networkcard.set_sriov_numvfs(nic.split('|')[0], 0):
415 raise RuntimeError('SRIOV cannot be disabled for NIC {}'.format(nic))
417 _LOGGER.debug("SRIOV disabled for NIC %s", nic.split('|')[0])
420 def handle_list_options(args):
421 """ Process --list cli arguments if needed
423 :param args: A dictionary with all CLI arguments
425 if args['list_trafficgens']:
426 print(Loader().get_trafficgens_printable())
429 if args['list_collectors']:
430 print(Loader().get_collectors_printable())
433 if args['list_vswitches']:
434 print(Loader().get_vswitches_printable())
437 if args['list_vnfs']:
438 print(Loader().get_vnfs_printable())
441 if args['list_fwdapps']:
442 print(Loader().get_pktfwds_printable())
445 if args['list_loadgens']:
446 print(Loader().get_loadgens_printable())
449 if args['list_settings']:
458 def list_testcases(args):
459 """ Print list of testcases requested by --list CLI argument
461 :param args: A dictionary with all CLI arguments
464 if args['integration']:
465 testcases = settings.getValue('INTEGRATION_TESTS')
467 testcases = settings.getValue('PERFORMANCE_TESTS')
469 print("Available Tests:")
470 print("================")
472 for test in testcases:
473 description = functions.format_description(test['Description'], 70)
474 if len(test['Name']) < 40:
475 print('* {:40} {}'.format('{}:'.format(test['Name']), description[0]))
477 print('* {}'.format('{}:'.format(test['Name'])))
478 print(' {:40} {}'.format('', description[0]))
479 for i in range(1, len(description)):
480 print(' {:40} {}'.format('', description[i]))
486 def vsperf_finalize():
487 """ Clean up before exit
489 # remove directory if no result files were created
491 results_path = settings.getValue('RESULTS_PATH')
492 if os.path.exists(results_path):
493 files_list = os.listdir(results_path)
495 _LOGGER.info("Removing empty result directory: " + results_path)
496 shutil.rmtree(results_path)
497 except AttributeError:
498 # skip it if parameter doesn't exist
501 # disable SRIOV if needed
503 if settings.getValue('SRIOV_ENABLED'):
504 disable_sriov(settings.getValue('WHITELIST_NICS_ORIG'))
505 except AttributeError:
506 # skip it if parameter doesn't exist
510 class MockTestCase(unittest.TestCase):
511 """Allow use of xmlrunner to generate Jenkins compatible output without
512 using xmlrunner to actually run tests.
515 suite = unittest.TestSuite()
516 suite.addTest(MockTestCase('Test1 passed ', True, 'Test1'))
517 suite.addTest(MockTestCase('Test2 failed because...', False, 'Test2'))
518 xmlrunner.XMLTestRunner(...).run(suite)
521 def __init__(self, msg, is_pass, test_name):
524 self.is_pass = is_pass
526 #dynamically create a test method with the right name
527 #but point the method at our generic test method
528 setattr(MockTestCase, test_name, self.generic_test)
530 super(MockTestCase, self).__init__(test_name)
532 def generic_test(self):
533 """Provide a generic function that raises or not based
534 on how self.is_pass was set in the constructor"""
535 self.assertTrue(self.is_pass, self.msg)
537 # pylint: disable=too-many-locals, too-many-branches, too-many-statements
541 args = parse_arguments()
545 settings.load_from_dir(os.path.join(_CURR_DIR, 'conf'))
547 # Load non performance/integration tests
548 if args['integration']:
549 settings.load_from_dir(os.path.join(_CURR_DIR, 'conf/integration'))
551 # load command line parameters first in case there are settings files
553 settings.load_from_dict(args)
555 if args['conf_file']:
556 settings.load_from_file(args['conf_file'])
559 settings.load_from_env()
561 # reload command line parameters since these should take higher priority
562 # than both a settings file and environment variables
563 settings.load_from_dict(args)
565 settings.setValue('mode', args['mode'])
567 # update paths to trafficgens if required
568 if settings.getValue('mode') == 'trafficgen':
569 functions.settings_update_paths()
571 # if required, handle list-* operations
572 handle_list_options(args)
574 configure_logging(settings.getValue('VERBOSITY'))
576 # check and fix locale
577 check_and_set_locale()
579 # configure trafficgens
580 if args['trafficgen']:
581 trafficgens = Loader().get_trafficgens()
582 if args['trafficgen'] not in trafficgens:
583 _LOGGER.error('There are no trafficgens matching \'%s\' found in'
584 ' \'%s\'. Exiting...', args['trafficgen'],
585 settings.getValue('TRAFFICGEN_DIR'))
588 # configuration validity checks
590 vswitch_none = args['vswitch'].strip().lower() == 'none'
592 settings.setValue('VSWITCH', 'none')
594 vswitches = Loader().get_vswitches()
595 if args['vswitch'] not in vswitches:
596 _LOGGER.error('There are no vswitches matching \'%s\' found in'
597 ' \'%s\'. Exiting...', args['vswitch'],
598 settings.getValue('VSWITCH_DIR'))
602 settings.setValue('PKTFWD', args['fwdapp'])
603 fwdapps = Loader().get_pktfwds()
604 if args['fwdapp'] not in fwdapps:
605 _LOGGER.error('There are no forwarding application'
606 ' matching \'%s\' found in'
607 ' \'%s\'. Exiting...', args['fwdapp'],
608 settings.getValue('PKTFWD_DIR'))
612 vnfs = Loader().get_vnfs()
613 if args['vnf'] not in vnfs:
614 _LOGGER.error('there are no vnfs matching \'%s\' found in'
615 ' \'%s\'. exiting...', args['vnf'],
616 settings.getValue('VNF_DIR'))
620 loadgens = Loader().get_loadgens()
621 if args['loadgen'] not in loadgens:
622 _LOGGER.error('There are no loadgens matching \'%s\' found in'
623 ' \'%s\'. Exiting...', args['loadgen'],
624 settings.getValue('LOADGEN_DIR'))
627 if args['exact_test_name'] and args['tests']:
628 _LOGGER.error("Cannot specify tests with both positional args and --test.")
631 # modify NIC configuration to decode enhanced PCI IDs
632 wl_nics_orig = list(networkcard.check_pci(pci) for pci in settings.getValue('WHITELIST_NICS'))
633 settings.setValue('WHITELIST_NICS_ORIG', wl_nics_orig)
635 # sriov handling is performed on checked/expanded PCI IDs
636 settings.setValue('SRIOV_ENABLED', enable_sriov(wl_nics_orig))
639 for nic in wl_nics_orig:
640 tmp_nic = networkcard.get_nic_info(nic)
642 nic_list.append({'pci' : tmp_nic,
643 'type' : 'vf' if networkcard.get_sriov_pf(tmp_nic) else 'pf',
644 'mac' : networkcard.get_mac(tmp_nic),
645 'driver' : networkcard.get_driver(tmp_nic),
646 'device' : networkcard.get_device_name(tmp_nic)})
649 raise RuntimeError("Invalid network card PCI ID: '{}'".format(nic))
651 settings.setValue('NICS', nic_list)
652 # for backward compatibility
653 settings.setValue('WHITELIST_NICS', list(nic['pci'] for nic in nic_list))
655 # generate results directory name
656 date = datetime.datetime.fromtimestamp(time.time())
657 results_dir = "results_" + date.strftime('%Y-%m-%d_%H-%M-%S')
658 results_path = os.path.join(settings.getValue('LOG_DIR'), results_dir)
659 settings.setValue('RESULTS_PATH', results_path)
661 # create results directory
662 if not os.path.exists(results_path):
663 _LOGGER.info("Creating result directory: " + results_path)
664 os.makedirs(results_path)
666 if settings.getValue('mode') == 'trafficgen':
667 # execute only traffic generator
668 _LOGGER.debug("Executing traffic generator:")
670 # set traffic details, so they can be passed to traffic ctl
671 traffic = copy.deepcopy(settings.getValue('TRAFFIC'))
673 traffic = functions.check_traffic(traffic)
675 traffic_ctl = component_factory.create_traffic(
676 traffic['traffic_type'],
677 loader.get_trafficgen_class())
679 traffic_ctl.send_traffic(traffic)
680 _LOGGER.debug("Traffic Results:")
681 traffic_ctl.print_results()
683 # write results into CSV file
684 result_file = os.path.join(results_path, "result.csv")
685 PerformanceTestCase.write_result_to_file(traffic_ctl.get_results(), result_file)
688 if args['integration']:
689 testcases = settings.getValue('INTEGRATION_TESTS')
691 testcases = settings.getValue('PERFORMANCE_TESTS')
693 if args['exact_test_name']:
694 exact_names = args['exact_test_name']
695 # positional args => exact matches only
696 selected_tests = [test for test in testcases if test['Name'] in exact_names]
698 # --tests => apply filter to select requested tests
699 selected_tests = apply_filter(testcases, args['tests'])
701 # Default - run all tests
702 selected_tests = testcases
704 if not len(selected_tests):
705 _LOGGER.error("No tests matched --tests option or positional args. Done.")
710 # Add pylint exception: Redefinition of test type from
711 # testcases.integration.IntegrationTestCase to testcases.performance.PerformanceTestCase
712 # pylint: disable=redefined-variable-type
713 suite = unittest.TestSuite()
714 settings_snapshot = copy.deepcopy(settings.__dict__)
715 for cfg in selected_tests:
716 test_name = cfg.get('Name', '<Name not set>')
718 if args['integration']:
719 test = IntegrationTestCase(cfg)
721 test = PerformanceTestCase(cfg)
723 suite.addTest(MockTestCase('', True, test.name))
724 # pylint: disable=broad-except
725 except (Exception) as ex:
726 _LOGGER.exception("Failed to run test: %s", test_name)
727 suite.addTest(MockTestCase(str(ex), False, test_name))
728 _LOGGER.info("Continuing with next test...")
730 settings.restore_from_dict(settings_snapshot)
732 # generate final rst report with results of all executed TCs
733 generate_final_report()
735 if settings.getValue('XUNIT'):
736 xmlrunner.XMLTestRunner(
737 output=settings.getValue('XUNIT_DIR'), outsuffix="",
738 verbosity=0).run(suite)
741 pod_name = args['opnfvpod']
742 installer_name = str(settings.getValue('OPNFV_INSTALLER')).lower()
743 opnfv_url = settings.getValue('OPNFV_URL')
744 pkg_list = settings.getValue('PACKAGE_LIST')
746 int_data = {'pod': pod_name,
747 'build_tag': get_build_tag(),
748 'installer': installer_name,
749 'pkg_list': pkg_list,
751 # pass vswitch name from configuration to be used for failed
752 # TCs; In case of successful TCs it is safer to use vswitch
753 # name from CSV as TC can override global configuration
754 'vswitch': str(settings.getValue('VSWITCH')).lower()}
755 tc_names = [tc['Name'] for tc in selected_tests]
756 opnfvdashboard.results2opnfv_dashboard(tc_names, results_path, int_data)
758 # cleanup before exit
761 if __name__ == "__main__":