diff --git a/meta/io.murano/Classes/configuration/Linux.yaml b/meta/io.murano/Classes/configuration/Linux.yaml index 9ac30d82..cc91d6ce 100644 --- a/meta/io.murano/Classes/configuration/Linux.yaml +++ b/meta/io.murano/Classes/configuration/Linux.yaml @@ -19,6 +19,7 @@ Name: Linux Methods: runCommand: + Usage: Static Arguments: - agent: Contract: $.class(sys:Agent) @@ -54,6 +55,7 @@ Methods: - Return: $agent.call($template, $resources) putFile: + Usage: Static Arguments: - agent: Contract: $.class(sys:Agent) diff --git a/murano/cmd/test_runner.py b/murano/cmd/test_runner.py index 511fad7a..98710f7a 100755 --- a/murano/cmd/test_runner.py +++ b/murano/cmd/test_runner.py @@ -233,21 +233,22 @@ class MuranoTestRunner(object): for m in test_cases: # Create new executor for each test case to provide # pure test environment - executer = executor.MuranoDslExecutor( + dsl_executor = executor.MuranoDslExecutor( pkg_loader, mock_context_manager.MockContextManager(), test_env) obj = package.find_class(pkg_class, False).new( - None, executer.object_store)(None) - self._call_service_method('setUp', executer, obj) + None, dsl_executor.object_store, dsl_executor)(None) + self._call_service_method('setUp', dsl_executor, obj) obj.type.methods[m].usage = 'Action' test_env.start() try: - obj.type.invoke(m, executer, obj, (), {}) + obj.type.invoke(m, dsl_executor, obj, (), {}) LOG.debug('\n.....{0}.{1}.....OK'.format(obj.type.name, m)) - self._call_service_method('tearDown', executer, obj) + self._call_service_method( + 'tearDown', dsl_executor, obj) except Exception: LOG.exception('\n.....{0}.{1}.....FAILURE\n' ''.format(obj.type.name, m)) diff --git a/murano/dsl/dsl.py b/murano/dsl/dsl.py index 31ba7e05..6dda22c1 100644 --- a/murano/dsl/dsl.py +++ b/murano/dsl/dsl.py @@ -109,9 +109,9 @@ class MuranoTypeName(yaqltypes.PythonType): if function_spec.meta.get(constants.META_MURANO_METHOD): context = helpers.get_caller_context(context) murano_type = helpers.get_type(context) - value = dsl_types.MuranoTypeReference(helpers.get_class( + value = helpers.get_class( murano_type.namespace_resolver.resolve_name(value), - context)) + context).get_reference() return value diff --git a/murano/dsl/dsl_types.py b/murano/dsl/dsl_types.py index 4b05f9a5..08b8e8d5 100644 --- a/murano/dsl/dsl_types.py +++ b/murano/dsl/dsl_types.py @@ -45,8 +45,16 @@ class MuranoTypeReference(object): def murano_class(self): return self.__murano_class - def __str__(self): - return self.__murano_class.name + def __repr__(self): + return '*' + repr(self.murano_class) + + def __eq__(self, other): + if not isinstance(other, MuranoTypeReference): + return False + return self.murano_class == other.murano_class + + def __hash__(self): + return hash(self.murano_class) class YaqlExpression(object): diff --git a/murano/dsl/exceptions.py b/murano/dsl/exceptions.py index 6a8329ec..801a8df0 100644 --- a/murano/dsl/exceptions.py +++ b/murano/dsl/exceptions.py @@ -160,3 +160,9 @@ class UninitializedPropertyAccessError(PropertyAccessError): class CircularExpressionDependenciesError(Exception): pass + + +class InvalidLhsTargetError(Exception): + def __init__(self, target): + super(InvalidLhsTargetError, self).__init__( + 'Invalid assignment target "%s"' % target) diff --git a/murano/dsl/executor.py b/murano/dsl/executor.py index fffa1182..3c99a60f 100644 --- a/murano/dsl/executor.py +++ b/murano/dsl/executor.py @@ -28,6 +28,7 @@ from murano.common.i18n import _LW from murano.dsl import attribute_store from murano.dsl import constants from murano.dsl import dsl +from murano.dsl import dsl_types from murano.dsl import helpers from murano.dsl import murano_method from murano.dsl import object_store @@ -85,7 +86,9 @@ class MuranoDslExecutor(object): context = self.create_method_context( self.create_object_context(this, context), method) - this = this.real_this + + if isinstance(this, dsl_types.MuranoObject): + this = this.real_this if method.arguments_scheme is not None: args, kwargs = self._canonize_parameters( @@ -119,7 +122,10 @@ class MuranoDslExecutor(object): @contextlib.contextmanager def _acquire_method_lock(self, func, this): method_id = id(func) - this_id = this.object_id + if isinstance(this, dsl_types.MuranoClass): + this_id = id(this) + else: + this_id = this.object_id thread_id = helpers.get_current_thread_id() while True: event, event_owner = self._locks.get( @@ -262,13 +268,24 @@ class MuranoDslExecutor(object): return context def create_object_context(self, obj, caller_context=None): - class_context = self.create_class_context(obj.type) - context = helpers.link_contexts( - class_context, self.context_manager.create_object_context( - obj)).create_child_context() - context[constants.CTX_THIS] = obj.real_this - context['this'] = obj.real_this - context[''] = obj.real_this + if isinstance(obj, dsl_types.MuranoClass): + obj_type = obj + obj = None + else: + obj_type = obj.type + class_context = self.create_class_context(obj_type) + if obj is not None: + context = helpers.link_contexts( + class_context, self.context_manager.create_object_context( + obj)).create_child_context() + context[constants.CTX_THIS] = obj.real_this + context['this'] = obj.real_this + context[''] = obj.real_this + else: + context = class_context.create_child_context() + type_ref = obj_type.get_reference() + context['this'] = type_ref + context[''] = type_ref if caller_context is not None: caller = caller_context diff --git a/murano/dsl/helpers.py b/murano/dsl/helpers.py index 5e4ed6f3..2949eb5f 100644 --- a/murano/dsl/helpers.py +++ b/murano/dsl/helpers.py @@ -153,7 +153,9 @@ def get_environment(context=None): def get_object_store(context=None): context = context or get_context() - return context[constants.CTX_THIS].object_store + this = context[constants.CTX_THIS] + return this.object_store if isinstance( + this, dsl_types.MuranoObject) else None def get_package_loader(context=None): diff --git a/murano/dsl/lhs_expression.py b/murano/dsl/lhs_expression.py index f008fb8c..d3a25aaf 100644 --- a/murano/dsl/lhs_expression.py +++ b/murano/dsl/lhs_expression.py @@ -20,8 +20,10 @@ from yaql.language import utils from yaql.language import yaqltypes from murano.dsl import constants +from murano.dsl import dsl from murano.dsl import dsl_types from murano.dsl import exceptions +from murano.dsl import yaql_functions from murano.dsl import yaql_integration @@ -68,6 +70,14 @@ class LhsExpression(object): ((key, value),)))) elif isinstance(src, dsl_types.MuranoObject): src.set_property(key, value, root_context) + elif isinstance(src, ( + dsl_types.MuranoTypeReference, + dsl_types.MuranoClass)): + if not isinstance(src, dsl_types.MuranoClass): + mc = src.murano_class + else: + mc = src + mc.set_property(key, value, root_context) else: raise ValueError( 'attribution may only be applied to ' @@ -118,16 +128,51 @@ class LhsExpression(object): else: return attribution(this, index) + def _wrap_type_reference(tr): + return LhsExpression.Property(lambda: tr, self._invalid_target) + + @specs.parameter('prefix', yaqltypes.Keyword()) + @specs.parameter('name', yaqltypes.Keyword()) + @specs.name('#operator_:') + def ns_resolve(prefix, name): + return _wrap_type_reference( + yaql_functions.ns_resolve(context, prefix, name)) + + @specs.parameter('name', yaqltypes.Keyword()) + @specs.name('#unary_operator_:') + def ns_resolve_unary(context, name): + return _wrap_type_reference( + yaql_functions.ns_resolve_unary(context, name)) + + @specs.parameter('object_', dsl_types.MuranoObject) + def type_(object_): + return _wrap_type_reference(yaql_functions.type_(object_)) + + @specs.name('type') + @specs.parameter('cls', dsl.MuranoTypeName()) + def type_from_name(cls): + return _wrap_type_reference(cls) + context = yaql_integration.create_empty_context() context.register_function(get_context_data, '#get_context_data') context.register_function(attribution, '#operator_.') context.register_function(indexation, '#indexer') + context.register_function(ns_resolve) + context.register_function(ns_resolve_unary) + context.register_function(type_) + context.register_function(type_from_name) return context + def _invalid_target(self, *args, **kwargs): + raise exceptions.InvalidLhsTargetError(self._expression) + def __call__(self, value, context): new_context = self._create_context(context) new_context[''] = context['$'] + new_context[constants.CTX_TYPE] = context[constants.CTX_TYPE] self._current_obj = None self._current_obj_name = None property = self._expression(context=new_context) + if not isinstance(property, LhsExpression.Property): + self._invalid_target() property.set(value) diff --git a/murano/dsl/murano_class.py b/murano/dsl/murano_class.py index 2402d6f3..f20dcb6b 100644 --- a/murano/dsl/murano_class.py +++ b/murano/dsl/murano_class.py @@ -46,6 +46,7 @@ class MuranoClass(dsl_types.MuranoClass): package.find_class(constants.CORE_LIBRARY_OBJECT)] self._context = None self._parent_mappings = self._build_parent_remappings() + self._property_values = {} @classmethod def create(cls, data, package, name=None): @@ -53,7 +54,7 @@ class MuranoClass(dsl_types.MuranoClass): ns_resolver = namespace_resolver.NamespaceResolver(namespaces) if not name: - name = ns_resolver.resolve_name(data['Name']) + name = ns_resolver.resolve_name(str(data['Name'])) parent_class_names = data.get('Extends') parent_classes = [] @@ -61,7 +62,7 @@ class MuranoClass(dsl_types.MuranoClass): if not utils.is_sequence(parent_class_names): parent_class_names = [parent_class_names] for parent_name in parent_class_names: - full_name = ns_resolver.resolve_name(parent_name) + full_name = ns_resolver.resolve_name(str(parent_name)) parent_classes.append(package.find_class(full_name)) type_obj = cls(ns_resolver, name, package, parent_classes) @@ -189,6 +190,19 @@ class MuranoClass(dsl_types.MuranoClass): return self._choose_symbol( lambda cls: cls.properties.get(name)) + def find_static_property(self, name): + def prop_func(cls): + prop = cls.properties.get(name) + if prop is not None and prop.usage == 'Static': + return prop + + result = self._choose_symbol(prop_func) + if len(result) < 1: + raise exceptions.NoPropertyFound(name) + elif len(result) > 1: + raise exceptions.AmbiguousPropertyNameError(name) + return result[0] + def find_single_method(self, name): result = self.find_method(name) if len(result) < 1: @@ -242,12 +256,13 @@ class MuranoClass(dsl_types.MuranoClass): return True return any(cls is self for cls in obj.ancestors()) - def new(self, owner, object_store, **kwargs): - obj = murano_object.MuranoObject(self, owner, object_store, **kwargs) + def new(self, owner, object_store, executor, **kwargs): + obj = murano_object.MuranoObject( + self, owner, object_store, executor, **kwargs) def initializer(__context, **params): if __context is None: - __context = object_store.executor.create_object_context(obj) + __context = executor.create_object_context(obj) init_context = __context.create_child_context() init_context[constants.CTX_ALLOW_PROPERTY_WRITES] = True obj.initialize(init_context, object_store, params) @@ -359,3 +374,17 @@ class MuranoClass(dsl_types.MuranoClass): m.yaql_function_definition, name=m.yaql_function_definition.name) return self._context + + def get_property(self, name, context): + prop = self.find_static_property(name) + cls = prop.murano_class + value = cls._property_values.get(name, prop.default) + return prop.validate(value, cls, None, context) + + def set_property(self, name, value, context): + prop = self.find_static_property(name) + cls = prop.murano_class + cls._property_values[name] = prop.validate(value, cls, None, context) + + def get_reference(self): + return dsl_types.MuranoTypeReference(self) diff --git a/murano/dsl/murano_method.py b/murano/dsl/murano_method.py index 9f167b38..5f59486e 100644 --- a/murano/dsl/murano_method.py +++ b/murano/dsl/murano_method.py @@ -35,7 +35,8 @@ virtual_exceptions.register() class MethodUsages(object): Action = 'Action' Runtime = 'Runtime' - All = set([Action, Runtime]) + Static = 'Static' + All = set([Action, Runtime, Static]) class MuranoMethod(dsl_types.MuranoMethod): @@ -111,13 +112,18 @@ class MuranoMethod(dsl_types.MuranoMethod): def invoke(self, executor, this, args, kwargs, context=None, skip_stub=False): - if not self.murano_class.is_compatible(this): + if self.usage == 'Static': + this = None + elif not self.murano_class.is_compatible(this): raise Exception("'this' must be of compatible type") if isinstance(this, dsl.MuranoObjectInterface): this = this.object + if this is not None: + this = this.cast(self.murano_class) + else: + this = self.murano_class return executor.invoke_method( - self, this.cast(self.murano_class), - context, args, kwargs, skip_stub) + self, this, context, args, kwargs, skip_stub) class MuranoMethodArgument(dsl_types.MuranoMethodArgument, typespec.Spec): diff --git a/murano/dsl/murano_object.py b/murano/dsl/murano_object.py index 3572b8cb..6c71491e 100644 --- a/murano/dsl/murano_object.py +++ b/murano/dsl/murano_object.py @@ -26,8 +26,9 @@ from murano.dsl import yaql_integration class MuranoObject(dsl_types.MuranoObject): - def __init__(self, murano_class, owner, object_store, object_id=None, - name=None, known_classes=None, defaults=None, this=None): + def __init__(self, murano_class, owner, object_store, executor, + object_id=None, name=None, known_classes=None, + defaults=None, this=None): if known_classes is None: known_classes = {} self.__owner = owner.real_this if owner else None @@ -39,7 +40,9 @@ class MuranoObject(dsl_types.MuranoObject): self.__this = this self.__name = name self.__extension = None - self.__object_store = weakref.ref(object_store) + self.__object_store = \ + None if object_store is None else weakref.ref(object_store) + self.__executor = weakref.ref(executor) self.__config = murano_class.package.get_class_config( murano_class.name) if not isinstance(self.__config, dict): @@ -49,7 +52,7 @@ class MuranoObject(dsl_types.MuranoObject): name = parent_class.name if name not in known_classes: obj = parent_class.new( - owner, object_store, object_id=self.__object_id, + owner, object_store, executor, object_id=self.__object_id, known_classes=known_classes, defaults=defaults, this=self.real_this).object @@ -72,7 +75,11 @@ class MuranoObject(dsl_types.MuranoObject): @property def object_store(self): - return self.__object_store() + return None if self.__object_store is None else self.__object_store() + + @property + def executor(self): + return self.__executor() def initialize(self, context, object_store, params): if self.__initialized: @@ -105,7 +112,8 @@ class MuranoObject(dsl_types.MuranoObject): if property_name in used_names: continue - if spec.usage == typespec.PropertyUsages.Config: + if spec.usage in (typespec.PropertyUsages.Config, + typespec.PropertyUsages.Static): used_names.add(property_name) continue if spec.usage == typespec.PropertyUsages.Runtime: @@ -133,7 +141,8 @@ class MuranoObject(dsl_types.MuranoObject): last_errors = errors executor = helpers.get_executor(context) - if not object_store.initializing and self.__extension is None: + if ((object_store is None or not object_store.initializing) and + self.__extension is None): method = self.type.methods.get('__init__') if method: filtered_params = yaql_integration.filter_parameters( @@ -146,7 +155,7 @@ class MuranoObject(dsl_types.MuranoObject): for parent in self.__parents.values(): parent.initialize(context, object_store, params) - if not object_store.initializing and init: + if (object_store is None or not object_store.initializing) and init: context[constants.CTX_ARGUMENT_OWNER] = self.real_this init.invoke(executor, self.real_this, (), init_args, context) self.__initialized = True @@ -173,11 +182,18 @@ class MuranoObject(dsl_types.MuranoObject): if caller_class is not None and caller_class.is_compatible(self): start_type, derived = caller_class, True if name in start_type.properties: - return self.cast(start_type)._get_property_value(name) + spec = start_type.properties[name] + if spec.usage == typespec.PropertyUsages.Static: + return spec.murano_class.get_property(name, context) + else: + return self.cast(start_type)._get_property_value(name) else: try: spec = start_type.find_single_property(name) - return self.cast(spec.murano_class).__properties[name] + if spec.usage == typespec.PropertyUsages.Static: + return spec.murano_class.get_property(name, context) + else: + return self.cast(spec.murano_class).__properties[name] except exceptions.NoPropertyFound: if derived: return self.cast(caller_class)._get_property_value(name) @@ -199,11 +215,12 @@ class MuranoObject(dsl_types.MuranoObject): declared_properties = start_type.find_properties( lambda p: p.name == name) if context is None: - context = self.object_store.executor.create_object_context(self) + context = self.executor.create_object_context(self) if len(declared_properties) > 0: declared_properties = self.type.find_properties( lambda p: p.name == name) values_to_assign = [] + classes_for_static_properties = [] for spec in declared_properties: if (caller_class is not None and not helpers.are_property_modifications_allowed(context) and @@ -211,16 +228,21 @@ class MuranoObject(dsl_types.MuranoObject): not derived)): raise exceptions.NoWriteAccessError(name) - default = self.__config.get(name, spec.default) - default = self.__defaults.get(name, default) - default = helpers.evaluate(default, context) + if spec.usage == typespec.PropertyUsages.Static: + classes_for_static_properties.append(spec.murano_class) + else: + default = self.__config.get(name, spec.default) + default = self.__defaults.get(name, default) + default = helpers.evaluate(default, context) - obj = self.cast(spec.murano_class) - values_to_assign.append((obj, spec.validate( - value, self.real_this, - self.real_this, default=default))) + obj = self.cast(spec.murano_class) + values_to_assign.append((obj, spec.validate( + value, self.real_this, + self.real_this, context, default=default))) for obj, value in values_to_assign: obj.__properties[name] = value + for cls in classes_for_static_properties: + cls.set_property(name, value, context) elif derived: obj = self.cast(caller_class) obj.__properties[name] = value diff --git a/murano/dsl/namespace_resolver.py b/murano/dsl/namespace_resolver.py index deab6911..872b1a85 100644 --- a/murano/dsl/namespace_resolver.py +++ b/murano/dsl/namespace_resolver.py @@ -12,26 +12,42 @@ # License for the specific language governing permissions and limitations # under the License. +import re + +TYPE_NAME_RE = re.compile(r'^([a-zA-Z_]\w*:|:)?[a-zA-Z_]\w*(\.[a-zA-Z_]\w*)*$') +NS_RE = re.compile(r'^([a-zA-Z_]\w*(\.[a-zA-Z_]\w*)*)?$') +PREFIX_RE = re.compile(r'^([a-zA-Z_]\w*|=)$') + class NamespaceResolver(object): def __init__(self, namespaces): - self._namespaces = namespaces + for prefix, ns in namespaces.items(): + if PREFIX_RE.match(prefix) is None: + raise ValueError( + 'Invalid namespace prefix "{0}"'.format(prefix)) + if NS_RE.match(ns) is None: + raise ValueError('Invalid namespace "{0}"'.format(ns)) + self._namespaces = namespaces.copy() + self._namespaces.setdefault('=', '') self._namespaces[''] = '' - def resolve_name(self, name, relative=None): - if name is None: - raise ValueError() - if name and name.startswith(':'): - return name[1:] - if ':' in name: + def resolve_name(self, name): + if name is None or TYPE_NAME_RE.match(name) is None: + raise ValueError('Invalid type name "{0}"'.format(name)) + if ':' not in name: + if '.' in name: + parts = ['', name] + else: + parts = ['=', name] + else: parts = name.split(':') - if len(parts) != 2 or not parts[1]: - raise NameError('Incorrectly formatted name ' + name) - if parts[0] not in self._namespaces: - raise KeyError('Unknown namespace prefix ' + parts[0]) - return '.'.join((self._namespaces[parts[0]], parts[1])) - if not relative and '=' in self._namespaces and '.' not in name: - return '.'.join((self._namespaces['='], name)) - if relative and '.' not in name: - return '.'.join((relative, name)) - return name + if not parts[0]: + parts[0] = '=' + + if parts[0] not in self._namespaces: + raise KeyError('Unknown namespace prefix ' + parts[0]) + + ns = self._namespaces[parts[0]] + if not ns: + return name + return '.'.join((ns, parts[1])) diff --git a/murano/dsl/object_store.py b/murano/dsl/object_store.py index a6ac5922..5c4c7486 100644 --- a/murano/dsl/object_store.py +++ b/murano/dsl/object_store.py @@ -81,7 +81,7 @@ class ObjectStore(object): return factory else: factory = class_obj.new( - owner, self, + owner, self, self.executor, name=system_key.get('name'), object_id=object_id, defaults=defaults) self._store[object_id] = factory diff --git a/murano/dsl/reflection.py b/murano/dsl/reflection.py index da34ecb5..0714da6c 100644 --- a/murano/dsl/reflection.py +++ b/murano/dsl/reflection.py @@ -157,6 +157,12 @@ def argument_owner(method_argument): return method_argument.murano_method +@specs.yaql_property(dsl_types.MuranoClass) +@specs.name('type') +def type_to_type_ref(murano_class): + return murano_class.get_reference() + + def register(context): funcs = ( class_name, methods, properties, ancestors, package, class_version, @@ -164,7 +170,8 @@ def register(context): property_usage, method_name, arguments, method_owner, types, package_name, package_version, - argument_name, argument_has_default, argument_owner + argument_name, argument_has_default, argument_owner, + type_to_type_ref ) for f in funcs: context.register_function(f) diff --git a/murano/dsl/typespec.py b/murano/dsl/typespec.py index af4f1506..aa45e3d0 100644 --- a/murano/dsl/typespec.py +++ b/murano/dsl/typespec.py @@ -14,7 +14,9 @@ import weakref +from murano.dsl import dsl_types from murano.dsl import exceptions +from murano.dsl import helpers from murano.dsl import type_scheme @@ -25,8 +27,9 @@ class PropertyUsages(object): Runtime = 'Runtime' Const = 'Const' Config = 'Config' - All = set([In, Out, InOut, Runtime, Const, Config]) - Writable = set([Out, InOut, Runtime]) + Static = 'Static' + All = set([In, Out, InOut, Runtime, Const, Config, Static]) + Writable = set([Out, InOut, Runtime, Static]) class Spec(object): @@ -41,13 +44,19 @@ class Spec(object): 'Unknown type {0}. Must be one of ({1})'.format( self._usage, ', '.join(PropertyUsages.All))) - def validate(self, value, this, owner, default=None): + def validate(self, value, this, owner, context, default=None): if default is None: default = self.default - return self._contract( - value, this.object_store.executor.create_object_context( - this.cast(self._container_class())), - this, owner, default) + executor = helpers.get_executor(context) + if isinstance(this, dsl_types.MuranoClass): + return self._contract( + value, executor.create_object_context(this), + None, None, default) + else: + return self._contract( + value, executor.create_object_context( + this.cast(self._container_class())), + this, owner, default) @property def default(self): diff --git a/murano/dsl/yaql_expression.py b/murano/dsl/yaql_expression.py index aa1752ce..d4e412d2 100644 --- a/murano/dsl/yaql_expression.py +++ b/murano/dsl/yaql_expression.py @@ -68,7 +68,7 @@ class YaqlExpression(dsl_types.YaqlExpression): def is_expression(expression, version): if not isinstance(expression, six.string_types): return False - if re.match('^[\s\w\d.:]*$', expression): + if re.match('^[\s\w\d.]*$', expression): return False try: yaql_integration.parse(expression, version) diff --git a/murano/dsl/yaql_functions.py b/murano/dsl/yaql_functions.py index 5171b1ce..3626c87e 100644 --- a/murano/dsl/yaql_functions.py +++ b/murano/dsl/yaql_functions.py @@ -46,12 +46,14 @@ def cast(context, object_, type__, version_spec=None): def new(__context, __type_name, __owner=None, __object_name=None, __extra=None, **parameters): object_store = helpers.get_object_store(__context) + executor = helpers.get_executor(__context) new_context = __context.create_child_context() for key, value in six.iteritems(parameters): if utils.is_keyword(key): new_context[key] = value return __type_name.murano_class.new( - __owner, object_store, name=__object_name)(new_context, **parameters) + __owner, object_store, executor, name=__object_name)( + new_context, **parameters) @specs.parameter('type_name', dsl.MuranoTypeName()) @@ -110,14 +112,32 @@ def sleep_(seconds): @specs.parameter('object_', dsl.MuranoObjectParameterType(nullable=True)) def type_(object_): + return None if object_ is None else object_.type.get_reference() + + +@specs.name('type') +@specs.parameter('object_', dsl.MuranoObjectParameterType(nullable=True)) +def type_legacy(object_): return None if object_ is None else object_.type.name +@specs.name('type') +@specs.parameter('cls', dsl.MuranoTypeName()) +def type_from_name(cls): + return cls + + @specs.parameter('object_', dsl.MuranoObjectParameterType(nullable=True)) def typeinfo(object_): return None if object_ is None else object_.type +@specs.parameter('cls', dsl.MuranoTypeName()) +@specs.name('typeinfo') +def typeinfo_for_class(cls): + return cls.murano_class + + @specs.parameter('object_', dsl.MuranoObjectParameterType(nullable=True)) def name(object_): return None if object_ is None else object_.name @@ -130,6 +150,13 @@ def obj_attribution(context, obj, property_name): return obj.get_property(property_name, context) +@specs.parameter('cls', dsl_types.MuranoTypeReference) +@specs.parameter('property_name', yaqltypes.Keyword()) +@specs.name('#operator_.') +def obj_attribution_static(context, cls, property_name): + return cls.murano_class.get_property(property_name, context) + + @specs.parameter('receiver', dsl.MuranoObjectParameterType()) @specs.parameter('expr', yaqltypes.Lambda(method=True)) @specs.inject('operator', yaqltypes.Super(with_context=True)) @@ -144,14 +171,32 @@ def op_dot(context, receiver, expr, operator): return operator(ctx2, receiver, expr) +@specs.parameter('sender', dsl_types.MuranoTypeReference) +@specs.parameter('expr', yaqltypes.Lambda(method=True)) +@specs.inject('operator', yaqltypes.Super(with_context=True)) +@specs.name('#operator_.') +def op_dot_static(context, sender, expr, operator): + executor = helpers.get_executor(context) + type_context = executor.context_manager.create_class_context( + sender.murano_class) + ctx2 = helpers.link_contexts(context, type_context) + return operator(ctx2, None, expr) + + @specs.parameter('prefix', yaqltypes.Keyword()) @specs.parameter('name', yaqltypes.Keyword()) @specs.name('#operator_:') def ns_resolve(context, prefix, name): murano_type = helpers.get_type(context) - return dsl_types.MuranoTypeReference(helpers.get_class( + return helpers.get_class( murano_type.namespace_resolver.resolve_name( - prefix + ':' + name), context)) + prefix + ':' + name), context).get_reference() + + +@specs.parameter('name', yaqltypes.Keyword()) +@specs.name('#unary_operator_:') +def ns_resolve_unary(context, name): + return ns_resolve(context, '', name) @specs.parameter('obj', dsl.MuranoObjectParameterType(nullable=True)) @@ -173,20 +218,28 @@ def register(context, runtime_version): context.register_function(require) context.register_function(find) context.register_function(sleep_) - context.register_function(type_) context.register_function(typeinfo) + context.register_function(typeinfo_for_class) context.register_function(name) context.register_function(obj_attribution) + context.register_function(obj_attribution_static) context.register_function(op_dot) + context.register_function(op_dot_static) context.register_function(ns_resolve) + context.register_function(ns_resolve_unary) reflection.register(context) context.register_function(is_instance_of) + if runtime_version <= constants.RUNTIME_VERSION_1_3: + context.register_function(type_legacy) + else: + context.register_function(type_) if runtime_version <= constants.RUNTIME_VERSION_1_1: - context2 = context.create_child_context() + context = context.create_child_context() for t in ('id', 'cast', 'super', 'psuper', 'type'): for spec in utils.to_extension_method(t, context): - context2.register_function(spec) - return context2 + context.register_function(spec) + + context.register_function(type_from_name) return context diff --git a/murano/dsl/yaql_integration.py b/murano/dsl/yaql_integration.py index 3ab3b6b4..e49d9afa 100644 --- a/murano/dsl/yaql_integration.py +++ b/murano/dsl/yaql_integration.py @@ -51,7 +51,10 @@ def _add_operators(engine_factory): engine_factory.insert_operator( '>', True, 'is', factory.OperatorType.BINARY_LEFT_ASSOCIATIVE, False) engine_factory.insert_operator( - '.', True, ':', factory.OperatorType.BINARY_LEFT_ASSOCIATIVE, True) + None, True, ':', factory.OperatorType.BINARY_LEFT_ASSOCIATIVE, True) + engine_factory.insert_operator( + ':', True, ':', factory.OperatorType.PREFIX_UNARY, False) + engine_factory.operators.insert(0, ()) def _create_engine(runtime_version): @@ -86,7 +89,7 @@ class ContractedValue(yaqltypes.GenericType): lambda value, sender, context, *args, **kwargs: self._value_spec.validate( value, sender.real_this, - context[constants.CTX_ARGUMENT_OWNER])) + context[constants.CTX_ARGUMENT_OWNER], context)) def convert(self, value, *args, **kwargs): if value is None: @@ -220,8 +223,9 @@ def _build_mpl_wrapper_function_definition(murano_method): fd.set_parameter(specs.ParameterDefinition( '__context', yaqltypes.Context(), 0)) - fd.set_parameter(specs.ParameterDefinition( - '__sender', yaqltypes.PythonType(dsl_types.MuranoObject, False), 1)) + nullable = murano_method.usage == 'Static' + sender_type = yaqltypes.PythonType(dsl_types.MuranoObject, nullable) + fd.set_parameter(specs.ParameterDefinition('__sender', sender_type, 1)) fd.meta[constants.META_MURANO_METHOD] = murano_method return fd diff --git a/murano/tests/unit/dsl/foundation/test_package_loader.py b/murano/tests/unit/dsl/foundation/test_package_loader.py index 9b686663..d5f2d795 100644 --- a/murano/tests/unit/dsl/foundation/test_package_loader.py +++ b/murano/tests/unit/dsl/foundation/test_package_loader.py @@ -80,7 +80,9 @@ class TestPackageLoader(package_loader.MuranoPackageLoader): def _build_index(self, directory): yamls = [os.path.join(dirpath, f) for dirpath, _, files in os.walk(directory) - for f in fnmatch.filter(files, '*.yaml')] + for f in fnmatch.filter(files, '*.yaml') + if f != 'manifest.yaml' + ] for class_def_file in yamls: self._load_class(class_def_file) diff --git a/murano/tests/unit/dsl/meta/TestStatics.yaml b/murano/tests/unit/dsl/meta/TestStatics.yaml new file mode 100644 index 00000000..cc31198b --- /dev/null +++ b/murano/tests/unit/dsl/meta/TestStatics.yaml @@ -0,0 +1,192 @@ +Namespaces: + ns: test + =: test + +Name: TestStatics + +Extends: TestStaticsBase + +Properties: + staticProperty: + Contract: $.string() + Usage: Static + Default: xxx + + conflictingStaticProperty: + Contract: $.string() + Default: 'conflictingStaticProperty-child' + Usage: Static + + instanceProperty: + Contract: $.string() + Default: instanceProperty + + staticProperty2: + Contract: $.string() + Default: staticProperty + Usage: Static + +Methods: + testStaticTest: + Usage: Static + Body: + Return: $ + + testCallStaticMethodOnObject: + Body: + Return: $.simpleStaticMethod() + + testCallStaticMethodOnClassName: + Body: + Return: :TestStatics.simpleStaticMethod() + + testCallStaticMethodOnClassNameWithNs: + Body: + Return: ns:TestStatics.simpleStaticMethod() + + testCallStaticMethodFromAnotherMethod: + Body: + Return: ns:TestStatics.simpleStaticMethod2() + + testStaticThis: + Body: + Return: $.returnStaticThis() + + testNoAccessToInstanceProperties: + Body: + Return: $.accessInstanceProperty() + + testAccessStaticPropertyFromInstanceMethod: + Body: + Return: $.staticProperty + + testAccessStaticPropertyFromStaticMethod: + Body: + Return: $.accessStaticProperty() + + simpleStaticMethod: + Usage: Static + Body: + Return: 123 + + simpleStaticMethod2: + Usage: Static + Body: + Return: $.simpleStaticMethod() + + $this.simpleStaticMethod() + + ns:TestStatics.simpleStaticMethod() + + :TestStatics.simpleStaticMethod() + + type('test.TestStatics').simpleStaticMethod() + + returnStaticThis: + Usage: Static + Body: + Return: $ + + accessInstanceProperty: + Usage: Static + Body: + Return: $.instanceProperty + + accessStaticProperty: + Usage: Static + Body: + Return: $.staticProperty + + testModifyStaticPropertyUsingDollar: + Body: + Return: $.modifyStaticPropertyUsingDollar() + + modifyStaticPropertyUsingDollar: + Usage: Static + Body: + - $.staticProperty: qq + - Return: $.staticProperty + + testModifyStaticPropertyUsingThis: + Body: + Return: $.modifyStaticPropertyUsingThis() + + modifyStaticPropertyUsingThis: + Usage: Static + Body: + - $this.staticProperty: qq + - Return: $this.staticProperty + + testModifyStaticPropertyUsingClassName: + Body: + Return: $.modifyStaticPropertyUsingClassName() + + modifyStaticPropertyUsingClassName: + Usage: Static + Body: + - :TestStatics.staticProperty: qq + - Return: :TestStatics.staticProperty + + testModifyStaticPropertyUsingNsClassName: + Body: + Return: $.modifyStaticPropertyUsingNsClassName() + + modifyStaticPropertyUsingNsClassName: + Usage: Static + Body: + - ns:TestStatics.staticProperty: qq + - Return: ns:TestStatics.staticProperty + + testModifyStaticPropertyUsingTypeFunc: + Body: + Return: $.modifyStaticPropertyUsingTypeFunc() + + modifyStaticPropertyUsingTypeFunc: + Usage: Static + Body: + - type('test.TestStatics').staticProperty: qq + - Return: type('test.TestStatics').staticProperty + + testPropertyIsStatic: + Body: + Return: $.modifyStaticPropertyOnInstance() + + modifyStaticPropertyOnInstance: + Usage: Static + Body: + - $obj1: new(TestStatics) + - $obj2: new(TestStatics) + - $obj1.modifyStaticPropertyUsingClassName() + - Return: $obj2.staticProperty + + testStaticPropertisNotLoaded: + Body: + Return: $.staticProperty2 + + testTypeIsSingleton: + Body: + - $t11: :TestStatics + - $t12: :TestStatics + - $t21: ns:TestStatics + - $t22: ns:TestStatics + - $t31: type('test.TestStatics') + - $t32: type('test.TestStatics') + - Return: $t11 = $t12 and $t21 = $t22 and $t31 = $t32 + + testStaticPropertyInheritance: + Body: + Return: $.baseStaticProperty + + :TestStaticsBase.baseStaticProperty + + :TestStatics.baseStaticProperty + + testStaticPropertyOverride: + Body: + Return: + - $.conflictingStaticProperty + - :TestStatics.conflictingStaticProperty + - :TestStaticsBase.conflictingStaticProperty + - type('test.TestStatics').conflictingStaticProperty + - type('test.TestStaticsBase').conflictingStaticProperty + + testTypeinfoOfType: + Body: + - $typeObj: type('test.TestStatics') + - $typeInfoOfType: typeinfo($typeObj) + - $obj: new('TestStatics') + - Return: typeinfo($obj) = $typeInfoOfType diff --git a/murano/tests/unit/dsl/meta/TestStaticsBase.yaml b/murano/tests/unit/dsl/meta/TestStaticsBase.yaml new file mode 100644 index 00000000..befa0d97 --- /dev/null +++ b/murano/tests/unit/dsl/meta/TestStaticsBase.yaml @@ -0,0 +1,15 @@ +Namespaces: + =: test + +Name: TestStaticsBase + +Properties: + baseStaticProperty: + Contract: $.string() + Default: baseStaticProperty + Usage: Static + + conflictingStaticProperty: + Contract: $.string() + Default: 'conflictingStaticProperty-base' + Usage: Static diff --git a/murano/tests/unit/dsl/test_statics.py b/murano/tests/unit/dsl/test_statics.py new file mode 100644 index 00000000..5ac93ebc --- /dev/null +++ b/murano/tests/unit/dsl/test_statics.py @@ -0,0 +1,106 @@ +# Copyright (c) 2016 Mirantis, Inc. +# +# 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 murano.dsl import dsl_types +from murano.dsl import exceptions +from murano.tests.unit.dsl.foundation import object_model as om +from murano.tests.unit.dsl.foundation import test_case + + +class TestStatics(test_case.DslTestCase): + def setUp(self): + super(TestStatics, self).setUp() + self._runner = self.new_runner( + om.Object('test.TestStatics', staticProperty2='INVALID')) + + def test_call_static_method_on_object(self): + self.assertEqual(123, self._runner.testCallStaticMethodOnObject()) + + def test_call_static_method_on_class_name(self): + self.assertEqual(123, self._runner.testCallStaticMethodOnClassName()) + + def test_call_static_method_on_class_name_with_ns(self): + self.assertEqual( + 123, self._runner.testCallStaticMethodOnClassNameWithNs()) + + def test_call_static_method_from_another_method(self): + self.assertEqual( + 123 * 5, self._runner.testCallStaticMethodFromAnotherMethod()) + + def test_static_this(self): + self.assertIsInstance( + self._runner.testStaticThis(), dsl_types.MuranoTypeReference) + + def test_no_access_to_instance_properties(self): + self.assertRaises( + exceptions.NoPropertyFound, + self._runner.testNoAccessToInstanceProperties) + + def test_access_static_property_from_instance_method(self): + self.assertEqual( + 'xxx', self._runner.testAccessStaticPropertyFromInstanceMethod()) + + def test_access_static_property_from_static_method(self): + self.assertEqual( + 'xxx', self._runner.testAccessStaticPropertyFromStaticMethod()) + + def test_modify_static_property_using_dollar(self): + self.assertEqual( + 'qq', self._runner.testModifyStaticPropertyUsingDollar()) + + def test_modify_static_property_using_this(self): + self.assertEqual( + 'qq', self._runner.testModifyStaticPropertyUsingThis()) + + def test_modify_static_property_using_class_name(self): + self.assertEqual( + 'qq', self._runner.testModifyStaticPropertyUsingClassName()) + + def test_modify_static_property_using_ns_class_name(self): + self.assertEqual( + 'qq', self._runner.testModifyStaticPropertyUsingNsClassName()) + + def test_modify_static_property_using_type_func(self): + self.assertEqual( + 'qq', self._runner.testModifyStaticPropertyUsingTypeFunc()) + + def test_property_is_static(self): + self.assertEqual('qq', self._runner.testPropertyIsStatic()) + + def test_static_properties_excluded_from_object_model(self): + self.assertEqual( + 'staticProperty', + self._runner.testStaticPropertisNotLoaded()) + + def test_type_is_singleton(self): + self.assertTrue(self._runner.testTypeIsSingleton()) + + def test_static_property_inheritance(self): + self.assertEqual( + 'baseStaticProperty' * 3, + self._runner.testStaticPropertyInheritance()) + + def test_static_property_override(self): + self.assertEqual( + [ + 'conflictingStaticProperty-child', + 'conflictingStaticProperty-child', + 'conflictingStaticProperty-base', + 'conflictingStaticProperty-child', + 'conflictingStaticProperty-base' + ], self._runner.testStaticPropertyOverride()) + + def test_type_info_of_type(self): + self.assertTrue(self._runner.testTypeinfoOfType()) diff --git a/murano/tests/unit/test_engine.py b/murano/tests/unit/test_engine.py index 7699bd7b..31d18147 100644 --- a/murano/tests/unit/test_engine.py +++ b/murano/tests/unit/test_engine.py @@ -50,23 +50,24 @@ class TestNamespaceResolving(base.MuranoTestCase): resolver = ns_resolver.NamespaceResolver({'=': 'com.example.murano'}) name = 'sys:' - self.assertRaises(NameError, resolver.resolve_name, name) + self.assertRaises(ValueError, resolver.resolve_name, name) def test_fails_w_excessive_prefix(self): ns = {'sys': 'com.example.murano.system'} resolver = ns_resolver.NamespaceResolver(ns) invalid_name = 'sys:excessive_ns:muranoResource' - self.assertRaises(NameError, resolver.resolve_name, invalid_name) + self.assertRaises(ValueError, resolver.resolve_name, invalid_name) - def test_cuts_empty_prefix(self): + def test_empty_prefix_is_default(self): resolver = ns_resolver.NamespaceResolver({'=': 'com.example.murano'}) # name without prefix delimiter name = 'some.arbitrary.name' resolved_name = resolver.resolve_name(':' + name) - self.assertEqual(name, resolved_name) + self.assertEqual( + 'com.example.murano.some.arbitrary.name', resolved_name) def test_resolves_specified_ns_prefix(self): ns = {'sys': 'com.example.murano.system'} @@ -85,20 +86,6 @@ class TestNamespaceResolving(base.MuranoTestCase): self.assertEqual(full_name, resolved_name) - def test_resolves_explicit_base(self): - resolver = ns_resolver.NamespaceResolver({'=': 'com.example.murano'}) - - resolved_name = resolver.resolve_name('Resource', relative='com.base') - - self.assertEqual('com.base.Resource', resolved_name) - - def test_resolves_explicit_base_w_empty_namespaces(self): - resolver = ns_resolver.NamespaceResolver({}) - - resolved_name = resolver.resolve_name('File', 'com.base') - - self.assertEqual('com.base.File', resolved_name) - def test_resolves_w_empty_namespaces(self): resolver = ns_resolver.NamespaceResolver({}) diff --git a/releasenotes/notes/statics-9943fe9873138dac.yaml b/releasenotes/notes/statics-9943fe9873138dac.yaml new file mode 100644 index 00000000..761b69e7 --- /dev/null +++ b/releasenotes/notes/statics-9943fe9873138dac.yaml @@ -0,0 +1,9 @@ +--- +features: + - "Static methods and properties were introduced. + Both properties and methods can be marked as Usage: Static + Statics can be accessed using ns:Class.property / ns:Class.method(), + :Class.property / :Class.method() to access class from current namespace + or type('full.name').property / type('full.name').method() to use full + type name." + - io.murano.configuration.Linux methods are now static