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, None, False) \
64 if config.openrc_file 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 self.config.service_chain != ChainType.EXT:
100 raise Exception("openrc_file in the configuration is required for PVP/PVVP chains")
102 self.specs.set_run_spec(self.config_plugin.get_run_spec(self.config,
103 self.specs.openstack))
104 self.chain_runner = ChainRunner(self.config,
110 # make sure that the min frame size is 64
112 for frame_size in self.config.frame_sizes:
114 if int(frame_size) < min_packet_size:
115 frame_size = str(min_packet_size)
116 LOG.info("Adjusting frame size %s bytes to minimum size %s bytes",
117 frame_size, min_packet_size)
118 if frame_size not in new_frame_sizes:
119 new_frame_sizes.append(frame_size)
121 new_frame_sizes.append(frame_size.upper())
122 self.config.frame_sizes = new_frame_sizes
124 "date": datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
125 "nfvbench_version": __version__,
126 "config": self.config_plugin.prepare_results_config(copy.deepcopy(self.config)),
129 "service_chain": self.chain_runner.run(),
130 "versions": self.chain_runner.get_version(),
134 if self.specs.openstack:
135 result['openstack_spec'] = {"vswitch": self.specs.openstack.vswitch,
136 "encaps": self.specs.openstack.encaps}
137 result['benchmarks']['network']['versions'].update(self.config_plugin.get_version())
139 status = NFVBench.STATUS_ERROR
140 message = traceback.format_exc()
141 except KeyboardInterrupt:
142 status = NFVBench.STATUS_ERROR
143 message = traceback.format_exc()
145 if self.chain_runner:
146 self.chain_runner.close()
148 if status == NFVBench.STATUS_OK:
149 # result2 = utils.dict_to_json_dict(result)
156 'error_message': message
159 def prepare_summary(self, result):
160 """Prepare summary of the result to print and send it to logger (eg: fluentd)."""
162 summary = NFVBenchSummarizer(result, fluent_logger)
163 LOG.info(str(summary))
165 def save(self, result):
166 """Save results in json format file."""
167 utils.save_json_result(result,
168 self.config.json_file,
169 self.config.std_json_path,
170 self.config.service_chain,
171 self.config.service_chain_count,
172 self.config.flow_count,
173 self.config.frame_sizes,
175 self.config.group_id)
177 def _update_config(self, opts):
178 """Recalculate the running config based on the base config and opts.
180 Sanity check on the config is done here as well.
182 self.config = AttrDict(dict(self.base_config))
183 # Update log file handler if needed after a config update (REST mode)
184 if 'log_file' in opts:
186 (path, _filename) = os.path.split(opts['log_file'])
187 if not os.path.exists(path):
189 'Path %s does not exist. Please verify root path is shared with host. Path '
190 'will be created.', path)
192 LOG.info('%s is created.', path)
193 if not any(isinstance(h, FileHandler) for h in log.getLogger().handlers):
194 log.add_file_logger(opts['log_file'])
196 for h in log.getLogger().handlers:
197 if isinstance(h, FileHandler) and h.baseFilename != opts['log_file']:
198 # clean log file handler
199 log.getLogger().removeHandler(h)
200 log.add_file_logger(opts['log_file'])
202 self.config.update(opts)
205 config.service_chain = config.service_chain.upper()
206 config.service_chain_count = int(config.service_chain_count)
207 if config.l2_loopback:
208 # force the number of chains to be 1 in case of untagged l2 loopback
209 # (on the other hand, multiple L2 vlan tagged service chains are allowed)
210 if not config.vlan_tagging:
211 config.service_chain_count = 1
212 config.service_chain = ChainType.EXT
214 LOG.info('Running L2 loopback: using EXT chain/no ARP')
216 # allow oversized vlan lists, just clip them
218 vlans = [list(v) for v in config.vlans]
220 del v[config.service_chain_count:]
225 # traffic profile override options
226 if 'frame_sizes' in opts:
229 unidir = opts['unidir']
230 override_custom_traffic(config, opts['frame_sizes'], unidir)
231 LOG.info("Frame size has been set to %s for current configuration", opts['frame_sizes'])
233 config.flow_count = utils.parse_flow_count(config.flow_count)
234 required_flow_count = config.service_chain_count * 2
235 if config.flow_count < required_flow_count:
236 LOG.info("Flow count %d has been set to minimum value of '%d' "
237 "for current configuration", config.flow_count,
239 config.flow_count = required_flow_count
241 if config.flow_count % 2:
242 config.flow_count += 1
244 # Possibly adjust the cache size
245 if config.cache_size < 0:
246 config.cache_size = config.flow_count
248 # The size must be capped to 10000 (where does this limit come from?)
249 config.cache_size = min(config.cache_size, 10000)
251 config.duration_sec = float(config.duration_sec)
252 config.interval_sec = float(config.interval_sec)
253 config.pause_sec = float(config.pause_sec)
255 if config.traffic is None or not config.traffic:
256 raise Exception("Missing traffic property in configuration")
258 if config.openrc_file:
259 config.openrc_file = os.path.expanduser(config.openrc_file)
260 if config.flavor.vcpus < 2:
261 raise Exception("Flavor vcpus must be >= 2")
263 config.ndr_run = (not config.no_traffic and
264 'ndr' in config.rate.strip().lower().split('_'))
265 config.pdr_run = (not config.no_traffic and
266 'pdr' in config.rate.strip().lower().split('_'))
267 config.single_run = (not config.no_traffic and
268 not (config.ndr_run or config.pdr_run))
270 config.json_file = config.json if config.json else None
272 (path, _filename) = os.path.split(config.json)
273 if not os.path.exists(path):
274 raise Exception('Please provide existing path for storing results in JSON file. '
275 'Path used: {path}'.format(path=path))
277 config.std_json_path = config.std_json if config.std_json else None
278 if config.std_json_path:
279 if not os.path.exists(config.std_json):
280 raise Exception('Please provide existing path for storing results in JSON file. '
281 'Path used: {path}'.format(path=config.std_json_path))
283 # Check that multiqueue is between 1 and 8 (8 is the max allowed by libvirt/qemu)
284 if config.vif_multiqueue_size < 1 or config.vif_multiqueue_size > 8:
285 raise Exception('vif_multiqueue_size (%d) must be in [1..8]' %
286 config.vif_multiqueue_size)
288 # VxLAN and MPLS sanity checks
289 if config.vxlan or config.mpls:
290 if config.vlan_tagging:
291 config.vlan_tagging = False
292 config.no_latency_streams = True
293 config.no_latency_stats = True
294 config.no_flow_stats = True
295 LOG.info('VxLAN or MPLS: vlan_tagging forced to False '
296 '(inner VLAN tagging must be disabled)')
298 self.config_plugin.validate_config(config, self.specs.openstack)
302 """Argument type to be used in parser.add_argument()
303 When a boolean like value is expected to be given
305 return (str(x).lower() != 'false') \
306 and (str(x).lower() != 'no') \
307 and (str(x).lower() != '0')
311 """Argument type to be used in parser.add_argument()
312 When an integer type value is expected to be given
313 (returns 0 if argument is invalid, hexa accepted)
318 def _parse_opts_from_cli():
319 parser = argparse.ArgumentParser()
321 parser.add_argument('--status', dest='status',
324 help='Provide NFVbench status')
326 parser.add_argument('-c', '--config', dest='config',
328 help='Override default values with a config file or '
329 'a yaml/json config string',
330 metavar='<file_name_or_yaml>')
332 parser.add_argument('--server', dest='server',
335 help='Run nfvbench in server mode')
337 parser.add_argument('--host', dest='host',
340 help='Host IP address on which server will be listening (default 0.0.0.0)')
342 parser.add_argument('-p', '--port', dest='port',
345 help='Port on which server will be listening (default 7555)')
347 parser.add_argument('-sc', '--service-chain', dest='service_chain',
348 choices=ChainType.names,
350 help='Service chain to run')
352 parser.add_argument('-scc', '--service-chain-count', dest='service_chain_count',
354 help='Set number of service chains to run',
355 metavar='<service_chain_count>')
357 parser.add_argument('-fc', '--flow-count', dest='flow_count',
359 help='Set number of total flows for all chains and all directions',
360 metavar='<flow_count>')
362 parser.add_argument('--rate', dest='rate',
364 help='Specify rate in pps, bps or %% as total for all directions',
367 parser.add_argument('--duration', dest='duration_sec',
369 help='Set duration to run traffic generator (in seconds)',
370 metavar='<duration_sec>')
372 parser.add_argument('--interval', dest='interval_sec',
374 help='Set interval to record traffic generator stats (in seconds)',
375 metavar='<interval_sec>')
377 parser.add_argument('--inter-node', dest='inter_node',
382 parser.add_argument('--sriov', dest='sriov',
385 help='Use SRIOV (no vswitch - requires SRIOV support in compute nodes)')
387 parser.add_argument('--use-sriov-middle-net', dest='use_sriov_middle_net',
390 help='Use SRIOV to handle the middle network traffic '
391 '(PVVP with SRIOV only)')
393 parser.add_argument('-d', '--debug', dest='debug',
396 help='print debug messages (verbose)')
398 parser.add_argument('-g', '--traffic-gen', dest='generator_profile',
400 help='Traffic generator profile to use')
402 parser.add_argument('-l3', '--l3-router', dest='l3_router',
405 help='Use L3 neutron routers to handle traffic')
407 parser.add_argument('-garp', '--gratuitous-arp', dest='periodic_gratuitous_arp',
410 help='Use gratuitous ARP to maintain session between TG '
411 'and L3 routers to handle traffic')
413 parser.add_argument('-0', '--no-traffic', dest='no_traffic',
416 help='Check config and connectivity only - do not generate traffic')
418 parser.add_argument('--no-arp', dest='no_arp',
421 help='Do not use ARP to find MAC addresses, '
422 'instead use values in config file')
424 parser.add_argument('--loop-vm-arp', dest='loop_vm_arp',
427 help='Use ARP to find MAC addresses '
428 'instead of using values from TRex ports (VPP forwarder only)')
430 parser.add_argument('--no-vswitch-access', dest='no_vswitch_access',
433 help='Skip vswitch configuration and retrieving of stats')
435 parser.add_argument('--vxlan', dest='vxlan',
438 help='Enable VxLan encapsulation')
440 parser.add_argument('--mpls', dest='mpls',
443 help='Enable MPLS encapsulation')
445 parser.add_argument('--no-cleanup', dest='no_cleanup',
448 help='no cleanup after run')
450 parser.add_argument('--cleanup', dest='cleanup',
453 help='Cleanup NFVbench resources (prompt to confirm)')
455 parser.add_argument('--force-cleanup', dest='force_cleanup',
458 help='Cleanup NFVbench resources (do not prompt)')
460 parser.add_argument('--restart', dest='restart',
463 help='Restart TRex server')
465 parser.add_argument('--json', dest='json',
467 help='store results in json format file',
468 metavar='<path>/<filename>')
470 parser.add_argument('--std-json', dest='std_json',
472 help='store results in json format file with nfvbench standard filename: '
473 '<service-chain-type>-<service-chain-count>-<flow-count>'
474 '-<packet-sizes>.json',
477 parser.add_argument('--show-default-config', dest='show_default_config',
480 help='print the default config in yaml format (unedited)')
482 parser.add_argument('--show-pre-config', dest='show_pre_config',
485 help='print the config in json format (cfg file applied)')
487 parser.add_argument('--show-config', dest='show_config',
490 help='print the running config in json format (final)')
492 parser.add_argument('-ss', '--show-summary', dest='summary',
494 help='Show summary from nfvbench json file',
497 parser.add_argument('-v', '--version', dest='version',
502 parser.add_argument('-fs', '--frame-size', dest='frame_sizes',
504 help='Override traffic profile frame sizes',
505 metavar='<frame_size_bytes or IMIX>')
507 parser.add_argument('--unidir', dest='unidir',
510 help='Override traffic profile direction (requires -fs)')
512 parser.add_argument('--log-file', '--logfile', dest='log_file',
514 help='Filename for saving logs',
515 metavar='<log_file>')
517 parser.add_argument('--user-label', '--userlabel', dest='user_label',
519 help='Custom label for performance records')
521 parser.add_argument('--hypervisor', dest='hypervisor',
523 metavar='<hypervisor name>',
524 help='Where chains must run ("compute", "az:", "az:compute")')
526 parser.add_argument('--l2-loopback', '--l2loopback', dest='l2_loopback',
528 metavar='<vlan(s)|no-tag|true|false>',
529 help='Port to port or port to switch to port L2 loopback '
530 'tagged with given VLAN id(s) or not (given \'no-tag\') '
531 '\'true\': use current vlans; \'false\': disable this mode.')
533 parser.add_argument('--i40e-mixed', dest='i40e_mixed',
536 metavar='<ignore,check,unbind>',
537 help='TRex behavior when dealing with a i40e network card driver'
538 ' [ https://trex-tgn.cisco.com/youtrack/issue/trex-528 ]')
540 parser.add_argument('--user-info', dest='user_info',
543 help='Custom data to be included as is '
544 'in the json report config branch - '
545 ' example, pay attention! no space: '
546 '--user-info=\'{"status":"explore","description":'
547 '{"target":"lab","ok":true,"version":2020}}\' - '
548 'this option may be repeated; given data will be merged.')
550 parser.add_argument('--vlan-tagging', dest='vlan_tagging',
555 help='Override the NFVbench \'vlan_tagging\' parameter')
557 parser.add_argument('--intf-speed', dest='intf_speed',
561 help='Override the NFVbench \'intf_speed\' '
562 'parameter (e.g. 10Gbps, auto, 16.72Gbps)')
564 parser.add_argument('--cores', dest='cores',
569 help='Override the T-Rex \'cores\' parameter')
571 parser.add_argument('--cache-size', dest='cache_size',
576 help='Specify the FE cache size (default: 0, flow-count if < 0)')
578 parser.add_argument('--service-mode', dest='service_mode',
581 help='Enable T-Rex service mode (for debugging purpose)')
583 parser.add_argument('--no-e2e-check', dest='no_e2e_check',
586 help='Skip "end to end" connectivity check (on test purpose)')
588 parser.add_argument('--no-flow-stats', dest='no_flow_stats',
591 help='Disable additional flow stats (on high load traffic)')
593 parser.add_argument('--no-latency-stats', dest='no_latency_stats',
596 help='Disable flow stats for latency traffic')
598 parser.add_argument('--no-latency-streams', dest='no_latency_streams',
601 help='Disable latency measurements (no streams)')
603 parser.add_argument('--user-id', dest='user_id',
608 help='Change json/log files ownership with this user (int)')
610 parser.add_argument('--group-id', dest='group_id',
615 help='Change json/log files ownership with this group (int)')
617 parser.add_argument('--show-trex-log', dest='show_trex_log',
620 help='Show the current TRex local server log file contents'
621 ' => diagnostic/help in case of configuration problems')
623 parser.add_argument('--debug-mask', dest='debug_mask',
628 help='General purpose register (debugging flags), '
629 'the hexadecimal notation (0x...) is accepted.'
630 'Designed for development needs (default: 0).')
632 opts, unknown_opts = parser.parse_known_args()
633 return opts, unknown_opts
636 def load_default_config():
637 default_cfg = resource_string(__name__, "cfg.default.yaml")
638 config = config_loads(default_cfg)
639 config.name = '(built-in default config)'
640 return config, default_cfg
643 def override_custom_traffic(config, frame_sizes, unidir):
644 """Override the traffic profiles with a custom one."""
645 if frame_sizes is not None:
646 traffic_profile_name = "custom_traffic_profile"
647 config.traffic_profile = [
649 "l2frame_size": frame_sizes,
650 "name": traffic_profile_name
654 traffic_profile_name = config.traffic["profile"]
656 bidirectional = config.traffic['bidirectional'] if unidir is None else not unidir
658 "bidirectional": bidirectional,
659 "profile": traffic_profile_name
663 def check_physnet(name, netattrs):
664 if not netattrs.physical_network:
665 raise Exception("SRIOV requires physical_network to be specified for the {n} network"
667 if not netattrs.segmentation_id:
668 raise Exception("SRIOV requires segmentation_id to be specified for the {n} network"
671 def status_cleanup(config, cleanup, force_cleanup):
672 LOG.info('Version: %s', pbr.version.VersionInfo('nfvbench').version_string_with_vcs())
673 # check if another run is pending
676 with utils.RunLock():
677 LOG.info('Status: idle')
679 LOG.info('Status: busy (run pending)')
681 # check nfvbench resources
682 if config.openrc_file and config.service_chain != ChainType.EXT:
683 cleaner = Cleaner(config)
684 count = cleaner.show_resources()
685 if count and (cleanup or force_cleanup):
686 cleaner.clean(not force_cleanup)
691 run_summary_required = False
694 # load default config file
695 config, default_cfg = load_default_config()
696 # possibly override the default user_id & group_id values
697 if 'USER_ID' in os.environ:
698 config.user_id = int(os.environ['USER_ID'])
699 if 'GROUP_ID' in os.environ:
700 config.group_id = int(os.environ['GROUP_ID'])
702 # create factory for platform specific classes
704 factory_module = importlib.import_module(config['factory_module'])
705 factory = getattr(factory_module, config['factory_class'])()
706 except AttributeError:
707 raise Exception("Requested factory module '{m}' or class '{c}' was not found."
708 .format(m=config['factory_module'],
709 c=config['factory_class'])) from AttributeError
710 # create config plugin for this platform
711 config_plugin = factory.get_config_plugin_class()(config)
712 config = config_plugin.get_config()
714 opts, unknown_opts = _parse_opts_from_cli()
715 log.set_level(debug=opts.debug)
718 print((pbr.version.VersionInfo('nfvbench').version_string_with_vcs()))
722 with open(opts.summary) as json_data:
723 result = json.load(json_data)
725 result['config']['user_label'] = opts.user_label
726 print((NFVBenchSummarizer(result, fluent_logger)))
729 # show default config in text/yaml format
730 if opts.show_default_config:
731 print((default_cfg.decode("utf-8")))
734 # dump the contents of the trex log file
735 if opts.show_trex_log:
737 print(open('/tmp/trex.log').read(), end="")
738 except FileNotFoundError:
739 print("No TRex log file found!")
742 # mask info logging in case of further config dump
743 if opts.show_config or opts.show_pre_config:
744 LOG.setLevel(log.logging.WARNING)
748 # do not check extra_specs in flavor as it can contain any key/value pairs
749 # the same principle applies also to the optional user_info open property
750 whitelist_keys = ['extra_specs', 'user_info']
751 # override default config options with start config at path parsed from CLI
752 # check if it is an inline yaml/json config or a file name
753 if os.path.isfile(opts.config):
754 LOG.info('Loading configuration file: %s', opts.config)
755 config = config_load(opts.config, config, whitelist_keys)
756 config.name = os.path.basename(opts.config)
758 LOG.info('Loading configuration string: %s', opts.config)
759 config = config_loads(opts.config, config, whitelist_keys)
761 # show current config in json format (before CLI overriding)
762 if opts.show_pre_config:
763 print((json.dumps(config, sort_keys=True, indent=4)))
766 # setup the fluent logger as soon as possible right after the config plugin is called,
767 # if there is any logging or result tag is set then initialize the fluent logger
768 for fluentd in config.fluentd:
769 if fluentd.logging_tag or fluentd.result_tag:
770 fluent_logger = FluentLogHandler(config.fluentd)
771 LOG.addHandler(fluent_logger)
774 # traffic profile override options
775 override_custom_traffic(config, opts.frame_sizes, opts.unidir)
777 # Copy over some of the cli options that are used in config.
778 # This explicit copy is sometimes necessary
779 # because some early evaluation depends on them
780 # and cannot wait for _update_config() coming further.
781 # It is good practice then to set them to None (<=> done)
782 # and even required if a specific conversion is performed here
783 # that would be corrupted by a default update (simple copy).
784 # On the other hand, some excessive assignments have been removed
785 # from here, since the _update_config() procedure does them well.
787 config.generator_profile = opts.generator_profile
788 if opts.sriov is not None:
791 if opts.log_file is not None:
792 config.log_file = opts.log_file
794 if opts.user_id is not None:
795 config.user_id = opts.user_id
797 if opts.group_id is not None:
798 config.group_id = opts.group_id
800 if opts.service_chain is not None:
801 config.service_chain = opts.service_chain
802 opts.service_chain = None
803 if opts.hypervisor is not None:
804 # can be any of 'comp1', 'nova:', 'nova:comp1'
805 config.compute_nodes = opts.hypervisor
806 opts.hypervisor = None
807 if opts.debug_mask is not None:
808 config.debug_mask = opts.debug_mask
809 opts.debug_mask = None
811 # convert 'user_info' opt from json string to dictionnary
812 # and merge the result with the current config dictionnary
813 if opts.user_info is not None:
814 for user_info_json in opts.user_info:
815 user_info_dict = json.loads(user_info_json)
817 config.user_info = config.user_info + user_info_dict
819 config.user_info = user_info_dict
820 opts.user_info = None
822 # port to port loopback (direct or through switch)
823 # we accept the following syntaxes for the CLI argument
824 # 'false' : mode not enabled
825 # 'true' : mode enabled with currently defined vlan IDs
826 # 'no-tag' : mode enabled with no vlan tagging
827 # <vlan IDs>: mode enabled using the given (pair of) vlan ID lists
828 # - If present, a '_' char will separate left an right ports lists
829 # e.g. 'a_x' => vlans: [[a],[x]]
830 # 'a,b,c_x,y,z' => [[a,b,c],[x,y,z]]
831 # - Otherwise the given vlan ID list applies to both sides
832 # e.g. 'a' => vlans: [[a],[a]]
833 # 'a,b' => [[a,b],[a,b]]
834 # - Vlan lists size needs to be at least the actual SCC value
835 # - Unless overriden in CLI opts, config.service_chain_count
836 # is adjusted to the size of the VLAN ID lists given here.
838 if opts.l2_loopback is not None:
839 arg_pair = opts.l2_loopback.lower().split('_')
840 if arg_pair[0] == 'false':
841 config.l2_loopback = False
843 config.l2_loopback = True
844 if config.service_chain != ChainType.EXT:
845 LOG.info('Changing service chain type to EXT')
846 config.service_chain = ChainType.EXT
847 if not config.no_arp:
848 LOG.info('Disabling ARP')
850 if arg_pair[0] == 'true':
853 # here explicit (not)tagging is not CLI overridable
854 opts.vlan_tagging = None
855 if arg_pair[0] == 'no-tag':
856 config.vlan_tagging = False
858 config.vlan_tagging = True
859 if len(arg_pair) == 1 or not arg_pair[1]:
860 arg_pair = [arg_pair[0], arg_pair[0]]
863 def append_vlan(port, vlan_id):
864 # a vlan tag value must be in [0..4095]
865 if vlan_id not in range(0, 4096):
867 vlans[port].append(vlan_id)
870 vlan_ids = arg_pair[port].split(',')
871 for vlan_id in vlan_ids:
872 append_vlan(port, int(vlan_id))
873 if len(vlans[0]) != len(vlans[1]):
876 # at least one invalid tag => no tagging
877 config.vlan_tagging = False
878 if config.vlan_tagging:
880 # force service chain count if not CLI overriden
881 if opts.service_chain_count is None:
882 config.service_chain_count = len(vlans[0])
883 opts.l2_loopback = None
885 if config.i40e_mixed is None:
886 config.i40e_mixed = 'ignore'
887 if config.use_sriov_middle_net is None:
888 config.use_sriov_middle_net = False
889 if opts.use_sriov_middle_net is not None:
890 config.use_sriov_middle_net = opts.use_sriov_middle_net
891 opts.use_sriov_middle_net = None
892 if (config.use_sriov_middle_net and (
893 (not config.sriov) or (config.service_chain != ChainType.PVVP))):
894 raise Exception("--use-sriov-middle-net is only valid for PVVP with SRIOV")
896 if config.sriov and config.service_chain != ChainType.EXT:
897 # if sriov is requested (does not apply to ext chains)
898 # make sure the physnet names are specified
899 check_physnet("left", config.internal_networks.left)
900 check_physnet("right", config.internal_networks.right)
901 if config.service_chain == ChainType.PVVP and config.use_sriov_middle_net:
902 check_physnet("middle", config.internal_networks.middle)
904 # update the config in the config plugin as it might have changed
905 # in a copy of the dict (config plugin still holds the original dict)
906 config_plugin.set_config(config)
908 if opts.status or opts.cleanup or opts.force_cleanup:
909 status_cleanup(config, opts.cleanup, opts.force_cleanup)
911 # add file log if requested
913 log.add_file_logger(config.log_file)
914 # possibly change file ownership
916 gid = config.group_id
920 os.chown(config.log_file, uid, gid)
922 openstack_spec = config_plugin.get_openstack_spec() if config.openrc_file \
925 nfvbench_instance = NFVBench(config, openstack_spec, config_plugin, factory)
928 server = WebServer(nfvbench_instance, fluent_logger)
930 port = int(opts.port)
932 server.run(host=opts.host)
934 server.run(host=opts.host, port=port)
935 # server.run() should never return
937 dry_run = opts.show_config
938 with utils.RunLock():
939 run_summary_required = True
941 err_msg = 'Unknown options: ' + ' '.join(unknown_opts)
943 raise Exception(err_msg)
945 # remove unfilled values
946 opts = {k: v for k, v in list(vars(opts).items()) if v is not None}
948 params = ' '.join(str(e) for e in sys.argv[1:])
949 result = nfvbench_instance.run(opts, params, dry_run=dry_run)
950 if 'error_message' in result:
951 raise Exception(result['error_message'])
953 if 'result' in result and result['status']:
954 nfvbench_instance.save(result['result'])
955 nfvbench_instance.prepare_summary(result['result'])
956 except Exception as exc:
957 run_summary_required = True
959 'status': NFVBench.STATUS_ERROR,
960 'error_message': traceback.format_exc()
965 # only send a summary record if there was an actual nfvbench run or
966 # if an error/exception was logged.
967 fluent_logger.send_run_summary(run_summary_required)
970 if __name__ == '__main__':