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