ARIA-139 Support attributes
* Fully implement attribute support in parser
* New intrinsic function evaluation mechanism
* Implemented more intrinsic functions, including get_attribute
* Fix to one-on-one relationship back population
* Fixes to TOSCA use case examples
* Indirectly related: re-enabled node_filter mechanism and reworked
filter constraints in order to make them serializable
* utils/type is much more robust now and consolidates all conversions
and names
* Moved dsl_specification to new utils/specification (because utils/type
uses it)
diff --git a/aria/core.py b/aria/core.py
index af1984a..cc943ef 100644
--- a/aria/core.py
+++ b/aria/core.py
@@ -77,10 +77,14 @@
consumption.ConsumerChain(
context,
(
+ consumption.CoerceServiceInstanceValues,
+ consumption.ValidateServiceInstance,
consumption.SatisfyRequirements,
+ consumption.CoerceServiceInstanceValues,
consumption.ValidateCapabilities,
consumption.FindHosts,
- consumption.ConfigureOperations
+ consumption.ConfigureOperations,
+ consumption.CoerceServiceInstanceValues
)).consume()
if context.validation.dump_issues():
raise exceptions.InstantiationError('Failed to instantiate service template')
diff --git a/aria/modeling/contraints.py b/aria/modeling/contraints.py
new file mode 100644
index 0000000..107b010
--- /dev/null
+++ b/aria/modeling/contraints.py
@@ -0,0 +1,28 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+
+
+class NodeTemplateConstraint(object):
+ """
+ Used to constrain requirements for node templates.
+
+ Must be serializable.
+ """
+
+ def matches(self, source_node_template, target_node_template):
+ """
+ Returns true is the target matches the constraint for the source.
+ """
+ raise NotImplementedError
diff --git a/aria/modeling/functions.py b/aria/modeling/functions.py
index 02f4454..06fd19f 100644
--- a/aria/modeling/functions.py
+++ b/aria/modeling/functions.py
@@ -13,20 +13,118 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from ..parser.consumption import ConsumptionContext
+from ..parser.exceptions import InvalidValueError
+from ..utils.collections import OrderedDict
+from . import exceptions
+
+
class Function(object):
"""
- An intrinsic function.
+ Base class for intrinsic functions. Serves as a placeholder for a value that should eventually
+ be derived by "evaluating" (calling) the function.
- Serves as a placeholder for a value that should eventually be derived by calling the function.
+ Note that this base class is provided as a convenience and you do not have to inherit it: any
+ object with an ``__evaluate__`` method would be treated similarly.
"""
@property
def as_raw(self):
raise NotImplementedError
- def _evaluate(self, context, container):
+ def __evaluate__(self, container_holder):
+ """
+ Evaluates the function if possible. If impossible, raises
+ :class:`CannotEvaluateFunctionException` (do not just return None).
+
+ :rtype: Evaluation (or any object with ``value`` and ``final`` properties)
+ """
+
raise NotImplementedError
def __deepcopy__(self, memo):
# Circumvent cloning in order to maintain our state
return self
+
+
+class Evaluation(object):
+ """
+ An evaluated :class:`Function` return value.
+ """
+
+ def __init__(self, value, final=False):
+ self.value = value
+ self.final = final
+
+
+def evaluate(value, container_holder, report_issues=False): # pylint: disable=too-many-branches
+ """
+ Recursively attempts to call ``__evaluate__``. If an evaluation occurred will return an
+ :class:`Evaluation`, otherwise it will be None. If any evaluation is non-final, then the entire
+ evaluation will also be non-final.
+
+ The ``container_holder`` argument should have three properties: ``container`` should return
+ the model that contains the value, ``service`` should return the containing
+ :class:`aria.modeling.models.Service` model or None, and ``service_template`` should return the
+ containing :class:`aria.modeling.models.ServiceTemplate` model or None.
+ """
+
+ evaluated = False
+ final = True
+
+ if hasattr(value, '__evaluate__'):
+ try:
+ evaluation = value.__evaluate__(container_holder)
+
+ # Verify evaluation structure
+ if (evaluation is None) \
+ or (not hasattr(evaluation, 'value')) \
+ or (not hasattr(evaluation, 'final')):
+ raise InvalidValueError('bad __evaluate__ implementation')
+
+ evaluated = True
+ value = evaluation.value
+ final = evaluation.final
+
+ # The evaluated value might itself be evaluable
+ evaluation = evaluate(value, container_holder, report_issues)
+ if evaluation is not None:
+ value = evaluation.value
+ if not evaluation.final:
+ final = False
+ except exceptions.CannotEvaluateFunctionException:
+ pass
+ except InvalidValueError as e:
+ if report_issues:
+ context = ConsumptionContext.get_thread_local()
+ context.validation.report(e.issue)
+
+ elif isinstance(value, list):
+ evaluated_list = []
+ for v in value:
+ evaluation = evaluate(v, container_holder, report_issues)
+ if evaluation is not None:
+ evaluated_list.append(evaluation.value)
+ evaluated = True
+ if not evaluation.final:
+ final = False
+ else:
+ evaluated_list.append(v)
+ if evaluated:
+ value = evaluated_list
+
+ elif isinstance(value, dict):
+ evaluated_dict = OrderedDict()
+ for k, v in value.iteritems():
+ evaluation = evaluate(v, container_holder, report_issues)
+ if evaluation is not None:
+ evaluated_dict[k] = evaluation.value
+ evaluated = True
+ if not evaluation.final:
+ final = False
+ else:
+ evaluated_dict[k] = v
+ if evaluated:
+ value = evaluated_dict
+
+ return Evaluation(value, final) if evaluated else None
diff --git a/aria/modeling/mixins.py b/aria/modeling/mixins.py
index e6db5a3..38c812d 100644
--- a/aria/modeling/mixins.py
+++ b/aria/modeling/mixins.py
@@ -124,7 +124,7 @@
def validate(self):
pass
- def coerce_values(self, container, report_issues):
+ def coerce_values(self, report_issues):
pass
def dump(self):
diff --git a/aria/modeling/relationship.py b/aria/modeling/relationship.py
index 291d08c..40be5b2 100644
--- a/aria/modeling/relationship.py
+++ b/aria/modeling/relationship.py
@@ -146,13 +146,18 @@
false to disable
:type back_populates: basestring|bool
"""
- if back_populates is None:
- back_populates = model_class.__tablename__
+ backref_kwargs = None
+ if back_populates is not NO_BACK_POP:
+ if back_populates is None:
+ back_populates = model_class.__tablename__
+ backref_kwargs = {'name': back_populates, 'uselist': False}
+ back_populates = None
return _relationship(model_class,
other_table,
fk=fk,
back_populates=back_populates,
+ backref_kwargs=backref_kwargs,
other_fk=other_fk)
@@ -190,6 +195,7 @@
rel_kwargs.setdefault('cascade', 'all')
if back_populates is None:
back_populates = model_class.__tablename__
+
return _relationship(
model_class,
child_table,
@@ -330,10 +336,11 @@
if backref_kwargs:
assert back_populates is None
- return relationship(lambda: _get_class_for_table(model_class, other_table_name),
- backref=backref(**backref_kwargs),
- **relationship_kwargs
- )
+ return relationship(
+ lambda: _get_class_for_table(model_class, other_table_name),
+ backref=backref(**backref_kwargs),
+ **relationship_kwargs
+ )
else:
if back_populates is not NO_BACK_POP:
relationship_kwargs['back_populates'] = back_populates
diff --git a/aria/modeling/service_common.py b/aria/modeling/service_common.py
index 1188f34..e9c96a4 100644
--- a/aria/modeling/service_common.py
+++ b/aria/modeling/service_common.py
@@ -23,19 +23,20 @@
from sqlalchemy.ext.declarative import declared_attr
from ..parser.consumption import ConsumptionContext
-from ..utils import collections, formatting, console
-from .mixins import InstanceModelMixin, TemplateModelMixin
+from ..utils import (collections, formatting, console, caching)
+from ..utils.type import (canonical_type_name, full_type_name)
+from .mixins import (InstanceModelMixin, TemplateModelMixin)
from . import (
relationship,
- utils
+ functions
)
-class ParameterBase(TemplateModelMixin):
+class ParameterBase(TemplateModelMixin, caching.HasCachedMethods):
"""
- Represents a typed value.
+ Represents a typed value. The value can contain nested intrinsic functions.
- This model is used by both service template and service instance elements.
+ This model can be used as the ``container_holder`` argument for :func:`functions.evaluate`.
:ivar name: Name
:vartype name: basestring
@@ -50,8 +51,120 @@
name = Column(Text)
type_name = Column(Text)
- value = Column(PickleType)
description = Column(Text)
+ _value = Column(PickleType)
+
+ @property
+ def value(self):
+ value = self._value
+ if value is not None:
+ evaluation = functions.evaluate(value, self)
+ if evaluation is not None:
+ value = evaluation.value
+ return value
+
+ @value.setter
+ def value(self, value):
+ self._value = value
+
+ @property
+ @caching.cachedmethod
+ def owner(self):
+ """
+ The sole owner of this parameter, which is another model that relates to it.
+
+ *All* parameters should have an owner model. In case this property method fails to find
+ it, it will raise a ValueError, which should signify an abnormal, orphaned parameter.
+ """
+
+ # Find first non-null relationship
+ for the_relationship in self.__mapper__.relationships:
+ v = getattr(self, the_relationship.key)
+ if v:
+ return v[0] # because we are many-to-many, the back reference will be a list
+
+ raise ValueError('orphaned parameter: does not have an owner: {0}'.format(self.name))
+
+
+ @property
+ @caching.cachedmethod
+ def container(self): # pylint: disable=too-many-return-statements,too-many-branches
+ """
+ The logical container for this parameter, which would be another model: service, node,
+ group, or policy (or their templates).
+
+ The logical container is equivalent to the ``SELF`` keyword used by intrinsic functions in
+ TOSCA.
+
+ *All* parameters should have a container model. In case this property method fails to find
+ it, it will raise a ValueError, which should signify an abnormal, orphaned parameter.
+ """
+
+ from . import models
+
+ container = self.owner
+
+ # Extract interface from operation
+ if isinstance(container, models.Operation):
+ container = container.interface
+ elif isinstance(container, models.OperationTemplate):
+ container = container.interface_template
+
+ # Extract from other models
+ if isinstance(container, models.Interface):
+ container = container.node or container.group or container.relationship
+ elif isinstance(container, models.InterfaceTemplate):
+ container = container.node_template or container.group_template \
+ or container.relationship_template
+ elif isinstance(container, models.Capability) or isinstance(container, models.Artifact):
+ container = container.node
+ elif isinstance(container, models.CapabilityTemplate) \
+ or isinstance(container, models.ArtifactTemplate):
+ container = container.node_template
+ elif isinstance(container, models.Task):
+ container = container.actor
+
+ # Extract node from relationship
+ if isinstance(container, models.Relationship):
+ container = container.source_node
+ elif isinstance(container, models.RelationshipTemplate):
+ container = container.requirement_template.node_template
+
+ if container is not None:
+ return container
+
+ raise ValueError('orphaned parameter: does not have a container: {0}'.format(self.name))
+
+ @property
+ @caching.cachedmethod
+ def service(self):
+ """
+ The :class:`Service` containing this parameter, or None if not contained in a service.
+ """
+
+ from . import models
+ container = self.container
+ if isinstance(container, models.Service):
+ return container
+ elif hasattr(container, 'service'):
+ return container.service
+ return None
+
+ @property
+ @caching.cachedmethod
+ def service_template(self):
+ """
+ The :class:`ServiceTemplate` containing this parameter, or None if not contained in a
+ service template.
+ """
+
+ from . import models
+ container = self.container
+ if isinstance(container, models.ServiceTemplate):
+ return container
+ elif hasattr(container, 'service_template'):
+ return container.service_template
+ return None
@property
def as_raw(self):
@@ -63,27 +176,30 @@
def instantiate(self, container):
from . import models
- return models.Parameter(name=self.name,
+ return models.Parameter(name=self.name, # pylint: disable=unexpected-keyword-arg
type_name=self.type_name,
- value=self.value,
+ _value=self._value,
description=self.description)
- def coerce_values(self, container, report_issues):
- if self.value is not None:
- self.value = utils.coerce_value(container, self.value,
- report_issues)
+ def coerce_values(self, report_issues):
+ value = self._value
+ if value is not None:
+ evaluation = functions.evaluate(value, self, report_issues)
+ if (evaluation is not None) and evaluation.final:
+ # A final evaluation can safely replace the existing value
+ self._value = evaluation.value
def dump(self):
context = ConsumptionContext.get_thread_local()
if self.type_name is not None:
console.puts('{0}: {1} ({2})'.format(
context.style.property(self.name),
- context.style.literal(self.value),
+ context.style.literal(formatting.as_raw(self.value)),
context.style.type(self.type_name)))
else:
console.puts('{0}: {1}'.format(
context.style.property(self.name),
- context.style.literal(self.value)))
+ context.style.literal(formatting.as_raw(self.value))))
if self.description:
console.puts(context.style.meta(self.description))
@@ -101,11 +217,15 @@
:param description: Description (optional)
:type description: basestring
"""
- return cls(name=name,
- type_name=formatting.full_type_name(value)
- if value is not None else None,
- value=value,
- description=description)
+
+ from . import models
+ type_name = canonical_type_name(value)
+ if type_name is None:
+ type_name = full_type_name(value)
+ return models.Parameter(name=name, # pylint: disable=unexpected-keyword-arg
+ type_name=type_name,
+ value=value,
+ description=description)
class TypeBase(InstanceModelMixin):
@@ -188,7 +308,7 @@
self._append_raw_children(types)
return types
- def coerce_values(self, container, report_issues):
+ def coerce_values(self, report_issues):
pass
def dump(self):
@@ -237,7 +357,7 @@
('name', self.name),
('value', self.value)))
- def coerce_values(self, container, report_issues):
+ def coerce_values(self, report_issues):
pass
def instantiate(self, container):
diff --git a/aria/modeling/service_instance.py b/aria/modeling/service_instance.py
index ad8e7ed..1efe1e1 100644
--- a/aria/modeling/service_instance.py
+++ b/aria/modeling/service_instance.py
@@ -251,16 +251,16 @@
utils.validate_dict_values(self.outputs)
utils.validate_dict_values(self.workflows)
- def coerce_values(self, container, report_issues):
- utils.coerce_dict_values(container, self.meta_data, report_issues)
- utils.coerce_dict_values(container, self.nodes, report_issues)
- utils.coerce_dict_values(container, self.groups, report_issues)
- utils.coerce_dict_values(container, self.policies, report_issues)
+ def coerce_values(self, report_issues):
+ utils.coerce_dict_values(self.meta_data, report_issues)
+ utils.coerce_dict_values(self.nodes, report_issues)
+ utils.coerce_dict_values(self.groups, report_issues)
+ utils.coerce_dict_values(self.policies, report_issues)
if self.substitution is not None:
- self.substitution.coerce_values(container, report_issues)
- utils.coerce_dict_values(container, self.inputs, report_issues)
- utils.coerce_dict_values(container, self.outputs, report_issues)
- utils.coerce_dict_values(container, self.workflows, report_issues)
+ self.substitution.coerce_values(report_issues)
+ utils.coerce_dict_values(self.inputs, report_issues)
+ utils.coerce_dict_values(self.outputs, report_issues)
+ utils.coerce_dict_values(self.workflows, report_issues)
def dump(self):
context = ConsumptionContext.get_thread_local()
@@ -513,6 +513,10 @@
def properties(cls):
return relationship.many_to_many(cls, 'parameter', prefix='properties', dict_key='name')
+ @declared_attr
+ def attributes(cls):
+ return relationship.many_to_many(cls, 'parameter', prefix='attributes', dict_key='name')
+
# endregion
description = Column(Text)
@@ -646,6 +650,7 @@
('name', self.name),
('type_name', self.type.name),
('properties', formatting.as_raw_dict(self.properties)),
+ ('attributes', formatting.as_raw_dict(self.properties)),
('interfaces', formatting.as_raw_list(self.interfaces)),
('artifacts', formatting.as_raw_list(self.artifacts)),
('capabilities', formatting.as_raw_list(self.capabilities)),
@@ -664,17 +669,19 @@
# TODO: validate that node template is of type?
utils.validate_dict_values(self.properties)
+ utils.validate_dict_values(self.attributes)
utils.validate_dict_values(self.interfaces)
utils.validate_dict_values(self.artifacts)
utils.validate_dict_values(self.capabilities)
utils.validate_list_values(self.outbound_relationships)
- def coerce_values(self, container, report_issues):
- utils.coerce_dict_values(self, self.properties, report_issues)
- utils.coerce_dict_values(self, self.interfaces, report_issues)
- utils.coerce_dict_values(self, self.artifacts, report_issues)
- utils.coerce_dict_values(self, self.capabilities, report_issues)
- utils.coerce_list_values(self, self.outbound_relationships, report_issues)
+ def coerce_values(self, report_issues):
+ utils.coerce_dict_values(self.properties, report_issues)
+ utils.coerce_dict_values(self.attributes, report_issues)
+ utils.coerce_dict_values(self.interfaces, report_issues)
+ utils.coerce_dict_values(self.artifacts, report_issues)
+ utils.coerce_dict_values(self.capabilities, report_issues)
+ utils.coerce_list_values(self.outbound_relationships, report_issues)
def dump(self):
context = ConsumptionContext.get_thread_local()
@@ -683,6 +690,7 @@
console.puts('Type: {0}'.format(context.style.type(self.type.name)))
console.puts('Template: {0}'.format(context.style.node(self.node_template.name)))
utils.dump_dict_values(self.properties, 'Properties')
+ utils.dump_dict_values(self.attributes, 'Attributes')
utils.dump_interfaces(self.interfaces)
utils.dump_dict_values(self.artifacts, 'Artifacts')
utils.dump_dict_values(self.capabilities, 'Capabilities')
@@ -797,9 +805,9 @@
utils.validate_dict_values(self.properties)
utils.validate_dict_values(self.interfaces)
- def coerce_values(self, container, report_issues):
- utils.coerce_dict_values(container, self.properties, report_issues)
- utils.coerce_dict_values(container, self.interfaces, report_issues)
+ def coerce_values(self, report_issues):
+ utils.coerce_dict_values(self.properties, report_issues)
+ utils.coerce_dict_values(self.interfaces, report_issues)
def dump(self):
context = ConsumptionContext.get_thread_local()
@@ -916,8 +924,8 @@
def validate(self):
utils.validate_dict_values(self.properties)
- def coerce_values(self, container, report_issues):
- utils.coerce_dict_values(container, self.properties, report_issues)
+ def coerce_values(self, report_issues):
+ utils.coerce_dict_values(self.properties, report_issues)
def dump(self):
context = ConsumptionContext.get_thread_local()
@@ -1017,8 +1025,8 @@
def validate(self):
utils.validate_dict_values(self.mappings)
- def coerce_values(self, container, report_issues):
- utils.coerce_dict_values(container, self.mappings, report_issues)
+ def coerce_values(self, report_issues):
+ utils.coerce_dict_values(self.mappings, report_issues)
def dump(self):
context = ConsumptionContext.get_thread_local()
@@ -1121,7 +1129,7 @@
return collections.OrderedDict((
('name', self.name)))
- def coerce_values(self, container, report_issues):
+ def coerce_values(self, report_issues):
pass
def validate(self):
@@ -1311,9 +1319,9 @@
utils.validate_dict_values(self.properties)
utils.validate_dict_values(self.interfaces)
- def coerce_values(self, container, report_issues):
- utils.coerce_dict_values(container, self.properties, report_issues)
- utils.coerce_dict_values(container, self.interfaces, report_issues)
+ def coerce_values(self, report_issues):
+ utils.coerce_dict_values(self.properties, report_issues)
+ utils.coerce_dict_values(self.interfaces, report_issues)
def dump(self):
context = ConsumptionContext.get_thread_local()
@@ -1451,8 +1459,8 @@
def validate(self):
utils.validate_dict_values(self.properties)
- def coerce_values(self, container, report_issues):
- utils.coerce_dict_values(container, self.properties, report_issues)
+ def coerce_values(self, report_issues):
+ utils.coerce_dict_values(self.properties, report_issues)
def dump(self):
context = ConsumptionContext.get_thread_local()
@@ -1598,9 +1606,9 @@
utils.validate_dict_values(self.inputs)
utils.validate_dict_values(self.operations)
- def coerce_values(self, container, report_issues):
- utils.coerce_dict_values(container, self.inputs, report_issues)
- utils.coerce_dict_values(container, self.operations, report_issues)
+ def coerce_values(self, report_issues):
+ utils.coerce_dict_values(self.inputs, report_issues)
+ utils.coerce_dict_values(self.operations, report_issues)
def dump(self):
context = ConsumptionContext.get_thread_local()
@@ -1765,8 +1773,8 @@
# TODO must be associated with interface or service
utils.validate_dict_values(self.inputs)
- def coerce_values(self, container, report_issues):
- utils.coerce_dict_values(container, self.inputs, report_issues)
+ def coerce_values(self, report_issues):
+ utils.coerce_dict_values(self.inputs, report_issues)
def dump(self):
context = ConsumptionContext.get_thread_local()
@@ -1905,8 +1913,8 @@
def validate(self):
utils.validate_dict_values(self.properties)
- def coerce_values(self, container, report_issues):
- utils.coerce_dict_values(container, self.properties, report_issues)
+ def coerce_values(self, report_issues):
+ utils.coerce_dict_values(self.properties, report_issues)
def dump(self):
context = ConsumptionContext.get_thread_local()
diff --git a/aria/modeling/service_template.py b/aria/modeling/service_template.py
index e3320fa..7a192a7 100644
--- a/aria/modeling/service_template.py
+++ b/aria/modeling/service_template.py
@@ -17,7 +17,6 @@
from __future__ import absolute_import # so we can import standard 'types'
-from types import FunctionType
from datetime import datetime
from sqlalchemy import (
@@ -25,7 +24,8 @@
Text,
Integer,
Boolean,
- DateTime
+ DateTime,
+ PickleType
)
from sqlalchemy.ext.declarative import declared_attr
from sqlalchemy.ext.associationproxy import association_proxy
@@ -347,16 +347,16 @@
if self.artifact_types is not None:
self.artifact_types.validate()
- def coerce_values(self, container, report_issues):
- utils.coerce_dict_values(container, self.meta_data, report_issues)
- utils.coerce_dict_values(container, self.node_templates, report_issues)
- utils.coerce_dict_values(container, self.group_templates, report_issues)
- utils.coerce_dict_values(container, self.policy_templates, report_issues)
+ def coerce_values(self, report_issues):
+ utils.coerce_dict_values(self.meta_data, report_issues)
+ utils.coerce_dict_values(self.node_templates, report_issues)
+ utils.coerce_dict_values(self.group_templates, report_issues)
+ utils.coerce_dict_values(self.policy_templates, report_issues)
if self.substitution_template is not None:
- self.substitution_template.coerce_values(container, report_issues)
- utils.coerce_dict_values(container, self.inputs, report_issues)
- utils.coerce_dict_values(container, self.outputs, report_issues)
- utils.coerce_dict_values(container, self.workflow_templates, report_issues)
+ self.substitution_template.coerce_values(report_issues)
+ utils.coerce_dict_values(self.inputs, report_issues)
+ utils.coerce_dict_values(self.outputs, report_issues)
+ utils.coerce_dict_values(self.workflow_templates, report_issues)
def dump(self):
context = ConsumptionContext.get_thread_local()
@@ -427,7 +427,7 @@
:ivar requirement_templates: Potential relationships with other nodes
:vartype requirement_templates: [:class:`RequirementTemplate`]
:ivar target_node_template_constraints: Constraints for filtering relationship targets
- :vartype target_node_template_constraints: [:class:`FunctionType`]
+ :vartype target_node_template_constraints: [:class:`NodeTemplateConstraint`]
:ivar service_template: Containing service template
:vartype service_template: :class:`ServiceTemplate`
:ivar group_templates: We are a member of these groups
@@ -504,6 +504,10 @@
return relationship.many_to_many(cls, 'parameter', prefix='properties', dict_key='name')
@declared_attr
+ def attributes(cls):
+ return relationship.many_to_many(cls, 'parameter', prefix='attributes', dict_key='name')
+
+ @declared_attr
def interface_templates(cls):
return relationship.one_to_many(cls, 'interface_template', dict_key='name')
@@ -525,12 +529,12 @@
default_instances = Column(Integer, default=1)
min_instances = Column(Integer, default=0)
max_instances = Column(Integer, default=None)
- target_node_template_constraints = Column(modeling_types.StrictList(FunctionType))
+ target_node_template_constraints = Column(PickleType)
- def is_target_node_valid(self, target_node_template):
+ def is_target_node_template_valid(self, target_node_template):
if self.target_node_template_constraints:
- for node_type_constraint in self.target_node_template_constraints:
- if not node_type_constraint(target_node_template, self):
+ for node_template_constraint in self.target_node_template_constraints:
+ if not node_template_constraint.matches(self, target_node_template):
return False
return True
@@ -544,6 +548,7 @@
('min_instances', self.min_instances),
('max_instances', self.max_instances),
('properties', formatting.as_raw_dict(self.properties)),
+ ('attributes', formatting.as_raw_dict(self.properties)),
('interface_templates', formatting.as_raw_list(self.interface_templates)),
('artifact_templates', formatting.as_raw_list(self.artifact_templates)),
('capability_templates', formatting.as_raw_list(self.capability_templates)),
@@ -564,24 +569,34 @@
runtime_properties={},
node_template=self)
utils.instantiate_dict(node, node.properties, self.properties)
+ utils.instantiate_dict(node, node.attributes, self.attributes)
utils.instantiate_dict(node, node.interfaces, self.interface_templates)
utils.instantiate_dict(node, node.artifacts, self.artifact_templates)
utils.instantiate_dict(node, node.capabilities, self.capability_templates)
+
+ # Default attributes
+ if 'tosca_name' in node.attributes:
+ node.attributes['tosca_name'].value = self.name
+ if 'tosca_id' in node.attributes:
+ node.attributes['tosca_id'].value = name
+
return node
def validate(self):
utils.validate_dict_values(self.properties)
+ utils.validate_dict_values(self.attributes)
utils.validate_dict_values(self.interface_templates)
utils.validate_dict_values(self.artifact_templates)
utils.validate_dict_values(self.capability_templates)
utils.validate_list_values(self.requirement_templates)
- def coerce_values(self, container, report_issues):
- utils.coerce_dict_values(self, self.properties, report_issues)
- utils.coerce_dict_values(self, self.interface_templates, report_issues)
- utils.coerce_dict_values(self, self.artifact_templates, report_issues)
- utils.coerce_dict_values(self, self.capability_templates, report_issues)
- utils.coerce_list_values(self, self.requirement_templates, report_issues)
+ def coerce_values(self, report_issues):
+ utils.coerce_dict_values(self.properties, report_issues)
+ utils.coerce_dict_values(self.attributes, report_issues)
+ utils.coerce_dict_values(self.interface_templates, report_issues)
+ utils.coerce_dict_values(self.artifact_templates, report_issues)
+ utils.coerce_dict_values(self.capability_templates, report_issues)
+ utils.coerce_list_values(self.requirement_templates, report_issues)
def dump(self):
context = ConsumptionContext.get_thread_local()
@@ -597,6 +612,7 @@
if self.max_instances is not None
else ' or more'))
utils.dump_dict_values(self.properties, 'Properties')
+ utils.dump_dict_values(self.attributes, 'Attributes')
utils.dump_interfaces(self.interface_templates)
utils.dump_dict_values(self.artifact_templates, 'Artifact templates')
utils.dump_dict_values(self.capability_templates, 'Capability templates')
@@ -720,9 +736,9 @@
utils.validate_dict_values(self.properties)
utils.validate_dict_values(self.interface_templates)
- def coerce_values(self, container, report_issues):
- utils.coerce_dict_values(self, self.properties, report_issues)
- utils.coerce_dict_values(self, self.interface_templates, report_issues)
+ def coerce_values(self, report_issues):
+ utils.coerce_dict_values(self.properties, report_issues)
+ utils.coerce_dict_values(self.interface_templates, report_issues)
def dump(self):
context = ConsumptionContext.get_thread_local()
@@ -851,8 +867,8 @@
def validate(self):
utils.validate_dict_values(self.properties)
- def coerce_values(self, container, report_issues):
- utils.coerce_dict_values(self, self.properties, report_issues)
+ def coerce_values(self, report_issues):
+ utils.coerce_dict_values(self.properties, report_issues)
def dump(self):
context = ConsumptionContext.get_thread_local()
@@ -945,8 +961,8 @@
def validate(self):
utils.validate_dict_values(self.mappings)
- def coerce_values(self, container, report_issues):
- utils.coerce_dict_values(self, self.mappings, report_issues)
+ def coerce_values(self, report_issues):
+ utils.coerce_dict_values(self.mappings, report_issues)
def dump(self):
context = ConsumptionContext.get_thread_local()
@@ -1049,7 +1065,7 @@
return collections.OrderedDict((
('name', self.name)))
- def coerce_values(self, container, report_issues):
+ def coerce_values(self, report_issues):
pass
def instantiate(self, container):
@@ -1113,7 +1129,7 @@
:ivar target_capability_name: Name of capability in target node (optional)
:vartype target_capability_name: basestring
:ivar target_node_template_constraints: Constraints for filtering relationship targets
- :vartype target_node_template_constraints: [:class:`FunctionType`]
+ :vartype target_node_template_constraints: [:class:`NodeTemplateConstraint`]
:ivar relationship_template: Template for relationships (optional)
:vartype relationship_template: :class:`RelationshipTemplate`
:ivar node_template: Containing node template
@@ -1183,9 +1199,7 @@
@declared_attr
def relationship_template(cls):
- return relationship.one_to_one(cls,
- 'relationship_template',
- back_populates=relationship.NO_BACK_POP)
+ return relationship.one_to_one(cls, 'relationship_template')
# endregion
@@ -1215,18 +1229,18 @@
# endregion
target_capability_name = Column(Text)
- target_node_template_constraints = Column(modeling_types.StrictList(FunctionType))
+ target_node_template_constraints = Column(PickleType)
def find_target(self, source_node_template):
context = ConsumptionContext.get_thread_local()
# We might already have a specific node template, so we'll just verify it
if self.target_node_template is not None:
- if not source_node_template.is_target_node_valid(self.target_node_template):
+ if not source_node_template.is_target_node_template_valid(self.target_node_template):
context.validation.report('requirement "{0}" of node template "{1}" is for node '
'template "{2}" but it does not match constraints'.format(
self.name,
- self.target_node_template_name,
+ self.target_node_template.name,
source_node_template.name),
level=validation.Issue.BETWEEN_TYPES)
if (self.target_capability_type is not None) \
@@ -1247,7 +1261,7 @@
if self.target_node_type.get_descendant(target_node_template.type.name) is None:
continue
- if not source_node_template.is_target_node_valid(target_node_template):
+ if not source_node_template.is_target_node_template_valid(target_node_template):
continue
target_node_capability = self.find_target_capability(source_node_template,
@@ -1284,9 +1298,9 @@
if self.relationship_template:
self.relationship_template.validate()
- def coerce_values(self, container, report_issues):
+ def coerce_values(self, report_issues):
if self.relationship_template is not None:
- self.relationship_template.coerce_values(container, report_issues)
+ self.relationship_template.coerce_values(report_issues)
def dump(self):
context = ConsumptionContext.get_thread_local()
@@ -1417,9 +1431,9 @@
utils.validate_dict_values(self.properties)
utils.validate_dict_values(self.interface_templates)
- def coerce_values(self, container, report_issues):
- utils.coerce_dict_values(self, self.properties, report_issues)
- utils.coerce_dict_values(self, self.interface_templates, report_issues)
+ def coerce_values(self, report_issues):
+ utils.coerce_dict_values(self.properties, report_issues)
+ utils.coerce_dict_values(self.interface_templates, report_issues)
def dump(self):
context = ConsumptionContext.get_thread_local()
@@ -1543,8 +1557,8 @@
# Apply requirement constraints
if requirement.target_node_template_constraints:
- for node_type_constraint in requirement.target_node_template_constraints:
- if not node_type_constraint(target_node_template, source_node_template):
+ for node_template_constraint in requirement.target_node_template_constraints:
+ if not node_template_constraint.matches(source_node_template, target_node_template):
return False
return True
@@ -1574,8 +1588,8 @@
def validate(self):
utils.validate_dict_values(self.properties)
- def coerce_values(self, container, report_issues):
- utils.coerce_dict_values(self, self.properties, report_issues)
+ def coerce_values(self, report_issues):
+ utils.coerce_dict_values(self.properties, report_issues)
def dump(self):
context = ConsumptionContext.get_thread_local()
@@ -1728,9 +1742,9 @@
utils.validate_dict_values(self.inputs)
utils.validate_dict_values(self.operation_templates)
- def coerce_values(self, container, report_issues):
- utils.coerce_dict_values(container, self.inputs, report_issues)
- utils.coerce_dict_values(container, self.operation_templates, report_issues)
+ def coerce_values(self, report_issues):
+ utils.coerce_dict_values(self.inputs, report_issues)
+ utils.coerce_dict_values(self.operation_templates, report_issues)
def dump(self):
context = ConsumptionContext.get_thread_local()
@@ -1882,7 +1896,7 @@
plugin = None
implementation = None
else:
- # using the execution plugin
+ # Using the execution plugin
plugin = None
implementation = self.implementation
@@ -1903,8 +1917,8 @@
def validate(self):
utils.validate_dict_values(self.inputs)
- def coerce_values(self, container, report_issues):
- utils.coerce_dict_values(container, self.inputs, report_issues)
+ def coerce_values(self, report_issues):
+ utils.coerce_dict_values(self.inputs, report_issues)
def dump(self):
context = ConsumptionContext.get_thread_local()
@@ -2051,8 +2065,8 @@
def validate(self):
utils.validate_dict_values(self.properties)
- def coerce_values(self, container, report_issues):
- utils.coerce_dict_values(container, self.properties, report_issues)
+ def coerce_values(self, report_issues):
+ utils.coerce_dict_values(self.properties, report_issues)
def dump(self):
context = ConsumptionContext.get_thread_local()
@@ -2128,7 +2142,7 @@
('version', self.version),
('enabled', self.enabled)))
- def coerce_values(self, container, report_issues):
+ def coerce_values(self, report_issues):
pass
def resolve(self, model_storage):
diff --git a/aria/modeling/utils.py b/aria/modeling/utils.py
index 91d7b9c..0404fe4 100644
--- a/aria/modeling/utils.py
+++ b/aria/modeling/utils.py
@@ -19,9 +19,6 @@
from . import exceptions
from ..parser.consumption import ConsumptionContext
-from ..parser.exceptions import InvalidValueError
-from ..parser.presentation import Value
-from ..utils.collections import OrderedDict
from ..utils.console import puts
from ..utils.type import validate_value_type
@@ -39,6 +36,21 @@
return JSONEncoder.default(self, o)
+class NodeTemplateContainerHolder(object):
+ """
+ Wrapper that allows using a :class:`aria.modeling.models.NodeTemplate` model directly as the
+ ``container_holder`` argument for :func:`aria.modeling.functions.evaluate`.
+ """
+
+ def __init__(self, node_template):
+ self.container = node_template
+ self.service = None
+
+ @property
+ def service_template(self):
+ return self.container.service_template
+
+
def create_inputs(inputs, template_inputs):
"""
:param inputs: key-value dict
@@ -50,7 +62,7 @@
from . import models
input_models = []
for input_name, input_val in merged_inputs.iteritems():
- parameter = models.Parameter(
+ parameter = models.Parameter( # pylint: disable=unexpected-keyword-arg
name=input_name,
type_name=template_inputs[input_name].type_name,
description=template_inputs[input_name].description,
@@ -109,39 +121,17 @@
return merged_inputs
-def coerce_value(container, value, report_issues=False):
- if isinstance(value, Value):
- value = value.value
-
- if isinstance(value, list):
- return [coerce_value(container, v, report_issues) for v in value]
- elif isinstance(value, dict):
- return OrderedDict((k, coerce_value(container, v, report_issues))
- for k, v in value.iteritems())
- elif hasattr(value, '_evaluate'):
- context = ConsumptionContext.get_thread_local()
- try:
- value = value._evaluate(context, container)
- value = coerce_value(container, value, report_issues)
- except exceptions.CannotEvaluateFunctionException:
- pass
- except InvalidValueError as e:
- if report_issues:
- context.validation.report(e.issue)
- return value
-
-
-def coerce_dict_values(container, the_dict, report_issues=False):
+def coerce_dict_values(the_dict, report_issues=False):
if not the_dict:
return
- coerce_list_values(container, the_dict.itervalues(), report_issues)
+ coerce_list_values(the_dict.itervalues(), report_issues)
-def coerce_list_values(container, the_list, report_issues=False):
+def coerce_list_values(the_list, report_issues=False):
if not the_list:
return
for value in the_list:
- value.coerce_values(container, report_issues)
+ value.coerce_values(report_issues)
def validate_dict_values(the_dict):
diff --git a/aria/orchestrator/execution_plugin/instantiation.py b/aria/orchestrator/execution_plugin/instantiation.py
index ce114f0..c09434e 100644
--- a/aria/orchestrator/execution_plugin/instantiation.py
+++ b/aria/orchestrator/execution_plugin/instantiation.py
@@ -15,7 +15,7 @@
# TODO: this module will eventually be moved to a new "aria.instantiation" package
-from ...utils.formatting import full_type_name
+from ...utils.type import full_type_name
from ...utils.collections import OrderedDict
from ...parser import validation
from ...parser.consumption import ConsumptionContext
diff --git a/aria/parser/__init__.py b/aria/parser/__init__.py
index 9ab8785..64df88a 100644
--- a/aria/parser/__init__.py
+++ b/aria/parser/__init__.py
@@ -13,7 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-from .specification import dsl_specification, iter_specifications
+from .specification import implements_specification, iter_specifications
MODULES = (
@@ -26,5 +26,5 @@
__all__ = (
'MODULES',
- 'dsl_specification',
+ 'implements_specification',
'iter_specifications')
diff --git a/aria/parser/consumption/__init__.py b/aria/parser/consumption/__init__.py
index 8f6d2b6..76e73be 100644
--- a/aria/parser/consumption/__init__.py
+++ b/aria/parser/consumption/__init__.py
@@ -28,9 +28,11 @@
Types,
ServiceInstance,
FindHosts,
+ ValidateServiceInstance,
ConfigureOperations,
SatisfyRequirements,
- ValidateCapabilities
+ ValidateCapabilities,
+ CoerceServiceInstanceValues
)
from .inputs import Inputs
@@ -45,7 +47,10 @@
'ServiceTemplate',
'Types',
'ServiceInstance',
- 'Inputs',
+ 'FindHosts',
+ 'ValidateServiceInstance',
+ 'ConfigureOperations',
'SatisfyRequirements',
- 'ValidateCapabilities'
+ 'ValidateCapabilities',
+ 'CoerceServiceInstanceValues'
)
diff --git a/aria/parser/consumption/modeling.py b/aria/parser/consumption/modeling.py
index 771fd7f..44027b9 100644
--- a/aria/parser/consumption/modeling.py
+++ b/aria/parser/consumption/modeling.py
@@ -42,7 +42,7 @@
"""
def consume(self):
- self.context.modeling.template.coerce_values(None, True)
+ self.context.modeling.template.coerce_values(True)
class ValidateServiceTemplate(Consumer):
@@ -116,7 +116,7 @@
"""
def consume(self):
- self.context.modeling.instance.coerce_values(None, True)
+ self.context.modeling.instance.coerce_values(True)
class ValidateServiceInstance(Consumer):
diff --git a/aria/parser/presentation/fields.py b/aria/parser/presentation/fields.py
index da3cb05..7f85723 100644
--- a/aria/parser/presentation/fields.py
+++ b/aria/parser/presentation/fields.py
@@ -21,7 +21,8 @@
from ...utils.collections import FrozenDict, FrozenList, deepcopy_with_locators, merge, OrderedDict
from ...utils.caching import cachedmethod
from ...utils.console import puts
-from ...utils.formatting import as_raw, safe_repr, full_type_name
+from ...utils.formatting import as_raw, safe_repr
+from ...utils.type import full_type_name
from ...utils.exceptions import print_exception
from ..exceptions import InvalidValueError
diff --git a/aria/parser/presentation/presentation.py b/aria/parser/presentation/presentation.py
index 0f098b5..644d880 100644
--- a/aria/parser/presentation/presentation.py
+++ b/aria/parser/presentation/presentation.py
@@ -15,7 +15,8 @@
from ...utils.caching import HasCachedMethods
from ...utils.collections import deepcopy_with_locators
-from ...utils.formatting import full_type_name, safe_repr
+from ...utils.formatting import safe_repr
+from ...utils.type import full_type_name
from ...utils.console import puts
from ..validation import Issue
from .null import none_to_null
diff --git a/aria/parser/presentation/utils.py b/aria/parser/presentation/utils.py
index 74adbd1..fbe971b 100644
--- a/aria/parser/presentation/utils.py
+++ b/aria/parser/presentation/utils.py
@@ -15,7 +15,8 @@
from types import FunctionType
-from ...utils.formatting import full_type_name, safe_repr
+from ...utils.formatting import safe_repr
+from ...utils.type import full_type_name
from ..validation import Issue
from .null import NULL
diff --git a/aria/parser/specification.py b/aria/parser/specification.py
index 1df11ce..714bed1 100644
--- a/aria/parser/specification.py
+++ b/aria/parser/specification.py
@@ -17,40 +17,7 @@
from ..extension import parser
from ..utils.collections import OrderedDict
-from ..utils.formatting import full_type_name
-
-_DSL_SPECIFICATIONS = {}
-
-
-def dsl_specification(section, spec):
- """
- Decorator for TOSCA specification.
-
- Used for documentation and standards compliance.
- """
- def decorator(obj):
- specification = _DSL_SPECIFICATIONS.get(spec)
-
- if specification is None:
- specification = {}
- _DSL_SPECIFICATIONS[spec] = specification
-
- if section in specification:
- raise Exception('you cannot specify the same @dsl_specification twice, consider'
- ' adding \'-1\', \'-2\', etc.: %s, %s' % (spec, section))
-
- specification[section] = OrderedDict((
- ('code', full_type_name(obj)),
- ('doc', obj.__doc__)))
-
- try:
- setattr(obj, '_dsl_specifications', {section: section, spec: spec})
- except BaseException:
- pass
-
- return obj
-
- return decorator
+from ..utils.specification import (DSL_SPECIFICATIONS, implements_specification) # pylint: disable=unused-import
def iter_specifications():
@@ -63,12 +30,10 @@
details['code'] = sections[k]['code']
yield k, _fix_details(sections[k], spec)
- for spec, sections in _DSL_SPECIFICATIONS.iteritems():
+ for spec, sections in DSL_SPECIFICATIONS.iteritems():
yield spec, iter_sections(spec, sections)
-# Utils
-
def _section_key(value):
try:
parts = value.split('-', 1)
diff --git a/aria/parser/validation/issue.py b/aria/parser/validation/issue.py
index f001efc..db8065d 100644
--- a/aria/parser/validation/issue.py
+++ b/aria/parser/validation/issue.py
@@ -16,7 +16,7 @@
from __future__ import absolute_import # so we can import standard 'collections'
from ...utils.collections import OrderedDict
-from ...utils.formatting import full_type_name
+from ...utils.type import full_type_name
class Issue(object):
diff --git a/aria/utils/caching.py b/aria/utils/caching.py
index 613ade6..c9e475a 100644
--- a/aria/utils/caching.py
+++ b/aria/utils/caching.py
@@ -65,22 +65,24 @@
return self.func(*args, **kwargs)
instance = args[0]
- cache = instance.get_method_cache()
+ if not hasattr(instance, '_method_cache'):
+ instance._method_cache = {}
+ method_cache = instance._method_cache
key = (self.func, args[1:], frozenset(kwargs.items()))
try:
with self.lock:
- return_value = cache[key]
+ return_value = method_cache[key]
self.hits += 1
except KeyError:
return_value = self.func(*args, **kwargs)
with self.lock:
- cache[key] = return_value
+ method_cache[key] = return_value
self.misses += 1
# Another thread may override our cache entry here, so we need to read
# it again to make sure all threads use the same return value
- return_value = cache.get(key, return_value)
+ return_value = method_cache.get(key, return_value)
return return_value
@@ -93,9 +95,6 @@
def __init__(self, method_cache=None):
self._method_cache = method_cache or {}
- def get_method_cache(self):
- return self._method_cache
-
@property
def _method_cache_info(self):
"""
diff --git a/aria/utils/formatting.py b/aria/utils/formatting.py
index b5e141d..f96a4ce 100644
--- a/aria/utils/formatting.py
+++ b/aria/utils/formatting.py
@@ -71,18 +71,6 @@
return super(YamlAsRawDumper, self).represent_data(data)
-def full_type_name(value):
- """
- The full class name of a type or object.
- """
-
- if not isinstance(value, type):
- value = value.__class__
- module = str(value.__module__)
- name = str(value.__name__)
- return name if module == '__builtin__' else '%s.%s' % (module, name)
-
-
def decode_list(data):
decoded_list = []
for item in data:
@@ -163,8 +151,8 @@
value = value()
elif isinstance(value, list):
value = list(value)
- for i, _ in enumerate(value):
- value[i] = as_raw(value[i])
+ for i, v in enumerate(value):
+ value[i] = as_raw(v)
elif isinstance(value, dict):
value = dict(value)
for k, v in value.iteritems():
diff --git a/aria/utils/specification.py b/aria/utils/specification.py
new file mode 100644
index 0000000..e74c103
--- /dev/null
+++ b/aria/utils/specification.py
@@ -0,0 +1,53 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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 .collections import OrderedDict
+
+
+DSL_SPECIFICATIONS = {}
+
+
+def implements_specification(section, spec):
+ """
+ Decorator for specification implementations.
+
+ Used for documentation and standards compliance.
+ """
+
+ from .type import full_type_name
+
+ def decorator(obj):
+ specification = DSL_SPECIFICATIONS.get(spec)
+
+ if specification is None:
+ specification = {}
+ DSL_SPECIFICATIONS[spec] = specification
+
+ if section in specification:
+ raise Exception('you cannot specify the same @implements_specification twice, consider'
+ ' adding \'-1\', \'-2\', etc.: {0}, {1}'.format(spec, section))
+
+ specification[section] = OrderedDict((
+ ('code', full_type_name(obj)),
+ ('doc', obj.__doc__)))
+
+ try:
+ setattr(obj, '_dsl_specifications', {section: section, spec: spec})
+ except BaseException:
+ pass
+
+ return obj
+
+ return decorator
diff --git a/aria/utils/type.py b/aria/utils/type.py
index dad5427..f08159a 100644
--- a/aria/utils/type.py
+++ b/aria/utils/type.py
@@ -13,49 +13,142 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+import datetime
+
+from .specification import implements_specification
+
+
+BASE_TYPES_TO_CANONICAL_NAMES = {
+ # TOSCA aliases:
+ None.__class__: 'null',
+ basestring: 'string',
+ int: 'integer',
+ float: 'float',
+ bool: 'boolean',
+ list: 'list',
+ tuple: 'list',
+ dict: 'map',
+ datetime.datetime: 'timestamp'
+}
+
+NAMES_TO_CANONICAL_TYPES = {
+ # Python:
+ 'none': None.__class__,
+ 'basestring': unicode,
+ 'str': unicode,
+ 'unicode': unicode,
+ 'int': int,
+ 'float': float, # also a TOSCA alias
+ 'bool': bool,
+ 'list': list, # also a TOSCA alias
+ 'tuple': list,
+ 'dict': dict,
+ 'datetime': datetime.datetime,
+
+ # YAML 1.2:
+ 'tag:yaml.org,2002:null': None.__class__,
+ 'tag:yaml.org,2002:str': unicode,
+ 'tag:yaml.org,2002:integer': int,
+ 'tag:yaml.org,2002:float': float,
+ 'tag:yaml.org,2002:bool': bool,
+
+ # TOSCA aliases:
+ 'null': None.__class__,
+ 'string': unicode,
+ 'integer': int,
+ 'boolean': bool,
+
+ # TOSCA custom types:
+ 'map': dict,
+ 'timestamp': datetime.datetime
+}
+
+
+def full_type_name(value):
+ """
+ The full class name of a type or instance.
+ """
+
+ if not isinstance(value, type):
+ value = value.__class__
+ module = str(value.__module__)
+ name = str(value.__name__)
+ return name if module == '__builtin__' else '{0}.{1}'.format(module, name)
+
+
+@implements_specification('3.2.1-1', 'tosca-simple-1.0')
+def canonical_type_name(value):
+ """
+ Returns the canonical TOSCA type name of a primitive value, or None if unknown.
+
+ For a list of TOSCA type names, see the `TOSCA Simple Profile v1.0
+ cos01 specification <http://docs.oasis-open.org/tosca/TOSCA-Simple-Profile-YAML/v1.0/cos01
+ /TOSCA-Simple-Profile-YAML-v1.0-cos01.html#_Toc373867862>`__
+ """
+
+ for the_type, name in BASE_TYPES_TO_CANONICAL_NAMES.iteritems():
+ if isinstance(value, the_type):
+ return name
+ return None
+
+
+@implements_specification('3.2.1-2', 'tosca-simple-1.0')
+def canonical_type(type_name):
+ """
+ Return the canonical type for any Python, YAML, or TOSCA type name or alias, or None if
+ unsupported.
+
+ :param type_name: Type name (case insensitive)
+ """
+
+ return NAMES_TO_CANONICAL_TYPES.get(type_name.lower())
+
def validate_value_type(value, type_name):
"""
- Validate a value is of a specific type.
+ Validate that a value is of a specific type. Supports Python, YAML, and TOSCA type names and
+ aliases.
+
A ValueError will be raised on type mismatch.
- Supports both python and yaml type names.
+
+ :param type_name: Type name (case insensitive)
"""
- #TODO add timestamp type?
- name_to_type = {
- 'list': list,
- 'dict': dict,
- 'tuple': tuple,
- 'str': str,
- 'unicode': str,
- 'string': str,
- 'int': int,
- 'integer': int,
- 'bool': bool,
- 'boolean': bool,
- 'float': float
- }
+ the_type = canonical_type(type_name)
+ if the_type is None:
+ raise RuntimeError('Unsupported type name: {0}'.format(type_name))
- type_ = name_to_type.get(type_name.lower())
- if type_ is None:
- raise RuntimeError('No supported type_name was provided')
+ # The following Python types do not inherit from the canonical type, but are considered valid
+ if (the_type is unicode) and isinstance(value, str):
+ return
+ if (the_type is list) and isinstance(value, tuple):
+ return
- if not isinstance(value, type_):
+ if not isinstance(value, the_type):
raise ValueError('Value {0} is not of type {1}'.format(value, type_name))
-def convert_value_to_type(str_value, type_name):
+def convert_value_to_type(str_value, python_type_name):
+ """
+ Converts a value to a specific Python primitive type.
+
+ A ValueError will be raised for unsupported types or conversion failure.
+
+ :param python_type_name: Python primitive type name (case insensitive)
+ """
+
+ python_type_name = python_type_name.lower()
try:
- if type_name.lower() in ['str', 'unicode']:
+ if python_type_name in ('str', 'unicode'):
return str_value.decode('utf-8')
- elif type_name.lower() == 'int':
+ elif python_type_name == 'int':
return int(str_value)
- elif type_name.lower() == 'bool':
+ elif python_type_name == 'bool':
return bool(str_value)
- elif type_name.lower() == 'float':
+ elif python_type_name == 'float':
return float(str_value)
else:
- raise ValueError('No supported type_name was provided')
+ raise ValueError('Unsupported Python type name: {0}'.format(python_type_name))
except ValueError:
- raise ValueError('Trying to convert {0} to {1} failed'.format(str_value,
- type_name))
+ raise ValueError('Failed to to convert {0} to {1}'.format(str_value,
+ python_type_name))
diff --git a/examples/tosca-simple-1.0/use-cases/block-storage-1/block-storage-1.yaml b/examples/tosca-simple-1.0/use-cases/block-storage-1/block-storage-1.yaml
index ff6dc92..b912fb2 100644
--- a/examples/tosca-simple-1.0/use-cases/block-storage-1/block-storage-1.yaml
+++ b/examples/tosca-simple-1.0/use-cases/block-storage-1/block-storage-1.yaml
@@ -65,4 +65,4 @@
value: { get_attribute: [ my_server, private_address ] }
volume_id:
description: The volume id of the block storage instance.
- value: { get_attribute: [ my_storage, volume_id ] }
+ value: { get_property: [ my_storage, volume_id ] } # ARIA NOTE: wrong in spec
diff --git a/examples/tosca-simple-1.0/use-cases/block-storage-2/block-storage-2.yaml b/examples/tosca-simple-1.0/use-cases/block-storage-2/block-storage-2.yaml
index 09c30a7..ac475cf 100644
--- a/examples/tosca-simple-1.0/use-cases/block-storage-2/block-storage-2.yaml
+++ b/examples/tosca-simple-1.0/use-cases/block-storage-2/block-storage-2.yaml
@@ -72,4 +72,4 @@
volume_id:
description: The volume id of the block storage instance.
- value: { get_attribute: [ my_storage, volume_id ] }
+ value: { get_property: [ my_storage, volume_id ] } # ARIA NOTE: wrong in spec
diff --git a/examples/tosca-simple-1.0/use-cases/block-storage-3/block-storage-3.yaml b/examples/tosca-simple-1.0/use-cases/block-storage-3/block-storage-3.yaml
index 3018fe9..c3f183e 100644
--- a/examples/tosca-simple-1.0/use-cases/block-storage-3/block-storage-3.yaml
+++ b/examples/tosca-simple-1.0/use-cases/block-storage-3/block-storage-3.yaml
@@ -65,4 +65,4 @@
value: { get_attribute: [ my_server, private_address ] }
volume_id:
description: The volume id of the block storage instance.
- value: { get_attribute: [ my_storage, volume_id ] }
+ value: { get_property: [ my_storage, volume_id ] } # ARIA NOTE: wrong in spec
diff --git a/examples/tosca-simple-1.0/use-cases/block-storage-4/block-storage-4.yaml b/examples/tosca-simple-1.0/use-cases/block-storage-4/block-storage-4.yaml
index 0693ddd..e2bdb9f 100644
--- a/examples/tosca-simple-1.0/use-cases/block-storage-4/block-storage-4.yaml
+++ b/examples/tosca-simple-1.0/use-cases/block-storage-4/block-storage-4.yaml
@@ -93,4 +93,4 @@
value: { get_attribute: [ my_web_app_tier_2, private_address ] }
volume_id:
description: The volume id of the block storage instance.
- value: { get_attribute: [ my_storage, volume_id ] }
+ value: { get_property: [ my_storage, volume_id ] } # ARIA NOTE: wrong in spec
diff --git a/examples/tosca-simple-1.0/use-cases/block-storage-5/block-storage-5.yaml b/examples/tosca-simple-1.0/use-cases/block-storage-5/block-storage-5.yaml
index 5f5cf71..a0c2229 100644
--- a/examples/tosca-simple-1.0/use-cases/block-storage-5/block-storage-5.yaml
+++ b/examples/tosca-simple-1.0/use-cases/block-storage-5/block-storage-5.yaml
@@ -100,10 +100,10 @@
outputs:
private_ip_1:
description: The private IP address of the application's first tier.
- value: { get_attribute: [my_web_app_tier_1, private_address] }
+ value: { get_attribute: [ my_web_app_tier_1, private_address ] }
private_ip_2:
description: The private IP address of the application's second tier.
- value: { get_attribute: [my_web_app_tier_2, private_address] }
+ value: { get_attribute: [ my_web_app_tier_2, private_address ] }
volume_id:
description: The volume id of the block storage instance.
- value: { get_attribute: [my_storage, volume_id] }
+ value: { get_property: [ my_storage, volume_id ] } # ARIA NOTE: wrong in spec
diff --git a/examples/tosca-simple-1.0/use-cases/block-storage-6/block-storage-6.yaml b/examples/tosca-simple-1.0/use-cases/block-storage-6/block-storage-6.yaml
index 808245b..534884a 100644
--- a/examples/tosca-simple-1.0/use-cases/block-storage-6/block-storage-6.yaml
+++ b/examples/tosca-simple-1.0/use-cases/block-storage-6/block-storage-6.yaml
@@ -96,7 +96,7 @@
value: { get_attribute: [ my_server2, private_address ] }
volume_id_1:
description: The volume id of the first block storage instance.
- value: { get_attribute: [my_storage, volume_id] }
+ value: { get_property: [ my_storage, volume_id ] } # ARIA NOTE: wrong in spec
volume_id_2:
description: The volume id of the second block storage instance.
- value: { get_attribute: [ my_storage2, volume_id ] }
+ value: { get_property: [ my_storage2, volume_id ] } # ARIA NOTE: wrong in spec
diff --git a/examples/tosca-simple-1.0/use-cases/multi-tier-1/multi-tier-1.yaml b/examples/tosca-simple-1.0/use-cases/multi-tier-1/multi-tier-1.yaml
index 3485e49..50401ec 100644
--- a/examples/tosca-simple-1.0/use-cases/multi-tier-1/multi-tier-1.yaml
+++ b/examples/tosca-simple-1.0/use-cases/multi-tier-1/multi-tier-1.yaml
@@ -59,7 +59,7 @@
implementation: scripts/nodejs/configure.sh
inputs:
github_url: { get_property: [ SELF, github_url ] }
- mongodb_ip: { get_attribute: [mongo_server, private_address] }
+ mongodb_ip: { get_attribute: [ mongo_server, private_address ] }
start: scripts/nodejs/start.sh
nodejs:
@@ -90,7 +90,7 @@
configure:
implementation: scripts/mongodb/config.sh
inputs:
- mongodb_ip: { get_attribute: [mongo_server, ip_address] }
+ mongodb_ip: { get_attribute: [ mongo_server, private_address ] } # ARIA NOTE: wrong in spec
start: scripts/mongodb/start.sh
elasticsearch:
@@ -115,7 +115,7 @@
pre_configure_source:
implementation: python/logstash/configure_elasticsearch.py
inputs:
- elasticsearch_ip: { get_attribute: [elasticsearch_server, ip_address] }
+ elasticsearch_ip: { get_attribute: [ elasticsearch_server, private_address ] } # ARIA NOTE: wrong in spec
interfaces:
Standard: # ARIA NOTE: wrong in spec
create: scripts/lostash/create.sh
@@ -133,8 +133,8 @@
configure:
implementation: scripts/kibana/config.sh
inputs:
- elasticsearch_ip: { get_attribute: [ elasticsearch_server, ip_address ] }
- kibana_ip: { get_attribute: [ kibana_server, ip_address ] }
+ elasticsearch_ip: { get_attribute: [ elasticsearch_server, private_address ] } # ARIA NOTE: wrong in spec
+ kibana_ip: { get_attribute: [ kibana_server, private_address ] } # ARIA NOTE: wrong in spec
start: scripts/kibana/start.sh
app_collectd:
@@ -155,7 +155,7 @@
configure:
implementation: python/collectd/config.py
inputs:
- logstash_ip: { get_attribute: [ logstash_server, ip_address ] }
+ logstash_ip: { get_attribute: [ logstash_server, private_address ] } # ARIA NOTE: wrong in spec
start: scripts/collectd/start.sh
app_rsyslog:
@@ -176,7 +176,7 @@
configure:
implementation: scripts/rsyslog/config.sh
inputs:
- logstash_ip: { get_attribute: [ logstash_server, ip_address ] }
+ logstash_ip: { get_attribute: [ logstash_server, private_address ] } # ARIA NOTE: wrong in spec
start: scripts/rsyslog/start.sh
app_server:
diff --git a/extensions/aria_extension_tosca/simple_v1_0/__init__.py b/extensions/aria_extension_tosca/simple_v1_0/__init__.py
index 29df362..7dcc60a 100644
--- a/extensions/aria_extension_tosca/simple_v1_0/__init__.py
+++ b/extensions/aria_extension_tosca/simple_v1_0/__init__.py
@@ -30,8 +30,6 @@
NodeType, GroupType, PolicyType)
from .data_types import (Timestamp, Version, Range, List, Map, ScalarSize, ScalarTime,
ScalarFrequency)
-from .functions import (Concat, Token, GetInput, GetProperty, GetAttribute, GetOperationOutput,
- GetNodesOfType, GetArtifact)
MODULES = (
'modeling',
@@ -89,12 +87,4 @@
'Map',
'ScalarSize',
'ScalarTime',
- 'ScalarFrequency',
- 'Concat',
- 'Token',
- 'GetInput',
- 'GetProperty',
- 'GetAttribute',
- 'GetOperationOutput',
- 'GetNodesOfType',
- 'GetArtifact')
+ 'ScalarFrequency')
diff --git a/extensions/aria_extension_tosca/simple_v1_0/assignments.py b/extensions/aria_extension_tosca/simple_v1_0/assignments.py
index 9a2179a..d929ce0 100644
--- a/extensions/aria_extension_tosca/simple_v1_0/assignments.py
+++ b/extensions/aria_extension_tosca/simple_v1_0/assignments.py
@@ -15,7 +15,7 @@
from aria.utils.collections import FrozenDict
from aria.utils.caching import cachedmethod
-from aria.parser import dsl_specification
+from aria.parser import implements_specification
from aria.parser.presentation import (AsIsPresentation, has_fields, allow_unknown_fields,
short_form_field, primitive_field, object_field,
object_dict_field, object_dict_unknown_fields,
@@ -32,7 +32,7 @@
from .presentation.types import (convert_shorthand_to_full_type_name,
get_type_by_full_or_shorthand_name)
-@dsl_specification('3.5.9', 'tosca-simple-1.0')
+@implements_specification('3.5.9', 'tosca-simple-1.0')
class PropertyAssignment(AsIsPresentation):
"""
This section defines the grammar for assigning values to named properties within TOSCA Node and
@@ -45,7 +45,7 @@
@short_form_field('implementation')
@has_fields
-@dsl_specification('3.5.13-2', 'tosca-simple-1.0')
+@implements_specification('3.5.13-2', 'tosca-simple-1.0')
class OperationAssignment(ExtensiblePresentation):
"""
An operation definition defines a named function or procedure that can be bound to an
@@ -105,7 +105,7 @@
@allow_unknown_fields
@has_fields
-@dsl_specification('3.5.14-2', 'tosca-simple-1.0')
+@implements_specification('3.5.14-2', 'tosca-simple-1.0')
class InterfaceAssignment(ExtensiblePresentation):
"""
An interface definition defines a named interface that can be associated with a Node or
@@ -200,7 +200,7 @@
@short_form_field('node')
@has_fields
-@dsl_specification('3.7.2', 'tosca-simple-1.0')
+@implements_specification('3.7.2', 'tosca-simple-1.0')
class RequirementAssignment(ExtensiblePresentation):
"""
A Requirement assignment allows template authors to provide either concrete names of TOSCA
@@ -297,7 +297,7 @@
return None, None
-@dsl_specification('3.5.11', 'tosca-simple-1.0')
+@implements_specification('3.5.11', 'tosca-simple-1.0')
class AttributeAssignment(AsIsPresentation):
"""
This section defines the grammar for assigning values to named attributes within TOSCA Node and
@@ -309,7 +309,7 @@
"""
@has_fields
-@dsl_specification('3.7.1', 'tosca-simple-1.0')
+@implements_specification('3.7.1', 'tosca-simple-1.0')
class CapabilityAssignment(ExtensiblePresentation):
"""
A capability assignment allows node template authors to assign values to properties and
@@ -351,7 +351,7 @@
if capability_definition is not None else None
@has_fields
-@dsl_specification('3.5.6', 'tosca-simple-1.0')
+@implements_specification('3.5.6', 'tosca-simple-1.0')
class ArtifactAssignment(ExtensiblePresentation):
"""
An artifact definition defines a named, typed file that can be associated with Node Type or Node
diff --git a/extensions/aria_extension_tosca/simple_v1_0/data_types.py b/extensions/aria_extension_tosca/simple_v1_0/data_types.py
index a06834c..c385f78 100644
--- a/extensions/aria_extension_tosca/simple_v1_0/data_types.py
+++ b/extensions/aria_extension_tosca/simple_v1_0/data_types.py
@@ -20,7 +20,7 @@
except ImportError:
from total_ordering import total_ordering
-from aria.parser import dsl_specification
+from aria.parser import implements_specification
from aria.utils.collections import StrictDict, OrderedDict
from aria.utils.formatting import safe_repr
@@ -51,7 +51,7 @@
UTC = Timezone()
@total_ordering
-@dsl_specification('timestamp', 'yaml-1.1')
+@implements_specification('timestamp', 'yaml-1.1')
class Timestamp(object):
'''
TOSCA timestamps follow the YAML specification, which in turn is a variant of ISO8601.
@@ -146,7 +146,7 @@
return '{0:g}'.format(the_datetime.microsecond / 1000000.0).lstrip('0')
@total_ordering
-@dsl_specification('3.2.2', 'tosca-simple-1.0')
+@implements_specification('3.2.2', 'tosca-simple-1.0')
class Version(object):
"""
TOSCA supports the concept of "reuse" of type definitions, as well as template definitions which
@@ -229,7 +229,7 @@
return True
return False
-@dsl_specification('3.2.3', 'tosca-simple-1.0')
+@implements_specification('3.2.3', 'tosca-simple-1.0')
class Range(object):
"""
The range type can be used to define numeric ranges with a lower and upper boundary. For
@@ -276,7 +276,7 @@
def as_raw(self):
return list(self.value)
-@dsl_specification('3.2.4', 'tosca-simple-1.0')
+@implements_specification('3.2.4', 'tosca-simple-1.0')
class List(list):
"""
The list type allows for specifying multiple values for a parameter of property. For example, if
@@ -309,7 +309,7 @@
def as_raw(self):
return list(self)
-@dsl_specification('3.2.5', 'tosca-simple-1.0')
+@implements_specification('3.2.5', 'tosca-simple-1.0')
class Map(StrictDict):
"""
The map type allows for specifying multiple values for a parameter of property as a map. In
@@ -349,7 +349,7 @@
return OrderedDict(self)
@total_ordering
-@dsl_specification('3.2.6', 'tosca-simple-1.0')
+@implements_specification('3.2.6', 'tosca-simple-1.0')
class Scalar(object):
"""
The scalar-unit type can be used to define scalar values along with a unit from the list of
@@ -416,7 +416,7 @@
value = self.TYPE(scalar) # pylint: disable=no-member
return self.value < value
-@dsl_specification('3.2.6.4', 'tosca-simple-1.0')
+@implements_specification('3.2.6.4', 'tosca-simple-1.0')
class ScalarSize(Scalar):
"""
Integer scalar for counting bytes.
@@ -444,7 +444,7 @@
TYPE = int
UNIT = 'bytes'
-@dsl_specification('3.2.6.5', 'tosca-simple-1.0')
+@implements_specification('3.2.6.5', 'tosca-simple-1.0')
class ScalarTime(Scalar):
"""
Floating point scalar for counting seconds.
@@ -469,7 +469,7 @@
TYPE = float
UNIT = 'seconds'
-@dsl_specification('3.2.6.6', 'tosca-simple-1.0')
+@implements_specification('3.2.6.6', 'tosca-simple-1.0')
class ScalarFrequency(Scalar):
"""
Floating point scalar for counting cycles per second (Hz).
diff --git a/extensions/aria_extension_tosca/simple_v1_0/definitions.py b/extensions/aria_extension_tosca/simple_v1_0/definitions.py
index b60a797..8564249 100644
--- a/extensions/aria_extension_tosca/simple_v1_0/definitions.py
+++ b/extensions/aria_extension_tosca/simple_v1_0/definitions.py
@@ -15,7 +15,7 @@
from aria.utils.collections import FrozenDict
from aria.utils.caching import cachedmethod
-from aria.parser import dsl_specification
+from aria.parser import implements_specification
from aria.parser.presentation import (has_fields, short_form_field, allow_unknown_fields,
primitive_field, primitive_list_field, object_field,
object_list_field, object_dict_field,
@@ -35,7 +35,7 @@
get_and_override_operation_definitions_from_type)
@has_fields
-@dsl_specification('3.5.8', 'tosca-simple-1.0')
+@implements_specification('3.5.8', 'tosca-simple-1.0')
class PropertyDefinition(ExtensiblePresentation):
"""
A property definition defines a named, typed value and related data that can be associated with
@@ -86,7 +86,7 @@
@primitive_field(str, default='supported', allowed=('supported', 'unsupported', 'experimental',
'deprecated'))
- @dsl_specification(section='3.5.8.3', spec='tosca-simple-1.0')
+ @implements_specification(section='3.5.8.3', spec='tosca-simple-1.0')
def status(self):
"""
The optional status of the property relative to the specification or implementation.
@@ -121,7 +121,7 @@
return get_property_constraints(context, self)
@has_fields
-@dsl_specification('3.5.10', 'tosca-simple-1.0')
+@implements_specification('3.5.10', 'tosca-simple-1.0')
class AttributeDefinition(ExtensiblePresentation):
"""
An attribute definition defines a named, typed value that can be associated with an entity
@@ -190,7 +190,7 @@
return get_data_type(context, self, 'type')
@has_fields
-@dsl_specification('3.5.12', 'tosca-simple-1.0')
+@implements_specification('3.5.12', 'tosca-simple-1.0')
class ParameterDefinition(PropertyDefinition):
"""
A parameter definition is essentially a TOSCA property definition; however, it also allows a
@@ -225,7 +225,7 @@
@short_form_field('implementation')
@has_fields
-@dsl_specification('3.5.13-1', 'tosca-simple-1.0')
+@implements_specification('3.5.13-1', 'tosca-simple-1.0')
class OperationDefinition(ExtensiblePresentation):
"""
An operation definition defines a named function or procedure that can be bound to an
@@ -266,7 +266,7 @@
@allow_unknown_fields
@has_fields
-@dsl_specification('3.5.14-1', 'tosca-simple-1.0')
+@implements_specification('3.5.14-1', 'tosca-simple-1.0')
class InterfaceDefinition(ExtensiblePresentation):
"""
An interface definition defines a named interface that can be associated with a Node or
@@ -352,7 +352,7 @@
@short_form_field('capability')
@has_fields
-@dsl_specification('3.6.2', 'tosca-simple-1.0')
+@implements_specification('3.6.2', 'tosca-simple-1.0')
class RequirementDefinition(ExtensiblePresentation):
"""
The Requirement definition describes a named requirement (dependencies) of a TOSCA Node Type or
@@ -418,7 +418,7 @@
@short_form_field('type')
@has_fields
-@dsl_specification('3.6.1', 'tosca-simple-1.0')
+@implements_specification('3.6.1', 'tosca-simple-1.0')
class CapabilityDefinition(ExtensiblePresentation):
"""
A capability definition defines a named, typed set of data that can be associated with Node Type
diff --git a/extensions/aria_extension_tosca/simple_v1_0/filters.py b/extensions/aria_extension_tosca/simple_v1_0/filters.py
index 617ce7a..838b505 100644
--- a/extensions/aria_extension_tosca/simple_v1_0/filters.py
+++ b/extensions/aria_extension_tosca/simple_v1_0/filters.py
@@ -14,7 +14,7 @@
# limitations under the License.
from aria.utils.caching import cachedmethod
-from aria.parser import dsl_specification
+from aria.parser import implements_specification
from aria.parser.presentation import (has_fields, object_sequenced_list_field, field_validator)
from .misc import ConstraintClause
@@ -45,7 +45,7 @@
return None
@has_fields
-@dsl_specification('3.5.4', 'tosca-simple-1.0')
+@implements_specification('3.5.4', 'tosca-simple-1.0')
class NodeFilter(ExtensiblePresentation):
"""
A node filter definition defines criteria for selection of a TOSCA Node Template based upon the
@@ -58,7 +58,7 @@
@field_validator(node_filter_properties_validator)
@object_sequenced_list_field(ConstraintClause)
- @dsl_specification('3.5.3', 'tosca-simple-1.0')
+ @implements_specification('3.5.3', 'tosca-simple-1.0')
def properties(self):
"""
An optional sequenced list of property filters that would be used to select (filter)
diff --git a/extensions/aria_extension_tosca/simple_v1_0/misc.py b/extensions/aria_extension_tosca/simple_v1_0/misc.py
index 42fc1ad..74eba18 100644
--- a/extensions/aria_extension_tosca/simple_v1_0/misc.py
+++ b/extensions/aria_extension_tosca/simple_v1_0/misc.py
@@ -16,7 +16,7 @@
from aria.utils.caching import cachedmethod
from aria.utils.console import puts
from aria.utils.formatting import as_raw
-from aria.parser import dsl_specification
+from aria.parser import implements_specification
from aria.parser.presentation import (AsIsPresentation, has_fields, allow_unknown_fields,
short_form_field, primitive_field, primitive_list_field,
primitive_dict_unknown_fields, object_field,
@@ -36,7 +36,7 @@
from .presentation.types import (convert_shorthand_to_full_type_name,
get_type_by_full_or_shorthand_name)
-@dsl_specification('3.5.1', 'tosca-simple-1.0')
+@implements_specification('3.5.1', 'tosca-simple-1.0')
class Description(AsIsPresentation):
"""
See the `TOSCA Simple Profile v1.0 cos01 specification <http://docs.oasis-open.org/tosca
@@ -53,10 +53,10 @@
@allow_unknown_fields
@has_fields
-@dsl_specification('3.9.3.2', 'tosca-simple-1.0')
+@implements_specification('3.9.3.2', 'tosca-simple-1.0')
class MetaData(ExtensiblePresentation):
@primitive_field(str)
- @dsl_specification('3.9.3.3', 'tosca-simple-1.0')
+ @implements_specification('3.9.3.3', 'tosca-simple-1.0')
def template_name(self):
"""
This optional metadata keyname can be used to declare the name of service template as a
@@ -64,7 +64,7 @@
"""
@primitive_field(str)
- @dsl_specification('3.9.3.4', 'tosca-simple-1.0')
+ @implements_specification('3.9.3.4', 'tosca-simple-1.0')
def template_author(self):
"""
This optional metadata keyname can be used to declare the author(s) of the service template
@@ -72,7 +72,7 @@
"""
@primitive_field(str)
- @dsl_specification('3.9.3.5', 'tosca-simple-1.0')
+ @implements_specification('3.9.3.5', 'tosca-simple-1.0')
def template_version(self):
"""
This optional metadata keyname can be used to declare a domain specific version of the
@@ -87,7 +87,7 @@
@short_form_field('url')
@has_fields
-@dsl_specification('3.5.5', 'tosca-simple-1.0')
+@implements_specification('3.5.5', 'tosca-simple-1.0')
class Repository(ExtensiblePresentation):
"""
A repository definition defines a named external repository which contains deployment and
@@ -128,7 +128,7 @@
@short_form_field('file')
@has_fields
-@dsl_specification('3.5.7', 'tosca-simple-1.0')
+@implements_specification('3.5.7', 'tosca-simple-1.0')
class Import(ExtensiblePresentation):
"""
An import definition is used within a TOSCA Service Template to locate and uniquely name another
@@ -177,7 +177,7 @@
"""
@has_fields
-@dsl_specification('3.5.2', 'tosca-simple-1.0')
+@implements_specification('3.5.2-1', 'tosca-simple-1.0')
class ConstraintClause(ExtensiblePresentation):
"""
A constraint clause defines an operation along with one or more compatible values that can be
@@ -376,7 +376,7 @@
validate_subtitution_mappings_capability(context, self)
@has_fields
-@dsl_specification('2.10', 'tosca-simple-1.0')
+@implements_specification('2.10', 'tosca-simple-1.0')
class SubstitutionMappings(ExtensiblePresentation):
@field_validator(type_validator('node type', convert_shorthand_to_full_type_name, 'node_types'))
@primitive_field(str, required=True)
diff --git a/extensions/aria_extension_tosca/simple_v1_0/modeling/__init__.py b/extensions/aria_extension_tosca/simple_v1_0/modeling/__init__.py
index 3bda7e2..99389e4 100644
--- a/extensions/aria_extension_tosca/simple_v1_0/modeling/__init__.py
+++ b/extensions/aria_extension_tosca/simple_v1_0/modeling/__init__.py
@@ -34,6 +34,8 @@
SubstitutionTemplateMapping, InterfaceTemplate, OperationTemplate,
ArtifactTemplate, Metadata, Parameter, PluginSpecification)
+from .constraints import (Equal, GreaterThan, GreaterOrEqual, LessThan, LessOrEqual, InRange,
+ ValidValues, Length, MinLength, MaxLength, Pattern)
from ..data_types import coerce_value
@@ -166,6 +168,8 @@
create_parameter_models_from_values(model.properties,
node_template._get_property_values(context))
+ create_parameter_models_from_values(model.attributes,
+ node_template._get_attribute_default_values(context))
create_interface_template_models(context, service_template, model.interface_templates,
node_template._get_interfaces(context))
@@ -181,10 +185,10 @@
model.capability_templates[capability_name] = \
create_capability_template_model(context, service_template, capability)
- if model.target_node_template_constraints:
+ if node_template.node_filter:
model.target_node_template_constraints = []
- create_node_filter_constraint_lambdas(context, node_template.node_filter,
- model.target_node_template_constraints)
+ create_node_filter_constraints(context, node_template.node_filter,
+ model.target_node_template_constraints)
return model
@@ -273,10 +277,10 @@
model = RequirementTemplate(**model)
- if model.target_node_template_constraints:
+ if requirement.node_filter:
model.target_node_template_constraints = []
- create_node_filter_constraint_lambdas(context, requirement.node_filter,
- model.target_node_template_constraints)
+ create_node_filter_constraints(context, requirement.node_filter,
+ model.target_node_template_constraints)
relationship = requirement.relationship
if relationship is not None:
@@ -348,7 +352,7 @@
inputs = interface.inputs
if inputs:
for input_name, the_input in inputs.iteritems():
- model.inputs[input_name] = Parameter(name=input_name,
+ model.inputs[input_name] = Parameter(name=input_name, # pylint: disable=unexpected-keyword-arg
type_name=the_input.value.type,
value=the_input.value.value,
description=the_input.value.description)
@@ -395,7 +399,7 @@
inputs = operation.inputs
if inputs:
for input_name, the_input in inputs.iteritems():
- model.inputs[input_name] = Parameter(name=input_name,
+ model.inputs[input_name] = Parameter(name=input_name, # pylint: disable=unexpected-keyword-arg
type_name=the_input.value.type,
value=the_input.value.value,
description=the_input.value.description)
@@ -491,7 +495,7 @@
elif prop_name == 'dependencies':
model.dependencies = prop.value
else:
- model.inputs[prop_name] = Parameter(name=prop_name,
+ model.inputs[prop_name] = Parameter(name=prop_name, # pylint: disable=unexpected-keyword-arg
type_name=prop.type,
value=prop.value,
description=prop.description)
@@ -536,7 +540,7 @@
def create_parameter_models_from_values(properties, source_properties):
if source_properties:
for property_name, prop in source_properties.iteritems():
- properties[property_name] = Parameter(name=property_name,
+ properties[property_name] = Parameter(name=property_name, # pylint: disable=unexpected-keyword-arg
type_name=prop.type,
value=prop.value,
description=prop.description)
@@ -545,7 +549,7 @@
def create_parameter_models_from_assignments(properties, source_properties):
if source_properties:
for property_name, prop in source_properties.iteritems():
- properties[property_name] = Parameter(name=property_name,
+ properties[property_name] = Parameter(name=property_name, # pylint: disable=unexpected-keyword-arg
type_name=prop.value.type,
value=prop.value.value,
description=prop.value.description)
@@ -559,17 +563,13 @@
interfaces[interface_name] = interface
-def create_node_filter_constraint_lambdas(context, node_filter, target_node_template_constraints):
- if node_filter is None:
- return
-
+def create_node_filter_constraints(context, node_filter, target_node_template_constraints):
properties = node_filter.properties
if properties is not None:
for property_name, constraint_clause in properties:
- func = create_constraint_clause_lambda(context, node_filter, constraint_clause,
- property_name, None)
- if func is not None:
- target_node_template_constraints.append(func)
+ constraint = create_constraint(context, node_filter, constraint_clause, property_name,
+ None)
+ target_node_template_constraints.append(constraint)
capabilities = node_filter.capabilities
if capabilities is not None:
@@ -577,129 +577,64 @@
properties = capability.properties
if properties is not None:
for property_name, constraint_clause in properties:
- func = create_constraint_clause_lambda(context, node_filter, constraint_clause,
- property_name, capability_name)
- if func is not None:
- target_node_template_constraints.append(func)
+ constraint = create_constraint(context, node_filter, constraint_clause,
+ property_name, capability_name)
+ target_node_template_constraints.append(constraint)
-def create_constraint_clause_lambda(context, node_filter, constraint_clause, property_name, # pylint: disable=too-many-return-statements
- capability_name):
+def create_constraint(context, node_filter, constraint_clause, property_name, capability_name): # pylint: disable=too-many-return-statements
constraint_key = constraint_clause._raw.keys()[0]
+
the_type = constraint_clause._get_type(context)
- def coerce_constraint(constraint, container):
- constraint = coerce_value(context, node_filter, the_type, None, None, constraint,
- constraint_key) if the_type is not None else constraint
- if hasattr(constraint, '_evaluate'):
- constraint = constraint._evaluate(context, container)
- return constraint
+ def coerce_constraint(constraint):
+ if the_type is not None:
+ return coerce_value(context, node_filter, the_type, None, None, constraint,
+ constraint_key)
+ else:
+ return constraint
- def get_value(node_type):
- if capability_name is not None:
- capability = node_type.capability_templates.get(capability_name)
- prop = capability.properties.get(property_name) if capability is not None else None
- return prop.value if prop is not None else None
- value = node_type.properties.get(property_name)
- return value.value if value is not None else None
+ def coerce_constraints(constraints):
+ if the_type is not None:
+ return tuple(coerce_constraint(constraint) for constraint in constraints)
+ else:
+ return constraints
if constraint_key == 'equal':
- def equal(node_type, container):
- constraint = coerce_constraint(constraint_clause.equal, container)
- value = get_value(node_type)
- return value == constraint
-
- return equal
-
+ return Equal(property_name, capability_name,
+ coerce_constraint(constraint_clause.equal))
elif constraint_key == 'greater_than':
- def greater_than(node_type, container):
- constraint = coerce_constraint(constraint_clause.greater_than, container)
- value = get_value(node_type)
- return value > constraint
-
- return greater_than
-
+ return GreaterThan(property_name, capability_name,
+ coerce_constraint(constraint_clause.greater_than))
elif constraint_key == 'greater_or_equal':
- def greater_or_equal(node_type, container):
- constraint = coerce_constraint(constraint_clause.greater_or_equal, container)
- value = get_value(node_type)
- return value >= constraint
-
- return greater_or_equal
-
+ return GreaterOrEqual(property_name, capability_name,
+ coerce_constraint(constraint_clause.greater_or_equal))
elif constraint_key == 'less_than':
- def less_than(node_type, container):
- constraint = coerce_constraint(constraint_clause.less_than, container)
- value = get_value(node_type)
- return value < constraint
-
- return less_than
-
+ return LessThan(property_name, capability_name,
+ coerce_constraint(constraint_clause.less_than))
elif constraint_key == 'less_or_equal':
- def less_or_equal(node_type, container):
- constraint = coerce_constraint(constraint_clause.less_or_equal, container)
- value = get_value(node_type)
- return value <= constraint
-
- return less_or_equal
-
+ return LessOrEqual(property_name, capability_name,
+ coerce_constraint(constraint_clause.less_or_equal))
elif constraint_key == 'in_range':
- def in_range(node_type, container):
- lower, upper = constraint_clause.in_range
- lower, upper = coerce_constraint(lower, container), coerce_constraint(upper, container)
- value = get_value(node_type)
- if value < lower:
- return False
- if (upper != 'UNBOUNDED') and (value > upper):
- return False
- return True
-
- return in_range
-
+ return InRange(property_name, capability_name,
+ coerce_constraints(constraint_clause.in_range))
elif constraint_key == 'valid_values':
- def valid_values(node_type, container):
- constraint = tuple(coerce_constraint(v, container)
- for v in constraint_clause.valid_values)
- value = get_value(node_type)
- return value in constraint
-
- return valid_values
-
+ return ValidValues(property_name, capability_name,
+ coerce_constraints(constraint_clause.valid_values))
elif constraint_key == 'length':
- def length(node_type, container): # pylint: disable=unused-argument
- constraint = constraint_clause.length
- value = get_value(node_type)
- return len(value) == constraint
-
- return length
-
+ return Length(property_name, capability_name,
+ coerce_constraint(constraint_clause.length))
elif constraint_key == 'min_length':
- def min_length(node_type, container): # pylint: disable=unused-argument
- constraint = constraint_clause.min_length
- value = get_value(node_type)
- return len(value) >= constraint
-
- return min_length
-
+ return MinLength(property_name, capability_name,
+ coerce_constraint(constraint_clause.min_length))
elif constraint_key == 'max_length':
- def max_length(node_type, container): # pylint: disable=unused-argument
- constraint = constraint_clause.max_length
- value = get_value(node_type)
- return len(value) >= constraint
-
- return max_length
-
+ return MaxLength(property_name, capability_name,
+ coerce_constraint(constraint_clause.max_length))
elif constraint_key == 'pattern':
- def pattern(node_type, container): # pylint: disable=unused-argument
- constraint = constraint_clause.pattern
- # Note: the TOSCA 1.0 spec does not specify the regular expression grammar, so we will
- # just use Python's
- value = node_type.properties.get(property_name)
- return re.match(constraint, str(value)) is not None
-
- return pattern
-
- return None
+ return Pattern(property_name, capability_name,
+ coerce_constraint(constraint_clause.pattern))
+ else:
+ raise ValueError('malformed node_filter: {0}'.format(constraint_key))
def split_prefix(string):
diff --git a/extensions/aria_extension_tosca/simple_v1_0/modeling/constraints.py b/extensions/aria_extension_tosca/simple_v1_0/modeling/constraints.py
new file mode 100644
index 0000000..7c99eab
--- /dev/null
+++ b/extensions/aria_extension_tosca/simple_v1_0/modeling/constraints.py
@@ -0,0 +1,144 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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 re
+
+from aria.modeling.contraints import NodeTemplateConstraint
+from aria.modeling.utils import NodeTemplateContainerHolder
+from aria.modeling.functions import evaluate
+from aria.parser import implements_specification
+
+
+@implements_specification('3.5.2-2', 'tosca-simple-1.0')
+class EvaluatingNodeTemplateConstraint(NodeTemplateConstraint):
+ """
+ A version of :class:`NodeTemplateConstraint` with boilerplate initialization for TOSCA
+ constraints.
+ """
+
+ def __init__(self, property_name, capability_name, constraint, as_list=False):
+ self.property_name = property_name
+ self.capability_name = capability_name
+ self.constraint = constraint
+ self.as_list = as_list
+
+ def matches(self, source_node_template, target_node_template):
+ # TOSCA node template constraints can refer to either capability properties or node
+ # template properties
+ if self.capability_name is not None:
+ # Capability property
+ capability = target_node_template.capability_templates.get(self.capability_name)
+ value = capability.properties.get(self.property_name) \
+ if capability is not None else None # Parameter
+ else:
+ # Node template property
+ value = target_node_template.properties.get(self.property_name) # Parameter
+
+ value = value.value if value is not None else None
+
+ container_holder = NodeTemplateContainerHolder(source_node_template)
+
+ if self.as_list:
+ constraints = []
+ for constraint in self.constraint:
+ evaluation = evaluate(constraint, container_holder)
+ if evaluation is not None:
+ constraints.append(evaluation.value)
+ else:
+ constraints.append(constraint)
+ constraint = constraints
+ else:
+ evaluation = evaluate(self.constraint, container_holder)
+ if evaluation is not None:
+ constraint = evaluation.value
+ else:
+ constraint = self.constraint
+
+ return self.matches_evaluated(value, constraint)
+
+ def matches_evaluated(self, value, constraint):
+ raise NotImplementedError
+
+
+class Equal(EvaluatingNodeTemplateConstraint):
+ def matches_evaluated(self, value, constraint):
+ return value == constraint
+
+
+class GreaterThan(EvaluatingNodeTemplateConstraint):
+ def matches_evaluated(self, value, constraint):
+ return value > constraint
+
+
+class GreaterOrEqual(EvaluatingNodeTemplateConstraint):
+ def matches_evaluated(self, value, constraint):
+ return value >= constraint
+
+
+class LessThan(EvaluatingNodeTemplateConstraint):
+ def matches_evaluated(self, value, constraint):
+ return value < constraint
+
+
+class LessOrEqual(EvaluatingNodeTemplateConstraint):
+ def matches_evaluated(self, value, constraint):
+ return value <= constraint
+
+
+class InRange(EvaluatingNodeTemplateConstraint):
+ def __init__(self, property_name, capability_name, constraint):
+ super(InRange, self).__init__(property_name, capability_name, constraint, as_list=True)
+
+ def matches_evaluated(self, value, constraints):
+ lower, upper = constraints
+ if value < lower:
+ return False
+ if (upper != 'UNBOUNDED') and (value > upper):
+ return False
+ return True
+
+
+class ValidValues(EvaluatingNodeTemplateConstraint):
+ def __init__(self, property_name, capability_name, constraint):
+ super(ValidValues, self).__init__(property_name, capability_name, constraint, as_list=True)
+
+ def matches_evaluated(self, value, constraints):
+ return value in constraints
+
+
+class Length(EvaluatingNodeTemplateConstraint):
+ def matches_evaluated(self, value, constraint):
+ return len(value) == constraint
+
+
+class MinLength(EvaluatingNodeTemplateConstraint):
+ def matches_evaluated(self, value, constraint):
+ return len(value) >= constraint
+
+
+class MaxLength(EvaluatingNodeTemplateConstraint):
+ def matches_evaluated(self, value, constraint):
+ return len(value) <= constraint
+
+
+class Pattern(EvaluatingNodeTemplateConstraint):
+ def matches_evaluated(self, value, constraint):
+ # From TOSCA 1.0 3.5.2.1:
+ #
+ # "Note: Future drafts of this specification will detail the use of regular expressions and
+ # reference an appropriate standardized grammar."
+ #
+ # So we will just use Python's.
+ return re.match(constraint, unicode(value)) is not None
diff --git a/extensions/aria_extension_tosca/simple_v1_0/modeling/data_types.py b/extensions/aria_extension_tosca/simple_v1_0/modeling/data_types.py
index 99dcfea..3952785 100644
--- a/extensions/aria_extension_tosca/simple_v1_0/modeling/data_types.py
+++ b/extensions/aria_extension_tosca/simple_v1_0/modeling/data_types.py
@@ -16,13 +16,14 @@
import re
from aria.utils.collections import OrderedDict
-from aria.utils.formatting import full_type_name, safe_repr
+from aria.utils.formatting import safe_repr
+from aria.utils.type import full_type_name
from aria.utils.imports import import_fullname
-from aria.parser import dsl_specification
+from aria.parser import implements_specification
from aria.parser.presentation import (get_locator, validate_primitive)
from aria.parser.validation import Issue
-from ..functions import get_function
+from .functions import get_function
from ..presentation.types import get_type_by_full_or_shorthand_name
#
@@ -295,8 +296,12 @@
elif constraint_key == 'pattern':
constraint = constraint_clause.pattern
try:
- # Note: the TOSCA 1.0 spec does not specify the regular expression grammar, so we will
- # just use Python's
+ # From TOSCA 1.0 3.5.2.1:
+ #
+ # "Note: Future drafts of this specification will detail the use of regular expressions
+ # and reference an appropriate standardized grammar."
+ #
+ # So we will just use Python's.
if re.match(constraint, str(value)) is None:
report('does not match regular expression', constraint)
return False
@@ -327,20 +332,20 @@
PRIMITIVE_DATA_TYPES = {
# YAML 1.2:
- 'tag:yaml.org,2002:str': str,
+ 'tag:yaml.org,2002:str': unicode,
'tag:yaml.org,2002:integer': int,
'tag:yaml.org,2002:float': float,
'tag:yaml.org,2002:bool': bool,
'tag:yaml.org,2002:null': None.__class__,
# TOSCA aliases:
- 'string': str,
+ 'string': unicode,
'integer': int,
'float': float,
'boolean': bool,
'null': None.__class__}
-@dsl_specification('3.2.1', 'tosca-simple-1.0')
+@implements_specification('3.2.1-3', 'tosca-simple-1.0')
def get_primitive_data_type(type_name):
"""
Many of the types we use in this profile are built-in types from the YAML 1.2 specification
@@ -371,6 +376,8 @@
If the extension is present, we will delegate to that hook.
"""
+ # TODO: should support models as well as presentations
+
is_function, func = get_function(context, presentation, value)
if is_function:
return func
diff --git a/extensions/aria_extension_tosca/simple_v1_0/functions.py b/extensions/aria_extension_tosca/simple_v1_0/modeling/functions.py
similarity index 63%
rename from extensions/aria_extension_tosca/simple_v1_0/functions.py
rename to extensions/aria_extension_tosca/simple_v1_0/modeling/functions.py
index 2f77420..7089ed9 100644
--- a/extensions/aria_extension_tosca/simple_v1_0/functions.py
+++ b/extensions/aria_extension_tosca/simple_v1_0/modeling/functions.py
@@ -14,20 +14,24 @@
# limitations under the License.
from cStringIO import StringIO
+import re
from aria.utils.collections import FrozenList
-from aria.utils.formatting import as_raw, safe_repr
-from aria.parser import dsl_specification
+from aria.utils.formatting import (as_raw, safe_repr)
+from aria.utils.type import full_type_name
+from aria.parser import implements_specification
from aria.parser.exceptions import InvalidValueError
from aria.parser.validation import Issue
from aria.modeling.exceptions import CannotEvaluateFunctionException
-from aria.modeling.functions import Function
+from aria.modeling.models import (Node, NodeTemplate, Relationship, RelationshipTemplate)
+from aria.modeling.functions import (Function, Evaluation)
+
#
# Intrinsic
#
-@dsl_specification('4.3.1', 'tosca-simple-1.0')
+@implements_specification('4.3.1', 'tosca-simple-1.0')
class Concat(Function):
"""
The :code:`concat` function is used to concatenate two or more string values within a TOSCA
@@ -39,8 +43,8 @@
if not isinstance(argument, list):
raise InvalidValueError(
- 'function "concat" argument must be a list of string expressions: %s'
- % safe_repr(argument),
+ 'function "concat" argument must be a list of string expressions: {0}'
+ .format(safe_repr(argument)),
locator=self.locator)
string_expressions = []
@@ -58,15 +62,18 @@
string_expressions.append(string_expression)
return {'concat': string_expressions}
- def _evaluate(self, context, container):
+ def __evaluate__(self, container_holder):
+ final = True
value = StringIO()
for e in self.string_expressions:
- if hasattr(e, '_evaluate'):
- e = e._evaluate(context, container)
- value.write(str(e))
- return value.getvalue()
+ e, final = evaluate(e, final, container_holder)
+ if e is not None:
+ value.write(unicode(e))
+ value = value.getvalue()
+ return Evaluation(value, final)
-@dsl_specification('4.3.2', 'tosca-simple-1.0')
+
+@implements_specification('4.3.2', 'tosca-simple-1.0')
class Token(Function):
"""
The :code:`token` function is used within a TOSCA service template on a string to parse out
@@ -77,8 +84,8 @@
self.locator = presentation._locator
if (not isinstance(argument, list)) or (len(argument) != 3):
- raise InvalidValueError('function "token" argument must be a list of 3 parameters: %s'
- % safe_repr(argument),
+ raise InvalidValueError('function "token" argument must be a list of 3 parameters: {0}'
+ .format(safe_repr(argument)),
locator=self.locator)
self.string_with_tokens = parse_string_expression(context, presentation, 'token', 0,
@@ -94,21 +101,30 @@
string_with_tokens = self.string_with_tokens
if hasattr(string_with_tokens, 'as_raw'):
string_with_tokens = as_raw(string_with_tokens)
- string_of_token_chars = self.string_with_tokens
+ string_of_token_chars = self.string_of_token_chars
if hasattr(string_of_token_chars, 'as_raw'):
string_of_token_chars = as_raw(string_of_token_chars)
return {'token': [string_with_tokens, string_of_token_chars, self.substring_index]}
- def _evaluate(self, context, container):
- string_with_tokens = self.string_with_tokens
- if hasattr(string_with_tokens, '_evaluate'):
- string_with_tokens = string_with_tokens._evaluate(context, container) # pylint: disable=no-member
+ def __evaluate__(self, container_holder):
+ final = True
+ string_with_tokens, final = evaluate(self.string_with_tokens, final, container_holder)
+ string_of_token_chars, final = evaluate(self.string_of_token_chars, final, container_holder)
+
+ if string_of_token_chars:
+ regex = '[' + ''.join(re.escape(c) for c in string_of_token_chars) + ']'
+ split = re.split(regex, string_with_tokens)
+ if self.substring_index < len(split):
+ return Evaluation(split[self.substring_index], final)
+
+ raise CannotEvaluateFunctionException()
+
#
# Property
#
-@dsl_specification('4.4.1', 'tosca-simple-1.0')
+@implements_specification('4.4.1', 'tosca-simple-1.0')
class GetInput(Function):
"""
The :code:`get_input` function is used to retrieve the values of properties declared within the
@@ -127,23 +143,31 @@
'inputs', self.input_property_name)
if the_input is None:
raise InvalidValueError(
- 'function "get_input" argument is not a valid input name: %s'
- % safe_repr(argument),
+ 'function "get_input" argument is not a valid input name: {0}'
+ .format(safe_repr(argument)),
locator=self.locator)
@property
def as_raw(self):
return {'get_input': as_raw(self.input_property_name)}
- def _evaluate(self, context, container): # pylint: disable=unused-argument
- if not context.modeling.instance:
+ def __evaluate__(self, container_holder):
+ service = container_holder.service
+ if service is None:
raise CannotEvaluateFunctionException()
- the_input = context.modeling.instance.inputs.get(
- self.input_property_name,
- context.modeling.template.inputs.get(self.input_property_name))
- return as_raw(the_input.value) if the_input is not None else None
-@dsl_specification('4.4.2', 'tosca-simple-1.0')
+ value = service.inputs.get(self.input_property_name)
+ if value is not None:
+ value = value.value
+ return Evaluation(value, False) # We never return final evaluations!
+
+ raise InvalidValueError(
+ 'function "get_input" argument is not a valid input name: {0}'
+ .format(safe_repr(self.input_property_name)),
+ locator=self.locator)
+
+
+@implements_specification('4.4.2', 'tosca-simple-1.0')
class GetProperty(Function):
"""
The :code:`get_property` function is used to retrieve property values between modelable entities
@@ -156,8 +180,7 @@
if (not isinstance(argument, list)) or (len(argument) < 2):
raise InvalidValueError(
'function "get_property" argument must be a list of at least 2 string expressions: '
- '%s'
- % safe_repr(argument),
+ '{0}'.format(safe_repr(argument)),
locator=self.locator)
self.modelable_entity_name = parse_modelable_entity_name(context, presentation,
@@ -169,8 +192,8 @@
def as_raw(self):
return {'get_property': [self.modelable_entity_name] + self.nested_property_name_or_index}
- def _evaluate(self, context, container):
- modelable_entities = get_modelable_entities(context, container, self.locator,
+ def __evaluate__(self, container_holder):
+ modelable_entities = get_modelable_entities(container_holder, 'get_property', self.locator,
self.modelable_entity_name)
req_or_cap_name = self.nested_property_name_or_index[0]
@@ -197,31 +220,22 @@
properties = modelable_entity.properties
nested_property_name_or_index = self.nested_property_name_or_index
- if properties:
- found = True
- value = properties
- for name in nested_property_name_or_index:
- if (isinstance(value, dict) and (name in value)) \
- or (isinstance(value, list) and name < len(list)):
- value = value[name]
- if hasattr(value, '_evaluate'):
- value = value._evaluate(context, modelable_entity)
- else:
- found = False
- break
- if found:
- return as_raw(value)
+ evaluation = get_modelable_entity_parameter(modelable_entity, properties,
+ nested_property_name_or_index)
+ if evaluation is not None:
+ return evaluation
raise InvalidValueError(
- 'function "get_property" could not find "%s" in modelable entity "%s"' \
- % ('.'.join(self.nested_property_name_or_index), self.modelable_entity_name),
+ 'function "get_property" could not find "{0}" in modelable entity "{1}"'
+ .format('.'.join(self.nested_property_name_or_index), self.modelable_entity_name),
locator=self.locator)
+
#
# Attribute
#
-@dsl_specification('4.5.1', 'tosca-simple-1.0')
+@implements_specification('4.5.1', 'tosca-simple-1.0')
class GetAttribute(Function):
"""
The :code:`get_attribute` function is used to retrieve the values of named attributes declared
@@ -234,27 +248,41 @@
if (not isinstance(argument, list)) or (len(argument) < 2):
raise InvalidValueError(
'function "get_attribute" argument must be a list of at least 2 string expressions:'
- ' %s'
- % safe_repr(argument),
+ ' {0}'.format(safe_repr(argument)),
locator=self.locator)
self.modelable_entity_name = parse_modelable_entity_name(context, presentation,
'get_attribute', 0, argument[0])
# The first of these will be tried as a req-or-cap name:
- self.nested_property_name_or_index = argument[1:]
+ self.nested_attribute_name_or_index = argument[1:]
@property
def as_raw(self):
- return {'get_attribute': [self.modelable_entity_name] + self.nested_property_name_or_index}
+ return {'get_attribute': [self.modelable_entity_name] + self.nested_attribute_name_or_index}
- def _evaluate(self, context, container): # pylint: disable=no-self-use,unused-argument
- raise CannotEvaluateFunctionException()
+ def __evaluate__(self, container_holder):
+ modelable_entities = get_modelable_entities(container_holder, 'get_attribute', self.locator,
+ self.modelable_entity_name)
+ for modelable_entity in modelable_entities:
+ attributes = modelable_entity.attributes
+ nested_attribute_name_or_index = self.nested_attribute_name_or_index
+ evaluation = get_modelable_entity_parameter(modelable_entity, attributes,
+ nested_attribute_name_or_index)
+ if evaluation is not None:
+ evaluation.final = False # We never return final evaluations!
+ return evaluation
+
+ raise InvalidValueError(
+ 'function "get_attribute" could not find "{0}" in modelable entity "{1}"'
+ .format('.'.join(self.nested_attribute_name_or_index), self.modelable_entity_name),
+ locator=self.locator)
+
#
# Operation
#
-@dsl_specification('4.6.1', 'tosca-simple-1.0') # pylint: disable=abstract-method
+@implements_specification('4.6.1', 'tosca-simple-1.0') # pylint: disable=abstract-method
class GetOperationOutput(Function):
"""
The :code:`get_operation_output` function is used to retrieve the values of variables exposed /
@@ -266,8 +294,8 @@
if (not isinstance(argument, list)) or (len(argument) != 4):
raise InvalidValueError(
- 'function "get_operation_output" argument must be a list of 4 parameters: %s'
- % safe_repr(argument),
+ 'function "get_operation_output" argument must be a list of 4 parameters: {0}'
+ .format(safe_repr(argument)),
locator=self.locator)
self.modelable_entity_name = parse_string_expression(context, presentation,
@@ -295,11 +323,12 @@
return {'get_operation_output': [self.modelable_entity_name, interface_name, operation_name,
output_variable_name]}
+
#
# Navigation
#
-@dsl_specification('4.7.1', 'tosca-simple-1.0')
+@implements_specification('4.7.1', 'tosca-simple-1.0')
class GetNodesOfType(Function):
"""
The :code:`get_nodes_of_type` function can be used to retrieve a list of all known instances of
@@ -316,8 +345,8 @@
node_types = context.presentation.get('service_template', 'node_types')
if (node_types is None) or (self.node_type_name not in node_types):
raise InvalidValueError(
- 'function "get_nodes_of_type" argument is not a valid node type name: %s'
- % safe_repr(argument),
+ 'function "get_nodes_of_type" argument is not a valid node type name: {0}'
+ .format(safe_repr(argument)),
locator=self.locator)
@property
@@ -327,14 +356,15 @@
node_type_name = as_raw(node_type_name)
return {'get_nodes_of_type': node_type_name}
- def _evaluate(self, context, container):
+ def __evaluate__(self, container):
pass
+
#
# Artifact
#
-@dsl_specification('4.8.1', 'tosca-simple-1.0') # pylint: disable=abstract-method
+@implements_specification('4.8.1', 'tosca-simple-1.0') # pylint: disable=abstract-method
class GetArtifact(Function):
"""
The :code:`get_artifact` function is used to retrieve artifact location between modelable
@@ -346,8 +376,8 @@
if (not isinstance(argument, list)) or (len(argument) < 2) or (len(argument) > 4):
raise InvalidValueError(
- 'function "get_artifact" argument must be a list of 2 to 4 parameters: %s'
- % safe_repr(argument),
+ 'function "get_artifact" argument must be a list of 2 to 4 parameters: {0}'
+ .format(safe_repr(argument)),
locator=self.locator)
self.modelable_entity_name = parse_string_expression(context, presentation, 'get_artifact',
@@ -370,6 +400,7 @@
location = as_raw(location)
return {'get_artifacts': [self.modelable_entity_name, artifact_name, location, self.remove]}
+
#
# Utils
#
@@ -386,6 +417,7 @@
return True, None
return False, None
+
def parse_string_expression(context, presentation, name, index, explanation, value): # pylint: disable=unused-argument
is_function, func = get_function(context, presentation, value)
if is_function:
@@ -394,6 +426,7 @@
value = str(value)
return value
+
def parse_int(context, presentation, name, index, explanation, value): # pylint: disable=unused-argument
if not isinstance(value, int):
try:
@@ -403,11 +436,13 @@
presentation._locator)
return value
+
def parse_bool(context, presentation, name, index, explanation, value): # pylint: disable=unused-argument
if not isinstance(value, bool):
raise invalid_value(name, index, 'a boolean', explanation, value, presentation._locator)
return value
+
def parse_modelable_entity_name(context, presentation, name, index, value):
value = parse_string_expression(context, presentation, name, index, 'the modelable entity name',
value)
@@ -436,66 +471,104 @@
or {}
if (value not in node_templates) and (value not in relationship_templates):
raise InvalidValueError(
- 'function "%s" parameter %d is not a valid modelable entity name: %s'
- % (name, index + 1, safe_repr(value)),
+ 'function "{0}" parameter {1:d} is not a valid modelable entity name: {2}'
+ .format(name, index + 1, safe_repr(value)),
locator=presentation._locator, level=Issue.BETWEEN_TYPES)
return value
+
def parse_self(presentation):
- from .templates import NodeTemplate, RelationshipTemplate
- from .types import NodeType, RelationshipType
+ from ..types import (NodeType, RelationshipType)
+ from ..templates import (
+ NodeTemplate as NodeTemplatePresentation,
+ RelationshipTemplate as RelationshipTemplatePresentation
+ )
if presentation is None:
return None, None
- elif isinstance(presentation, NodeTemplate) or isinstance(presentation, NodeType):
+ elif isinstance(presentation, NodeTemplatePresentation) or isinstance(presentation, NodeType):
return presentation, 'node_template'
- elif isinstance(presentation, RelationshipTemplate) \
+ elif isinstance(presentation, RelationshipTemplatePresentation) \
or isinstance(presentation, RelationshipType):
return presentation, 'relationship_template'
else:
return parse_self(presentation._container)
-@dsl_specification('4.1', 'tosca-simple-1.0')
-def get_modelable_entities(context, container, locator, modelable_entity_name):
+
+def evaluate(value, final, container_holder):
+ """
+ Calls ``__evaluate__`` and passes on ``final`` state.
+ """
+
+ if hasattr(value, '__evaluate__'):
+ value = value.__evaluate__(container_holder)
+ if not value.final:
+ final = False
+ return value.value, final
+ else:
+ return value, final
+
+
+@implements_specification('4.1', 'tosca-simple-1.0')
+def get_modelable_entities(container_holder, name, locator, modelable_entity_name):
"""
The following keywords MAY be used in some TOSCA function in place of a TOSCA Node or
Relationship Template name.
"""
if modelable_entity_name == 'SELF':
- return get_self(context, container)
+ return get_self(container_holder, name, locator)
elif modelable_entity_name == 'HOST':
- return get_host(context, container)
+ return get_hosts(container_holder, name, locator)
elif modelable_entity_name == 'SOURCE':
- return get_source(context, container)
+ return get_source(container_holder, name, locator)
elif modelable_entity_name == 'TARGET':
- return get_target(context, container)
+ return get_target(container_holder, name, locator)
elif isinstance(modelable_entity_name, basestring):
- node_templates = \
- context.presentation.get('service_template', 'topology_template', 'node_templates') \
- or {}
- if modelable_entity_name in node_templates:
- return [node_templates[modelable_entity_name]]
- relationship_templates = \
- context.presentation.get('service_template', 'topology_template',
- 'relationship_templates') \
- or {}
- if modelable_entity_name in relationship_templates:
- return [relationship_templates[modelable_entity_name]]
+ modelable_entities = []
- raise InvalidValueError('function "get_property" could not find modelable entity "%s"'
- % modelable_entity_name,
+ service = container_holder.service
+ if service is not None:
+ for node in service.nodes.itervalues():
+ if node.node_template.name == modelable_entity_name:
+ modelable_entities.append(node)
+ else:
+ service_template = container_holder.service_template
+ if service_template is not None:
+ for node_template in service_template.node_templates.itervalues():
+ if node_template.name == modelable_entity_name:
+ modelable_entities.append(node_template)
+
+ if not modelable_entities:
+ raise CannotEvaluateFunctionException()
+
+ return modelable_entities
+
+ raise InvalidValueError('function "{0}" could not find modelable entity "{1}"'
+ .format(name, modelable_entity_name),
locator=locator)
-def get_self(context, container): # pylint: disable=unused-argument
+
+def get_self(container_holder, name, locator):
"""
A TOSCA orchestrator will interpret this keyword as the Node or Relationship Template instance
that contains the function at the time the function is evaluated.
"""
+ container = container_holder.container
+ if (not isinstance(container, Node)) and \
+ (not isinstance(container, NodeTemplate)) and \
+ (not isinstance(container, Relationship)) and \
+ (not isinstance(container, RelationshipTemplate)):
+ raise InvalidValueError('function "{0}" refers to "SELF" but it is not contained in '
+ 'a node or a relationship: {1}'.format(name,
+ full_type_name(container)),
+ locator=locator)
+
return [container]
-def get_host(context, container): # pylint: disable=unused-argument
+
+def get_hosts(container_holder, name, locator):
"""
A TOSCA orchestrator will interpret this keyword to refer to the all nodes that "host" the node
using this reference (i.e., as identified by its HostedOn relationship).
@@ -507,30 +580,98 @@
ends.
"""
- return []
+ container = container_holder.container
+ if (not isinstance(container, Node)) and (not isinstance(container, NodeTemplate)):
+ raise InvalidValueError('function "{0}" refers to "HOST" but it is not contained in '
+ 'a node: {1}'.format(name, full_type_name(container)),
+ locator=locator)
-def get_source(context, container): # pylint: disable=unused-argument
+ if not isinstance(container, Node):
+ # NodeTemplate does not have "host"; we'll wait until instantiation
+ raise CannotEvaluateFunctionException()
+
+ host = container.host
+ if host is None:
+ # We might have a host later
+ raise CannotEvaluateFunctionException()
+
+ return [host]
+
+
+def get_source(container_holder, name, locator):
"""
A TOSCA orchestrator will interpret this keyword as the Node Template instance that is at the
source end of the relationship that contains the referencing function.
"""
- return []
+ container = container_holder.container
+ if (not isinstance(container, Relationship)) and \
+ (not isinstance(container, RelationshipTemplate)):
+ raise InvalidValueError('function "{0}" refers to "SOURCE" but it is not contained in '
+ 'a relationship: {1}'.format(name, full_type_name(container)),
+ locator=locator)
-def get_target(context, container): # pylint: disable=unused-argument
+ if not isinstance(container, RelationshipTemplate):
+ # RelationshipTemplate does not have "source_node"; we'll wait until instantiation
+ raise CannotEvaluateFunctionException()
+
+ return [container.source_node]
+
+
+def get_target(container_holder, name, locator):
"""
A TOSCA orchestrator will interpret this keyword as the Node Template instance that is at the
target end of the relationship that contains the referencing function.
"""
+ container = container_holder.container
+ if (not isinstance(container, Relationship)) and \
+ (not isinstance(container, RelationshipTemplate)):
+ raise InvalidValueError('function "{0}" refers to "TARGET" but it is not contained in '
+ 'a relationship: {1}'.format(name, full_type_name(container)),
+ locator=locator)
+
+ if not isinstance(container, RelationshipTemplate):
+ # RelationshipTemplate does not have "target_node"; we'll wait until instantiation
+ raise CannotEvaluateFunctionException()
+
+ return [container.target_node]
+
+
+def get_modelable_entity_parameter(modelable_entity, parameters, nested_parameter_name_or_index):
+ if not parameters:
+ return False, True, None
+
+ found = True
+ final = True
+ value = parameters
+
+ for name_or_index in nested_parameter_name_or_index:
+ if (isinstance(value, dict) and (name_or_index in value)) \
+ or ((isinstance(value, list) and (name_or_index < len(value)))):
+ value = value[name_or_index] # Parameter
+ # We are not using Parameter.value, but rather Parameter._value, because we want to make
+ # sure to get "final" (it is swallowed by Parameter.value)
+ value, final = evaluate(value._value, final, value)
+ else:
+ found = False
+ break
+
+ return Evaluation(value, final) if found else None
+
+
def invalid_modelable_entity_name(name, index, value, locator, contexts):
- return InvalidValueError('function "%s" parameter %d can be "%s" only in %s'
- % (name, index + 1, value, contexts),
+ return InvalidValueError('function "{0}" parameter {1:d} can be "{2}" only in {3}'
+ .format(name, index + 1, value, contexts),
locator=locator, level=Issue.FIELD)
+
def invalid_value(name, index, the_type, explanation, value, locator):
return InvalidValueError(
- 'function "%s" %s is not %s%s: %s'
- % (name, ('parameter %d' % (index + 1)) if index is not None else 'argument',
- the_type, (', %s' % explanation) if explanation is not None else '', safe_repr(value)),
+ 'function "{0}" {1} is not {2}{3}: {4}'
+ .format(name,
+ 'parameter {0:d}'.format(index + 1) if index is not None else 'argument',
+ the_type,
+ ', {0}'.format(explanation) if explanation is not None else '',
+ safe_repr(value)),
locator=locator, level=Issue.FIELD)
diff --git a/extensions/aria_extension_tosca/simple_v1_0/modeling/properties.py b/extensions/aria_extension_tosca/simple_v1_0/modeling/properties.py
index f61cb99..9c3ea42 100644
--- a/extensions/aria_extension_tosca/simple_v1_0/modeling/properties.py
+++ b/extensions/aria_extension_tosca/simple_v1_0/modeling/properties.py
@@ -58,7 +58,8 @@
# NodeTemplate, RelationshipTemplate, GroupTemplate, PolicyTemplate
#
-def get_assigned_and_defined_property_values(context, presentation):
+def get_assigned_and_defined_property_values(context, presentation, field_name='property',
+ field_name_plural='properties'):
"""
Returns the assigned property values while making sure they are defined in our type.
@@ -70,8 +71,9 @@
values = OrderedDict()
the_type = presentation._get_type(context)
- assignments = presentation.properties
- definitions = the_type._get_properties(context) if the_type is not None else None
+ assignments = getattr(presentation, field_name_plural)
+ get_fn_name = '_get_{0}'.format(field_name_plural)
+ definitions = getattr(the_type, get_fn_name)(context) if the_type is not None else None
# Fill in our assignments, but make sure they are defined
if assignments:
@@ -80,14 +82,14 @@
definition = definitions[name]
values[name] = coerce_property_value(context, value, definition, value.value)
else:
- context.validation.report('assignment to undefined property "%s" in "%s"'
- % (name, presentation._fullname),
+ context.validation.report('assignment to undefined {0} "{1}" in "{2}"'
+ .format(field_name, name, presentation._fullname),
locator=value._locator, level=Issue.BETWEEN_TYPES)
# Fill in defaults from the definitions
if definitions:
for name, definition in definitions.iteritems():
- if (values.get(name) is None) and (definition.default is not None):
+ if values.get(name) is None:
values[name] = coerce_property_value(context, presentation, definition,
definition.default)
@@ -181,7 +183,8 @@
def coerce_property_value(context, presentation, definition, value, aspect=None):
the_type = definition._get_type(context) if definition is not None else None
entry_schema = definition.entry_schema if definition is not None else None
- constraints = definition._get_constraints(context) if definition is not None else None
+ constraints = definition._get_constraints(context) \
+ if ((definition is not None) and hasattr(definition, '_get_constraints')) else None
value = coerce_value(context, presentation, the_type, entry_schema, constraints, value, aspect)
if (the_type is not None) and hasattr(the_type, '_name'):
type_name = the_type._name
diff --git a/extensions/aria_extension_tosca/simple_v1_0/presentation/field_validators.py b/extensions/aria_extension_tosca/simple_v1_0/presentation/field_validators.py
index 6ff4384..d7b03ae 100644
--- a/extensions/aria_extension_tosca/simple_v1_0/presentation/field_validators.py
+++ b/extensions/aria_extension_tosca/simple_v1_0/presentation/field_validators.py
@@ -16,7 +16,7 @@
import re
from aria.utils.formatting import safe_repr
-from aria.parser import dsl_specification
+from aria.parser import implements_specification
from aria.parser.presentation import (report_issue_for_unknown_type, derived_from_validator)
from aria.parser.validation import Issue
@@ -28,8 +28,7 @@
# NodeTemplate, RelationshipTemplate
#
-@dsl_specification('3.7.3.3', 'tosca-simple-1.0')
-@dsl_specification('3.7.4.3', 'tosca-simple-1.0')
+@implements_specification('3.7.3.3', 'tosca-simple-1.0')
def copy_validator(template_type_name, templates_dict_name):
"""
Makes sure that the field refers to an existing template defined in the root presenter.
@@ -304,8 +303,12 @@
value = getattr(presentation, field.name)
if value is not None:
try:
- # Note: the TOSCA 1.0 spec does not specify the regular expression grammar, so we will
- # just use Python's
+ # From TOSCA 1.0 3.5.2.1:
+ #
+ # "Note: Future drafts of this specification will detail the use of regular expressions
+ # and reference an appropriate standardized grammar."
+ #
+ # So we will just use Python's.
re.compile(value)
except re.error as e:
context.validation.report(
diff --git a/extensions/aria_extension_tosca/simple_v1_0/presenter.py b/extensions/aria_extension_tosca/simple_v1_0/presenter.py
index c88decd..f64078f 100644
--- a/extensions/aria_extension_tosca/simple_v1_0/presenter.py
+++ b/extensions/aria_extension_tosca/simple_v1_0/presenter.py
@@ -17,9 +17,9 @@
from aria.utils.caching import cachedmethod
from aria.parser.presentation import Presenter
-from .functions import (Concat, Token, GetInput, GetProperty, GetAttribute, GetOperationOutput,
- GetNodesOfType, GetArtifact)
from .modeling import create_service_template_model
+from .modeling.functions import (Concat, Token, GetInput, GetProperty, GetAttribute,
+ GetOperationOutput, GetNodesOfType, GetArtifact)
from .templates import ServiceTemplate
class ToscaSimplePresenter1_0(Presenter): # pylint: disable=invalid-name,abstract-method
diff --git a/extensions/aria_extension_tosca/simple_v1_0/templates.py b/extensions/aria_extension_tosca/simple_v1_0/templates.py
index 6860b72..ce6b5d9 100644
--- a/extensions/aria_extension_tosca/simple_v1_0/templates.py
+++ b/extensions/aria_extension_tosca/simple_v1_0/templates.py
@@ -15,7 +15,7 @@
from aria.utils.collections import FrozenDict, FrozenList
from aria.utils.caching import cachedmethod
-from aria.parser import dsl_specification
+from aria.parser import implements_specification
from aria.parser.presentation import (has_fields, primitive_field, primitive_list_field,
object_field, object_list_field, object_dict_field,
object_sequenced_list_field, field_validator,
@@ -26,7 +26,7 @@
from .definitions import ParameterDefinition
from .filters import NodeFilter
from .misc import (Description, MetaData, Repository, Import, SubstitutionMappings)
-from .modeling.properties import get_assigned_and_defined_property_values, get_parameter_values
+from .modeling.properties import (get_assigned_and_defined_property_values, get_parameter_values)
from .modeling.interfaces import get_template_interfaces
from .modeling.requirements import get_template_requirements
from .modeling.capabilities import get_template_capabilities
@@ -41,7 +41,7 @@
NodeType, GroupType, PolicyType)
@has_fields
-@dsl_specification('3.7.3', 'tosca-simple-1.0')
+@implements_specification('3.7.3', 'tosca-simple-1.0')
class NodeTemplate(ExtensiblePresentation):
"""
A Node Template specifies the occurrence of a manageable software component as part of an
@@ -160,6 +160,11 @@
return FrozenDict(get_assigned_and_defined_property_values(context, self))
@cachedmethod
+ def _get_attribute_default_values(self, context):
+ return FrozenDict(get_assigned_and_defined_property_values(context, self,
+ 'attribute', 'attributes'))
+
+ @cachedmethod
def _get_requirements(self, context):
return FrozenList(get_template_requirements(context, self))
@@ -198,7 +203,7 @@
'copy'))
@has_fields
-@dsl_specification('3.7.4', 'tosca-simple-1.0')
+@implements_specification('3.7.4', 'tosca-simple-1.0')
class RelationshipTemplate(ExtensiblePresentation):
"""
A Relationship Template specifies the occurrence of a manageable relationship between node
@@ -297,7 +302,7 @@
'copy'))
@has_fields
-@dsl_specification('3.7.5', 'tosca-simple-1.0')
+@implements_specification('3.7.5', 'tosca-simple-1.0')
class GroupTemplate(ExtensiblePresentation):
"""
A group definition defines a logical grouping of node templates, typically for management
@@ -370,7 +375,7 @@
self._get_interfaces(context)
@has_fields
-@dsl_specification('3.7.6', 'tosca-simple-1.0')
+@implements_specification('3.7.6', 'tosca-simple-1.0')
class PolicyTemplate(ExtensiblePresentation):
"""
A policy definition defines a policy that can be associated with a TOSCA topology or top-level
@@ -434,7 +439,7 @@
self._get_property_values(context)
@has_fields
-@dsl_specification('3.8', 'tosca-simple-1.0')
+@implements_specification('3.8', 'tosca-simple-1.0')
class TopologyTemplate(ExtensiblePresentation):
"""
This section defines the topology template of a cloud application. The main ingredients of the
@@ -544,7 +549,7 @@
'substitution_mappings'))
@has_fields
-@dsl_specification('3.9', 'tosca-simple-1.0')
+@implements_specification('3.9', 'tosca-simple-1.0')
class ServiceTemplate(ExtensiblePresentation):
"""
See the `TOSCA Simple Profile v1.0 cos01 specification <http://docs.oasis-open.org/tosca
@@ -553,7 +558,7 @@
"""
@primitive_field(str)
- @dsl_specification('3.9.3.1', 'tosca-simple-1.0')
+ @implements_specification('3.9.3.1', 'tosca-simple-1.0')
def tosca_definitions_version(self):
"""
Defines the version of the TOSCA Simple Profile specification the template (grammar)
@@ -580,7 +585,7 @@
"""
@object_field(Description)
- @dsl_specification('3.9.3.6', 'tosca-simple-1.0')
+ @implements_specification('3.9.3.6', 'tosca-simple-1.0')
def description(self):
"""
Declares a description for this Service Template and its contents.
@@ -589,7 +594,7 @@
"""
@primitive_field()
- @dsl_specification('3.9.3.7', 'tosca-simple-1.0')
+ @implements_specification('3.9.3.7', 'tosca-simple-1.0')
def dsl_definitions(self):
"""
Declares optional DSL-specific definitions and conventions. For example, in YAML, this
@@ -602,7 +607,7 @@
"""
@object_dict_field(Repository)
- @dsl_specification('3.9.3.8', 'tosca-simple-1.0')
+ @implements_specification('3.9.3.8', 'tosca-simple-1.0')
def repositories(self):
"""
Declares the list of external repositories which contain artifacts that are referenced in
@@ -613,7 +618,7 @@
"""
@object_list_field(Import)
- @dsl_specification('3.9.3.9', 'tosca-simple-1.0')
+ @implements_specification('3.9.3.9', 'tosca-simple-1.0')
def imports(self):
"""
Declares import statements external TOSCA Definitions documents. For example, these may be
@@ -623,7 +628,7 @@
"""
@object_dict_field(ArtifactType)
- @dsl_specification('3.9.3.10', 'tosca-simple-1.0')
+ @implements_specification('3.9.3.10', 'tosca-simple-1.0')
def artifact_types(self):
"""
This section contains an optional list of artifact type definitions for use in the service
@@ -633,7 +638,7 @@
"""
@object_dict_field(DataType)
- @dsl_specification('3.9.3.11', 'tosca-simple-1.0')
+ @implements_specification('3.9.3.11', 'tosca-simple-1.0')
def data_types(self):
"""
Declares a list of optional TOSCA Data Type definitions.
@@ -642,7 +647,7 @@
"""
@object_dict_field(CapabilityType)
- @dsl_specification('3.9.3.12', 'tosca-simple-1.0')
+ @implements_specification('3.9.3.12', 'tosca-simple-1.0')
def capability_types(self):
"""
This section contains an optional list of capability type definitions for use in the service
@@ -652,7 +657,7 @@
"""
@object_dict_field(InterfaceType)
- @dsl_specification('3.9.3.13', 'tosca-simple-1.0')
+ @implements_specification('3.9.3.13', 'tosca-simple-1.0')
def interface_types(self):
"""
This section contains an optional list of interface type definitions for use in the service
@@ -662,7 +667,7 @@
"""
@object_dict_field(RelationshipType)
- @dsl_specification('3.9.3.14', 'tosca-simple-1.0')
+ @implements_specification('3.9.3.14', 'tosca-simple-1.0')
def relationship_types(self):
"""
This section contains a set of relationship type definitions for use in the service
@@ -672,7 +677,7 @@
"""
@object_dict_field(NodeType)
- @dsl_specification('3.9.3.15', 'tosca-simple-1.0')
+ @implements_specification('3.9.3.15', 'tosca-simple-1.0')
def node_types(self):
"""
This section contains a set of node type definitions for use in the service template.
@@ -681,7 +686,7 @@
"""
@object_dict_field(GroupType)
- @dsl_specification('3.9.3.16', 'tosca-simple-1.0')
+ @implements_specification('3.9.3.16', 'tosca-simple-1.0')
def group_types(self):
"""
This section contains a list of group type definitions for use in the service template.
@@ -690,7 +695,7 @@
"""
@object_dict_field(PolicyType)
- @dsl_specification('3.9.3.17', 'tosca-simple-1.0')
+ @implements_specification('3.9.3.17', 'tosca-simple-1.0')
def policy_types(self):
"""
This section contains a list of policy type definitions for use in the service template.
diff --git a/extensions/aria_extension_tosca/simple_v1_0/types.py b/extensions/aria_extension_tosca/simple_v1_0/types.py
index 2112f7f..bc80eb9 100644
--- a/extensions/aria_extension_tosca/simple_v1_0/types.py
+++ b/extensions/aria_extension_tosca/simple_v1_0/types.py
@@ -15,7 +15,7 @@
from aria.utils.collections import FrozenDict, FrozenList
from aria.utils.caching import cachedmethod
-from aria.parser import dsl_specification
+from aria.parser import implements_specification
from aria.parser.presentation import (has_fields, allow_unknown_fields, primitive_field,
primitive_list_field, object_field, object_dict_field,
object_list_field, object_sequenced_list_field,
@@ -46,7 +46,7 @@
from .presentation.types import convert_shorthand_to_full_type_name
@has_fields
-@dsl_specification('3.6.3', 'tosca-simple-1.0')
+@implements_specification('3.6.3', 'tosca-simple-1.0')
class ArtifactType(ExtensiblePresentation):
"""
An Artifact Type is a reusable entity that defines the type of one or more files that are used
@@ -131,7 +131,7 @@
'properties'))
@has_fields
-@dsl_specification('3.6.5', 'tosca-simple-1.0')
+@implements_specification('3.6.5', 'tosca-simple-1.0')
class DataType(ExtensiblePresentation):
"""
A Data Type definition defines the schema for new named datatypes in TOSCA.
@@ -225,7 +225,7 @@
'properties'))
@has_fields
-@dsl_specification('3.6.6', 'tosca-simple-1.0')
+@implements_specification('3.6.6', 'tosca-simple-1.0')
class CapabilityType(ExtensiblePresentation):
"""
A Capability Type is a reusable entity that describes a kind of capability that a Node Type can
@@ -328,7 +328,7 @@
@allow_unknown_fields
@has_fields
-@dsl_specification('3.6.4', 'tosca-simple-1.0')
+@implements_specification('3.6.4', 'tosca-simple-1.0')
class InterfaceType(ExtensiblePresentation):
"""
An Interface Type is a reusable entity that describes a set of operations that can be used to
@@ -406,7 +406,7 @@
'operations'))
@has_fields
-@dsl_specification('3.6.9', 'tosca-simple-1.0')
+@implements_specification('3.6.9', 'tosca-simple-1.0')
class RelationshipType(ExtensiblePresentation):
"""
A Relationship Type is a reusable entity that defines the type of one or more relationships
@@ -520,7 +520,7 @@
'interfaces'))
@has_fields
-@dsl_specification('3.6.8', 'tosca-simple-1.0')
+@implements_specification('3.6.8', 'tosca-simple-1.0')
class NodeType(ExtensiblePresentation):
"""
A Node Type is a reusable entity that defines the type of one or more Node Templates. As such, a
@@ -668,7 +668,7 @@
'capabilities'))
@has_fields
-@dsl_specification('3.6.10', 'tosca-simple-1.0')
+@implements_specification('3.6.10', 'tosca-simple-1.0')
class GroupType(ExtensiblePresentation):
"""
A Group Type defines logical grouping types for nodes, typically for different management
@@ -781,7 +781,7 @@
'interfaces'))
@has_fields
-@dsl_specification('3.6.11', 'tosca-simple-1.0')
+@implements_specification('3.6.11', 'tosca-simple-1.0')
class PolicyType(ExtensiblePresentation):
"""
A Policy Type defines a type of requirement that affects or governs an application or service's
diff --git a/tests/resources/service-templates/tosca-simple-1.0/node-cellar/node-cellar.yaml b/tests/resources/service-templates/tosca-simple-1.0/node-cellar/node-cellar.yaml
index 349a166..8e80640 100644
--- a/tests/resources/service-templates/tosca-simple-1.0/node-cellar/node-cellar.yaml
+++ b/tests/resources/service-templates/tosca-simple-1.0/node-cellar/node-cellar.yaml
@@ -33,6 +33,7 @@
- types/mongodb.yaml
- types/nginx.yaml
- aria-1.0
+
dsl_definitions:
default_openstack_credential: &DEFAULT_OPENSTACK_CREDENTIAL
@@ -94,8 +95,11 @@
properties:
unpack_credential:
user: gigaspaces
- token: { get_property: [ SELF, app_endpoint, protocol ] }
+ token: { get_attribute: [ SELF, tosca_id ] }
+ #token: { get_property: [ SELF, app_endpoint, protocol ] }
#token: { get_property: [ HOST, flavor_name ] }
+ #token: { token: [ { get_property: [ HOST, flavor_name ] }, '.', 1 ] }
+ #token: { token: [ 'zero.one|two-three', '.|-', 3 ] }
interfaces:
Maintenance:
enable: juju > charm.maintenance_on