NFVBENCH-180: Add a 'no_e2e_check' option to skip "end to end" connectivity check
[nfvbench.git] / nfvbench / nfvbench.py
1 #!/usr/bin/env python
2 # Copyright 2016 Cisco Systems, Inc.  All rights reserved.
3 #
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
7 #
8 #         http://www.apache.org/licenses/LICENSE-2.0
9 #
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
14 #    under the License.
15 #
16
17 import argparse
18 import copy
19 import datetime
20 import importlib
21 import json
22 import os
23 import sys
24 import traceback
25
26 from attrdict import AttrDict
27 from logging import FileHandler
28 import pbr.version
29 from pkg_resources import resource_string
30
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
38 from . import log
39 from .log import LOG
40 from .nfvbenchd import WebServer
41 from .specs import ChainType
42 from .specs import Specs
43 from .summarizer import NFVBenchSummarizer
44 from . import utils
45
46 fluent_logger = None
47
48
49 class NFVBench(object):
50     """Main class of NFV benchmarking tool."""
51
52     STATUS_OK = 'OK'
53     STATUS_ERROR = 'ERROR'
54
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()
59         self.config = None
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
66         self.specs = Specs()
67         self.specs.set_openstack_spec(openstack_spec)
68         self.vni_ports = []
69         sys.stdout.flush()
70
71     def set_notifier(self, notifier):
72         self.notifier = notifier
73
74     def run(self, opts, args):
75         """This run() method is called for every NFVbench benchmark request.
76
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         """
80         status = NFVBench.STATUS_OK
81         result = None
82         message = ''
83         if fluent_logger:
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()
87         LOG.info(args)
88         try:
89             # recalc the running config based on the base config and options for this run
90             self._update_config(opts)
91
92             # check that an empty openrc file (no OpenStack) is only allowed
93             # with EXT chain
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")
96
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,
100                                             self.cred,
101                                             self.specs,
102                                             self.factory,
103                                             self.notifier)
104             new_frame_sizes = []
105             # make sure that the min frame size is 64
106             min_packet_size = 64
107             for frame_size in self.config.frame_sizes:
108                 try:
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)
115                 except ValueError:
116                     new_frame_sizes.append(frame_size.upper())
117             self.config.frame_sizes = new_frame_sizes
118             result = {
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)),
122                 "benchmarks": {
123                     "network": {
124                         "service_chain": self.chain_runner.run(),
125                         "versions": self.chain_runner.get_version(),
126                     }
127                 }
128             }
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())
133         except Exception:
134             status = NFVBench.STATUS_ERROR
135             message = traceback.format_exc()
136         except KeyboardInterrupt:
137             status = NFVBench.STATUS_ERROR
138             message = traceback.format_exc()
139         finally:
140             if self.chain_runner:
141                 self.chain_runner.close()
142
143         if status == NFVBench.STATUS_OK:
144             # result2 = utils.dict_to_json_dict(result)
145             return {
146                 'status': status,
147                 'result': result
148             }
149         return {
150             'status': status,
151             'error_message': message
152         }
153
154     def prepare_summary(self, result):
155         """Prepare summary of the result to print and send it to logger (eg: fluentd)."""
156         global fluent_logger
157         summary = NFVBenchSummarizer(result, fluent_logger)
158         LOG.info(str(summary))
159
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)
169
170     def _update_config(self, opts):
171         """Recalculate the running config based on the base config and opts.
172
173         Sanity check on the config is done here as well.
174         """
175         self.config = AttrDict(dict(self.base_config))
176         # Update log file handler if needed after a config update (REST mode)
177         if 'log_file' in opts:
178             if opts['log_file']:
179                 (path, _filename) = os.path.split(opts['log_file'])
180                 if not os.path.exists(path):
181                     LOG.warning(
182                         'Path %s does not exist. Please verify root path is shared with host. Path '
183                         'will be created.', path)
184                     os.makedirs(path)
185                     LOG.info('%s is created.', path)
186                 for h in log.getLogger().handlers:
187                     if isinstance(h, FileHandler) and h.baseFilename != opts['log_file']:
188                         # clean log file handler
189                         log.getLogger().removeHandler(h)
190                 # add handler if not existing to avoid duplicates handlers
191                 if len(log.getLogger().handlers) == 1:
192                     log.add_file_logger(opts['log_file'])
193
194         self.config.update(opts)
195         config = self.config
196
197         config.service_chain = config.service_chain.upper()
198         config.service_chain_count = int(config.service_chain_count)
199         if config.l2_loopback:
200             # force the number of chains to be 1 in case of l2 loopback
201             config.service_chain_count = 1
202             config.service_chain = ChainType.EXT
203             config.no_arp = True
204             LOG.info('Running L2 loopback: using EXT chain/no ARP')
205
206         # traffic profile override options
207         if 'frame_sizes' in opts:
208             unidir = False
209             if 'unidir' in opts:
210                 unidir = opts['unidir']
211             override_custom_traffic(config, opts['frame_sizes'], unidir)
212             LOG.info("Frame size has been set to %s for current configuration", opts['frame_sizes'])
213
214         config.flow_count = utils.parse_flow_count(config.flow_count)
215         required_flow_count = config.service_chain_count * 2
216         if config.flow_count < required_flow_count:
217             LOG.info("Flow count %d has been set to minimum value of '%d' "
218                      "for current configuration", config.flow_count,
219                      required_flow_count)
220             config.flow_count = required_flow_count
221
222         if config.flow_count % 2:
223             config.flow_count += 1
224
225         # Possibly adjust the cache size
226         if config.cache_size < 0:
227             config.cache_size = config.flow_count
228
229         # The size must be capped to 10000 (where does this limit come from?)
230         if config.cache_size > 10000:
231             config.cache_size = 10000
232
233         config.duration_sec = float(config.duration_sec)
234         config.interval_sec = float(config.interval_sec)
235         config.pause_sec = float(config.pause_sec)
236
237         if config.traffic is None or not config.traffic:
238             raise Exception("Missing traffic property in configuration")
239
240         if config.openrc_file:
241             config.openrc_file = os.path.expanduser(config.openrc_file)
242             if config.flavor.vcpus < 2:
243                 raise Exception("Flavor vcpus must be >= 2")
244
245         config.ndr_run = (not config.no_traffic and
246                           'ndr' in config.rate.strip().lower().split('_'))
247         config.pdr_run = (not config.no_traffic and
248                           'pdr' in config.rate.strip().lower().split('_'))
249         config.single_run = (not config.no_traffic and
250                              not (config.ndr_run or config.pdr_run))
251
252         config.json_file = config.json if config.json else None
253         if config.json_file:
254             (path, _filename) = os.path.split(config.json)
255             if not os.path.exists(path):
256                 raise Exception('Please provide existing path for storing results in JSON file. '
257                                 'Path used: {path}'.format(path=path))
258
259         config.std_json_path = config.std_json if config.std_json else None
260         if config.std_json_path:
261             if not os.path.exists(config.std_json):
262                 raise Exception('Please provide existing path for storing results in JSON file. '
263                                 'Path used: {path}'.format(path=config.std_json_path))
264
265         # Check that multiqueue is between 1 and 8 (8 is the max allowed by libvirt/qemu)
266         if config.vif_multiqueue_size < 1 or config.vif_multiqueue_size > 8:
267             raise Exception('vif_multiqueue_size (%d) must be in [1..8]' %
268                             config.vif_multiqueue_size)
269
270         # VxLAN and MPLS sanity checks
271         if config.vxlan or config.mpls:
272             if config.vlan_tagging:
273                 config.vlan_tagging = False
274                 config.no_latency_streams = True
275                 config.no_latency_stats = True
276                 config.no_flow_stats = True
277                 LOG.info('VxLAN or MPLS: vlan_tagging forced to False '
278                          '(inner VLAN tagging must be disabled)')
279
280         self.config_plugin.validate_config(config, self.specs.openstack)
281
282
283 def bool_arg(x):
284     """Argument type to be used in parser.add_argument()
285     When a boolean like value is expected to be given
286     """
287     return (str(x).lower() != 'false') \
288         and (str(x).lower() != 'no') \
289         and (str(x).lower() != '0')
290
291
292 def int_arg(x):
293     """Argument type to be used in parser.add_argument()
294     When an integer type value is expected to be given
295     (returns 0 if argument is invalid, hexa accepted)
296     """
297     return int(x, 0)
298
299
300 def _parse_opts_from_cli():
301     parser = argparse.ArgumentParser()
302
303     parser.add_argument('--status', dest='status',
304                         action='store_true',
305                         default=None,
306                         help='Provide NFVbench status')
307
308     parser.add_argument('-c', '--config', dest='config',
309                         action='store',
310                         help='Override default values with a config file or '
311                              'a yaml/json config string',
312                         metavar='<file_name_or_yaml>')
313
314     parser.add_argument('--server', dest='server',
315                         default=None,
316                         action='store_true',
317                         help='Run nfvbench in server mode')
318
319     parser.add_argument('--host', dest='host',
320                         action='store',
321                         default='0.0.0.0',
322                         help='Host IP address on which server will be listening (default 0.0.0.0)')
323
324     parser.add_argument('-p', '--port', dest='port',
325                         action='store',
326                         default=7555,
327                         help='Port on which server will be listening (default 7555)')
328
329     parser.add_argument('-sc', '--service-chain', dest='service_chain',
330                         choices=ChainType.names,
331                         action='store',
332                         help='Service chain to run')
333
334     parser.add_argument('-scc', '--service-chain-count', dest='service_chain_count',
335                         action='store',
336                         help='Set number of service chains to run',
337                         metavar='<service_chain_count>')
338
339     parser.add_argument('-fc', '--flow-count', dest='flow_count',
340                         action='store',
341                         help='Set number of total flows for all chains and all directions',
342                         metavar='<flow_count>')
343
344     parser.add_argument('--rate', dest='rate',
345                         action='store',
346                         help='Specify rate in pps, bps or %% as total for all directions',
347                         metavar='<rate>')
348
349     parser.add_argument('--duration', dest='duration_sec',
350                         action='store',
351                         help='Set duration to run traffic generator (in seconds)',
352                         metavar='<duration_sec>')
353
354     parser.add_argument('--interval', dest='interval_sec',
355                         action='store',
356                         help='Set interval to record traffic generator stats (in seconds)',
357                         metavar='<interval_sec>')
358
359     parser.add_argument('--inter-node', dest='inter_node',
360                         default=None,
361                         action='store_true',
362                         help='(deprecated)')
363
364     parser.add_argument('--sriov', dest='sriov',
365                         default=None,
366                         action='store_true',
367                         help='Use SRIOV (no vswitch - requires SRIOV support in compute nodes)')
368
369     parser.add_argument('--use-sriov-middle-net', dest='use_sriov_middle_net',
370                         default=None,
371                         action='store_true',
372                         help='Use SRIOV to handle the middle network traffic '
373                              '(PVVP with SRIOV only)')
374
375     parser.add_argument('-d', '--debug', dest='debug',
376                         action='store_true',
377                         default=None,
378                         help='print debug messages (verbose)')
379
380     parser.add_argument('-g', '--traffic-gen', dest='generator_profile',
381                         action='store',
382                         help='Traffic generator profile to use')
383
384     parser.add_argument('-l3', '--l3-router', dest='l3_router',
385                         default=None,
386                         action='store_true',
387                         help='Use L3 neutron routers to handle traffic')
388
389     parser.add_argument('-0', '--no-traffic', dest='no_traffic',
390                         default=None,
391                         action='store_true',
392                         help='Check config and connectivity only - do not generate traffic')
393
394     parser.add_argument('--no-arp', dest='no_arp',
395                         default=None,
396                         action='store_true',
397                         help='Do not use ARP to find MAC addresses, '
398                              'instead use values in config file')
399
400     parser.add_argument('--loop-vm-arp', dest='loop_vm_arp',
401                         default=None,
402                         action='store_true',
403                         help='Use ARP to find MAC addresses '
404                              'instead of using values from TRex ports (VPP forwarder only)')
405
406     parser.add_argument('--no-vswitch-access', dest='no_vswitch_access',
407                         default=None,
408                         action='store_true',
409                         help='Skip vswitch configuration and retrieving of stats')
410
411     parser.add_argument('--vxlan', dest='vxlan',
412                         default=None,
413                         action='store_true',
414                         help='Enable VxLan encapsulation')
415
416     parser.add_argument('--mpls', dest='mpls',
417                         default=None,
418                         action='store_true',
419                         help='Enable MPLS encapsulation')
420
421     parser.add_argument('--no-cleanup', dest='no_cleanup',
422                         default=None,
423                         action='store_true',
424                         help='no cleanup after run')
425
426     parser.add_argument('--cleanup', dest='cleanup',
427                         default=None,
428                         action='store_true',
429                         help='Cleanup NFVbench resources (prompt to confirm)')
430
431     parser.add_argument('--force-cleanup', dest='force_cleanup',
432                         default=None,
433                         action='store_true',
434                         help='Cleanup NFVbench resources (do not prompt)')
435
436     parser.add_argument('--restart', dest='restart',
437                         default=None,
438                         action='store_true',
439                         help='Restart TRex server')
440
441     parser.add_argument('--json', dest='json',
442                         action='store',
443                         help='store results in json format file',
444                         metavar='<path>/<filename>')
445
446     parser.add_argument('--std-json', dest='std_json',
447                         action='store',
448                         help='store results in json format file with nfvbench standard filename: '
449                              '<service-chain-type>-<service-chain-count>-<flow-count>'
450                              '-<packet-sizes>.json',
451                         metavar='<path>')
452
453     parser.add_argument('--show-default-config', dest='show_default_config',
454                         default=None,
455                         action='store_true',
456                         help='print the default config in yaml format (unedited)')
457
458     parser.add_argument('--show-config', dest='show_config',
459                         default=None,
460                         action='store_true',
461                         help='print the running config in json format')
462
463     parser.add_argument('-ss', '--show-summary', dest='summary',
464                         action='store',
465                         help='Show summary from nfvbench json file',
466                         metavar='<json>')
467
468     parser.add_argument('-v', '--version', dest='version',
469                         default=None,
470                         action='store_true',
471                         help='Show version')
472
473     parser.add_argument('-fs', '--frame-size', dest='frame_sizes',
474                         action='append',
475                         help='Override traffic profile frame sizes',
476                         metavar='<frame_size_bytes or IMIX>')
477
478     parser.add_argument('--unidir', dest='unidir',
479                         action='store_true',
480                         default=None,
481                         help='Override traffic profile direction (requires -fs)')
482
483     parser.add_argument('--log-file', '--logfile', dest='log_file',
484                         action='store',
485                         help='Filename for saving logs',
486                         metavar='<log_file>')
487
488     parser.add_argument('--user-label', '--userlabel', dest='user_label',
489                         action='store',
490                         help='Custom label for performance records')
491
492     parser.add_argument('--hypervisor', dest='hypervisor',
493                         action='store',
494                         metavar='<hypervisor name>',
495                         help='Where chains must run ("compute", "az:", "az:compute")')
496
497     parser.add_argument('--l2-loopback', '--l2loopback', dest='l2_loopback',
498                         action='store',
499                         metavar='<vlan>',
500                         help='Port to port or port to switch to port L2 loopback with VLAN id')
501
502     parser.add_argument('--user-info', dest='user_info',
503                         action='store',
504                         metavar='<data>',
505                         help='Custom data to be included as is in the json report config branch - '
506                                + ' example, pay attention! no space: '
507                                + '--user-info=\'{"status":"explore","description":'
508                                + '{"target":"lab","ok":true,"version":2020}}\'')
509
510     parser.add_argument('--vlan-tagging', dest='vlan_tagging',
511                         type=bool_arg,
512                         metavar='<boolean>',
513                         action='store',
514                         default=None,
515                         help='Override the NFVbench \'vlan_tagging\' parameter')
516
517     parser.add_argument('--intf-speed', dest='intf_speed',
518                         metavar='<speed>',
519                         action='store',
520                         default=None,
521                         help='Override the NFVbench \'intf_speed\' '
522                                 + 'parameter (e.g. 10Gbps, auto, 16.72Gbps)')
523
524     parser.add_argument('--cores', dest='cores',
525                         type=int_arg,
526                         metavar='<number>',
527                         action='store',
528                         default=None,
529                         help='Override the T-Rex \'cores\' parameter')
530
531     parser.add_argument('--cache-size', dest='cache_size',
532                         type=int_arg,
533                         metavar='<size>',
534                         action='store',
535                         default='0',
536                         help='Specify the FE cache size (default: 0, flow-count if < 0)')
537
538     parser.add_argument('--service-mode', dest='service_mode',
539                         action='store_true',
540                         default=False,
541                         help='Enable T-Rex service mode (for debugging purpose)')
542
543     parser.add_argument('--no-e2e-check', dest='no_e2e_check',
544                         action='store_true',
545                         default=False,
546                         help='Skip "end to end" connectivity check (on test purpose)')
547
548     parser.add_argument('--no-flow-stats', dest='no_flow_stats',
549                         action='store_true',
550                         default=False,
551                         help='Disable additional flow stats (on high load traffic)')
552
553     parser.add_argument('--no-latency-stats', dest='no_latency_stats',
554                         action='store_true',
555                         default=False,
556                         help='Disable flow stats for latency traffic')
557
558     parser.add_argument('--no-latency-streams', dest='no_latency_streams',
559                         action='store_true',
560                         default=False,
561                         help='Disable latency measurements (no streams)')
562
563     parser.add_argument('--debug-mask', dest='debug_mask',
564                         type=int_arg,
565                         metavar='<mask>',
566                         action='store',
567                         default='0x00000000',
568                         help='General purpose register (debugging flags), '
569                                 + 'the hexadecimal notation (0x...) is accepted.'
570                                 + 'Designed for development needs.')
571
572     opts, unknown_opts = parser.parse_known_args()
573     return opts, unknown_opts
574
575
576 def load_default_config():
577     default_cfg = resource_string(__name__, "cfg.default.yaml")
578     config = config_loads(default_cfg)
579     config.name = '(built-in default config)'
580     return config, default_cfg
581
582
583 def override_custom_traffic(config, frame_sizes, unidir):
584     """Override the traffic profiles with a custom one."""
585     if frame_sizes is not None:
586         traffic_profile_name = "custom_traffic_profile"
587         config.traffic_profile = [
588             {
589                 "l2frame_size": frame_sizes,
590                 "name": traffic_profile_name
591             }
592         ]
593     else:
594         traffic_profile_name = config.traffic["profile"]
595
596     bidirectional = config.traffic['bidirectional'] if unidir is None else not unidir
597     config.traffic = {
598         "bidirectional": bidirectional,
599         "profile": traffic_profile_name
600     }
601
602
603 def check_physnet(name, netattrs):
604     if not netattrs.physical_network:
605         raise Exception("SRIOV requires physical_network to be specified for the {n} network"
606                         .format(n=name))
607     if not netattrs.segmentation_id:
608         raise Exception("SRIOV requires segmentation_id to be specified for the {n} network"
609                         .format(n=name))
610
611 def status_cleanup(config, cleanup, force_cleanup):
612     LOG.info('Version: %s', pbr.version.VersionInfo('nfvbench').version_string_with_vcs())
613     # check if another run is pending
614     ret_code = 0
615     try:
616         with utils.RunLock():
617             LOG.info('Status: idle')
618     except Exception:
619         LOG.info('Status: busy (run pending)')
620         ret_code = 1
621     # check nfvbench resources
622     if config.openrc_file and config.service_chain != ChainType.EXT:
623         cleaner = Cleaner(config)
624         count = cleaner.show_resources()
625         if count and (cleanup or force_cleanup):
626             cleaner.clean(not force_cleanup)
627     sys.exit(ret_code)
628
629 def main():
630     global fluent_logger
631     run_summary_required = False
632     try:
633         log.setup()
634         # load default config file
635         config, default_cfg = load_default_config()
636         # create factory for platform specific classes
637         try:
638             factory_module = importlib.import_module(config['factory_module'])
639             factory = getattr(factory_module, config['factory_class'])()
640         except AttributeError:
641             raise Exception("Requested factory module '{m}' or class '{c}' was not found."
642                             .format(m=config['factory_module'],
643                                     c=config['factory_class'])) from AttributeError
644         # create config plugin for this platform
645         config_plugin = factory.get_config_plugin_class()(config)
646         config = config_plugin.get_config()
647
648         opts, unknown_opts = _parse_opts_from_cli()
649         log.set_level(debug=opts.debug)
650
651         if opts.version:
652             print((pbr.version.VersionInfo('nfvbench').version_string_with_vcs()))
653             sys.exit(0)
654
655         if opts.summary:
656             with open(opts.summary) as json_data:
657                 result = json.load(json_data)
658                 if opts.user_label:
659                     result['config']['user_label'] = opts.user_label
660                 print((NFVBenchSummarizer(result, fluent_logger)))
661             sys.exit(0)
662
663         # show default config in text/yaml format
664         if opts.show_default_config:
665             print((default_cfg.decode("utf-8")))
666             sys.exit(0)
667
668         config.name = ''
669         if opts.config:
670             # do not check extra_specs in flavor as it can contain any key/value pairs
671             # the same principle applies also to the optional user_info open property
672             whitelist_keys = ['extra_specs', 'user_info']
673             # override default config options with start config at path parsed from CLI
674             # check if it is an inline yaml/json config or a file name
675             if os.path.isfile(opts.config):
676                 LOG.info('Loading configuration file: %s', opts.config)
677                 config = config_load(opts.config, config, whitelist_keys)
678                 config.name = os.path.basename(opts.config)
679             else:
680                 LOG.info('Loading configuration string: %s', opts.config)
681                 config = config_loads(opts.config, config, whitelist_keys)
682
683         # setup the fluent logger as soon as possible right after the config plugin is called,
684         # if there is any logging or result tag is set then initialize the fluent logger
685         for fluentd in config.fluentd:
686             if fluentd.logging_tag or fluentd.result_tag:
687                 fluent_logger = FluentLogHandler(config.fluentd)
688                 LOG.addHandler(fluent_logger)
689                 break
690
691         # convert 'user_info' opt from json string to dictionnary
692         # and merge the result with the current config dictionnary
693         if opts.user_info:
694             opts.user_info = json.loads(opts.user_info)
695             if config.user_info:
696                 config.user_info = config.user_info + opts.user_info
697             else:
698                 config.user_info = opts.user_info
699             # hide the option to further _update_config()
700             opts.user_info = None
701
702         # traffic profile override options
703         override_custom_traffic(config, opts.frame_sizes, opts.unidir)
704
705         # copy over cli options that are used in config
706         config.generator_profile = opts.generator_profile
707         if opts.sriov:
708             config.sriov = True
709         if opts.log_file:
710             config.log_file = opts.log_file
711         if opts.service_chain:
712             config.service_chain = opts.service_chain
713         if opts.service_chain_count:
714             config.service_chain_count = opts.service_chain_count
715         if opts.no_vswitch_access:
716             config.no_vswitch_access = opts.no_vswitch_access
717         if opts.hypervisor:
718             # can be any of 'comp1', 'nova:', 'nova:comp1'
719             config.compute_nodes = opts.hypervisor
720         if opts.vxlan:
721             config.vxlan = True
722         if opts.mpls:
723             config.mpls = True
724         if opts.restart:
725             config.restart = True
726         if opts.service_mode:
727             config.service_mode = True
728         if opts.no_flow_stats:
729             config.no_flow_stats = True
730         if opts.no_latency_stats:
731             config.no_latency_stats = True
732         if opts.no_latency_streams:
733             config.no_latency_streams = True
734         # port to port loopback (direct or through switch)
735         if opts.l2_loopback:
736             config.l2_loopback = True
737             if config.service_chain != ChainType.EXT:
738                 LOG.info('Changing service chain type to EXT')
739                 config.service_chain = ChainType.EXT
740             if not config.no_arp:
741                 LOG.info('Disabling ARP')
742                 config.no_arp = True
743             config.vlans = [int(opts.l2_loopback), int(opts.l2_loopback)]
744             LOG.info('Running L2 loopback: using EXT chain/no ARP')
745
746         if opts.use_sriov_middle_net:
747             if (not config.sriov) or (config.service_chain != ChainType.PVVP):
748                 raise Exception("--use-sriov-middle-net is only valid for PVVP with SRIOV")
749             config.use_sriov_middle_net = True
750
751         if config.sriov and config.service_chain != ChainType.EXT:
752             # if sriov is requested (does not apply to ext chains)
753             # make sure the physnet names are specified
754             check_physnet("left", config.internal_networks.left)
755             check_physnet("right", config.internal_networks.right)
756             if config.service_chain == ChainType.PVVP and config.use_sriov_middle_net:
757                 check_physnet("middle", config.internal_networks.middle)
758
759         # show running config in json format
760         if opts.show_config:
761             print((json.dumps(config, sort_keys=True, indent=4)))
762             sys.exit(0)
763
764         # update the config in the config plugin as it might have changed
765         # in a copy of the dict (config plugin still holds the original dict)
766         config_plugin.set_config(config)
767
768         if opts.status or opts.cleanup or opts.force_cleanup:
769             status_cleanup(config, opts.cleanup, opts.force_cleanup)
770
771         # add file log if requested
772         if config.log_file:
773             log.add_file_logger(config.log_file)
774
775         openstack_spec = config_plugin.get_openstack_spec() if config.openrc_file \
776             else None
777
778         nfvbench_instance = NFVBench(config, openstack_spec, config_plugin, factory)
779
780         if opts.server:
781             server = WebServer(nfvbench_instance, fluent_logger)
782             try:
783                 port = int(opts.port)
784             except ValueError:
785                 server.run(host=opts.host)
786             else:
787                 server.run(host=opts.host, port=port)
788             # server.run() should never return
789         else:
790             with utils.RunLock():
791                 run_summary_required = True
792                 if unknown_opts:
793                     err_msg = 'Unknown options: ' + ' '.join(unknown_opts)
794                     LOG.error(err_msg)
795                     raise Exception(err_msg)
796
797                 # remove unfilled values
798                 opts = {k: v for k, v in list(vars(opts).items()) if v is not None}
799                 # get CLI args
800                 params = ' '.join(str(e) for e in sys.argv[1:])
801                 result = nfvbench_instance.run(opts, params)
802                 if 'error_message' in result:
803                     raise Exception(result['error_message'])
804
805                 if 'result' in result and result['status']:
806                     nfvbench_instance.save(result['result'])
807                     nfvbench_instance.prepare_summary(result['result'])
808     except Exception as exc:
809         run_summary_required = True
810         LOG.error({
811             'status': NFVBench.STATUS_ERROR,
812             'error_message': traceback.format_exc()
813         })
814         print((str(exc)))
815     finally:
816         if fluent_logger:
817             # only send a summary record if there was an actual nfvbench run or
818             # if an error/exception was logged.
819             fluent_logger.send_run_summary(run_summary_required)
820
821
822 if __name__ == '__main__':
823     main()