Skip to content

Commit

Permalink
Merge pull request #471 from openvstorage/parse_default_args_configur…
Browse files Browse the repository at this point in the history
…ation

Parse default args configuration
(cherry picked from commit dbba883)
  • Loading branch information
JeffreyDevloo committed May 29, 2018
1 parent 35e8084 commit 9ca2dfb
Showing 1 changed file with 96 additions and 22 deletions.
118 changes: 96 additions & 22 deletions ovs/extensions/healthcheck/expose_to_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,11 @@
import click
import inspect
from functools import wraps
from ovs.extensions.generic.configuration import Configuration
from ovs.extensions.healthcheck.decorators import node_check
from ovs.extensions.healthcheck.result import HCResults
from ovs.extensions.storage.volatilefactory import VolatileFactory
from ovs.extensions.healthcheck.logger import Logger
from ovs.extensions.storage.volatilefactory import VolatileFactory

# @todo Make it recursive. Current layout enforces SUBMODULE COMMAND, SUBMODULE, SUB, COMMAND is not possible

Expand Down Expand Up @@ -66,6 +67,8 @@ def option(*param_decls, **attrs):
"""
Decorator to create an option value for the exposed method
Wraps around the click decorator
Please note: creating commands should be done using the click decorator for commands to read in the __click_params__ attribute
to attach the right options to it.
:param param_decls: All possible param declarations (eg '--to-json', '-t')
:param attrs: All possible attributes. See click.Option for all possible items
"""
Expand All @@ -88,7 +91,7 @@ class CLIContext(object):
pass


class CLI(click.MultiCommand):
class CLI(click.Group):
"""
Click CLI which dynamically loads all possible commands
Implementations require an entry point
Expand Down Expand Up @@ -122,6 +125,7 @@ def list_commands(self, ctx):
:param ctx: Passed context
:return: List of files to look for commands
"""
_ = ctx
sub_commands = self._discover_methods().keys() # Returns all underlying modules
sub_commands.sort()
return sub_commands
Expand All @@ -135,6 +139,10 @@ def get_command(self, ctx, name):
:return: Function pointer to the command or None when no import could happen
:rtype: callable
"""
cmd = self.commands.get(name)
if cmd:
return cmd
# More extensive - build the command and register
discovery_data = self._discover_methods()
if name in discovery_data.keys():
# The current passed name is a module. Wrap it up in a group and add all commands under it dynamically
Expand All @@ -146,6 +154,7 @@ def get_command(self, ctx, name):
cl = getattr(mod, function_data['class'])()
module_commands[function_name] = click.Command(function_name, callback=getattr(cl, function_data['function']))
ret = self.GROUP_MODULE_CLASS(name, module_commands)
self.add_command(ret)
return ret

@classmethod
Expand Down Expand Up @@ -217,13 +226,13 @@ def get_and_cache():
# Able to use the cache, has not expired yet
del exposed_methods['expires']
return exposed_methods
except:
except Exception:
cls.logger.exception('Unable to retrieve the exposed resources from cache')
exposed_methods = discover()
try:
cls._discovery_cache = exposed_methods
cls._volatile_client.set(cls.CACHE_KEY, exposed_methods)
except:
except Exception:
cls.logger.exception('Unable to cache the exposed resources')
del exposed_methods['expires']
return exposed_methods
Expand All @@ -243,7 +252,7 @@ class CLIAddonGroup(CLI):
"""
Handles retrieving the right command
"""
# @todo make it recurive here. The depth of the relation should indicate returning a command or antoher CLIAddonGroup
# @todo make it recursive here. The depth of the relation should indicate returning a command or another CLIAddonGroup

def __init__(self, *args, **kwargs):
# type: (*any, **any) -> None
Expand Down Expand Up @@ -286,6 +295,10 @@ def get_command(self, ctx, name):
:return: Function pointer to the command or None when no import could happen
:rtype: callable
"""
cmd = self.commands.get(name)
if cmd:
return cmd
# More extensive - build the command and register
# @todo Make recursive with other groups
discovery_data = self._discover_methods() # Will be coming from cache
current_module_name = ctx.command.name
Expand All @@ -295,10 +308,13 @@ def get_command(self, ctx, name):
mod = imp.load_source(function_data['module_name'], function_data['location'])
cl = getattr(mod, function_data['class'])()
method_to_run = getattr(cl, function_data['function'])
click_command = click.command(name=name,
help=function_data.get('help'),
short_help=function_data.get('short_help'))
return click_command(method_to_run)
# Wrap around the click decorator to extract the option arguments using the function parameters
click_command_wrap = click.command(name=name,
help=function_data.get('help'),
short_help=function_data.get('short_help'))
cmd = click_command_wrap(method_to_run)
self.add_command(cmd)
return cmd

