Support of MuranoPL extended metadata was added

Now it is possible to
1) Define metadata classes that describe arbitrary metadata.
    Meta-classes has all the capabilities of regular classes and
    in addition has 3 new attributes: Cardinality, Applies and Inherited
2) It is possible to attach meta-class instances to packages,
    classes (including other meta-classes), properties, methods and
    method arguments. Each of them got new "Meta" key containing
    list (or single scalar) of meta-class instances

Implements: blueprint metadata-in-muranopl
Change-Id: Ieac999a877221a9ecd7d7379b39557036e882aa7
This commit is contained in:
Stan Lagun
2016-02-21 21:23:06 +03:00
committed by Alexander Tivelkov
parent 3aa97d0f5b
commit f36fe4929d
28 changed files with 907 additions and 116 deletions

View File

@@ -41,6 +41,8 @@ DM_ATTRIBUTES = 'Attributes'
META_MURANO_METHOD = '?muranoMethod'
META_NO_TRACE = '?noTrace'
META_MPL_META = 'Meta'
META_USAGE = 'Usage'
CORE_LIBRARY = 'io.murano'
CORE_LIBRARY_OBJECT = 'io.murano.Object'

View File

@@ -17,6 +17,7 @@ import os.path
import six
from yaql.language import expressions as yaql_expressions
from yaql.language import specs
from yaql.language import utils
from yaql.language import yaqltypes
from yaql import yaql_interface
@@ -335,3 +336,12 @@ def to_mutable(obj, yaql_engine=None):
limiter = lambda it: utils.limit_iterable(it, constants.ITERATORS_LIMIT)
return converter(obj, limiter, yaql_engine, converter)
def meta(type_name, value):
def wrapper(func):
fd = specs.get_function_definition(func)
mpl_meta = fd.meta.get(constants.META_MPL_META, [])
mpl_meta.append({type_name: value})
specs.meta(type_name, mpl_meta)(func)
return wrapper

View File

@@ -13,6 +13,46 @@
# under the License.
class ClassUsages(object):
Class = 'Class'
Meta = 'Meta'
All = {Class, Meta}
class MetaCardinality(object):
One = 'One'
Many = 'Many'
All = {One, Many}
class MetaTargets(object):
Package = 'Package'
Type = 'Type'
Property = 'Property'
Method = 'Method'
Argument = 'Argument'
All = {Package, Type, Property, Method, Argument}
class PropertyUsages(object):
In = 'In'
Out = 'Out'
InOut = 'InOut'
Runtime = 'Runtime'
Const = 'Const'
Config = 'Config'
Static = 'Static'
All = {In, Out, InOut, Runtime, Const, Config, Static}
Writable = {Out, InOut, Runtime, Static}
class MethodUsages(object):
Action = 'Action'
Runtime = 'Runtime'
Static = 'Static'
All = {Action, Runtime, Static}
class MuranoType(object):
pass
@@ -21,6 +61,10 @@ class MuranoClass(MuranoType):
pass
class MuranoMetaClass(MuranoClass):
pass
class MuranoObject(object):
pass

View File

@@ -166,3 +166,7 @@ class InvalidLhsTargetError(Exception):
def __init__(self, target):
super(InvalidLhsTargetError, self).__init__(
'Invalid assignment target "%s"' % target)
class InvalidInheritanceError(Exception):
pass

View File

@@ -30,7 +30,6 @@ 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
from murano.dsl.principal_objects import stack_trace
from murano.dsl import yaql_integration
@@ -87,7 +86,7 @@ class MuranoDslExecutor(object):
yaql_engine, method_context, this.real_this)(*args, **kwargs)
if (context[constants.CTX_ACTIONS_ONLY] and method.usage !=
murano_method.MethodUsages.Action):
dsl_types.MethodUsages.Action):
raise Exception('{0} is not an action'.format(method.name))
if method.is_static:

View File

@@ -469,3 +469,66 @@ def inspect_is_property(cls, name):
if m is None:
return False
return inspect.isdatadescriptor(m)
def updated_dict(d, val):
if d is None:
d = {}
else:
d = d.copy()
if val is not None:
d.update(val)
return d
def resolve_type(value, scope_type, return_reference=False):
if value is None:
return None
if isinstance(scope_type, dsl_types.MuranoTypeReference):
scope_type = scope_type.type
if not isinstance(value, (dsl_types.MuranoType,
dsl_types.MuranoTypeReference)):
name = scope_type.namespace_resolver.resolve_name(value)
result = scope_type.package.find_class(name)
else:
result = value
if isinstance(result, dsl_types.MuranoTypeReference):
if return_reference:
return result
return result.type
elif return_reference:
return result.get_reference()
return result
def instantiate(data, owner, object_store, context, scope_type,
default_type=None, defaults=None):
if data is None:
data = {}
if not isinstance(data, yaqlutils.MappingType):
raise ValueError('Incorrect object initialization format')
default_type = resolve_type(default_type, scope_type)
if len(data) == 1:
key = next(iter(data.keys()))
ns_resolver = scope_type.namespace_resolver
if ns_resolver.is_typename(key, False) or isinstance(
key, (dsl_types.MuranoTypeReference, dsl_types.MuranoType)):
type_obj = resolve_type(key, scope_type)
props = yaqlutils.filter_parameters_dict(data[key] or {})
return type_obj.new(
owner, object_store, object_store.executor)(
context, **props)
data = updated_dict(defaults, data)
if '?' not in data:
if not default_type:
raise ValueError('Type information is missing')
data.update({'?': {
'type': default_type.name,
'classVersion': str(default_type.version)
}})
if 'id' not in data['?']:
data['?']['id'] = uuid.uuid4().hex
return object_store.load(data, owner, context)

View File

@@ -14,7 +14,6 @@
import six
from six.moves import range
from murano.dsl import constants
from murano.dsl import dsl_exception

117
murano/dsl/meta.py Normal file
View File

