NFVBENCH-177: Add a config item 'user_info' and theoretical max rate value
[nfvbench.git] / nfvbench / nfvbench.py
index 4a2a285..9b8b3d1 100644 (file)
@@ -24,23 +24,24 @@ import sys
 import traceback
 
 from attrdict import AttrDict
+from logging import FileHandler
 import pbr.version
 from pkg_resources import resource_string
 
-from __init__ import __version__
-from chain_runner import ChainRunner
-from cleanup import Cleaner
-from config import config_load
-from config import config_loads
-import credentials as credentials
-from fluentd import FluentLogHandler
-import log
-from log import LOG
-from nfvbenchd import WebServer
-from specs import ChainType
-from specs import Specs
-from summarizer import NFVBenchSummarizer
-import utils
+from .__init__ import __version__
+from .chain_runner import ChainRunner
+from .cleanup import Cleaner
+from .config import config_load
+from .config import config_loads
+from . import credentials
+from .fluentd import FluentLogHandler
+from . import log
+from .log import LOG
+from .nfvbenchd import WebServer
+from .specs import ChainType
+from .specs import Specs
+from .summarizer import NFVBenchSummarizer
+from . import utils
 
 fluent_logger = None
 
@@ -87,7 +88,8 @@ class NFVBench(object):
         try:
             # recalc the running config based on the base config and options for this run
             self._update_config(opts)
-
+            if int(self.config.cache_size) < 0:
+                self.config.cache_size = self.config.flow_count
             # check that an empty openrc file (no OpenStack) is only allowed
             # with EXT chain
             if not self.config.openrc_file and self.config.service_chain != ChainType.EXT:
@@ -172,6 +174,24 @@ class NFVBench(object):
         Sanity check on the config is done here as well.
         """
         self.config = AttrDict(dict(self.base_config))
+        # Update log file handler if needed after a config update (REST mode)
+        if 'log_file' in opts:
+            if opts['log_file']:
+                (path, _filename) = os.path.split(opts['log_file'])
+                if not os.path.exists(path):
+                    LOG.warning(
+                        'Path %s does not exist. Please verify root path is shared with host. Path '
+                        'will be created.', path)
+                    os.makedirs(path)
+                    LOG.info('%s is created.', path)
+                for h in log.getLogger().handlers:
+                    if isinstance(h, FileHandler) and h.baseFilename != opts['log_file']:
+                        # clean log file handler
+                        log.getLogger().removeHandler(h)
+                # add handler if not existing to avoid duplicates handlers
+                if len(log.getLogger().handlers) == 1:
+                    log.add_file_logger(opts['log_file'])
+
         self.config.update(opts)
         config = self.config
 
@@ -183,6 +203,15 @@ class NFVBench(object):
             config.service_chain = ChainType.EXT
             config.no_arp = True
             LOG.info('Running L2 loopback: using EXT chain/no ARP')
+
+        # traffic profile override options
+        if 'frame_sizes' in opts:
+            unidir = False
+            if 'unidir' in opts:
+                unidir = opts['unidir']
+            override_custom_traffic(config, opts['frame_sizes'], unidir)
+            LOG.info("Frame size has been set to %s for current configuration", opts['frame_sizes'])
+
         config.flow_count = utils.parse_flow_count(config.flow_count)
         required_flow_count = config.service_chain_count * 2
         if config.flow_count < required_flow_count:
@@ -232,16 +261,36 @@ class NFVBench(object):
             raise Exception('vif_multiqueue_size (%d) must be in [1..8]' %
                             config.vif_multiqueue_size)
 
-        # VxLAN sanity checks
-        if config.vxlan:
+        # VxLAN and MPLS sanity checks
+        if config.vxlan or config.mpls:
             if config.vlan_tagging:
                 config.vlan_tagging = False
-                LOG.info('VxLAN: vlan_tagging forced to False '
+                config.no_latency_streams = True
+                config.no_latency_stats = True
+                config.no_flow_stats = True
+                LOG.info('VxLAN or MPLS: vlan_tagging forced to False '
                          '(inner VLAN tagging must be disabled)')
 
         self.config_plugin.validate_config(config, self.specs.openstack)
 
 
+def bool_arg(x):
+    """Argument type to be used in parser.add_argument()
+    When a boolean like value is expected to be given
+    """
+    return (str(x).lower() != 'false') \
+        and (str(x).lower() != 'no') \
+        and (str(x).lower() != '0')
+
+
+def int_arg(x):
+    """Argument type to be used in parser.add_argument()
+    When an integer type value is expected to be given
+    (returns 0 if argument is invalid, hexa accepted)
+    """
+    return int(x, 0)
+
+
 def _parse_opts_from_cli():
     parser = argparse.ArgumentParser()
 
@@ -342,6 +391,12 @@ def _parse_opts_from_cli():
                         help='Do not use ARP to find MAC addresses, '
                              'instead use values in config file')
 
+    parser.add_argument('--loop-vm-arp', dest='loop_vm_arp',
+                        default=None,
+                        action='store_true',
+                        help='Use ARP to find MAC addresses '
+                             'instead of using values from TRex ports (VPP forwarder only)')
+
     parser.add_argument('--no-vswitch-access', dest='no_vswitch_access',
                         default=None,
                         action='store_true',
@@ -352,6 +407,11 @@ def _parse_opts_from_cli():
                         action='store_true',
                         help='Enable VxLan encapsulation')
 
+    parser.add_argument('--mpls', dest='mpls',
+                        default=None,
+                        action='store_true',
+                        help='Enable MPLS encapsulation')
+
     parser.add_argument('--no-cleanup', dest='no_cleanup',
                         default=None,
                         action='store_true',
@@ -433,6 +493,64 @@ def _parse_opts_from_cli():
                         metavar='<vlan>',
                         help='Port to port or port to switch to port L2 loopback with VLAN id')
 
+    """Option to allow for passing custom information to results post-processing"""
+    parser.add_argument('--user-info', dest='user_info',
+                        action='store',
+                        metavar='<data>',
+                        help='Custom data to be included as is in the json report config branch - '
+                                + ' example, pay attention! no space: '
+                                + '--user-info=\'{"status":"explore","description":{"target":"lab"'
+                                + ',"ok":true,"version":2020}\'')
+
+    """Option to allow for overriding the NFVbench 'vlan_tagging' option"""
+    parser.add_argument('--vlan-tagging', dest='vlan_tagging',
+                        type=bool_arg,
+                        metavar='<boolean>',
+                        action='store',
+                        default=None,
+                        help='Override the NFVbench \'vlan_tagging\' parameter')
+
+    """Option to allow for overriding the T-Rex 'intf_speed' parameter"""
+    parser.add_argument('--intf-speed', dest='intf_speed',
+                        metavar='<speed>',
+                        action='store',
+                        default=None,
+                        help='Override the NFVbench \'intf_speed\' parameter '
+                                + '(e.g. 10Gbps, auto, 16.72Gbps)')
+
+    """Option to allow for overriding the T-Rex 'cores' parameter"""
+    parser.add_argument('--cores', dest='cores',
+                        type=int_arg,
+                        metavar='<number>',
+                        action='store',
+                        default=None,
+                        help='Override the T-Rex \'cores\' parameter')
+
+    parser.add_argument('--cache-size', dest='cache_size',
+                        action='store',
+                        default='0',
+                        help='Specify the FE cache size (default: 0, flow-count if < 0)')
+
+    parser.add_argument('--service-mode', dest='service_mode',
+                        action='store_true',
+                        default=False,
+                        help='Enable T-Rex service mode for debugging only')
+
+    parser.add_argument('--no-flow-stats', dest='no_flow_stats',
+                        action='store_true',
+                        default=False,
+                        help='Disable extra flow stats (on high load traffic)')
+
+    parser.add_argument('--no-latency-stats', dest='no_latency_stats',
+                        action='store_true',
+                        default=False,
+                        help='Disable flow stats for latency traffic')
+
+    parser.add_argument('--no-latency-streams', dest='no_latency_streams',
+                        action='store_true',
+                        default=False,
+                        help='Disable latency measurements (no streams)')
+
     opts, unknown_opts = parser.parse_known_args()
     return opts, unknown_opts
 
@@ -503,7 +621,8 @@ def main():
             factory = getattr(factory_module, config['factory_class'])()
         except AttributeError:
             raise Exception("Requested factory module '{m}' or class '{c}' was not found."
-                            .format(m=config['factory_module'], c=config['factory_class']))
+                            .format(m=config['factory_module'],
+                                    c=config['factory_class'])) from AttributeError
         # create config plugin for this platform
         config_plugin = factory.get_config_plugin_class()(config)
         config = config_plugin.get_config()
@@ -512,7 +631,7 @@ def main():
         log.set_level(debug=opts.debug)
 
         if opts.version:
-            print pbr.version.VersionInfo('nfvbench').version_string_with_vcs()
+            print((pbr.version.VersionInfo('nfvbench').version_string_with_vcs()))
             sys.exit(0)
 
         if opts.summary:
@@ -520,18 +639,19 @@ def main():
                 result = json.load(json_data)
                 if opts.user_label:
                     result['config']['user_label'] = opts.user_label
-                print NFVBenchSummarizer(result, fluent_logger)
+                print((NFVBenchSummarizer(result, fluent_logger)))
             sys.exit(0)
 
         # show default config in text/yaml format
         if opts.show_default_config:
-            print default_cfg
+            print((default_cfg.decode("utf-8")))
             sys.exit(0)
 
         config.name = ''
         if opts.config:
             # do not check extra_specs in flavor as it can contain any key/value pairs
-            whitelist_keys = ['extra_specs']
+            # the same principle applies also to the optional user_info open property
+            whitelist_keys = ['extra_specs', 'user_info']
             # override default config options with start config at path parsed from CLI
             # check if it is an inline yaml/json config or a file name
             if os.path.isfile(opts.config):
@@ -550,6 +670,17 @@ def main():
                 LOG.addHandler(fluent_logger)
                 break
 
+        # convert 'user_info' opt from json string to dictionnary
+        # and merge the result with the current config dictionnary
+        if opts.user_info:
+            opts.user_info = json.loads(opts.user_info)
+            if config.user_info:
+                config.user_info = config.user_info + opts.user_info
+            else:
+                config.user_info = opts.user_info
+            # hide the option to further _update_config()
+            opts.user_info = None
+
         # traffic profile override options
         override_custom_traffic(config, opts.frame_sizes, opts.unidir)
 
@@ -570,8 +701,18 @@ def main():
             config.compute_nodes = opts.hypervisor
         if opts.vxlan:
             config.vxlan = True
+        if opts.mpls:
+            config.mpls = True
         if opts.restart:
             config.restart = True
+        if opts.service_mode:
+            config.service_mode = True
+        if opts.no_flow_stats:
+            config.no_flow_stats = True
+        if opts.no_latency_stats:
+            config.no_latency_stats = True
+        if opts.no_latency_streams:
+            config.no_latency_streams = True
         # port to port loopback (direct or through switch)
         if opts.l2_loopback:
             config.l2_loopback = True
@@ -599,7 +740,7 @@ def main():
 
         # show running config in json format
         if opts.show_config:
-            print json.dumps(config, sort_keys=True, indent=4)
+            print((json.dumps(config, sort_keys=True, indent=4)))
             sys.exit(0)
 
         # update the config in the config plugin as it might have changed
@@ -636,7 +777,7 @@ def main():
                     raise Exception(err_msg)
 
                 # remove unfilled values
-                opts = {k: v for k, v in vars(opts).iteritems() if v is not None}
+                opts = {k: v for k, v in list(vars(opts).items()) if v is not None}
                 # get CLI args
                 params = ' '.join(str(e) for e in sys.argv[1:])
                 result = nfvbench_instance.run(opts, params)
@@ -652,7 +793,7 @@ def main():
             'status': NFVBench.STATUS_ERROR,
             'error_message': traceback.format_exc()
         })
-        print str(exc)
+        print((str(exc)))
     finally:
         if fluent_logger:
             # only send a summary record if there was an actual nfvbench run or