add output model
Project: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/commit/7e1d7eb8 Tree: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/tree/7e1d7eb8 Diff: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/diff/7e1d7eb8 Branch: refs/heads/ARIA-180-convert-parameter-to-one-to-many Commit: 7e1d7eb802274539db7b09941d6fab1f6220716f Parents: 78d6019 Author: Avia Efrat <a...@gigaspaces.com> Authored: Sun May 21 16:28:30 2017 +0300 Committer: Avia Efrat <a...@gigaspaces.com> Committed: Mon May 22 14:06:27 2017 +0300 ---------------------------------------------------------------------- aria/modeling/models.py | 9 + aria/modeling/relationship.py | 4 +- aria/modeling/service_common.py | 197 +++++++++++++++++++ aria/modeling/service_instance.py | 4 +- aria/modeling/service_template.py | 4 +- examples/hello-world/helloworld.yaml | 5 + .../simple_v1_0/modeling/__init__.py | 20 +- 7 files changed, 230 insertions(+), 13 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/7e1d7eb8/aria/modeling/models.py ---------------------------------------------------------------------- diff --git a/aria/modeling/models.py b/aria/modeling/models.py index 584b877..f3acca6 100644 --- a/aria/modeling/models.py +++ b/aria/modeling/models.py @@ -73,6 +73,7 @@ __all__ = ( # Common service models 'Parameter', + 'Output' 'Type', 'Metadata', @@ -210,6 +211,13 @@ class Parameter(aria_declarative_base, service_common.ParameterBase): pass +class Output(aria_declarative_base, service_common.OutputBase): + # Temporarily, until we will separate the Parameter model into Input, Output, Property and + # Attribute, Parameter will represent Input, Property and Attribute, and Output will represent + # the outputs. + pass + + class Type(aria_declarative_base, service_common.TypeBase): pass @@ -277,6 +285,7 @@ models_to_register = [ # Common service models Parameter, + Output, Type, Metadata, http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/7e1d7eb8/aria/modeling/relationship.py ---------------------------------------------------------------------- diff --git a/aria/modeling/relationship.py b/aria/modeling/relationship.py index 40be5b2..c4f4cf3 100644 --- a/aria/modeling/relationship.py +++ b/aria/modeling/relationship.py @@ -287,8 +287,8 @@ def many_to_many(model_class, if prefix is not None: secondary_table_name = '{0}_{1}'.format(prefix, secondary_table_name) - if other_property is None: - other_property = '{0}_{1}'.format(prefix, formatting.pluralize(this_table)) + if other_property is None: + other_property = '{0}_{1}'.format(prefix, formatting.pluralize(this_table)) secondary_table = _get_secondary_table( model_class.metadata, http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/7e1d7eb8/aria/modeling/service_common.py ---------------------------------------------------------------------- diff --git a/aria/modeling/service_common.py b/aria/modeling/service_common.py index e9c96a4..05698b6 100644 --- a/aria/modeling/service_common.py +++ b/aria/modeling/service_common.py @@ -228,6 +228,203 @@ class ParameterBase(TemplateModelMixin, caching.HasCachedMethods): description=description) +# TODO dry this code. currently it is a copy of ParameterBase +class OutputBase(TemplateModelMixin, caching.HasCachedMethods): + """ + Represents a typed value. The value can contain nested intrinsic functions. + + This model can be used as the ``container_holder`` argument for :func:`functions.evaluate`. + + :ivar name: Name + :vartype name: basestring + :ivar type_name: Type name + :vartype type_name: basestring + :ivar value: Value + :ivar description: Description + :vartype description: basestring + """ + + __tablename__ = 'output' + + name = Column(Text) + type_name = Column(Text) + 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 output: 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 output: 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): + return collections.OrderedDict(( + ('name', self.name), + ('type_name', self.type_name), + ('value', self.value), + ('description', self.description))) + + def instantiate(self, container): + from . import models + return models.Output(name=self.name, # pylint: disable=unexpected-keyword-arg + type_name=self.type_name, + _value=self._value, + description=self.description) + + 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(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(formatting.as_raw(self.value)))) + if self.description: + console.puts(context.style.meta(self.description)) + + def unwrap(self): + return self.name, self.value + + @classmethod + def wrap(cls, name, value, description=None): + """ + Wraps an arbitrary value as a parameter. The type will be guessed via introspection. + + :param name: Parameter name + :type name: basestring + :param value: Parameter value + :param description: Description (optional) + :type description: basestring + """ + + from . import models + type_name = canonical_type_name(value) + if type_name is None: + type_name = full_type_name(value) + return models.Output(name=name, # pylint: disable=unexpected-keyword-arg + type_name=type_name, + value=value, + description=description) + + class TypeBase(InstanceModelMixin): """ Represents a type and its children. http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/7e1d7eb8/aria/modeling/service_instance.py ---------------------------------------------------------------------- diff --git a/aria/modeling/service_instance.py b/aria/modeling/service_instance.py index 41a388d..4236caa 100644 --- a/aria/modeling/service_instance.py +++ b/aria/modeling/service_instance.py @@ -65,7 +65,7 @@ class ServiceBase(InstanceModelMixin): :ivar inputs: Externally provided parameters :vartype inputs: {basestring: :class:`Parameter`} :ivar outputs: These parameters are filled in after service installation - :vartype outputs: {basestring: :class:`Parameter`} + :vartype outputs: {basestring: :class:`Output`} :ivar workflows: Custom workflows that can be performed on the service :vartype workflows: {basestring: :class:`Operation`} :ivar plugins: Plugins used by the service @@ -171,7 +171,7 @@ class ServiceBase(InstanceModelMixin): @declared_attr def outputs(cls): - return relationship.many_to_many(cls, 'parameter', prefix='outputs', dict_key='name') + return relationship.many_to_many(cls, 'output', dict_key='name') @declared_attr def plugins(cls): http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/7e1d7eb8/aria/modeling/service_template.py ---------------------------------------------------------------------- diff --git a/aria/modeling/service_template.py b/aria/modeling/service_template.py index 1eb95a3..e253617 100644 --- a/aria/modeling/service_template.py +++ b/aria/modeling/service_template.py @@ -69,7 +69,7 @@ class ServiceTemplateBase(TemplateModelMixin): :ivar inputs: Externally provided parameters :vartype inputs: {basestring: :class:`Parameter`} :ivar outputs: These parameters are filled in after service installation - :vartype outputs: {basestring: :class:`Parameter`} + :vartype outputs: {basestring: :class:`Output`} :ivar workflow_templates: Custom workflows that can be performed on the service :vartype workflow_templates: {basestring: :class:`OperationTemplate`} :ivar plugin_specifications: Plugins used by the service @@ -253,7 +253,7 @@ class ServiceTemplateBase(TemplateModelMixin): @declared_attr def outputs(cls): - return relationship.many_to_many(cls, 'parameter', prefix='outputs', dict_key='name') + return relationship.many_to_many(cls, 'output', dict_key='name') # endregion http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/7e1d7eb8/examples/hello-world/helloworld.yaml ---------------------------------------------------------------------- diff --git a/examples/hello-world/helloworld.yaml b/examples/hello-world/helloworld.yaml index 77cef30..55c5030 100644 --- a/examples/hello-world/helloworld.yaml +++ b/examples/hello-world/helloworld.yaml @@ -30,3 +30,8 @@ topology_template: configure: scripts/configure.sh start: scripts/start.sh stop: scripts/stop.sh + + outputs: + test_output: + description: description_of_test_output + value: value_of_test_output http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/7e1d7eb8/extensions/aria_extension_tosca/simple_v1_0/modeling/__init__.py ---------------------------------------------------------------------- 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 99389e4..1769ce5 100644 --- a/extensions/aria_extension_tosca/simple_v1_0/modeling/__init__.py +++ b/extensions/aria_extension_tosca/simple_v1_0/modeling/__init__.py @@ -32,7 +32,8 @@ from aria.modeling.models import (Type, ServiceTemplate, NodeTemplate, RequirementTemplate, RelationshipTemplate, CapabilityTemplate, GroupTemplate, PolicyTemplate, SubstitutionTemplate, SubstitutionTemplateMapping, InterfaceTemplate, OperationTemplate, - ArtifactTemplate, Metadata, Parameter, PluginSpecification) + ArtifactTemplate, Metadata, Parameter, Output, + PluginSpecification) from .constraints import (Equal, GreaterThan, GreaterOrEqual, LessThan, LessOrEqual, InRange, ValidValues, Length, MinLength, MaxLength, Pattern) @@ -91,7 +92,8 @@ def create_service_template_model(context): # pylint: disable=too-many-locals,to create_parameter_models_from_values(model.inputs, topology_template._get_input_values(context)) create_parameter_models_from_values(model.outputs, - topology_template._get_output_values(context)) + topology_template._get_output_values(context), + model_class=Output) # Plugin specifications policies = context.presentation.get('service_template', 'topology_template', 'policies') @@ -537,13 +539,17 @@ def create_types(context, root, types): container.children.append(model) -def create_parameter_models_from_values(properties, source_properties): +def create_parameter_models_from_values(properties, source_properties, model_class=None): + + if model_class is None: + model_class = Parameter + if source_properties: for property_name, prop in source_properties.iteritems(): - properties[property_name] = Parameter(name=property_name, # pylint: disable=unexpected-keyword-arg - type_name=prop.type, - value=prop.value, - description=prop.description) + properties[property_name] = model_class(name=property_name, # pylint: disable=unexpected-keyword-arg + type_name=prop.type, + value=prop.value, + description=prop.description) def create_parameter_models_from_assignments(properties, source_properties):