Fix API inconsistencies with GET /v2/workflows

Add missing API parameters to Workbooks, Actions, Executions, Cron
Triggers, Environments, Action Executions and Tasks:
marker, limit, sort_keys, sort_dirs, fields, filters

Change-Id: I207bc93e84067f83f926843f0b9895a19b831079
Partial-Bug: #1585646
This commit is contained in:
Xavier Hardy 2016-06-21 09:28:33 +02:00
parent 0ff3627f9e
commit 0e51d26e1c
12 changed files with 627 additions and 246 deletions

View File

@ -182,9 +182,9 @@ class ActionsController(rest.RestController, hooks.HookController):
db_api.delete_action_definition(name) db_api.delete_action_definition(name)
@wsme_pecan.wsexpose(Actions, types.uuid, int, types.uniquelist, @wsme_pecan.wsexpose(Actions, types.uuid, int, types.uniquelist,
types.list) types.list, types.uniquelist, types.jsontype)
def get_all(self, marker=None, limit=None, sort_keys='name', def get_all(self, marker=None, limit=None, sort_keys='name',
sort_dirs='asc'): sort_dirs='asc', fields='', **filters):
"""Return all actions. """Return all actions.
:param marker: Optional. Pagination marker for large data sets. :param marker: Optional. Pagination marker for large data sets.
@ -196,35 +196,30 @@ class ActionsController(rest.RestController, hooks.HookController):
:param sort_dirs: Optional. Directions to sort corresponding to :param sort_dirs: Optional. Directions to sort corresponding to
sort_keys, "asc" or "desc" can be choosed. sort_keys, "asc" or "desc" can be choosed.
Default: asc. Default: asc.
:param fields: Optional. A specified list of fields of the resource to
be returned. 'id' will be included automatically in
fields if it's provided, since it will be used when
constructing 'next' link.
:param filters: Optional. A list of filters to apply to the result.
Where project_id is the same as the requester or Where project_id is the same as the requester or
project_id is different but the scope is public. project_id is different but the scope is public.
""" """
acl.enforce('actions:list', context.ctx()) acl.enforce('actions:list', context.ctx())
LOG.info("Fetch actions. marker=%s, limit=%s, sort_keys=%s, " LOG.info("Fetch actions. marker=%s, limit=%s, sort_keys=%s, "
"sort_dirs=%s", marker, limit, sort_keys, sort_dirs) "sort_dirs=%s, filters=%s", marker, limit, sort_keys,
sort_dirs, filters)
rest_utils.validate_query_params(limit, sort_keys, sort_dirs) return rest_utils.get_all(
Actions,
marker_obj = None Action,
db_api.get_action_definitions,
if marker: db_api.get_action_definition_by_id,
marker_obj = db_api.get_action_definition_by_id(marker) resource_function=None,
marker=marker,
db_action_defs = db_api.get_action_definitions(
limit=limit, limit=limit,
marker=marker_obj,
sort_keys=sort_keys, sort_keys=sort_keys,
sort_dirs=sort_dirs sort_dirs=sort_dirs,
) fields=fields,
**filters
actions_list = [Action.from_dict(db_model.to_dict())
for db_model in db_action_defs]
return Actions.convert_with_links(
actions_list,
limit,
pecan.request.host_url,
sort_keys=','.join(sort_keys),
sort_dirs=','.join(sort_dirs)
) )

View File

