Update to py38
[nfvbench.git] / nfvbench / nfvbench.py
index 427c94c..891b2bb 100644 (file)
@@ -60,8 +60,8 @@ class NFVBench(object):
         self.config_plugin = config_plugin
         self.factory = factory
         self.notifier = notifier
         self.config_plugin = config_plugin
         self.factory = factory
         self.notifier = notifier
-        self.cred = credentials.Credentials(config.openrc_file, None, False) \
-            if config.openrc_file else None
+        self.cred = credentials.Credentials(config.openrc_file, config.clouds_detail, None, False) \
+            if config.openrc_file or config.clouds_detail else None
         self.chain_runner = None
         self.specs = Specs()
         self.specs.set_openstack_spec(openstack_spec)
         self.chain_runner = None
         self.specs = Specs()
         self.specs.set_openstack_spec(openstack_spec)
@@ -71,11 +71,12 @@ class NFVBench(object):
     def set_notifier(self, notifier):
         self.notifier = notifier
 
     def set_notifier(self, notifier):
         self.notifier = notifier
 
-    def run(self, opts, args):
+    def run(self, opts, args, dry_run=False):
         """This run() method is called for every NFVbench benchmark request.
 
         In CLI mode, this method is called only once per invocation.
         In REST server mode, this is called once per REST POST request
         """This run() method is called for every NFVbench benchmark request.
 
         In CLI mode, this method is called only once per invocation.
         In REST server mode, this is called once per REST POST request
+        On dry_run, show the running config in json format then exit
         """
         status = NFVBench.STATUS_OK
         result = None
         """
         status = NFVBench.STATUS_OK
         result = None
@@ -89,10 +90,16 @@ class NFVBench(object):
             # recalc the running config based on the base config and options for this run
             self._update_config(opts)
 
             # recalc the running config based on the base config and options for this run
             self._update_config(opts)
 
+            if dry_run:
+                print((json.dumps(self.config, sort_keys=True, indent=4)))
+                sys.exit(0)
+
             # check that an empty openrc file (no OpenStack) is only allowed
             # with EXT chain
             # 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:
-                raise Exception("openrc_file in the configuration is required for PVP/PVVP chains")
+            if (not self.config.openrc_file and not self.config.clouds_detail) and \
+                    self.config.service_chain != ChainType.EXT:
+                raise Exception("openrc_file or clouds_detail in the configuration is required"
+                                " for PVP/PVVP chains")
 
             self.specs.set_run_spec(self.config_plugin.get_run_spec(self.config,
                                                                     self.specs.openstack))
 
             self.specs.set_run_spec(self.config_plugin.get_run_spec(self.config,
                                                                     self.specs.openstack))
@@ -185,13 +192,14 @@ class NFVBench(object):
                         'will be created.', path)
                     os.makedirs(path)
                     LOG.info('%s is created.', 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:
+                if not any(isinstance(h, FileHandler) for h in log.getLogger().handlers):
                     log.add_file_logger(opts['log_file'])
                     log.add_file_logger(opts['log_file'])
+                else:
+                    for h in log.getLogger().handlers:
+                        if isinstance(h, FileHandler) and h.baseFilename != opts['log_file']:
+                            # clean log file handler
+                            log.getLogger().removeHandler(h)
+                            log.add_file_logger(opts['log_file'])
 
         self.config.update(opts)
         config = self.config
 
         self.config.update(opts)
         config = self.config
@@ -240,8 +248,7 @@ class NFVBench(object):
             config.cache_size = config.flow_count
 
         # The size must be capped to 10000 (where does this limit come from?)
             config.cache_size = config.flow_count
 
         # The size must be capped to 10000 (where does this limit come from?)
-        if config.cache_size > 10000:
-            config.cache_size = 10000
+        config.cache_size = min(config.cache_size, 10000)
 
         config.duration_sec = float(config.duration_sec)
         config.interval_sec = float(config.interval_sec)
 
         config.duration_sec = float(config.duration_sec)
         config.interval_sec = float(config.interval_sec)
@@ -399,6 +406,12 @@ def _parse_opts_from_cli():
                         action='store_true',
                         help='Use L3 neutron routers to handle traffic')
 
                         action='store_true',
                         help='Use L3 neutron routers to handle traffic')
 
+    parser.add_argument('-garp', '--gratuitous-arp', dest='periodic_gratuitous_arp',
+                        default=None,
+                        action='store_true',
+                        help='Use gratuitous ARP to maintain session between TG '
+                             'and L3 routers to handle traffic')
+
     parser.add_argument('-0', '--no-traffic', dest='no_traffic',
                         default=None,
                         action='store_true',
     parser.add_argument('-0', '--no-traffic', dest='no_traffic',
                         default=None,
                         action='store_true',
@@ -468,10 +481,15 @@ def _parse_opts_from_cli():
                         action='store_true',
                         help='print the default config in yaml format (unedited)')
 
                         action='store_true',
                         help='print the default config in yaml format (unedited)')
 
