2 # Copyright 2016 Cisco Systems, Inc. All rights reserved.
4 # Licensed under the Apache License, Version 2.0 (the "License"); you may
5 # not use this file except in compliance with the License. You may obtain
6 # a copy of the License at
8 # http://www.apache.org/licenses/LICENSE-2.0
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 # License for the specific language governing permissions and limitations
26 from attrdict import AttrDict
27 from logging import FileHandler
29 from pkg_resources import resource_string
31 from .__init__ import __version__
32 from .chain_runner import ChainRunner
33 from .cleanup import Cleaner
34 from .config import config_load
35 from .config import config_loads
36 from . import credentials
37 from .fluentd import FluentLogHandler
40 from .nfvbenchd import WebServer
41 from .specs import ChainType
42 from .specs import Specs
43 from .summarizer import NFVBenchSummarizer
49 class NFVBench(object):
50 """Main class of NFV benchmarking tool."""
53 STATUS_ERROR = 'ERROR'
55 def __init__(self, config, openstack_spec, config_plugin, factory, notifier=None):
56 # the base config never changes for a given NFVbench instance
57 self.base_config = config
58 # this is the running config, updated at every run()
60 self.config_plugin = config_plugin
61 self.factory = factory
62 self.notifier = notifier
63 self.cred = credentials.Credentials(config.openrc_file, config.clouds_detail, None, False) \
64 if config.openrc_file or config.clouds_detail else None
65 self.chain_runner = None
67 self.specs.set_openstack_spec(openstack_spec)
71 def set_notifier(self, notifier):
72 self.notifier = notifier
74 def run(self, opts, args, dry_run=False):
75 """This run() method is called for every NFVbench benchmark request.
77 In CLI mode, this method is called only once per invocation.
78 In REST server mode, this is called once per REST POST request
79 On dry_run, show the running config in json format then exit
81 status = NFVBench.STATUS_OK
85 # take a snapshot of the current time for this new run
86 # so that all subsequent logs can relate to this run
87 fluent_logger.start_new_run()
90 # recalc the running config based on the base config and options for this run
91 self._update_config(opts)
94 print((json.dumps(self.config, sort_keys=True, indent=4)))
97 # check that an empty openrc file (no OpenStack) is only allowed
99 if (not self.config.openrc_file and not self.config.clouds_detail) and \
100 self.config.service_chain != ChainType.EXT:
101 raise Exception("openrc_file or clouds_detail in the configuration is required"
102 " for PVP/PVVP chains")
104 self.specs.set_run_spec(self.config_plugin.get_run_spec(self.config,
105 self.specs.openstack))
106 self.chain_runner = ChainRunner(self.config,
112 # make sure that the min frame size is 64
114 for frame_size in self.config.frame_sizes:
116 if int(frame_size) < min_packet_size:
117 frame_size = str(min_packet_size)
118 LOG.info("Adjusting frame size %s bytes to minimum size %s bytes",
119 frame_size, min_packet_size)
120 if frame_size not in new_frame_sizes:
121 new_frame_sizes.append(frame_size)
123 new_frame_sizes.append(frame_size.upper())
124 self.config.frame_sizes = new_frame_sizes
126 "date": datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
127 "nfvbench_version": __version__,
128 "config": self.config_plugin.prepare_results_config(copy.deepcopy(self.config)),
131 "service_chain": self.chain_runner.run(),
132 "versions": self.chain_runner.get_version(),
136 if self.specs.openstack:
137 result['openstack_spec'] = {"vswitch": self.specs.openstack.vswitch,
138 "encaps": self.specs.openstack.encaps}
139 result['benchmarks']['network']['versions'].update(self.config_plugin.get_version())
141 status = NFVBench.STATUS_ERROR
142 message = traceback.format_exc()
143 except KeyboardInterrupt:
144 status = NFVBench.STATUS_ERROR
145 message = traceback.format_exc()
147 if self.chain_runner:
148 self.chain_runner.close()
150 if status == NFVBench.STATUS_OK:
151 # result2 = utils.dict_to_json_dict(result)
158 'error_message': message
161 def prepare_summary(self, result):
162 """Prepare summary of the result to print and send it to logger (eg: fluentd)."""
164 summary = NFVBenchSummarizer(result, fluent_logger)
165 LOG.info(str(summary))
167 def save(self, result):
168 """Save results in json format file."""
169 utils.save_json_result(result,
170 self.config.json_file,
171 self.config.std_json_path,
172 self.config.service_chain,
173 self.config.service_chain_count,
174 self.config.flow_count,
175 self.config.frame_sizes,
177 self.config.group_id)
179 def _update_config(self, opts):
180 """Recalculate the running config based on the base config and opts.
182 Sanity check on the config is done here as well.
184 self.config = AttrDict(dict(self.base_config))
185 # Update log file handler if needed after a config update (REST mode)
186 if 'log_file' in opts:
188 (path, _filename) = os.path.split(opts['log_file'])
189 if not os.path.exists(path):
191 'Path %s does not exist. Please verify root path is shared with host. Path '
192 'will be created.', path)
194 LOG.info('%s is created.', path)
195 if not any(isinstance(h, FileHandler) for h in log.getLogger().handlers):
196 log.add_file_logger(opts['log_file'])
198 for h in log.getLogger().handlers:
199 if isinstance(h, FileHandler) and h.baseFilename != opts['log_file']:
200 # clean log file handler
201 log.getLogger().removeHandler(h)
202 log.add_file_logger(opts['log_file'])
204 self.config.update(opts)
207 config.service_chain = config.service_chain.upper()
208 config.service_chain_count = int(config.service_chain_count)
209 if config.l2_loopback:
210 # force the number of chains to be 1 in case of untagged l2 loopback
211 # (on the other hand, multiple L2 vlan tagged service chains are allowed)
212 if not config.vlan_tagging:
213 config.service_chain_count = 1
214 config.service_chain = ChainType.EXT
216 LOG.info('Running L2 loopback: using EXT chain/no ARP')
218 # allow oversized vlan lists, just clip them
220 vlans = [list(v) for v in config.vlans]
222 del v[config.service_chain_count:]
227 # traffic profile override options
228 if 'frame_sizes' in opts:
231 unidir = opts['unidir']
232 override_custom_traffic(config, opts['frame_sizes'], unidir)
233 LOG.info("Frame size has been set to %s for current configuration", opts['frame_sizes'])
235 config.flow_count = utils.parse_flow_count(config.flow_count)
236 required_flow_count = config.service_chain_count * 2
237 if config.flow_count < required_flow_count:
238 LOG.info("Flow count %d has been set to minimum value of '%d' "
239 "for current configuration", config.flow_count,
241 config.flow_count = required_flow_count
243 if config.flow_count % 2:
244 config.flow_count += 1
246 # Possibly adjust the cache size
247 if config.cache_size < 0:
248 config.cache_size = config.flow_count
250 # The size must be capped to 10000 (where does this limit come from?)
251 config.cache_size = min(config.cache_size, 10000)
253 config.duration_sec = float(config.duration_sec)
254 config.interval_sec = float(config.interval_sec)
255 config.pause_sec = float(config.pause_sec)
257 if config.traffic is None or not config.traffic:
258 raise Exception("Missing traffic property in configuration")
260 if config.openrc_file:
261 config.openrc_file = os.path.expanduser(config.openrc_file)
262 if config.flavor.vcpus < 2:
263 raise Exception("Flavor vcpus must be >= 2")
265 config.ndr_run = (not config.no_traffic and
266 'ndr' in config.rate.strip().lower().split('_'))
267 config.pdr_run = (not config.no_traffic and
268 'pdr' in config.rate.strip().lower().split('_'))
269 config.single_run = (not config.no_traffic and
270 not (config.ndr_run or config.pdr_run))
272 config.json_file = config.json if config.json else None
274 (path, _filename) = os.path.split(config.json)
275 if not os.path.exists(path):
276 raise Exception('Please provide existing path for storing results in JSON file. '
277 'Path used: {path}'.format(path=path))
279 config.std_json_path = config.std_json if config.std_json else None
280 if config.std_json_path:
281 if not os.path.exists(config.std_json):
282 raise Exception('Please provide existing path for storing results in JSON file. '
283 'Path used: {path}'.format(path=config.std_json_path))
285 # Check that multiqueue is between 1 and 8 (8 is the max allowed by libvirt/qemu)
286 if config.vif_multiqueue_size < 1 or config.vif_multiqueue_size > 8:
287 raise Exception('vif_multiqueue_size (%d) must be in [1..8]' %
288 config.vif_multiqueue_size)
290 # VxLAN and MPLS sanity checks
291 if config.vxlan or config.mpls:
292 if config.vlan_tagging:
293 config.vlan_tagging = False
294 config.no_latency_streams = True
295 config.no_latency_stats = True
296 config.no_flow_stats = True
297 LOG.info('VxLAN or MPLS: vlan_tagging forced to False '
298 '(inner VLAN tagging must be disabled)')
300 self.config_plugin.validate_config(config, self.specs.openstack)
304 """Argument type to be used in parser.add_argument()
305 When a boolean like value is expected to be given
307 return (str(x).lower() != 'false') \
308 and (str(x).lower() != 'no') \
309 and (str(x).lower() != '0')
313 """Argument type to be used in parser.add_argument()
314 When an integer type value is expected to be given
315 (returns 0 if argument is invalid, hexa accepted)
320 def _parse_opts_from_cli():
321 parser = argparse.ArgumentParser()
323 parser.add_argument('--status', dest='status',
326 help='Provide NFVbench status')
328 parser.add_argument('-c', '--config', dest='config',
330 help='Override default values with a config file or '
331 'a yaml/json config string',
332 metavar='<file_name_or_yaml>')
334 parser.add_argument('--server', dest='server',
337 help='Run nfvbench in server mode')
339 parser.add_argument('--host', dest='host',
342 help='Host IP address on which server will be listening (default 0.0.0.0)')
344 parser.add_argument('-p', '--port', dest='port',
347 help='Port on which server will be listening (default 7555)')
349 parser.add_argument('-sc', '--service-chain', dest='service_chain',
350 choices=ChainType.names,
352 help='Service chain to run')
354 parser.add_argument('-scc', '--service-chain-count', dest='service_chain_count',
356 help='Set number of service chains to run',
357 metavar='<service_chain_count>')
359 parser.add_argument('-fc', '--flow-count', dest='flow_count',
361 help='Set number of total flows for all chains and all directions',
362 metavar='<flow_count>')
364 parser.add_argument('--rate', dest='rate',
366 help='Specify rate in pps, bps or %% as total for all directions',
369 parser.add_argument('--duration', dest='duration_sec',
371 help='Set duration to run traffic generator (in seconds)',
372 metavar='<duration_sec>')
374 parser.add_argument('--interval', dest='interval_sec',
376 help='Set interval to record traffic generator stats (in seconds)',
377 metavar='<interval_sec>')
379 parser.add_argument('--inter-node', dest='inter_node',
384 parser.add_argument('--sriov', dest='sriov',
387 help='Use SRIOV (no vswitch - requires SRIOV support in compute nodes)')
389 parser.add_argument('--use-sriov-middle-net', dest='use_sriov_middle_net',
392 help='Use SRIOV to handle the middle network traffic '
393 '(PVVP with SRIOV only)')
395 parser.add_argument('-d', '--debug', dest='debug',
398 help='print debug messages (verbose)')
400 parser.add_argument('-g', '--traffic-gen', dest='generator_profile',
402 help='Traffic generator profile to use')
404 parser.add_argument('-l3', '--l3-router', dest='l3_router',
407 help='Use L3 neutron routers to handle traffic')
409 parser.add_argument('-garp', '--gratuitous-arp', dest='periodic_gratuitous_arp',
412 help='Use gratuitous ARP to maintain session between TG '
413 'and L3 routers to handle traffic')
415 parser.add_argument('-0', '--no-traffic', dest='no_traffic',
418 help='Check config and connectivity only - do not generate traffic')
420 parser.add_argument('--no-arp', dest='no_arp',
423 help='Do not use ARP to find MAC addresses, '
424 'instead use values in config file')
426 parser.add_argument('--loop-vm-arp', dest='loop_vm_arp',
429 help='Use ARP to find MAC addresses '
430 'instead of using values from TRex ports (VPP forwarder only)')
432 parser.add_argument('--no-vswitch-access', dest='no_vswitch_access',
435 help='Skip vswitch configuration and retrieving of stats')
437 parser.add_argument('--vxlan', dest='vxlan',
440 help='Enable VxLan encapsulation')
442 parser.add_argument('--mpls', dest='mpls',
445 help='Enable MPLS encapsulation')
447 parser.add_argument('--no-cleanup', dest='no_cleanup',
450 help='no cleanup after run')
452 parser.add_argument('--cleanup', dest='cleanup',
455 help='Cleanup NFVbench resources (prompt to confirm)')
457 parser.add_argument('--force-cleanup', dest='force_cleanup',
460 help='Cleanup NFVbench resources (do not prompt)')
462 parser.add_argument('--restart', dest='restart',
465 help='Restart TRex server')
467 parser.add_argument('--json', dest='json',
469 help='store results in json format file',
470 metavar='<path>/<filename>')
472 parser.add_argument('--std-json', dest='std_json',
474 help='store results in json format file with nfvbench standard filename: '
475 '<service-chain-type>-<service-chain-count>-<flow-count>'
476 '-<packet-sizes>.json',
479 parser.add_argument('--show-default-config', dest='show_default_config',
482 help='print the default config in yaml format (unedited)')
484 parser.add_argument('--show-pre-config', dest='show_pre_config',
487 help='print the config in json format (cfg file applied)')
489 parser.add_argument('--show-config', dest='show_config',
492 help='print the running config in json format (final)')
494 parser.add_argument('-ss', '--show-summary', dest='summary',
496 help='Show summary from nfvbench json file',
499 parser.add_argument('-v', '--version', dest='version',
504 parser.add_argument('-fs', '--frame-size', dest='frame_sizes',
506 help='Override traffic profile frame sizes',
507 metavar='<frame_size_bytes or IMIX>')
509 parser.add_argument('--unidir', dest='unidir',
512 help='Override traffic profile direction (requires -fs)')
514 parser.add_argument('--log-file', '--logfile', dest='log_file',
516 help='Filename for saving logs',
517 metavar='<log_file>')
519 parser.add_argument('--user-label', '--userlabel', dest='user_label',
521 help='Custom label for performance records')
523 parser.add_argument('--hypervisor', dest='hypervisor',
525 metavar='<hypervisor name>',
526 help='Where chains must run ("compute", "az:", "az:compute")')
528 parser.add_argument('--l2-loopback', '--l2loopback', dest='l2_loopback',
530 metavar='<vlan(s)|no-tag|true|false>',
531 help='Port to port or port to switch to port L2 loopback '
532 'tagged with given VLAN id(s) or not (given \'no-tag\') '
533 '\'true\': use current vlans; \'false\': disable this mode.')
535 parser.add_argument('--i40e-mixed', dest='i40e_mixed',
538 metavar='<ignore,check,unbind>',
539 help='TRex behavior when dealing with a i40e network card driver'
540 ' [ https://trex-tgn.cisco.com/youtrack/issue/trex-528 ]')
542 parser.add_argument('--user-info', dest='user_info',
545 help='Custom data to be included as is '
546 'in the json report config branch - '
547 ' example, pay attention! no space: '
548 '--user-info=\'{"status":"explore","description":'
549 '{"target":"lab","ok":true,"version":2020}}\' - '
550 'this option may be repeated; given data will be merged.')
552 parser.add_argument('--vlan-tagging', dest='vlan_tagging',
557 help='Override the NFVbench \'vlan_tagging\' parameter')
559 parser.add_argument('--intf-speed', dest='intf_speed',
563 help='Override the NFVbench \'intf_speed\' '
564 'parameter (e.g. 10Gbps, auto, 16.72Gbps)')
566 parser.add_argument('--cores', dest='cores',
571 help='Override the T-Rex \'cores\' parameter')
573 parser.add_argument('--cache-size', dest='cache_size',
578 help='Specify the FE cache size (default: 0, flow-count if < 0)')
580 parser.add_argument('--service-mode', dest='service_mode',
583 help='Enable T-Rex service mode (for debugging purpose)')
585 parser.add_argument('--no-e2e-check', dest='no_e2e_check',
588 help='Skip "end to end" connectivity check (on test purpose)')
590 parser.add_argument('--no-flow-stats', dest='no_flow_stats',
593 help='Disable additional flow stats (on high load traffic)')
595 parser.add_argument('--no-latency-stats', dest='no_latency_stats',
598 help='Disable flow stats for latency traffic')
600 parser.add_argument('--no-latency-streams', dest='no_latency_streams',
603 help='Disable latency measurements (no streams)')
605 parser.add_argument('--user-id', dest='user_id',
610 help='Change json/log files ownership with this user (int)')
612 parser.add_argument('--group-id', dest='group_id',
617 help='Change json/log files ownership with this group (int)')
619 parser.add_argument('--show-trex-log', dest='show_trex_log',
622 help='Show the current TRex local server log file contents'
623 ' => diagnostic/help in case of configuration problems')
625 parser.add_argument('--debug-mask', dest='debug_mask',
630 help='General purpose register (debugging flags), '
631 'the hexadecimal notation (0x...) is accepted.'
632 'Designed for development needs (default: 0).')
634 opts, unknown_opts = parser.parse_known_args()
635 return opts, unknown_opts
638 def load_default_config():
639 default_cfg = resource_string(__name__, "cfg.default.yaml")
640 config = config_loads(default_cfg)
641 config.name = '(built-in default config)'
642 return config, default_cfg
645 def override_custom_traffic(config, frame_sizes, unidir):
646 """Override the traffic profiles with a custom one."""
647 if frame_sizes is not None:
648 traffic_profile_name = "custom_traffic_profile"
649 config.traffic_profile = [
651 "l2frame_size": frame_sizes,
652 "name": traffic_profile_name
656 traffic_profile_name = config.traffic["profile"]
658 bidirectional = config.traffic['bidirectional'] if unidir is None else not unidir
660 "bidirectional": bidirectional,
661 "profile": traffic_profile_name
665 def check_physnet(name, netattrs):
666 if not netattrs.physical_network:
667 raise Exception("SRIOV requires physical_network to be specified for the {n} network"
669 if not netattrs.segmentation_id:
670 raise Exception("SRIOV requires segmentation_id to be specified for the {n} network"
673 def status_cleanup(config, cleanup, force_cleanup):
674 LOG.info('Version: %s', pbr.version.VersionInfo('nfvbench').version_string_with_vcs())
675 # check if another run is pending
678 with utils.RunLock():
679 LOG.info('Status: idle')
681 LOG.info('Status: busy (run pending)')
683 # check nfvbench resources
684 if config.openrc_file and config.service_chain != ChainType.EXT:
685 cleaner = Cleaner(config)
686 count = cleaner.show_resources()
687 if count and (cleanup or force_cleanup):
688 cleaner.clean(not force_cleanup)
693 run_summary_required = False
696 # load default config file
697 config, default_cfg = load_default_config()
698 # possibly override the default user_id & group_id values
699 if 'USER_ID' in os.environ:
700 config.user_id = int(os.environ['USER_ID'])
701 if 'GROUP_ID' in os.environ:
702 config.group_id = int(os.environ['GROUP_ID'])
704 # create factory for platform specific classes
706 factory_module = importlib.import_module(config['factory_module'])
707 factory = getattr(factory_module, config['factory_class'])()
708 except AttributeError:
709 raise Exception("Requested factory module '{m}' or class '{c}' was not found."
710 .format(m=config['factory_module'],
711 c=config['factory_class'])) from AttributeError
712 # create config plugin for this platform
713 config_plugin = factory.get_config_plugin_class()(config)
714 config = config_plugin.get_config()
716 opts, unknown_opts = _parse_opts_from_cli()
717 log.set_level(debug=opts.debug)
720 print((pbr.version.VersionInfo('nfvbench').version_string_with_vcs()))
724 with open(opts.summary, encoding="utf-8") as json_data:
725 result = json.load(json_data)
727 result['config']['user_label'] = opts.user_label
728 print((NFVBenchSummarizer(result, fluent_logger)))
731 # show default config in text/yaml format
732 if opts.show_default_config:
733 print((default_cfg.decode("utf-8")))
736 # dump the contents of the trex log file
737 if opts.show_trex_log:
739 with open('/tmp/trex.log', encoding="utf-8") as trex_log_file:
740 print(trex_log_file.read(), end="")
741 except FileNotFoundError:
742 print("No TRex log file found!")
745 # mask info logging in case of further config dump
746 if opts.show_config or opts.show_pre_config:
747 LOG.setLevel(log.logging.WARNING)
751 # do not check extra_specs in flavor as it can contain any key/value pairs
752 # the same principle applies also to the optional user_info open property
753 whitelist_keys = ['extra_specs', 'user_info']
754 # override default config options with start config at path parsed from CLI
755 # check if it is an inline yaml/json config or a file name
756 if os.path.isfile(opts.config):
757 LOG.info('Loading configuration file: %s', opts.config)
758 config = config_load(opts.config, config, whitelist_keys)
759 config.name = os.path.basename(opts.config)
761 LOG.info('Loading configuration string: %s', opts.config)
762 config = config_loads(opts.config, config, whitelist_keys)
764 # show current config in json format (before CLI overriding)
765 if opts.show_pre_config:
766 print((json.dumps(config, sort_keys=True, indent=4)))
769 # setup the fluent logger as soon as possible right after the config plugin is called,
770 # if there is any logging or result tag is set then initialize the fluent logger
771 for fluentd in config.fluentd:
772 if fluentd.logging_tag or fluentd.result_tag:
773 fluent_logger = FluentLogHandler(config.fluentd)
774 LOG.addHandler(fluent_logger)
777 # traffic profile override options
778 override_custom_traffic(config, opts.frame_sizes, opts.unidir)
780 # Copy over some of the cli options that are used in config.
781 # This explicit copy is sometimes necessary
782 # because some early evaluation depends on them
783 # and cannot wait for _update_config() coming further.
784 # It is good practice then to set them to None (<=> done)
785 # and even required if a specific conversion is performed here
786 # that would be corrupted by a default update (simple copy).
787 # On the other hand, some excessive assignments have been removed
788 # from here, since the _update_config() procedure does them well.
790 config.generator_profile = opts.generator_profile
791 if opts.sriov is not None:
794 if opts.log_file is not None:
795 config.log_file = opts.log_file
797 if opts.user_id is not None:
798 config.user_id = opts.user_id
800 if opts.group_id is not None:
801 config.group_id = opts.group_id
803 if opts.service_chain is not None:
804 config.service_chain = opts.service_chain
805 opts.service_chain = None
806 if opts.hypervisor is not None:
807 # can be any of 'comp1', 'nova:', 'nova:comp1'
808 config.compute_nodes = opts.hypervisor
809 opts.hypervisor = None
810 if opts.debug_mask is not None:
811 config.debug_mask = opts.debug_mask
812 opts.debug_mask = None
814 # convert 'user_info' opt from json string to dictionnary
815 # and merge the result with the current config dictionnary
816 if opts.user_info is not None:
817 for user_info_json in opts.user_info:
818 user_info_dict = json.loads(user_info_json)
820 config.user_info = config.user_info + user_info_dict
822 config.user_info = user_info_dict
823 opts.user_info = None
825 # port to port loopback (direct or through switch)
826 # we accept the following syntaxes for the CLI argument
827 # 'false' : mode not enabled
828 # 'true' : mode enabled with currently defined vlan IDs
829 # 'no-tag' : mode enabled with no vlan tagging
830 # <vlan IDs>: mode enabled using the given (pair of) vlan ID lists
831 # - If present, a '_' char will separate left an right ports lists
832 # e.g. 'a_x' => vlans: [[a],[x]]
833 # 'a,b,c_x,y,z' => [[a,b,c],[x,y,z]]
834 # - Otherwise the given vlan ID list applies to both sides
835 # e.g. 'a' => vlans: [[a],[a]]
836 # 'a,b' => [[a,b],[a,b]]
837 # - Vlan lists size needs to be at least the actual SCC value
838 # - Unless overriden in CLI opts, config.service_chain_count
839 # is adjusted to the size of the VLAN ID lists given here.
841 if opts.l2_loopback is not None:
842 arg_pair = opts.l2_loopback.lower().split('_')
843 if arg_pair[0] == 'false':
844 config.l2_loopback = False
846 config.l2_loopback = True
847 if config.service_chain != ChainType.EXT:
848 LOG.info('Changing service chain type to EXT')
849 config.service_chain = ChainType.EXT
850 if not config.no_arp:
851 LOG.info('Disabling ARP')
853 if arg_pair[0] == 'true':
856 # here explicit (not)tagging is not CLI overridable
857 opts.vlan_tagging = None
858 if arg_pair[0] == 'no-tag':
859 config.vlan_tagging = False
861 config.vlan_tagging = True
862 if len(arg_pair) == 1 or not arg_pair[1]:
863 arg_pair = [arg_pair[0], arg_pair[0]]
866 def append_vlan(port, vlan_id):
867 # a vlan tag value must be in [0..4095]
868 if vlan_id not in range(0, 4096):
870 vlans[port].append(vlan_id)
873 vlan_ids = arg_pair[port].split(',')
874 for vlan_id in vlan_ids:
875 append_vlan(port, int(vlan_id))
876 if len(vlans[0]) != len(vlans[1]):
879 # at least one invalid tag => no tagging
880 config.vlan_tagging = False
881 if config.vlan_tagging:
883 # force service chain count if not CLI overriden
884 if opts.service_chain_count is None:
885 config.service_chain_count = len(vlans[0])
886 opts.l2_loopback = None
888 if config.i40e_mixed is None:
889 config.i40e_mixed = 'ignore'
890 if config.use_sriov_middle_net is None:
891 config.use_sriov_middle_net = False
892 if opts.use_sriov_middle_net is not None:
893 config.use_sriov_middle_net = opts.use_sriov_middle_net
894 opts.use_sriov_middle_net = None
895 if (config.use_sriov_middle_net and (
896 (not config.sriov) or (config.service_chain != ChainType.PVVP))):
897 raise Exception("--use-sriov-middle-net is only valid for PVVP with SRIOV")
899 if config.sriov and config.service_chain != ChainType.EXT:
900 # if sriov is requested (does not apply to ext chains)
901 # make sure the physnet names are specified
902 check_physnet("left", config.internal_networks.left)
903 check_physnet("right", config.internal_networks.right)
904 if config.service_chain == ChainType.PVVP and config.use_sriov_middle_net:
905 check_physnet("middle", config.internal_networks.middle)
907 # update the config in the config plugin as it might have changed
908 # in a copy of the dict (config plugin still holds the original dict)
909 config_plugin.set_config(config)
911 if opts.status or opts.cleanup or opts.force_cleanup:
912 status_cleanup(config, opts.cleanup, opts.force_cleanup)
914 # add file log if requested
916 log.add_file_logger(config.log_file)
917 # possibly change file ownership
919 gid = config.group_id
923 os.chown(config.log_file, uid, gid)
925 openstack_spec = config_plugin.get_openstack_spec() if config.openrc_file \
928 nfvbench_instance = NFVBench(config, openstack_spec, config_plugin, factory)
931 server = WebServer(nfvbench_instance, fluent_logger)
933 port = int(opts.port)
935 server.run(host=opts.host)
937 server.run(host=opts.host, port=port)
938 # server.run() should never return
940 dry_run = opts.show_config
941 with utils.RunLock():
942 run_summary_required = True
944 err_msg = 'Unknown options: ' + ' '.join(unknown_opts)
946 raise Exception(err_msg)
948 # remove unfilled values
949 opts = {k: v for k, v in list(vars(opts).items()) if v is not None}
951 params = ' '.join(str(e) for e in sys.argv[1:])
952 result = nfvbench_instance.run(opts, params, dry_run=dry_run)
953 if 'error_message' in result:
954 raise Exception(result['error_message'])
956 if 'result' in result and result['status']:
957 nfvbench_instance.save(result['result'])
958 nfvbench_instance.prepare_summary(result['result'])
959 except Exception as exc:
960 run_summary_required = True
962 'status': NFVBench.STATUS_ERROR,
963 'error_message': traceback.format_exc()
968 # only send a summary record if there was an actual nfvbench run or
969 # if an error/exception was logged.
970 fluent_logger.send_run_summary(run_summary_required)
973 if __name__ == '__main__':