Fix dynamic actions further
* Reworked /code_sources and /dynamic_actions API endpoints to simplify them. For now they don't work with multiple objects and they are consistent with other endpoints. If needed, we'll add support for multiple objects (i.e. adding multiple dynamic actions with a single request) later in a backwards compatible manner. * Simplified unit tests. * Got rid of services/*.py modules since they didn't do anything useful. They were just wrappers around DB API calls. Change-Id: Ib5a53f1f1a185f0395ffae1ab0c401633fcdd0fc
This commit is contained in:
parent
f78f33507e
commit
a73fe5b8a3
|
@ -137,7 +137,7 @@ def _get_python_module(code_source_id, namespace=''):
|
|||
namespace=namespace
|
||||
)
|
||||
|
||||
mod = _load_python_module(code_source.name, code_source.src)
|
||||
mod = _load_python_module(code_source.name, code_source.content)
|
||||
|
||||
return mod, code_source.version
|
||||
|
||||
|
@ -177,7 +177,7 @@ class DynamicActionProvider(ml_actions.ActionProvider):
|
|||
# Reload module.
|
||||
code_src = db_api.get_code_source(code_src_id)
|
||||
|
||||
module = _load_python_module(code_src.name, code_src.src)
|
||||
module = _load_python_module(code_src.name, code_src.content)
|
||||
|
||||
self._code_sources[code_src_id] = (module, code_src.version)
|
||||
else:
|
||||
|
|
|
@ -60,6 +60,7 @@ class Resource(wtypes.Base):
|
|||
res = "%s [" % type(self).__name__
|
||||
|
||||
first = True
|
||||
|
||||
for attr in self._wsme_attributes:
|
||||
if not first:
|
||||
res += ', '
|
||||
|
|
|
@ -28,7 +28,6 @@ from mistral.api.hooks import content_type as ct_hook
|
|||
from mistral import context
|
||||
|
||||
from mistral.db.v2 import api as db_api
|
||||
from mistral.services import code_sources
|
||||
|
||||
from mistral.utils import filter_utils
|
||||
from mistral.utils import rest_utils
|
||||
|
@ -40,32 +39,76 @@ class CodeSourcesController(rest.RestController, hooks.HookController):
|
|||
__hooks__ = [ct_hook.ContentTypeHook("application/json", ['POST', 'PUT'])]
|
||||
|
||||
@rest_utils.wrap_pecan_controller_exception
|
||||
@pecan.expose(content_type="multipart/form-data")
|
||||
def post(self, namespace='', **files):
|
||||
"""Creates new Code Sources.
|
||||
@pecan.expose(content_type="text/plain")
|
||||
def post(self, name, scope='private', namespace=''):
|
||||
"""Creates new code sources.
|
||||
|
||||
:param name: Code source name (i.e. the name of the module).
|
||||
:param scope: Optional. Scope (private or public).
|
||||
:param namespace: Optional. The namespace to create the code sources
|
||||
in.
|
||||
:params **files: a list of files to create code sources from,
|
||||
the variable name of the file will be the module name
|
||||
"""
|
||||
|
||||
acl.enforce('code_sources:create', context.ctx())
|
||||
|
||||
# Extract content directly from the request.
|
||||
content = pecan.request.text
|
||||
|
||||
LOG.debug(
|
||||
'Creating Code Sources with names: %s in namespace:[%s]',
|
||||
files.keys(),
|
||||
'Creating code source [names=%s, scope=%s, namespace=%s]',
|
||||
name,
|
||||
scope,
|
||||
namespace
|
||||
)
|
||||
|
||||
code_sources_db = code_sources.create_code_sources(namespace, **files)
|
||||
db_model = rest_utils.rest_retry_on_db_error(
|
||||
db_api.create_code_source)(
|
||||
{
|
||||
'name': name,
|
||||
'content': content,
|
||||
'namespace': namespace,
|
||||
'scope': scope,
|
||||
'version': 1,
|
||||
}
|
||||
)
|
||||
|
||||
code_sources_list = [
|
||||
resources.CodeSource.from_db_model(db_cs)
|
||||
for db_cs in code_sources_db
|
||||
]
|
||||
pecan.response.status = 201
|
||||
|
||||
return resources.CodeSources(code_sources=code_sources_list).to_json()
|
||||
return resources.CodeSource.from_db_model(db_model).to_json()
|
||||
|
||||
@rest_utils.wrap_pecan_controller_exception
|
||||
@pecan.expose(content_type="text/plain")
|
||||
def put(self, identifier, scope='private', namespace=''):
|
||||
"""Update code source.
|
||||
|
||||
:param identifier: Identifier (name or ID) of the code source.
|
||||
:param scope: Scope (private or public) of the code source.
|
||||
:param namespace: Optional. The namespace of the code source.
|
||||
"""
|
||||
acl.enforce('code_sources:update', context.ctx())
|
||||
|
||||
LOG.debug(
|
||||
'Updating code source [identifier(name or id)=%s, scope=%s,'
|
||||
' namespace=%s]',
|
||||
identifier,
|
||||
scope,
|
||||
namespace
|
||||
)
|
||||
|
||||
content = pecan.request.text
|
||||
|
||||
db_model = rest_utils.rest_retry_on_db_error(
|
||||
db_api.update_code_source
|
||||
)(
|
||||
identifier=identifier,
|
||||
namespace=namespace,
|
||||
values={
|
||||
'scope': scope,
|
||||
'content': content
|
||||
}
|
||||
)
|
||||
|
||||
return resources.CodeSource.from_db_model(db_model).to_json()
|
||||
|
||||
@wsme_pecan.wsexpose(resources.CodeSources, types.uuid, int,
|
||||
types.uniquelist, types.list, types.uniquelist,
|
||||
|
@ -73,8 +116,7 @@ class CodeSourcesController(rest.RestController, hooks.HookController):
|
|||
resources.SCOPE_TYPES, types.uuid, wtypes.text,
|
||||
wtypes.text, bool, wtypes.text)
|
||||
def get_all(self, marker=None, limit=None, sort_keys='created_at',
|
||||
sort_dirs='asc', fields='', name=None,
|
||||
tags=None, scope=None,
|
||||
sort_dirs='asc', fields='', name=None, tags=None, scope=None,
|
||||
project_id=None, created_at=None, updated_at=None,
|
||||
all_projects=False, namespace=None):
|
||||
"""Return a list of Code Sources.
|
||||
|
@ -95,9 +137,6 @@ class CodeSourcesController(rest.RestController, hooks.HookController):
|
|||
:param name: Optional. Keep only resources with a specific name.
|
||||
:param namespace: Optional. Keep only resources with a specific
|
||||
namespace
|
||||
:param input: Optional. Keep only resources with a specific input.
|
||||
:param definition: Optional. Keep only resources with a specific
|
||||
definition.
|
||||
:param tags: Optional. Keep only resources containing specific tags.
|
||||
:param scope: Optional. Keep only resources with a specific scope.
|
||||
:param project_id: Optional. The same as the requester project_id
|
||||
|
@ -150,7 +189,7 @@ class CodeSourcesController(rest.RestController, hooks.HookController):
|
|||
@rest_utils.wrap_wsme_controller_exception
|
||||
@wsme_pecan.wsexpose(resources.CodeSource, wtypes.text, wtypes.text)
|
||||
def get(self, identifier, namespace=''):
|
||||
"""Return the named Code Source.
|
||||
"""Return a code source.
|
||||
|
||||
:param identifier: Name or UUID of the code source to retrieve.
|
||||
:param namespace: Optional. Namespace of the code source to retrieve.
|
||||
|
@ -159,13 +198,13 @@ class CodeSourcesController(rest.RestController, hooks.HookController):
|
|||
acl.enforce('code_sources:get', context.ctx())
|
||||
|
||||
LOG.debug(
|
||||
'Fetch Code Source [identifier=%s], [namespace=%s]',
|
||||
'Fetch code source [identifier=%s, namespace=%s]',
|
||||
identifier,
|
||||
namespace
|
||||
)
|
||||
|
||||
db_model = rest_utils.rest_retry_on_db_error(
|
||||
code_sources.get_code_source)(
|
||||
db_api.get_code_source)(
|
||||
identifier=identifier,
|
||||
namespace=namespace
|
||||
)
|
||||
|
@ -175,7 +214,7 @@ class CodeSourcesController(rest.RestController, hooks.HookController):
|
|||
@rest_utils.wrap_pecan_controller_exception
|
||||
@wsme_pecan.wsexpose(None, wtypes.text, wtypes.text, status_code=204)
|
||||
def delete(self, identifier, namespace=''):
|
||||
"""Delete a Code Source.
|
||||
"""Delete a code source.
|
||||
|
||||
:param identifier: Name or ID of Code Source to delete.
|
||||
:param namespace: Optional. Namespace of the Code Source to delete.
|
||||
|
@ -184,39 +223,12 @@ class CodeSourcesController(rest.RestController, hooks.HookController):
|
|||
acl.enforce('code_sources:delete', context.ctx())
|
||||
|
||||
LOG.debug(
|
||||
'Delete Code Source [identifier=%s, namespace=%s]',
|
||||
'Delete code source [identifier=%s, namespace=%s]',
|
||||
identifier,
|
||||
namespace
|
||||
)
|
||||
|
||||
rest_utils.rest_retry_on_db_error(
|
||||
code_sources.delete_code_source
|
||||
)(
|
||||
rest_utils.rest_retry_on_db_error(db_api.delete_code_source)(
|
||||
identifier=identifier,
|
||||
namespace=namespace
|
||||
)
|
||||
|
||||
@rest_utils.wrap_pecan_controller_exception
|
||||
@pecan.expose(content_type="multipart/form-data")
|
||||
def put(self, namespace='', **files):
|
||||
"""Update Code Sources.
|
||||
|
||||
:param namespace: Optional. The namespace of the code sources.
|
||||
:params **files: a list of files to update code sources from.
|
||||
"""
|
||||
acl.enforce('code_sources:update', context.ctx())
|
||||
|
||||
LOG.debug(
|
||||
'Updating Code Sources with names: %s in namespace:[%s]',
|
||||
files.keys(),
|
||||
namespace
|
||||
)
|
||||
|
||||
code_sources_db = code_sources.update_code_sources(namespace, **files)
|
||||
|
||||
code_sources_list = [
|
||||
resources.CodeSource.from_db_model(db_cs)
|
||||
for db_cs in code_sources_db
|
||||
]
|
||||
|
||||
return resources.CodeSources(code_sources=code_sources_list).to_json()
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
# limitations under the License.
|
||||
|
||||
from oslo_log import log as logging
|
||||
import pecan
|
||||
from pecan import hooks
|
||||
from pecan import rest
|
||||
from wsme import types as wtypes
|
||||
|
@ -25,10 +24,9 @@ from mistral.api.controllers.v2 import resources
|
|||
from mistral.api.controllers.v2 import types
|
||||
from mistral.api.hooks import content_type as ct_hook
|
||||
from mistral import context
|
||||
from mistral.utils import safe_yaml
|
||||
from mistral import exceptions as exc
|
||||
|
||||
from mistral.db.v2 import api as db_api
|
||||
from mistral.services import dynamic_actions
|
||||
|
||||
from mistral.utils import filter_utils
|
||||
from mistral.utils import rest_utils
|
||||
|
@ -40,33 +38,94 @@ class DynamicActionsController(rest.RestController, hooks.HookController):
|
|||
__hooks__ = [ct_hook.ContentTypeHook("application/json", ['POST', 'PUT'])]
|
||||
|
||||
@rest_utils.wrap_pecan_controller_exception
|
||||
@pecan.expose(content_type="text/plain")
|
||||
def post(self, namespace=''):
|
||||
"""Creates new dynamic actions.
|
||||
@wsme_pecan.wsexpose(
|
||||
resources.DynamicAction,
|
||||
body=resources.DynamicAction,
|
||||
status_code=201
|
||||
)
|
||||
def post(self, dyn_action):
|
||||
"""Creates new dynamic action.
|
||||
|
||||
:param namespace: Optional. The namespace to create the actions in.
|
||||
|
||||
The text is allowed to have multiple actions. In such case, they all
|
||||
will be created.
|
||||
:param dyn_action: Dynamic action to create.
|
||||
"""
|
||||
acl.enforce('dynamic_actions:create', context.ctx())
|
||||
|
||||
actions = safe_yaml.load(pecan.request.text)
|
||||
LOG.debug('Creating dynamic action [action=%s]', dyn_action)
|
||||
|
||||
LOG.debug(
|
||||
'Creating dynamic actions with names: %s in namespace:[%s]',
|
||||
actions,
|
||||
namespace
|
||||
if not dyn_action.code_source_id and not dyn_action.code_source_name:
|
||||
raise exc.InputException(
|
||||
"Either 'code_source_id' or 'code_source_name'"
|
||||
" must be provided."
|
||||
)
|
||||
|
||||
code_source = db_api.get_code_source(
|
||||
dyn_action.code_source_id or dyn_action.code_source_name,
|
||||
namespace=dyn_action.namespace
|
||||
)
|
||||
|
||||
actions_db = dynamic_actions.create_dynamic_actions(actions, namespace)
|
||||
# TODO(rakhmerov): Ideally we also need to check if the specified
|
||||
# class exists in the specified code source. But probably it's not
|
||||
# a controller responsibility.
|
||||
|
||||
actions_list = [
|
||||
resources.DynamicAction.from_db_model(action)
|
||||
for action in actions_db
|
||||
]
|
||||
db_model = rest_utils.rest_retry_on_db_error(
|
||||
db_api.create_dynamic_action_definition
|
||||
)(
|
||||
{
|
||||
'name': dyn_action.name,
|
||||
'namespace': dyn_action.namespace,
|
||||
'class_name': dyn_action.class_name,
|
||||
'code_source_id': code_source.id,
|
||||
'code_source_name': code_source.name
|
||||
}
|
||||
)
|
||||
|
||||
return resources.DynamicActions(dynamic_actions=actions_list).to_json()
|
||||
return resources.DynamicAction.from_db_model(db_model)
|
||||
|
||||
@rest_utils.wrap_pecan_controller_exception
|
||||
@wsme_pecan.wsexpose(
|
||||
resources.DynamicAction,
|
||||
body=resources.DynamicAction
|
||||
)
|
||||
def put(self, dyn_action):
|
||||
"""Update dynamic action.
|
||||
|
||||
:param dyn_action: Dynamic action to create.
|
||||
"""
|
||||
acl.enforce('dynamic_actions:update', context.ctx())
|
||||
|
||||
LOG.debug('Updating dynamic action [action=%s]', dyn_action)
|
||||
|
||||
if not dyn_action.id and not dyn_action.name:
|
||||
raise exc.InputException("Either 'name' or 'id' must be provided.")
|
||||
|
||||
values = {'class_name': dyn_action.class_name}
|
||||
|
||||
if dyn_action.scope:
|
||||
values['scope'] = dyn_action.scope
|
||||
|
||||
# A client may also want to update a source code.
|
||||
if dyn_action.code_source_id or dyn_action.code_source_name:
|
||||
code_source = db_api.get_code_source(
|
||||
dyn_action.code_source_id or dyn_action.code_source_name,
|
||||
namespace=dyn_action.namespace
|
||||
)
|
||||
|
||||
values['code_source_id'] = code_source.id
|
||||
values['code_source_name'] = code_source.name
|
||||
|
||||
# TODO(rakhmerov): Ideally we also need to check if the specified
|
||||
# class exists in the specified code source. But probably it's not
|
||||
# a controller responsibility.
|
||||
|
||||
db_model = rest_utils.rest_retry_on_db_error(
|
||||
db_api.update_dynamic_action_definition
|
||||
)(
|
||||
dyn_action.id or dyn_action.name,
|
||||
values,
|
||||
namespace=dyn_action.namespace
|
||||
)
|
||||
|
||||
return resources.DynamicAction.from_db_model(db_model)
|
||||
|
||||
@wsme_pecan.wsexpose(resources.DynamicActions, types.uuid, int,
|
||||
types.uniquelist, types.list, types.uniquelist,
|
||||
|
@ -159,13 +218,14 @@ class DynamicActionsController(rest.RestController, hooks.HookController):
|
|||
acl.enforce('dynamic_actions:get', context.ctx())
|
||||
|
||||
LOG.debug(
|
||||
'Fetch Action [identifier=%s], [namespace=%s]',
|
||||
'Fetch dynamic action [identifier=%s, namespace=%s]',
|
||||
identifier,
|
||||
namespace
|
||||
)
|
||||
|
||||
db_model = rest_utils.rest_retry_on_db_error(
|
||||
dynamic_actions.get_dynamic_action)(
|
||||
db_api.get_dynamic_action_definition
|
||||
)(
|
||||
identifier=identifier,
|
||||
namespace=namespace
|
||||
)
|
||||
|
@ -173,53 +233,25 @@ class DynamicActionsController(rest.RestController, hooks.HookController):
|
|||
return resources.DynamicAction.from_db_model(db_model)
|
||||
|
||||
@rest_utils.wrap_pecan_controller_exception
|
||||
@pecan.expose(content_type="multipart/form-data")
|
||||
@wsme_pecan.wsexpose(None, wtypes.text, wtypes.text, status_code=204)
|
||||
def delete(self, identifier, namespace=''):
|
||||
"""Delete an Action.
|
||||
"""Delete a dynamic action.
|
||||
|
||||
:param identifier: Name or ID of Action to delete.
|
||||
:param namespace: Optional. Namespace of the Action to delete.
|
||||
:param identifier: Name or ID of the action to delete.
|
||||
:param namespace: Optional. Namespace of the action to delete.
|
||||
"""
|
||||
|
||||
acl.enforce('dynamic_actions:delete', context.ctx())
|
||||
|
||||
LOG.debug(
|
||||
'Delete Action [identifier=%s, namespace=%s]',
|
||||
'Delete dynamic action [identifier=%s, namespace=%s]',
|
||||
identifier,
|
||||
namespace
|
||||
)
|
||||
|
||||
rest_utils.rest_retry_on_db_error(
|
||||
dynamic_actions.delete_dynamic_action)(
|
||||
db_api.delete_dynamic_action_definition
|
||||
)(
|
||||
identifier=identifier,
|
||||
namespace=namespace
|
||||
)
|
||||
|
||||
@rest_utils.wrap_pecan_controller_exception
|
||||
@pecan.expose(content_type="text/plain")
|
||||
def put(self, namespace=''):
|
||||
"""Update Actions.
|
||||
|
||||
:param namespace: Optional. The namespace to update the actions in.
|
||||
|
||||
The text is allowed to have multiple Actions, In such case,
|
||||
they all will be updated.
|
||||
"""
|
||||
acl.enforce('dynamic_actions:update', context.ctx())
|
||||
|
||||
actions = safe_yaml.load(pecan.request.text)
|
||||
|
||||
LOG.debug(
|
||||
'Updating Actions with names: %s in namespace:[%s]',
|
||||
actions.keys(),
|
||||
namespace
|
||||
)
|
||||
|
||||
actions_db = dynamic_actions.update_dynamic_actions(actions, namespace)
|
||||
|
||||
actions_list = [
|
||||
resources.DynamicAction.from_db_model(action)
|
||||
for action in actions_db
|
||||
]
|
||||
|
||||
return resources.DynamicActions(dynamic_actions=actions_list).to_json()
|
||||
|
|
|
@ -190,16 +190,17 @@ class CodeSource(resource.Resource, ScopedResource):
|
|||
|
||||
id = wtypes.text
|
||||
name = wtypes.text
|
||||
src = wtypes.text
|
||||
scope = SCOPE_TYPES
|
||||
namespace = wtypes.text
|
||||
content = wtypes.text
|
||||
version = wtypes.IntegerType(minimum=1)
|
||||
|
||||
project_id = wsme.wsattr(wtypes.text, readonly=True)
|
||||
scope = SCOPE_TYPES
|
||||
|
||||
actions = [wtypes.text]
|
||||
|
||||
created_at = wtypes.text
|
||||
updated_at = wtypes.text
|
||||
namespace = wtypes.text
|
||||
|
||||
@classmethod
|
||||
def sample(cls):
|
||||
|
@ -216,24 +217,6 @@ class CodeSource(resource.Resource, ScopedResource):
|
|||
namespace=''
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_db_model(cls, db_model):
|
||||
return CodeSource(
|
||||
id=getattr(db_model, 'id', db_model.name),
|
||||
name=db_model.name,
|
||||
version=db_model.version,
|
||||
src=db_model.src,
|
||||
namespace=db_model.namespace,
|
||||
project_id=db_model.project_id,
|
||||
scope=db_model.scope,
|
||||
created_at=utils.datetime_to_str(
|
||||
getattr(db_model, 'created_at', '')
|
||||
),
|
||||
updated_at=utils.datetime_to_str(
|
||||
getattr(db_model, 'updated_at', '')
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class CodeSources(resource.ResourceList):
|
||||
"""A collection of CodeSources."""
|
||||
|
@ -264,13 +247,16 @@ class DynamicAction(resource.Resource, ScopedResource):
|
|||
|
||||
id = wtypes.text
|
||||
name = wtypes.text
|
||||
namespace = wsme.wsattr(wtypes.text, default='')
|
||||
code_source_id = wtypes.text
|
||||
code_source_name = wtypes.text
|
||||
class_name = wtypes.text
|
||||
|
||||
project_id = wsme.wsattr(wtypes.text, readonly=True)
|
||||
scope = SCOPE_TYPES
|
||||
|
||||
created_at = wtypes.text
|
||||
updated_at = wtypes.text
|
||||
namespace = wtypes.text
|
||||
|
||||
@classmethod
|
||||
def sample(cls):
|
||||
|
@ -279,6 +265,7 @@ class DynamicAction(resource.Resource, ScopedResource):
|
|||
name='actionName',
|
||||
class_name='className',
|
||||
code_source_id='233e4567-354b-12d3-4444-426655444444',
|
||||
code_source_name='my_sample_module',
|
||||
scope='private',
|
||||
project_id='a7eb669e9819420ea4bd1453e672c0a7',
|
||||
created_at='1970-01-01T00:00:00.000000',
|
||||
|
@ -286,24 +273,6 @@ class DynamicAction(resource.Resource, ScopedResource):
|
|||
namespace=''
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_db_model(cls, db_model):
|
||||
return DynamicAction(
|
||||
id=getattr(db_model, 'id', db_model.name),
|
||||
name=db_model.name,
|
||||
code_source_id=db_model.code_source_id,
|
||||
class_name=db_model.class_name,
|
||||
namespace=db_model.namespace,
|
||||
project_id=db_model.project_id,
|
||||
scope=db_model.scope,
|
||||
created_at=utils.datetime_to_str(
|
||||
getattr(db_model, 'created_at', '')
|
||||
),
|
||||
updated_at=utils.datetime_to_str(
|
||||
getattr(db_model, 'updated_at', '')
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class DynamicActions(resource.ResourceList):
|
||||
"""A collection of DynamicActions."""
|
||||
|
|
|
@ -40,7 +40,8 @@ class WorkbooksController(rest.RestController, hooks.HookController):
|
|||
__hooks__ = [ct_hook.ContentTypeHook("application/json", ['POST', 'PUT'])]
|
||||
|
||||
validate = validation.SpecValidationController(
|
||||
spec_parser.get_workbook_spec_from_yaml)
|
||||
spec_parser.get_workbook_spec_from_yaml
|
||||
)
|
||||
|
||||
@rest_utils.wrap_wsme_controller_exception
|
||||
@wsme_pecan.wsexpose(resources.Workbook, wtypes.text, wtypes.text)
|
||||
|
@ -56,9 +57,12 @@ class WorkbooksController(rest.RestController, hooks.HookController):
|
|||
|
||||
# Use retries to prevent possible failures.
|
||||
r = rest_utils.create_db_retry_object()
|
||||
db_model = r.call(db_api.get_workbook,
|
||||
name,
|
||||
namespace=namespace)
|
||||
|
||||
db_model = r.call(
|
||||
db_api.get_workbook,
|
||||
name,
|
||||
namespace=namespace
|
||||
)
|
||||
|
||||
return resources.Workbook.from_db_model(db_model)
|
||||
|
||||
|
@ -68,9 +72,6 @@ class WorkbooksController(rest.RestController, hooks.HookController):
|
|||
"""Update a workbook.
|
||||
|
||||
:param namespace: Optional. Namespace of workbook to update.
|
||||
:param validate: Optional. If set to False, disables validation of
|
||||
the workflow YAML definition syntax, but only if allowed in the
|
||||
service configuration. By default, validation is enabled.
|
||||
"""
|
||||
|
||||
acl.enforce('workbooks:update', context.ctx())
|
||||
|
|
|
@ -47,7 +47,8 @@ class WorkflowsController(rest.RestController, hooks.HookController):
|
|||
__hooks__ = [ct_hook.ContentTypeHook("application/json", ['POST', 'PUT'])]
|
||||
|
||||
validate = validation.SpecValidationController(
|
||||
spec_parser.get_workflow_list_spec_from_yaml)
|
||||
spec_parser.get_workflow_list_spec_from_yaml
|
||||
)
|
||||
|
||||
@pecan.expose()
|
||||
def _lookup(self, identifier, sub_resource, *remainder):
|
||||
|
|
|
@ -38,7 +38,7 @@ def upgrade():
|
|||
sa.Column('name', sa.String(length=255), nullable=False),
|
||||
sa.Column('project_id', sa.String(length=80), nullable=True),
|
||||
sa.Column('namespace', sa.String(length=255), nullable=True),
|
||||
sa.Column('src', sa.TEXT, nullable=False),
|
||||
sa.Column('content', sa.TEXT, nullable=False),
|
||||
sa.Column('version', sa.Integer, nullable=False),
|
||||
sa.Column('tags', st.JsonEncoded(), nullable=True),
|
||||
sa.Column('scope', sa.String(length=80), nullable=True),
|
||||
|
@ -61,6 +61,7 @@ def upgrade():
|
|||
sa.Column('scope', sa.String(length=80), nullable=True),
|
||||
sa.Column('project_id', sa.String(length=80), nullable=True),
|
||||
sa.Column('code_source_id', sa.String(length=36), nullable=False),
|
||||
sa.Column('code_source_name', sa.String(length=255), nullable=False),
|
||||
sa.Column('namespace', sa.String(length=255), nullable=True),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=True),
|
||||
|
|
|
@ -184,10 +184,6 @@ def create_dynamic_action_definition(values):
|
|||
return IMPL.create_dynamic_action_definition(values)
|
||||
|
||||
|
||||
def delete_dynamic_action_definition(identifier, namespace=''):
|
||||
return IMPL.delete_dynamic_action_definition(identifier, namespace)
|
||||
|
||||
|
||||
def update_dynamic_action_definition(identifier, values, namespace=''):
|
||||
return IMPL.update_dynamic_action_definition(identifier, values, namespace)
|
||||
|
||||
|
@ -204,12 +200,24 @@ def get_dynamic_action_definitions(limit=None, marker=None, sort_keys=None,
|
|||
)
|
||||
|
||||
|
||||
def delete_dynamic_action_definition(identifier, namespace=''):
|
||||
return IMPL.delete_dynamic_action_definition(identifier, namespace)
|
||||
|
||||
|
||||
def delete_dynamic_action_definitions(**kwargs):
|
||||
return IMPL.delete_dynamic_action_definitions(**kwargs)
|
||||
|
||||
|
||||
# Code sources.
|
||||
|
||||
def get_code_source(identifier, namespace='', fields=()):
|
||||
return IMPL.get_code_source(identifier, fields, namespace=namespace)
|
||||
|
||||
|
||||
def load_code_source(identifier, namespace='', fields=()):
|
||||
return IMPL.load_code_source(identifier, fields, namespace=namespace)
|
||||
|
||||
|
||||
def create_code_source(values):
|
||||
return IMPL.create_code_source(values)
|
||||
|
||||
|
@ -218,10 +226,6 @@ def update_code_source(identifier, values, namespace=''):
|
|||
return IMPL.update_code_source(identifier, values, namespace=namespace)
|
||||
|
||||
|
||||
def delete_code_source(name, namespace=''):
|
||||
return IMPL.delete_code_source(name, namespace=namespace)
|
||||
|
||||
|
||||
def get_code_sources(limit=None, marker=None, sort_keys=None,
|
||||
sort_dirs=None, fields=None, **kwargs):
|
||||
return IMPL.get_code_sources(
|
||||
|
@ -234,6 +238,14 @@ def get_code_sources(limit=None, marker=None, sort_keys=None,
|
|||
)
|
||||
|
||||
|
||||
def delete_code_source(identifier, namespace=''):
|
||||
return IMPL.delete_code_source(identifier, namespace=namespace)
|
||||
|
||||
|
||||
def delete_code_sources(**kwargs):
|
||||
return IMPL.delete_code_sources(**kwargs)
|
||||
|
||||
|
||||
# Action definitions.
|
||||
|
||||
def get_action_definition_by_id(id, fields=()):
|
||||
|
|
|
@ -687,6 +687,16 @@ def get_code_source(identifier, fields=(), session=None, namespace=''):
|
|||
return code_src
|
||||
|
||||
|
||||
@b.session_aware()
|
||||
def load_code_source(identifier, fields=(), session=None, namespace=''):
|
||||
return _get_db_object_by_name_and_namespace_or_id(
|
||||
models.CodeSource,
|
||||
identifier,
|
||||
namespace=namespace,
|
||||
columns=fields
|
||||
)
|
||||
|
||||
|
||||
@b.session_aware()
|
||||
def update_code_source(identifier, values, namespace='', session=None):
|
||||
code_src = get_code_source(identifier, namespace=namespace)
|
||||
|
@ -709,6 +719,11 @@ def delete_code_source(identifier, namespace='', session=None):
|
|||
session.delete(code_src)
|
||||
|
||||
|
||||
@b.session_aware()
|
||||
def delete_code_sources(session=None, **kwargs):
|
||||
return _delete_all(models.CodeSource, **kwargs)
|
||||
|
||||
|
||||
# Dynamic actions.
|
||||
|
||||
@b.session_aware()
|
||||
|
@ -785,6 +800,11 @@ def delete_dynamic_action_definition(identifier, namespace='', session=None):
|
|||
session.delete(action_def)
|
||||
|
||||
|
||||
@b.session_aware()
|
||||
def delete_dynamic_action_definitions(session=None, **kwargs):
|
||||
return _delete_all(models.DynamicActionDefinition, **kwargs)
|
||||
|
||||
|
||||
# Action definitions.
|
||||
|
||||
@b.session_aware()
|
||||
|
|
|
@ -213,7 +213,7 @@ class CodeSource(mb.MistralSecureModelBase):
|
|||
# Main properties.
|
||||
id = mb.id_column()
|
||||
name = sa.Column(sa.String(255))
|
||||
src = sa.Column(sa.Text())
|
||||
content = sa.Column(sa.Text())
|
||||
version = sa.Column(sa.Integer())
|
||||
namespace = sa.Column(sa.String(255), nullable=True)
|
||||
tags = sa.Column(st.JsonListType())
|
||||
|
@ -238,6 +238,7 @@ class DynamicActionDefinition(mb.MistralSecureModelBase):
|
|||
name = sa.Column(sa.String(255))
|
||||
namespace = sa.Column(sa.String(255), nullable=True)
|
||||
class_name = sa.Column(sa.String(255))
|
||||
code_source_name = sa.Column(sa.String(255))
|
||||
|
||||
|
||||
DynamicActionDefinition.code_source_id = sa.Column(
|
||||
|
@ -246,6 +247,12 @@ DynamicActionDefinition.code_source_id = sa.Column(
|
|||
nullable=False
|
||||
)
|
||||
|
||||
DynamicActionDefinition.code_source = relationship(
|
||||
CodeSource,
|
||||
remote_side=CodeSource.id,
|
||||
lazy='select'
|
||||
)
|
||||
|
||||
|
||||
# Execution objects.
|
||||
|
||||
|
|
|
@ -1,95 +0,0 @@
|
|||
# Copyright 2020 Nokia Software.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
from oslo_log import log as logging
|
||||
|
||||
from mistral.db.v2 import api as db_api
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
_SYSTEM_PROVIDER = None
|
||||
_TEST_PROVIDER = None
|
||||
|
||||
|
||||
def create_code_source(name, src_code, namespace='', version=1):
|
||||
with db_api.transaction():
|
||||
return db_api.create_code_source({
|
||||
'name': name,
|
||||
'namespace': namespace,
|
||||
'version': version,
|
||||
'src': src_code,
|
||||
})
|
||||
|
||||
|
||||
def create_code_sources(namespace='', **files):
|
||||
return _update_or_create_code_sources(
|
||||
create_code_source,
|
||||
namespace,
|
||||
**files
|
||||
)
|
||||
|
||||
|
||||
def _update_or_create_code_sources(operation, namespace='', **files):
|
||||
code_sources = []
|
||||
|
||||
for file in files:
|
||||
filename = files[file].name
|
||||
file_content = files[file].file.read().decode()
|
||||
|
||||
code_sources.append(
|
||||
operation(
|
||||
filename,
|
||||
file_content,
|
||||
namespace
|
||||
)
|
||||
)
|
||||
|
||||
return code_sources
|
||||
|
||||
|
||||
def update_code_sources(namespace='', **files):
|
||||
return _update_or_create_code_sources(
|
||||
update_code_source,
|
||||
namespace,
|
||||
**files
|
||||
)
|
||||
|
||||
|
||||
def update_code_source(identifier, src_code, namespace=''):
|
||||
with db_api.transaction():
|
||||
return db_api.update_code_source(
|
||||
identifier=identifier,
|
||||
namespace=namespace,
|
||||
values={'src': src_code}
|
||||
)
|
||||
|
||||
|
||||
def delete_code_source(identifier, namespace=''):
|
||||
with db_api.transaction():
|
||||
db_api.delete_code_source(identifier, namespace=namespace)
|
||||
|
||||
|
||||
def delete_code_sources(code_sources, namespace=''):
|
||||
with db_api.transaction():
|
||||
for code_source in code_sources:
|
||||
db_api.delete_code_source(code_source, namespace=namespace)
|
||||
|
||||
|
||||
def get_code_source(identifier, namespace='', fields=()):
|
||||
return db_api.get_code_source(
|
||||
identifier,
|
||||
namespace=namespace,
|
||||
fields=fields
|
||||
)
|
|
@ -1,83 +0,0 @@
|
|||
# Copyright 2020 Nokia Software.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
from oslo_log import log as logging
|
||||
|
||||
from mistral.db.v2 import api as db_api
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def create_dynamic_actions(action_list, namespace=''):
|
||||
created_actions = []
|
||||
|
||||
with db_api.transaction():
|
||||
for action in action_list:
|
||||
created_actions.append(
|
||||
db_api.create_dynamic_action_definition({
|
||||
'name': action['name'],
|
||||
'class_name': action['class_name'],
|
||||
'namespace': namespace,
|
||||
'code_source_id': action['code_source_id']
|
||||
})
|
||||
)
|
||||
|
||||
return created_actions
|
||||
|
||||
|
||||
def delete_dynamic_action(identifier, namespace=''):
|
||||
with db_api.transaction():
|
||||
return db_api.delete_dynamic_action_definition(
|
||||
identifier,
|
||||
namespace
|
||||
)
|
||||
|
||||
|
||||
def get_dynamic_actions(limit=None, marker=None, sort_keys=None,
|
||||
sort_dirs=None, fields=None, **kwargs):
|
||||
with db_api.transaction():
|
||||
return db_api.get_dynamic_action_definitions(
|
||||
limit=limit,
|
||||
marker=marker,
|
||||
sort_keys=sort_keys,
|
||||
sort_dirs=sort_dirs,
|
||||
fields=fields,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
|
||||
def get_dynamic_action(identifier, namespace=''):
|
||||
with db_api.transaction():
|
||||
return db_api.get_dynamic_action_definition(
|
||||
identifier,
|
||||
namespace=namespace
|
||||
)
|
||||
|
||||
|
||||
def update_dynamic_action(identifier, values, namespace=''):
|
||||
with db_api.transaction():
|
||||
return db_api.update_dynamic_action_definition(
|
||||
identifier,
|
||||
values,
|
||||
namespace
|
||||
)
|
||||
|
||||
|
||||
def update_dynamic_actions(actions, namespace=''):
|
||||
return [
|
||||
update_dynamic_action(name, values, namespace)
|
||||
for name, values in actions.items()
|
||||
]
|
|
@ -13,8 +13,7 @@
|
|||
# limitations under the License.
|
||||
|
||||
from mistral.actions import dynamic_action
|
||||
from mistral.services import code_sources as code_sources_service
|
||||
from mistral.services import dynamic_actions as dynamic_actions_service
|
||||
from mistral.db.v2 import api as db_api
|
||||
from mistral.tests.unit import base
|
||||
|
||||
DUMMY_CODE_SOURCE = """from mistral_lib import actions
|
||||
|
@ -37,33 +36,36 @@ NAMESPACE = "ns"
|
|||
|
||||
class DynamicActionProviderTest(base.DbTestCase):
|
||||
def _create_code_source(self, namespace=''):
|
||||
return code_sources_service.create_code_source(
|
||||
name='code_source',
|
||||
src_code=DUMMY_CODE_SOURCE,
|
||||
namespace=namespace
|
||||
return db_api.create_code_source(
|
||||
{
|
||||
'name': 'code_source',
|
||||
'content': DUMMY_CODE_SOURCE,
|
||||
'namespace': namespace,
|
||||
'version': 0
|
||||
}
|
||||
)
|
||||
|
||||
def _delete_code_source(self):
|
||||
return code_sources_service.delete_code_source(
|
||||
identifier='code_source',
|
||||
)
|
||||
return db_api.delete_code_source('code_source')
|
||||
|
||||
def _create_dynamic_actions(self, code_source_id, namespace=''):
|
||||
actions = [
|
||||
def _create_dynamic_actions(self, code_source, namespace=''):
|
||||
db_api.create_dynamic_action_definition(
|
||||
{
|
||||
"name": "dummy_action",
|
||||
"namespace": namespace,
|
||||
"class_name": "DummyAction",
|
||||
"code_source_id": code_source_id
|
||||
},
|
||||
"code_source_id": code_source.id,
|
||||
"code_source_name": code_source.name
|
||||
}
|
||||
)
|
||||
db_api.create_dynamic_action_definition(
|
||||
{
|
||||
"name": "dummy_action2",
|
||||
"namespace": namespace,
|
||||
"class_name": "DummyAction2",
|
||||
"code_source_id": code_source_id
|
||||
}]
|
||||
|
||||
dynamic_actions_service.create_dynamic_actions(
|
||||
actions,
|
||||
namespace=namespace
|
||||
"code_source_id": code_source.id,
|
||||
"code_source_name": code_source.name
|
||||
}
|
||||
)
|
||||
|
||||
def test_dynamic_actions(self):
|
||||
|
@ -75,7 +77,7 @@ class DynamicActionProviderTest(base.DbTestCase):
|
|||
|
||||
code_source = self._create_code_source()
|
||||
|
||||
self._create_dynamic_actions(code_source_id=code_source['id'])
|
||||
self._create_dynamic_actions(code_source)
|
||||
|
||||
action_descs = provider.find_all()
|
||||
|
||||
|
@ -92,7 +94,7 @@ class DynamicActionProviderTest(base.DbTestCase):
|
|||
|
||||
code_source = self._create_code_source()
|
||||
|
||||
self._create_dynamic_actions(code_source_id=code_source['id'])
|
||||
self._create_dynamic_actions(code_source)
|
||||
|
||||
action_descs = provider.find_all()
|
||||
|
||||
|
@ -114,9 +116,10 @@ class DynamicActionProviderTest(base.DbTestCase):
|
|||
code_source = self._create_code_source()
|
||||
|
||||
self._create_dynamic_actions(
|
||||
code_source_id=code_source['id'],
|
||||
code_source=code_source,
|
||||
namespace=NAMESPACE
|
||||
)
|
||||
|
||||
action_descs = provider.find_all(namespace=NAMESPACE)
|
||||
|
||||
self.assertEqual(2, len(action_descs))
|
||||
|
|
|
@ -13,11 +13,12 @@
|
|||
# limitations under the License.
|
||||
|
||||
|
||||
from mistral.db.v2 import api as db_api
|
||||
from mistral.tests.unit.api import base
|
||||
|
||||
FILE_CONTENT = """test file"""
|
||||
FILE_CONTENT = "test file"
|
||||
|
||||
UPDATED_FILE_CONTENT = """updated content"""
|
||||
UPDATED_FILE_CONTENT = "updated content"
|
||||
|
||||
MODULE_NAME = 'modulename%s'
|
||||
NAMESPACE = "NS"
|
||||
|
@ -27,11 +28,9 @@ class TestCodeSourcesController(base.APITest):
|
|||
def _create_code_source(self, module_name, file_content,
|
||||
namespace=NAMESPACE, expect_errors=False):
|
||||
return self.app.post(
|
||||
'/v2/code_sources',
|
||||
params={'namespace': namespace},
|
||||
upload_files=[
|
||||
(module_name, 'filename', file_content.encode())
|
||||
],
|
||||
'/v2/code_sources?name=%s&namespace=%s' % (module_name, namespace),
|
||||
file_content,
|
||||
headers={'Content-Type': 'text/plain'},
|
||||
expect_errors=expect_errors
|
||||
)
|
||||
|
||||
|
@ -40,79 +39,71 @@ class TestCodeSourcesController(base.APITest):
|
|||
'/v2/code_sources/%s?namespace=%s' % (id, namespace)
|
||||
)
|
||||
|
||||
def test_create_code_source(self):
|
||||
def setUp(self):
|
||||
super(TestCodeSourcesController, self).setUp()
|
||||
|
||||
self.addCleanup(db_api.delete_code_sources)
|
||||
|
||||
def test_post(self):
|
||||
mod_name = MODULE_NAME % 'create'
|
||||
|
||||
resp = self._create_code_source(
|
||||
mod_name,
|
||||
FILE_CONTENT
|
||||
)
|
||||
resp = self._create_code_source(mod_name, FILE_CONTENT)
|
||||
|
||||
resp_json = resp.json
|
||||
self.assertEqual(201, resp.status_int)
|
||||
|
||||
self.assertEqual(200, resp.status_int)
|
||||
code_src = resp.json
|
||||
|
||||
code_sources = resp_json.get('code_sources')
|
||||
self.assertEqual(mod_name, code_src['name'])
|
||||
self.assertEqual(FILE_CONTENT, code_src['content'])
|
||||
self.assertEqual(1, code_src['version'])
|
||||
self.assertEqual(NAMESPACE, code_src['namespace'])
|
||||
|
||||
self.assertEqual(1, len(code_sources))
|
||||
|
||||
code_source = code_sources[0]
|
||||
|
||||
self.assertEqual(mod_name, code_source.get('name'))
|
||||
self.assertEqual(FILE_CONTENT, code_source.get('src'))
|
||||
self.assertEqual(1, code_source.get('version'))
|
||||
self.assertEqual(NAMESPACE, code_source.get('namespace'))
|
||||
|
||||
self._delete_code_source(mod_name)
|
||||
|
||||
def test_update_code_source(self):
|
||||
def test_put(self):
|
||||
mod_name = MODULE_NAME % 'update'
|
||||
|
||||
self._create_code_source(mod_name, FILE_CONTENT)
|
||||
|
||||
resp = self.app.put(
|
||||
'/v2/code_sources/',
|
||||
params='namespace=%s' % NAMESPACE,
|
||||
upload_files=[
|
||||
(mod_name, 'filename', UPDATED_FILE_CONTENT.encode())
|
||||
],
|
||||
'/v2/code_sources?identifier=%s&namespace=%s' %
|
||||
(mod_name, NAMESPACE),
|
||||
UPDATED_FILE_CONTENT,
|
||||
headers={'Content-Type': 'text/plain'}
|
||||
)
|
||||
|
||||
resp_json = resp.json
|
||||
|
||||
self.assertEqual(200, resp.status_int)
|
||||
|
||||
code_sources = resp_json.get('code_sources')
|
||||
code_src = resp.json
|
||||
|
||||
self.assertEqual(1, len(code_sources))
|
||||
self.assertEqual(mod_name, code_src['name'])
|
||||
self.assertEqual(UPDATED_FILE_CONTENT, code_src['content'])
|
||||
self.assertEqual(2, code_src['version'])
|
||||
self.assertEqual(NAMESPACE, code_src['namespace'])
|
||||
|
||||
code_source = code_sources[0]
|
||||
|
||||
self.assertEqual(200, resp.status_int)
|
||||
|
||||
self.assertEqual(mod_name, code_source.get('name'))
|
||||
self.assertEqual(UPDATED_FILE_CONTENT, code_source.get('src'))
|
||||
self.assertEqual(2, code_source.get('version'))
|
||||
self.assertEqual(NAMESPACE, code_source.get('namespace'))
|
||||
|
||||
self._delete_code_source(mod_name)
|
||||
|
||||
def test_delete_code_source(self):
|
||||
def test_delete(self):
|
||||
mod_name = MODULE_NAME % 'delete'
|
||||
|
||||
resp = self._create_code_source(mod_name, FILE_CONTENT)
|
||||
|
||||
resp_json = resp.json
|
||||
self.assertEqual(201, resp.status_int)
|
||||
self.assertEqual(mod_name, resp.json['name'])
|
||||
|
||||
self.assertEqual(200, resp.status_int)
|
||||
|
||||
code_sources = resp_json.get('code_sources')
|
||||
|
||||
self.assertEqual(1, len(code_sources))
|
||||
# Make sure the object is in DB.
|
||||
self.assertIsNotNone(
|
||||
db_api.load_code_source(mod_name, namespace=NAMESPACE)
|
||||
)
|
||||
|
||||
self._delete_code_source(mod_name)
|
||||
|
||||
def test_create_duplicate_code_source(self):
|
||||
# Make sure the object was deleted from DB.
|
||||
self.assertIsNone(
|
||||
db_api.load_code_source(mod_name, namespace=NAMESPACE)
|
||||
)
|
||||
|
||||
def test_post_duplicate(self):
|
||||
mod_name = MODULE_NAME % 'duplicate'
|
||||
|
||||
self._create_code_source(mod_name, FILE_CONTENT)
|
||||
|
||||
resp = self._create_code_source(
|
||||
mod_name,
|
||||
FILE_CONTENT, expect_errors=True
|
||||
|
@ -120,28 +111,28 @@ class TestCodeSourcesController(base.APITest):
|
|||
|
||||
self.assertEqual(409, resp.status_int)
|
||||
self.assertIn('Duplicate entry for CodeSource', resp)
|
||||
|
||||
self._delete_code_source(mod_name)
|
||||
|
||||
def test_get_code_source(self):
|
||||
def test_get(self):
|
||||
mod_name = MODULE_NAME % 'get'
|
||||
|
||||
self._create_code_source(mod_name, FILE_CONTENT)
|
||||
|
||||
resp = self.app.get(
|
||||
'/v2/code_sources/%s' % mod_name,
|
||||
params='namespace=%s' % NAMESPACE
|
||||
'/v2/code_sources/%s?namespace=%s' % (mod_name, NAMESPACE)
|
||||
)
|
||||
|
||||
resp_json = resp.json
|
||||
|
||||
self.assertEqual(200, resp.status_int)
|
||||
|
||||
self.assertEqual(mod_name, resp_json.get('name'))
|
||||
self.assertEqual(FILE_CONTENT, resp_json.get('src'))
|
||||
self.assertEqual(1, resp_json.get('version'))
|
||||
self.assertEqual(NAMESPACE, resp_json.get('namespace'))
|
||||
self.assertEqual(mod_name, resp_json['name'])
|
||||
self.assertEqual(FILE_CONTENT, resp_json['content'])
|
||||
self.assertEqual(1, resp_json['version'])
|
||||
self.assertEqual(NAMESPACE, resp_json['namespace'])
|
||||
|
||||
self._delete_code_source(mod_name)
|
||||
|
||||
def test_get_all_code_source(self):
|
||||
def test_get_all(self):
|
||||
mod_name = MODULE_NAME % 'getall'
|
||||
mod2_name = MODULE_NAME % '2getall'
|
||||
|
||||
|
@ -152,13 +143,11 @@ class TestCodeSourcesController(base.APITest):
|
|||
'/v2/code_sources',
|
||||
params='namespace=%s' % NAMESPACE
|
||||
)
|
||||
|
||||
resp_json = resp.json
|
||||
|
||||
self.assertEqual(200, resp.status_int)
|
||||
|
||||
code_sources = resp_json.get('code_sources')
|
||||
code_sources = resp_json['code_sources']
|
||||
|
||||
self.assertEqual(2, len(code_sources))
|
||||
|
||||
self._delete_code_source(mod_name)
|
||||
self._delete_code_source(mod2_name)
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from mistral.db.v2 import api as db_api
|
||||
from mistral.services import actions
|
||||
from mistral.tests.unit.api import base
|
||||
|
||||
|
@ -33,70 +34,49 @@ class DummyAction2(actions.Action):
|
|||
def test(self, context):
|
||||
return None"""
|
||||
|
||||
CREATE_REQUEST = """
|
||||
-
|
||||
name: dummy_action
|
||||
class_name: DummyAction
|
||||
code_source_id: {}
|
||||
"""
|
||||
|
||||
UPDATE_REQUEST = """
|
||||
dummy_action:
|
||||
class_name: NewDummyAction
|
||||
code_source_id: {}
|
||||
"""
|
||||
|
||||
|
||||
class TestDynamicActionsController(base.APITest):
|
||||
def setUp(self):
|
||||
super(TestDynamicActionsController, self).setUp()
|
||||
|
||||
resp = self._create_code_source().json
|
||||
|
||||
self.code_source_id = resp.get('code_sources')[0].get('id')
|
||||
|
||||
self.addCleanup(self._delete_code_source)
|
||||
|
||||
def _create_code_source(self):
|
||||
return self.app.post(
|
||||
'/v2/code_sources',
|
||||
upload_files=[
|
||||
('test_dummy_module', 'filename', TEST_MODULE_TEXT.encode())
|
||||
],
|
||||
resp = self.app.post(
|
||||
'/v2/code_sources?name=test_dummy_module',
|
||||
TEST_MODULE_TEXT
|
||||
)
|
||||
|
||||
def _create_dynamic_action(self, body):
|
||||
return self.app.post(
|
||||
self.code_source_id = resp.json['id']
|
||||
|
||||
self.addCleanup(db_api.delete_code_sources)
|
||||
self.addCleanup(db_api.delete_dynamic_action_definitions)
|
||||
|
||||
def _create_dynamic_action(self):
|
||||
return self.app.post_json(
|
||||
'/v2/dynamic_actions',
|
||||
body,
|
||||
content_type="text/plain"
|
||||
{
|
||||
'name': 'dummy_action',
|
||||
'class_name': 'DummyAction',
|
||||
'code_source_id': self.code_source_id
|
||||
}
|
||||
)
|
||||
|
||||
def _delete_code_source(self):
|
||||
return self.app.delete('/v2/code_sources/test_dummy_module')
|
||||
|
||||
def test_create_dynamic_action(self):
|
||||
resp = self._create_dynamic_action(
|
||||
CREATE_REQUEST.format(self.code_source_id)
|
||||
def test_post(self):
|
||||
resp = self.app.post_json(
|
||||
'/v2/dynamic_actions',
|
||||
{
|
||||
'name': 'dummy_action',
|
||||
'class_name': 'DummyAction',
|
||||
'code_source_id': self.code_source_id
|
||||
}
|
||||
)
|
||||
|
||||
# Check the structure of the response.
|
||||
resp_json = resp.json
|
||||
self.assertEqual(201, resp.status_int)
|
||||
|
||||
self.assertEqual(200, resp.status_int)
|
||||
dyn_action = resp.json
|
||||
|
||||
dynamic_actions = resp_json.get('dynamic_actions')
|
||||
|
||||
self.assertEqual(1, len(dynamic_actions))
|
||||
|
||||
dynamic_action = dynamic_actions[0]
|
||||
|
||||
self.assertEqual('dummy_action', dynamic_action.get('name'))
|
||||
self.assertEqual('DummyAction', dynamic_action.get('class_name'))
|
||||
self.assertEqual(
|
||||
self.code_source_id,
|
||||
dynamic_action.get('code_source_id')
|
||||
)
|
||||
self.assertEqual('dummy_action', dyn_action['name'])
|
||||
self.assertEqual('DummyAction', dyn_action['class_name'])
|
||||
self.assertEqual(self.code_source_id, dyn_action['code_source_id'])
|
||||
|
||||
# Make sure the action can be found via the system action provider
|
||||
# and it's fully functioning.
|
||||
|
@ -112,52 +92,42 @@ class TestDynamicActionsController(base.APITest):
|
|||
|
||||
self.assertEqual("Hello from the dummy action 1!", action.run(None))
|
||||
|
||||
# Delete the action
|
||||
self.app.delete('/v2/dynamic_actions/dummy_action')
|
||||
def test_put(self):
|
||||
resp = self._create_dynamic_action()
|
||||
|
||||
def test_update_dynamic_action(self):
|
||||
self._create_dynamic_action(
|
||||
CREATE_REQUEST.format(self.code_source_id)
|
||||
)
|
||||
self.assertEqual(201, resp.status_int)
|
||||
|
||||
resp = self.app.put(
|
||||
resp = self.app.put_json(
|
||||
'/v2/dynamic_actions',
|
||||
UPDATE_REQUEST.format(self.code_source_id),
|
||||
content_type="text/plain"
|
||||
)
|
||||
|
||||
resp_json = resp.json
|
||||
|
||||
self.assertEqual(200, resp.status_int)
|
||||
|
||||
dynamic_actions = resp_json.get('dynamic_actions')
|
||||
|
||||
self.assertEqual(1, len(dynamic_actions))
|
||||
|
||||
dynamic_action = dynamic_actions[0]
|
||||
|
||||
self.assertEqual('dummy_action', dynamic_action.get('name'))
|
||||
self.assertEqual('NewDummyAction', dynamic_action.get('class_name'))
|
||||
self.assertEqual(
|
||||
self.code_source_id,
|
||||
dynamic_action.get('code_source_id')
|
||||
)
|
||||
|
||||
self.app.delete('/v2/dynamic_actions/dummy_action')
|
||||
|
||||
def test_get_dynamic_action(self):
|
||||
resp = self._create_dynamic_action(
|
||||
CREATE_REQUEST.format(self.code_source_id)
|
||||
{
|
||||
'name': 'dummy_action',
|
||||
'class_name': 'NewDummyAction'
|
||||
}
|
||||
)
|
||||
|
||||
self.assertEqual(200, resp.status_int)
|
||||
|
||||
self.app.delete('/v2/dynamic_actions/dummy_action')
|
||||
dyn_action = resp.json
|
||||
|
||||
def test_get_all_dynamic_actions(self):
|
||||
self._create_dynamic_action(
|
||||
CREATE_REQUEST.format(self.code_source_id)
|
||||
)
|
||||
self.assertEqual('dummy_action', dyn_action['name'])
|
||||
self.assertEqual('NewDummyAction', dyn_action['class_name'])
|
||||
|
||||
def test_get(self):
|
||||
self._create_dynamic_action()
|
||||
|
||||
resp = self.app.get('/v2/dynamic_actions/dummy_action')
|
||||
|
||||
self.assertEqual(200, resp.status_int)
|
||||
|
||||
dyn_action = resp.json
|
||||
|
||||
self.assertEqual('dummy_action', dyn_action['name'])
|
||||
self.assertEqual('DummyAction', dyn_action['class_name'])
|
||||
self.assertEqual(self.code_source_id, dyn_action['code_source_id'])
|
||||
self.assertEqual('test_dummy_module', dyn_action['code_source_name'])
|
||||
|
||||
def test_get_all(self):
|
||||
self._create_dynamic_action()
|
||||
|
||||
resp = self.app.get('/v2/dynamic_actions')
|
||||
|
||||
|
@ -169,15 +139,11 @@ class TestDynamicActionsController(base.APITest):
|
|||
|
||||
self.assertEqual(1, len(dynamic_actions))
|
||||
|
||||
self.app.delete('/v2/dynamic_actions/dummy_action')
|
||||
|
||||
def test_delete_dynamic_action(self):
|
||||
resp = self._create_dynamic_action(
|
||||
CREATE_REQUEST.format(self.code_source_id)
|
||||
)
|
||||
def test_delete(self):
|
||||
resp = self._create_dynamic_action()
|
||||
|
||||
# Check the structure of the response
|
||||
self.assertEqual(200, resp.status_int)
|
||||
self.assertEqual(201, resp.status_int)
|
||||
|
||||
resp = self.app.get('/v2/dynamic_actions/dummy_action')
|
||||
|
||||
|
|
Loading…
Reference in New Issue