###############################
# Healthcheck implementations #
Expand Down Expand Up @@ -341,6 +357,9 @@ class HealthCheckShared(object):
logger = Logger("healthcheck-ovs_clirunner")
CMD_FOLDER = os.path.join(os.path.dirname(__file__), 'suites') # Folder to query for commands

CONTEXT_SETTINGS_KEY = '/ovs/healthcheck/default_arguments'
_context_settings = {} # Cache

@staticmethod
def get_healthcheck_results(result_handler):
# type (HCResults) -> dict
Expand All @@ -362,6 +381,12 @@ def get_healthcheck_results(result_handler):
# returns dict with minimal and detailed information
return {'result': result, 'recap': dict(recount)}

@classmethod
def get_default_arguments(cls):
if not cls._context_settings:
cls._context_settings = Configuration.get(cls.CONTEXT_SETTINGS_KEY, default={})
return cls._context_settings


class HealthcheckAddonGroup(CLIAddonGroup):
"""
Expand All @@ -379,7 +404,7 @@ class HealthcheckAddonGroup(CLIAddonGroup):
def __init__(self, *args, **kwargs):
# type: (*any, **any) -> None
# Allow modules to be invoked without any other options behind them for backwards compatibility
super(HealthcheckAddonGroup, self).__init__(chain=True,
super(HealthcheckAddonGroup, self).__init__(chain=True, # Chain allows for multiple commands to specified
invoke_without_command=True,
callback=click.pass_context(self.run_methods_in_module),
*args, **kwargs)
Expand Down Expand Up @@ -407,6 +432,10 @@ def get_command(self, ctx, name):
:return: Function pointer to the command or None when no import could happen
:rtype: callable
"""
cmd = self.commands.get(name)
if cmd:
return cmd
# More extensive - build the command and register
discovery_data = self._discover_methods() # Will be coming from cache
result_handler = ctx.obj.result_handler # type: HCResults
current_module_name = ctx.command.name
Expand All @@ -421,11 +450,13 @@ def get_command(self, ctx, name):
method_to_run = getattr(cl, function_data['function'])
full_name = '{0}-{1}'.format(module_name, name)
wrapped_function = (self.healthcheck_wrapper(result_handler, full_name)(method_to_run)) # Inject our Healthcheck arguments
# Wrap around the click decorator to extract the option arguments
click_command = click.command(name=name,
help=function_data.get('help'),
short_help=function_data.get('short_help'))
return click_command(wrapped_function)
# Wrap around the click decorator to extract the option arguments using the function parameters
click_command_wrap = click.command(name=name,
help=function_data.get('help'),
short_help=function_data.get('short_help'))
cmd = click_command_wrap(wrapped_function)
self.add_command(cmd)
return cmd