@ -79,11 +79,16 @@ class ActionExecution(resource.Resource):
) )
class ActionExecutions(resource.Resource): class ActionExecutions(resource.ResourceList):
"""A collection of action_executions.""" """A collection of action_executions."""
action_executions = [ActionExecution] action_executions = [ActionExecution]
def __init__(self, **kwargs):
self._type = 'action_executions'
super(ActionExecutions, self).__init__(**kwargs)
@classmethod @classmethod
def sample(cls): def sample(cls):
return cls(action_executions=[ActionExecution.sample()]) return cls(action_executions=[ActionExecution.sample()])
@ -115,18 +120,48 @@ def _get_action_execution_resource(action_ex):
return res return res
def _get_action_executions(task_execution_id=None): def _get_action_executions(task_execution_id=None, marker=None, limit=None,
kwargs = {'type': 'action_execution'} sort_keys='created_at', sort_dirs='asc',
fields='', **filters):
"""Return all action executions.
Where project_id is the same as the requestor or
project_id is different but the scope is public.
:param marker: Optional. Pagination marker for large data sets.
:param limit: Optional. Maximum number of resources to return in a
single result. Default value is None for backward
compatibility.
:param sort_keys: Optional. Columns to sort results by.
Default: created_at, which is backward compatible.
:param sort_dirs: Optional. Directions to sort corresponding to
sort_keys, "asc" or "desc" can be chosen.
Default: desc. The length of sort_dirs can be equal
or less than that of sort_keys.
:param fields: Optional. A specified list of fields of the resource to
be returned. 'id' will be included automatically in
fields if it's provided, since it will be used when
constructing 'next' link.
:param filters: Optional. A list of filters to apply to the result.
"""
filters['type'] = 'action_execution'
if task_execution_id: if task_execution_id:
kwargs['task_execution_id'] = task_execution_id filters['task_execution_id'] = task_execution_id
action_execs = [ return rest_utils.get_all(
_get_action_execution_resource(a_ex) ActionExecutions,
for a_ex in db_api.get_action_executions(**kwargs) ActionExecution,
] db_api.get_action_executions,
db_api.get_action_execution,
return ActionExecutions(action_executions=action_execs) resource_function=_get_action_execution_resource,
marker=marker,
limit=limit,
sort_keys=sort_keys,
sort_dirs=sort_dirs,
fields=fields,
**filters
)
class ActionExecutionsController(rest.RestController): class ActionExecutionsController(rest.RestController):
@ -194,13 +229,45 @@ class ActionExecutionsController(rest.RestController):
return ActionExecution.from_dict(values) return ActionExecution.from_dict(values)
@wsme_pecan.wsexpose(ActionExecutions) @wsme_pecan.wsexpose(ActionExecutions, types.uuid, int, types.uniquelist,
def get_all(self): types.list, types.uniquelist, types.jsontype)
"""Return all action_executions within the execution.""" def get_all(self, marker=None, limit=None, sort_keys='created_at',
acl.enforce('action_executions:list', context.ctx()) sort_dirs='asc', fields='', **filters):
LOG.info("Fetch action_executions") """Return all tasks within the execution.
return _get_action_executions() Where project_id is the same as the requestor or
project_id is different but the scope is public.
:param marker: Optional. Pagination marker for large data sets.
:param limit: Optional. Maximum number of resources to return in a
single result. Default value is None for backward
compatibility.
:param sort_keys: Optional. Columns to sort results by.
Default: created_at, which is backward compatible.
:param sort_dirs: Optional. Directions to sort corresponding to
sort_keys, "asc" or "desc" can be chosen.
Default: desc. The length of sort_dirs can be equal
or less than that of sort_keys.
:param fields: Optional. A specified list of fields of the resource to
be returned. 'id' will be included automatically in
fields if it's provided, since it will be used when
constructing 'next' link.
:param filters: Optional. A list of filters to apply to the result.
"""
acl.enforce('action_executions:list', context.ctx())
LOG.info("Fetch action_executions. marker=%s, limit=%s, "
"sort_keys=%s, sort_dirs=%s, filters=%s",
marker, limit, sort_keys, sort_dirs, filters)
return _get_action_executions(
marker=marker,
limit=limit,
sort_keys=sort_keys,
sort_dirs=sort_dirs,
fields=fields,
**filters
)
@rest_utils.wrap_wsme_controller_exception @rest_utils.wrap_wsme_controller_exception
@wsme_pecan.wsexpose(None, wtypes.text, status_code=204) @wsme_pecan.wsexpose(None, wtypes.text, status_code=204)
@ -228,13 +295,46 @@ class ActionExecutionsController(rest.RestController):
class TasksActionExecutionController(rest.RestController): class TasksActionExecutionController(rest.RestController):
@wsme_pecan.wsexpose(ActionExecutions, wtypes.text) @wsme_pecan.wsexpose(ActionExecutions, types.uuid, types.uuid, int,
def get_all(self, task_execution_id): types.uniquelist, types.list, types.uniquelist,
"""Return all action executions within the task execution.""" types.jsontype)
acl.enforce('action_executions:list', context.ctx()) def get_all(self, task_execution_id, marker=None, limit=None,
LOG.info("Fetch action executions") sort_keys='created_at', sort_dirs='asc', fields='', **filters):
"""Return all tasks within the execution.
return _get_action_executions(task_execution_id=task_execution_id) Where project_id is the same as the requestor or
project_id is different but the scope is public.
:param marker: Optional. Pagination marker for large data sets.
:param limit: Optional. Maximum number of resources to return in a
single result. Default value is None for backward
compatibility.
:param sort_keys: Optional. Columns to sort results by.
Default: created_at, which is backward compatible.
:param sort_dirs: Optional. Directions to sort corresponding to
sort_keys, "asc" or "desc" can be chosen.
Default: desc. The length of sort_dirs can be equal
or less than that of sort_keys.
:param fields: Optional. A specified list of fields of the resource to
be returned. 'id' will be included automatically in
fields if it's provided, since it will be used when
constructing 'next' link.
:param filters: Optional. A list of filters to apply to the result.
"""
acl.enforce('action_executions:list', context.ctx())
LOG.info("Fetch action_executions. marker=%s, limit=%s, "
"sort_keys=%s, sort_dirs=%s, filters=%s",
marker, limit, sort_keys, sort_dirs, filters)
return _get_action_executions(
task_execution_id=task_execution_id,
marker=marker,
limit=limit,
sort_keys=sort_keys,
sort_dirs=sort_dirs,
fields=fields,
**filters
)
@rest_utils.wrap_wsme_controller_exception @rest_utils.wrap_wsme_controller_exception
@wsme_pecan.wsexpose(ActionExecution, wtypes.text, wtypes.text) @wsme_pecan.wsexpose(ActionExecution, wtypes.text, wtypes.text)

View File

