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 if config.cache_size > 10000:
250 config.cache_size = 10000
252 config.duration_sec = float(config.duration_sec)
253 config.interval_sec = float(config.interval_sec)
254 config.pause_sec = float(config.pause_sec)
256 if config.traffic is None or not config.traffic:
257 raise Exception("Missing traffic property in configuration")
259 if config.openrc_file:
260 config.openrc_file = os.path.expanduser(config.openrc_file)
261 if config.flavor.vcpus < 2:
262 raise Exception("Flavor vcpus must be >= 2")
264 config.ndr_run = (not config.no_traffic and
265 'ndr' in config.rate.strip().lower().split('_'))
266 config.pdr_run = (not config.no_traffic and
267 'pdr' in config.rate.strip().lower().split('_'))
268 config.single_run = (not config.no_traffic and
269 not (config.ndr_run or config.pdr_run))
271 config.json_file = config.json if config.json else None
273 (path, _filename) = os.path.split(config.json)
274 if not os.path.exists(path):
275 raise Exception('Please provide existing path for storing results in JSON file. '
276 'Path used: {path}'.format(path=path))
278 config.std_json_path = config.std_json if config.std_json else None
279 if config.std_json_path:
280 if not os.path.exists(config.std_json):
281 raise Exception('Please provide existing path for storing results in JSON file. '
282 'Path used: {path}'.format(path=config.std_json_path))
284 # Check that multiqueue is between 1 and 8 (8 is the max allowed by libvirt/qemu)
285 if config.vif_multiqueue_size < 1 or config.vif_multiqueue_size > 8:
286 raise Exception('vif_multiqueue_size (%d) must be in [1..8]' %
287 config.vif_multiqueue_size)
289 # VxLAN and MPLS sanity checks
290 if config.vxlan or config.mpls:
291 if config.vlan_tagging:
292 config.vlan_tagging = False
293 config.no_latency_streams = True
294 config.no_latency_stats = True
295 config.no_flow_stats = True
296 LOG.info('VxLAN or MPLS: vlan_tagging forced to False '
297 '(inner VLAN tagging must be disabled)')
299 self.config_plugin.validate_config(config, self.specs.openstack)
303 """Argument type to be used in parser.add_argument()
304 When a boolean like value is expected to be given
306 return (str(x).lower() != 'false') \
307 and (str(x).lower() != 'no') \
308 and (str(x).lower() != '0')
312 """Argument type to be used in parser.add_argument()
313 When an integer type value is expected to be given
314 (returns 0 if argument is invalid, hexa accepted)
319 def _parse_opts_from_cli():
320 parser = argparse.ArgumentParser()
322 parser.add_argument('--status', dest='status',
325 help='Provide NFVbench status')
327 parser.add_argument('-c', '--config', dest='config',
329 help='Override default values with a config file or '
330 'a yaml/json config string',
331 metavar='<file_name_or_yaml>')
333 parser.add_argument('--server', dest='server',
336 help='Run nfvbench in server mode')
338 parser.add_argument('--host', dest='host',
341 help='Host IP address on which server will be listening (default 0.0.0.0)')
343 parser.add_argument('-p', '--port', dest='port',
346 help='Port on which server will be listening (default 7555)')
348 parser.add_argument('-sc', '--service-chain', dest='service_chain',
349 choices=ChainType.names,
351 help='Service chain to run')
353 parser.add_argument('-scc', '--service-chain-count', dest='service_chain_count',
355 help='Set number of service chains to run',
356 metavar='<service_chain_count>')
358 parser.add_argument('-fc', '--flow-count', dest='flow_count',
360 help='Set number of total flows for all chains and all directions',
361 metavar='<flow_count>')
363 parser.add_argument('--rate', dest='rate',
365 help='Specify rate in pps, bps or %% as total for all directions',
368 parser.add_argument('--duration', dest='duration_sec',
370 help='Set duration to run traffic generator (in seconds)',
371 metavar='<duration_sec>')
373 parser.add_argument('--interval', dest='interval_sec',
375 help='Set interval to record traffic generator stats (in seconds)',
376 metavar='<interval_sec>')
378 parser.add_argument('--inter-node', dest='inter_node',
383 parser.add_argument('--sriov', dest='sriov',
386 help='Use SRIOV (no vswitch - requires SRIOV support in compute nodes)')
388 parser.add_argument('--use-sriov-middle-net', dest='use_sriov_middle_net',
391 help='Use SRIOV to handle the middle network traffic '
392 '(PVVP with SRIOV only)')
394 parser.add_argument('-d', '--debug', dest='debug',
397 help='print debug messages (verbose)')
399 parser.add_argument('-g', '--traffic-gen', dest='generator_profile',
401 help='Traffic generator profile to use')
403 parser.add_argument('-l3', '--l3-router', dest='l3_router',
406 help='Use L3 neutron routers to handle traffic')
408 parser.add_argument('-garp', '--gratuitous-arp', dest='periodic_gratuitous_arp',
411 help='Use gratuitous ARP to maintain session between TG '
412 'and L3 routers to handle traffic')
414 parser.add_argument('-0', '--no-traffic', dest='no_traffic',
417 help='Check config and connectivity only - do not generate traffic')
419 parser.add_argument('--no-arp', dest='no_arp',
422 help='Do not use ARP to find MAC addresses, '
423 'instead use values in config file')
425 parser.add_argument('--loop-vm-arp', dest='loop_vm_arp',
428 help='Use ARP to find MAC addresses '
429 'instead of using values from TRex ports (VPP forwarder only)')
431 parser.add_argument('--no-vswitch-access', dest='no_vswitch_access',
434 help='Skip vswitch configuration and retrieving of stats')
436 parser.add_argument('--vxlan', dest='vxlan',
439 help='Enable VxLan encapsulation')
441 parser.add_argument('--mpls', dest='mpls',
444 help='Enable MPLS encapsulation')
446 parser.add_argument('--no-cleanup', dest='no_cleanup',
449 help='no cleanup after run')
451 parser.add_argument('--cleanup', dest='cleanup',
454 help='Cleanup NFVbench resources (prompt to confirm)')
456 parser.add_argument('--force-cleanup', dest='force_cleanup',
459 help='Cleanup NFVbench resources (do not prompt)')
461 parser.add_argument('--restart', dest='restart',
464 help='Restart TRex server')
466 parser.add_argument('--json', dest='json',
468 help='store results in json format file',
469 metavar='<path>/<filename>')
471 parser.add_argument('--std-json', dest='std_json',
473 help='store results in json format file with nfvbench standard filename: '
474 '<service-chain-type>-<service-chain-count>-<flow-count>'
475 '-<packet-sizes>.json',
478 parser.add_argument('--show-default-config', dest='show_default_config',
481 help='print the default config in yaml format (unedited)')
483 parser.add_argument('--show-pre-config', dest='show_pre_config',
486 help='print the config in json format (cfg file applied)')
488 parser.add_argument('--show-config', dest='show_config',
491 help='print the running config in json format (final)')
493 parser.add_argument('-ss', '--show-summary', dest='summary',
495 help='Show summary from nfvbench json file',
498 parser.add_argument('-v', '--version', dest='version',
503 parser.add_argument('-fs', '--frame-size', dest='frame_sizes',
505 help='Override traffic profile frame sizes',
506 metavar='<frame_size_bytes or IMIX>')
508 parser.add_argument('--unidir', dest='unidir',
511 help='Override traffic profile direction (requires -fs)')
513 parser.add_argument('--log-file', '--logfile', dest='log_file',
515 help='Filename for saving logs',
516 metavar='<log_file>')
518 parser.add_argument('--user-label', '--userlabel', dest='user_label',
520 help='Custom label for performance records')
522 parser.add_argument('--hypervisor', dest='hypervisor',
524 metavar='<hypervisor name>',
525 help='Where chains must run ("compute", "az:", "az:compute")')
527 parser.add_argument('--l2-loopback', '--l2loopback', dest='l2_loopback',
529 metavar='<vlan(s)|no-tag|true|false>',
530 help='Port to port or port to switch to port L2 loopback '
531 'tagged with given VLAN id(s) or not (given \'no-tag\') '
532 '\'true\': use current vlans; \'false\': disable this mode.')
534 parser.add_argument('--user-info', dest='user_info',
537 help='Custom data to be included as is '
538 'in the json report config branch - '
539 ' example, pay attention! no space: '
540 '--user-info=\'{"status":"explore","description":'
541 '{"target":"lab","ok":true,"version":2020}}\' - '
542 'this option may be repeated; given data will be merged.')
544 parser.add_argument('--vlan-tagging', dest='vlan_tagging',
549 help='Override the NFVbench \'vlan_tagging\' parameter')
551 parser.add_argument('--intf-speed', dest='intf_speed',
555 help='Override the NFVbench \'intf_speed\' '
556 'parameter (e.g. 10Gbps, auto, 16.72Gbps)')
558 parser.add_argument('--cores', dest='cores',
563 help='Override the T-Rex \'cores\' parameter')
565 parser.add_argument('--cache-size', dest='cache_size',
570 help='Specify the FE cache size (default: 0, flow-count if < 0)')
572 parser.add_argument('--service-mode', dest='service_mode',
575 help='Enable T-Rex service mode (for debugging purpose)')
577 parser.add_argument('--no-e2e-check', dest='no_e2e_check',
580 help='Skip "end to end" connectivity check (on test purpose)')
582 parser.add_argument('--no-flow-stats', dest='no_flow_stats',
585 help='Disable additional flow stats (on high load traffic)')
587 parser.add_argument('--no-latency-stats', dest='no_latency_stats',
590 help='Disable flow stats for latency traffic')
592 parser.add_argument('--no-latency-streams', dest='no_latency_streams',
595 help='Disable latency measurements (no streams)')
597 parser.add_argument('--user-id', dest='user_id',
602 help='Change json/log files ownership with this user (int)')
604 parser.add_argument('--group-id', dest='group_id',
609 help='Change json/log files ownership with this group (int)')
611 parser.add_argument('--show-trex-log', dest='show_trex_log',
614 help='Show the current TRex local server log file contents'
615 ' => diagnostic/help in case of configuration problems')
617 parser.add_argument('--debug-mask', dest='debug_mask',
622 help='General purpose register (debugging flags), '
623 'the hexadecimal notation (0x...) is accepted.'
624 'Designed for development needs (default: 0).')
626 opts, unknown_opts = parser.parse_known_args()
627 return opts, unknown_opts
630 def load_default_config():
631 default_cfg = resource_string(__name__, "cfg.default.yaml")
632 config = config_loads(default_cfg)
633 config.name = '(built-in default config)'
634 return config, default_cfg
637 def override_custom_traffic(config, frame_sizes, unidir):
638 """Override the traffic profiles with a custom one."""
639 if frame_sizes is not None:
640 traffic_profile_name = "custom_traffic_profile"
641 config.traffic_profile = [
643 "l2frame_size": frame_sizes,
644 "name": traffic_profile_name
648 traffic_profile_name = config.traffic["profile"]
650 bidirectional = config.traffic['bidirectional'] if unidir is None else not unidir
652 "bidirectional": bidirectional,
653 "profile": traffic_profile_name
657 def check_physnet(name, netattrs):
658 if not netattrs.physical_network:
659 raise Exception("SRIOV requires physical_network to be specified for the {n} network"
661 if not netattrs.segmentation_id:
662 raise Exception("SRIOV requires segmentation_id to be specified for the {n} network"
665 def status_cleanup(config, cleanup, force_cleanup):
666 LOG.info('Version: %s', pbr.version.VersionInfo('nfvbench').version_string_with_vcs())
667 # check if another run is pending
670 with utils.RunLock():
671 LOG.info('Status: idle')
673 LOG.info('Status: busy (run pending)')
675 # check nfvbench resources
676 if config.openrc_file and config.service_chain != ChainType.EXT:
677 cleaner = Cleaner(config)
678 count = cleaner.show_resources()
679 if count and (cleanup or force_cleanup):
680 cleaner.clean(not force_cleanup)
685 run_summary_required = False
688 # load default config file
689 config, default_cfg = load_default_config()
690 # possibly override the default user_id & group_id values
691 if 'USER_ID' in os.environ:
692 config.user_id = int(os.environ['USER_ID'])
693 if 'GROUP_ID' in os.environ:
694 config.group_id = int(os.environ['GROUP_ID'])
696 # create factory for platform specific classes
698 factory_module = importlib.import_module(config['factory_module'])
699 factory = getattr(factory_module, config['factory_class'])()
700 except AttributeError:
701 raise Exception("Requested factory module '{m}' or class '{c}' was not found."
702 .format(m=config['factory_module'],
703 c=config['factory_class'])) from AttributeError
704 # create config plugin for this platform
705 config_plugin = factory.get_config_plugin_class()(config)
706 config = config_plugin.get_config()
708 opts, unknown_opts = _parse_opts_from_cli()
709 log.set_level(debug=opts.debug)
712 print((pbr.version.VersionInfo('nfvbench').version_string_with_vcs()))
716 with open(opts.summary) as json_data:
717 result = json.load(json_data)
719 result['config']['user_label'] = opts.user_label
720 print((NFVBenchSummarizer(result, fluent_logger)))
723 # show default config in text/yaml format
724 if opts.show_default_config:
725 print((default_cfg.decode("utf-8")))
728 # dump the contents of the trex log file
729 if opts.show_trex_log:
731 print(open('/tmp/trex.log').read(), end="")
732 except FileNotFoundError:
733 print("No TRex log file found!")
736 # mask info logging in case of further config dump
737 if opts.show_config or opts.show_pre_config:
738 LOG.setLevel(log.logging.WARNING)
742 # do not check extra_specs in flavor as it can contain any key/value pairs
743 # the same principle applies also to the optional user_info open property
744 whitelist_keys = ['extra_specs', 'user_info']
745 # override default config options with start config at path parsed from CLI
746 # check if it is an inline yaml/json config or a file name
747 if os.path.isfile(opts.config):
748 LOG.info('Loading configuration file: %s', opts.config)
749 config = config_load(opts.config, config, whitelist_keys)
750 config.name = os.path.basename(opts.config)
752 LOG.info('Loading configuration string: %s', opts.config)
753 config = config_loads(opts.config, config, whitelist_keys)
755 # show current config in json format (before CLI overriding)
756 if opts.show_pre_config:
757 print((json.dumps(config, sort_keys=True, indent=4)))
760 # setup the fluent logger as soon as possible right after the config plugin is called,
761 # if there is any logging or result tag is set then initialize the fluent logger
762 for fluentd in config.fluentd:
763 if fluentd.logging_tag or fluentd.result_tag:
764 fluent_logger = FluentLogHandler(config.fluentd)
765 LOG.addHandler(fluent_logger)
768 # traffic profile override options
769 override_custom_traffic(config, opts.frame_sizes, opts.unidir)
771 # Copy over some of the cli options that are used in config.
772 # This explicit copy is sometimes necessary
773 # because some early evaluation depends on them
774 # and cannot wait for _update_config() coming further.
775 # It is good practice then to set them to None (<=> done)
776 # and even required if a specific conversion is performed here
777 # that would be corrupted by a default update (simple copy).
778 # On the other hand, some excessive assignments have been removed
779 # from here, since the _update_config() procedure does them well.
781 config.generator_profile = opts.generator_profile
782 if opts.sriov is not None:
785 if opts.log_file is not None:
786 config.log_file = opts.log_file
788 if opts.user_id is not None:
789 config.user_id = opts.user_id
791 if opts.group_id is not None:
792 config.group_id = opts.group_id
794 if opts.service_chain is not None:
795 config.service_chain = opts.service_chain
796 opts.service_chain = None
797 if opts.hypervisor is not None:
798 # can be any of 'comp1', 'nova:', 'nova:comp1'
799 config.compute_nodes = opts.hypervisor
800 opts.hypervisor = None
801 if opts.debug_mask is not None:
802 config.debug_mask = opts.debug_mask
803 opts.debug_mask = None
805 # convert 'user_info' opt from json string to dictionnary
806 # and merge the result with the current config dictionnary
807 if opts.user_info is not None:
808 for user_info_json in opts.user_info:
809 user_info_dict = json.loads(user_info_json)
811 config.user_info = config.user_info + user_info_dict
813 config.user_info = user_info_dict
814 opts.user_info = None
816 # port to port loopback (direct or through switch)
817 # we accept the following syntaxes for the CLI argument
818 # 'false' : mode not enabled
819 # 'true' : mode enabled with currently defined vlan IDs
820 # 'no-tag' : mode enabled with no vlan tagging
821 # <vlan IDs>: mode enabled using the given (pair of) vlan ID lists
822 # - If present, a '_' char will separate left an right ports lists
823 # e.g. 'a_x' => vlans: [[a],[x]]
824 # 'a,b,c_x,y,z' => [[a,b,c],[x,y,z]]
825 # - Otherwise the given vlan ID list applies to both sides
826 # e.g. 'a' => vlans: [[a],[a]]
827 # 'a,b' => [[a,b],[a,b]]
828 # - Vlan lists size needs to be at least the actual SCC value
829 # - Unless overriden in CLI opts, config.service_chain_count
830 # is adjusted to the size of the VLAN ID lists given here.
832 if opts.l2_loopback is not None:
833 arg_pair = opts.l2_loopback.lower().split('_')
834 if arg_pair[0] == 'false':
835 config.l2_loopback = False
837 config.l2_loopback = True
838 if config.service_chain != ChainType.EXT:
839 LOG.info('Changing service chain type to EXT')
840 config.service_chain = ChainType.EXT
841 if not config.no_arp:
842 LOG.info('Disabling ARP')
844 if arg_pair[0] == 'true':
847 # here explicit (not)tagging is not CLI overridable
848 opts.vlan_tagging = None
849 if arg_pair[0] == 'no-tag':
850 config.vlan_tagging = False
852 config.vlan_tagging = True
853 if len(arg_pair) == 1 or not arg_pair[1]:
854 arg_pair = [arg_pair[0], arg_pair[0]]
857 def append_vlan(port, vlan_id):
858 # a vlan tag value must be in [0..4095]
859 if vlan_id not in range(0, 4096):
861 vlans[port].append(vlan_id)
864 vlan_ids = arg_pair[port].split(',')
865 for vlan_id in vlan_ids:
866 append_vlan(port, int(vlan_id))
867 if len(vlans[0]) != len(vlans[1]):
870 # at least one invalid tag => no tagging
871 config.vlan_tagging = False
872 if config.vlan_tagging:
874 # force service chain count if not CLI overriden
875 if opts.service_chain_count is None:
876 config.service_chain_count = len(vlans[0])
877 opts.l2_loopback = None
879 if config.use_sriov_middle_net is None:
880 config.use_sriov_middle_net = False
881 if opts.use_sriov_middle_net is not None:
882 config.use_sriov_middle_net = opts.use_sriov_middle_net
883 opts.use_sriov_middle_net = None
884 if (config.use_sriov_middle_net and (
885 (not config.sriov) or (config.service_chain != ChainType.PVVP))):
886 raise Exception("--use-sriov-middle-net is only valid for PVVP with SRIOV")
888 if config.sriov and config.service_chain != ChainType.EXT:
889 # if sriov is requested (does not apply to ext chains)
890 # make sure the physnet names are specified
891 check_physnet("left", config.internal_networks.left)
892 check_physnet("right", config.internal_networks.right)
893 if config.service_chain == ChainType.PVVP and config.use_sriov_middle_net:
894 check_physnet("middle", config.internal_networks.middle)
896 # update the config in the config plugin as it might have changed
897 # in a copy of the dict (config plugin still holds the original dict)
898 config_plugin.set_config(config)
900 if opts.status or opts.cleanup or opts.force_cleanup:
901 status_cleanup(config, opts.cleanup, opts.force_cleanup)
903 # add file log if requested
905 log.add_file_logger(config.log_file)
906 # possibly change file ownership
908 gid = config.group_id
912 os.chown(config.log_file, uid, gid)
914 openstack_spec = config_plugin.get_openstack_spec() if config.openrc_file \
917 nfvbench_instance = NFVBench(config, openstack_spec, config_plugin, factory)
920 server = WebServer(nfvbench_instance, fluent_logger)
922 port = int(opts.port)
924 server.run(host=opts.host)
926 server.run(host=opts.host, port=port)
927 # server.run() should never return
929 dry_run = opts.show_config
930 with utils.RunLock():
931 run_summary_required = True
933 err_msg = 'Unknown options: ' + ' '.join(unknown_opts)
935 raise Exception(err_msg)
937 # remove unfilled values
938 opts = {k: v for k, v in list(vars(opts).items()) if v is not None}
940 params = ' '.join(str(e) for e in sys.argv[1:])
941 result = nfvbench_instance.run(opts, params, dry_run=dry_run)
942 if 'error_message' in result:
943 raise Exception(result['error_message'])
945 if 'result' in result and result['status']:
946 nfvbench_instance.save(result['result'])
947 nfvbench_instance.prepare_summary(result['result'])
948 except Exception as exc:
949 run_summary_required = True
951 'status': NFVBench.STATUS_ERROR,
952 'error_message': traceback.format_exc()
957 # only send a summary record if there was an actual nfvbench run or
958 # if an error/exception was logged.
959 fluent_logger.send_run_summary(run_summary_required)
962 if __name__ == '__main__':