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):
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
80 status = NFVBench.STATUS_OK
84 # take a snapshot of the current time for this new run
85 # so that all subsequent logs can relate to this run
86 fluent_logger.start_new_run()
89 # recalc the running config based on the base config and options for this run
90 self._update_config(opts)
92 # check that an empty openrc file (no OpenStack) is only allowed
94 if not self.config.openrc_file and self.config.service_chain != ChainType.EXT:
95 raise Exception("openrc_file in the configuration is required for PVP/PVVP chains")
97 self.specs.set_run_spec(self.config_plugin.get_run_spec(self.config,
98 self.specs.openstack))
99 self.chain_runner = ChainRunner(self.config,
105 # make sure that the min frame size is 64
107 for frame_size in self.config.frame_sizes:
109 if int(frame_size) < min_packet_size:
110 frame_size = str(min_packet_size)
111 LOG.info("Adjusting frame size %s bytes to minimum size %s bytes",
112 frame_size, min_packet_size)
113 if frame_size not in new_frame_sizes:
114 new_frame_sizes.append(frame_size)
116 new_frame_sizes.append(frame_size.upper())
117 self.config.frame_sizes = new_frame_sizes
119 "date": datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
120 "nfvbench_version": __version__,
121 "config": self.config_plugin.prepare_results_config(copy.deepcopy(self.config)),
124 "service_chain": self.chain_runner.run(),
125 "versions": self.chain_runner.get_version(),
129 if self.specs.openstack:
130 result['openstack_spec'] = {"vswitch": self.specs.openstack.vswitch,
131 "encaps": self.specs.openstack.encaps}
132 result['benchmarks']['network']['versions'].update(self.config_plugin.get_version())
134 status = NFVBench.STATUS_ERROR
135 message = traceback.format_exc()
136 except KeyboardInterrupt:
137 status = NFVBench.STATUS_ERROR
138 message = traceback.format_exc()
140 if self.chain_runner:
141 self.chain_runner.close()
143 if status == NFVBench.STATUS_OK:
144 # result2 = utils.dict_to_json_dict(result)
151 'error_message': message
154 def prepare_summary(self, result):
155 """Prepare summary of the result to print and send it to logger (eg: fluentd)."""
157 summary = NFVBenchSummarizer(result, fluent_logger)
158 LOG.info(str(summary))
160 def save(self, result):
161 """Save results in json format file."""
162 utils.save_json_result(result,
163 self.config.json_file,
164 self.config.std_json_path,
165 self.config.service_chain,
166 self.config.service_chain_count,
167 self.config.flow_count,
168 self.config.frame_sizes,
170 self.config.group_id)
172 def _update_config(self, opts):
173 """Recalculate the running config based on the base config and opts.
175 Sanity check on the config is done here as well.
177 self.config = AttrDict(dict(self.base_config))
178 # Update log file handler if needed after a config update (REST mode)
179 if 'log_file' in opts:
181 (path, _filename) = os.path.split(opts['log_file'])
182 if not os.path.exists(path):
184 'Path %s does not exist. Please verify root path is shared with host. Path '
185 'will be created.', path)
187 LOG.info('%s is created.', path)
188 if not any(isinstance(h, FileHandler) for h in log.getLogger().handlers):
189 log.add_file_logger(opts['log_file'])
191 for h in log.getLogger().handlers:
192 if isinstance(h, FileHandler) and h.baseFilename != opts['log_file']:
193 # clean log file handler
194 log.getLogger().removeHandler(h)
195 log.add_file_logger(opts['log_file'])
197 self.config.update(opts)
200 config.service_chain = config.service_chain.upper()
201 config.service_chain_count = int(config.service_chain_count)
202 if config.l2_loopback:
203 # force the number of chains to be 1 in case of untagged l2 loopback
204 # (on the other hand, multiple L2 vlan tagged service chains are allowed)
205 if not config.vlan_tagging:
206 config.service_chain_count = 1
207 config.service_chain = ChainType.EXT
209 LOG.info('Running L2 loopback: using EXT chain/no ARP')
211 # allow oversized vlan lists, just clip them
213 vlans = [list(v) for v in config.vlans]
215 del v[config.service_chain_count:]
220 # traffic profile override options
221 if 'frame_sizes' in opts:
224 unidir = opts['unidir']
225 override_custom_traffic(config, opts['frame_sizes'], unidir)
226 LOG.info("Frame size has been set to %s for current configuration", opts['frame_sizes'])
228 config.flow_count = utils.parse_flow_count(config.flow_count)
229 required_flow_count = config.service_chain_count * 2
230 if config.flow_count < required_flow_count:
231 LOG.info("Flow count %d has been set to minimum value of '%d' "
232 "for current configuration", config.flow_count,
234 config.flow_count = required_flow_count
236 if config.flow_count % 2:
237 config.flow_count += 1
239 # Possibly adjust the cache size
240 if config.cache_size < 0:
241 config.cache_size = config.flow_count
243 # The size must be capped to 10000 (where does this limit come from?)
244 if config.cache_size > 10000:
245 config.cache_size = 10000
247 config.duration_sec = float(config.duration_sec)
248 config.interval_sec = float(config.interval_sec)
249 config.pause_sec = float(config.pause_sec)
251 if config.traffic is None or not config.traffic:
252 raise Exception("Missing traffic property in configuration")
254 if config.openrc_file:
255 config.openrc_file = os.path.expanduser(config.openrc_file)
256 if config.flavor.vcpus < 2:
257 raise Exception("Flavor vcpus must be >= 2")
259 config.ndr_run = (not config.no_traffic and
260 'ndr' in config.rate.strip().lower().split('_'))
261 config.pdr_run = (not config.no_traffic and
262 'pdr' in config.rate.strip().lower().split('_'))
263 config.single_run = (not config.no_traffic and
264 not (config.ndr_run or config.pdr_run))
266 config.json_file = config.json if config.json else None
268 (path, _filename) = os.path.split(config.json)
269 if not os.path.exists(path):
270 raise Exception('Please provide existing path for storing results in JSON file. '
271 'Path used: {path}'.format(path=path))
273 config.std_json_path = config.std_json if config.std_json else None
274 if config.std_json_path:
275 if not os.path.exists(config.std_json):
276 raise Exception('Please provide existing path for storing results in JSON file. '
277 'Path used: {path}'.format(path=config.std_json_path))
279 # Check that multiqueue is between 1 and 8 (8 is the max allowed by libvirt/qemu)
280 if config.vif_multiqueue_size < 1 or config.vif_multiqueue_size > 8:
281 raise Exception('vif_multiqueue_size (%d) must be in [1..8]' %
282 config.vif_multiqueue_size)
284 # VxLAN and MPLS sanity checks
285 if config.vxlan or config.mpls:
286 if config.vlan_tagging:
287 config.vlan_tagging = False
288 config.no_latency_streams = True
289 config.no_latency_stats = True
290 config.no_flow_stats = True
291 LOG.info('VxLAN or MPLS: vlan_tagging forced to False '
292 '(inner VLAN tagging must be disabled)')
294 self.config_plugin.validate_config(config, self.specs.openstack)
298 """Argument type to be used in parser.add_argument()
299 When a boolean like value is expected to be given
301 return (str(x).lower() != 'false') \
302 and (str(x).lower() != 'no') \
303 and (str(x).lower() != '0')
307 """Argument type to be used in parser.add_argument()
308 When an integer type value is expected to be given
309 (returns 0 if argument is invalid, hexa accepted)
314 def _parse_opts_from_cli():
315 parser = argparse.ArgumentParser()
317 parser.add_argument('--status', dest='status',
320 help='Provide NFVbench status')
322 parser.add_argument('-c', '--config', dest='config',
324 help='Override default values with a config file or '
325 'a yaml/json config string',
326 metavar='<file_name_or_yaml>')
328 parser.add_argument('--server', dest='server',
331 help='Run nfvbench in server mode')
333 parser.add_argument('--host', dest='host',
336 help='Host IP address on which server will be listening (default 0.0.0.0)')
338 parser.add_argument('-p', '--port', dest='port',
341 help='Port on which server will be listening (default 7555)')
343 parser.add_argument('-sc', '--service-chain', dest='service_chain',
344 choices=ChainType.names,
346 help='Service chain to run')
348 parser.add_argument('-scc', '--service-chain-count', dest='service_chain_count',
350 help='Set number of service chains to run',
351 metavar='<service_chain_count>')
353 parser.add_argument('-fc', '--flow-count', dest='flow_count',
355 help='Set number of total flows for all chains and all directions',
356 metavar='<flow_count>')
358 parser.add_argument('--rate', dest='rate',
360 help='Specify rate in pps, bps or %% as total for all directions',
363 parser.add_argument('--duration', dest='duration_sec',
365 help='Set duration to run traffic generator (in seconds)',
366 metavar='<duration_sec>')
368 parser.add_argument('--interval', dest='interval_sec',
370 help='Set interval to record traffic generator stats (in seconds)',
371 metavar='<interval_sec>')
373 parser.add_argument('--inter-node', dest='inter_node',
378 parser.add_argument('--sriov', dest='sriov',
381 help='Use SRIOV (no vswitch - requires SRIOV support in compute nodes)')
383 parser.add_argument('--use-sriov-middle-net', dest='use_sriov_middle_net',
386 help='Use SRIOV to handle the middle network traffic '
387 '(PVVP with SRIOV only)')
389 parser.add_argument('-d', '--debug', dest='debug',
392 help='print debug messages (verbose)')
394 parser.add_argument('-g', '--traffic-gen', dest='generator_profile',
396 help='Traffic generator profile to use')
398 parser.add_argument('-l3', '--l3-router', dest='l3_router',
401 help='Use L3 neutron routers to handle traffic')
403 parser.add_argument('-0', '--no-traffic', dest='no_traffic',
406 help='Check config and connectivity only - do not generate traffic')
408 parser.add_argument('--no-arp', dest='no_arp',
411 help='Do not use ARP to find MAC addresses, '
412 'instead use values in config file')
414 parser.add_argument('--loop-vm-arp', dest='loop_vm_arp',
417 help='Use ARP to find MAC addresses '
418 'instead of using values from TRex ports (VPP forwarder only)')
420 parser.add_argument('--no-vswitch-access', dest='no_vswitch_access',
423 help='Skip vswitch configuration and retrieving of stats')
425 parser.add_argument('--vxlan', dest='vxlan',
428 help='Enable VxLan encapsulation')
430 parser.add_argument('--mpls', dest='mpls',
433 help='Enable MPLS encapsulation')
435 parser.add_argument('--no-cleanup', dest='no_cleanup',
438 help='no cleanup after run')
440 parser.add_argument('--cleanup', dest='cleanup',
443 help='Cleanup NFVbench resources (prompt to confirm)')
445 parser.add_argument('--force-cleanup', dest='force_cleanup',
448 help='Cleanup NFVbench resources (do not prompt)')
450 parser.add_argument('--restart', dest='restart',
453 help='Restart TRex server')
455 parser.add_argument('--json', dest='json',
457 help='store results in json format file',
458 metavar='<path>/<filename>')
460 parser.add_argument('--std-json', dest='std_json',
462 help='store results in json format file with nfvbench standard filename: '
463 '<service-chain-type>-<service-chain-count>-<flow-count>'
464 '-<packet-sizes>.json',
467 parser.add_argument('--show-default-config', dest='show_default_config',
470 help='print the default config in yaml format (unedited)')
472 parser.add_argument('--show-config', dest='show_config',
475 help='print the running config in json format')
477 parser.add_argument('-ss', '--show-summary', dest='summary',
479 help='Show summary from nfvbench json file',
482 parser.add_argument('-v', '--version', dest='version',
487 parser.add_argument('-fs', '--frame-size', dest='frame_sizes',
489 help='Override traffic profile frame sizes',
490 metavar='<frame_size_bytes or IMIX>')
492 parser.add_argument('--unidir', dest='unidir',
495 help='Override traffic profile direction (requires -fs)')
497 parser.add_argument('--log-file', '--logfile', dest='log_file',
499 help='Filename for saving logs',
500 metavar='<log_file>')
502 parser.add_argument('--user-label', '--userlabel', dest='user_label',
504 help='Custom label for performance records')
506 parser.add_argument('--hypervisor', dest='hypervisor',
508 metavar='<hypervisor name>',
509 help='Where chains must run ("compute", "az:", "az:compute")')
511 parser.add_argument('--l2-loopback', '--l2loopback', dest='l2_loopback',
513 metavar='<vlan(s)|no-tag|true|false>',
514 help='Port to port or port to switch to port L2 loopback '
515 'tagged with given VLAN id(s) or not (given \'no-tag\') '
516 '\'true\': use current vlans; \'false\': disable this mode.')
518 parser.add_argument('--user-info', dest='user_info',
521 help='Custom data to be included as is '
522 'in the json report config branch - '
523 ' example, pay attention! no space: '
524 '--user-info=\'{"status":"explore","description":'
525 '{"target":"lab","ok":true,"version":2020}}\' - '
526 'this option may be repeated; given data will be merged.')
528 parser.add_argument('--vlan-tagging', dest='vlan_tagging',
533 help='Override the NFVbench \'vlan_tagging\' parameter')
535 parser.add_argument('--intf-speed', dest='intf_speed',
539 help='Override the NFVbench \'intf_speed\' '
540 'parameter (e.g. 10Gbps, auto, 16.72Gbps)')
542 parser.add_argument('--cores', dest='cores',
547 help='Override the T-Rex \'cores\' parameter')
549 parser.add_argument('--cache-size', dest='cache_size',
554 help='Specify the FE cache size (default: 0, flow-count if < 0)')
556 parser.add_argument('--service-mode', dest='service_mode',
559 help='Enable T-Rex service mode (for debugging purpose)')
561 parser.add_argument('--no-e2e-check', dest='no_e2e_check',
564 help='Skip "end to end" connectivity check (on test purpose)')
566 parser.add_argument('--no-flow-stats', dest='no_flow_stats',
569 help='Disable additional flow stats (on high load traffic)')
571 parser.add_argument('--no-latency-stats', dest='no_latency_stats',
574 help='Disable flow stats for latency traffic')
576 parser.add_argument('--no-latency-streams', dest='no_latency_streams',
579 help='Disable latency measurements (no streams)')
581 parser.add_argument('--user-id', dest='user_id',
586 help='Change json/log files ownership with this user (int)')
588 parser.add_argument('--group-id', dest='group_id',
593 help='Change json/log files ownership with this group (int)')
595 parser.add_argument('--show-trex-log', dest='show_trex_log',
598 help='Show the current TRex local server log file contents'
599 ' => diagnostic/help in case of configuration problems')
601 parser.add_argument('--debug-mask', dest='debug_mask',
606 help='General purpose register (debugging flags), '
607 'the hexadecimal notation (0x...) is accepted.'
608 'Designed for development needs (default: 0).')
610 opts, unknown_opts = parser.parse_known_args()
611 return opts, unknown_opts
614 def load_default_config():
615 default_cfg = resource_string(__name__, "cfg.default.yaml")
616 config = config_loads(default_cfg)
617 config.name = '(built-in default config)'
618 return config, default_cfg
621 def override_custom_traffic(config, frame_sizes, unidir):
622 """Override the traffic profiles with a custom one."""
623 if frame_sizes is not None:
624 traffic_profile_name = "custom_traffic_profile"
625 config.traffic_profile = [
627 "l2frame_size": frame_sizes,
628 "name": traffic_profile_name
632 traffic_profile_name = config.traffic["profile"]
634 bidirectional = config.traffic['bidirectional'] if unidir is None else not unidir
636 "bidirectional": bidirectional,
637 "profile": traffic_profile_name
641 def check_physnet(name, netattrs):
642 if not netattrs.physical_network:
643 raise Exception("SRIOV requires physical_network to be specified for the {n} network"
645 if not netattrs.segmentation_id:
646 raise Exception("SRIOV requires segmentation_id to be specified for the {n} network"
649 def status_cleanup(config, cleanup, force_cleanup):
650 LOG.info('Version: %s', pbr.version.VersionInfo('nfvbench').version_string_with_vcs())
651 # check if another run is pending
654 with utils.RunLock():
655 LOG.info('Status: idle')
657 LOG.info('Status: busy (run pending)')
659 # check nfvbench resources
660 if config.openrc_file and config.service_chain != ChainType.EXT:
661 cleaner = Cleaner(config)
662 count = cleaner.show_resources()
663 if count and (cleanup or force_cleanup):
664 cleaner.clean(not force_cleanup)
669 run_summary_required = False
672 # load default config file
673 config, default_cfg = load_default_config()
674 # possibly override the default user_id & group_id values
675 if 'USER_ID' in os.environ:
676 config.user_id = int(os.environ['USER_ID'])
677 if 'GROUP_ID' in os.environ:
678 config.group_id = int(os.environ['GROUP_ID'])
680 # create factory for platform specific classes
682 factory_module = importlib.import_module(config['factory_module'])
683 factory = getattr(factory_module, config['factory_class'])()
684 except AttributeError:
685 raise Exception("Requested factory module '{m}' or class '{c}' was not found."
686 .format(m=config['factory_module'],
687 c=config['factory_class'])) from AttributeError
688 # create config plugin for this platform
689 config_plugin = factory.get_config_plugin_class()(config)
690 config = config_plugin.get_config()
692 opts, unknown_opts = _parse_opts_from_cli()
693 log.set_level(debug=opts.debug)
696 print((pbr.version.VersionInfo('nfvbench').version_string_with_vcs()))
700 with open(opts.summary) as json_data:
701 result = json.load(json_data)
703 result['config']['user_label'] = opts.user_label
704 print((NFVBenchSummarizer(result, fluent_logger)))
707 # show default config in text/yaml format
708 if opts.show_default_config:
709 print((default_cfg.decode("utf-8")))
712 # dump the contents of the trex log file
713 if opts.show_trex_log:
715 print(open('/tmp/trex.log').read(), end="")
716 except FileNotFoundError:
717 print("No TRex log file found!")
722 # do not check extra_specs in flavor as it can contain any key/value pairs
723 # the same principle applies also to the optional user_info open property
724 whitelist_keys = ['extra_specs', 'user_info']
725 # override default config options with start config at path parsed from CLI
726 # check if it is an inline yaml/json config or a file name
727 if os.path.isfile(opts.config):
728 LOG.info('Loading configuration file: %s', opts.config)
729 config = config_load(opts.config, config, whitelist_keys)
730 config.name = os.path.basename(opts.config)
732 LOG.info('Loading configuration string: %s', opts.config)
733 config = config_loads(opts.config, config, whitelist_keys)
735 # setup the fluent logger as soon as possible right after the config plugin is called,
736 # if there is any logging or result tag is set then initialize the fluent logger
737 for fluentd in config.fluentd:
738 if fluentd.logging_tag or fluentd.result_tag:
739 fluent_logger = FluentLogHandler(config.fluentd)
740 LOG.addHandler(fluent_logger)
743 # traffic profile override options
744 override_custom_traffic(config, opts.frame_sizes, opts.unidir)
746 # Copy over some of the cli options that are used in config.
747 # This explicit copy is sometimes necessary
748 # because some early evaluation depends on them
749 # and cannot wait for _update_config() coming further.
750 # It is good practice then to set them to None (<=> done)
751 # and even required if a specific conversion is performed here
752 # that would be corrupted by a default update (simple copy).
753 # On the other hand, some excessive assignments have been removed
754 # from here, since the _update_config() procedure does them well.
756 config.generator_profile = opts.generator_profile
757 if opts.sriov is not None:
760 if opts.log_file is not None:
761 config.log_file = opts.log_file
763 if opts.user_id is not None:
764 config.user_id = opts.user_id
766 if opts.group_id is not None:
767 config.group_id = opts.group_id
769 if opts.service_chain is not None:
770 config.service_chain = opts.service_chain
771 opts.service_chain = None
772 if opts.hypervisor is not None:
773 # can be any of 'comp1', 'nova:', 'nova:comp1'
774 config.compute_nodes = opts.hypervisor
775 opts.hypervisor = None
776 if opts.debug_mask is not None:
777 config.debug_mask = opts.debug_mask
778 opts.debug_mask = None
780 # convert 'user_info' opt from json string to dictionnary
781 # and merge the result with the current config dictionnary
782 if opts.user_info is not None:
783 for user_info_json in opts.user_info:
784 user_info_dict = json.loads(user_info_json)
786 config.user_info = config.user_info + user_info_dict
788 config.user_info = user_info_dict
789 opts.user_info = None
791 # port to port loopback (direct or through switch)
792 # we accept the following syntaxes for the CLI argument
793 # 'false' : mode not enabled
794 # 'true' : mode enabled with currently defined vlan IDs
795 # 'no-tag' : mode enabled with no vlan tagging
796 # <vlan IDs>: mode enabled using the given (pair of) vlan ID lists
797 # - If present, a '_' char will separate left an right ports lists
798 # e.g. 'a_x' => vlans: [[a],[x]]
799 # 'a,b,c_x,y,z' => [[a,b,c],[x,y,z]]
800 # - Otherwise the given vlan ID list applies to both sides
801 # e.g. 'a' => vlans: [[a],[a]]
802 # 'a,b' => [[a,b],[a,b]]
803 # - Vlan lists size needs to be at least the actual SCC value
804 # - Unless overriden in CLI opts, config.service_chain_count
805 # is adjusted to the size of the VLAN ID lists given here.
807 if opts.l2_loopback is not None:
808 arg_pair = opts.l2_loopback.lower().split('_')
809 if arg_pair[0] == 'false':
810 config.l2_loopback = False
812 config.l2_loopback = True
813 if config.service_chain != ChainType.EXT:
814 LOG.info('Changing service chain type to EXT')
815 config.service_chain = ChainType.EXT
816 if not config.no_arp:
817 LOG.info('Disabling ARP')
819 if arg_pair[0] == 'true':
822 # here explicit (not)tagging is not CLI overridable
823 opts.vlan_tagging = None
824 if arg_pair[0] == 'no-tag':
825 config.vlan_tagging = False
827 config.vlan_tagging = True
828 if len(arg_pair) == 1 or not arg_pair[1]:
829 arg_pair = [arg_pair[0], arg_pair[0]]
832 def append_vlan(port, vlan_id):
833 # a vlan tag value must be in [0..4095]
834 if vlan_id not in range(0, 4096):
836 vlans[port].append(vlan_id)
839 vlan_ids = arg_pair[port].split(',')
840 for vlan_id in vlan_ids:
841 append_vlan(port, int(vlan_id))
842 if len(vlans[0]) != len(vlans[1]):
845 # at least one invalid tag => no tagging
846 config.vlan_tagging = False
847 if config.vlan_tagging:
849 # force service chain count if not CLI overriden
850 if opts.service_chain_count is None:
851 config.service_chain_count = len(vlans[0])
852 opts.l2_loopback = None
854 if config.use_sriov_middle_net is None:
855 config.use_sriov_middle_net = False
856 if opts.use_sriov_middle_net is not None:
857 config.use_sriov_middle_net = opts.use_sriov_middle_net
858 opts.use_sriov_middle_net = None
859 if (config.use_sriov_middle_net and (
860 (not config.sriov) or (config.service_chain != ChainType.PVVP))):
861 raise Exception("--use-sriov-middle-net is only valid for PVVP with SRIOV")
863 if config.sriov and config.service_chain != ChainType.EXT:
864 # if sriov is requested (does not apply to ext chains)
865 # make sure the physnet names are specified
866 check_physnet("left", config.internal_networks.left)
867 check_physnet("right", config.internal_networks.right)
868 if config.service_chain == ChainType.PVVP and config.use_sriov_middle_net:
869 check_physnet("middle", config.internal_networks.middle)
871 # show running config in json format
873 print((json.dumps(config, sort_keys=True, indent=4)))
876 # update the config in the config plugin as it might have changed
877 # in a copy of the dict (config plugin still holds the original dict)
878 config_plugin.set_config(config)
880 if opts.status or opts.cleanup or opts.force_cleanup:
881 status_cleanup(config, opts.cleanup, opts.force_cleanup)
883 # add file log if requested
885 log.add_file_logger(config.log_file)
886 # possibly change file ownership
888 gid = config.group_id
892 os.chown(config.log_file, uid, gid)
894 openstack_spec = config_plugin.get_openstack_spec() if config.openrc_file \
897 nfvbench_instance = NFVBench(config, openstack_spec, config_plugin, factory)
900 server = WebServer(nfvbench_instance, fluent_logger)
902 port = int(opts.port)
904 server.run(host=opts.host)
906 server.run(host=opts.host, port=port)
907 # server.run() should never return
909 with utils.RunLock():
910 run_summary_required = True
912 err_msg = 'Unknown options: ' + ' '.join(unknown_opts)
914 raise Exception(err_msg)
916 # remove unfilled values
917 opts = {k: v for k, v in list(vars(opts).items()) if v is not None}
919 params = ' '.join(str(e) for e in sys.argv[1:])
920 result = nfvbench_instance.run(opts, params)
921 if 'error_message' in result:
922 raise Exception(result['error_message'])
924 if 'result' in result and result['status']:
925 nfvbench_instance.save(result['result'])
926 nfvbench_instance.prepare_summary(result['result'])
927 except Exception as exc:
928 run_summary_required = True
930 'status': NFVBench.STATUS_ERROR,
931 'error_message': traceback.format_exc()
936 # only send a summary record if there was an actual nfvbench run or
937 # if an error/exception was logged.
938 fluent_logger.send_run_summary(run_summary_required)
941 if __name__ == '__main__':