@ -64,11 +64,16 @@ class CronTrigger(resource.Resource):
updated_at='1970-01-01T00:00:00.000000') updated_at='1970-01-01T00:00:00.000000')
class CronTriggers(resource.Resource): class CronTriggers(resource.ResourceList):
"""A collection of cron triggers.""" """A collection of cron triggers."""
cron_triggers = [CronTrigger] cron_triggers = [CronTrigger]
def __init__(self, **kwargs):
self._type = 'cron_triggers'
super(CronTriggers, self).__init__(**kwargs)
@classmethod @classmethod
def sample(cls): def sample(cls):
return cls(cron_triggers=[CronTrigger.sample()]) return cls(cron_triggers=[CronTrigger.sample()])
@ -119,16 +124,44 @@ class CronTriggersController(rest.RestController):
db_api.delete_cron_trigger(name) db_api.delete_cron_trigger(name)
@wsme_pecan.wsexpose(CronTriggers) @wsme_pecan.wsexpose(CronTriggers, types.uuid, int, types.uniquelist,
def get_all(self): types.list, types.uniquelist, types.jsontype)
"""Return all cron triggers.""" def get_all(self, marker=None, limit=None, sort_keys='created_at',
sort_dirs='asc', fields='', **filters):
"""Return all cron triggers.
:param marker: Optional. Pagination marker for large data sets.
:param limit: Optional. Maximum number of resources to return in a
single result. Default value is None for backward
compatibility.
:param sort_keys: Optional. Columns to sort results by.
Default: created_at, which is backward compatible.
:param sort_dirs: Optional. Directions to sort corresponding to
sort_keys, "asc" or "desc" can be chosen.
Default: desc. The length of sort_dirs can be equal
or less than that of sort_keys.
:param fields: Optional. A specified list of fields of the resource to
be returned. 'id' will be included automatically in
fields if it's provided, since it will be used when
constructing 'next' link.
:param filters: Optional. A list of filters to apply to the result.
"""
acl.enforce('cron_triggers:list', context.ctx()) acl.enforce('cron_triggers:list', context.ctx())
LOG.info("Fetch cron triggers.") LOG.info("Fetch cron triggers. marker=%s, limit=%s, sort_keys=%s, "
"sort_dirs=%s, filters=%s", marker, limit, sort_keys,
sort_dirs, filters)
_list = [ return rest_utils.get_all(
CronTrigger.from_dict(db_model.to_dict()) CronTriggers,
for db_model in db_api.get_cron_triggers() CronTrigger,
] db_api.get_cron_triggers,
db_api.get_cron_trigger,
return CronTriggers(cron_triggers=_list) resource_function=None,
marker=marker,
limit=limit,
sort_keys=sort_keys,
sort_dirs=sort_dirs,
fields=fields,
**filters
)

View File

@ -61,33 +61,66 @@ class Environment(resource.Resource):
updated_at='1970-01-01T00:00:00.000000') updated_at='1970-01-01T00:00:00.000000')
class Environments(resource.Resource): class Environments(resource.ResourceList):
"""A collection of Environment resources.""" """A collection of Environment resources."""
environments = [Environment] environments = [Environment]
def __init__(self, **kwargs):
self._type = 'environments'
super(Environments, self).__init__(**kwargs)
@classmethod @classmethod
def sample(cls): def sample(cls):
return cls(environments=[Environment.sample()]) return cls(environments=[Environment.sample()])
class EnvironmentController(rest.RestController): class EnvironmentController(rest.RestController):
@wsme_pecan.wsexpose(Environments) @wsme_pecan.wsexpose(Environments, types.uuid, int, types.uniquelist,
def get_all(self): types.list, types.uniquelist, types.jsontype)
def get_all(self, marker=None, limit=None, sort_keys='created_at',
sort_dirs='asc', fields='', **filters):
"""Return all environments. """Return all environments.
Where project_id is the same as the requestor or Where project_id is the same as the requestor or
project_id is different but the scope is public. project_id is different but the scope is public.
:param marker: Optional. Pagination marker for large data sets.
:param limit: Optional. Maximum number of resources to return in a
single result. Default value is None for backward
compatibility.
:param sort_keys: Optional. Columns to sort results by.
Default: created_at, which is backward compatible.
:param sort_dirs: Optional. Directions to sort corresponding to
sort_keys, "asc" or "desc" can be chosen.
Default: desc. The length of sort_dirs can be equal
or less than that of sort_keys.
:param fields: Optional. A specified list of fields of the resource to
be returned. 'id' will be included automatically in
fields if it's provided, since it will be used when
constructing 'next' link.
:param filters: Optional. A list of filters to apply to the result.
""" """
acl.enforce('environments:list', context.ctx()) acl.enforce('environments:list', context.ctx())
LOG.info("Fetch environments.") LOG.info("Fetch environments. marker=%s, limit=%s, sort_keys=%s, "
"sort_dirs=%s, filters=%s", marker, limit, sort_keys,
sort_dirs, filters)
environments = [ return rest_utils.get_all(
Environment.from_dict(db_model.to_dict()) Environments,
for db_model in db_api.get_environments() Environment,
] db_api.get_environments,
db_api.get_environment,
return Environments(environments=environments) resource_function=None,
marker=marker,
limit=limit,
sort_keys=sort_keys,
sort_dirs=sort_dirs,
fields=fields,
**filters
)
@rest_utils.wrap_wsme_controller_exception @rest_utils.wrap_wsme_controller_exception
@wsme_pecan.wsexpose(Environment, wtypes.text) @wsme_pecan.wsexpose(Environment, wtypes.text)

View File

