Implement new syntax for action declaration

`Scope` keyword is introduced to declare method visibility.
For that package format version is incremented to 1.4.

`Scope` attribute can have two possible values:
* `Session` - regular method that is accessible from anywhere in
the current execution session. This is the default if the
attribute is omitted;
* `Public` - accessible anywhere, both within the session and from
outside through the API call (i.e. it is an action in current
terminology).

It allows to declare static actions with the following syntax:
methodName:
  Usage: Static
  Scope: Public

For backward compatibility `Usage: Action` remains valid and
acceptable but it is deprecated for format versions > 1.3. An
exception is raised if both `Usage: Action` and `Scope: Session`
are specified.

Change-Id: I5a6d664d017d5ea99a5159d7eb2e111143c7b0a3
Partially-implements: blueprint static-actions
This commit is contained in:
Valerii Kovalchuk 2016-06-14 12:51:43 +03:00
parent d8ccb215be
commit 91fd92c136
12 changed files with 106 additions and 18 deletions

View File

@ -32,9 +32,13 @@ can be tracked.
.. note::
Now murano doesn't support big files download during action execution. This is
because action results are stored in murano database and are limited by approximately 10kb size.
because action results are stored in murano database and are limited by
approximately 10kb size.
To mark a method as an action, use ``Usage: Action``.
To mark a method as an action, use ``Scope: Public`` or ``Usage: Action``.
The latter option is deprecated for the package format versions > 1.3 and
occasionally will be no longer supported. Also, you cannot use both
``Usage: Action`` and ``Scope: Session`` in one method.
The following example shows an action that returns an archive with a
configuration file:
@ -42,7 +46,7 @@ configuration file:
::
exportConfig:
Usage: Action
Scope: Public
Body:
- $._environment.reporter.report($this, 'Action exportConfig called')
- $resources: new(sys:Resources)

View File

@ -282,7 +282,7 @@ Methods are defined in the Workflow section of the class using the
following template::
methodName:
Usage: Action
Scope: Public
Arguments:
- list
- of
@ -292,7 +292,7 @@ following template::
- of
- instructions
Action is an optional parameter that specifies methods to be executed
Public is an optional parameter that specifies methods to be executed
by direct triggering after deployment.
Arguments are optional too, and are declared using the same syntax
@ -344,10 +344,28 @@ access it. The following usages are available:
* - | Action
- | Method can be invoked from outside (using Murano API).
This option is deprecated for the package format versions > 1.3 in
favor of ``Scope: Public`` and occasionally will be no longer
supported.
See :ref:`actions` for details.
The usage attribute is optional and can be omitted (which implies ``Runtime``).
The ``Usage`` attribute is optional and can be omitted (which implies
``Runtime``).
Method scope
++++++++++++
The ``Scope`` attribute declares method visibility. It can have two possible
values:
* `Session` - regular method that is accessible from anywhere in the current
execution session. This is the default if the attribute is omitted;
* `Public` - accessible anywhere, both within the session and from
outside through the API call.
The ``Scope`` attribute is optional and can be omitted (which implies
``Session``).
Expressions
+++++++++++

View File

@ -244,7 +244,7 @@ The following example shows how applications can access attached metadata:
Methods:
sampleAction:
Usage: Action
Scope: Public
Body:
- $._environment.reporter.report($this, typeinfo($).meta.
where($ is MetaClassOne).single().description)
@ -295,7 +295,7 @@ meta-classes:
Methods:
importantAction:
Usage: Action
Scope: Public
Meta:
m:CallerMustBeAdmin

View File

@ -165,6 +165,10 @@ The short description of versions:
recommended specifying this format in new applications,
where compatibility with older versions of murano is not
required.
**1.4** supported since Newton. Keyword ``Scope`` is introduced
for class methods to declare method's accessibility from
outside through the API call.
================== ===========================================================
UI forms versioning

View File