+    parser.add_argument('--show-pre-config', dest='show_pre_config',
+                        default=None,
+                        action='store_true',
+                        help='print the config in json format (cfg file applied)')
+
     parser.add_argument('--show-config', dest='show_config',
                         default=None,
                         action='store_true',
     parser.add_argument('--show-config', dest='show_config',
                         default=None,
                         action='store_true',
-                        help='print the running config in json format')
+                        help='print the running config in json format (final)')
 
     parser.add_argument('-ss', '--show-summary', dest='summary',
                         action='store',
 
     parser.add_argument('-ss', '--show-summary', dest='summary',
                         action='store',
@@ -514,6 +532,13 @@ def _parse_opts_from_cli():
                              'tagged with given VLAN id(s) or not (given \'no-tag\') '
                              '\'true\': use current vlans; \'false\': disable this mode.')
 
                              'tagged with given VLAN id(s) or not (given \'no-tag\') '
                              '\'true\': use current vlans; \'false\': disable this mode.')
 
+    parser.add_argument('--i40e-mixed', dest='i40e_mixed',
+                        action='store',
+                        default=None,
+                        metavar='<ignore,check,unbind>',
+                        help='TRex behavior when dealing with a i40e network card driver'
+                             ' [ https://trex-tgn.cisco.com/youtrack/issue/trex-528 ]')
+
     parser.add_argument('--user-info', dest='user_info',
                         action='append',
                         metavar='<data>',
     parser.add_argument('--user-info', dest='user_info',
                         action='append',
                         metavar='<data>',
@@ -696,7 +721,7 @@ def main():
             sys.exit(0)
 
         if opts.summary:
             sys.exit(0)
 
         if opts.summary:
-            with open(opts.summary) as json_data:
+            with open(opts.summary, encoding="utf-8") as json_data:
                 result = json.load(json_data)
                 if opts.user_label:
                     result['config']['user_label'] = opts.user_label
                 result = json.load(json_data)
                 if opts.user_label:
                     result['config']['user_label'] = opts.user_label
@@ -711,11 +736,16 @@ def main():
         # dump the contents of the trex log file
         if opts.show_trex_log:
             try:
         # dump the contents of the trex log file
         if opts.show_trex_log:
             try:
-                print(open('/tmp/trex.log').read(), end="")
+                with open('/tmp/trex.log', encoding="utf-8") as trex_log_file:
+                    print(trex_log_file.read(), end="")
             except FileNotFoundError:
                 print("No TRex log file found!")
             sys.exit(0)
 
             except FileNotFoundError:
                 print("No TRex log file found!")
             sys.exit(0)
 
+        # mask info logging in case of further config dump
+        if opts.show_config or opts.show_pre_config:
+            LOG.setLevel(log.logging.WARNING)
+
         config.name = ''
         if opts.config:
             # do not check extra_specs in flavor as it can contain any key/value pairs
         config.name = ''
         if opts.config:
             # do not check extra_specs in flavor as it can contain any key/value pairs
@@ -731,6 +761,11 @@ def main():
                 LOG.info('Loading configuration string: %s', opts.config)
                 config = config_loads(opts.config, config, whitelist_keys)
 
                 LOG.info('Loading configuration string: %s', opts.config)
                 config = config_loads(opts.config, config, whitelist_keys)
 
+        # show current config in json format (before CLI overriding)
+        if opts.show_pre_config:
+            print((json.dumps(config, sort_keys=True, indent=4)))
+            sys.exit(0)
+
         # setup the fluent logger as soon as possible right after the config plugin is called,
         # if there is any logging or result tag is set then initialize the fluent logger
         for fluentd in config.fluentd:
         # setup the fluent logger as soon as possible right after the config plugin is called,
         # if there is any logging or result tag is set then initialize the fluent logger
         for fluentd in config.fluentd:
@@ -850,6 +885,8 @@ def main():
                                 config.service_chain_count = len(vlans[0])
             opts.l2_loopback = None
 
                                 config.service_chain_count = len(vlans[0])
             opts.l2_loopback = None
 
+        if config.i40e_mixed is None:
+            config.i40e_mixed = 'ignore'
         if config.use_sriov_middle_net is None:
             config.use_sriov_middle_net = False
         if opts.use_sriov_middle_net is not None:
         if config.use_sriov_middle_net is None:
             config.use_sriov_middle_net = False
         if opts.use_sriov_middle_net is not None:
@@ -867,11 +904,6 @@ def main():
             if config.service_chain == ChainType.PVVP and config.use_sriov_middle_net:
                 check_physnet("middle", config.internal_networks.middle)
 
             if config.service_chain == ChainType.PVVP and config.use_sriov_middle_net:
                 check_physnet("middle", config.internal_networks.middle)
 
-        # show running config in json format
-        if opts.show_config:
-            print((json.dumps(config, sort_keys=True, indent=4)))
-            sys.exit(0)
-
         # update the config in the config plugin as it might have changed
         # in a copy of the dict (config plugin still holds the original dict)
         config_plugin.set_config(config)
         # update the config in the config plugin as it might have changed
         # in a copy of the dict (config plugin still holds the original dict)
         config_plugin.set_config(config)
@@ -905,6 +937,7 @@ def main():
                 server.run(host=opts.host, port=port)
             # server.run() should never return
         else:
                 server.run(host=opts.host, port=port)
             # server.run() should never return
         else:
+            dry_run = opts.show_config
             with utils.RunLock():
                 run_summary_required = True
                 if unknown_opts:
             with utils.RunLock():
                 run_summary_required = True
                 if unknown_opts:
@@ -916,7 +949,7 @@ def main():
                 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:])
                 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)
+                result = nfvbench_instance.run(opts, params, dry_run=dry_run)
                 if 'error_message' in result:
                     raise Exception(result['error_message'])
 
                 if 'error_message' in result:
                     raise Exception(result['error_message'])