@ -15,7 +15,6 @@
# limitations under the License. # limitations under the License.
from oslo_log import log as logging from oslo_log import log as logging
import pecan
from pecan import rest from pecan import rest
from wsme import types as wtypes from wsme import types as wtypes
import wsmeext.pecan as wsme_pecan import wsmeext.pecan as wsme_pecan
@ -255,9 +254,9 @@ class ExecutionsController(rest.RestController):
return db_api.delete_workflow_execution(id) return db_api.delete_workflow_execution(id)
@wsme_pecan.wsexpose(Executions, types.uuid, int, types.uniquelist, @wsme_pecan.wsexpose(Executions, types.uuid, int, types.uniquelist,
types.list) types.list, types.uniquelist, types.jsontype)
def get_all(self, marker=None, limit=None, sort_keys='created_at', def get_all(self, marker=None, limit=None, sort_keys='created_at',
sort_dirs='asc'): sort_dirs='asc', fields='', **filters):
"""Return all Executions. """Return all Executions.
:param marker: Optional. Pagination marker for large data sets. :param marker: Optional. Pagination marker for large data sets.
@ -270,36 +269,30 @@ class ExecutionsController(rest.RestController):
sort_keys, "asc" or "desc" can be chosen. sort_keys, "asc" or "desc" can be chosen.
Default: desc. The length of sort_dirs can be equal Default: desc. The length of sort_dirs can be equal
or less than that of sort_keys. or less than that of sort_keys.
:param fields: Optional. A specified list of fields of the resource to
be returned. 'id' will be included automatically in
fields if it's provided, since it will be used when
constructing 'next' link.
:param filters: Optional. A list of filters to apply to the result.
""" """
acl.enforce('executions:list', context.ctx()) acl.enforce('executions:list', context.ctx())
LOG.info( LOG.info(
"Fetch executions. marker=%s, limit=%s, sort_keys=%s, " "Fetch executions. marker=%s, limit=%s, sort_keys=%s, "
"sort_dirs=%s", marker, limit, sort_keys, sort_dirs "sort_dirs=%s, filters=%s", marker, limit, sort_keys, sort_dirs,
filters
) )
rest_utils.validate_query_params(limit, sort_keys, sort_dirs) return rest_utils.get_all(
Executions,
marker_obj = None Execution,
db_api.get_workflow_executions,
if marker: db_api.get_workflow_execution,
marker_obj = db_api.get_workflow_execution(marker) resource_function=None,
marker=marker,
db_workflow_exs = db_api.get_workflow_executions(
limit=limit, limit=limit,
marker=marker_obj,
sort_keys=sort_keys, sort_keys=sort_keys,
sort_dirs=sort_dirs sort_dirs=sort_dirs,
) fields=fields,
**filters
wf_executions = [
Execution.from_dict(db_model.to_dict())
for db_model in db_workflow_exs
]
return Executions.convert_with_links(
wf_executions,
limit,
pecan.request.host_url,
sort_keys=','.join(sort_keys),
sort_dirs=','.join(sort_dirs)
) )

View File