@ -44,6 +44,7 @@ META_MURANO_METHOD = '?muranoMethod'
META_NO_TRACE = '?noTrace'
META_MPL_META = 'Meta'
META_USAGE = 'Usage'
META_SCOPE = 'Scope'
CORE_LIBRARY = 'io.murano'
CORE_LIBRARY_OBJECT = 'io.murano.Object'
@ -56,3 +57,4 @@ RUNTIME_VERSION_1_0 = semantic_version.Version('1.0.0')
RUNTIME_VERSION_1_1 = semantic_version.Version('1.1.0')
RUNTIME_VERSION_1_2 = semantic_version.Version('1.2.0')
RUNTIME_VERSION_1_3 = semantic_version.Version('1.3.0')
RUNTIME_VERSION_1_4 = semantic_version.Version('1.4.0')

View File

@ -57,6 +57,13 @@ class MethodUsages(object):
StaticMethods = {Static, Extension}
class MethodScopes(object):
Session = 'Session'
Public = 'Public'
All = {Session, Public}
class MethodArgumentUsages(object):
Standard = 'Standard'
VarArgs = 'VarArgs'

View File

@ -94,8 +94,7 @@ class MuranoDslExecutor(object):
return stub(yaql_engine, method_context, this.real_this)(
*args, **kwargs)
if (context[constants.CTX_ACTIONS_ONLY] and method.usage !=
dsl_types.MethodUsages.Action):
if context[constants.CTX_ACTIONS_ONLY] and not method.is_action:
raise Exception('{0} is not an action'.format(method.name))
if method.is_static:

View File

