Skip to content
This repository has been archived by the owner. It is now read-only.
Permalink
Browse files
ARIA-180 Separate Parameter models and change relationships
First, the Parameter model was separated in 6 individual models:
Input, Output, Property, Attribute, Configuration, Argument.

The first four models are TOSCA-related, and we needed to separate them
as according to the TOSCA spec, all four of these represent different
things, and have different fields.

The other two models (Configuration, Argument) are
orchestration-related, as and such are not supposed to represent the
same object as the TOSCA-related objects.

Second, up until now, the relationships of the then-Parameter models
were many-to-many to any other object related to them. This was a problem,
as logically the relationships were one-to-many (from the other models
to the Parameter models).
In this commit, we changed them to one-to-many. In addition, separating
the Parameter models allowed us to more accurately represent the relationship of each individual Parameter-model with its related models.
  • Loading branch information
aviaefrat committed Jun 4, 2017
1 parent 9174f94 commit b6d3c43ba42ccc3fa7287aff132c83563abea035
Showing 25 changed files with 867 additions and 511 deletions.
@@ -23,10 +23,14 @@
from sqlalchemy import (
Column,
Integer,
Text
Text,
PickleType
)

from . import utils
from ..parser.consumption import ConsumptionContext
from ..utils import console, collections, caching, formatting
from ..utils.type import canonical_type_name, full_type_name
from . import utils, functions


class ModelMixin(object):
@@ -140,3 +144,211 @@ class TemplateModelMixin(InstanceModelMixin):

def instantiate(self, container):
raise NotImplementedError


class ParameterMixin(TemplateModelMixin, caching.HasCachedMethods): #pylint: disable=abstract-method
"""
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__ = 'parameter'

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

raise ValueError('orphaned {class_name}: does not have an owner: {name}'.format(
class_name=type(self).__name__, name=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):
return collections.OrderedDict((
('name', self.name),
('type_name', self.type_name),
('value', self.value),
('description', self.description)))

def instantiate(self, container):
return self.__class__(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))

@property
def unwrapped(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.
For primitive types, we will prefer their TOSCA aliases. 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>`__
:param name: Parameter name
:type name: basestring
:param value: Parameter value
:param description: Description (optional)
:type description: basestring
"""

type_name = canonical_type_name(value)
if type_name is None:
type_name = full_type_name(value)
return cls(name=name, # pylint: disable=unexpected-keyword-arg
type_name=type_name,
value=value,
description=description)

def as_other_parameter_model(self, other_model_cls):

name, value = self.unwrapped
return other_model_cls.wrap(name, value)

def as_argument(self):
from . import models
return self.as_other_parameter_model(models.Argument)
@@ -72,15 +72,20 @@
'ServiceModification',

# Common service models
'Parameter',
'Input',
'Configuration',
'Output',
'Property',
'Attribute',
'Type',
'Metadata',

# Orchestration models
'Execution',
'Plugin',
'Task',
'Log'
'Log',
'Argument'
)


@@ -206,7 +211,24 @@ class ServiceModification(aria_declarative_base, service_changes.ServiceModifica

# region common service models

class Parameter(aria_declarative_base, service_common.ParameterBase):

class Input(aria_declarative_base, service_common.InputBase):
pass


class Configuration(aria_declarative_base, service_common.ConfigurationBase):
pass


class Output(aria_declarative_base, service_common.OutputBase):
pass


class Property(aria_declarative_base, service_common.PropertyBase):
pass


class Attribute(aria_declarative_base, service_common.AttributeBase):
pass


@@ -237,6 +259,11 @@ class Task(aria_declarative_base, orchestration.TaskBase):
class Log(aria_declarative_base, orchestration.LogBase):
pass


class Argument(aria_declarative_base, orchestration.ArgumentBase):
pass


# endregion


@@ -276,13 +303,18 @@ class Log(aria_declarative_base, orchestration.LogBase):
ServiceModification,

# Common service models
Parameter,
Input,
Configuration,
Output,
Property,
Attribute,
Type,
Metadata,

# Orchestration models
Execution,
Plugin,
Task,
Log
Log,
Argument
]
@@ -39,7 +39,7 @@
from sqlalchemy.ext.declarative import declared_attr

from ..orchestrator.exceptions import (TaskAbortException, TaskRetryException)
from .mixins import ModelMixin
from .mixins import ModelMixin, ParameterMixin
from . import (
relationship,
types as modeling_types
@@ -115,7 +115,7 @@ def tasks(cls):

@declared_attr
def inputs(cls):
return relationship.many_to_many(cls, 'parameter', prefix='inputs', dict_key='name')
return relationship.one_to_many(cls, 'input', dict_key='name')

# region foreign keys

@@ -233,7 +233,7 @@ class TaskBase(ModelMixin):
:ivar function: Python path to an ``@operation`` function
:vartype function: basestring
:ivar arguments: Arguments that can be used by this task
:vartype arguments: {basestring: :class:`Parameter`}
:vartype arguments: {basestring: :class:`Argument`}
:ivar max_attempts: Maximum number of retries allowed in case of failure
:vartype max_attempts: int
:ivar retry_interval: Interval between retries (in seconds)
@@ -301,7 +301,7 @@ def execution(cls):

@declared_attr
def arguments(cls):
return relationship.many_to_many(cls, 'parameter', prefix='arguments', dict_key='name')
return relationship.one_to_many(cls, 'argument', dict_key='name')

function = Column(String)
max_attempts = Column(Integer, default=1)
@@ -433,3 +433,32 @@ def __str__(self):
def __repr__(self):
name = (self.task.actor if self.task else self.execution).name
return '{name}: {self.msg}'.format(name=name, self=self)


class ArgumentBase(ParameterMixin):

__tablename__ = 'argument'

# region foreign keys

@declared_attr
def task_fk(cls):
return relationship.foreign_key('task', nullable=True)

@declared_attr
def operation_fk(cls):
return relationship.foreign_key('operation', nullable=True)

# endregion

# region many_to_one relationships

@declared_attr
def task(cls):
return relationship.many_to_one(cls, 'task')

@declared_attr
def operation(cls):
return relationship.many_to_one(cls, 'operation')

# endregion

0 comments on commit b6d3c43

Please sign in to comment.