@ -85,11 +85,16 @@ class Task(resource.Resource):
) )
class Tasks(resource.Resource): class Tasks(resource.ResourceList):
"""A collection of tasks.""" """A collection of tasks."""
tasks = [Task] tasks = [Task]
def __init__(self, **kwargs):
self._type = 'tasks'
super(Tasks, self).__init__(**kwargs)
@classmethod @classmethod
def sample(cls): def sample(cls):
return cls(tasks=[Task.sample()]) return cls(tasks=[Task.sample()])
@ -102,16 +107,46 @@ def _get_task_resource_with_result(task_ex):
return task return task
def _get_task_resources_with_results(wf_ex_id=None): def _get_task_resources_with_results(wf_ex_id=None, marker=None, limit=None,
filters = {} sort_keys='created_at', sort_dirs='asc',
fields='', **filters):
"""Return all tasks within the execution.
Where project_id is the same as the requestor or
project_id is different but the scope is public.
:param marker: Optional. Pagination marker for large data sets.
:param limit: Optional. Maximum number of resources to return in a
single result. Default value is None for backward
compatibility.
:param sort_keys: Optional. Columns to sort results by.
Default: created_at, which is backward compatible.
:param sort_dirs: Optional. Directions to sort corresponding to
sort_keys, "asc" or "desc" can be chosen.
Default: desc. The length of sort_dirs can be equal
or less than that of sort_keys.
:param fields: Optional. A specified list of fields of the resource to
be returned. 'id' will be included automatically in
fields if it's provided, since it will be used when
constructing 'next' link.
:param filters: Optional. A list of filters to apply to the result.
"""
if wf_ex_id: if wf_ex_id:
filters['workflow_execution_id'] = wf_ex_id filters['workflow_execution_id'] = wf_ex_id
task_exs = db_api.get_task_executions(**filters) return rest_utils.get_all(
tasks = [_get_task_resource_with_result(t_e) for t_e in task_exs] Tasks,
Task,
return Tasks(tasks=tasks) db_api.get_task_executions,
db_api.get_task_execution,
resource_function=_get_task_resource_with_result,
marker=marker,
limit=limit,
sort_keys=sort_keys,
sort_dirs=sort_dirs,
fields=fields,
**filters
)
class TasksController(rest.RestController): class TasksController(rest.RestController):
@ -128,13 +163,45 @@ class TasksController(rest.RestController):
return _get_task_resource_with_result(task_ex) return _get_task_resource_with_result(task_ex)
@wsme_pecan.wsexpose(Tasks) @wsme_pecan.wsexpose(Tasks, types.uuid, int, types.uniquelist,
def get_all(self): types.list, types.uniquelist, types.jsontype)
"""Return all tasks within the execution.""" def get_all(self, marker=None, limit=None, sort_keys='created_at',
acl.enforce('tasks:list', context.ctx()) sort_dirs='asc', fields='', **filters):
LOG.info("Fetch tasks") """Return all tasks.
return _get_task_resources_with_results() Where project_id is the same as the requestor or
project_id is different but the scope is public.
:param marker: Optional. Pagination marker for large data sets.
:param limit: Optional. Maximum number of resources to return in a
single result. Default value is None for backward
compatibility.
:param sort_keys: Optional. Columns to sort results by.
Default: created_at, which is backward compatible.
:param sort_dirs: Optional. Directions to sort corresponding to
sort_keys, "asc" or "desc" can be chosen.
Default: desc. The length of sort_dirs can be equal
or less than that of sort_keys.
:param fields: Optional. A specified list of fields of the resource to
be returned. 'id' will be included automatically in
fields if it's provided, since it will be used when
constructing 'next' link.
:param filters: Optional. A list of filters to apply to the result.
"""
acl.enforce('tasks:list', context.ctx())
LOG.info("Fetch tasks. marker=%s, limit=%s, sort_keys=%s, "
"sort_dirs=%s, filters=%s", marker, limit, sort_keys,
sort_dirs, filters)
return _get_task_resources_with_results(
marker=marker,
limit=limit,
sort_keys=sort_keys,
sort_dirs=sort_dirs,
fields=fields,
**filters
)
@rest_utils.wrap_wsme_controller_exception @rest_utils.wrap_wsme_controller_exception
@wsme_pecan.wsexpose(Task, wtypes.text, body=Task) @wsme_pecan.wsexpose(Task, wtypes.text, body=Task)
@ -190,10 +257,44 @@ class TasksController(rest.RestController):
class ExecutionTasksController(rest.RestController): class ExecutionTasksController(rest.RestController):
@wsme_pecan.wsexpose(Tasks, wtypes.text)
def get_all(self, workflow_execution_id):
"""Return all tasks within the workflow execution."""
acl.enforce('tasks:list', context.ctx())
LOG.info("Fetch tasks.")
return _get_task_resources_with_results(workflow_execution_id) @wsme_pecan.wsexpose(Tasks, types.uuid, types.uuid, int, types.uniquelist,
types.list, types.uniquelist)
def get_all(self, workflow_execution_id, marker=None, limit=None,
sort_keys='created_at', sort_dirs='asc', fields='', **filters):
"""Return all tasks within the execution.
Where project_id is the same as the requestor or
project_id is different but the scope is public.
:param marker: Optional. Pagination marker for large data sets.
:param limit: Optional. Maximum number of resources to return in a
single result. Default value is None for backward
compatibility.
:param sort_keys: Optional. Columns to sort results by.
Default: created_at, which is backward compatible.
:param sort_dirs: Optional. Directions to sort corresponding to
sort_keys, "asc" or "desc" can be chosen.
Default: desc. The length of sort_dirs can be equal
or less than that of sort_keys.
:param fields: Optional. A specified list of fields of the resource to
be returned. 'id' will be included automatically in
fields if it's provided, since it will be used when
constructing 'next' link.
:param filters: Optional. A list of filters to apply to the result.
"""
acl.enforce('tasks:list', context.ctx())
LOG.info("Fetch tasks. workflow_execution_id=%s, marker=%s, limit=%s, "
"sort_keys=%s, sort_dirs=%s, filters=%s",
workflow_execution_id, marker, limit, sort_keys, sort_dirs,
filters)
return _get_task_resources_with_results(
wf_ex_id=workflow_execution_id,
marker=marker,
limit=limit,
sort_keys=sort_keys,
sort_dirs=sort_dirs,
fields=fields,
**filters
)

View File

@ -22,6 +22,7 @@ import wsmeext.pecan as wsme_pecan
from mistral.api import access_control as acl from mistral.api import access_control as acl
from mistral.api.controllers import resource from mistral.api.controllers import resource
from mistral.api.controllers.v2 import types
from mistral.api.controllers.v2 import validation from mistral.api.controllers.v2 import validation
from mistral.api.hooks import content_type as ct_hook from mistral.api.hooks import content_type as ct_hook
from mistral import context from mistral import context
@ -62,11 +63,16 @@ class Workbook(resource.Resource):
updated_at='1970-01-01T00:00:00.000000') updated_at='1970-01-01T00:00:00.000000')
class Workbooks(resource.Resource): class Workbooks(resource.ResourceList):
"""A collection of Workbooks.""" """A collection of Workbooks."""
workbooks = [Workbook] workbooks = [Workbook]
def __init__(self, **kwargs):
self._type = 'workbooks'
super(Workbooks, self).__init__(**kwargs)
@classmethod @classmethod
def sample(cls): def sample(cls):
return cls(workbooks=[Workbook.sample()]) return cls(workbooks=[Workbook.sample()])
@ -123,17 +129,45 @@ class WorkbooksController(rest.RestController, hooks.HookController):
db_api.delete_workbook(name) db_api.delete_workbook(name)
@wsme_pecan.wsexpose(Workbooks) @wsme_pecan.wsexpose(Workbooks, types.uuid, int, types.uniquelist,
def get_all(self): types.list, types.uniquelist, types.jsontype)
"""Return all workbooks. def get_all(self, marker=None, limit=None, sort_keys='created_at',
sort_dirs='asc', fields='', **filters):
"""Return a list of workbooks.
:param marker: Optional. Pagination marker for large data sets.
:param limit: Optional. Maximum number of resources to return in a
single result. Default value is None for backward
compatibility.
:param sort_keys: Optional. Columns to sort results by.
Default: created_at.
:param sort_dirs: Optional. Directions to sort corresponding to
sort_keys, "asc" or "desc" can be choosed.
Default: asc.
:param fields: Optional. A specified list of fields of the resource to
be returned. 'id' will be included automatically in
fields if it's provided, since it will be used when
constructing 'next' link.
:param filters: Optional. A list of filters to apply to the result.
Where project_id is the same as the requestor or Where project_id is the same as the requestor or
project_id is different but the scope is public. project_id is different but the scope is public.
""" """
acl.enforce('workbooks:list', context.ctx()) acl.enforce('workbooks:list', context.ctx())
LOG.info("Fetch workbooks.") LOG.info("Fetch workbooks. marker=%s, limit=%s, sort_keys=%s, "
"sort_dirs=%s, fields=%s, filters=%s", marker, limit,
sort_keys, sort_dirs, fields, filters)
workbooks_list = [Workbook.from_dict(db_model.to_dict()) return rest_utils.get_all(
for db_model in db_api.get_workbooks()] Workbooks,
Workbook,
return Workbooks(workbooks=workbooks_list) db_api.get_workbooks,
db_api.get_workbook,
resource_function=None,
marker=marker,
limit=limit,
sort_keys=sort_keys,
sort_dirs=sort_dirs,
fields=fields,
**filters
)