@ -16,6 +16,7 @@ import collections
import sys
import weakref
from oslo_log import log as logging
import six
from yaql.language import specs
@ -31,6 +32,7 @@ from murano.dsl import virtual_exceptions
from murano.dsl import yaql_integration
LOG = logging.getLogger(__name__)
macros.register()
virtual_exceptions.register()
@ -51,6 +53,7 @@ class MuranoMethod(dsl_types.MuranoMethod, meta.MetaProvider):
self._body = yaql_integration.get_function_definition(
payload, self_ref, original_name)
self._arguments_scheme = None
self._scope = self._body.meta.get(constants.META_SCOPE)
if declaring_type.extension_class and any((
helpers.inspect_is_static(
declaring_type.extension_class, original_name),
@ -67,6 +70,9 @@ class MuranoMethod(dsl_types.MuranoMethod, meta.MetaProvider):
if self._usage not in dsl_types.MethodUsages.InstanceMethods:
raise ValueError(
'Invalid Usage for instance method ' + self.name)
self._resolve_usage_and_scope()
if self._scope is None:
self._scope = dsl_types.MethodScopes.Session
if (self._body.name.startswith('#') or
self._body.name.startswith('*')):
raise ValueError(
@ -80,6 +86,10 @@ class MuranoMethod(dsl_types.MuranoMethod, meta.MetaProvider):
self._body = macros.MethodBlock(payload.get('Body'), name)
self._usage = payload.get(
'Usage') or dsl_types.MethodUsages.Runtime
self._scope = payload.get('Scope')
self._resolve_usage_and_scope()
if self._scope is None:
self._scope = dsl_types.MethodScopes.Session
arguments_scheme = helpers.list_value(payload.get('Arguments'))
if isinstance(arguments_scheme, dict):
arguments_scheme = [{key: value} for key, value in
@ -123,6 +133,18 @@ class MuranoMethod(dsl_types.MuranoMethod, meta.MetaProvider):
self._instance_stub, self._static_stub = \
yaql_integration.build_stub_function_definitions(self_ref)
def _resolve_usage_and_scope(self):
if self._usage == dsl_types.MethodUsages.Action:
runtime_version = self.declaring_type.package.runtime_version
if runtime_version > constants.RUNTIME_VERSION_1_3:
LOG.warning('"Usage: Action" is deprecated, '
'use "Scope: Public" instead')
if self._scope == dsl_types.MethodScopes.Session:
raise ValueError(
'Both "Usage: Action" and "Scope: Session" are '
'provided for method ' + self.name)
self._scope = dsl_types.MethodScopes.Public
@property
def name(self):
return self._name
@ -151,6 +173,14 @@ class MuranoMethod(dsl_types.MuranoMethod, meta.MetaProvider):
def usage(self, value):
self._usage = value
@property
def scope(self):
return self._scope
@scope.setter
def scope(self, value):
self._scope = value
@property
def body(self):
return self._body
@ -159,6 +189,11 @@ class MuranoMethod(dsl_types.MuranoMethod, meta.MetaProvider):
def is_static(self):
return self.usage in dsl_types.MethodUsages.StaticMethods
@property
def is_action(self):
return (self.scope == dsl_types.MethodScopes.Public or
self.usage == dsl_types.MethodUsages.Action)
def get_meta(self, context):
def meta_producer(cls):
method = cls.methods.get(self.name)

View File

@ -72,8 +72,7 @@ def serialize_model(root_object, executor, allow_refs=False):
def _serialize_available_action(obj, current_actions):
result = {}
actions = obj.type.find_methods(
lambda m: m.usage == dsl_types.MethodUsages.Action)
actions = obj.type.find_methods(lambda m: m.is_action)
for action in actions:
action_id = '{0}_{1}'.format(obj.object_id, action.name)
entry = current_actions.get(action_id, {'enabled': True})

View File

@ -36,7 +36,7 @@ def get_plugin_loader():
if PLUGIN_LOADER is None:
PLUGIN_LOADER = package_types_loader.PluginLoader()
for runtime_version in ('1.0', '1.1', '1.2', '1.3'):
for runtime_version in ('1.0', '1.1', '1.2', '1.3', '1.4'):
format_string = 'MuranoPL/' + runtime_version
PLUGIN_LOADER.register_format(
format_string, murano.packages.mpl_package.MuranoPlPackage)

View File

@ -26,14 +26,22 @@ class TestActionsSerializer(base.MuranoTestCase):
def _get_mocked_obj(self):
method1 = mock.Mock()
method1.usage = dsl_types.MethodUsages.Action
method1.scope = dsl_types.MethodScopes.Public
method1.name = 'method1'
method2 = mock.Mock()
method2.usage = dsl_types.MethodUsages.Runtime
method2.name = 'method2'
method3 = mock.Mock()
method3.usage = dsl_types.MethodUsages.Action
method3.scope = dsl_types.MethodScopes.Public
method3.name = 'method3'
method4 = mock.Mock()
method4.usage = dsl_types.MethodUsages.Action
method4.name = 'method4'
for method in [method1, method2, method3, method4]:
method.is_action = (method.scope ==
dsl_types.MethodScopes.Public or
method.usage == dsl_types.MethodUsages.Action)
obj2_type = mock.Mock()
obj2_type.declared_parents = []
@ -43,9 +51,10 @@ class TestActionsSerializer(base.MuranoTestCase):
obj = mock.Mock()
obj.object_id = 'id1'
obj.type.declared_parents = [obj2_type]
obj.type.methods = {'method1': method1, 'method2': method2}
obj.type.methods = {'method1': method1, 'method2': method2,
'method4': method4}
obj.type.find_methods = lambda p: filter(
p, [method1, method2, method3])
p, [method1, method2, method3, method4])
return obj
@ -54,9 +63,12 @@ class TestActionsSerializer(base.MuranoTestCase):
obj_actions = serializer._serialize_available_action(obj, {})
expected_result = {'name': 'method1', 'enabled': True}
self.assertIn('id1_method1', obj_actions)
self.assertIn('id1_method4', obj_actions)
expected_result = {'name': 'method1', 'enabled': True}
self.assertEqual(expected_result, obj_actions['id1_method1'])
expected_result = {'name': 'method4', 'enabled': True}
self.assertEqual(expected_result, obj_actions['id1_method4'])
def test_that_only_actions_are_serialized(self):
obj = self._get_mocked_obj()

View File

@ -0,0 +1,8 @@
---
upgrade:
- New manifest format 1.4.0 is available. Keyword 'Scope' is introduced
for class methods to declare method's accessibility from outside through
the API call.
deprecations:
- Keyword 'Usage Action' is deprecated to declare class method as an
action in favor of 'Scope Public' for the package format versions > 1.3.