Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

mgr/dashboard: fix query parameters in task annotated endpoints #23229

Merged
merged 1 commit into from Jul 30, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
77 changes: 38 additions & 39 deletions src/pybind/mgr/dashboard/controllers/__init__.py
Expand Up @@ -257,6 +257,32 @@ def json_error_page(status, message, traceback, version):
version=version))


def _get_function_params(func):
"""
Retrieves the list of parameters declared in function.
Each parameter is represented as dict with keys:
* name (str): the name of the parameter
* required (bool): whether the parameter is required or not
* default (obj): the parameter's default value
"""
fspec = getargspec(func)

func_params = []
nd = len(fspec.args) if not fspec.defaults else -len(fspec.defaults)
for param in fspec.args[1:nd]:
func_params.append({'name': param, 'required': True})

if fspec.defaults:
for param, val in zip(fspec.args[nd:], fspec.defaults):
func_params.append({
'name': param,
'required': False,
'default': val
})

return func_params


class Task(object):
def __init__(self, name, metadata, wait_for=5.0, exception_handler=None):
self.name = name
Expand All @@ -268,24 +294,23 @@ def __init__(self, name, metadata, wait_for=5.0, exception_handler=None):
self.exception_handler = exception_handler

def _gen_arg_map(self, func, args, kwargs):
# pylint: disable=deprecated-method
arg_map = {}
if sys.version_info > (3, 0): # pylint: disable=no-else-return
sig = inspect.signature(func)
arg_list = [a for a in sig.parameters]
else:
sig = getargspec(func)
arg_list = [a for a in sig.args]
params = _get_function_params(func)

for idx, arg in enumerate(arg_list):
args = args[1:] # exclude self
for idx, param in enumerate(params):
if idx < len(args):
arg_map[arg] = args[idx]
arg_map[param['name']] = args[idx]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Too many ]s and [s. Can we create a new named tuple FuncParam in _get_function_params?

from collections import namedtuple
FuncParam = namedtuple('FuncParam', 'name default required')
func_params.append(FuncParam(name=param , default=val, required=False)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could but that would require a lot of changes which are out of scope of this fix. I suggest you to create an issue for that.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It wouldn't require any algorithmic changes, except for replacing param['xyz'] with param.xyz

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The _get_function_params function is used in more 6 places throughout the code.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exactly. That's why we need to return proper types here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code in this PR is fixing a concrete bug reported in the issue tracker. I don't see a strong reason to also add an additional commit with code cleanup to this PR. The code cleanup can be done in a separate PR.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right now, this issue is just an inconvenience to @tspmelo . Merging this as it is would introduce further technical dept.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Btw, I'm not blocking this PR in github)

else:
if arg in kwargs:
arg_map[arg] = kwargs[arg]
if arg in arg_map:
if param['name'] in kwargs:
arg_map[param['name']] = kwargs[param['name']]
else:
assert not param['required']
arg_map[param['name']] = param['default']

if param['name'] in arg_map:
# This is not a type error. We are using the index here.
arg_map[idx] = arg_map[arg]
arg_map[idx+1] = arg_map[param['name']]

return arg_map

Expand Down Expand Up @@ -325,32 +350,6 @@ def wrapper(*args, **kwargs):
return wrapper


def _get_function_params(func):
"""
Retrieves the list of parameters declared in function.
Each parameter is represented as dict with keys:
* name (str): the name of the parameter
* required (bool): whether the parameter is required or not
* default (obj): the parameter's default value
"""
fspec = getargspec(func)

func_params = []
nd = len(fspec.args) if not fspec.defaults else -len(fspec.defaults)
for param in fspec.args[1:nd]:
func_params.append({'name': param, 'required': True})

if fspec.defaults:
for param, val in zip(fspec.args[nd:], fspec.defaults):
func_params.append({
'name': param,
'required': False,
'default': val
})

return func_params


class BaseController(object):
"""
Base class for all controllers providing API endpoints.
Expand Down
9 changes: 9 additions & 0 deletions src/pybind/mgr/dashboard/tests/test_rest_tasks.py
Expand Up @@ -38,6 +38,11 @@ def foo(self, param):
def bar(self, key, param=None):
return {'my_param': param, 'key': key}

@Task('task/query', ['{param}'])
@RESTController.Collection('POST', query_params=['param'])
def query(self, param=None):
return {'my_param': param}


class TaskControllerTest(ControllerTestCase):
@classmethod
Expand Down Expand Up @@ -75,3 +80,7 @@ def test_foo_task(self):
def test_bar_task(self):
self._task_put('/test/task/3/bar', {'param': 'hello'})
self.assertJsonBody({'my_param': 'hello', 'key': '3'})

def test_query_param(self):
self._task_post('/test/task/query')
self.assertJsonBody({'my_param': None})