View File

@ -236,9 +236,9 @@ class WorkflowsController(rest.RestController, hooks.HookController):
@rest_utils.wrap_wsme_controller_exception @rest_utils.wrap_wsme_controller_exception
@wsme_pecan.wsexpose(Workflows, types.uuid, int, types.uniquelist, @wsme_pecan.wsexpose(Workflows, types.uuid, int, types.uniquelist,
types.list, types.uniquelist) types.list, types.uniquelist, types.jsontype)
def get_all(self, marker=None, limit=None, sort_keys='created_at', def get_all(self, marker=None, limit=None, sort_keys='created_at',
sort_dirs='asc', fields=''): sort_dirs='asc', fields='', **filters):
"""Return a list of workflows. """Return a list of workflows.
:param marker: Optional. Pagination marker for large data sets. :param marker: Optional. Pagination marker for large data sets.
@ -254,46 +254,26 @@ class WorkflowsController(rest.RestController, hooks.HookController):
be returned. 'id' will be included automatically in be returned. 'id' will be included automatically in
fields if it's provided, since it will be used when fields if it's provided, since it will be used when
constructing 'next' link. constructing 'next' link.
:param filters: Optional. A list of filters to apply to the result.
Where project_id is the same as the requester or Where project_id is the same as the requester or
project_id is different but the scope is public. project_id is different but the scope is public.
""" """
acl.enforce('workflows:list', context.ctx()) acl.enforce('workflows:list', context.ctx())
LOG.info("Fetch workflows. marker=%s, limit=%s, sort_keys=%s, " LOG.info("Fetch workflows. marker=%s, limit=%s, sort_keys=%s, "
"sort_dirs=%s, fields=%s", marker, limit, sort_keys, "sort_dirs=%s, fields=%s, filters=%s", marker, limit,
sort_dirs, fields) sort_keys, sort_dirs, fields, filters)
if fields and 'id' not in fields: return rest_utils.get_all(
fields.insert(0, 'id') Workflows,
Workflow,
rest_utils.validate_query_params(limit, sort_keys, sort_dirs) db_api.get_workflow_definitions,
rest_utils.validate_fields(fields, Workflow.get_fields()) db_api.get_workflow_definition_by_id,
resource_function=None,
marker_obj = None marker=marker,
if marker:
marker_obj = db_api.get_workflow_definition_by_id(marker)
db_workflows = db_api.get_workflow_definitions(
limit=limit, limit=limit,
marker=marker_obj,
sort_keys=sort_keys, sort_keys=sort_keys,
sort_dirs=sort_dirs, sort_dirs=sort_dirs,
fields=fields fields=fields,
) **filters
workflows_list = []
for data in db_workflows:
workflow_dict = (dict(zip(fields, data)) if fields else
data.to_dict())
workflows_list.append(Workflow.from_dict(workflow_dict))
return Workflows.convert_with_links(
workflows_list,
limit,
pecan.request.host_url,
sort_keys=','.join(sort_keys),
sort_dirs=','.join(sort_dirs),
fields=','.join(fields) if fields else ''
) )

View File

@ -76,8 +76,16 @@ def load_workbook(name):
return IMPL.load_workbook(name) return IMPL.load_workbook(name)
def get_workbooks(): def get_workbooks(limit=None, marker=None, sort_keys=None,
return IMPL.get_workbooks() sort_dirs=None, fields=None, **kwargs):
return IMPL.get_workbooks(
limit=limit,
marker=marker,
sort_keys=sort_keys,
sort_dirs=sort_dirs,
fields=fields,
**kwargs
)
def create_workbook(values): def create_workbook(values):
@ -328,8 +336,15 @@ def load_task_execution(name):
return IMPL.load_task_execution(name) return IMPL.load_task_execution(name)
def get_task_executions(**kwargs): def get_task_executions(limit=None, marker=None, sort_keys=['created_at'],
return IMPL.get_task_executions(**kwargs) sort_dirs=None, **kwargs):
return IMPL.get_task_executions(
limit=limit,
marker=marker,
sort_keys=sort_keys,
sort_dirs=sort_dirs,
**kwargs
)
def create_task_execution(values): def create_task_execution(values):
@ -428,8 +443,16 @@ def load_environment(name):
return IMPL.load_environment(name) return IMPL.load_environment(name)
def get_environments(): def get_environments(limit=None, marker=None, sort_keys=['name'],
return IMPL.get_environments() sort_dirs=None, **kwargs):
return IMPL.get_environments(
limit=limit,
marker=marker,
sort_keys=sort_keys,
sort_dirs=sort_dirs,
**kwargs
)
def create_environment(values): def create_environment(values):