@@ -0,0 +1,117 @@
# 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.
import abc
import operator
import weakref
import six
from murano.dsl import dsl_types
from murano.dsl import helpers
from murano.dsl import object_store
class MetaProvider(object):
@abc.abstractmethod
def get_meta(self, context):
raise NotImplementedError()
class MetaData(MetaProvider):
def __init__(self, definition, target, scope_type):
scope_type = weakref.ref(scope_type)
if not definition:
definition = []
elif not isinstance(definition, list):
definition = [definition]
factories = []
used_types = set()
for d in definition:
if isinstance(d, dict):
if len(d) != 1:
raise ValueError('Invalid Meta format')
name = next(iter(d.keys()))
props = d[name] or {}
else:
name = d
props = {}
type_obj = helpers.resolve_type(name, scope_type())
if type_obj.usage != dsl_types.ClassUsages.Meta:
raise ValueError('Only Meta classes can be attached')
if target not in type_obj.targets:
raise ValueError(
u'Meta class {} is not applicable here'.format(
type_obj.name))
if type_obj in used_types and (
type_obj.cardinality != dsl_types.MetaCardinality.Many):
raise ValueError('Cannot attach several Meta instances '
'with cardinality One')
used_types.add(type_obj)
factory_maker = lambda template: \
lambda context, store: helpers.instantiate(
template, owner=None, object_store=store,
context=context, scope_type=scope_type())
factories.append(factory_maker({type_obj: props}))
self._meta_factories = factories
self._meta = None
def get_meta(self, context):
if self._meta is None:
executor = helpers.get_executor(context)
store = object_store.ObjectStore(executor)
self._meta = list(map(
lambda x: x(context, store),
self._meta_factories))
return self._meta
def merge_providers(initial_class, producer, context):
def merger(cls_list, skip_list):
result = set()
all_meta = []
for cls in cls_list:
cls_skip_list = skip_list.copy()
provider = producer(cls)
meta = [] if provider is None else provider.get_meta(context)
for item in meta:
cardinality = item.type.cardinality
inherited = item.type.inherited
if cls is not initial_class and (
not inherited or item.type in skip_list):
continue
if cardinality == dsl_types.MetaCardinality.One:
cls_skip_list.add(item.type)
all_meta.append((cls, item))
all_meta.extend(merger(cls.parents(initial_class), cls_skip_list))
meta_types = {}
for cls, item in all_meta:
entry = meta_types.get(item.type)
if entry is not None:
if entry is not cls:
raise ValueError(
u'Found more than one instance of Meta {} '
u'with Cardinality One'.format(item.type.name))
else:
continue
if item.type.cardinality == dsl_types.MetaCardinality.One:
meta_types[item.type] = cls
result.add((cls, item))
return result
meta = merger([initial_class], set())
return list(six.moves.map(operator.itemgetter(1), meta))

View File