def healthcheck_wrapper(self, result_handler, test_name):
# type: (HCResults, str) -> callable
Expand Down Expand Up @@ -462,7 +493,7 @@ def new_function(*args, **kwargs):
except (click.Abort, KeyboardInterrupt):
self.logger.warning('Caught keyboard interrupt during {0}. Output may be incomplete!'.format(test_name))
raise HealthcheckTerminatedException(result_handler=result_handler) # Will be handled more globally. The whole Healthcheck should abort
except:
except Exception:
self.logger.exception('Unhandled exception caught when executing {0}'.format(test_name))
result_handler.exception('Unhandled exception caught when executing {0}'.format(test_name))
# Change the name to the desired one
Expand Down Expand Up @@ -500,15 +531,17 @@ class HealthCheckCLI(CLI):

logger = HealthCheckShared.logger

def __init__(self, *args, **kwargs):
# type: (*any, **any) -> None
def __init__(self, context_settings=None, *args, **kwargs):
# type: (dict, *any, **any) -> None
"""
Initializes a CLI instance
Injects a healthcheck specific callback
"""
super(HealthCheckCLI, self).__init__(chain=True,
invoke_without_command=True,
if context_settings is None:
context_settings = dict(default_map=HealthCheckShared.get_default_arguments())
super(HealthCheckCLI, self).__init__(invoke_without_command=True,
result_callback=self.healthcheck_result_handler,
context_settings=context_settings,
*args, **kwargs)

def parse_args(self, ctx, args):
Expand Down Expand Up @@ -536,11 +569,16 @@ def get_command(self, ctx, name):
:return: Function pointer to the command or None when no import could happen
:rtype: callable
"""
cmd = self.commands.get(name)
if cmd:
return cmd
# More extensive - build the command and register
discovery_data = self._discover_methods()
if name in discovery_data.keys():
# The current passed name is a module. Wrap it up in a group and add all commands under it dynamically
ret = self.GROUP_MODULE_CLASS(name=name)
return ret
cmd = self.GROUP_MODULE_CLASS(name=name)
self.add_command(cmd)
return cmd

@staticmethod
@click.pass_context
Expand Down Expand Up @@ -584,6 +622,28 @@ def invoke(self, ctx):
except (click.Abort, KeyboardInterrupt):
raise HealthcheckTerminatedException(result_handler=ctx.obj.result_handler)

def generate_configuration_options(self, cmd=None, ctx=None):
"""
Generate a dict with all possible configuration options available
Only to be
"""
options = {}
cmd = cmd or self
ctx = cmd.make_context(cmd.name, [], ctx)
if not ctx.obj: # Inject the result handler as the Healthcheck wraps the functions and requires this object
result_handler = HCResults(unattended=False, to_json=False)
ctx.obj = HealthCheckCLiContext(result_handler)
if hasattr(cmd, 'list_commands'):
for sub_cmd_name in cmd.list_commands(ctx):
sub_cmd = cmd.get_command(ctx, sub_cmd_name)
sub_cmd_options = {sub_cmd.name: self.generate_configuration_options(sub_cmd, ctx)}
options.update(sub_cmd_options)
else:
for param in cmd.params:
handled_value, _ = param.handle_parse_result(ctx, {}, [])
options.update({param.name: handled_value})
return options


@click.group(cls=HealthCheckCLI)
@click.option('--unattended', is_flag=True, help='Only output the results in a compact format')
Expand Down Expand Up @@ -618,6 +678,20 @@ def run_method(cls, *args, **kwargs):
"""
Executes the given method like it would be executed through the CLI
"""
_ = kwargs
if not isinstance(args, tuple):
args = (args,)
return healthcheck_entry_point(args)

@classmethod
def generate_configuration_options(cls, re_use_current_settings=False):
# type: (bool) -> dict
"""
Generate a complete structure indicating where tweaking is possible together with the default values
:param re_use_current_settings: Re-use the settings currently set. Defaults to False
It will regenerate a complete structure and apply the already set values if set to True
:return: All options available to the healthcheck
:rtype: dict
"""
cli = HealthCheckCLI(context_settings=None if re_use_current_settings else {})
return cli.generate_configuration_options()

0 comments on commit 9ca2dfb

Please sign in to comment.