View File

@ -153,20 +153,56 @@ def _delete_all(model, session=None, **kwargs):
_secure_query(model).filter_by(**kwargs).delete(synchronize_session=False) _secure_query(model).filter_by(**kwargs).delete(synchronize_session=False)
def _get_collection_sorted_by_name(model, **kwargs): def _get_collection(model, limit=None, marker=None, sort_keys=None,
sort_dirs=None, fields=None, query=None, **kwargs):
columns = (
tuple([getattr(model, f) for f in fields if hasattr(model, f)])
if fields else ()
)
if query is None:
query = _secure_query(model, *columns).filter_by(**kwargs)
try:
return _paginate_query(
model,
limit,
marker,
sort_keys,
sort_dirs,
query
)
except Exception as e:
raise exc.DBQueryEntryException(
"Failed when querying database, error type: %s, "
"error message: %s" % (e.__class__.__name__, e.message)
)
def _get_collection_sorted_by_name(model, fields=None, sort_keys=['name'],
**kwargs):
# Note(lane): Sometimes tenant_A needs to get resources of tenant_B, # Note(lane): Sometimes tenant_A needs to get resources of tenant_B,
# especially in resource sharing scenario, the resource owner needs to # especially in resource sharing scenario, the resource owner needs to
# check if the resource is used by a member. # check if the resource is used by a member.
query = (b.model_query(model) if 'project_id' in kwargs columns = (
else _secure_query(model)) tuple([getattr(model, f) for f in fields if hasattr(model, f)])
if fields else ()
)
return query.filter_by(**kwargs).order_by(model.name).all() query = (b.model_query(model, *columns) if 'project_id' in kwargs
else _secure_query(model, *columns))
return _get_collection(
model=model,
query=query,
sort_keys=sort_keys,
fields=fields,
**kwargs
)
def _get_collection_sorted_by_time(model, **kwargs): def _get_collection_sorted_by_time(model, sort_keys=['created_at'], **kwargs):
query = _secure_query(model) return _get_collection(model, sort_keys=sort_keys, **kwargs)
return query.filter_by(**kwargs).order_by(model.created_at).all()
def _get_db_object_by_name(model, name): def _get_db_object_by_name(model, name):
@ -259,19 +295,6 @@ def delete_workbooks(**kwargs):
# Workflow definitions. # Workflow definitions.
WORKFLOW_COL_MAPPING = {
'id': models.WorkflowDefinition.id,
'name': models.WorkflowDefinition.name,
'input': models.WorkflowDefinition.spec,
'definition': models.WorkflowDefinition.definition,
'tags': models.WorkflowDefinition.tags,
'scope': models.WorkflowDefinition.scope,
'created_at': models.WorkflowDefinition.created_at,
'updated_at': models.WorkflowDefinition.updated_at
}
def get_workflow_definition(identifier): def get_workflow_definition(identifier):
"""Gets workflow definition by name or uuid. """Gets workflow definition by name or uuid.
@ -306,29 +329,18 @@ def load_workflow_definition(name):
return _get_workflow_definition(name) return _get_workflow_definition(name)
def get_workflow_definitions(limit=None, marker=None, sort_keys=None, def get_workflow_definitions(sort_keys=['created_at'], fields=None, **kwargs):
sort_dirs=None, fields=None, **kwargs): if fields and 'input' in fields:
columns = ( fields.remove('input')
tuple(WORKFLOW_COL_MAPPING.get(f) for f in fields) if fields else () fields.append('spec')
return _get_collection(
model=models.WorkflowDefinition,
sort_keys=sort_keys,
fields=fields,
**kwargs
) )
query = _secure_query(models.WorkflowDefinition, *columns)
try:
return _paginate_query(
models.WorkflowDefinition,
limit,
marker,
sort_keys,
sort_dirs,
query
)
except Exception as e:
raise exc.DBQueryEntryError(
"Failed when querying database, error type: %s, "
"error message: %s" % (e.__class__.__name__, e.message)
)
@b.session_aware() @b.session_aware()
def create_workflow_definition(values, session=None): def create_workflow_definition(values, session=None):
@ -471,24 +483,12 @@ def load_action_definition(name):
return _get_action_definition(name) return _get_action_definition(name)
def get_action_definitions(limit=None, marker=None, sort_keys=['name'], def get_action_definitions(sort_keys=['name'], **kwargs):
sort_dirs=None, **kwargs): return _get_collection(
query = _secure_query(models.ActionDefinition).filter_by(**kwargs) model=models.ActionDefinition,
sort_keys=sort_keys,
try: **kwargs
return _paginate_query( )
models.ActionDefinition,
limit,
marker,
sort_keys,
sort_dirs,
query
)
except Exception as e:
raise exc.DBQueryEntryError(
"Failed when querying database, error type: %s, "
"error message: %s" % (e.__class__.__name__, e.message)
)
@b.session_aware() @b.session_aware()
@ -747,24 +747,12 @@ def ensure_workflow_execution_exists(id):
get_workflow_execution(id) get_workflow_execution(id)
def get_workflow_executions(limit=None, marker=None, sort_keys=['created_at'], def get_workflow_executions(sort_keys=['created_at'], **kwargs):
sort_dirs=None, **kwargs): return _get_collection(
query = _secure_query(models.WorkflowExecution).filter_by(**kwargs) models.WorkflowExecution,
sort_keys=sort_keys,
try: **kwargs
return _paginate_query( )
models.WorkflowExecution,
limit,
marker,
sort_keys,
sort_dirs,
query
)
except Exception as e:
raise exc.DBQueryEntryError(
"Failed when quering database, error type: %s, "
"error message: %s" % (e.__class__.__name__, e.message)
)
@b.session_aware() @b.session_aware()
@ -1144,10 +1132,15 @@ def _get_cron_trigger(name):
return _get_db_object_by_name(models.CronTrigger, name) return _get_db_object_by_name(models.CronTrigger, name)
def _get_cron_triggers(**kwargs): def _get_cron_triggers(*columns, **kwargs):
query = b.model_query(models.CronTrigger) query = b.model_query(models.CronTrigger)
return query.filter_by(**kwargs).all() return _get_collection(
models.CronTrigger,
query=query,
*columns,
**kwargs
)
# Environments. # Environments.