@@ -19,11 +19,13 @@ import weakref
import six
from yaql.language import specs
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 helpers
from murano.dsl import macros
from murano.dsl import meta
from murano.dsl import typespec
from murano.dsl import virtual_exceptions
from murano.dsl import yaql_integration
@@ -33,18 +35,12 @@ macros.register()
virtual_exceptions.register()
class MethodUsages(object):
Action = 'Action'
Runtime = 'Runtime'
Static = 'Static'
All = set([Action, Runtime, Static])
class MuranoMethod(dsl_types.MuranoMethod):
class MuranoMethod(dsl_types.MuranoMethod, meta.MetaProvider):
def __init__(self, declaring_type, name, payload, original_name=None):
self._name = name
original_name = original_name or name
self._declaring_type = weakref.ref(declaring_type)
self._meta_values = None
if callable(payload):
if isinstance(payload, specs.FunctionDefinition):
@@ -58,19 +54,23 @@ class MuranoMethod(dsl_types.MuranoMethod):
declaring_type.extension_class, original_name),
helpers.inspect_is_classmethod(
declaring_type.extension_class, original_name))):
self._usage = MethodUsages.Static
self._usage = dsl_types.MethodUsages.Static
else:
self._usage = (self._body.meta.get('usage') or
self._body.meta.get('Usage') or
MethodUsages.Runtime)
self._usage = (self._body.meta.get(constants.META_USAGE) or
dsl_types.MethodUsages.Runtime)
if (self._body.name.startswith('#') or
self._body.name.startswith('*')):
raise ValueError(
'Import of special yaql functions is forbidden')
self._meta = meta.MetaData(
self._body.meta.get(constants.META_MPL_META),
dsl_types.MetaTargets.Method,
declaring_type)
else:
payload = payload or {}
self._body = macros.MethodBlock(payload.get('Body') or [], name)
self._usage = payload.get('Usage') or MethodUsages.Runtime
self._usage = payload.get(
'Usage') or dsl_types.MethodUsages.Runtime
arguments_scheme = payload.get('Arguments') or []
if isinstance(arguments_scheme, dict):
arguments_scheme = [{key: value} for key, value in
@@ -83,6 +83,10 @@ class MuranoMethod(dsl_types.MuranoMethod):
name = list(record.keys())[0]
self._arguments_scheme[name] = MuranoMethodArgument(
self, self.name, name, record[name])
self._meta = meta.MetaData(
payload.get('Meta'),
dsl_types.MetaTargets.Method,
declaring_type)
self._yaql_function_definition = \
yaql_integration.build_wrapper_function_definition(
weakref.proxy(self))
@@ -117,7 +121,19 @@ class MuranoMethod(dsl_types.MuranoMethod):
@property
def is_static(self):
return self.usage == MethodUsages.Static
return self.usage == dsl_types.MethodUsages.Static
def get_meta(self, context):
def meta_producer(cls):
method = cls.methods.get(self.name)
if method is None:
return None
return method._meta
if self._meta_values is None:
self._meta_values = meta.merge_providers(
self.declaring_type, meta_producer, context)
return self._meta_values
def __repr__(self):
return 'MuranoMethod({0}::{1})'.format(
@@ -140,13 +156,17 @@ class MuranoMethod(dsl_types.MuranoMethod):
self, this, context, args, kwargs, skip_stub)
class MuranoMethodArgument(dsl_types.MuranoMethodArgument, typespec.Spec):
class MuranoMethodArgument(dsl_types.MuranoMethodArgument, typespec.Spec,
meta.MetaProvider):
def __init__(self, murano_method, method_name, arg_name, declaration):
super(MuranoMethodArgument, self).__init__(
declaration, murano_method.declaring_type)
self._method_name = method_name
self._arg_name = arg_name
self._murano_method = weakref.ref(murano_method)
self._meta = meta.MetaData(
declaration.get('Meta'),
dsl_types.MetaTargets.Argument, self.murano_method.declaring_type)
def validate(self, *args, **kwargs):
try:
@@ -167,6 +187,9 @@ class MuranoMethodArgument(dsl_types.MuranoMethodArgument, typespec.Spec):
def name(self):
return self._arg_name
def get_meta(self, context):
return self._meta.get_meta(context)
def __repr__(self):
return 'MuranoMethodArgument({method}::{name})'.format(
method=self.murano_method.name, name=self.name)

View File

@@ -21,14 +21,12 @@ from murano.dsl import dsl
from murano.dsl import dsl_types
from murano.dsl import exceptions
from murano.dsl import helpers
from murano.dsl import typespec
from murano.dsl import yaql_integration
class MuranoObject(dsl_types.MuranoObject):
def __init__(self, murano_class, owner, object_store, executor,
object_id=None, name=None, known_classes=None,
defaults=None, this=None):
object_id=None, name=None, known_classes=None, this=None):
if known_classes is None:
known_classes = {}
self.__owner = owner.real_this if owner else None
@@ -36,7 +34,6 @@ class MuranoObject(dsl_types.MuranoObject):
self.__type = murano_class
self.__properties = {}
self.__parents = {}
self.__defaults = defaults or {}
self.__this = this
self.__name = name
self.__extension = None
@@ -53,8 +50,7 @@ class MuranoObject(dsl_types.MuranoObject):
if name not in known_classes:
obj = parent_class.new(
owner, object_store, executor, object_id=self.__object_id,
known_classes=known_classes, defaults=defaults,
this=self.real_this).object
known_classes=known_classes, this=self.real_this).object
self.__parents[name] = known_classes[name] = obj
else:
@@ -86,7 +82,7 @@ class MuranoObject(dsl_types.MuranoObject):
return
for property_name in self.__type.properties:
spec = self.__type.properties[property_name]
if spec.usage == typespec.PropertyUsages.Config:
if spec.usage == dsl_types.PropertyUsages.Config:
if property_name in self.__config:
property_value = self.__config[property_name]
else:
@@ -112,11 +108,11 @@ class MuranoObject(dsl_types.MuranoObject):
if property_name in used_names:
continue
if spec.usage in (typespec.PropertyUsages.Config,
typespec.PropertyUsages.Static):
if spec.usage in (dsl_types.PropertyUsages.Config,
dsl_types.PropertyUsages.Static):
used_names.add(property_name)
continue
if spec.usage == typespec.PropertyUsages.Runtime:
if spec.usage == dsl_types.PropertyUsages.Runtime:
if not spec.has_default:
used_names.add(property_name)
continue
@@ -127,12 +123,13 @@ class MuranoObject(dsl_types.MuranoObject):
if is_init_arg:
init_args[property_name] = property_value
else:
self.set_property(property_name, property_value)
self.set_property(
property_name, property_value, context)
used_names.add(property_name)
except exceptions.UninitializedPropertyAccessError:
errors += 1
except exceptions.ContractViolationException:
if spec.usage != typespec.PropertyUsages.Runtime:
if spec.usage != dsl_types.PropertyUsages.Runtime:
raise
if not errors:
break
@@ -183,14 +180,14 @@ class MuranoObject(dsl_types.MuranoObject):
start_type, derived = caller_class, True
if name in start_type.properties:
spec = start_type.properties[name]
if spec.usage == typespec.PropertyUsages.Static:
if spec.usage == dsl_types.PropertyUsages.Static:
return spec.declaring_type.get_property(name, context)
else:
return self.cast(start_type)._get_property_value(name)
else:
try:
spec = start_type.find_single_property(name)
if spec.usage == typespec.PropertyUsages.Static:
if spec.usage == dsl_types.PropertyUsages.Static:
return spec.declaring_type.get_property(name, context)
else:
return self.cast(spec.declaring_type).__properties[name]
@@ -224,16 +221,15 @@ class MuranoObject(dsl_types.MuranoObject):
for spec in declared_properties:
if (caller_class is not None and not
helpers.are_property_modifications_allowed(context) and
(spec.usage not in typespec.PropertyUsages.Writable or
(spec.usage not in dsl_types.PropertyUsages.Writable or
not derived)):
raise exceptions.NoWriteAccessError(name)
if spec.usage == typespec.PropertyUsages.Static:
if spec.usage == dsl_types.PropertyUsages.Static:
classes_for_static_properties.append(spec.declaring_type)
else:
default = self.__config.get(name, spec.default)
default = self.__defaults.get(name, default)
default = helpers.evaluate(default, context)
# default = helpers.evaluate(default, context)
obj = self.cast(spec.declaring_type)
values_to_assign.append((obj, spec.validate(
@@ -276,7 +272,7 @@ class MuranoObject(dsl_types.MuranoObject):
for property_name in self.type.properties:
if property_name in self.__properties:
spec = self.type.properties[property_name]
if spec.usage != typespec.PropertyUsages.Runtime:
if spec.usage != dsl_types.PropertyUsages.Runtime:
result[property_name] = self.__properties[
property_name]
return result

View File

@@ -24,6 +24,7 @@ from murano.dsl import constants
from murano.dsl import dsl_types
from murano.dsl import exceptions
from murano.dsl import helpers
from murano.dsl import meta as dslmeta
from murano.dsl import murano_object
from murano.dsl import murano_type
from murano.dsl import namespace_resolver
@@ -31,12 +32,13 @@ from murano.dsl import principal_objects
from murano.dsl import yaql_integration
class MuranoPackage(dsl_types.MuranoPackage):
class MuranoPackage(dsl_types.MuranoPackage, dslmeta.MetaProvider):
def __init__(self, package_loader, name, version=None,
runtime_version=None, requirements=None):
runtime_version=None, requirements=None, meta=None):
super(MuranoPackage, self).__init__()
self._package_loader = weakref.proxy(package_loader)
self._name = name
self._meta = None
self._version = helpers.parse_version(version)
self._runtime_version = helpers.parse_version(runtime_version)
self._requirements = {
@@ -54,6 +56,9 @@ class MuranoPackage(dsl_types.MuranoPackage):
self._native_load_queue = {}
if self.name == constants.CORE_LIBRARY:
principal_objects.register(self)
self._package_class = self._create_package_class()
self._meta = dslmeta.MetaData(
meta, dsl_types.MetaTargets.Package, self._package_class)
@property
def package_loader(self):
@@ -87,7 +92,7 @@ class MuranoPackage(dsl_types.MuranoPackage):
def get_class_config(self, name):
return {}
def _register_mpl_classes(self, data, name):
def _register_mpl_classes(self, data, name=None):
type_obj = self._classes.get(name)
if type_obj is not None:
return type_obj
@@ -187,7 +192,8 @@ class MuranoPackage(dsl_types.MuranoPackage):
except exceptions.NoClassFound:
pkgs_for_search.append(referenced_package)
continue
raise exceptions.NoClassFound(name, packages=pkgs_for_search)
raise exceptions.NoClassFound(
name, packages=pkgs_for_search + [self])
raise exceptions.NoClassFound(name, packages=[self])
@@ -195,5 +201,15 @@ class MuranoPackage(dsl_types.MuranoPackage):
def context(self):
return None
def _create_package_class(self):
ns_resolver = namespace_resolver.NamespaceResolver(None)
return murano_type.MuranoClass(
ns_resolver, self.name, self, utils.NO_VALUE)
def get_meta(self, context):
if not self._meta:
return []
return self._meta.get_meta(context)
def __repr__(self):
return 'MuranoPackage({name})'.format(name=self.name)

View File

@@ -19,14 +19,20 @@ import six
from murano.dsl import dsl_types
from murano.dsl import exceptions
from murano.dsl import meta
from murano.dsl import typespec
class MuranoProperty(dsl_types.MuranoProperty, typespec.Spec):
class MuranoProperty(dsl_types.MuranoProperty, typespec.Spec,
meta.MetaProvider):
def __init__(self, declaring_type, property_name, declaration):
super(MuranoProperty, self).__init__(declaration, declaring_type)
self._property_name = property_name
self._declaring_type = weakref.ref(declaring_type)
self._meta = meta.MetaData(
declaration.get('Meta'),
dsl_types.MetaTargets.Property, declaring_type)
self._meta_values = None
def validate(self, *args, **kwargs):
try:
@@ -45,6 +51,18 @@ class MuranoProperty(dsl_types.MuranoProperty, typespec.Spec):
def name(self):
return self._property_name
def get_meta(self, context):
def meta_producer(cls):
prop = cls.properties.get(self.name)
if prop is None:
return None
return prop._meta
if self._meta_values is None:
self._meta_values = meta.merge_providers(
self.declaring_type, meta_producer, context)
return self._meta_values
def __repr__(self):
return 'MuranoProperty({type}::{name})'.format(
type=self.declaring_type.name, name=self.name)

View File

@@ -12,6 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
import abc
import collections
import weakref
@@ -24,6 +25,7 @@ from murano.dsl import dsl
from murano.dsl import dsl_types
from murano.dsl import exceptions
from murano.dsl import helpers
from murano.dsl import meta as dslmeta
from murano.dsl import murano_method
from murano.dsl import murano_object
from murano.dsl import murano_property
@@ -48,22 +50,48 @@ class MuranoType(dsl_types.MuranoType):
def namespace_resolver(self):
return self._namespace_resolver
@abc.abstractproperty
@property
def usage(self):
raise NotImplementedError()
class MuranoClass(dsl_types.MuranoClass, MuranoType):
def __init__(self, ns_resolver, name, package, parents=None):
@property
def version(self):
return self.package.version
def get_reference(self):
return dsl_types.MuranoTypeReference(self)
class MuranoClass(dsl_types.MuranoClass, MuranoType, dslmeta.MetaProvider):
_allowed_usages = {dsl_types.ClassUsages.Class}
def __init__(self, ns_resolver, name, package, parents, meta=None):
super(MuranoClass, self).__init__(ns_resolver, name, package)
self._methods = {}
self._properties = {}
self._config = {}
self._extension_class = None
if self._name == constants.CORE_LIBRARY_OBJECT:
if (self._name == constants.CORE_LIBRARY_OBJECT or
parents is utils.NO_VALUE):
self._parents = []
else:
self._parents = parents or [
package.find_class(constants.CORE_LIBRARY_OBJECT)]
for p in self._parents:
if p.usage not in self._allowed_usages:
raise exceptions.InvalidInheritanceError(
u'Type {0} cannot have parent with Usage {1}'.format(
self.name, p.usage))
self._context = None
self._parent_mappings = self._build_parent_remappings()
self._property_values = {}
self._meta = dslmeta.MetaData(meta, dsl_types.MetaTargets.Type, self)
self._meta_values = None
@property
def usage(self):
return dsl_types.ClassUsages.Class
@property
def declared_parents(self):
@@ -111,10 +139,10 @@ class MuranoClass(dsl_types.MuranoClass, MuranoType):
names.update(c.properties.keys())
return tuple(names)
def add_property(self, name, property_typespec):
def add_property(self, property_typespec):
if not isinstance(property_typespec, murano_property.MuranoProperty):
raise TypeError('property_typespec')
self._properties[name] = property_typespec
self._properties[property_typespec.name] = property_typespec
def _find_symbol_chains(self, func, origin):
queue = collections.deque([(self, ())])
@@ -248,10 +276,6 @@ class MuranoClass(dsl_types.MuranoClass, MuranoType):
def __repr__(self):
return 'MuranoClass({0}/{1})'.format(self.name, self.version)
@property
def version(self):
return self.package.version
def _build_parent_remappings(self):
"""Remaps class parents.
@@ -360,11 +384,67 @@ class MuranoClass(dsl_types.MuranoClass, MuranoType):
cls = prop.declaring_type
cls._property_values[name] = prop.validate(value, cls, None, context)
def get_reference(self):
return dsl_types.MuranoTypeReference(self)
def get_meta(self, context):
if self._meta_values is None:
self._meta_values = dslmeta.merge_providers(
self, lambda cls: cls._meta, context)
return self._meta_values
class MuranoMetaClass(dsl_types.MuranoMetaClass, MuranoClass):
_allowed_usages = {dsl_types.ClassUsages.Meta, dsl_types.ClassUsages.Class}
def __init__(self, ns_resolver, name, package, parents, meta=None):
super(MuranoMetaClass, self).__init__(
ns_resolver, name, package, parents, meta)
self._cardinality = dsl_types.MetaCardinality.One
self._targets = list(dsl_types.MetaCardinality.All)
self._inherited = False
@property
def usage(self):
return dsl_types.ClassUsages.Meta
@property
def cardinality(self):
return self._cardinality
@cardinality.setter
def cardinality(self, value):
self._cardinality = value
@property
def targets(self):
return self._targets
@targets.setter
def targets(self, value):
self._targets = value
@property
def inherited(self):
return self._inherited
@inherited.setter
def inherited(self, value):
self._inherited = value
def __repr__(self):
return 'MuranoMetaClass({0}/{1})'.format(self.name, self.version)
def create(data, package, name, ns_resolver):
usage = data.get('Usage', dsl_types.ClassUsages.Class)
if usage == dsl_types.ClassUsages.Class:
return _create_class(MuranoClass, name, ns_resolver, data, package)
elif usage == dsl_types.ClassUsages.Meta:
return _create_meta_class(
MuranoMetaClass, name, ns_resolver, data, package)
else:
raise ValueError(u'Invalid type Usage: "{}"'.format(usage))
def _create_class(cls, name, ns_resolver, data, package, *args, **kwargs):
parent_class_names = data.get('Extends')
parent_classes = []
if parent_class_names:
@@ -374,13 +454,15 @@ def create(data, package, name, ns_resolver):
full_name = ns_resolver.resolve_name(str(parent_name))
parent_classes.append(package.find_class(full_name))
type_obj = MuranoClass(ns_resolver, name, package, parent_classes)
type_obj = cls(
ns_resolver, name, package, parent_classes, data.get('Meta'),
*args, **kwargs)
properties = data.get('Properties') or {}
for property_name, property_spec in six.iteritems(properties):
spec = murano_property.MuranoProperty(
type_obj, property_name, property_spec)
type_obj.add_property(property_name, spec)
type_obj.add_property(spec)
methods = data.get('Methods') or data.get('Workflow') or {}
@@ -394,3 +476,32 @@ def create(data, package, name, ns_resolver):
method_mappings.get(method_name, method_name), payload)
return type_obj
def _create_meta_class(cls, name, ns_resolver, data, package, *args, **kwargs):
cardinality = data.get('Cardinality', dsl_types.MetaCardinality.One)
if cardinality not in dsl_types.MetaCardinality.All:
raise ValueError(u'Invalid MetaClass Cardinality "{}"'.format(
cardinality))
applies_to = data.get('Applies', dsl_types.MetaTargets.All)
if isinstance(applies_to, six.string_types):
applies_to = [applies_to]
if isinstance(applies_to, list):
applies_to = set(applies_to)
delta = applies_to - dsl_types.MetaTargets.All - {'All'}
if delta:
raise ValueError(u'Invalid MetaClass target(s) {}:'.format(
', '.join(map(u'"{}"'.format, delta)))
)
if 'All' in applies_to:
applies_to = dsl_types.MetaTargets.All
inherited = data.get('Inherited', False)
if not isinstance(inherited, bool):
raise ValueError('Invalid Inherited value. Must be true or false')
meta_cls = _create_class(
cls, name, ns_resolver, data, package, *args, **kwargs)
meta_cls.targets = list(applies_to)
meta_cls.cardinality = cardinality
meta_cls.inherited = inherited
return meta_cls

View File

@@ -14,6 +14,8 @@
import re
import six
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*|=)$')
@@ -21,7 +23,11 @@ PREFIX_RE = re.compile(r'^([a-zA-Z_]\w*|=)$')
class NamespaceResolver(object):
def __init__(self, namespaces):
if namespaces is None:
namespaces = {}
for prefix, ns in namespaces.items():
if ns is None:
ns = ''
if PREFIX_RE.match(prefix) is None:
raise ValueError(
'Invalid namespace prefix "{0}"'.format(prefix))
@@ -32,8 +38,9 @@ class NamespaceResolver(object):
self._namespaces[''] = ''
def resolve_name(self, name):
if name is None or TYPE_NAME_RE.match(name) is None:
if not self.is_typename(name, True):
raise ValueError('Invalid type name "{0}"'.format(name))
name = six.text_type(name)
if ':' not in name:
if '.' in name:
parts = ['', name]
@@ -51,3 +58,12 @@ class NamespaceResolver(object):
if not ns:
return parts[1]
return '.'.join((ns, parts[1]))
@staticmethod
def is_typename(name, relaxed):
if not name:
return False
name = six.text_type(name)
if not relaxed and ':' not in name:
return False
return TYPE_NAME_RE.match(name) is not None

View File

@@ -52,7 +52,7 @@ class ObjectStore(object):
def put(self, murano_object):
self._store[murano_object.object_id] = murano_object
def load(self, value, owner, context=None, defaults=None):
def load(self, value, owner, context=None):
if value is None:
return None
if '?' not in value or 'type' not in value['?']:
@@ -83,7 +83,7 @@ class ObjectStore(object):
factory = class_obj.new(
owner, self, self.executor,
name=system_key.get('name'),
object_id=object_id, defaults=defaults)
object_id=object_id)
self._store[object_id] = factory
system_value = ObjectStore._get_designer_attributes(system_key)
self._designer_attributes_store[object_id] = system_value

View File

@@ -19,12 +19,19 @@ from yaql import yaqlization
from murano.dsl import dsl
from murano.dsl import dsl_types
from murano.dsl import helpers
from murano.dsl import meta
@specs.yaql_property(dsl_types.MuranoType)
@specs.name('name')
def class_name(murano_class):
return murano_class.name
def type_name(murano_type):
return murano_type.name
@specs.yaql_property(dsl_types.MuranoType)
@specs.name('usage')
def type_usage(murano_type):
return murano_type.usage
@specs.yaql_property(dsl_types.MuranoClass)
@@ -54,15 +61,15 @@ def ancestors(murano_class):
return tuple(murano_class.ancestors())
@specs.yaql_property(dsl_types.MuranoClass)
def package(murano_class):
return murano_class.package
@specs.yaql_property(dsl_types.MuranoType)
def package(murano_type):
return murano_type.package
@specs.yaql_property(dsl_types.MuranoClass)
@specs.name('version')
def class_version(murano_class):
return murano_class.version
def type_version(murano_type):
return murano_type.version
@specs.yaql_property(dsl_types.MuranoProperty)
@@ -195,21 +202,44 @@ def argument_owner(method_argument):
return method_argument.murano_method
@specs.yaql_property(dsl_types.MuranoClass)
@specs.yaql_property(dsl_types.MuranoType)
@specs.name('type')
def type_to_type_ref(murano_class):
return murano_class.get_reference()
def type_to_type_ref(murano_type):
return murano_type.get_reference()
@specs.parameter('provider', meta.MetaProvider)
@specs.name('#property#meta')
def get_meta(context, provider):
return provider.get_meta(context)
@specs.yaql_property(dsl_types.MuranoMetaClass)
def cardinality(murano_meta_class):
return murano_meta_class.cardinality
@specs.yaql_property(dsl_types.MuranoMetaClass)
def targets(murano_meta_class):
return murano_meta_class.targets
@specs.yaql_property(dsl_types.MuranoMetaClass)
def inherited(murano_meta_class):
return murano_meta_class.inherited
def register(context):
funcs = (
class_name, methods, properties, ancestors, package, class_version,
type_name, type_usage, type_version, type_to_type_ref,
methods, properties, ancestors, package,
property_name, property_has_default, property_owner,
property_usage, property_get_value, property_set_value,
method_name, arguments, method_owner, method_invoke,
types, package_name, package_version,
argument_name, argument_has_default, argument_owner,
type_to_type_ref
cardinality, targets, inherited,
get_meta
)
for f in funcs:
context.register_function(f)

View File

@@ -18,7 +18,6 @@ from yaql import utils
from murano.dsl import dsl
from murano.dsl import dsl_types
from murano.dsl import murano_method
class ObjRef(object):
@@ -73,7 +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 == murano_method.MethodUsages.Action)
lambda m: m.usage == dsl_types.MethodUsages.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

@@ -13,7 +13,6 @@
# under the License.
import sys
import uuid
import six
from yaql.language import specs
@@ -35,7 +34,7 @@ class TypeScheme(object):
self._spec = spec
@staticmethod
def prepare_context(root_context, this, owner, default):
def prepare_context(root_context, this, owner, default, calling_type):
@specs.parameter('value', nullable=True)
@specs.method
def int_(value):
@@ -164,17 +163,9 @@ class TypeScheme(object):
elif isinstance(value, dsl_types.MuranoObjectInterface):
obj = value.object
elif isinstance(value, utils.MappingType):
if '?' not in value:
new_value = {'?': {
'id': uuid.uuid4().hex,
'type': default_name.type.name,
'classVersion': str(default_name.type.version)
}}
new_value.update(value)
value = new_value
obj = object_store.load(
value, owner, root_context, defaults=default)
obj = helpers.instantiate(
value, owner, object_store, root_context,
calling_type, default_name, default)
elif isinstance(value, six.string_types):
obj = object_store.get(value)
if obj is None:
@@ -304,7 +295,7 @@ class TypeScheme(object):
else:
return self._map_scalar(data, spec)
def __call__(self, data, context, this, owner, default):
def __call__(self, data, context, this, owner, default, calling_type):
# TODO(ativelkov, slagun): temporary fix, need a better way of handling
# composite defaults
# A bug (#1313694) has been filed
@@ -312,7 +303,8 @@ class TypeScheme(object):
if data is dsl.NO_VALUE:
data = helpers.evaluate(default, context)
context = self.prepare_context(context, this, owner, default)
context = self.prepare_context(
context, this, owner, default, calling_type)
return self._map(data, self._spec, context, '')

View File

@@ -20,29 +20,17 @@ from murano.dsl import helpers
from murano.dsl import type_scheme
class PropertyUsages(object):
In = 'In'
Out = 'Out'
InOut = 'InOut'
Runtime = 'Runtime'
Const = 'Const'
Config = 'Config'
Static = 'Static'
All = set([In, Out, InOut, Runtime, Const, Config, Static])
Writable = set([Out, InOut, Runtime, Static])
class Spec(object):
def __init__(self, declaration, container_class):
self._container_class = weakref.ref(container_class)
def __init__(self, declaration, container_type):
self._container_type = weakref.ref(container_type)
self._contract = type_scheme.TypeScheme(declaration['Contract'])
self._usage = declaration.get('Usage') or 'In'
self._usage = declaration.get('Usage') or dsl_types.PropertyUsages.In
self._default = declaration.get('Default')
self._has_default = 'Default' in declaration
if self._usage not in PropertyUsages.All:
if self._usage not in dsl_types.PropertyUsages.All:
raise exceptions.DslSyntaxError(
'Unknown type {0}. Must be one of ({1})'.format(
self._usage, ', '.join(PropertyUsages.All)))
self._usage, ', '.join(dsl_types.PropertyUsages.All)))
def validate(self, value, this, owner, context, default=None):
if default is None:
@@ -51,12 +39,12 @@ class Spec(object):
if isinstance(this, dsl_types.MuranoType):
return self._contract(
value, executor.create_object_context(this),
None, None, default)
None, None, default, helpers.get_type(context))
else:
return self._contract(
value, executor.create_object_context(
this.cast(self._container_class())),
this, owner, default)
this.cast(self._container_type())),
this, owner, default, helpers.get_type(context))
@property
def default(self):

View File

@@ -32,7 +32,8 @@ class MuranoPackage(murano_package.MuranoPackage):
application_package.full_name,
application_package.version,
application_package.runtime_version,
application_package.requirements
application_package.requirements,
application_package.meta
)
def get_class_config(self, name):

View File

@@ -26,6 +26,7 @@ class MuranoPlPackage(package_base.PackageBase):
self._classes = manifest.get('Classes')
self._ui_file = manifest.get('UI', 'ui.yaml')
self._requirements = manifest.get('Require') or {}
self._meta = manifest.get('Meta')
@property
def classes(self):
@@ -54,3 +55,7 @@ class MuranoPlPackage(package_base.PackageBase):
name, 'File with class definition not found')
with open(full_path) as stream:
return stream.read(), full_path
@property
def meta(self):
return self._meta

View File

@@ -109,6 +109,10 @@ class Package(object):
def ui(self):
raise NotImplementedError()
@abc.abstractproperty
def meta(self):
raise NotImplementedError()
def _zip_dir(path, zip_file):
for root, _, files in os.walk(path):

View File

@@ -107,6 +107,10 @@ class PackageBase(package.Package):
def logo(self):
return self._load_image(self._logo, 'logo.png', 'logo')
@property
def meta(self):
return None
@property
def supplier_logo(self):
return self._load_image(

View File

@@ -27,11 +27,11 @@ from murano.tests.unit.dsl.foundation import object_model
class TestPackage(murano_package.MuranoPackage):
def __init__(self, pkg_loader, name, version,
runtime_version, requirements, configs):
runtime_version, requirements, configs, meta):
self.__configs = configs
super(TestPackage, self).__init__(
pkg_loader, name, version,
runtime_version, requirements)
runtime_version, requirements, meta)
def get_class_config(self, name):
return self.__configs.get(name, {})
@@ -43,7 +43,7 @@ class TestPackage(murano_package.MuranoPackage):
class TestPackageLoader(package_loader.MuranoPackageLoader):
_classes_cache = {}
def __init__(self, directory, package_name, parent_loader=None):
def __init__(self, directory, package_name, parent_loader=None, meta=None):
self._package_name = package_name
self._yaml_loader = yaql_yaml_loader.get_loader('1.0')
if directory in TestPackageLoader._classes_cache:
@@ -56,7 +56,7 @@ class TestPackageLoader(package_loader.MuranoPackageLoader):
self._configs = {}
self._package = TestPackage(
self, package_name, None, constants.RUNTIME_VERSION_1_0,
None, self._configs)
None, self._configs, meta)
for name, payload in six.iteritems(self._classes):
self._package.register_class(payload, name)
super(TestPackageLoader, self).__init__()

View File

@@ -0,0 +1,275 @@
Namespaces:
=: metatests
Name: InheritedMultiMeta
Usage: Meta
Cardinality: Many
Applies: All
Inherited: true
Properties:
val:
Contract: $.int().notNull()
Default: 111
--- # ------------------------------------------------------------------------
Name: InheritedSingleMeta
Usage: Meta
Cardinality: One
Applies: All
Inherited: true
Properties:
val:
Contract: $.int().notNull()
Default: 222
--- # ------------------------------------------------------------------------
Name: InheritedSingleMeta2
Usage: Meta
Cardinality: One
Applies: All
Inherited: true
Extends: InheritedSingleMeta
--- # ------------------------------------------------------------------------
Name: MultiMeta
Usage: Meta
Cardinality: Many
Applies: All
Inherited: false
Properties:
val:
Contract: $.int().notNull()
Default: 333
--- # ------------------------------------------------------------------------
Name: SingleMeta
Usage: Meta
Cardinality: One
Applies: All
Inherited: false
Properties:
val:
Contract: $.int().notNull()
Default: 444
--- # ------------------------------------------------------------------------
Name: SingleMeta2
Usage: Meta
Cardinality: One
Applies: All
Inherited: false
Extends: SingleMeta
--- # ------------------------------------------------------------------------
Name: ComplexMeta
Usage: Meta
Cardinality: Many
Properties:
cls:
Contract: $.class(PropertyType).notNull()
--- # ------------------------------------------------------------------------
Name: ParentClass0
Meta: [InheritedMultiMeta, SingleMeta]
Properties:
prop1:
Contract: $
Meta:
- SingleMeta:
val: 1
Methods:
foo:
Meta:
- InheritedMultiMeta:
val: 1
- InheritedSingleMeta:
val: 2
- SingleMeta:
val: 3
- InheritedSingleMeta2:
val: 10
- SingleMeta2:
val: 11
Arguments:
arg:
Contract: $.string()
--- # ------------------------------------------------------------------------
Name: ParentClass1
Extends: ParentClass0
Meta:
- InheritedMultiMeta:
val: 1
- InheritedSingleMeta:
val: 6
- SingleMeta:
val: 7
Methods:
foo:
Meta:
- InheritedMultiMeta:
val: 4
- InheritedSingleMeta:
val: 5
- SingleMeta:
val: 6
Arguments:
arg:
Contract: $.string()
--- # ------------------------------------------------------------------------
Name: ParentClass2
Extends: ParentClass0
Usage: Class
Meta:
- InheritedMultiMeta:
val: 2
- SingleMeta:
val: 3
Properties:
prop2:
Contract: $
Meta:
- InheritedMultiMeta:
val: 1
- MultiMeta:
val: 2
- SingleMeta:
val: 3
--- # ------------------------------------------------------------------------
Name: TestMeta
Extends: [ParentClass1, ParentClass2]
Meta:
- 'metatests.InheritedMultiMeta':
val: 4
- :SingleMeta:
val: 5
Properties:
prop2:
Contract: $
Meta:
- InheritedMultiMeta:
val: 4
Methods:
testClassInheritedMeta:
Body:
- Return: typeinfo($).meta.where($ is InheritedMultiMeta).val
testClassNotInheritedMeta:
Body:
- Return: typeinfo($).meta.
where($ is SingleMeta or $ is InheritedSingleMeta).val
testParentClassNotInheritedMeta:
Body:
- Return: typeinfo(ParentClass2).meta.
where(not typeinfo($).inherited).single().val
testMethodMeta:
Body:
- Return: typeinfo($).methods.where($.name = foo).single().meta.val
testMethodArgumentMeta:
Body:
- Return: typeinfo($).
methods.where($.name = foo).single().
arguments.single().meta.val
testInheritedPropertyMeta:
Body:
- Return: typeinfo($).properties.
where($.name = prop1).single().meta.val
testOverriddenPropertyMeta:
Body:
- Return: typeinfo($).properties.
where($.name = prop2).single().meta.val
testPackageMeta:
Body:
- Return: typeinfo($).package.meta
testComplexMeta:
Body:
- Return: typeinfo($).
methods.where($.name = bar).single().meta.cls.
select([$.prop, typeinfo($).name])
foo:
Meta:
- InheritedMultiMeta:
val: 7
- InheritedSingleMeta:
val: 8
- SingleMeta:
val: 9
Arguments:
arg:
Contract: $.string()
Meta:
- SingleMeta:
val: 1
- MultiMeta:
val: 2
- MultiMeta:
val: 3
bar:
Meta:
- ComplexMeta:
cls:
:PropertyType:
prop: 1
- ComplexMeta:
cls:
prop: 2
- :ComplexMeta:
cls:
:PropertyType2:
prop: 3
- 'metatests.ComplexMeta':
cls:
prop: 4
- ComplexMeta:
cls:
?:
type: 'metatests.PropertyType2'
prop: 5
--- # ------------------------------------------------------------------------
Name: PropertyType
Properties:
prop:
Contract: $.int().notNull()
Default: 44
--- # ------------------------------------------------------------------------
Name: PropertyType2
Extends: PropertyType

View File

@@ -0,0 +1,63 @@
# 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.tests.unit.dsl.foundation import object_model as om
from murano.tests.unit.dsl.foundation import test_case
class TestMeta(test_case.DslTestCase):
def setUp(self):
super(TestMeta, self).setUp()
self._runner = self.new_runner(om.Object('metatests.TestMeta'))
def test_class_inherited_meta(self):
self.assertItemsEqual(
[4, 1, 111, 2], self._runner.testClassInheritedMeta())
def test_class_not_inherited_meta(self):
self.assertItemsEqual(
[5, 6], self._runner.testClassNotInheritedMeta())
def test_parent_class_not_inherited_meta(self):
self.assertEqual(3, self._runner.testParentClassNotInheritedMeta())
def test_method_meta(self):
self.assertItemsEqual(
[7, 8, 9, 4, 1, 10], self._runner.testMethodMeta())
def test_method_argument_meta(self):
self.assertItemsEqual(
[1, 2, 3], self._runner.testMethodArgumentMeta())
def test_inherited_property_meta(self):
self.assertEqual(
[1], self._runner.testInheritedPropertyMeta())
def test_overridden_property_meta(self):
self.assertItemsEqual(
[1, 4], self._runner.testOverriddenPropertyMeta())
def test_package_meta(self):
self.assertEqual(
[], self._runner.testPackageMeta())
def test_complex_meta(self):
self.assertItemsEqual([
[1, 'metatests.PropertyType'],
[2, 'metatests.PropertyType'],
[3, 'metatests.PropertyType2'],
[4, 'metatests.PropertyType'],
[5, 'metatests.PropertyType2']
], self._runner.testComplexMeta())

View File

@@ -14,7 +14,7 @@
import mock
from murano.dsl import murano_method
from murano.dsl import dsl_types
from murano.dsl import serializer
from murano.services import actions
from murano.tests.unit import base
@@ -26,13 +26,13 @@ class TestActionsSerializer(base.MuranoTestCase):
def _get_mocked_obj(self):
method1 = mock.Mock()
method1.usage = murano_method.MethodUsages.Action
method1.usage = dsl_types.MethodUsages.Action
method1.name = 'method1'
method2 = mock.Mock()
method2.usage = murano_method.MethodUsages.Runtime
method2.usage = dsl_types.MethodUsages.Runtime
method2.name = 'method2'
method3 = mock.Mock()
method3.usage = murano_method.MethodUsages.Action
method3.usage = dsl_types.MethodUsages.Action
method3.name = 'method3'
obj2_type = mock.Mock()

View File

@@ -0,0 +1,12 @@
---
features:
- Added ability to extend MuranoPL entities with custom metadata.
- >
For MuranoPL classes new key "Usage" was added. By default it is "Class".
But it can also be "Meta" to define meta-class. Meta-class has all the
capabilities of regular classes and in addition has 3 new attributes:
Cardinality, Applies and Inherited.
- It is possible to attach meta-class instances to packages,
classes (including other meta-classes), properties, methods and
method arguments. Each of them got new "Meta" key containing
list (or single scalar) of meta-class instances.