[NFVBENCH-168] Improve config properties managed after a REST call
[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 _parse_opts_from_cli():
278     parser = argparse.ArgumentParser()
279
280     parser.add_argument('--status', dest='status',
281                         action='store_true',
282                         default=None,
283                         help='Provide NFVbench status')
284
285     parser.add_argument('-c', '--config', dest='config',
286                         action='store',
287                         help='Override default values with a config file or '
288                              'a yaml/json config string',
289                         metavar='<file_name_or_yaml>')
290
291     parser.add_argument('--server', dest='server',
292                         default=None,
293                         action='store_true',
294                         help='Run nfvbench in server mode')
295
296     parser.add_argument('--host', dest='host',
297                         action='store',
298                         default='0.0.0.0',
299                         help='Host IP address on which server will be listening (default 0.0.0.0)')
300
301     parser.add_argument('-p', '--port', dest='port',
302                         action='store',
303                         default=7555,
304                         help='Port on which server will be listening (default 7555)')
305
306     parser.add_argument('-sc', '--service-chain', dest='service_chain',
307                         choices=ChainType.names,
308                         action='store',
309                         help='Service chain to run')
310
311     parser.add_argument('-scc', '--service-chain-count', dest='service_chain_count',
312                         action='store',
313                         help='Set number of service chains to run',
314                         metavar='<service_chain_count>')
315
316     parser.add_argument('-fc', '--flow-count', dest='flow_count',
317                         action='store',
318                         help='Set number of total flows for all chains and all directions',
319                         metavar='<flow_count>')
320
321     parser.add_argument('--rate', dest='rate',
322                         action='store',
323                         help='Specify rate in pps, bps or %% as total for all directions',
324                         metavar='<rate>')
325
326     parser.add_argument('--duration', dest='duration_sec',
327                         action='store',
328                         help='Set duration to run traffic generator (in seconds)',
329                         metavar='<duration_sec>')
330
331     parser.add_argument('--interval', dest='interval_sec',
332                         action='store',
333                         help='Set interval to record traffic generator stats (in seconds)',
334                         metavar='<interval_sec>')
335
336     parser.add_argument('--inter-node', dest='inter_node',
337                         default=None,
338                         action='store_true',
339                         help='(deprecated)')
340
341     parser.add_argument('--sriov', dest='sriov',
342                         default=None,
343                         action='store_true',
344                         help='Use SRIOV (no vswitch - requires SRIOV support in compute nodes)')
345
346     parser.add_argument('--use-sriov-middle-net', dest='use_sriov_middle_net',
347                         default=None,
348                         action='store_true',
349                         help='Use SRIOV to handle the middle network traffic '
350                              '(PVVP with SRIOV only)')
351
352     parser.add_argument('-d', '--debug', dest='debug',
353                         action='store_true',
354                         default=None,
355                         help='print debug messages (verbose)')
356
357     parser.add_argument('-g', '--traffic-gen', dest='generator_profile',
358                         action='store',
359                         help='Traffic generator profile to use')
360
361     parser.add_argument('-l3', '--l3-router', dest='l3_router',
362                         default=None,
363                         action='store_true',
364                         help='Use L3 neutron routers to handle traffic')
365
366     parser.add_argument('-0', '--no-traffic', dest='no_traffic',
367                         default=None,
368                         action='store_true',
369                         help='Check config and connectivity only - do not generate traffic')
370
371     parser.add_argument('--no-arp', dest='no_arp',
372                         default=None,
373                         action='store_true',
374                         help='Do not use ARP to find MAC addresses, '
375                              'instead use values in config file')
376
377     parser.add_argument('--loop-vm-arp', dest='loop_vm_arp',
378                         default=None,
379                         action='store_true',
380                         help='Use ARP to find MAC addresses '
381                              'instead of using values from TRex ports (VPP forwarder only)')
382
383     parser.add_argument('--no-vswitch-access', dest='no_vswitch_access',
384                         default=None,
385                         action='store_true',
386                         help='Skip vswitch configuration and retrieving of stats')
387
388     parser.add_argument('--vxlan', dest='vxlan',
389                         default=None,
390                         action='store_true',
391                         help='Enable VxLan encapsulation')
392
393     parser.add_argument('--mpls', dest='mpls',
394                         default=None,
395                         action='store_true',
396                         help='Enable MPLS encapsulation')
397
398     parser.add_argument('--no-cleanup', dest='no_cleanup',
399                         default=None,
400                         action='store_true',
401                         help='no cleanup after run')
402
403     parser.add_argument('--cleanup', dest='cleanup',
404                         default=None,
405                         action='store_true',
406                         help='Cleanup NFVbench resources (prompt to confirm)')
407
408     parser.add_argument('--force-cleanup', dest='force_cleanup',
409                         default=None,
410                         action='store_true',
411                         help='Cleanup NFVbench resources (do not prompt)')
412
413     parser.add_argument('--restart', dest='restart',
414                         default=None,
415                         action='store_true',
416                         help='Restart TRex server')
417
418     parser.add_argument('--json', dest='json',
419                         action='store',
420                         help='store results in json format file',
421                         metavar='<path>/<filename>')
422
423     parser.add_argument('--std-json', dest='std_json',
424                         action='store',
425                         help='store results in json format file with nfvbench standard filename: '
426                              '<service-chain-type>-<service-chain-count>-<flow-count>'
427                              '-<packet-sizes>.json',
428                         metavar='<path>')
429
430     parser.add_argument('--show-default-config', dest='show_default_config',
431                         default=None,
432                         action='store_true',
433                         help='print the default config in yaml format (unedited)')
434
435     parser.add_argument('--show-config', dest='show_config',
436                         default=None,
437                         action='store_true',
438                         help='print the running config in json format')
439
440     parser.add_argument('-ss', '--show-summary', dest='summary',
441                         action='store',
442                         help='Show summary from nfvbench json file',
443                         metavar='<json>')
444
445     parser.add_argument('-v', '--version', dest='version',
446                         default=None,
447                         action='store_true',
448                         help='Show version')
449
450     parser.add_argument('-fs', '--frame-size', dest='frame_sizes',
451                         action='append',
452                         help='Override traffic profile frame sizes',
453                         metavar='<frame_size_bytes or IMIX>')
454
455     parser.add_argument('--unidir', dest='unidir',
456                         action='store_true',
457                         default=None,
458                         help='Override traffic profile direction (requires -fs)')
459
460     parser.add_argument('--log-file', '--logfile', dest='log_file',
461                         action='store',
462                         help='Filename for saving logs',
463                         metavar='<log_file>')
464
465     parser.add_argument('--user-label', '--userlabel', dest='user_label',
466                         action='store',
467                         help='Custom label for performance records')
468
469     parser.add_argument('--hypervisor', dest='hypervisor',
470                         action='store',
471                         metavar='<hypervisor name>',
472                         help='Where chains must run ("compute", "az:", "az:compute")')
473
474     parser.add_argument('--l2-loopback', '--l2loopback', dest='l2_loopback',
475                         action='store',
476                         metavar='<vlan>',
477                         help='Port to port or port to switch to port L2 loopback with VLAN id')
478
479     parser.add_argument('--cache-size', dest='cache_size',
480                         action='store',
481                         default='0',
482                         help='Specify the FE cache size (default: 0, flow-count if < 0)')
483
484     parser.add_argument('--service-mode', dest='service_mode',
485                         action='store_true',
486                         default=False,
487                         help='Enable T-Rex service mode for debugging only')
488
489     parser.add_argument('--no-flow-stats', dest='no_flow_stats',
490                         action='store_true',
491                         default=False,
492                         help='Disable extra flow stats (on high load traffic)')
493
494     parser.add_argument('--no-latency-stats', dest='no_latency_stats',
495                         action='store_true',
496                         default=False,
497                         help='Disable flow stats for latency traffic')
498
499     parser.add_argument('--no-latency-streams', dest='no_latency_streams',
500                         action='store_true',
501                         default=False,
502                         help='Disable latency measurements (no streams)')
503
504     opts, unknown_opts = parser.parse_known_args()
505     return opts, unknown_opts
506
507
508 def load_default_config():
509     default_cfg = resource_string(__name__, "cfg.default.yaml")
510     config = config_loads(default_cfg)
511     config.name = '(built-in default config)'
512     return config, default_cfg
513
514
515 def override_custom_traffic(config, frame_sizes, unidir):
516     """Override the traffic profiles with a custom one."""
517     if frame_sizes is not None:
518         traffic_profile_name = "custom_traffic_profile"
519         config.traffic_profile = [
520             {
521                 "l2frame_size": frame_sizes,
522                 "name": traffic_profile_name
523             }
524         ]
525     else:
526         traffic_profile_name = config.traffic["profile"]
527
528     bidirectional = config.traffic['bidirectional'] if unidir is None else not unidir
529     config.traffic = {
530         "bidirectional": bidirectional,
531         "profile": traffic_profile_name
532     }
533
534
535 def check_physnet(name, netattrs):
536     if not netattrs.physical_network:
537         raise Exception("SRIOV requires physical_network to be specified for the {n} network"
538                         .format(n=name))
539     if not netattrs.segmentation_id:
540         raise Exception("SRIOV requires segmentation_id to be specified for the {n} network"
541                         .format(n=name))
542
543 def status_cleanup(config, cleanup, force_cleanup):
544     LOG.info('Version: %s', pbr.version.VersionInfo('nfvbench').version_string_with_vcs())
545     # check if another run is pending
546     ret_code = 0
547     try:
548         with utils.RunLock():
549             LOG.info('Status: idle')
550     except Exception:
551         LOG.info('Status: busy (run pending)')
552         ret_code = 1
553     # check nfvbench resources
554     if config.openrc_file and config.service_chain != ChainType.EXT:
555         cleaner = Cleaner(config)
556         count = cleaner.show_resources()
557         if count and (cleanup or force_cleanup):
558             cleaner.clean(not force_cleanup)
559     sys.exit(ret_code)
560
561 def main():
562     global fluent_logger
563     run_summary_required = False
564     try:
565         log.setup()
566         # load default config file
567         config, default_cfg = load_default_config()
568         # create factory for platform specific classes
569         try:
570             factory_module = importlib.import_module(config['factory_module'])
571             factory = getattr(factory_module, config['factory_class'])()
572         except AttributeError:
573             raise Exception("Requested factory module '{m}' or class '{c}' was not found."
574                             .format(m=config['factory_module'], c=config['factory_class']))
575         # create config plugin for this platform
576         config_plugin = factory.get_config_plugin_class()(config)
577         config = config_plugin.get_config()
578
579         opts, unknown_opts = _parse_opts_from_cli()
580         log.set_level(debug=opts.debug)
581
582         if opts.version:
583             print((pbr.version.VersionInfo('nfvbench').version_string_with_vcs()))
584             sys.exit(0)
585
586         if opts.summary:
587             with open(opts.summary) as json_data:
588                 result = json.load(json_data)
589                 if opts.user_label:
590                     result['config']['user_label'] = opts.user_label
591                 print((NFVBenchSummarizer(result, fluent_logger)))
592             sys.exit(0)
593
594         # show default config in text/yaml format
595         if opts.show_default_config:
596             print((default_cfg.decode("utf-8")))
597             sys.exit(0)
598
599         config.name = ''
600         if opts.config:
601             # do not check extra_specs in flavor as it can contain any key/value pairs
602             whitelist_keys = ['extra_specs']
603             # override default config options with start config at path parsed from CLI
604             # check if it is an inline yaml/json config or a file name
605             if os.path.isfile(opts.config):
606                 LOG.info('Loading configuration file: %s', opts.config)
607                 config = config_load(opts.config, config, whitelist_keys)
608                 config.name = os.path.basename(opts.config)
609             else:
610                 LOG.info('Loading configuration string: %s', opts.config)
611                 config = config_loads(opts.config, config, whitelist_keys)
612
613         # setup the fluent logger as soon as possible right after the config plugin is called,
614         # if there is any logging or result tag is set then initialize the fluent logger
615         for fluentd in config.fluentd:
616             if fluentd.logging_tag or fluentd.result_tag:
617                 fluent_logger = FluentLogHandler(config.fluentd)
618                 LOG.addHandler(fluent_logger)
619                 break
620
621         # traffic profile override options
622         override_custom_traffic(config, opts.frame_sizes, opts.unidir)
623
624         # copy over cli options that are used in config
625         config.generator_profile = opts.generator_profile
626         if opts.sriov:
627             config.sriov = True
628         if opts.log_file:
629             config.log_file = opts.log_file
630         if opts.service_chain:
631             config.service_chain = opts.service_chain
632         if opts.service_chain_count:
633             config.service_chain_count = opts.service_chain_count
634         if opts.no_vswitch_access:
635             config.no_vswitch_access = opts.no_vswitch_access
636         if opts.hypervisor:
637             # can be any of 'comp1', 'nova:', 'nova:comp1'
638             config.compute_nodes = opts.hypervisor
639         if opts.vxlan:
640             config.vxlan = True
641         if opts.mpls:
642             config.mpls = True
643         if opts.restart:
644             config.restart = True
645         if opts.service_mode:
646             config.service_mode = True
647         if opts.no_flow_stats:
648             config.no_flow_stats = True
649         if opts.no_latency_stats:
650             config.no_latency_stats = True
651         if opts.no_latency_streams:
652             config.no_latency_streams = True
653         # port to port loopback (direct or through switch)
654         if opts.l2_loopback:
655             config.l2_loopback = True
656             if config.service_chain != ChainType.EXT:
657                 LOG.info('Changing service chain type to EXT')
658                 config.service_chain = ChainType.EXT
659             if not config.no_arp:
660                 LOG.info('Disabling ARP')
661                 config.no_arp = True
662             config.vlans = [int(opts.l2_loopback), int(opts.l2_loopback)]
663             LOG.info('Running L2 loopback: using EXT chain/no ARP')
664
665         if opts.use_sriov_middle_net:
666             if (not config.sriov) or (config.service_chain != ChainType.PVVP):
667                 raise Exception("--use-sriov-middle-net is only valid for PVVP with SRIOV")
668             config.use_sriov_middle_net = True
669
670         if config.sriov and config.service_chain != ChainType.EXT:
671             # if sriov is requested (does not apply to ext chains)
672             # make sure the physnet names are specified
673             check_physnet("left", config.internal_networks.left)
674             check_physnet("right", config.internal_networks.right)
675             if config.service_chain == ChainType.PVVP and config.use_sriov_middle_net:
676                 check_physnet("middle", config.internal_networks.middle)
677
678         # show running config in json format
679         if opts.show_config:
680             print((json.dumps(config, sort_keys=True, indent=4)))
681             sys.exit(0)
682
683         # update the config in the config plugin as it might have changed
684         # in a copy of the dict (config plugin still holds the original dict)
685         config_plugin.set_config(config)
686
687         if opts.status or opts.cleanup or opts.force_cleanup:
688             status_cleanup(config, opts.cleanup, opts.force_cleanup)
689
690         # add file log if requested
691         if config.log_file:
692             log.add_file_logger(config.log_file)
693
694         openstack_spec = config_plugin.get_openstack_spec() if config.openrc_file \
695             else None
696
697         nfvbench_instance = NFVBench(config, openstack_spec, config_plugin, factory)
698
699         if opts.server:
700             server = WebServer(nfvbench_instance, fluent_logger)
701             try:
702                 port = int(opts.port)
703             except ValueError:
704                 server.run(host=opts.host)
705             else:
706                 server.run(host=opts.host, port=port)
707             # server.run() should never return
708         else:
709             with utils.RunLock():
710                 run_summary_required = True
711                 if unknown_opts:
712                     err_msg = 'Unknown options: ' + ' '.join(unknown_opts)
713                     LOG.error(err_msg)
714                     raise Exception(err_msg)
715
716                 # remove unfilled values
717                 opts = {k: v for k, v in list(vars(opts).items()) if v is not None}
718                 # get CLI args
719                 params = ' '.join(str(e) for e in sys.argv[1:])
720                 result = nfvbench_instance.run(opts, params)
721                 if 'error_message' in result:
722                     raise Exception(result['error_message'])
723
724                 if 'result' in result and result['status']:
725                     nfvbench_instance.save(result['result'])
726                     nfvbench_instance.prepare_summary(result['result'])
727     except Exception as exc:
728         run_summary_required = True
729         LOG.error({
730             'status': NFVBench.STATUS_ERROR,
731             'error_message': traceback.format_exc()
732         })
733         print((str(exc)))
734     finally:
735         if fluent_logger:
736             # only send a summary record if there was an actual nfvbench run or
737             # if an error/exception was logged.
738             fluent_logger.send_run_summary(run_summary_required)
739
740
741 if __name__ == '__main__':
742     main()