View File

@ -185,10 +185,10 @@ class TestActionExecutionsController(base.APITest):
self.assertEqual(201, resp.status_int) self.assertEqual(201, resp.status_int)
action_exec = ACTION_EX action_exec = copy.deepcopy(ACTION_EX)
del action_exec['task_name'] del action_exec['task_name']
self.assertDictEqual(ACTION_EX, resp.json) self.assertDictEqual(action_exec, resp.json)
f.assert_called_once_with( f.assert_called_once_with(
ACTION_EX['name'], ACTION_EX['name'],
@ -212,10 +212,10 @@ class TestActionExecutionsController(base.APITest):
self.assertEqual(201, resp.status_int) self.assertEqual(201, resp.status_int)
action_exec = ACTION_EX action_exec = copy.deepcopy(ACTION_EX)
del action_exec['task_name'] del action_exec['task_name']
self.assertDictEqual(ACTION_EX, resp.json) self.assertDictEqual(action_exec, resp.json)
f.assert_called_once_with( f.assert_called_once_with(
ACTION_EX['name'], ACTION_EX['name'],

View File

@ -19,11 +19,15 @@ import json
import pecan import pecan
import six import six
from oslo_log import log as logging
from webob import Response from webob import Response
from wsme import exc as wsme_exc from wsme import exc as wsme_exc
from mistral import exceptions as exc from mistral import exceptions as exc
LOG = logging.getLogger(__name__)
def wrap_wsme_controller_exception(func): def wrap_wsme_controller_exception(func):
"""Decorator for controllers method. """Decorator for controllers method.
@ -102,3 +106,95 @@ def validate_fields(fields, object_fields):
raise wsme_exc.ClientSideError( raise wsme_exc.ClientSideError(
'Field(s) %s are invalid.' % ', '.join(invalid_fields) 'Field(s) %s are invalid.' % ', '.join(invalid_fields)
) )
def get_all(list_cls, cls, get_all_function, get_function,
resource_function=None, marker=None, limit=None,
sort_keys='created_at', sort_dirs='asc', fields='', **filters):
"""Return a list of cls.
:param list_cls: Collection class (e.g.: Actions, Workflows, ...).
:param cls: Class (e.g.: Action, Workflow, ...).
:param get_all_function: Request function to get all elements with
filtering (limit, marker, sort_keys, sort_dirs,
fields)
:param get_function: Function used to fetch the marker
:param resource_function: Optional, function used to fetch additional data
:param marker: Optional. Pagination marker for large data sets.
:param limit: Optional. Maximum number of resources to return in a
single result. Default value is None for backward
compatibility.
:param sort_keys: Optional. Columns to sort results by.
Default: created_at.
:param sort_dirs: Optional. Directions to sort corresponding to
sort_keys, "asc" or "desc" can be choosed.
Default: asc.
:param fields: Optional. A specified list of fields of the resource to
be returned. 'id' will be included automatically in
fields if it's provided, since it will be used when
constructing 'next' link.
:param filters: Optional. A specified dictionary of filters to match.
"""
if fields and 'id' not in fields:
fields.insert(0, 'id')
validate_query_params(limit, sort_keys, sort_dirs)
validate_fields(fields, cls.get_fields())
marker_obj = None
if marker:
marker_obj = get_function(marker)
list_to_return = []
if resource_function:
# do not filter fields yet, resource_function needs the ORM object
db_list = get_all_function(
limit=limit,
marker=marker_obj,
sort_keys=sort_keys,
sort_dirs=sort_dirs,
**filters
)
for data in db_list:
obj = resource_function(data)
# filter fields using a loop instead of the ORM
if fields:
data = []
for f in fields:
if hasattr(obj, f):
data.append(getattr(obj, f))
dict_data = dict(zip(fields, data))
else:
dict_data = obj.to_dict()
list_to_return.append(cls.from_dict(dict_data))
else:
db_list = get_all_function(
limit=limit,
marker=marker_obj,
sort_keys=sort_keys,
sort_dirs=sort_dirs,
fields=fields,
**filters
)
for data in db_list:
dict_data = (dict(zip(fields, data)) if fields else
data.to_dict())
list_to_return.append(cls.from_dict(dict_data))
return list_cls.convert_with_links(
list_to_return,
limit,
pecan.request.host_url,
sort_keys=','.join(sort_keys),
sort_dirs=','.join(sort_dirs),
fields=','.join(fields) if fields else '',
**filters
)