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 for h in log.getLogger().handlers:
189 if isinstance(h, FileHandler) and h.baseFilename != opts['log_file']:
190 # clean log file handler
191 log.getLogger().removeHandler(h)
192 # add handler if not existing to avoid duplicates handlers
193 if len(log.getLogger().handlers) == 1:
194 log.add_file_logger(opts['log_file'])
196 self.config.update(opts)
199 config.service_chain = config.service_chain.upper()
200 config.service_chain_count = int(config.service_chain_count)
201 if config.l2_loopback:
202 # force the number of chains to be 1 in case of untagged l2 loopback
203 # (on the other hand, multiple L2 vlan tagged service chains are allowed)
204 if not config.vlan_tagging:
205 config.service_chain_count = 1
206 config.service_chain = ChainType.EXT
208 LOG.info('Running L2 loopback: using EXT chain/no ARP')
210 # allow oversized vlan lists, just clip them
212 vlans = [list(v) for v in config.vlans]
214 del v[config.service_chain_count:]
219 # traffic profile override options
220 if 'frame_sizes' in opts:
223 unidir = opts['unidir']
224 override_custom_traffic(config, opts['frame_sizes'], unidir)
225 LOG.info("Frame size has been set to %s for current configuration", opts['frame_sizes'])
227 config.flow_count = utils.parse_flow_count(config.flow_count)
228 required_flow_count = config.service_chain_count * 2
229 if config.flow_count < required_flow_count:
230 LOG.info("Flow count %d has been set to minimum value of '%d' "
231 "for current configuration", config.flow_count,
233 config.flow_count = required_flow_count
235 if config.flow_count % 2:
236 config.flow_count += 1
238 # Possibly adjust the cache size
239 if config.cache_size < 0:
240 config.cache_size = config.flow_count
242 # The size must be capped to 10000 (where does this limit come from?)
243 if config.cache_size > 10000:
244 config.cache_size = 10000
246 config.duration_sec = float(config.duration_sec)
247 config.interval_sec = float(config.interval_sec)
248 config.pause_sec = float(config.pause_sec)
250 if config.traffic is None or not config.traffic:
251 raise Exception("Missing traffic property in configuration")
253 if config.openrc_file:
254 config.openrc_file = os.path.expanduser(config.openrc_file)
255 if config.flavor.vcpus < 2:
256 raise Exception("Flavor vcpus must be >= 2")
258 config.ndr_run = (not config.no_traffic and
259 'ndr' in config.rate.strip().lower().split('_'))
260 config.pdr_run = (not config.no_traffic and
261 'pdr' in config.rate.strip().lower().split('_'))
262 config.single_run = (not config.no_traffic and
263 not (config.ndr_run or config.pdr_run))
265 config.json_file = config.json if config.json else None
267 (path, _filename) = os.path.split(config.json)
268 if not os.path.exists(path):
269 raise Exception('Please provide existing path for storing results in JSON file. '
270 'Path used: {path}'.format(path=path))
272 config.std_json_path = config.std_json if config.std_json else None
273 if config.std_json_path:
274 if not os.path.exists(config.std_json):
275 raise Exception('Please provide existing path for storing results in JSON file. '
276 'Path used: {path}'.format(path=config.std_json_path))
278 # Check that multiqueue is between 1 and 8 (8 is the max allowed by libvirt/qemu)
279 if config.vif_multiqueue_size < 1 or config.vif_multiqueue_size > 8:
280 raise Exception('vif_multiqueue_size (%d) must be in [1..8]' %
281 config.vif_multiqueue_size)
283 # VxLAN and MPLS sanity checks
284 if config.vxlan or config.mpls:
285 if config.vlan_tagging:
286 config.vlan_tagging = False
287 config.no_latency_streams = True
288 config.no_latency_stats = True
289 config.no_flow_stats = True
290 LOG.info('VxLAN or MPLS: vlan_tagging forced to False '
291 '(inner VLAN tagging must be disabled)')
293 self.config_plugin.validate_config(config, self.specs.openstack)
297 """Argument type to be used in parser.add_argument()
298 When a boolean like value is expected to be given
300 return (str(x).lower() != 'false') \
301 and (str(x).lower() != 'no') \
302 and (str(x).lower() != '0')
306 """Argument type to be used in parser.add_argument()
307 When an integer type value is expected to be given
308 (returns 0 if argument is invalid, hexa accepted)
313 def _parse_opts_from_cli():
314 parser = argparse.ArgumentParser()
316 parser.add_argument('--status', dest='status',
319 help='Provide NFVbench status')
321 parser.add_argument('-c', '--config', dest='config',
323 help='Override default values with a config file or '
324 'a yaml/json config string',
325 metavar='<file_name_or_yaml>')
327 parser.add_argument('--server', dest='server',
330 help='Run nfvbench in server mode')
332 parser.add_argument('--host', dest='host',
335 help='Host IP address on which server will be listening (default 0.0.0.0)')
337 parser.add_argument('-p', '--port', dest='port',
340 help='Port on which server will be listening (default 7555)')
342 parser.add_argument('-sc', '--service-chain', dest='service_chain',
343 choices=ChainType.names,
345 help='Service chain to run')
347 parser.add_argument('-scc', '--service-chain-count', dest='service_chain_count',
349 help='Set number of service chains to run',
350 metavar='<service_chain_count>')
352 parser.add_argument('-fc', '--flow-count', dest='flow_count',
354 help='Set number of total flows for all chains and all directions',
355 metavar='<flow_count>')
357 parser.add_argument('--rate', dest='rate',
359 help='Specify rate in pps, bps or %% as total for all directions',
362 parser.add_argument('--duration', dest='duration_sec',
364 help='Set duration to run traffic generator (in seconds)',
365 metavar='<duration_sec>')
367 parser.add_argument('--interval', dest='interval_sec',
369 help='Set interval to record traffic generator stats (in seconds)',
370 metavar='<interval_sec>')
372 parser.add_argument('--inter-node', dest='inter_node',
377 parser.add_argument('--sriov', dest='sriov',
380 help='Use SRIOV (no vswitch - requires SRIOV support in compute nodes)')
382 parser.add_argument('--use-sriov-middle-net', dest='use_sriov_middle_net',
385 help='Use SRIOV to handle the middle network traffic '
386 '(PVVP with SRIOV only)')
388 parser.add_argument('-d', '--debug', dest='debug',
391 help='print debug messages (verbose)')
393 parser.add_argument('-g', '--traffic-gen', dest='generator_profile',
395 help='Traffic generator profile to use')
397 parser.add_argument('-l3', '--l3-router', dest='l3_router',
400 help='Use L3 neutron routers to handle traffic')
402 parser.add_argument('-0', '--no-traffic', dest='no_traffic',
405 help='Check config and connectivity only - do not generate traffic')
407 parser.add_argument('--no-arp', dest='no_arp',
410 help='Do not use ARP to find MAC addresses, '
411 'instead use values in config file')
413 parser.add_argument('--loop-vm-arp', dest='loop_vm_arp',
416 help='Use ARP to find MAC addresses '
417 'instead of using values from TRex ports (VPP forwarder only)')
419 parser.add_argument('--no-vswitch-access', dest='no_vswitch_access',
422 help='Skip vswitch configuration and retrieving of stats')
424 parser.add_argument('--vxlan', dest='vxlan',
427 help='Enable VxLan encapsulation')
429 parser.add_argument('--mpls', dest='mpls',
432 help='Enable MPLS encapsulation')
434 parser.add_argument('--no-cleanup', dest='no_cleanup',
437 help='no cleanup after run')
439 parser.add_argument('--cleanup', dest='cleanup',
442 help='Cleanup NFVbench resources (prompt to confirm)')
444 parser.add_argument('--force-cleanup', dest='force_cleanup',
447 help='Cleanup NFVbench resources (do not prompt)')
449 parser.add_argument('--restart', dest='restart',
452 help='Restart TRex server')
454 parser.add_argument('--json', dest='json',
456 help='store results in json format file',
457 metavar='<path>/<filename>')
459 parser.add_argument('--std-json', dest='std_json',
461 help='store results in json format file with nfvbench standard filename: '
462 '<service-chain-type>-<service-chain-count>-<flow-count>'
463 '-<packet-sizes>.json',
466 parser.add_argument('--show-default-config', dest='show_default_config',
469 help='print the default config in yaml format (unedited)')
471 parser.add_argument('--show-config', dest='show_config',
474 help='print the running config in json format')
476 parser.add_argument('-ss', '--show-summary', dest='summary',
478 help='Show summary from nfvbench json file',
481 parser.add_argument('-v', '--version', dest='version',
486 parser.add_argument('-fs', '--frame-size', dest='frame_sizes',
488 help='Override traffic profile frame sizes',
489 metavar='<frame_size_bytes or IMIX>')
491 parser.add_argument('--unidir', dest='unidir',
494 help='Override traffic profile direction (requires -fs)')
496 parser.add_argument('--log-file', '--logfile', dest='log_file',
498 help='Filename for saving logs',
499 metavar='<log_file>')
501 parser.add_argument('--user-label', '--userlabel', dest='user_label',
503 help='Custom label for performance records')
505 parser.add_argument('--hypervisor', dest='hypervisor',
507 metavar='<hypervisor name>',
508 help='Where chains must run ("compute", "az:", "az:compute")')
510 parser.add_argument('--l2-loopback', '--l2loopback', dest='l2_loopback',
512 metavar='<vlan(s)|no-tag|true|false>',
513 help='Port to port or port to switch to port L2 loopback '
514 'tagged with given VLAN id(s) or not (given \'no-tag\') '
515 '\'true\': use current vlans; \'false\': disable this mode.')
517 parser.add_argument('--user-info', dest='user_info',
520 help='Custom data to be included as is '
521 'in the json report config branch - '
522 ' example, pay attention! no space: '
523 '--user-info=\'{"status":"explore","description":'
524 '{"target":"lab","ok":true,"version":2020}}\' - '
525 'this option may be repeated; given data will be merged.')
527 parser.add_argument('--vlan-tagging', dest='vlan_tagging',
532 help='Override the NFVbench \'vlan_tagging\' parameter')
534 parser.add_argument('--intf-speed', dest='intf_speed',
538 help='Override the NFVbench \'intf_speed\' '
539 'parameter (e.g. 10Gbps, auto, 16.72Gbps)')
541 parser.add_argument('--cores', dest='cores',
546 help='Override the T-Rex \'cores\' parameter')
548 parser.add_argument('--cache-size', dest='cache_size',
553 help='Specify the FE cache size (default: 0, flow-count if < 0)')
555 parser.add_argument('--service-mode', dest='service_mode',
558 help='Enable T-Rex service mode (for debugging purpose)')
560 parser.add_argument('--no-e2e-check', dest='no_e2e_check',
563 help='Skip "end to end" connectivity check (on test purpose)')
565 parser.add_argument('--no-flow-stats', dest='no_flow_stats',
568 help='Disable additional flow stats (on high load traffic)')
570 parser.add_argument('--no-latency-stats', dest='no_latency_stats',
573 help='Disable flow stats for latency traffic')
575 parser.add_argument('--no-latency-streams', dest='no_latency_streams',
578 help='Disable latency measurements (no streams)')
580 parser.add_argument('--user-id', dest='user_id',
585 help='Change json/log files ownership with this user (int)')
587 parser.add_argument('--group-id', dest='group_id',
592 help='Change json/log files ownership with this group (int)')
594 parser.add_argument('--show-trex-log', dest='show_trex_log',
597 help='Show the current TRex local server log file contents'
598 ' => diagnostic/help in case of configuration problems')
600 parser.add_argument('--debug-mask', dest='debug_mask',
605 help='General purpose register (debugging flags), '
606 'the hexadecimal notation (0x...) is accepted.'
607 'Designed for development needs (default: 0).')
609 opts, unknown_opts = parser.parse_known_args()
610 return opts, unknown_opts
613 def load_default_config():
614 default_cfg = resource_string(__name__, "cfg.default.yaml")
615 config = config_loads(default_cfg)
616 config.name = '(built-in default config)'
617 return config, default_cfg
620 def override_custom_traffic(config, frame_sizes, unidir):
621 """Override the traffic profiles with a custom one."""
622 if frame_sizes is not None:
623 traffic_profile_name = "custom_traffic_profile"
624 config.traffic_profile = [
626 "l2frame_size": frame_sizes,
627 "name": traffic_profile_name
631 traffic_profile_name = config.traffic["profile"]
633 bidirectional = config.traffic['bidirectional'] if unidir is None else not unidir
635 "bidirectional": bidirectional,
636 "profile": traffic_profile_name
640 def check_physnet(name, netattrs):
641 if not netattrs.physical_network:
642 raise Exception("SRIOV requires physical_network to be specified for the {n} network"
644 if not netattrs.segmentation_id:
645 raise Exception("SRIOV requires segmentation_id to be specified for the {n} network"
648 def status_cleanup(config, cleanup, force_cleanup):
649 LOG.info('Version: %s', pbr.version.VersionInfo('nfvbench').version_string_with_vcs())
650 # check if another run is pending
653 with utils.RunLock():
654 LOG.info('Status: idle')
656 LOG.info('Status: busy (run pending)')
658 # check nfvbench resources
659 if config.openrc_file and config.service_chain != ChainType.EXT:
660 cleaner = Cleaner(config)
661 count = cleaner.show_resources()
662 if count and (cleanup or force_cleanup):
663 cleaner.clean(not force_cleanup)
668 run_summary_required = False
671 # load default config file
672 config, default_cfg = load_default_config()
673 # possibly override the default user_id & group_id values
674 if 'USER_ID' in os.environ:
675 config.user_id = int(os.environ['USER_ID'])
676 if 'GROUP_ID' in os.environ:
677 config.group_id = int(os.environ['GROUP_ID'])
679 # create factory for platform specific classes
681 factory_module = importlib.import_module(config['factory_module'])
682 factory = getattr(factory_module, config['factory_class'])()
683 except AttributeError:
684 raise Exception("Requested factory module '{m}' or class '{c}' was not found."
685 .format(m=config['factory_module'],
686 c=config['factory_class'])) from AttributeError
687 # create config plugin for this platform
688 config_plugin = factory.get_config_plugin_class()(config)
689 config = config_plugin.get_config()
691 opts, unknown_opts = _parse_opts_from_cli()
692 log.set_level(debug=opts.debug)
695 print((pbr.version.VersionInfo('nfvbench').version_string_with_vcs()))
699 with open(opts.summary) as json_data:
700 result = json.load(json_data)
702 result['config']['user_label'] = opts.user_label
703 print((NFVBenchSummarizer(result, fluent_logger)))
706 # show default config in text/yaml format
707 if opts.show_default_config:
708 print((default_cfg.decode("utf-8")))
711 # dump the contents of the trex log file
712 if opts.show_trex_log:
714 print(open('/tmp/trex.log').read(), end="")
715 except FileNotFoundError:
716 print("No TRex log file found!")
721 # do not check extra_specs in flavor as it can contain any key/value pairs
722 # the same principle applies also to the optional user_info open property
723 whitelist_keys = ['extra_specs', 'user_info']
724 # override default config options with start config at path parsed from CLI
725 # check if it is an inline yaml/json config or a file name
726 if os.path.isfile(opts.config):
727 LOG.info('Loading configuration file: %s', opts.config)
728 config = config_load(opts.config, config, whitelist_keys)
729 config.name = os.path.basename(opts.config)
731 LOG.info('Loading configuration string: %s', opts.config)
732 config = config_loads(opts.config, config, whitelist_keys)
734 # setup the fluent logger as soon as possible right after the config plugin is called,
735 # if there is any logging or result tag is set then initialize the fluent logger
736 for fluentd in config.fluentd:
737 if fluentd.logging_tag or fluentd.result_tag:
738 fluent_logger = FluentLogHandler(config.fluentd)
739 LOG.addHandler(fluent_logger)
742 # traffic profile override options
743 override_custom_traffic(config, opts.frame_sizes, opts.unidir)
745 # Copy over some of the cli options that are used in config.
746 # This explicit copy is sometimes necessary
747 # because some early evaluation depends on them
748 # and cannot wait for _update_config() coming further.
749 # It is good practice then to set them to None (<=> done)
750 # and even required if a specific conversion is performed here
751 # that would be corrupted by a default update (simple copy).
752 # On the other hand, some excessive assignments have been removed
753 # from here, since the _update_config() procedure does them well.
755 config.generator_profile = opts.generator_profile
756 if opts.sriov is not None:
759 if opts.log_file is not None:
760 config.log_file = opts.log_file
762 if opts.user_id is not None:
763 config.user_id = opts.user_id
765 if opts.group_id is not None:
766 config.group_id = opts.group_id
768 if opts.service_chain is not None:
769 config.service_chain = opts.service_chain
770 opts.service_chain = None
771 if opts.hypervisor is not None:
772 # can be any of 'comp1', 'nova:', 'nova:comp1'
773 config.compute_nodes = opts.hypervisor
774 opts.hypervisor = None
775 if opts.debug_mask is not None:
776 config.debug_mask = opts.debug_mask
777 opts.debug_mask = None
779 # convert 'user_info' opt from json string to dictionnary
780 # and merge the result with the current config dictionnary
781 if opts.user_info is not None:
782 for user_info_json in opts.user_info:
783 user_info_dict = json.loads(user_info_json)
785 config.user_info = config.user_info + user_info_dict
787 config.user_info = user_info_dict
788 opts.user_info = None
790 # port to port loopback (direct or through switch)
791 # we accept the following syntaxes for the CLI argument
792 # 'false' : mode not enabled
793 # 'true' : mode enabled with currently defined vlan IDs
794 # 'no-tag' : mode enabled with no vlan tagging
795 # <vlan IDs>: mode enabled using the given (pair of) vlan ID lists
796 # - If present, a '_' char will separate left an right ports lists
797 # e.g. 'a_x' => vlans: [[a],[x]]
798 # 'a,b,c_x,y,z' => [[a,b,c],[x,y,z]]
799 # - Otherwise the given vlan ID list applies to both sides
800 # e.g. 'a' => vlans: [[a],[a]]
801 # 'a,b' => [[a,b],[a,b]]
802 # - Vlan lists size needs to be at least the actual SCC value
803 # - Unless overriden in CLI opts, config.service_chain_count
804 # is adjusted to the size of the VLAN ID lists given here.
806 if opts.l2_loopback is not None:
807 arg_pair = opts.l2_loopback.lower().split('_')
808 if arg_pair[0] == 'false':
809 config.l2_loopback = False
811 config.l2_loopback = True
812 if config.service_chain != ChainType.EXT:
813 LOG.info('Changing service chain type to EXT')
814 config.service_chain = ChainType.EXT
815 if not config.no_arp:
816 LOG.info('Disabling ARP')
818 if arg_pair[0] == 'true':
821 # here explicit (not)tagging is not CLI overridable
822 opts.vlan_tagging = None
823 if arg_pair[0] == 'no-tag':
824 config.vlan_tagging = False
826 config.vlan_tagging = True
827 if len(arg_pair) == 1 or not arg_pair[1]:
828 arg_pair = [arg_pair[0], arg_pair[0]]
831 def append_vlan(port, vlan_id):
832 # a vlan tag value must be in [0..4095]
833 if vlan_id not in range(0, 4096):
835 vlans[port].append(vlan_id)
838 vlan_ids = arg_pair[port].split(',')
839 for vlan_id in vlan_ids:
840 append_vlan(port, int(vlan_id))
841 if len(vlans[0]) != len(vlans[1]):
844 # at least one invalid tag => no tagging
845 config.vlan_tagging = False
846 if config.vlan_tagging:
848 # force service chain count if not CLI overriden
849 if opts.service_chain_count is None:
850 config.service_chain_count = len(vlans[0])
851 opts.l2_loopback = None
853 if config.use_sriov_middle_net is None:
854 config.use_sriov_middle_net = False
855 if opts.use_sriov_middle_net is not None:
856 config.use_sriov_middle_net = opts.use_sriov_middle_net
857 opts.use_sriov_middle_net = None
858 if (config.use_sriov_middle_net and (
859 (not config.sriov) or (config.service_chain != ChainType.PVVP))):
860 raise Exception("--use-sriov-middle-net is only valid for PVVP with SRIOV")
862 if config.sriov and config.service_chain != ChainType.EXT:
863 # if sriov is requested (does not apply to ext chains)
864 # make sure the physnet names are specified
865 check_physnet("left", config.internal_networks.left)
866 check_physnet("right", config.internal_networks.right)
867 if config.service_chain == ChainType.PVVP and config.use_sriov_middle_net:
868 check_physnet("middle", config.internal_networks.middle)
870 # show running config in json format
872 print((json.dumps(config, sort_keys=True, indent=4)))
875 # update the config in the config plugin as it might have changed
876 # in a copy of the dict (config plugin still holds the original dict)
877 config_plugin.set_config(config)
879 if opts.status or opts.cleanup or opts.force_cleanup:
880 status_cleanup(config, opts.cleanup, opts.force_cleanup)
882 # add file log if requested
884 log.add_file_logger(config.log_file)
885 # possibly change file ownership
887 gid = config.group_id
891 os.chown(config.log_file, uid, gid)
893 openstack_spec = config_plugin.get_openstack_spec() if config.openrc_file \
896 nfvbench_instance = NFVBench(config, openstack_spec, config_plugin, factory)
899 server = WebServer(nfvbench_instance, fluent_logger)
901 port = int(opts.port)
903 server.run(host=opts.host)
905 server.run(host=opts.host, port=port)
906 # server.run() should never return
908 with utils.RunLock():
909 run_summary_required = True
911 err_msg = 'Unknown options: ' + ' '.join(unknown_opts)
913 raise Exception(err_msg)
915 # remove unfilled values
916 opts = {k: v for k, v in list(vars(opts).items()) if v is not None}
918 params = ' '.join(str(e) for e in sys.argv[1:])
919 result = nfvbench_instance.run(opts, params)
920 if 'error_message' in result:
921 raise Exception(result['error_message'])
923 if 'result' in result and result['status']:
924 nfvbench_instance.save(result['result'])
925 nfvbench_instance.prepare_summary(result['result'])
926 except Exception as exc:
927 run_summary_required = True
929 'status': NFVBench.STATUS_ERROR,
930 'error_message': traceback.format_exc()
935 # only send a summary record if there was an actual nfvbench run or
936 # if an error/exception was logged.
937 fluent_logger.send_run_summary(run_summary_required)
940 if __name__ == '__main__':