summaryrefslogtreecommitdiffstats
path: root/azure/aria/aria-extension-cloudify/src/aria/aria/modeling
diff options
context:
space:
mode:
Diffstat (limited to 'azure/aria/aria-extension-cloudify/src/aria/aria/modeling')
-rw-r--r--azure/aria/aria-extension-cloudify/src/aria/aria/modeling/__init__.py54
-rw-r--r--azure/aria/aria-extension-cloudify/src/aria/aria/modeling/constraints.py31
-rw-r--r--azure/aria/aria-extension-cloudify/src/aria/aria/modeling/exceptions.py63
-rw-r--r--azure/aria/aria-extension-cloudify/src/aria/aria/modeling/functions.py140
-rw-r--r--azure/aria/aria-extension-cloudify/src/aria/aria/modeling/mixins.py333
-rw-r--r--azure/aria/aria-extension-cloudify/src/aria/aria/modeling/models.py427
-rw-r--r--azure/aria/aria-extension-cloudify/src/aria/aria/modeling/orchestration.py715
-rw-r--r--azure/aria/aria-extension-cloudify/src/aria/aria/modeling/relationship.py395
-rw-r--r--azure/aria/aria-extension-cloudify/src/aria/aria/modeling/service_changes.py253
-rw-r--r--azure/aria/aria-extension-cloudify/src/aria/aria/modeling/service_common.py601
-rw-r--r--azure/aria/aria-extension-cloudify/src/aria/aria/modeling/service_instance.py1695
-rw-r--r--azure/aria/aria-extension-cloudify/src/aria/aria/modeling/service_template.py1758
-rw-r--r--azure/aria/aria-extension-cloudify/src/aria/aria/modeling/types.py318
-rw-r--r--azure/aria/aria-extension-cloudify/src/aria/aria/modeling/utils.py185
14 files changed, 6968 insertions, 0 deletions
diff --git a/azure/aria/aria-extension-cloudify/src/aria/aria/modeling/__init__.py b/azure/aria/aria-extension-cloudify/src/aria/aria/modeling/__init__.py
new file mode 100644
index 0000000..57bc188
--- /dev/null
+++ b/azure/aria/aria-extension-cloudify/src/aria/aria/modeling/__init__.py
@@ -0,0 +1,54 @@
+# 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.
+
+"""
+This package provides an API for modeling ARIA's state and serializing it to storage.
+"""
+
+from collections import namedtuple
+
+from . import (
+ mixins,
+ types,
+ models,
+ utils,
+ service_template as _service_template_bases,
+ service_instance as _service_instance_bases,
+ service_changes as _service_changes_bases,
+ service_common as _service_common_bases,
+ orchestration as _orchestration_bases
+)
+
+
+_ModelBasesCls = namedtuple('ModelBase', 'service_template,'
+ 'service_instance,'
+ 'service_changes,'
+ 'service_common,'
+ 'orchestration')
+
+model_bases = _ModelBasesCls(service_template=_service_template_bases,
+ service_instance=_service_instance_bases,
+ service_changes=_service_changes_bases,
+ service_common=_service_common_bases,
+ orchestration=_orchestration_bases)
+
+
+__all__ = (
+ 'mixins',
+ 'types',
+ 'models',
+ 'model_bases',
+ 'utils'
+)
diff --git a/azure/aria/aria-extension-cloudify/src/aria/aria/modeling/constraints.py b/azure/aria/aria-extension-cloudify/src/aria/aria/modeling/constraints.py
new file mode 100644
index 0000000..8ed33d5
--- /dev/null
+++ b/azure/aria/aria-extension-cloudify/src/aria/aria/modeling/constraints.py
@@ -0,0 +1,31 @@
+# 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.
+
+"""
+Constraints for the requirements-and-capabilities matching mechanism.
+"""
+
+class NodeTemplateConstraint(object):
+ """
+ Used to constrain requirements for node templates.
+
+ Must be serializable.
+ """
+
+ def matches(self, source_node_template, target_node_template):
+ """
+ Returns ``True`` if the target matches the constraint for the source.
+ """
+ raise NotImplementedError
diff --git a/azure/aria/aria-extension-cloudify/src/aria/aria/modeling/exceptions.py b/azure/aria/aria-extension-cloudify/src/aria/aria/modeling/exceptions.py
new file mode 100644
index 0000000..cddc049
--- /dev/null
+++ b/azure/aria/aria-extension-cloudify/src/aria/aria/modeling/exceptions.py
@@ -0,0 +1,63 @@
+# 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.
+
+"""
+Modeling exceptions.
+"""
+
+from ..exceptions import AriaException
+
+
+class ModelingException(AriaException):
+ """
+ ARIA modeling exception.
+ """
+
+
+class ParameterException(ModelingException):
+ """
+ ARIA parameter exception.
+ """
+ pass
+
+
+class ValueFormatException(ModelingException):
+ """
+ ARIA modeling exception: the value is in the wrong format.
+ """
+
+
+class CannotEvaluateFunctionException(ModelingException):
+ """
+ ARIA modeling exception: cannot evaluate the function at this time.
+ """
+
+
+class MissingRequiredInputsException(ParameterException):
+ """
+ ARIA modeling exception: Required parameters have been omitted.
+ """
+
+
+class ParametersOfWrongTypeException(ParameterException):
+ """
+ ARIA modeling exception: Parameters of the wrong types have been provided.
+ """
+
+
+class UndeclaredInputsException(ParameterException):
+ """
+ ARIA modeling exception: Undeclared parameters have been provided.
+ """
diff --git a/azure/aria/aria-extension-cloudify/src/aria/aria/modeling/functions.py b/azure/aria/aria-extension-cloudify/src/aria/aria/modeling/functions.py
new file mode 100644
index 0000000..554bbfb
--- /dev/null
+++ b/azure/aria/aria-extension-cloudify/src/aria/aria/modeling/functions.py
@@ -0,0 +1,140 @@
+# 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.
+
+"""
+Mechanism for evaluating intrinsic functions.
+"""
+from ..parser.exceptions import InvalidValueError
+from ..parser.consumption import ConsumptionContext
+from ..utils.collections import OrderedDict
+from ..utils.type import full_type_name
+from . import exceptions
+
+
+class Function(object):
+ """
+ Base class for intrinsic functions. Serves as a placeholder for a value that should eventually
+ be derived by "evaluating" (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, container_holder):
+ """
+ Evaluates the function if possible.
+
+ :rtype: :class:`Evaluation` (or any object with ``value`` and ``final`` properties)
+ :raises CannotEvaluateFunctionException: if cannot be evaluated at this time (do *not* just
+ return ``None``)
+ """
+
+ 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.
+
+ :ivar value: evaluated value
+ :ivar final: whether the value is final
+ :vartype final: boolean
+ """
+
+ 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: {0}'
+ .format(full_type_name(value)))
+
+ 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/azure/aria/aria-extension-cloudify/src/aria/aria/modeling/mixins.py b/azure/aria/aria-extension-cloudify/src/aria/aria/modeling/mixins.py
new file mode 100644
index 0000000..d58c25a
--- /dev/null
+++ b/azure/aria/aria-extension-cloudify/src/aria/aria/modeling/mixins.py
@@ -0,0 +1,333 @@
+# 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.
+
+"""
+ARIA modeling mix-ins module
+"""
+
+from sqlalchemy.ext import associationproxy
+from sqlalchemy import (
+ Column,
+ Integer,
+ Text,
+ PickleType
+)
+
+from ..utils import collections, caching
+from ..utils.type import canonical_type_name, full_type_name
+from . import utils, functions
+
+
+class ModelMixin(object):
+
+ @utils.classproperty
+ def __modelname__(cls): # pylint: disable=no-self-argument
+ return getattr(cls, '__mapiname__', cls.__tablename__)
+
+ @classmethod
+ def id_column_name(cls):
+ raise NotImplementedError
+
+ @classmethod
+ def name_column_name(cls):
+ raise NotImplementedError
+
+ def to_dict(self, fields=None, suppress_error=False):
+ """
+ Create a dict representation of the model.
+
+ :param suppress_error: if set to ``True``, sets ``None`` to attributes that it's unable to
+ retrieve (e.g., if a relationship wasn't established yet, and so it's impossible to access
+ a property through it)
+ """
+
+ res = dict()
+ fields = fields or self.fields()
+ for field in fields:
+ try:
+ field_value = getattr(self, field)
+ except AttributeError:
+ if suppress_error:
+ field_value = None
+ else:
+ raise
+ if isinstance(field_value, list):
+ field_value = list(field_value)
+ elif isinstance(field_value, dict):
+ field_value = dict(field_value)
+ elif isinstance(field_value, ModelMixin):
+ field_value = field_value.to_dict()
+ res[field] = field_value
+
+ return res
+
+ @classmethod
+ def fields(cls):
+ """
+ List of field names for this table.
+
+ Mostly for backwards compatibility in the code (that uses ``fields``).
+ """
+
+ fields = set(cls._iter_association_proxies())
+ fields.update(cls.__table__.columns.keys())
+ return fields - set(getattr(cls, '__private_fields__', ()))
+
+ @classmethod
+ def _iter_association_proxies(cls):
+ for col, value in vars(cls).items():
+ if isinstance(value, associationproxy.AssociationProxy):
+ yield col
+
+ def __repr__(self):
+ return '<{cls} id=`{id}`>'.format(
+ cls=self.__class__.__name__,
+ id=getattr(self, self.name_column_name()))
+
+
+class ModelIDMixin(object):
+ id = Column(Integer, primary_key=True, autoincrement=True, doc="""
+ Unique ID.
+
+ :type: :obj:`int`
+ """)
+
+ name = Column(Text, index=True, doc="""
+ Model name.
+
+ :type: :obj:`basestring`
+ """)
+
+ @classmethod
+ def id_column_name(cls):
+ return 'id'
+
+ @classmethod
+ def name_column_name(cls):
+ return 'name'
+
+
+class InstanceModelMixin(ModelMixin):
+ """
+ Mix-in for service instance models.
+
+ All models support validation, diagnostic dumping, and representation as raw data (which can be
+ translated into JSON or YAML) via :meth:`as_raw`.
+ """
+
+ @property
+ def as_raw(self):
+ raise NotImplementedError
+
+ def coerce_values(self, report_issues):
+ pass
+
+
+class TemplateModelMixin(InstanceModelMixin): # pylint: disable=abstract-method
+ """
+ Mix-in for service template models.
+
+ All model models can be instantiated into service instance models.
+ """
+
+
+class ParameterMixin(TemplateModelMixin, caching.HasCachedMethods): #pylint: disable=abstract-method
+ """
+ Mix-in for typed values. The value can contain nested intrinsic functions.
+
+ This model can be used as the ``container_holder`` argument for
+ :func:`~aria.modeling.functions.evaluate`.
+ """
+
+ type_name = Column(Text, doc="""
+ Type name.
+
+ :type: :obj:`basestring`
+ """)
+
+ description = Column(Text, doc="""
+ Human-readable description.
+
+ :type: :obj:`basestring`
+ """)
+
+ _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.
+
+ :raises ~exceptions.ValueError: if failed to find an owner, which signifies 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.
+
+ :raises ~exceptions.ValueError: if failed to find a container model, which signifies 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:`~aria.modeling.models.Service` model containing this parameter, or ``None`` if
+ not contained in a service.
+
+ :raises ~exceptions.ValueError: if failed to find a container model, which signifies an
+ abnormal, orphaned parameter
+ """
+
+ 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:`~aria.modeling.models.ServiceTemplate` model containing this parameter, or
+ ``None`` if not contained in a service template.
+
+ :raises ~exceptions.ValueError: if failed to find a container model, which signifies an
+ abnormal, orphaned parameter
+ """
+
+ 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)))
+
+ @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: human-readable 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)
diff --git a/azure/aria/aria-extension-cloudify/src/aria/aria/modeling/models.py b/azure/aria/aria-extension-cloudify/src/aria/aria/modeling/models.py
new file mode 100644
index 0000000..cf84fdb
--- /dev/null
+++ b/azure/aria/aria-extension-cloudify/src/aria/aria/modeling/models.py
@@ -0,0 +1,427 @@
+# 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.
+
+"""
+Data models.
+
+Service template models
+-----------------------
+
+.. autosummary::
+ :nosignatures:
+
+ aria.modeling.models.ServiceTemplate
+ aria.modeling.models.NodeTemplate
+ aria.modeling.models.GroupTemplate
+ aria.modeling.models.PolicyTemplate
+ aria.modeling.models.SubstitutionTemplate
+ aria.modeling.models.SubstitutionTemplateMapping
+ aria.modeling.models.RequirementTemplate
+ aria.modeling.models.RelationshipTemplate
+ aria.modeling.models.CapabilityTemplate
+ aria.modeling.models.InterfaceTemplate
+ aria.modeling.models.OperationTemplate
+ aria.modeling.models.ArtifactTemplate
+ aria.modeling.models.PluginSpecification
+
+Service instance models
+-----------------------
+
+.. autosummary::
+ :nosignatures:
+
+ aria.modeling.models.Service
+ aria.modeling.models.Node
+ aria.modeling.models.Group
+ aria.modeling.models.Policy
+ aria.modeling.models.Substitution
+ aria.modeling.models.SubstitutionMapping
+ aria.modeling.models.Relationship
+ aria.modeling.models.Capability
+ aria.modeling.models.Interface
+ aria.modeling.models.Operation
+ aria.modeling.models.Artifact
+
+Common models
+-------------
+
+.. autosummary::
+ :nosignatures:
+
+ aria.modeling.models.Output
+ aria.modeling.models.Input
+ aria.modeling.models.Configuration
+ aria.modeling.models.Property
+ aria.modeling.models.Attribute
+ aria.modeling.models.Type
+ aria.modeling.models.Metadata
+
+Orchestration models
+--------------------
+
+.. autosummary::
+ :nosignatures:
+
+ aria.modeling.models.Execution
+ aria.modeling.models.Task
+ aria.modeling.models.Log
+ aria.modeling.models.Plugin
+ aria.modeling.models.Argument
+"""
+
+# pylint: disable=abstract-method
+
+from sqlalchemy.ext.declarative import declarative_base
+from sqlalchemy import (
+ Column,
+ Text
+)
+
+from . import (
+ service_template,
+ service_instance,
+ service_changes,
+ service_common,
+ orchestration,
+ mixins,
+ utils
+)
+
+
+aria_declarative_base = declarative_base(cls=mixins.ModelIDMixin)
+
+
+# See also models_to_register at the bottom of this file
+__all__ = (
+ 'models_to_register',
+
+ # Service template models
+ 'ServiceTemplate',
+ 'NodeTemplate',
+ 'GroupTemplate',
+ 'PolicyTemplate',
+ 'SubstitutionTemplate',
+ 'SubstitutionTemplateMapping',
+ 'RequirementTemplate',
+ 'RelationshipTemplate',
+ 'CapabilityTemplate',
+ 'InterfaceTemplate',
+ 'OperationTemplate',
+ 'ArtifactTemplate',
+ 'PluginSpecification',
+
+ # Service instance models
+ 'Service',
+ 'Node',
+ 'Group',
+ 'Policy',
+ 'Substitution',
+ 'SubstitutionMapping',
+ 'Relationship',
+ 'Capability',
+ 'Interface',
+ 'Operation',
+ 'Artifact',
+
+ # Service changes models
+ 'ServiceUpdate',
+ 'ServiceUpdateStep',
+ 'ServiceModification',
+
+ # Common service models
+ 'Input',
+ 'Configuration',
+ 'Output',
+ 'Property',
+ 'Attribute',
+ 'Type',
+ 'Metadata',
+
+ # Orchestration models
+ 'Execution',
+ 'Plugin',
+ 'Task',
+ 'Log',
+ 'Argument'
+)
+
+
+# region service template models
+
+@utils.fix_doc
+class ServiceTemplate(aria_declarative_base, service_template.ServiceTemplateBase):
+ name = Column(Text, index=True, unique=True)
+
+
+@utils.fix_doc
+class NodeTemplate(aria_declarative_base, service_template.NodeTemplateBase):
+ pass
+
+
+@utils.fix_doc
+class GroupTemplate(aria_declarative_base, service_template.GroupTemplateBase):
+ pass
+
+
+@utils.fix_doc
+class PolicyTemplate(aria_declarative_base, service_template.PolicyTemplateBase):
+ pass
+
+
+@utils.fix_doc
+class SubstitutionTemplate(aria_declarative_base, service_template.SubstitutionTemplateBase):
+ pass
+
+
+@utils.fix_doc
+class SubstitutionTemplateMapping(aria_declarative_base,
+ service_template.SubstitutionTemplateMappingBase):
+ pass
+
+
+@utils.fix_doc
+class RequirementTemplate(aria_declarative_base, service_template.RequirementTemplateBase):
+ pass
+
+
+@utils.fix_doc
+class RelationshipTemplate(aria_declarative_base, service_template.RelationshipTemplateBase):
+ pass
+
+
+@utils.fix_doc
+class CapabilityTemplate(aria_declarative_base, service_template.CapabilityTemplateBase):
+ pass
+
+
+@utils.fix_doc
+class InterfaceTemplate(aria_declarative_base, service_template.InterfaceTemplateBase):
+ pass
+
+
+@utils.fix_doc
+class OperationTemplate(aria_declarative_base, service_template.OperationTemplateBase):
+ pass
+
+
+@utils.fix_doc
+class ArtifactTemplate(aria_declarative_base, service_template.ArtifactTemplateBase):
+ pass
+
+
+@utils.fix_doc
+class PluginSpecification(aria_declarative_base, service_template.PluginSpecificationBase):
+ pass
+
+# endregion
+
+
+# region service instance models
+
+@utils.fix_doc
+class Service(aria_declarative_base, service_instance.ServiceBase):
+ name = Column(Text, index=True, unique=True)
+
+
+@utils.fix_doc
+class Node(aria_declarative_base, service_instance.NodeBase):
+ pass
+
+
+@utils.fix_doc
+class Group(aria_declarative_base, service_instance.GroupBase):
+ pass
+
+
+@utils.fix_doc
+class Policy(aria_declarative_base, service_instance.PolicyBase):
+ pass
+
+
+@utils.fix_doc
+class Substitution(aria_declarative_base, service_instance.SubstitutionBase):
+ pass
+
+
+@utils.fix_doc
+class SubstitutionMapping(aria_declarative_base, service_instance.SubstitutionMappingBase):
+ pass
+
+
+@utils.fix_doc
+class Relationship(aria_declarative_base, service_instance.RelationshipBase):
+ pass
+
+
+@utils.fix_doc
+class Capability(aria_declarative_base, service_instance.CapabilityBase):
+ pass
+
+
+@utils.fix_doc
+class Interface(aria_declarative_base, service_instance.InterfaceBase):
+ pass
+
+
+@utils.fix_doc
+class Operation(aria_declarative_base, service_instance.OperationBase):
+ pass
+
+
+@utils.fix_doc
+class Artifact(aria_declarative_base, service_instance.ArtifactBase):
+ pass
+
+# endregion
+
+
+# region service changes models
+
+@utils.fix_doc
+class ServiceUpdate(aria_declarative_base, service_changes.ServiceUpdateBase):
+ pass
+
+
+@utils.fix_doc
+class ServiceUpdateStep(aria_declarative_base, service_changes.ServiceUpdateStepBase):
+ pass
+
+
+@utils.fix_doc
+class ServiceModification(aria_declarative_base, service_changes.ServiceModificationBase):
+ pass
+
+# endregion
+
+
+# region common service models
+
+@utils.fix_doc
+class Input(aria_declarative_base, service_common.InputBase):
+ pass
+
+
+@utils.fix_doc
+class Configuration(aria_declarative_base, service_common.ConfigurationBase):
+ pass
+
+
+@utils.fix_doc
+class Output(aria_declarative_base, service_common.OutputBase):
+ pass
+
+
+@utils.fix_doc
+class Property(aria_declarative_base, service_common.PropertyBase):
+ pass
+
+
+@utils.fix_doc
+class Attribute(aria_declarative_base, service_common.AttributeBase):
+ pass
+
+
+@utils.fix_doc
+class Type(aria_declarative_base, service_common.TypeBase):
+ pass
+
+
+@utils.fix_doc
+class Metadata(aria_declarative_base, service_common.MetadataBase):
+ pass
+
+# endregion
+
+
+# region orchestration models
+
+@utils.fix_doc
+class Execution(aria_declarative_base, orchestration.ExecutionBase):
+ pass
+
+
+@utils.fix_doc
+class Plugin(aria_declarative_base, orchestration.PluginBase):
+ pass
+
+
+@utils.fix_doc
+class Task(aria_declarative_base, orchestration.TaskBase):
+ pass
+
+
+@utils.fix_doc
+class Log(aria_declarative_base, orchestration.LogBase):
+ pass
+
+
+@utils.fix_doc
+class Argument(aria_declarative_base, orchestration.ArgumentBase):
+ pass
+
+# endregion
+
+
+# See also __all__ at the top of this file
+models_to_register = (
+ # Service template models
+ ServiceTemplate,
+ NodeTemplate,
+ GroupTemplate,
+ PolicyTemplate,
+ SubstitutionTemplate,
+ SubstitutionTemplateMapping,
+ RequirementTemplate,
+ RelationshipTemplate,
+ CapabilityTemplate,
+ InterfaceTemplate,
+ OperationTemplate,
+ ArtifactTemplate,
+ PluginSpecification,
+
+ # Service instance models
+ Service,
+ Node,
+ Group,
+ Policy,
+ SubstitutionMapping,
+ Substitution,
+ Relationship,
+ Capability,
+ Interface,
+ Operation,
+ Artifact,
+
+ # Service changes models
+ ServiceUpdate,
+ ServiceUpdateStep,
+ ServiceModification,
+
+ # Common service models
+ Input,
+ Configuration,
+ Output,
+ Property,
+ Attribute,
+ Type,
+ Metadata,
+
+ # Orchestration models
+ Execution,
+ Plugin,
+ Task,
+ Log,
+ Argument
+)
diff --git a/azure/aria/aria-extension-cloudify/src/aria/aria/modeling/orchestration.py b/azure/aria/aria-extension-cloudify/src/aria/aria/modeling/orchestration.py
new file mode 100644
index 0000000..4d4f0fe
--- /dev/null
+++ b/azure/aria/aria-extension-cloudify/src/aria/aria/modeling/orchestration.py
@@ -0,0 +1,715 @@
+# 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.
+
+"""
+ARIA modeling orchestration module
+"""
+
+# pylint: disable=no-self-argument, no-member, abstract-method
+from datetime import datetime
+
+from sqlalchemy import (
+ Column,
+ Integer,
+ Text,
+ DateTime,
+ Boolean,
+ Enum,
+ String,
+ Float,
+ orm,
+ PickleType)
+from sqlalchemy.ext.declarative import declared_attr
+
+from ..orchestrator.exceptions import (TaskAbortException, TaskRetryException)
+from . import mixins
+from . import (
+ relationship,
+ types as modeling_types
+)
+
+
+class ExecutionBase(mixins.ModelMixin):
+ """
+ Workflow execution.
+ """
+
+ __tablename__ = 'execution'
+
+ __private_fields__ = ('service_fk',
+ 'service_template')
+
+ SUCCEEDED = 'succeeded'
+ FAILED = 'failed'
+ CANCELLED = 'cancelled'
+ PENDING = 'pending'
+ STARTED = 'started'
+ CANCELLING = 'cancelling'
+
+ STATES = (SUCCEEDED, FAILED, CANCELLED, PENDING, STARTED, CANCELLING)
+ END_STATES = (SUCCEEDED, FAILED, CANCELLED)
+
+ VALID_TRANSITIONS = {
+ PENDING: (STARTED, CANCELLED),
+ STARTED: END_STATES + (CANCELLING,),
+ CANCELLING: END_STATES,
+ # Retrying
+ CANCELLED: PENDING,
+ FAILED: PENDING
+ }
+
+ # region one_to_many relationships
+
+ @declared_attr
+ def inputs(cls):
+ """
+ Execution parameters.
+
+ :type: {:obj:`basestring`: :class:`Input`}
+ """
+ return relationship.one_to_many(cls, 'input', dict_key='name')
+
+ @declared_attr
+ def tasks(cls):
+ """
+ Tasks.
+
+ :type: [:class:`Task`]
+ """
+ return relationship.one_to_many(cls, 'task')
+
+ @declared_attr
+ def logs(cls):
+ """
+ Log messages for the execution (including log messages for its tasks).
+
+ :type: [:class:`Log`]
+ """
+ return relationship.one_to_many(cls, 'log')
+
+ # endregion
+
+ # region many_to_one relationships
+
+ @declared_attr
+ def service(cls):
+ """
+ Associated service.
+
+ :type: :class:`Service`
+ """
+ return relationship.many_to_one(cls, 'service')
+
+ # endregion
+
+ # region association proxies
+
+ @declared_attr
+ def service_name(cls):
+ return relationship.association_proxy('service', cls.name_column_name())
+
+ @declared_attr
+ def service_template(cls):
+ return relationship.association_proxy('service', 'service_template')
+
+ @declared_attr
+ def service_template_name(cls):
+ return relationship.association_proxy('service', 'service_template_name')
+
+ # endregion
+
+ # region foreign keys
+
+ @declared_attr
+ def service_fk(cls):
+ return relationship.foreign_key('service')
+
+ # endregion
+
+ created_at = Column(DateTime, index=True, doc="""
+ Creation timestamp.
+
+ :type: :class:`~datetime.datetime`
+ """)
+
+ started_at = Column(DateTime, nullable=True, index=True, doc="""
+ Started timestamp.
+
+ :type: :class:`~datetime.datetime`
+ """)
+
+ ended_at = Column(DateTime, nullable=True, index=True, doc="""
+ Ended timestamp.
+
+ :type: :class:`~datetime.datetime`
+ """)
+
+ error = Column(Text, nullable=True, doc="""
+ Error message.
+
+ :type: :obj:`basestring`
+ """)
+
+ status = Column(Enum(*STATES, name='execution_status'), default=PENDING, doc="""
+ Status.
+
+ :type: :obj:`basestring`
+ """)
+
+ workflow_name = Column(Text, doc="""
+ Workflow name.
+
+ :type: :obj:`basestring`
+ """)
+
+ @orm.validates('status')
+ def validate_status(self, key, value):
+ """Validation function that verifies execution status transitions are OK"""
+ try:
+ current_status = getattr(self, key)
+ except AttributeError:
+ return
+ valid_transitions = self.VALID_TRANSITIONS.get(current_status, [])
+ if all([current_status is not None,
+ current_status != value,
+ value not in valid_transitions]):
+ raise ValueError('Cannot change execution status from {current} to {new}'.format(
+ current=current_status,
+ new=value))
+ return value
+
+ def has_ended(self):
+ return self.status in self.END_STATES
+
+ def is_active(self):
+ return not self.has_ended() and self.status != self.PENDING
+
+ def __str__(self):
+ return '<{0} id=`{1}` (status={2})>'.format(
+ self.__class__.__name__,
+ getattr(self, self.name_column_name()),
+ self.status
+ )
+
+
+class TaskBase(mixins.ModelMixin):
+ """
+ Represents the smallest unit of stateful execution in ARIA. The task state includes inputs,
+ outputs, as well as an atomic status, ensuring that the task can only be running once at any
+ given time.
+
+ The Python :attr:`function` is usually provided by an associated :class:`Plugin`. The
+ :attr:`arguments` of the function should be set according to the specific signature of the
+ function.
+
+ Tasks may be "one shot" or may be configured to run repeatedly in the case of failure.
+
+ Tasks are often based on :class:`Operation`, and thus act on either a :class:`Node` or a
+ :class:`Relationship`, however this is not required.
+ """
+
+ __tablename__ = 'task'
+
+ __private_fields__ = ('node_fk',
+ 'relationship_fk',
+ 'plugin_fk',
+ 'execution_fk')
+
+ START_WORKFLOW = 'start_workflow'
+ END_WORKFLOW = 'end_workflow'
+ START_SUBWROFKLOW = 'start_subworkflow'
+ END_SUBWORKFLOW = 'end_subworkflow'
+ STUB = 'stub'
+ CONDITIONAL = 'conditional'
+
+ STUB_TYPES = (
+ START_WORKFLOW,
+ START_SUBWROFKLOW,
+ END_WORKFLOW,
+ END_SUBWORKFLOW,
+ STUB,
+ CONDITIONAL,
+ )
+
+ PENDING = 'pending'
+ RETRYING = 'retrying'
+ SENT = 'sent'
+ STARTED = 'started'
+ SUCCESS = 'success'
+ FAILED = 'failed'
+ STATES = (
+ PENDING,
+ RETRYING,
+ SENT,
+ STARTED,
+ SUCCESS,
+ FAILED,
+ )
+ INFINITE_RETRIES = -1
+
+ # region one_to_many relationships
+
+ @declared_attr
+ def logs(cls):
+ """
+ Log messages.
+
+ :type: [:class:`Log`]
+ """
+ return relationship.one_to_many(cls, 'log')
+
+ @declared_attr
+ def arguments(cls):
+ """
+ Arguments sent to the Python :attr:`function``.
+
+ :type: {:obj:`basestring`: :class:`Argument`}
+ """
+ return relationship.one_to_many(cls, 'argument', dict_key='name')
+
+ # endregion
+
+ # region many_one relationships
+
+ @declared_attr
+ def execution(cls):
+ """
+ Containing execution.
+
+ :type: :class:`Execution`
+ """
+ return relationship.many_to_one(cls, 'execution')
+
+ @declared_attr
+ def node(cls):
+ """
+ Node actor (can be ``None``).
+
+ :type: :class:`Node`
+ """
+ return relationship.many_to_one(cls, 'node')
+
+ @declared_attr
+ def relationship(cls):
+ """
+ Relationship actor (can be ``None``).
+
+ :type: :class:`Relationship`
+ """
+ return relationship.many_to_one(cls, 'relationship')
+
+ @declared_attr
+ def plugin(cls):
+ """
+ Associated plugin.
+
+ :type: :class:`Plugin`
+ """
+ return relationship.many_to_one(cls, 'plugin')
+
+ # endregion
+
+ # region association proxies
+
+ @declared_attr
+ def node_name(cls):
+ return relationship.association_proxy('node', cls.name_column_name())
+
+ @declared_attr
+ def relationship_name(cls):
+ return relationship.association_proxy('relationship', cls.name_column_name())
+
+ @declared_attr
+ def execution_name(cls):
+ return relationship.association_proxy('execution', cls.name_column_name())
+
+ # endregion
+
+ # region foreign keys
+
+ @declared_attr
+ def execution_fk(cls):
+ return relationship.foreign_key('execution', nullable=True)
+
+ @declared_attr
+ def node_fk(cls):
+ return relationship.foreign_key('node', nullable=True)
+
+ @declared_attr
+ def relationship_fk(cls):
+ return relationship.foreign_key('relationship', nullable=True)
+
+ @declared_attr
+ def plugin_fk(cls):
+ return relationship.foreign_key('plugin', nullable=True)
+
+ # endregion
+
+ status = Column(Enum(*STATES, name='status'), default=PENDING, doc="""
+ Current atomic status ('pending', 'retrying', 'sent', 'started', 'success', 'failed').
+
+ :type: :obj:`basestring`
+ """)
+
+ due_at = Column(DateTime, nullable=False, index=True, default=datetime.utcnow(), doc="""
+ Timestamp to start the task.
+
+ :type: :class:`~datetime.datetime`
+ """)
+
+ started_at = Column(DateTime, default=None, doc="""
+ Started timestamp.
+
+ :type: :class:`~datetime.datetime`
+ """)
+
+ ended_at = Column(DateTime, default=None, doc="""
+ Ended timestamp.
+
+ :type: :class:`~datetime.datetime`
+ """)
+
+ attempts_count = Column(Integer, default=1, doc="""
+ How many attempts occurred.
+
+ :type: :class:`~datetime.datetime`
+ """)
+
+ function = Column(String, doc="""
+ Full path to Python function.
+
+ :type: :obj:`basestring`
+ """)
+
+ max_attempts = Column(Integer, default=1, doc="""
+ Maximum number of attempts allowed in case of task failure.
+
+ :type: :obj:`int`
+ """)
+
+ retry_interval = Column(Float, default=0, doc="""
+ Interval between task retry attemps (in seconds).
+
+ :type: :obj:`float`
+ """)
+
+ ignore_failure = Column(Boolean, default=False, doc="""
+ Set to ``True`` to ignore failures.
+
+ :type: :obj:`bool`
+ """)
+
+ interface_name = Column(String, doc="""
+ Name of interface on node or relationship.
+
+ :type: :obj:`basestring`
+ """)
+
+ operation_name = Column(String, doc="""
+ Name of operation in interface on node or relationship.
+
+ :type: :obj:`basestring`
+ """)
+
+ _api_id = Column(String)
+ _executor = Column(PickleType)
+ _context_cls = Column(PickleType)
+ _stub_type = Column(Enum(*STUB_TYPES))
+
+ @property
+ def actor(self):
+ """
+ Actor of the task (node or relationship).
+ """
+ return self.node or self.relationship
+
+ @orm.validates('max_attempts')
+ def validate_max_attempts(self, _, value): # pylint: disable=no-self-use
+ """
+ Validates that max attempts is either -1 or a positive number.
+ """
+ if value < 1 and value != TaskBase.INFINITE_RETRIES:
+ raise ValueError('Max attempts can be either -1 (infinite) or any positive number. '
+ 'Got {value}'.format(value=value))
+ return value
+
+ @staticmethod
+ def abort(message=None):
+ raise TaskAbortException(message)
+
+ @staticmethod
+ def retry(message=None, retry_interval=None):
+ raise TaskRetryException(message, retry_interval=retry_interval)
+
+ @declared_attr
+ def dependencies(cls):
+ return relationship.many_to_many(cls, self=True)
+
+ def has_ended(self):
+ return self.status in (self.SUCCESS, self.FAILED)
+
+ def is_waiting(self):
+ if self._stub_type:
+ return not self.has_ended()
+ else:
+ return self.status in (self.PENDING, self.RETRYING)
+
+ @classmethod
+ def from_api_task(cls, api_task, executor, **kwargs):
+ instantiation_kwargs = {}
+
+ if hasattr(api_task.actor, 'outbound_relationships'):
+ instantiation_kwargs['node'] = api_task.actor
+ elif hasattr(api_task.actor, 'source_node'):
+ instantiation_kwargs['relationship'] = api_task.actor
+ else:
+ raise RuntimeError('No operation context could be created for {actor.model_cls}'
+ .format(actor=api_task.actor))
+
+ instantiation_kwargs.update(
+ {
+ 'name': api_task.name,
+ 'status': cls.PENDING,
+ 'max_attempts': api_task.max_attempts,
+ 'retry_interval': api_task.retry_interval,
+ 'ignore_failure': api_task.ignore_failure,
+ 'execution': api_task._workflow_context.execution,
+ 'interface_name': api_task.interface_name,
+ 'operation_name': api_task.operation_name,
+
+ # Only non-stub tasks have these fields
+ 'plugin': api_task.plugin,
+ 'function': api_task.function,
+ 'arguments': api_task.arguments,
+ '_context_cls': api_task._context_cls,
+ '_executor': executor,
+ }
+ )
+
+ instantiation_kwargs.update(**kwargs)
+
+ return cls(**instantiation_kwargs)
+
+
+class LogBase(mixins.ModelMixin):
+ """
+ Single log message.
+ """
+
+ __tablename__ = 'log'
+
+ __private_fields__ = ('execution_fk',
+ 'task_fk')
+
+ # region many_to_one relationships
+
+ @declared_attr
+ def execution(cls):
+ """
+ Containing execution.
+
+ :type: :class:`Execution`
+ """
+ return relationship.many_to_one(cls, 'execution')
+
+ @declared_attr
+ def task(cls):
+ """
+ Containing task (can be ``None``).
+
+ :type: :class:`Task`
+ """
+ return relationship.many_to_one(cls, 'task')
+
+ # endregion
+
+ # region foreign keys
+
+ @declared_attr
+ def execution_fk(cls):
+ return relationship.foreign_key('execution')
+
+ @declared_attr
+ def task_fk(cls):
+ return relationship.foreign_key('task', nullable=True)
+
+ # endregion
+
+ level = Column(String, doc="""
+ Log level.
+
+ :type: :obj:`basestring`
+ """)
+
+ msg = Column(String, doc="""
+ Log message.
+
+ :type: :obj:`basestring`
+ """)
+
+ created_at = Column(DateTime, index=True, doc="""
+ Creation timestamp.
+
+ :type: :class:`~datetime.datetime`
+ """)
+
+ traceback = Column(Text, doc="""
+ Error traceback in case of failure.
+
+ :type: :class:`~datetime.datetime`
+ """)
+
+ def __str__(self):
+ return self.msg
+
+ def __repr__(self):
+ name = (self.task.actor if self.task else self.execution).name
+ return '{name}: {self.msg}'.format(name=name, self=self)
+
+
+class PluginBase(mixins.ModelMixin):
+ """
+ Installed plugin.
+
+ Plugins are usually packaged as `wagons <https://github.com/cloudify-cosmo/wagon>`__, which
+ are archives of one or more `wheels <https://packaging.python.org/distributing/#wheels>`__.
+ Most of these fields are indeed extracted from the installed wagon's metadata.
+ """
+
+ __tablename__ = 'plugin'
+
+ # region one_to_many relationships
+
+ @declared_attr
+ def tasks(cls):
+ """
+ Associated Tasks.
+
+ :type: [:class:`Task`]
+ """
+ return relationship.one_to_many(cls, 'task')
+
+ # endregion
+
+ archive_name = Column(Text, nullable=False, index=True, doc="""
+ Filename (not the full path) of the wagon's archive, often with a ``.wgn`` extension.
+
+ :type: :obj:`basestring`
+ """)
+
+ distribution = Column(Text, doc="""
+ Name of the operating system on which the wagon was installed (e.g. ``ubuntu``).
+
+ :type: :obj:`basestring`
+ """)
+
+ distribution_release = Column(Text, doc="""
+ Release of the operating system on which the wagon was installed (e.g. ``trusty``).
+
+ :type: :obj:`basestring`
+ """)
+
+ distribution_version = Column(Text, doc="""
+ Version of the operating system on which the wagon was installed (e.g. ``14.04``).
+
+ :type: :obj:`basestring`
+ """)
+
+ package_name = Column(Text, nullable=False, index=True, doc="""
+ Primary Python package name used when the wagon was installed, which is one of the wheels in the
+ wagon (e.g. ``cloudify-script-plugin``).
+
+ :type: :obj:`basestring`
+ """)
+
+ package_source = Column(Text, doc="""
+ Full install string for the primary Python package name used when the wagon was installed (e.g.
+ ``cloudify-script-plugin==1.2``).
+
+ :type: :obj:`basestring`
+ """)
+
+ package_version = Column(Text, doc="""
+ Version for the primary Python package name used when the wagon was installed (e.g. ``1.2``).
+
+ :type: :obj:`basestring`
+ """)
+
+ supported_platform = Column(Text, doc="""
+ If the wheels are *all* pure Python then this would be "any", otherwise it would be the
+ installed platform name (e.g. ``linux_x86_64``).
+
+ :type: :obj:`basestring`
+ """)
+
+ supported_py_versions = Column(modeling_types.StrictList(basestring), doc="""
+ Python versions supported by all the wheels (e.g. ``["py26", "py27"]``)
+
+ :type: [:obj:`basestring`]
+ """)
+
+ wheels = Column(modeling_types.StrictList(basestring), nullable=False, doc="""
+ Filenames of the wheels archived in the wagon, often with a ``.whl`` extension.
+
+ :type: [:obj:`basestring`]
+ """)
+
+ uploaded_at = Column(DateTime, nullable=False, index=True, doc="""
+ Timestamp for when the wagon was installed.
+
+ :type: :class:`~datetime.datetime`
+ """)
+
+
+class ArgumentBase(mixins.ParameterMixin):
+ """
+ Python function argument parameter.
+ """
+
+ __tablename__ = 'argument'
+
+ # region many_to_one relationships
+
+ @declared_attr
+ def task(cls):
+ """
+ Containing task (can be ``None``);
+
+ :type: :class:`Task`
+ """
+ return relationship.many_to_one(cls, 'task')
+
+ @declared_attr
+ def operation(cls):
+ """
+ Containing operation (can be ``None``);
+
+ :type: :class:`Operation`
+ """
+ return relationship.many_to_one(cls, 'operation')
+
+ # endregion
+
+ # 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
diff --git a/azure/aria/aria-extension-cloudify/src/aria/aria/modeling/relationship.py b/azure/aria/aria-extension-cloudify/src/aria/aria/modeling/relationship.py
new file mode 100644
index 0000000..0d906de
--- /dev/null
+++ b/azure/aria/aria-extension-cloudify/src/aria/aria/modeling/relationship.py
@@ -0,0 +1,395 @@
+# 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.
+
+"""
+ARIA modeling relationship module
+"""
+
+# pylint: disable=invalid-name, redefined-outer-name
+
+from sqlalchemy.orm import relationship, backref
+from sqlalchemy.orm.collections import attribute_mapped_collection
+from sqlalchemy.ext.associationproxy import association_proxy as original_association_proxy
+from sqlalchemy import (
+ Column,
+ ForeignKey,
+ Integer,
+ Table
+)
+
+from ..utils import formatting
+
+NO_BACK_POP = 'NO_BACK_POP'
+
+
+def foreign_key(other_table, nullable=False):
+ """
+ Declare a foreign key property, which will also create a foreign key column in the table with
+ the name of the property. By convention the property name should end in "_fk".
+
+ You are required to explicitly create foreign keys in order to allow for one-to-one,
+ one-to-many, and many-to-one relationships (but not for many-to-many relationships). If you do
+ not do so, SQLAlchemy will fail to create the relationship property and raise an exception with
+ a clear error message.
+
+ You should normally not have to access this property directly, but instead use the associated
+ relationship properties.
+
+ *This utility method should only be used during class creation.*
+
+ :param other_table: other table name
+ :type other_table: basestring
+ :param nullable: ``True`` to allow null values (meaning that there is no relationship)
+ :type nullable: bool
+ """
+
+ return Column(Integer,
+ ForeignKey('{table}.id'.format(table=other_table), ondelete='CASCADE'),
+ nullable=nullable)
+
+
+def one_to_one_self(model_class, fk):
+ """
+ Declare a one-to-one relationship property. The property value would be an instance of the same
+ model.
+
+ You will need an associated foreign key to our own table.
+
+ *This utility method should only be used during class creation.*
+
+ :param model_class: class in which this relationship will be declared
+ :type model_class: type
+ :param fk: foreign key name
+ :type fk: basestring
+ """
+
+ remote_side = '{model_class}.{remote_column}'.format(
+ model_class=model_class.__name__,
+ remote_column=model_class.id_column_name()
+ )
+
+ primaryjoin = '{remote_side} == {model_class}.{column}'.format(
+ remote_side=remote_side,
+ model_class=model_class.__name__,
+ column=fk
+ )
+ return _relationship(
+ model_class,
+ model_class.__tablename__,
+ relationship_kwargs={
+ 'primaryjoin': primaryjoin,
+ 'remote_side': remote_side,
+ 'post_update': True
+ }
+ )
+
+
+def one_to_one(model_class,
+ other_table,
+ fk=None,
+ other_fk=None,
+ back_populates=None):
+ """
+ Declare a one-to-one relationship property. The property value would be an instance of the other
+ table's model.
+
+ You have two options for the foreign key. Either this table can have an associated key to the
+ other table (use the ``fk`` argument) or the other table can have an associated foreign key to
+ this our table (use the ``other_fk`` argument).
+
+ *This utility method should only be used during class creation.*
+
+ :param model_class: class in which this relationship will be declared
+ :type model_class: type
+ :param other_table: other table name
+ :type other_table: basestring
+ :param fk: foreign key name at our table (no need specify if there's no ambiguity)
+ :type fk: basestring
+ :param other_fk: foreign key name at the other table (no need specify if there's no ambiguity)
+ :type other_fk: basestring
+ :param back_populates: override name of matching many-to-many property at other table; set to
+ ``False`` to disable
+ :type back_populates: basestring or bool
+ """
+ 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)
+
+
+def one_to_many(model_class,
+ other_table=None,
+ other_fk=None,
+ dict_key=None,
+ back_populates=None,
+ rel_kwargs=None,
+ self=False):
+ """
+ Declare a one-to-many relationship property. The property value would be a list or dict of
+ instances of the child table's model.
+
+ The child table will need an associated foreign key to our table.
+
+ The declaration will automatically create a matching many-to-one property at the child model,
+ named after our table name. Use the ``child_property`` argument to override this name.
+
+ *This utility method should only be used during class creation.*
+
+ :param model_class: class in which this relationship will be declared
+ :type model_class: type
+ :param other_table: other table name
+ :type other_table: basestring
+ :param other_fk: foreign key name at the other table (no need specify if there's no ambiguity)
+ :type other_fk: basestring
+ :param dict_key: if set the value will be a dict with this key as the dict key; otherwise will
+ be a list
+ :type dict_key: basestring
+ :param back_populates: override name of matching many-to-one property at other table; set to
+ ``False`` to disable
+ :type back_populates: basestring or bool
+ :param rel_kwargs: additional relationship kwargs to be used by SQLAlchemy
+ :type rel_kwargs: dict
+ :param self: used for relationships between a table and itself. if set, other_table will
+ become the same as the source table.
+ :type self: bool
+ """
+ relationship_kwargs = rel_kwargs or {}
+ if self:
+ assert other_fk
+ other_table_name = model_class.__tablename__
+ back_populates = False
+ relationship_kwargs['remote_side'] = '{model}.{column}'.format(model=model_class.__name__,
+ column=other_fk)
+
+ else:
+ assert other_table
+ other_table_name = other_table
+ if back_populates is None:
+ back_populates = model_class.__tablename__
+ relationship_kwargs.setdefault('cascade', 'all')
+
+ return _relationship(
+ model_class,
+ other_table_name,
+ back_populates=back_populates,
+ other_fk=other_fk,
+ dict_key=dict_key,
+ relationship_kwargs=relationship_kwargs)
+
+
+def many_to_one(model_class,
+ parent_table,
+ fk=None,
+ parent_fk=None,
+ back_populates=None):
+ """
+ Declare a many-to-one relationship property. The property value would be an instance of the
+ parent table's model.
+
+ You will need an associated foreign key to the parent table.
+
+ The declaration will automatically create a matching one-to-many property at the child model,
+ named after the plural form of our table name. Use the ``parent_property`` argument to override
+ this name. Note: the automatic property will always be a SQLAlchemy query object; if you need a
+ Python collection then use :func:`one_to_many` at that model.
+
+ *This utility method should only be used during class creation.*
+
+ :param model_class: class in which this relationship will be declared
+ :type model_class: type
+ :param parent_table: parent table name
+ :type parent_table: basestring
+ :param fk: foreign key name at our table (no need specify if there's no ambiguity)
+ :type fk: basestring
+ :param back_populates: override name of matching one-to-many property at parent table; set to
+ ``False`` to disable
+ :type back_populates: basestring or bool
+ """
+ if back_populates is None:
+ back_populates = formatting.pluralize(model_class.__tablename__)
+
+ return _relationship(model_class,
+ parent_table,
+ back_populates=back_populates,
+ fk=fk,
+ other_fk=parent_fk)
+
+
+def many_to_many(model_class,
+ other_table=None,
+ prefix=None,
+ dict_key=None,
+ other_property=None,
+ self=False):
+ """
+ Declare a many-to-many relationship property. The property value would be a list or dict of
+ instances of the other table's model.
+
+ You do not need associated foreign keys for this relationship. Instead, an extra table will be
+ created for you.
+
+ The declaration will automatically create a matching many-to-many property at the other model,
+ named after the plural form of our table name. Use the ``other_property`` argument to override
+ this name. Note: the automatic property will always be a SQLAlchemy query object; if you need a
+ Python collection then use :func:`many_to_many` again at that model.
+
+ *This utility method should only be used during class creation.*
+
+ :param model_class: class in which this relationship will be declared
+ :type model_class: type
+ :param other_table: parent table name
+ :type other_table: basestring
+ :param prefix: optional prefix for extra table name as well as for ``other_property``
+ :type prefix: basestring
+ :param dict_key: if set the value will be a dict with this key as the dict key; otherwise will
+ be a list
+ :type dict_key: basestring
+ :param other_property: override name of matching many-to-many property at other table; set to
+ ``False`` to disable
+ :type other_property: basestring or bool
+ :param self: used for relationships between a table and itself. if set, other_table will
+ become the same as the source table.
+ :type self: bool
+ """
+
+ this_table = model_class.__tablename__
+ this_column_name = '{0}_id'.format(this_table)
+ this_foreign_key = '{0}.id'.format(this_table)
+
+ if self:
+ other_table = this_table
+
+ other_column_name = '{0}_{1}'.format(other_table, 'self_ref_id' if self else 'id')
+ other_foreign_key = '{0}.{1}'.format(other_table, 'id')
+
+ secondary_table_name = '{0}_{1}'.format(this_table, other_table)
+
+ 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))
+
+ secondary_table = _get_secondary_table(
+ model_class.metadata,
+ secondary_table_name,
+ this_column_name,
+ other_column_name,
+ this_foreign_key,
+ other_foreign_key
+ )
+
+ kwargs = {'relationship_kwargs': {'secondary': secondary_table}}
+
+ if self:
+ kwargs['back_populates'] = NO_BACK_POP
+ kwargs['relationship_kwargs']['primaryjoin'] = \
+ getattr(model_class, 'id') == getattr(secondary_table.c, this_column_name)
+ kwargs['relationship_kwargs']['secondaryjoin'] = \
+ getattr(model_class, 'id') == getattr(secondary_table.c, other_column_name)
+ else:
+ kwargs['backref_kwargs'] = \
+ {'name': other_property, 'uselist': True} if other_property else None
+ kwargs['dict_key'] = dict_key
+
+ return _relationship(model_class, other_table, **kwargs)
+
+
+def association_proxy(*args, **kwargs):
+ if 'type' in kwargs:
+ type_ = kwargs.get('type')
+ del kwargs['type']
+ else:
+ type_ = ':obj:`basestring`'
+ proxy = original_association_proxy(*args, **kwargs)
+ proxy.__doc__ = """
+ Internal. For use in SQLAlchemy queries.
+
+ :type: {0}
+ """.format(type_)
+ return proxy
+
+
+def _relationship(model_class,
+ other_table_name,
+ back_populates=None,
+ backref_kwargs=None,
+ relationship_kwargs=None,
+ fk=None,
+ other_fk=None,
+ dict_key=None):
+ relationship_kwargs = relationship_kwargs or {}
+
+ if fk:
+ relationship_kwargs.setdefault(
+ 'foreign_keys',
+ lambda: getattr(_get_class_for_table(model_class, model_class.__tablename__), fk)
+ )
+
+ elif other_fk:
+ relationship_kwargs.setdefault(
+ 'foreign_keys',
+ lambda: getattr(_get_class_for_table(model_class, other_table_name), other_fk)
+ )
+
+ if dict_key:
+ relationship_kwargs.setdefault('collection_class',
+ attribute_mapped_collection(dict_key))
+
+ 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
+ )
+ else:
+ if back_populates is not NO_BACK_POP:
+ relationship_kwargs['back_populates'] = back_populates
+ return relationship(lambda: _get_class_for_table(model_class, other_table_name),
+ **relationship_kwargs)
+
+
+def _get_class_for_table(model_class, tablename):
+ if tablename in (model_class.__name__, model_class.__tablename__):
+ return model_class
+
+ for table_cls in model_class._decl_class_registry.itervalues():
+ if tablename == getattr(table_cls, '__tablename__', None):
+ return table_cls
+
+ raise ValueError('unknown table: {0}'.format(tablename))
+
+
+def _get_secondary_table(metadata,
+ name,
+ first_column,
+ second_column,
+ first_foreign_key,
+ second_foreign_key):
+ return Table(
+ name,
+ metadata,
+ Column(first_column, Integer, ForeignKey(first_foreign_key)),
+ Column(second_column, Integer, ForeignKey(second_foreign_key))
+ )
diff --git a/azure/aria/aria-extension-cloudify/src/aria/aria/modeling/service_changes.py b/azure/aria/aria-extension-cloudify/src/aria/aria/modeling/service_changes.py
new file mode 100644
index 0000000..061262a
--- /dev/null
+++ b/azure/aria/aria-extension-cloudify/src/aria/aria/modeling/service_changes.py
@@ -0,0 +1,253 @@
+# 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.
+
+"""
+ARIA modeling service changes module
+"""
+
+# pylint: disable=no-self-argument, no-member, abstract-method
+
+from collections import namedtuple
+
+from sqlalchemy import (
+ Column,
+ Text,
+ DateTime,
+ Enum,
+)
+from sqlalchemy.ext.declarative import declared_attr
+
+from .types import (List, Dict)
+from .mixins import ModelMixin
+from . import relationship
+
+
+class ServiceUpdateBase(ModelMixin):
+ """
+ Deployment update model representation.
+ """
+ __tablename__ = 'service_update'
+
+ __private_fields__ = ('service_fk',
+ 'execution_fk')
+
+ created_at = Column(DateTime, nullable=False, index=True)
+ service_plan = Column(Dict, nullable=False)
+ service_update_nodes = Column(Dict)
+ service_update_service = Column(Dict)
+ service_update_node_templates = Column(List)
+ modified_entity_ids = Column(Dict)
+ state = Column(Text)
+
+ # region association proxies
+
+ @declared_attr
+ def execution_name(cls):
+ return relationship.association_proxy('execution', cls.name_column_name())
+
+ @declared_attr
+ def service_name(cls):
+ return relationship.association_proxy('service', cls.name_column_name())
+
+ # endregion
+
+ # region one_to_one relationships
+
+ # endregion
+
+ # region one_to_many relationships
+
+ @declared_attr
+ def steps(cls):
+ return relationship.one_to_many(cls, 'service_update_step')
+
+ # endregion
+
+ # region many_to_one relationships
+
+ @declared_attr
+ def execution(cls):
+ return relationship.one_to_one(cls, 'execution', back_populates=relationship.NO_BACK_POP)
+
+ @declared_attr
+ def service(cls):
+ return relationship.many_to_one(cls, 'service', back_populates='updates')
+
+ # endregion
+
+ # region foreign keys
+
+ @declared_attr
+ def execution_fk(cls):
+ return relationship.foreign_key('execution', nullable=True)
+
+ @declared_attr
+ def service_fk(cls):
+ return relationship.foreign_key('service')
+
+ # endregion
+
+ def to_dict(self, suppress_error=False, **kwargs):
+ dep_update_dict = super(ServiceUpdateBase, self).to_dict(suppress_error) #pylint: disable=no-member
+ # Taking care of the fact the DeploymentSteps are _BaseModels
+ dep_update_dict['steps'] = [step.to_dict() for step in self.steps]
+ return dep_update_dict
+
+
+class ServiceUpdateStepBase(ModelMixin):
+ """
+ Deployment update step model representation.
+ """
+
+ __tablename__ = 'service_update_step'
+
+ __private_fields__ = ('service_update_fk',)
+
+ _action_types = namedtuple('ACTION_TYPES', 'ADD, REMOVE, MODIFY')
+ ACTION_TYPES = _action_types(ADD='add', REMOVE='remove', MODIFY='modify')
+
+ _entity_types = namedtuple(
+ 'ENTITY_TYPES',
+ 'NODE, RELATIONSHIP, PROPERTY, OPERATION, WORKFLOW, OUTPUT, DESCRIPTION, GROUP, PLUGIN')
+ ENTITY_TYPES = _entity_types(
+ NODE='node',
+ RELATIONSHIP='relationship',
+ PROPERTY='property',
+ OPERATION='operation',
+ WORKFLOW='workflow',
+ OUTPUT='output',
+ DESCRIPTION='description',
+ GROUP='group',
+ PLUGIN='plugin'
+ )
+
+ action = Column(Enum(*ACTION_TYPES, name='action_type'), nullable=False)
+ entity_id = Column(Text, nullable=False)
+ entity_type = Column(Enum(*ENTITY_TYPES, name='entity_type'), nullable=False)
+
+ # region association proxies
+
+ @declared_attr
+ def service_update_name(cls):
+ return relationship.association_proxy('service_update', cls.name_column_name())
+
+ # endregion
+
+ # region one_to_one relationships
+
+ # endregion
+
+ # region one_to_many relationships
+
+ # endregion
+
+ # region many_to_one relationships
+
+ @declared_attr
+ def service_update(cls):
+ return relationship.many_to_one(cls, 'service_update', back_populates='steps')
+
+ # endregion
+
+ # region foreign keys
+
+ @declared_attr
+ def service_update_fk(cls):
+ return relationship.foreign_key('service_update')
+
+ # endregion
+
+ def __hash__(self):
+ return hash((getattr(self, self.id_column_name()), self.entity_id))
+
+ def __lt__(self, other):
+ """
+ the order is 'remove' < 'modify' < 'add'
+ :param other:
+ :return:
+ """
+ if not isinstance(other, self.__class__):
+ return not self >= other
+
+ if self.action != other.action:
+ if self.action == 'remove':
+ return_value = True
+ elif self.action == 'add':
+ return_value = False
+ else:
+ return_value = other.action == 'add'
+ return return_value
+
+ if self.action == 'add':
+ return self.entity_type == 'node' and other.entity_type == 'relationship'
+ if self.action == 'remove':
+ return self.entity_type == 'relationship' and other.entity_type == 'node'
+ return False
+
+
+class ServiceModificationBase(ModelMixin):
+ """
+ Deployment modification model representation.
+ """
+
+ __tablename__ = 'service_modification'
+
+ __private_fields__ = ('service_fk',)
+
+ STARTED = 'started'
+ FINISHED = 'finished'
+ ROLLEDBACK = 'rolledback'
+
+ STATES = [STARTED, FINISHED, ROLLEDBACK]
+ END_STATES = [FINISHED, ROLLEDBACK]
+
+ context = Column(Dict)
+ created_at = Column(DateTime, nullable=False, index=True)
+ ended_at = Column(DateTime, index=True)
+ modified_node_templates = Column(Dict)
+ nodes = Column(Dict)
+ status = Column(Enum(*STATES, name='service_modification_status'))
+
+ # region association proxies
+
+ @declared_attr
+ def service_name(cls):
+ return relationship.association_proxy('service', cls.name_column_name())
+
+ # endregion
+
+ # region one_to_one relationships
+
+ # endregion
+
+ # region one_to_many relationships
+
+ # endregion
+
+ # region many_to_one relationships
+
+ @declared_attr
+ def service(cls):
+ return relationship.many_to_one(cls, 'service', back_populates='modifications')
+
+ # endregion
+
+ # region foreign keys
+
+ @declared_attr
+ def service_fk(cls):
+ return relationship.foreign_key('service')
+
+ # endregion
diff --git a/azure/aria/aria-extension-cloudify/src/aria/aria/modeling/service_common.py b/azure/aria/aria-extension-cloudify/src/aria/aria/modeling/service_common.py
new file mode 100644
index 0000000..d1f6b00
--- /dev/null
+++ b/azure/aria/aria-extension-cloudify/src/aria/aria/modeling/service_common.py
@@ -0,0 +1,601 @@
+# 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.
+
+"""
+ARIA modeling service common module
+"""
+
+# pylint: disable=no-self-argument, no-member, abstract-method
+
+from sqlalchemy import (
+ Column,
+ Text,
+ Boolean
+)
+from sqlalchemy.ext.declarative import declared_attr
+
+from ..utils import (
+ collections,
+ formatting
+)
+from .mixins import InstanceModelMixin, TemplateModelMixin, ParameterMixin
+from . import relationship
+
+
+class OutputBase(ParameterMixin):
+ """
+ Output parameter or declaration for an output parameter.
+ """
+
+ __tablename__ = 'output'
+
+ # region many_to_one relationships
+
+ @declared_attr
+ def service_template(cls):
+ """
+ Containing service template (can be ``None``).
+
+ :type: :class:`ServiceTemplate`
+ """
+ return relationship.many_to_one(cls, 'service_template')
+
+ @declared_attr
+ def service(cls):
+ """
+ Containing service (can be ``None``).
+
+ :type: :class:`ServiceTemplate`
+ """
+ return relationship.many_to_one(cls, 'service')
+
+ # endregion
+
+ # region foreign keys
+
+ @declared_attr
+ def service_template_fk(cls):
+ return relationship.foreign_key('service_template', nullable=True)
+
+ @declared_attr
+ def service_fk(cls):
+ return relationship.foreign_key('service', nullable=True)
+
+ # endregion
+
+
+class InputBase(ParameterMixin):
+ """
+ Input parameter or declaration for an input parameter.
+ """
+
+ __tablename__ = 'input'
+
+ required = Column(Boolean, doc="""
+ Is the input mandatory.
+
+ :type: :obj:`bool`
+ """)
+
+ @classmethod
+ def wrap(cls, name, value, description=None, required=True): # pylint: disable=arguments-differ
+ input = super(InputBase, cls).wrap(name, value, description)
+ input.required = required
+ return input
+
+ # region many_to_one relationships
+
+ @declared_attr
+ def service_template(cls):
+ """
+ Containing service template (can be ``None``).
+
+ :type: :class:`ServiceTemplate`
+ """
+ return relationship.many_to_one(cls, 'service_template')
+
+ @declared_attr
+ def service(cls):
+ """
+ Containing service (can be ``None``).
+
+ :type: :class:`Service`
+ """
+ return relationship.many_to_one(cls, 'service')
+
+ @declared_attr
+ def interface(cls):
+ """
+ Containing interface (can be ``None``).
+
+ :type: :class:`Interface`
+ """
+ return relationship.many_to_one(cls, 'interface')
+
+ @declared_attr
+ def operation(cls):
+ """
+ Containing operation (can be ``None``).
+
+ :type: :class:`Operation`
+ """
+ return relationship.many_to_one(cls, 'operation')
+
+ @declared_attr
+ def interface_template(cls):
+ """
+ Containing interface template (can be ``None``).
+
+ :type: :class:`InterfaceTemplate`
+ """
+ return relationship.many_to_one(cls, 'interface_template')
+
+ @declared_attr
+ def operation_template(cls):
+ """
+ Containing operation template (can be ``None``).
+
+ :type: :class:`OperationTemplate`
+ """
+ return relationship.many_to_one(cls, 'operation_template')
+
+ @declared_attr
+ def execution(cls):
+ """
+ Containing execution (can be ``None``).
+
+ :type: :class:`Execution`
+ """
+ return relationship.many_to_one(cls, 'execution')
+
+ # endregion
+
+ # region foreign keys
+
+ @declared_attr
+ def service_template_fk(cls):
+ return relationship.foreign_key('service_template', nullable=True)
+
+ @declared_attr
+ def service_fk(cls):
+ return relationship.foreign_key('service', nullable=True)
+
+ @declared_attr
+ def interface_fk(cls):
+ return relationship.foreign_key('interface', nullable=True)
+
+ @declared_attr
+ def operation_fk(cls):
+ return relationship.foreign_key('operation', nullable=True)
+
+ @declared_attr
+ def interface_template_fk(cls):
+ return relationship.foreign_key('interface_template', nullable=True)
+
+ @declared_attr
+ def operation_template_fk(cls):
+ return relationship.foreign_key('operation_template', nullable=True)
+
+ @declared_attr
+ def execution_fk(cls):
+ return relationship.foreign_key('execution', nullable=True)
+
+ @declared_attr
+ def task_fk(cls):
+ return relationship.foreign_key('task', nullable=True)
+
+ # endregion
+
+
+class ConfigurationBase(ParameterMixin):
+ """
+ Configuration parameter.
+ """
+
+ __tablename__ = 'configuration'
+
+ # region many_to_one relationships
+
+ @declared_attr
+ def operation_template(cls):
+ """
+ Containing operation template (can be ``None``).
+
+ :type: :class:`OperationTemplate`
+ """
+ return relationship.many_to_one(cls, 'operation_template')
+
+ @declared_attr
+ def operation(cls):
+ """
+ Containing operation (can be ``None``).
+
+ :type: :class:`Operation`
+ """
+ return relationship.many_to_one(cls, 'operation')
+
+ # endregion
+
+ # region foreign keys
+
+ @declared_attr
+ def operation_template_fk(cls):
+ return relationship.foreign_key('operation_template', nullable=True)
+
+ @declared_attr
+ def operation_fk(cls):
+ return relationship.foreign_key('operation', nullable=True)
+
+ # endregion
+
+
+class PropertyBase(ParameterMixin):
+ """
+ Property parameter or declaration for a property parameter.
+ """
+
+ __tablename__ = 'property'
+
+ # region many_to_one relationships
+
+ @declared_attr
+ def node_template(cls):
+ """
+ Containing node template (can be ``None``).
+
+ :type: :class:`NodeTemplate`
+ """
+ return relationship.many_to_one(cls, 'node_template')
+
+ @declared_attr
+ def group_template(cls):
+ """
+ Containing group template (can be ``None``).
+
+ :type: :class:`GroupTemplate`
+ """
+ return relationship.many_to_one(cls, 'group_template')
+
+ @declared_attr
+ def policy_template(cls):
+ """
+ Containing policy template (can be ``None``).
+
+ :type: :class:`PolicyTemplate`
+ """
+ return relationship.many_to_one(cls, 'policy_template')
+
+ @declared_attr
+ def relationship_template(cls):
+ """
+ Containing relationship template (can be ``None``).
+
+ :type: :class:`RelationshipTemplate`
+ """
+ return relationship.many_to_one(cls, 'relationship_template')
+
+ @declared_attr
+ def capability_template(cls):
+ """
+ Containing capability template (can be ``None``).
+
+ :type: :class:`CapabilityTemplate`
+ """
+ return relationship.many_to_one(cls, 'capability_template')
+
+ @declared_attr
+ def artifact_template(cls):
+ """
+ Containing artifact template (can be ``None``).
+
+ :type: :class:`ArtifactTemplate`
+ """
+ return relationship.many_to_one(cls, 'artifact_template')
+
+ @declared_attr
+ def node(cls):
+ """
+ Containing node (can be ``None``).
+
+ :type: :class:`Node`
+ """
+ return relationship.many_to_one(cls, 'node')
+
+ @declared_attr
+ def group(cls):
+ """
+ Containing group (can be ``None``).
+
+ :type: :class:`Group`
+ """
+ return relationship.many_to_one(cls, 'group')
+
+ @declared_attr
+ def policy(cls):
+ """
+ Containing policy (can be ``None``).
+
+ :type: :class:`Policy`
+ """
+ return relationship.many_to_one(cls, 'policy')
+
+ @declared_attr
+ def relationship(cls):
+ """
+ Containing relationship (can be ``None``).
+
+ :type: :class:`Relationship`
+ """
+ return relationship.many_to_one(cls, 'relationship')
+
+ @declared_attr
+ def capability(cls):
+ """
+ Containing capability (can be ``None``).
+
+ :type: :class:`Capability`
+ """
+ return relationship.many_to_one(cls, 'capability')
+
+ @declared_attr
+ def artifact(cls):
+ """
+ Containing artifact (can be ``None``).
+
+ :type: :class:`Artifact`
+ """
+ return relationship.many_to_one(cls, 'artifact')
+
+ # endregion
+
+ # region foreign keys
+
+ @declared_attr
+ def node_template_fk(cls):
+ return relationship.foreign_key('node_template', nullable=True)
+
+ @declared_attr
+ def group_template_fk(cls):
+ return relationship.foreign_key('group_template', nullable=True)
+
+ @declared_attr
+ def policy_template_fk(cls):
+ return relationship.foreign_key('policy_template', nullable=True)
+
+ @declared_attr
+ def relationship_template_fk(cls):
+ return relationship.foreign_key('relationship_template', nullable=True)
+
+ @declared_attr
+ def capability_template_fk(cls):
+ return relationship.foreign_key('capability_template', nullable=True)
+
+ @declared_attr
+ def artifact_template_fk(cls):
+ return relationship.foreign_key('artifact_template', nullable=True)
+
+ @declared_attr
+ def node_fk(cls):
+ return relationship.foreign_key('node', nullable=True)
+
+ @declared_attr
+ def group_fk(cls):
+ return relationship.foreign_key('group', nullable=True)
+
+ @declared_attr
+ def policy_fk(cls):
+ return relationship.foreign_key('policy', nullable=True)
+
+ @declared_attr
+ def relationship_fk(cls):
+ return relationship.foreign_key('relationship', nullable=True)
+
+ @declared_attr
+ def capability_fk(cls):
+ return relationship.foreign_key('capability', nullable=True)
+
+ @declared_attr
+ def artifact_fk(cls):
+ return relationship.foreign_key('artifact', nullable=True)
+
+ # endregion
+
+
+class AttributeBase(ParameterMixin):
+ """
+ Attribute parameter or declaration for an attribute parameter.
+ """
+
+ __tablename__ = 'attribute'
+
+ # region many_to_one relationships
+
+ @declared_attr
+ def node_template(cls):
+ """
+ Containing node template (can be ``None``).
+
+ :type: :class:`NodeTemplate`
+ """
+ return relationship.many_to_one(cls, 'node_template')
+
+ @declared_attr
+ def node(cls):
+ """
+ Containing node (can be ``None``).
+
+ :type: :class:`Node`
+ """
+ return relationship.many_to_one(cls, 'node')
+
+ # endregion
+
+ # region foreign keys
+
+ @declared_attr
+ def node_template_fk(cls):
+ """For Attribute many-to-one to NodeTemplate"""
+ return relationship.foreign_key('node_template', nullable=True)
+
+ @declared_attr
+ def node_fk(cls):
+ """For Attribute many-to-one to Node"""
+ return relationship.foreign_key('node', nullable=True)
+
+ # endregion
+
+
+class TypeBase(InstanceModelMixin):
+ """
+ Type and its children. Can serve as the root for a type hierarchy.
+ """
+
+ __tablename__ = 'type'
+
+ __private_fields__ = ('parent_type_fk',)
+
+ variant = Column(Text, nullable=False)
+
+ description = Column(Text, doc="""
+ Human-readable description.
+
+ :type: :obj:`basestring`
+ """)
+
+ _role = Column(Text, name='role')
+
+ # region one_to_one relationships
+
+ @declared_attr
+ def parent(cls):
+ """
+ Parent type (will be ``None`` for the root of a type hierarchy).
+
+ :type: :class:`Type`
+ """
+ return relationship.one_to_one_self(cls, 'parent_type_fk')
+
+ # endregion
+
+ # region one_to_many relationships
+
+ @declared_attr
+ def children(cls):
+ """
+ Children.
+
+ :type: [:class:`Type`]
+ """
+ return relationship.one_to_many(cls, other_fk='parent_type_fk', self=True)
+
+ # endregion
+
+ # region foreign keys
+
+ @declared_attr
+ def parent_type_fk(cls):
+ """For Type one-to-many to Type"""
+ return relationship.foreign_key('type', nullable=True)
+
+ # endregion
+
+ @property
+ def role(self):
+ def get_role(the_type):
+ if the_type is None:
+ return None
+ elif the_type._role is None:
+ return get_role(the_type.parent)
+ return the_type._role
+
+ return get_role(self)
+
+ @role.setter
+ def role(self, value):
+ self._role = value
+
+ def is_descendant(self, base_name, name):
+ base = self.get_descendant(base_name)
+ if base is not None:
+ if base.get_descendant(name) is not None:
+ return True
+ return False
+
+ def get_descendant(self, name):
+ if self.name == name:
+ return self
+ for child in self.children:
+ found = child.get_descendant(name)
+ if found is not None:
+ return found
+ return None
+
+ def iter_descendants(self):
+ for child in self.children:
+ yield child
+ for descendant in child.iter_descendants():
+ yield descendant
+
+ @property
+ def as_raw(self):
+ return collections.OrderedDict((
+ ('name', self.name),
+ ('description', self.description),
+ ('role', self.role)))
+
+ @property
+ def as_raw_all(self):
+ types = []
+ self._append_raw_children(types)
+ return types
+
+ def _append_raw_children(self, types):
+ for child in self.children:
+ raw_child = formatting.as_raw(child)
+ raw_child['parent'] = self.name
+ types.append(raw_child)
+ child._append_raw_children(types)
+
+ @property
+ def hierarchy(self):
+ """
+ Type hierarchy as a list beginning with this type and ending in the root.
+
+ :type: [:class:`Type`]
+ """
+ return [self] + (self.parent.hierarchy if self.parent else [])
+
+
+class MetadataBase(TemplateModelMixin):
+ """
+ Custom values associated with the service.
+
+ This model is used by both service template and service instance elements.
+
+ :ivar name: name
+ :vartype name: basestring
+ :ivar value: value
+ :vartype value: basestring
+ """
+
+ __tablename__ = 'metadata'
+
+ value = Column(Text)
+
+ @property
+ def as_raw(self):
+ return collections.OrderedDict((
+ ('name', self.name),
+ ('value', self.value)))
diff --git a/azure/aria/aria-extension-cloudify/src/aria/aria/modeling/service_instance.py b/azure/aria/aria-extension-cloudify/src/aria/aria/modeling/service_instance.py
new file mode 100644
index 0000000..01c4da9
--- /dev/null
+++ b/azure/aria/aria-extension-cloudify/src/aria/aria/modeling/service_instance.py
@@ -0,0 +1,1695 @@
+# 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.
+
+"""
+ARIA modeling service instance module
+"""
+
+# pylint: disable=too-many-lines, no-self-argument, no-member, abstract-method
+
+from sqlalchemy import (
+ Column,
+ Text,
+ Integer,
+ Enum,
+ Boolean
+)
+from sqlalchemy import DateTime
+from sqlalchemy.ext.declarative import declared_attr
+from sqlalchemy.ext.orderinglist import ordering_list
+
+from . import (
+ relationship,
+ types as modeling_types
+)
+from .mixins import InstanceModelMixin
+
+from ..utils import (
+ collections,
+ formatting
+)
+
+
+class ServiceBase(InstanceModelMixin):
+ """
+ Usually an instance of a :class:`ServiceTemplate` and its many associated templates (node
+ templates, group templates, policy templates, etc.). However, it can also be created
+ programmatically.
+ """
+
+ __tablename__ = 'service'
+
+ __private_fields__ = ('substitution_fk',
+ 'service_template_fk')
+
+ # region one_to_one relationships
+
+ @declared_attr
+ def substitution(cls):
+ """
+ Exposes the entire service as a single node.
+
+ :type: :class:`Substitution`
+ """
+ return relationship.one_to_one(cls, 'substitution', back_populates=relationship.NO_BACK_POP)
+
+ # endregion
+
+ # region one_to_many relationships
+
+ @declared_attr
+ def outputs(cls):
+ """
+ Output parameters.
+
+ :type: {:obj:`basestring`: :class:`Output`}
+ """
+ return relationship.one_to_many(cls, 'output', dict_key='name')
+
+ @declared_attr
+ def inputs(cls):
+ """
+ Externally provided parameters.
+
+ :type: {:obj:`basestring`: :class:`Input`}
+ """
+ return relationship.one_to_many(cls, 'input', dict_key='name')
+
+ @declared_attr
+ def updates(cls):
+ """
+ Service updates.
+
+ :type: [:class:`ServiceUpdate`]
+ """
+ return relationship.one_to_many(cls, 'service_update')
+
+ @declared_attr
+ def modifications(cls):
+ """
+ Service modifications.
+
+ :type: [:class:`ServiceModification`]
+ """
+ return relationship.one_to_many(cls, 'service_modification')
+
+ @declared_attr
+ def executions(cls):
+ """
+ Executions.
+
+ :type: [:class:`Execution`]
+ """
+ return relationship.one_to_many(cls, 'execution')
+
+ @declared_attr
+ def nodes(cls):
+ """
+ Nodes.
+
+ :type: {:obj:`basestring`, :class:`Node`}
+ """
+ return relationship.one_to_many(cls, 'node', dict_key='name')
+
+ @declared_attr
+ def groups(cls):
+ """
+ Groups.
+
+ :type: {:obj:`basestring`, :class:`Group`}
+ """
+ return relationship.one_to_many(cls, 'group', dict_key='name')
+
+ @declared_attr
+ def policies(cls):
+ """
+ Policies.
+
+ :type: {:obj:`basestring`, :class:`Policy`}
+ """
+ return relationship.one_to_many(cls, 'policy', dict_key='name')
+
+ @declared_attr
+ def workflows(cls):
+ """
+ Workflows.
+
+ :type: {:obj:`basestring`, :class:`Operation`}
+ """
+ return relationship.one_to_many(cls, 'operation', dict_key='name')
+
+ # endregion
+
+ # region many_to_one relationships
+
+ @declared_attr
+ def service_template(cls):
+ """
+ Source service template (can be ``None``).
+
+ :type: :class:`ServiceTemplate`
+ """
+ return relationship.many_to_one(cls, 'service_template')
+
+ # endregion
+
+ # region many_to_many relationships
+
+ @declared_attr
+ def meta_data(cls):
+ """
+ Associated metadata.
+
+ :type: {:obj:`basestring`, :class:`Metadata`}
+ """
+ # Warning! We cannot use the attr name "metadata" because it's used by SQLAlchemy!
+ return relationship.many_to_many(cls, 'metadata', dict_key='name')
+
+ @declared_attr
+ def plugins(cls):
+ """
+ Associated plugins.
+
+ :type: {:obj:`basestring`, :class:`Plugin`}
+ """
+ return relationship.many_to_many(cls, 'plugin', dict_key='name')
+
+ # endregion
+
+ # region association proxies
+
+ @declared_attr
+ def service_template_name(cls):
+ return relationship.association_proxy('service_template', 'name', type=':obj:`basestring`')
+
+ # endregion
+
+ # region foreign keys
+
+ @declared_attr
+ def substitution_fk(cls):
+ """Service one-to-one to Substitution"""
+ return relationship.foreign_key('substitution', nullable=True)
+
+ @declared_attr
+ def service_template_fk(cls):
+ """For Service many-to-one to ServiceTemplate"""
+ return relationship.foreign_key('service_template', nullable=True)
+
+ # endregion
+
+ description = Column(Text, doc="""
+ Human-readable description.
+
+ :type: :obj:`basestring`
+ """)
+
+ created_at = Column(DateTime, nullable=False, index=True, doc="""
+ Creation timestamp.
+
+ :type: :class:`~datetime.datetime`
+ """)
+
+ updated_at = Column(DateTime, doc="""
+ Update timestamp.
+
+ :type: :class:`~datetime.datetime`
+ """)
+
+ def get_node_by_type(self, type_name):
+ """
+ Finds the first node of a type (or descendent type).
+ """
+ service_template = self.service_template
+
+ if service_template is not None:
+ node_types = service_template.node_types
+ if node_types is not None:
+ for node in self.nodes.itervalues():
+ if node_types.is_descendant(type_name, node.type.name):
+ return node
+
+ return None
+
+ def get_policy_by_type(self, type_name):
+ """
+ Finds the first policy of a type (or descendent type).
+ """
+ service_template = self.service_template
+
+ if service_template is not None:
+ policy_types = service_template.policy_types
+ if policy_types is not None:
+ for policy in self.policies.itervalues():
+ if policy_types.is_descendant(type_name, policy.type.name):
+ return policy
+
+ return None
+
+ @property
+ def as_raw(self):
+ return collections.OrderedDict((
+ ('description', self.description),
+ ('metadata', formatting.as_raw_dict(self.meta_data)),
+ ('nodes', formatting.as_raw_list(self.nodes)),
+ ('groups', formatting.as_raw_list(self.groups)),
+ ('policies', formatting.as_raw_list(self.policies)),
+ ('substitution', formatting.as_raw(self.substitution)),
+ ('inputs', formatting.as_raw_dict(self.inputs)),
+ ('outputs', formatting.as_raw_dict(self.outputs)),
+ ('workflows', formatting.as_raw_list(self.workflows))))
+
+
+class NodeBase(InstanceModelMixin):
+ """
+ Typed vertex in the service topology.
+
+ Nodes may have zero or more :class:`Relationship` instances to other nodes, together forming
+ a many-to-many node graph.
+
+ Usually an instance of a :class:`NodeTemplate`.
+ """
+
+ __tablename__ = 'node'
+
+ __private_fields__ = ('type_fk',
+ 'host_fk',
+ 'service_fk',
+ 'node_template_fk')
+
+ INITIAL = 'initial'
+ CREATING = 'creating'
+ CREATED = 'created'
+ CONFIGURING = 'configuring'
+ CONFIGURED = 'configured'
+ STARTING = 'starting'
+ STARTED = 'started'
+ STOPPING = 'stopping'
+ DELETING = 'deleting'
+ DELETED = 'deleted'
+ ERROR = 'error'
+
+ # Note: 'deleted' isn't actually part of the TOSCA spec, since according the description of the
+ # 'deleting' state: "Node is transitioning from its current state to one where it is deleted and
+ # its state is no longer tracked by the instance model." However, we prefer to be able to
+ # retrieve information about deleted nodes, so we chose to add this 'deleted' state to enable us
+ # to do so.
+
+ STATES = (INITIAL, CREATING, CREATED, CONFIGURING, CONFIGURED, STARTING, STARTED, STOPPING,
+ DELETING, DELETED, ERROR)
+
+ _OP_TO_STATE = {'create': {'transitional': CREATING, 'finished': CREATED},
+ 'configure': {'transitional': CONFIGURING, 'finished': CONFIGURED},
+ 'start': {'transitional': STARTING, 'finished': STARTED},
+ 'stop': {'transitional': STOPPING, 'finished': CONFIGURED},
+ 'delete': {'transitional': DELETING, 'finished': DELETED}}
+
+ # region one_to_one relationships
+
+ @declared_attr
+ def host(cls): # pylint: disable=method-hidden
+ """
+ Node in which we are hosted (can be ``None``).
+
+ Normally the host node is found by following the relationship graph (relationships with
+ ``host`` roles) to final nodes (with ``host`` roles).
+
+ :type: :class:`Node`
+ """
+ return relationship.one_to_one_self(cls, 'host_fk')
+
+ # endregion
+
+ # region one_to_many relationships
+
+ @declared_attr
+ def tasks(cls):
+ """
+ Associated tasks.
+
+ :type: [:class:`Task`]
+ """
+ return relationship.one_to_many(cls, 'task')
+
+ @declared_attr
+ def interfaces(cls):
+ """
+ Associated interfaces.
+
+ :type: {:obj:`basestring`: :class:`Interface`}
+ """
+ return relationship.one_to_many(cls, 'interface', dict_key='name')
+
+ @declared_attr
+ def properties(cls):
+ """
+ Associated immutable parameters.
+
+ :type: {:obj:`basestring`: :class:`Property`}
+ """
+ return relationship.one_to_many(cls, 'property', dict_key='name')
+
+ @declared_attr
+ def attributes(cls):
+ """
+ Associated mutable parameters.
+
+ :type: {:obj:`basestring`: :class:`Attribute`}
+ """
+ return relationship.one_to_many(cls, 'attribute', dict_key='name')
+
+ @declared_attr
+ def artifacts(cls):
+ """
+ Associated artifacts.
+
+ :type: {:obj:`basestring`: :class:`Artifact`}
+ """
+ return relationship.one_to_many(cls, 'artifact', dict_key='name')
+
+ @declared_attr
+ def capabilities(cls):
+ """
+ Associated exposed capabilities.
+
+ :type: {:obj:`basestring`: :class:`Capability`}
+ """
+ return relationship.one_to_many(cls, 'capability', dict_key='name')
+
+ @declared_attr
+ def outbound_relationships(cls):
+ """
+ Relationships to other nodes.
+
+ :type: [:class:`Relationship`]
+ """
+ return relationship.one_to_many(
+ cls, 'relationship', other_fk='source_node_fk', back_populates='source_node',
+ rel_kwargs=dict(
+ order_by='Relationship.source_position',
+ collection_class=ordering_list('source_position', count_from=0)
+ )
+ )
+
+ @declared_attr
+ def inbound_relationships(cls):
+ """
+ Relationships from other nodes.
+
+ :type: [:class:`Relationship`]
+ """
+ return relationship.one_to_many(
+ cls, 'relationship', other_fk='target_node_fk', back_populates='target_node',
+ rel_kwargs=dict(
+ order_by='Relationship.target_position',
+ collection_class=ordering_list('target_position', count_from=0)
+ )
+ )
+
+ # endregion
+
+ # region many_to_one relationships
+
+ @declared_attr
+ def service(cls):
+ """
+ Containing service.
+
+ :type: :class:`Service`
+ """
+ return relationship.many_to_one(cls, 'service')
+
+ @declared_attr
+ def node_template(cls):
+ """
+ Source node template (can be ``None``).
+
+ :type: :class:`NodeTemplate`
+ """
+ return relationship.many_to_one(cls, 'node_template')
+
+ @declared_attr
+ def type(cls):
+ """
+ Node type.
+
+ :type: :class:`Type`
+ """
+ return relationship.many_to_one(cls, 'type', back_populates=relationship.NO_BACK_POP)
+
+ # endregion
+
+ # region association proxies
+
+ @declared_attr
+ def service_name(cls):
+ return relationship.association_proxy('service', 'name', type=':obj:`basestring`')
+
+ @declared_attr
+ def node_template_name(cls):
+ return relationship.association_proxy('node_template', 'name', type=':obj:`basestring`')
+
+ # endregion
+
+ # region foreign_keys
+
+ @declared_attr
+ def type_fk(cls):
+ """For Node many-to-one to Type"""
+ return relationship.foreign_key('type')
+
+ @declared_attr
+ def host_fk(cls):
+ """For Node one-to-one to Node"""
+ return relationship.foreign_key('node', nullable=True)
+
+ @declared_attr
+ def service_fk(cls):
+ """For Service one-to-many to Node"""
+ return relationship.foreign_key('service')
+
+ @declared_attr
+ def node_template_fk(cls):
+ """For Node many-to-one to NodeTemplate"""
+ return relationship.foreign_key('node_template')
+
+ # endregion
+
+ description = Column(Text, doc="""
+ Human-readable description.
+
+ :type: :obj:`basestring`
+ """)
+
+ state = Column(Enum(*STATES, name='node_state'), nullable=False, default=INITIAL, doc="""
+ TOSCA state.
+
+ :type: :obj:`basestring`
+ """)
+
+ version = Column(Integer, default=1, doc="""
+ Used by :mod:`aria.storage.instrumentation`.
+
+ :type: :obj:`int`
+ """)
+
+ __mapper_args__ = {'version_id_col': version} # Enable SQLAlchemy automatic version counting
+
+ @classmethod
+ def determine_state(cls, op_name, is_transitional):
+ """
+ :returns the state the node should be in as a result of running the operation on this node.
+
+ E.g. if we are running tosca.interfaces.node.lifecycle.Standard.create, then
+ the resulting state should either 'creating' (if the task just started) or 'created'
+ (if the task ended).
+
+ If the operation is not a standard TOSCA lifecycle operation, then we return None.
+ """
+
+ state_type = 'transitional' if is_transitional else 'finished'
+ try:
+ return cls._OP_TO_STATE[op_name][state_type]
+ except KeyError:
+ return None
+
+ def is_available(self):
+ return self.state not in (self.INITIAL, self.DELETED, self.ERROR)
+
+ def get_outbound_relationship_by_name(self, name):
+ for the_relationship in self.outbound_relationships:
+ if the_relationship.name == name:
+ return the_relationship
+ return None
+
+ def get_inbound_relationship_by_name(self, name):
+ for the_relationship in self.inbound_relationships:
+ if the_relationship.name == name:
+ return the_relationship
+ return None
+
+ @property
+ def host_address(self):
+ if self.host and self.host.attributes:
+ attribute = self.host.attributes.get('ip')
+ if attribute is not None:
+ return attribute.value
+ return None
+
+ @property
+ def as_raw(self):
+ return collections.OrderedDict((
+ ('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)),
+ ('relationships', formatting.as_raw_list(self.outbound_relationships))))
+
+
+class GroupBase(InstanceModelMixin):
+ """
+ Typed logical container for zero or more :class:`Node` instances.
+
+ Usually an instance of a :class:`GroupTemplate`.
+ """
+
+ __tablename__ = 'group'
+
+ __private_fields__ = ('type_fk',
+ 'service_fk',
+ 'group_template_fk')
+
+ # region one_to_many relationships
+
+ @declared_attr
+ def properties(cls):
+ """
+ Associated immutable parameters.
+
+ :type: {:obj:`basestring`: :class:`Property`}
+ """
+ return relationship.one_to_many(cls, 'property', dict_key='name')
+
+ @declared_attr
+ def interfaces(cls):
+ """
+ Associated interfaces.
+
+ :type: {:obj:`basestring`: :class:`Interface`}
+ """
+ return relationship.one_to_many(cls, 'interface', dict_key='name')
+
+ # endregion
+
+ # region many_to_one relationships
+
+ @declared_attr
+ def service(cls):
+ """
+ Containing service.
+
+ :type: :class:`Service`
+ """
+ return relationship.many_to_one(cls, 'service')
+
+ @declared_attr
+ def group_template(cls):
+ """
+ Source group template (can be ``None``).
+
+ :type: :class:`GroupTemplate`
+ """
+ return relationship.many_to_one(cls, 'group_template')
+
+ @declared_attr
+ def type(cls):
+ """
+ Group type.
+
+ :type: :class:`Type`
+ """
+ return relationship.many_to_one(cls, 'type', back_populates=relationship.NO_BACK_POP)
+
+ # endregion
+
+ # region many_to_many relationships
+
+ @declared_attr
+ def nodes(cls):
+ """
+ Member nodes.
+
+ :type: [:class:`Node`]
+ """
+ return relationship.many_to_many(cls, 'node')
+
+ # endregion
+
+ # region foreign_keys
+
+ @declared_attr
+ def type_fk(cls):
+ """For Group many-to-one to Type"""
+ return relationship.foreign_key('type')
+
+ @declared_attr
+ def service_fk(cls):
+ """For Service one-to-many to Group"""
+ return relationship.foreign_key('service')
+
+ @declared_attr
+ def group_template_fk(cls):
+ """For Group many-to-one to GroupTemplate"""
+ return relationship.foreign_key('group_template', nullable=True)
+
+ # endregion
+
+ description = Column(Text, doc="""
+ Human-readable description.
+
+ :type: :obj:`basestring`
+ """)
+
+ @property
+ def as_raw(self):
+ return collections.OrderedDict((
+ ('name', self.name),
+ ('properties', formatting.as_raw_dict(self.properties)),
+ ('interfaces', formatting.as_raw_list(self.interfaces))))
+
+
+class PolicyBase(InstanceModelMixin):
+ """
+ Typed set of orchestration hints applied to zero or more :class:`Node` or :class:`Group`
+ instances.
+
+ Usually an instance of a :class:`PolicyTemplate`.
+ """
+
+ __tablename__ = 'policy'
+
+ __private_fields__ = ('type_fk',
+ 'service_fk',
+ 'policy_template_fk')
+
+ # region one_to_many relationships
+
+ @declared_attr
+ def properties(cls):
+ """
+ Associated immutable parameters.
+
+ :type: {:obj:`basestring`: :class:`Property`}
+ """
+ return relationship.one_to_many(cls, 'property', dict_key='name')
+
+ # endregion
+
+ # region many_to_one relationships
+
+ @declared_attr
+ def service(cls):
+ """
+ Containing service.
+
+ :type: :class:`Service`
+ """
+ return relationship.many_to_one(cls, 'service')
+
+ @declared_attr
+ def policy_template(cls):
+ """
+ Source policy template (can be ``None``).
+
+ :type: :class:`PolicyTemplate`
+ """
+ return relationship.many_to_one(cls, 'policy_template')
+
+ @declared_attr
+ def type(cls):
+ """
+ Group type.
+
+ :type: :class:`Type`
+ """
+ return relationship.many_to_one(cls, 'type', back_populates=relationship.NO_BACK_POP)
+
+ # endregion
+
+ # region many_to_many relationships
+
+ @declared_attr
+ def nodes(cls):
+ """
+ Policy is enacted on these nodes.
+
+ :type: {:obj:`basestring`: :class:`Node`}
+ """
+ return relationship.many_to_many(cls, 'node')
+
+ @declared_attr
+ def groups(cls):
+ """
+ Policy is enacted on nodes in these groups.
+
+ :type: {:obj:`basestring`: :class:`Group`}
+ """
+ return relationship.many_to_many(cls, 'group')
+
+ # endregion
+
+ # region foreign_keys
+
+ @declared_attr
+ def type_fk(cls):
+ """For Policy many-to-one to Type"""
+ return relationship.foreign_key('type')
+
+ @declared_attr
+ def service_fk(cls):
+ """For Service one-to-many to Policy"""
+ return relationship.foreign_key('service')
+
+ @declared_attr
+ def policy_template_fk(cls):
+ """For Policy many-to-one to PolicyTemplate"""
+ return relationship.foreign_key('policy_template', nullable=True)
+
+ # endregion
+
+ description = Column(Text, doc="""
+ Human-readable description.
+
+ :type: :obj:`basestring`
+ """)
+
+ @property
+ def as_raw(self):
+ return collections.OrderedDict((
+ ('name', self.name),
+ ('type_name', self.type.name),
+ ('properties', formatting.as_raw_dict(self.properties))))
+
+
+class SubstitutionBase(InstanceModelMixin):
+ """
+ Exposes the entire service as a single node.
+
+ Usually an instance of a :class:`SubstitutionTemplate`.
+ """
+
+ __tablename__ = 'substitution'
+
+ __private_fields__ = ('node_type_fk',
+ 'substitution_template_fk')
+
+ # region one_to_many relationships
+
+ @declared_attr
+ def mappings(cls):
+ """
+ Map requirement and capabilities to exposed node.
+
+ :type: {:obj:`basestring`: :class:`SubstitutionMapping`}
+ """
+ return relationship.one_to_many(cls, 'substitution_mapping', dict_key='name')
+
+ # endregion
+
+ # region many_to_one relationships
+
+ @declared_attr
+ def service(cls):
+ """
+ Containing service.
+
+ :type: :class:`Service`
+ """
+ return relationship.one_to_one(cls, 'service', back_populates=relationship.NO_BACK_POP)
+
+ @declared_attr
+ def substitution_template(cls):
+ """
+ Source substitution template (can be ``None``).
+
+ :type: :class:`SubstitutionTemplate`
+ """
+ return relationship.many_to_one(cls, 'substitution_template')
+
+ @declared_attr
+ def node_type(cls):
+ """
+ Exposed node type.
+
+ :type: :class:`Type`
+ """
+ return relationship.many_to_one(cls, 'type', back_populates=relationship.NO_BACK_POP)
+
+ # endregion
+
+ # region foreign_keys
+
+ @declared_attr
+ def node_type_fk(cls):
+ """For Substitution many-to-one to Type"""
+ return relationship.foreign_key('type')
+
+ @declared_attr
+ def substitution_template_fk(cls):
+ """For Substitution many-to-one to SubstitutionTemplate"""
+ return relationship.foreign_key('substitution_template', nullable=True)
+
+ # endregion
+
+ @property
+ def as_raw(self):
+ return collections.OrderedDict((
+ ('node_type_name', self.node_type.name),
+ ('mappings', formatting.as_raw_dict(self.mappings))))
+
+
+class SubstitutionMappingBase(InstanceModelMixin):
+ """
+ Used by :class:`Substitution` to map a capability or a requirement to the exposed node.
+
+ The :attr:`name` field should match the capability or requirement template name on the exposed
+ node's type.
+
+ Only one of :attr:`capability` and :attr:`requirement_template` can be set. If the latter is
+ set, then :attr:`node` must also be set.
+
+ Usually an instance of a :class:`SubstitutionMappingTemplate`.
+ """
+
+ __tablename__ = 'substitution_mapping'
+
+ __private_fields__ = ('substitution_fk',
+ 'node_fk',
+ 'capability_fk',
+ 'requirement_template_fk')
+
+ # region one_to_one relationships
+
+ @declared_attr
+ def capability(cls):
+ """
+ Capability to expose (can be ``None``).
+
+ :type: :class:`Capability`
+ """
+ return relationship.one_to_one(cls, 'capability', back_populates=relationship.NO_BACK_POP)
+
+ @declared_attr
+ def requirement_template(cls):
+ """
+ Requirement template to expose (can be ``None``).
+
+ :type: :class:`RequirementTemplate`
+ """
+ return relationship.one_to_one(cls, 'requirement_template',
+ back_populates=relationship.NO_BACK_POP)
+
+ @declared_attr
+ def node(cls):
+ """
+ Node for which to expose :attr:`requirement_template` (can be ``None``).
+
+ :type: :class:`Node`
+ """
+ return relationship.one_to_one(cls, 'node', back_populates=relationship.NO_BACK_POP)
+
+ # endregion
+
+ # region many_to_one relationships
+
+ @declared_attr
+ def substitution(cls):
+ """
+ Containing substitution.
+
+ :type: :class:`Substitution`
+ """
+ return relationship.many_to_one(cls, 'substitution', back_populates='mappings')
+
+ # endregion
+
+ # region foreign keys
+
+ @declared_attr
+ def substitution_fk(cls):
+ """For Substitution one-to-many to SubstitutionMapping"""
+ return relationship.foreign_key('substitution')
+
+ @declared_attr
+ def capability_fk(cls):
+ """For Substitution one-to-one to Capability"""
+ return relationship.foreign_key('capability', nullable=True)
+
+ @declared_attr
+ def node_fk(cls):
+ """For Substitution one-to-one to Node"""
+ return relationship.foreign_key('node', nullable=True)
+
+ @declared_attr
+ def requirement_template_fk(cls):
+ """For Substitution one-to-one to RequirementTemplate"""
+ return relationship.foreign_key('requirement_template', nullable=True)
+
+ # endregion
+
+ @property
+ def as_raw(self):
+ return collections.OrderedDict((
+ ('name', self.name),))
+
+
+class RelationshipBase(InstanceModelMixin):
+ """
+ Optionally-typed edge in the service topology, connecting a :class:`Node` to a
+ :class:`Capability` of another node.
+
+ Might be an instance of :class:`RelationshipTemplate` and/or :class:`RequirementTemplate`.
+ """
+
+ __tablename__ = 'relationship'
+
+ __private_fields__ = ('type_fk',
+ 'source_node_fk',
+ 'target_node_fk',
+ 'target_capability_fk',
+ 'requirement_template_fk',
+ 'relationship_template_fk',
+ 'target_position',
+ 'source_position')
+
+ # region one_to_one relationships
+
+ @declared_attr
+ def target_capability(cls):
+ """
+ Target capability.
+
+ :type: :class:`Capability`
+ """
+ return relationship.one_to_one(cls, 'capability', back_populates=relationship.NO_BACK_POP)
+
+ # endregion
+
+ # region one_to_many relationships
+
+ @declared_attr
+ def tasks(cls):
+ """
+ Associated tasks.
+
+ :type: [:class:`Task`]
+ """
+ return relationship.one_to_many(cls, 'task')
+
+ @declared_attr
+ def interfaces(cls):
+ """
+ Associated interfaces.
+
+ :type: {:obj:`basestring`: :class:`Interface`}
+ """
+ return relationship.one_to_many(cls, 'interface', dict_key='name')
+
+ @declared_attr
+ def properties(cls):
+ """
+ Associated immutable parameters.
+
+ :type: {:obj:`basestring`: :class:`Property`}
+ """
+ return relationship.one_to_many(cls, 'property', dict_key='name')
+
+ # endregion
+
+ # region many_to_one relationships
+
+ @declared_attr
+ def source_node(cls):
+ """
+ Source node.
+
+ :type: :class:`Node`
+ """
+ return relationship.many_to_one(
+ cls, 'node', fk='source_node_fk', back_populates='outbound_relationships')
+
+ @declared_attr
+ def target_node(cls):
+ """
+ Target node.
+
+ :type: :class:`Node`
+ """
+ return relationship.many_to_one(
+ cls, 'node', fk='target_node_fk', back_populates='inbound_relationships')
+
+ @declared_attr
+ def relationship_template(cls):
+ """
+ Source relationship template (can be ``None``).
+
+ :type: :class:`RelationshipTemplate`
+ """
+ return relationship.many_to_one(cls, 'relationship_template')
+
+ @declared_attr
+ def requirement_template(cls):
+ """
+ Source requirement template (can be ``None``).
+
+ :type: :class:`RequirementTemplate`
+ """
+ return relationship.many_to_one(cls, 'requirement_template')
+
+ @declared_attr
+ def type(cls):
+ """
+ Relationship type.
+
+ :type: :class:`Type`
+ """
+ return relationship.many_to_one(cls, 'type', back_populates=relationship.NO_BACK_POP)
+
+ # endregion
+
+ # region association proxies
+
+ @declared_attr
+ def source_node_name(cls):
+ return relationship.association_proxy('source_node', 'name')
+
+ @declared_attr
+ def target_node_name(cls):
+ return relationship.association_proxy('target_node', 'name')
+
+ # endregion
+
+ # region foreign keys
+
+ @declared_attr
+ def type_fk(cls):
+ """For Relationship many-to-one to Type"""
+ return relationship.foreign_key('type', nullable=True)
+
+ @declared_attr
+ def source_node_fk(cls):
+ """For Node one-to-many to Relationship"""
+ return relationship.foreign_key('node')
+
+ @declared_attr
+ def target_node_fk(cls):
+ """For Node one-to-many to Relationship"""
+ return relationship.foreign_key('node')
+
+ @declared_attr
+ def target_capability_fk(cls):
+ """For Relationship one-to-one to Capability"""
+ return relationship.foreign_key('capability', nullable=True)
+
+ @declared_attr
+ def requirement_template_fk(cls):
+ """For Relationship many-to-one to RequirementTemplate"""
+ return relationship.foreign_key('requirement_template', nullable=True)
+
+ @declared_attr
+ def relationship_template_fk(cls):
+ """For Relationship many-to-one to RelationshipTemplate"""
+ return relationship.foreign_key('relationship_template', nullable=True)
+
+ # endregion
+
+ source_position = Column(Integer, doc="""
+ Position at source.
+
+ :type: :obj:`int`
+ """)
+
+ target_position = Column(Integer, doc="""
+ Position at target.
+
+ :type: :obj:`int`
+ """)
+
+ @property
+ def as_raw(self):
+ return collections.OrderedDict((
+ ('name', self.name),
+ ('target_node_id', self.target_node.name),
+ ('type_name', self.type.name
+ if self.type is not None else None),
+ ('template_name', self.relationship_template.name
+ if self.relationship_template is not None else None),
+ ('properties', formatting.as_raw_dict(self.properties)),
+ ('interfaces', formatting.as_raw_list(self.interfaces))))
+
+
+class CapabilityBase(InstanceModelMixin):
+ """
+ Typed attachment serving two purposes: to provide extra properties and attributes to a
+ :class:`Node`, and to expose targets for :class:`Relationship` instances from other nodes.
+
+ Usually an instance of a :class:`CapabilityTemplate`.
+ """
+
+ __tablename__ = 'capability'
+
+ __private_fields__ = ('capability_fk',
+ 'node_fk',
+ 'capability_template_fk')
+
+ # region one_to_many relationships
+
+ @declared_attr
+ def properties(cls):
+ """
+ Associated immutable parameters.
+
+ :type: {:obj:`basestring`: :class:`Property`}
+ """
+ return relationship.one_to_many(cls, 'property', dict_key='name')
+
+ # endregion
+
+ # region many_to_one relationships
+
+ @declared_attr
+ def node(cls):
+ """
+ Containing node.
+
+ :type: :class:`Node`
+ """
+ return relationship.many_to_one(cls, 'node')
+
+ @declared_attr
+ def capability_template(cls):
+ """
+ Source capability template (can be ``None``).
+
+ :type: :class:`CapabilityTemplate`
+ """
+ return relationship.many_to_one(cls, 'capability_template')
+
+ @declared_attr
+ def type(cls):
+ """
+ Capability type.
+
+ :type: :class:`Type`
+ """
+ return relationship.many_to_one(cls, 'type', back_populates=relationship.NO_BACK_POP)
+
+ # endregion
+
+ # region foreign_keys
+
+ @declared_attr
+ def type_fk(cls):
+ """For Capability many-to-one to Type"""
+ return relationship.foreign_key('type')
+
+ @declared_attr
+ def node_fk(cls):
+ """For Node one-to-many to Capability"""
+ return relationship.foreign_key('node')
+
+ @declared_attr
+ def capability_template_fk(cls):
+ """For Capability many-to-one to CapabilityTemplate"""
+ return relationship.foreign_key('capability_template', nullable=True)
+
+ # endregion
+
+ min_occurrences = Column(Integer, default=None, doc="""
+ Minimum number of requirement matches required.
+
+ :type: :obj:`int`
+ """)
+
+ max_occurrences = Column(Integer, default=None, doc="""
+ Maximum number of requirement matches allowed.
+
+ :type: :obj:`int`
+ """)
+
+ occurrences = Column(Integer, default=0, doc="""
+ Number of requirement matches.
+
+ :type: :obj:`int`
+ """)
+
+ @property
+ def has_enough_relationships(self):
+ if self.min_occurrences is not None:
+ return self.occurrences >= self.min_occurrences
+ return True
+
+ def relate(self):
+ if self.max_occurrences is not None:
+ if self.occurrences == self.max_occurrences:
+ return False
+ self.occurrences += 1
+ return True
+
+ @property
+ def as_raw(self):
+ return collections.OrderedDict((
+ ('name', self.name),
+ ('type_name', self.type.name),
+ ('properties', formatting.as_raw_dict(self.properties))))
+
+
+class InterfaceBase(InstanceModelMixin):
+ """
+ Typed bundle of :class:`Operation` instances.
+
+ Can be associated with a :class:`Node`, a :class:`Group`, or a :class:`Relationship`.
+
+ Usually an instance of a :class:`InterfaceTemplate`.
+ """
+
+ __tablename__ = 'interface'
+
+ __private_fields__ = ('type_fk',
+ 'node_fk',
+ 'group_fk',
+ 'relationship_fk',
+ 'interface_template_fk')
+
+ # region one_to_many relationships
+
+ @declared_attr
+ def inputs(cls):
+ """
+ Parameters for all operations of the interface.
+
+ :type: {:obj:`basestring`: :class:`Input`}
+ """
+ return relationship.one_to_many(cls, 'input', dict_key='name')
+
+ @declared_attr
+ def operations(cls):
+ """
+ Associated operations.
+
+ :type: {:obj:`basestring`: :class:`Operation`}
+ """
+ return relationship.one_to_many(cls, 'operation', dict_key='name')
+
+ # endregion
+
+ # region many_to_one relationships
+
+ @declared_attr
+ def node(cls):
+ """
+ Containing node (can be ``None``).
+
+ :type: :class:`Node`
+ """
+ return relationship.many_to_one(cls, 'node')
+
+ @declared_attr
+ def group(cls):
+ """
+ Containing group (can be ``None``).
+
+ :type: :class:`Group`
+ """
+ return relationship.many_to_one(cls, 'group')
+
+ @declared_attr
+ def relationship(cls):
+ """
+ Containing relationship (can be ``None``).
+
+ :type: :class:`Relationship`
+ """
+ return relationship.many_to_one(cls, 'relationship')
+
+ @declared_attr
+ def interface_template(cls):
+ """
+ Source interface template (can be ``None``).
+
+ :type: :class:`InterfaceTemplate`
+ """
+ return relationship.many_to_one(cls, 'interface_template')
+
+ @declared_attr
+ def type(cls):
+ """
+ Interface type.
+
+ :type: :class:`Type`
+ """
+ return relationship.many_to_one(cls, 'type', back_populates=relationship.NO_BACK_POP)
+
+ # endregion
+
+ # region foreign_keys
+
+ @declared_attr
+ def type_fk(cls):
+ """For Interface many-to-one to Type"""
+ return relationship.foreign_key('type')
+
+ @declared_attr
+ def node_fk(cls):
+ """For Node one-to-many to Interface"""
+ return relationship.foreign_key('node', nullable=True)
+
+ @declared_attr
+ def group_fk(cls):
+ """For Group one-to-many to Interface"""
+ return relationship.foreign_key('group', nullable=True)
+
+ @declared_attr
+ def relationship_fk(cls):
+ """For Relationship one-to-many to Interface"""
+ return relationship.foreign_key('relationship', nullable=True)
+
+ @declared_attr
+ def interface_template_fk(cls):
+ """For Interface many-to-one to InterfaceTemplate"""
+ return relationship.foreign_key('interface_template', nullable=True)
+
+ # endregion
+
+ description = Column(Text, doc="""
+ Human-readable description.
+
+ :type: :obj:`basestring`
+ """)
+
+ @property
+ def as_raw(self):
+ return collections.OrderedDict((
+ ('name', self.name),
+ ('description', self.description),
+ ('type_name', self.type.name),
+ ('inputs', formatting.as_raw_dict(self.inputs)),
+ ('operations', formatting.as_raw_list(self.operations))))
+
+
+class OperationBase(InstanceModelMixin):
+ """
+ Entry points to Python functions called as part of a workflow execution.
+
+ The operation signature (its :attr:`name` and its :attr:`inputs`'s names and types) is declared
+ by the type of the :class:`Interface`, however each operation can provide its own
+ :attr:`implementation` as well as additional inputs.
+
+ The Python :attr:`function` is usually provided by an associated :class:`Plugin`. Its purpose is
+ to execute the implementation, providing it with both the operation's and interface's inputs.
+ The :attr:`arguments` of the function should be set according to the specific signature of the
+ function.
+
+ Additionally, :attr:`configuration` parameters can be provided as hints to configure the
+ function's behavior. For example, they can be used to configure remote execution credentials.
+
+ Might be an instance of :class:`OperationTemplate`.
+ """
+
+ __tablename__ = 'operation'
+
+ __private_fields__ = ('service_fk',
+ 'interface_fk',
+ 'plugin_fk',
+ 'operation_template_fk')
+
+ # region one_to_one relationships
+
+ @declared_attr
+ def plugin(cls):
+ """
+ Associated plugin.
+
+ :type: :class:`Plugin`
+ """
+ return relationship.one_to_one(cls, 'plugin', back_populates=relationship.NO_BACK_POP)
+
+ # endregion
+
+ # region one_to_many relationships
+
+ @declared_attr
+ def inputs(cls):
+ """
+ Parameters provided to the :attr:`implementation`.
+
+ :type: {:obj:`basestring`: :class:`Input`}
+ """
+ return relationship.one_to_many(cls, 'input', dict_key='name')
+
+ @declared_attr
+ def arguments(cls):
+ """
+ Arguments sent to the Python :attr:`function`.
+
+ :type: {:obj:`basestring`: :class:`Argument`}
+ """
+ return relationship.one_to_many(cls, 'argument', dict_key='name')
+
+ @declared_attr
+ def configurations(cls):
+ """
+ Configuration parameters for the Python :attr:`function`.
+
+ :type: {:obj:`basestring`: :class:`Configuration`}
+ """
+ return relationship.one_to_many(cls, 'configuration', dict_key='name')
+
+ # endregion
+
+ # region many_to_one relationships
+
+ @declared_attr
+ def service(cls):
+ """
+ Containing service (can be ``None``). For workflow operations.
+
+ :type: :class:`Service`
+ """
+ return relationship.many_to_one(cls, 'service', back_populates='workflows')
+
+ @declared_attr
+ def interface(cls):
+ """
+ Containing interface (can be ``None``).
+
+ :type: :class:`Interface`
+ """
+ return relationship.many_to_one(cls, 'interface')
+
+ @declared_attr
+ def operation_template(cls):
+ """
+ Source operation template (can be ``None``).
+
+ :type: :class:`OperationTemplate`
+ """
+ return relationship.many_to_one(cls, 'operation_template')
+
+ # endregion
+
+ # region foreign_keys
+
+ @declared_attr
+ def service_fk(cls):
+ """For Service one-to-many to Operation"""
+ return relationship.foreign_key('service', nullable=True)
+
+ @declared_attr
+ def interface_fk(cls):
+ """For Interface one-to-many to Operation"""
+ return relationship.foreign_key('interface', nullable=True)
+
+ @declared_attr
+ def plugin_fk(cls):
+ """For Operation one-to-one to Plugin"""
+ return relationship.foreign_key('plugin', nullable=True)
+
+ @declared_attr
+ def operation_template_fk(cls):
+ """For Operation many-to-one to OperationTemplate"""
+ return relationship.foreign_key('operation_template', nullable=True)
+
+ # endregion
+
+ description = Column(Text, doc="""
+ Human-readable description.
+
+ :type: :obj:`basestring`
+ """)
+
+ relationship_edge = Column(Boolean, doc="""
+ When ``True`` specifies that the operation is on the relationship's target edge; ``False`` is
+ the source edge (only used by operations on relationships)
+
+ :type: :obj:`bool`
+ """)
+
+ implementation = Column(Text, doc="""
+ Implementation (usually the name of an artifact).
+
+ :type: :obj:`basestring`
+ """)
+
+ dependencies = Column(modeling_types.StrictList(item_cls=basestring), doc="""
+ Dependencies (usually names of artifacts).
+
+ :type: [:obj:`basestring`]
+ """)
+
+ function = Column(Text, doc="""
+ Full path to Python function.
+
+ :type: :obj:`basestring`
+ """)
+
+ executor = Column(Text, doc="""
+ Name of executor.
+
+ :type: :obj:`basestring`
+ """)
+
+ max_attempts = Column(Integer, doc="""
+ Maximum number of attempts allowed in case of task failure.
+
+ :type: :obj:`int`
+ """)
+
+ retry_interval = Column(Integer, doc="""
+ Interval between task retry attempts (in seconds).
+
+ :type: :obj:`float`
+ """)
+
+ @property
+ def as_raw(self):
+ return collections.OrderedDict((
+ ('name', self.name),
+ ('description', self.description),
+ ('implementation', self.implementation),
+ ('dependencies', self.dependencies),
+ ('inputs', formatting.as_raw_dict(self.inputs))))
+
+
+class ArtifactBase(InstanceModelMixin):
+ """
+ Typed file, either provided in a CSAR or downloaded from a repository.
+
+ Usually an instance of :class:`ArtifactTemplate`.
+ """
+
+ __tablename__ = 'artifact'
+
+ __private_fields__ = ('type_fk',
+ 'node_fk',
+ 'artifact_template_fk')
+
+ # region one_to_many relationships
+
+ @declared_attr
+ def properties(cls):
+ """
+ Associated immutable parameters.
+
+ :type: {:obj:`basestring`: :class:`Property`}
+ """
+ return relationship.one_to_many(cls, 'property', dict_key='name')
+
+ # endregion
+
+ # region many_to_one relationships
+
+ @declared_attr
+ def node(cls):
+ """
+ Containing node.
+
+ :type: :class:`Node`
+ """
+ return relationship.many_to_one(cls, 'node')
+
+ @declared_attr
+ def artifact_template(cls):
+ """
+ Source artifact template (can be ``None``).
+
+ :type: :class:`ArtifactTemplate`
+ """
+ return relationship.many_to_one(cls, 'artifact_template')
+
+ @declared_attr
+ def type(cls):
+ """
+ Artifact type.
+
+ :type: :class:`Type`
+ """
+ return relationship.many_to_one(cls, 'type', back_populates=relationship.NO_BACK_POP)
+
+ # endregion
+
+ # region foreign_keys
+
+ @declared_attr
+ def type_fk(cls):
+ """For Artifact many-to-one to Type"""
+ return relationship.foreign_key('type')
+
+ @declared_attr
+ def node_fk(cls):
+ """For Node one-to-many to Artifact"""
+ return relationship.foreign_key('node')
+
+ @declared_attr
+ def artifact_template_fk(cls):
+ """For Artifact many-to-one to ArtifactTemplate"""
+ return relationship.foreign_key('artifact_template', nullable=True)
+
+ # endregion
+
+ description = Column(Text, doc="""
+ Human-readable description.
+
+ :type: :obj:`basestring`
+ """)
+
+ source_path = Column(Text, doc="""
+ Source path (in CSAR or repository).
+
+ :type: :obj:`basestring`
+ """)
+
+ target_path = Column(Text, doc="""
+ Path at which to install at destination.
+
+ :type: :obj:`basestring`
+ """)
+
+ repository_url = Column(Text, doc="""
+ Repository URL.
+
+ :type: :obj:`basestring`
+ """)
+
+ repository_credential = Column(modeling_types.StrictDict(basestring, basestring), doc="""
+ Credentials for accessing the repository.
+
+ :type: {:obj:`basestring`, :obj:`basestring`}
+ """)
+
+ @property
+ def as_raw(self):
+ return collections.OrderedDict((
+ ('name', self.name),
+ ('description', self.description),
+ ('type_name', self.type.name),
+ ('source_path', self.source_path),
+ ('target_path', self.target_path),
+ ('repository_url', self.repository_url),
+ ('repository_credential', formatting.as_agnostic(self.repository_credential)),
+ ('properties', formatting.as_raw_dict(self.properties))))
diff --git a/azure/aria/aria-extension-cloudify/src/aria/aria/modeling/service_template.py b/azure/aria/aria-extension-cloudify/src/aria/aria/modeling/service_template.py
new file mode 100644
index 0000000..cd0adb4
--- /dev/null
+++ b/azure/aria/aria-extension-cloudify/src/aria/aria/modeling/service_template.py
@@ -0,0 +1,1758 @@
+# 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.
+
+"""
+ARIA modeling service template module
+"""
+
+# pylint: disable=too-many-lines, no-self-argument, no-member, abstract-method
+
+from __future__ import absolute_import # so we can import standard 'types'
+
+from sqlalchemy import (
+ Column,
+ Text,
+ Integer,
+ Boolean,
+ DateTime,
+ PickleType
+)
+from sqlalchemy.ext.declarative import declared_attr
+
+from ..utils import (collections, formatting)
+from .mixins import TemplateModelMixin
+from . import (
+ relationship,
+ types as modeling_types
+)
+
+
+class ServiceTemplateBase(TemplateModelMixin):
+ """
+ Template for creating :class:`Service` instances.
+
+ Usually created by various DSL parsers, such as ARIA's TOSCA extension. However, it can also be
+ created programmatically.
+ """
+
+ __tablename__ = 'service_template'
+
+ __private_fields__ = ('substitution_template_fk',
+ 'node_type_fk',
+ 'group_type_fk',
+ 'policy_type_fk',
+ 'relationship_type_fk',
+ 'capability_type_fk',
+ 'interface_type_fk',
+ 'artifact_type_fk')
+
+ # region one_to_one relationships
+
+ @declared_attr
+ def substitution_template(cls):
+ """
+ Exposes an entire service as a single node.
+
+ :type: :class:`SubstitutionTemplate`
+ """
+ return relationship.one_to_one(
+ cls, 'substitution_template', back_populates=relationship.NO_BACK_POP)
+
+ @declared_attr
+ def node_types(cls):
+ """
+ Base for the node type hierarchy,
+
+ :type: :class:`Type`
+ """
+ return relationship.one_to_one(
+ cls, 'type', fk='node_type_fk', back_populates=relationship.NO_BACK_POP)
+
+ @declared_attr
+ def group_types(cls):
+ """
+ Base for the group type hierarchy,
+
+ :type: :class:`Type`
+ """
+ return relationship.one_to_one(
+ cls, 'type', fk='group_type_fk', back_populates=relationship.NO_BACK_POP)
+
+ @declared_attr
+ def policy_types(cls):
+ """
+ Base for the policy type hierarchy,
+
+ :type: :class:`Type`
+ """
+ return relationship.one_to_one(
+ cls, 'type', fk='policy_type_fk', back_populates=relationship.NO_BACK_POP)
+
+ @declared_attr
+ def relationship_types(cls):
+ """
+ Base for the relationship type hierarchy,
+
+ :type: :class:`Type`
+ """
+ return relationship.one_to_one(
+ cls, 'type', fk='relationship_type_fk', back_populates=relationship.NO_BACK_POP)
+
+ @declared_attr
+ def capability_types(cls):
+ """
+ Base for the capability type hierarchy,
+
+ :type: :class:`Type`
+ """
+ return relationship.one_to_one(
+ cls, 'type', fk='capability_type_fk', back_populates=relationship.NO_BACK_POP)
+
+ @declared_attr
+ def interface_types(cls):
+ """
+ Base for the interface type hierarchy,
+
+ :type: :class:`Type`
+ """
+ return relationship.one_to_one(
+ cls, 'type', fk='interface_type_fk', back_populates=relationship.NO_BACK_POP)
+
+ @declared_attr
+ def artifact_types(cls):
+ """
+ Base for the artifact type hierarchy,
+
+ :type: :class:`Type`
+ """
+ return relationship.one_to_one(
+ cls, 'type', fk='artifact_type_fk', back_populates=relationship.NO_BACK_POP)
+
+ # endregion
+
+ # region one_to_many relationships
+
+ @declared_attr
+ def services(cls):
+ """
+ Instantiated services.
+
+ :type: [:class:`Service`]
+ """
+ return relationship.one_to_many(cls, 'service', dict_key='name')
+
+ @declared_attr
+ def node_templates(cls):
+ """
+ Templates for creating nodes.
+
+ :type: {:obj:`basestring`, :class:`NodeTemplate`}
+ """
+ return relationship.one_to_many(cls, 'node_template', dict_key='name')
+
+ @declared_attr
+ def group_templates(cls):
+ """
+ Templates for creating groups.
+
+ :type: {:obj:`basestring`, :class:`GroupTemplate`}
+ """
+ return relationship.one_to_many(cls, 'group_template', dict_key='name')
+
+ @declared_attr
+ def policy_templates(cls):
+ """
+ Templates for creating policies.
+
+ :type: {:obj:`basestring`, :class:`PolicyTemplate`}
+ """
+ return relationship.one_to_many(cls, 'policy_template', dict_key='name')
+
+ @declared_attr
+ def workflow_templates(cls):
+ """
+ Templates for creating workflows.
+
+ :type: {:obj:`basestring`, :class:`OperationTemplate`}
+ """
+ return relationship.one_to_many(cls, 'operation_template', dict_key='name')
+
+ @declared_attr
+ def outputs(cls):
+ """
+ Declarations for output parameters are filled in after service installation.
+
+ :type: {:obj:`basestring`: :class:`Output`}
+ """
+ return relationship.one_to_many(cls, 'output', dict_key='name')
+
+ @declared_attr
+ def inputs(cls):
+ """
+ Declarations for externally provided parameters.
+
+ :type: {:obj:`basestring`: :class:`Input`}
+ """
+ return relationship.one_to_many(cls, 'input', dict_key='name')
+
+ @declared_attr
+ def plugin_specifications(cls):
+ """
+ Required plugins for instantiated services.
+
+ :type: {:obj:`basestring`: :class:`PluginSpecification`}
+ """
+ return relationship.one_to_many(cls, 'plugin_specification', dict_key='name')
+
+ # endregion
+
+ # region many_to_many relationships
+
+ @declared_attr
+ def meta_data(cls):
+ """
+ Associated metadata.
+
+ :type: {:obj:`basestring`: :class:`Metadata`}
+ """
+ # Warning! We cannot use the attr name "metadata" because it's used by SQLAlchemy!
+ return relationship.many_to_many(cls, 'metadata', dict_key='name')
+
+ # endregion
+
+ # region foreign keys
+
+ @declared_attr
+ def substitution_template_fk(cls):
+ """For ServiceTemplate one-to-one to SubstitutionTemplate"""
+ return relationship.foreign_key('substitution_template', nullable=True)
+
+ @declared_attr
+ def node_type_fk(cls):
+ """For ServiceTemplate one-to-one to Type"""
+ return relationship.foreign_key('type', nullable=True)
+
+ @declared_attr
+ def group_type_fk(cls):
+ """For ServiceTemplate one-to-one to Type"""
+ return relationship.foreign_key('type', nullable=True)
+
+ @declared_attr
+ def policy_type_fk(cls):
+ """For ServiceTemplate one-to-one to Type"""
+ return relationship.foreign_key('type', nullable=True)
+
+ @declared_attr
+ def relationship_type_fk(cls):
+ """For ServiceTemplate one-to-one to Type"""
+ return relationship.foreign_key('type', nullable=True)
+
+ @declared_attr
+ def capability_type_fk(cls):
+ """For ServiceTemplate one-to-one to Type"""
+ return relationship.foreign_key('type', nullable=True)
+
+ @declared_attr
+ def interface_type_fk(cls):
+ """For ServiceTemplate one-to-one to Type"""
+ return relationship.foreign_key('type', nullable=True)
+
+ @declared_attr
+ def artifact_type_fk(cls):
+ """For ServiceTemplate one-to-one to Type"""
+ return relationship.foreign_key('type', nullable=True)
+
+ # endregion
+
+ description = Column(Text, doc="""
+ Human-readable description.
+
+ :type: :obj:`basestring`
+ """)
+
+ main_file_name = Column(Text, doc="""
+ Filename of CSAR or YAML file from which this service template was parsed.
+
+ :type: :obj:`basestring`
+ """)
+
+ created_at = Column(DateTime, nullable=False, index=True, doc="""
+ Creation timestamp.
+
+ :type: :class:`~datetime.datetime`
+ """)
+
+ updated_at = Column(DateTime, doc="""
+ Update timestamp.
+
+ :type: :class:`~datetime.datetime`
+ """)
+
+ @property
+ def as_raw(self):
+ return collections.OrderedDict((
+ ('description', self.description),
+ ('metadata', formatting.as_raw_dict(self.meta_data)),
+ ('node_templates', formatting.as_raw_list(self.node_templates)),
+ ('group_templates', formatting.as_raw_list(self.group_templates)),
+ ('policy_templates', formatting.as_raw_list(self.policy_templates)),
+ ('substitution_template', formatting.as_raw(self.substitution_template)),
+ ('inputs', formatting.as_raw_dict(self.inputs)),
+ ('outputs', formatting.as_raw_dict(self.outputs)),
+ ('workflow_templates', formatting.as_raw_list(self.workflow_templates))))
+
+ @property
+ def types_as_raw(self):
+ return collections.OrderedDict((
+ ('node_types', formatting.as_raw(self.node_types)),
+ ('group_types', formatting.as_raw(self.group_types)),
+ ('policy_types', formatting.as_raw(self.policy_types)),
+ ('relationship_types', formatting.as_raw(self.relationship_types)),
+ ('capability_types', formatting.as_raw(self.capability_types)),
+ ('interface_types', formatting.as_raw(self.interface_types)),
+ ('artifact_types', formatting.as_raw(self.artifact_types))))
+
+
+class NodeTemplateBase(TemplateModelMixin):
+ """
+ Template for creating zero or more :class:`Node` instances, which are typed vertices in the
+ service topology.
+ """
+
+ __tablename__ = 'node_template'
+
+ __private_fields__ = ('type_fk',
+ 'service_template_fk')
+
+ # region one_to_many relationships
+
+ @declared_attr
+ def nodes(cls):
+ """
+ Instantiated nodes.
+
+ :type: [:class:`Node`]
+ """
+ return relationship.one_to_many(cls, 'node')
+
+ @declared_attr
+ def interface_templates(cls):
+ """
+ Associated interface templates.
+
+ :type: {:obj:`basestring`: :class:`InterfaceTemplate`}
+ """
+ return relationship.one_to_many(cls, 'interface_template', dict_key='name')
+
+ @declared_attr
+ def artifact_templates(cls):
+ """
+ Associated artifacts.
+
+ :type: {:obj:`basestring`: :class:`ArtifactTemplate`}
+ """
+ return relationship.one_to_many(cls, 'artifact_template', dict_key='name')
+
+ @declared_attr
+ def capability_templates(cls):
+ """
+ Associated exposed capability templates.
+
+ :type: {:obj:`basestring`: :class:`CapabilityTemplate`}
+ """
+ return relationship.one_to_many(cls, 'capability_template', dict_key='name')
+
+ @declared_attr
+ def requirement_templates(cls):
+ """
+ Associated potential relationships with other nodes.
+
+ :type: [:class:`RequirementTemplate`]
+ """
+ return relationship.one_to_many(cls, 'requirement_template', other_fk='node_template_fk')
+
+ @declared_attr
+ def properties(cls):
+ """
+ Declarations for associated immutable parameters.
+
+ :type: {:obj:`basestring`: :class:`Property`}
+ """
+ return relationship.one_to_many(cls, 'property', dict_key='name')
+
+ @declared_attr
+ def attributes(cls):
+ """
+ Declarations for associated mutable parameters.
+
+ :type: {:obj:`basestring`: :class:`Attribute`}
+ """
+ return relationship.one_to_many(cls, 'attribute', dict_key='name')
+
+ # endregion
+
+ # region many_to_one relationships
+
+ @declared_attr
+ def type(cls):
+ """
+ Node type.
+
+ :type: :class:`Type`
+ """
+ return relationship.many_to_one(cls, 'type', back_populates=relationship.NO_BACK_POP)
+
+ @declared_attr
+ def service_template(cls):
+ """
+ Containing service template.
+
+ :type: :class:`ServiceTemplate`
+ """
+ return relationship.many_to_one(cls, 'service_template')
+
+ # endregion
+
+ # region association proxies
+
+ @declared_attr
+ def service_template_name(cls):
+ return relationship.association_proxy('service_template', 'name')
+
+ @declared_attr
+ def type_name(cls):
+ return relationship.association_proxy('type', 'name')
+
+ # endregion
+
+ # region foreign_keys
+
+ @declared_attr
+ def type_fk(cls):
+ """For NodeTemplate many-to-one to Type"""
+ return relationship.foreign_key('type')
+
+ @declared_attr
+ def service_template_fk(cls):
+ """For ServiceTemplate one-to-many to NodeTemplate"""
+ return relationship.foreign_key('service_template')
+
+ # endregion
+
+ description = Column(Text, doc="""
+ Human-readable description.
+
+ :type: :obj:`basestring`
+ """)
+
+ directives = Column(PickleType, doc="""
+ Directives that apply to this node template.
+
+ :type: [:obj:`basestring`]
+ """)
+
+ default_instances = Column(Integer, default=1, doc="""
+ Default number nodes that will appear in the service.
+
+ :type: :obj:`int`
+ """)
+
+ min_instances = Column(Integer, default=0, doc="""
+ Minimum number nodes that will appear in the service.
+
+ :type: :obj:`int`
+ """)
+
+ max_instances = Column(Integer, default=None, doc="""
+ Maximum number nodes that will appear in the service.
+
+ :type: :obj:`int`
+ """)
+
+ target_node_template_constraints = Column(PickleType, doc="""
+ Constraints for filtering relationship targets.
+
+ :type: [:class:`NodeTemplateConstraint`]
+ """)
+
+ @property
+ def as_raw(self):
+ return collections.OrderedDict((
+ ('name', self.name),
+ ('description', self.description),
+ ('type_name', self.type.name),
+ ('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)),
+ ('requirement_templates', formatting.as_raw_list(self.requirement_templates))))
+
+ def is_target_node_template_valid(self, target_node_template):
+ """
+ Checks if ``target_node_template`` matches all our ``target_node_template_constraints``.
+ """
+
+ if self.target_node_template_constraints:
+ for node_template_constraint in self.target_node_template_constraints:
+ if not node_template_constraint.matches(self, target_node_template):
+ return False
+ return True
+
+ @property
+ def _next_index(self):
+ """
+ Next available node index.
+
+ :returns: node index
+ :rtype: int
+ """
+
+ max_index = 0
+ if self.nodes:
+ max_index = max(int(n.name.rsplit('_', 1)[-1]) for n in self.nodes)
+ return max_index + 1
+
+ @property
+ def _next_name(self):
+ """
+ Next available node name.
+
+ :returns: node name
+ :rtype: basestring
+ """
+
+ return '{name}_{index}'.format(name=self.name, index=self._next_index)
+
+ @property
+ def scaling(self):
+ scaling = {}
+
+ def extract_property(properties, name):
+ if name in scaling:
+ return
+ prop = properties.get(name)
+ if (prop is not None) and (prop.type_name == 'integer') and (prop.value is not None):
+ scaling[name] = prop.value
+
+ def extract_properties(properties):
+ extract_property(properties, 'min_instances')
+ extract_property(properties, 'max_instances')
+ extract_property(properties, 'default_instances')
+
+ # From our scaling capabilities
+ for capability_template in self.capability_templates.itervalues():
+ if capability_template.type.role == 'scaling':
+ extract_properties(capability_template.properties)
+
+ # From service scaling policies
+ for policy_template in self.service_template.policy_templates.itervalues():
+ if policy_template.type.role == 'scaling':
+ if policy_template.is_for_node_template(self.name):
+ extract_properties(policy_template.properties)
+
+ # Defaults
+ scaling.setdefault('min_instances', 0)
+ scaling.setdefault('max_instances', 1)
+ scaling.setdefault('default_instances', 1)
+
+ return scaling
+
+
+class GroupTemplateBase(TemplateModelMixin):
+ """
+ Template for creating a :class:`Group` instance, which is a typed logical container for zero or
+ more :class:`Node` instances.
+ """
+
+ __tablename__ = 'group_template'
+
+ __private_fields__ = ('type_fk',
+ 'service_template_fk')
+
+ # region one_to_many relationships
+
+ @declared_attr
+ def groups(cls):
+ """
+ Instantiated groups.
+
+ :type: [:class:`Group`]
+ """
+ return relationship.one_to_many(cls, 'group')
+
+ @declared_attr
+ def interface_templates(cls):
+ """
+ Associated interface templates.
+
+ :type: {:obj:`basestring`: :class:`InterfaceTemplate`}
+ """
+ return relationship.one_to_many(cls, 'interface_template', dict_key='name')
+
+ @declared_attr
+ def properties(cls):
+ """
+ Declarations for associated immutable parameters.
+
+ :type: {:obj:`basestring`: :class:`Property`}
+ """
+ return relationship.one_to_many(cls, 'property', dict_key='name')
+
+ # endregion
+
+ # region many_to_one relationships
+
+ @declared_attr
+ def service_template(cls):
+ """
+ Containing service template.
+
+ :type: :class:`ServiceTemplate`
+ """
+ return relationship.many_to_one(cls, 'service_template')
+
+ @declared_attr
+ def type(cls):
+ """
+ Group type.
+
+ :type: :class:`Type`
+ """
+ return relationship.many_to_one(cls, 'type', back_populates=relationship.NO_BACK_POP)
+
+ # endregion
+
+ # region many_to_many relationships
+
+ @declared_attr
+ def node_templates(cls):
+ """
+ Nodes instantiated by these templates will be members of the group.
+
+ :type: [:class:`NodeTemplate`]
+ """
+ return relationship.many_to_many(cls, 'node_template')
+
+ # endregion
+
+ # region foreign keys
+
+ @declared_attr
+ def type_fk(cls):
+ """For GroupTemplate many-to-one to Type"""
+ return relationship.foreign_key('type')
+
+ @declared_attr
+ def service_template_fk(cls):
+ """For ServiceTemplate one-to-many to GroupTemplate"""
+ return relationship.foreign_key('service_template')
+
+ # endregion
+
+ description = Column(Text, doc="""
+ Human-readable description.
+
+ :type: :obj:`basestring`
+ """)
+
+ @property
+ def as_raw(self):
+ return collections.OrderedDict((
+ ('name', self.name),
+ ('description', self.description),
+ ('type_name', self.type.name),
+ ('properties', formatting.as_raw_dict(self.properties)),
+ ('interface_templates', formatting.as_raw_list(self.interface_templates))))
+
+ def contains_node_template(self, name):
+ for node_template in self.node_templates:
+ if node_template.name == name:
+ return True
+ return False
+
+
+class PolicyTemplateBase(TemplateModelMixin):
+ """
+ Template for creating a :class:`Policy` instance, which is a typed set of orchestration hints
+ applied to zero or more :class:`Node` or :class:`Group` instances.
+ """
+
+ __tablename__ = 'policy_template'
+
+ __private_fields__ = ('type_fk',
+ 'service_template_fk')
+
+ # region one_to_many relationships
+
+ @declared_attr
+ def policies(cls):
+ """
+ Instantiated policies.
+
+ :type: [:class:`Policy`]
+ """
+ return relationship.one_to_many(cls, 'policy')
+
+ @declared_attr
+ def properties(cls):
+ """
+ Declarations for associated immutable parameters.
+
+ :type: {:obj:`basestring`: :class:`Property`}
+ """
+ return relationship.one_to_many(cls, 'property', dict_key='name')
+
+ # endregion
+
+ # region many_to_one relationships
+
+ @declared_attr
+ def service_template(cls):
+ """
+ Containing service template.
+
+ :type: :class:`ServiceTemplate`
+ """
+ return relationship.many_to_one(cls, 'service_template')
+
+ @declared_attr
+ def type(cls):
+ """
+ Policy type.
+
+ :type: :class:`Type`
+ """
+ return relationship.many_to_one(cls, 'type', back_populates=relationship.NO_BACK_POP)
+
+ # endregion
+
+ # region many_to_many relationships
+
+ @declared_attr
+ def node_templates(cls):
+ """
+ Policy will be enacted on all nodes instantiated by these templates.
+
+ :type: {:obj:`basestring`: :class:`NodeTemplate`}
+ """
+ return relationship.many_to_many(cls, 'node_template')
+
+ @declared_attr
+ def group_templates(cls):
+ """
+ Policy will be enacted on all nodes in all groups instantiated by these templates.
+
+ :type: {:obj:`basestring`: :class:`GroupTemplate`}
+ """
+ return relationship.many_to_many(cls, 'group_template')
+
+ # endregion
+
+ # region foreign keys
+
+ @declared_attr
+ def type_fk(cls):
+ """For PolicyTemplate many-to-one to Type"""
+ return relationship.foreign_key('type')
+
+ @declared_attr
+ def service_template_fk(cls):
+ """For ServiceTemplate one-to-many to PolicyTemplate"""
+ return relationship.foreign_key('service_template')
+
+ # endregion
+
+ description = Column(Text, doc="""
+ Human-readable description.
+
+ :type: :obj:`basestring`
+ """)
+
+ @property
+ def as_raw(self):
+ return collections.OrderedDict((
+ ('name', self.name),
+ ('description', self.description),
+ ('type_name', self.type.name),
+ ('properties', formatting.as_raw_dict(self.properties))))
+
+ def is_for_node_template(self, name):
+ for node_template in self.node_templates:
+ if node_template.name == name:
+ return True
+ for group_template in self.group_templates:
+ if group_template.contains_node_template(name):
+ return True
+ return False
+
+ def is_for_group_template(self, name):
+ for group_template in self.group_templates:
+ if group_template.name == name:
+ return True
+ return False
+
+
+class SubstitutionTemplateBase(TemplateModelMixin):
+ """
+ Template for creating a :class:`Substitution` instance, which exposes an entire instantiated
+ service as a single node.
+ """
+
+ __tablename__ = 'substitution_template'
+
+ __private_fields__ = ('node_type_fk',)
+
+ # region one_to_many relationships
+
+ @declared_attr
+ def substitutions(cls):
+ """
+ Instantiated substitutions.
+
+ :type: [:class:`Substitution`]
+ """
+ return relationship.one_to_many(cls, 'substitution')
+
+ @declared_attr
+ def mappings(cls):
+ """
+ Map requirement and capabilities to exposed node.
+
+ :type: {:obj:`basestring`: :class:`SubstitutionTemplateMapping`}
+ """
+ return relationship.one_to_many(cls, 'substitution_template_mapping', dict_key='name')
+
+ # endregion
+
+ # region many_to_one relationships
+
+ @declared_attr
+ def node_type(cls):
+ """
+ Exposed node type.
+
+ :type: :class:`Type`
+ """
+ return relationship.many_to_one(cls, 'type', back_populates=relationship.NO_BACK_POP)
+
+ # endregion
+
+ # region foreign keys
+
+ @declared_attr
+ def node_type_fk(cls):
+ """For SubstitutionTemplate many-to-one to Type"""
+ return relationship.foreign_key('type')
+
+ # endregion
+
+ @property
+ def as_raw(self):
+ return collections.OrderedDict((
+ ('node_type_name', self.node_type.name),
+ ('mappings', formatting.as_raw_dict(self.mappings))))
+
+
+class SubstitutionTemplateMappingBase(TemplateModelMixin):
+ """
+ Used by :class:`SubstitutionTemplate` to map a capability template or a requirement template to
+ the exposed node.
+
+ The :attr:`name` field should match the capability or requirement name on the exposed node's
+ type.
+
+ Only one of :attr:`capability_template` and :attr:`requirement_template` can be set.
+ """
+
+ __tablename__ = 'substitution_template_mapping'
+
+ __private_fields__ = ('substitution_template_fk',
+ 'capability_template_fk',
+ 'requirement_template_fk')
+
+ # region one_to_one relationships
+
+ @declared_attr
+ def capability_template(cls):
+ """
+ Capability template to expose (can be ``None``).
+
+ :type: :class:`CapabilityTemplate`
+ """
+ return relationship.one_to_one(
+ cls, 'capability_template', back_populates=relationship.NO_BACK_POP)
+
+ @declared_attr
+ def requirement_template(cls):
+ """
+ Requirement template to expose (can be ``None``).
+
+ :type: :class:`RequirementTemplate`
+ """
+ return relationship.one_to_one(
+ cls, 'requirement_template', back_populates=relationship.NO_BACK_POP)
+
+ # endregion
+
+ # region many_to_one relationships
+
+ @declared_attr
+ def substitution_template(cls):
+ """
+ Containing substitution template.
+
+ :type: :class:`SubstitutionTemplate`
+ """
+ return relationship.many_to_one(cls, 'substitution_template', back_populates='mappings')
+
+ # endregion
+
+ # region foreign keys
+
+ @declared_attr
+ def substitution_template_fk(cls):
+ """For SubstitutionTemplate one-to-many to SubstitutionTemplateMapping"""
+ return relationship.foreign_key('substitution_template')
+
+ @declared_attr
+ def capability_template_fk(cls):
+ """For SubstitutionTemplate one-to-one to CapabilityTemplate"""
+ return relationship.foreign_key('capability_template', nullable=True)
+
+ @declared_attr
+ def requirement_template_fk(cls):
+ """For SubstitutionTemplate one-to-one to RequirementTemplate"""
+ return relationship.foreign_key('requirement_template', nullable=True)
+
+ # endregion
+
+ @property
+ def as_raw(self):
+ return collections.OrderedDict((
+ ('name', self.name),))
+
+
+class RequirementTemplateBase(TemplateModelMixin):
+ """
+ Template for creating :class:`Relationship` instances, which are optionally-typed edges in the
+ service topology, connecting a :class:`Node` to a :class:`Capability` of another node.
+
+ Note that there is no equivalent "Requirement" instance model. Instead, during instantiation a
+ requirement template is matched with a capability and a :class:`Relationship` is instantiated.
+
+ A requirement template *must* target a :class:`CapabilityType` or a capability name. It can
+ optionally target a specific :class:`NodeType` or :class:`NodeTemplate`.
+
+ Requirement templates may optionally contain a :class:`RelationshipTemplate`. If they do not,
+ a :class:`Relationship` will be instantiated with default values.
+ """
+
+ __tablename__ = 'requirement_template'
+
+ __private_fields__ = ('target_capability_type_fk',
+ 'target_node_template_fk',
+ 'target_node_type_fk',
+ 'relationship_template_fk',
+ 'node_template_fk')
+
+ # region one_to_one relationships
+
+ @declared_attr
+ def target_capability_type(cls):
+ """
+ Target capability type.
+
+ :type: :class:`CapabilityType`
+ """
+ return relationship.one_to_one(cls,
+ 'type',
+ fk='target_capability_type_fk',
+ back_populates=relationship.NO_BACK_POP)
+
+ @declared_attr
+ def target_node_template(cls):
+ """
+ Target node template (can be ``None``).
+
+ :type: :class:`NodeTemplate`
+ """
+ return relationship.one_to_one(cls,
+ 'node_template',
+ fk='target_node_template_fk',
+ back_populates=relationship.NO_BACK_POP)
+
+ @declared_attr
+ def relationship_template(cls):
+ """
+ Associated relationship template (can be ``None``).
+
+ :type: :class:`RelationshipTemplate`
+ """
+ return relationship.one_to_one(cls, 'relationship_template')
+
+ # endregion
+
+ # region one_to_many relationships
+
+ @declared_attr
+ def relationships(cls):
+ """
+ Instantiated relationships.
+
+ :type: [:class:`Relationship`]
+ """
+ return relationship.one_to_many(cls, 'relationship')
+
+ # endregion
+
+ # region many_to_one relationships
+
+ @declared_attr
+ def node_template(cls):
+ """
+ Containing node template.
+
+ :type: :class:`NodeTemplate`
+ """
+ return relationship.many_to_one(cls, 'node_template', fk='node_template_fk')
+
+ @declared_attr
+ def target_node_type(cls):
+ """
+ Target node type (can be ``None``).
+
+ :type: :class:`Type`
+ """
+ return relationship.many_to_one(
+ cls, 'type', fk='target_node_type_fk', back_populates=relationship.NO_BACK_POP)
+
+ # endregion
+
+ # region foreign keys
+
+ @declared_attr
+ def target_node_type_fk(cls):
+ """For RequirementTemplate many-to-one to Type"""
+ return relationship.foreign_key('type', nullable=True)
+
+ @declared_attr
+ def target_node_template_fk(cls):
+ """For RequirementTemplate one-to-one to NodeTemplate"""
+ return relationship.foreign_key('node_template', nullable=True)
+
+ @declared_attr
+ def target_capability_type_fk(cls):
+ """For RequirementTemplate one-to-one to Type"""
+ return relationship.foreign_key('type', nullable=True)
+
+ @declared_attr
+ def node_template_fk(cls):
+ """For NodeTemplate one-to-many to RequirementTemplate"""
+ return relationship.foreign_key('node_template')
+
+ @declared_attr
+ def relationship_template_fk(cls):
+ """For RequirementTemplate one-to-one to RelationshipTemplate"""
+ return relationship.foreign_key('relationship_template', nullable=True)
+
+ # endregion
+
+ target_capability_name = Column(Text, doc="""
+ Target capability name in node template or node type (can be ``None``).
+
+ :type: :obj:`basestring`
+ """)
+
+ target_node_template_constraints = Column(PickleType, doc="""
+ Constraints for filtering relationship targets.
+
+ :type: [:class:`NodeTemplateConstraint`]
+ """)
+
+ @property
+ def as_raw(self):
+ return collections.OrderedDict((
+ ('name', self.name),
+ ('target_node_type_name', self.target_node_type.name
+ if self.target_node_type is not None else None),
+ ('target_node_template_name', self.target_node_template.name
+ if self.target_node_template is not None else None),
+ ('target_capability_type_name', self.target_capability_type.name
+ if self.target_capability_type is not None else None),
+ ('target_capability_name', self.target_capability_name),
+ ('relationship_template', formatting.as_raw(self.relationship_template))))
+
+
+class RelationshipTemplateBase(TemplateModelMixin):
+ """
+ Optional addition to a :class:`RequirementTemplate`.
+
+ Note that a relationship template here is not exactly equivalent to a relationship template
+ entity in TOSCA. For example, a TOSCA requirement specifying a relationship type rather than a
+ relationship template would still be represented here as a relationship template.
+ """
+
+ __tablename__ = 'relationship_template'
+
+ __private_fields__ = ('type_fk',)
+
+ # region one_to_many relationships
+
+ @declared_attr
+ def relationships(cls):
+ """
+ Instantiated relationships.
+
+ :type: [:class:`Relationship`]
+ """
+ return relationship.one_to_many(cls, 'relationship')
+
+ @declared_attr
+ def interface_templates(cls):
+ """
+ Associated interface templates.
+
+ :type: {:obj:`basestring`: :class:`InterfaceTemplate`}
+ """
+ return relationship.one_to_many(cls, 'interface_template', dict_key='name')
+
+ @declared_attr
+ def properties(cls):
+ """
+ Declarations for associated immutable parameters.
+
+ :type: {:obj:`basestring`: :class:`Property`}
+ """
+ return relationship.one_to_many(cls, 'property', dict_key='name')
+
+ # endregion
+
+ # region many_to_one relationships
+
+ @declared_attr
+ def type(cls):
+ """
+ Relationship type.
+
+ :type: :class:`Type`
+ """
+ return relationship.many_to_one(cls, 'type', back_populates=relationship.NO_BACK_POP)
+
+ # endregion
+
+ # region foreign keys
+
+ @declared_attr
+ def type_fk(cls):
+ """For RelationshipTemplate many-to-one to Type"""
+ return relationship.foreign_key('type', nullable=True)
+
+ # endregion
+
+ description = Column(Text, doc="""
+ Human-readable description.
+
+ :type: :obj:`basestring`
+ """)
+
+ @property
+ def as_raw(self):
+ return collections.OrderedDict((
+ ('type_name', self.type.name if self.type is not None else None),
+ ('name', self.name),
+ ('description', self.description),
+ ('properties', formatting.as_raw_dict(self.properties)),
+ ('interface_templates', formatting.as_raw_list(self.interface_templates))))
+
+
+class CapabilityTemplateBase(TemplateModelMixin):
+ """
+ Template for creating :class:`Capability` instances, typed attachments which serve two purposes:
+ to provide extra properties and attributes to :class:`Node` instances, and to expose targets for
+ :class:`Relationship` instances from other nodes.
+ """
+
+ __tablename__ = 'capability_template'
+
+ __private_fields__ = ('type_fk',
+ 'node_template_fk')
+
+ # region one_to_many relationships
+
+ @declared_attr
+ def capabilities(cls):
+ """
+ Instantiated capabilities.
+
+ :type: [:class:`Capability`]
+ """
+ return relationship.one_to_many(cls, 'capability')
+
+ @declared_attr
+ def properties(cls):
+ """
+ Declarations for associated immutable parameters.
+
+ :type: {:obj:`basestring`: :class:`Property`}
+ """
+ return relationship.one_to_many(cls, 'property', dict_key='name')
+
+ # endregion
+
+ # region many_to_one relationships
+
+ @declared_attr
+ def node_template(cls):
+ """
+ Containing node template.
+
+ :type: :class:`NodeTemplate`
+ """
+ return relationship.many_to_one(cls, 'node_template')
+
+ @declared_attr
+ def type(cls):
+ """
+ Capability type.
+
+ :type: :class:`Type`
+ """
+ return relationship.many_to_one(cls, 'type', back_populates=relationship.NO_BACK_POP)
+
+ # endregion
+
+ # region many_to_many relationships
+
+ @declared_attr
+ def valid_source_node_types(cls):
+ """
+ Reject requirements that are not from these node types.
+
+ :type: [:class:`Type`]
+ """
+ return relationship.many_to_many(cls, 'type', prefix='valid_sources')
+
+ # endregion
+
+ # region foreign keys
+
+ @declared_attr
+ def type_fk(cls):
+ """For CapabilityTemplate many-to-one to Type"""
+ return relationship.foreign_key('type')
+
+ @declared_attr
+ def node_template_fk(cls):
+ """For NodeTemplate one-to-many to CapabilityTemplate"""
+ return relationship.foreign_key('node_template')
+
+ # endregion
+
+ description = Column(Text, doc="""
+ Human-readable description.
+
+ :type: :obj:`basestring`
+ """)
+
+ min_occurrences = Column(Integer, default=None, doc="""
+ Minimum number of requirement matches required.
+
+ :type: :obj:`int`
+ """)
+
+ max_occurrences = Column(Integer, default=None, doc="""
+ Maximum number of requirement matches allowed.
+
+ :type: :obj:`int`
+ """)
+
+ @property
+ def as_raw(self):
+ return collections.OrderedDict((
+ ('name', self.name),
+ ('description', self.description),
+ ('type_name', self.type.name),
+ ('min_occurrences', self.min_occurrences),
+ ('max_occurrences', self.max_occurrences),
+ ('valid_source_node_types', [v.name for v in self.valid_source_node_types]),
+ ('properties', formatting.as_raw_dict(self.properties))))
+
+
+class InterfaceTemplateBase(TemplateModelMixin):
+ """
+ Template for creating :class:`Interface` instances, which are typed bundles of
+ :class:`Operation` instances.
+
+ Can be associated with a :class:`NodeTemplate`, a :class:`GroupTemplate`, or a
+ :class:`RelationshipTemplate`.
+ """
+
+ __tablename__ = 'interface_template'
+
+ __private_fields__ = ('type_fk',
+ 'node_template_fk',
+ 'group_template_fk',
+ 'relationship_template_fk')
+
+ # region one_to_many relationships
+
+ @declared_attr
+ def inputs(cls):
+ """
+ Declarations for externally provided parameters that can be used by all operations of the
+ interface.
+
+ :type: {:obj:`basestring`: :class:`Input`}
+ """
+ return relationship.one_to_many(cls, 'input', dict_key='name')
+
+ @declared_attr
+ def interfaces(cls):
+ """
+ Instantiated interfaces.
+
+ :type: [:class:`Interface`]
+ """
+ return relationship.one_to_many(cls, 'interface')
+
+ @declared_attr
+ def operation_templates(cls):
+ """
+ Associated operation templates.
+
+ :type: {:obj:`basestring`: :class:`OperationTemplate`}
+ """
+ return relationship.one_to_many(cls, 'operation_template', dict_key='name')
+
+ # endregion
+
+ # region many_to_one relationships
+
+ @declared_attr
+ def node_template(cls):
+ """
+ Containing node template (can be ``None``).
+
+ :type: :class:`NodeTemplate`
+ """
+ return relationship.many_to_one(cls, 'node_template')
+
+ @declared_attr
+ def group_template(cls):
+ """
+ Containing group template (can be ``None``).
+
+ :type: :class:`GroupTemplate`
+ """
+ return relationship.many_to_one(cls, 'group_template')
+
+ @declared_attr
+ def relationship_template(cls):
+ """
+ Containing relationship template (can be ``None``).
+
+ :type: :class:`RelationshipTemplate`
+ """
+ return relationship.many_to_one(cls, 'relationship_template')
+
+ @declared_attr
+ def type(cls):
+ """
+ Interface type.
+
+ :type: :class:`Type`
+ """
+ return relationship.many_to_one(cls, 'type', back_populates=relationship.NO_BACK_POP)
+
+ # endregion
+
+ # region foreign keys
+
+ @declared_attr
+ def type_fk(cls):
+ """For InterfaceTemplate many-to-one to Type"""
+ return relationship.foreign_key('type')
+
+ @declared_attr
+ def node_template_fk(cls):
+ """For NodeTemplate one-to-many to InterfaceTemplate"""
+ return relationship.foreign_key('node_template', nullable=True)
+
+ @declared_attr
+ def group_template_fk(cls):
+ """For GroupTemplate one-to-many to InterfaceTemplate"""
+ return relationship.foreign_key('group_template', nullable=True)
+
+ @declared_attr
+ def relationship_template_fk(cls):
+ """For RelationshipTemplate one-to-many to InterfaceTemplate"""
+ return relationship.foreign_key('relationship_template', nullable=True)
+
+ # endregion
+
+ description = Column(Text, doc="""
+ Human-readable description.
+
+ :type: :obj:`basestring`
+ """)
+
+ @property
+ def as_raw(self):
+ return collections.OrderedDict((
+ ('name', self.name),
+ ('description', self.description),
+ ('type_name', self.type.name),
+ ('inputs', formatting.as_raw_dict(self.inputs)), # pylint: disable=no-member
+ # TODO fix self.properties reference
+ ('operation_templates', formatting.as_raw_list(self.operation_templates))))
+
+
+class OperationTemplateBase(TemplateModelMixin):
+ """
+ Template for creating :class:`Operation` instances, which are entry points to Python functions
+ called as part of a workflow execution.
+ """
+
+ __tablename__ = 'operation_template'
+
+ __private_fields__ = ('service_template_fk',
+ 'interface_template_fk',
+ 'plugin_fk')
+
+ # region one_to_one relationships
+
+ @declared_attr
+ def plugin_specification(cls):
+ """
+ Associated plugin specification.
+
+ :type: :class:`PluginSpecification`
+ """
+ return relationship.one_to_one(
+ cls, 'plugin_specification', back_populates=relationship.NO_BACK_POP)
+
+ # endregion
+
+ # region one_to_many relationships
+
+ @declared_attr
+ def operations(cls):
+ """
+ Instantiated operations.
+
+ :type: [:class:`Operation`]
+ """
+ return relationship.one_to_many(cls, 'operation')
+
+ @declared_attr
+ def inputs(cls):
+ """
+ Declarations for parameters provided to the :attr:`implementation`.
+
+ :type: {:obj:`basestring`: :class:`Input`}
+ """
+ return relationship.one_to_many(cls, 'input', dict_key='name')
+
+ @declared_attr
+ def configurations(cls):
+ """
+ Configuration parameters for the operation instance Python :attr:`function`.
+
+ :type: {:obj:`basestring`: :class:`Configuration`}
+ """
+ return relationship.one_to_many(cls, 'configuration', dict_key='name')
+
+ # endregion
+
+ # region many_to_one relationships
+
+ @declared_attr
+ def service_template(cls):
+ """
+ Containing service template (can be ``None``). For workflow operation templates.
+
+ :type: :class:`ServiceTemplate`
+ """
+ return relationship.many_to_one(cls, 'service_template',
+ back_populates='workflow_templates')
+
+ @declared_attr
+ def interface_template(cls):
+ """
+ Containing interface template (can be ``None``).
+
+ :type: :class:`InterfaceTemplate`
+ """
+ return relationship.many_to_one(cls, 'interface_template')
+
+ # endregion
+
+ # region foreign keys
+
+ @declared_attr
+ def service_template_fk(cls):
+ """For ServiceTemplate one-to-many to OperationTemplate"""
+ return relationship.foreign_key('service_template', nullable=True)
+
+ @declared_attr
+ def interface_template_fk(cls):
+ """For InterfaceTemplate one-to-many to OperationTemplate"""
+ return relationship.foreign_key('interface_template', nullable=True)
+
+ @declared_attr
+ def plugin_specification_fk(cls):
+ """For OperationTemplate one-to-one to PluginSpecification"""
+ return relationship.foreign_key('plugin_specification', nullable=True)
+
+ # endregion
+
+ description = Column(Text, doc="""
+ Human-readable description.
+
+ :type: :obj:`basestring`
+ """)
+
+ relationship_edge = Column(Boolean, doc="""
+ When ``True`` specifies that the operation is on the relationship's target edge; ``False`` is
+ the source edge (only used by operations on relationships)
+
+ :type: :obj:`bool`
+ """)
+
+ implementation = Column(Text, doc="""
+ Implementation (usually the name of an artifact).
+
+ :type: :obj:`basestring`
+ """)
+
+ dependencies = Column(modeling_types.StrictList(item_cls=basestring), doc="""
+ Dependencies (usually names of artifacts).
+
+ :type: [:obj:`basestring`]
+ """)
+
+ function = Column(Text, doc="""
+ Full path to Python function.
+
+ :type: :obj:`basestring`
+ """)
+
+ executor = Column(Text, doc="""
+ Name of executor.
+
+ :type: :obj:`basestring`
+ """)
+
+ max_attempts = Column(Integer, doc="""
+ Maximum number of attempts allowed in case of task failure.
+
+ :type: :obj:`int`
+ """)
+
+ retry_interval = Column(Integer, doc="""
+ Interval between task retry attemps (in seconds).
+
+ :type: :obj:`float`
+ """)
+
+ @property
+ def as_raw(self):
+ return collections.OrderedDict((
+ ('name', self.name),
+ ('description', self.description),
+ ('implementation', self.implementation),
+ ('dependencies', self.dependencies),
+ ('inputs', formatting.as_raw_dict(self.inputs))))
+
+
+class ArtifactTemplateBase(TemplateModelMixin):
+ """
+ Template for creating an :class:`Artifact` instance, which is a typed file, either provided in a
+ CSAR or downloaded from a repository.
+ """
+
+ __tablename__ = 'artifact_template'
+
+ __private_fields__ = ('type_fk',
+ 'node_template_fk')
+
+ # region one_to_many relationships
+
+ @declared_attr
+ def artifacts(cls):
+ """
+ Instantiated artifacts.
+
+ :type: [:class:`Artifact`]
+ """
+ return relationship.one_to_many(cls, 'artifact')
+
+ @declared_attr
+ def properties(cls):
+ """
+ Declarations for associated immutable parameters.
+
+ :type: {:obj:`basestring`: :class:`Property`}
+ """
+ return relationship.one_to_many(cls, 'property', dict_key='name')
+
+ # endregion
+
+ # region many_to_one relationships
+
+ @declared_attr
+ def node_template(cls):
+ """
+ Containing node template.
+
+ :type: :class:`NodeTemplate`
+ """
+ return relationship.many_to_one(cls, 'node_template')
+
+ @declared_attr
+ def type(cls):
+ """
+ Artifact type.
+
+ :type: :class:`Type`
+ """
+ return relationship.many_to_one(cls, 'type', back_populates=relationship.NO_BACK_POP)
+
+ # endregion
+
+ # region foreign keys
+
+ @declared_attr
+ def type_fk(cls):
+ """For ArtifactTemplate many-to-one to Type"""
+ return relationship.foreign_key('type')
+
+ @declared_attr
+ def node_template_fk(cls):
+ """For NodeTemplate one-to-many to ArtifactTemplate"""
+ return relationship.foreign_key('node_template')
+
+ # endregion
+
+ description = Column(Text, doc="""
+ Human-readable description.
+
+ :type: :obj:`basestring`
+ """)
+
+ source_path = Column(Text, doc="""
+ Source path (in CSAR or repository).
+
+ :type: :obj:`basestring`
+ """)
+
+ target_path = Column(Text, doc="""
+ Path at which to install at destination.
+
+ :type: :obj:`basestring`
+ """)
+
+ repository_url = Column(Text, doc="""
+ Repository URL.
+
+ :type: :obj:`basestring`
+ """)
+
+ repository_credential = Column(modeling_types.StrictDict(basestring, basestring), doc="""
+ Credentials for accessing the repository.
+
+ :type: {:obj:`basestring`, :obj:`basestring`}
+ """)
+
+ @property
+ def as_raw(self):
+ return collections.OrderedDict((
+ ('name', self.name),
+ ('description', self.description),
+ ('type_name', self.type.name),
+ ('source_path', self.source_path),
+ ('target_path', self.target_path),
+ ('repository_url', self.repository_url),
+ ('repository_credential', formatting.as_agnostic(self.repository_credential)),
+ ('properties', formatting.as_raw_dict(self.properties))))
+
+
+class PluginSpecificationBase(TemplateModelMixin):
+ """
+ Requirement for a :class:`Plugin`.
+
+ The actual plugin to be selected depends on those currently installed in ARIA.
+ """
+
+ __tablename__ = 'plugin_specification'
+
+ __private_fields__ = ('service_template_fk',
+ 'plugin_fk')
+
+ # region many_to_one relationships
+
+ @declared_attr
+ def service_template(cls):
+ """
+ Containing service template.
+
+ :type: :class:`ServiceTemplate`
+ """
+ return relationship.many_to_one(cls, 'service_template')
+
+ @declared_attr
+ def plugin(cls): # pylint: disable=method-hidden
+ """
+ Matched plugin.
+
+ :type: :class:`Plugin`
+ """
+ return relationship.many_to_one(cls, 'plugin', back_populates=relationship.NO_BACK_POP)
+
+ # endregion
+
+ # region foreign keys
+
+ @declared_attr
+ def service_template_fk(cls):
+ """For ServiceTemplate one-to-many to PluginSpecification"""
+ return relationship.foreign_key('service_template', nullable=True)
+
+ @declared_attr
+ def plugin_fk(cls):
+ """For PluginSpecification many-to-one to Plugin"""
+ return relationship.foreign_key('plugin', nullable=True)
+
+ # endregion
+
+ version = Column(Text, doc="""
+ Minimum plugin version.
+
+ :type: :obj:`basestring`
+ """)
+
+ enabled = Column(Boolean, nullable=False, default=True, doc="""
+ Whether the plugin is enabled.
+
+ :type: :obj:`bool`
+ """)
+
+ @property
+ def as_raw(self):
+ return collections.OrderedDict((
+ ('name', self.name),
+ ('version', self.version),
+ ('enabled', self.enabled)))
diff --git a/azure/aria/aria-extension-cloudify/src/aria/aria/modeling/types.py b/azure/aria/aria-extension-cloudify/src/aria/aria/modeling/types.py
new file mode 100644
index 0000000..38240fa
--- /dev/null
+++ b/azure/aria/aria-extension-cloudify/src/aria/aria/modeling/types.py
@@ -0,0 +1,318 @@
+# 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.
+
+"""
+Allows JSON-serializable collections to be used as SQLAlchemy column types.
+"""
+
+import json
+from collections import namedtuple
+
+from sqlalchemy import (
+ TypeDecorator,
+ VARCHAR,
+ event
+)
+from sqlalchemy.ext import mutable
+from ruamel import yaml
+
+from . import exceptions
+
+
+class _MutableType(TypeDecorator):
+ """
+ Dict representation of type.
+ """
+ @property
+ def python_type(self):
+ raise NotImplementedError
+
+ def process_literal_param(self, value, dialect):
+ pass
+
+ impl = VARCHAR
+
+ def process_bind_param(self, value, dialect):
+ if value is not None:
+ value = json.dumps(value)
+ return value
+
+ def process_result_value(self, value, dialect):
+ if value is not None:
+ value = json.loads(value)
+ return value
+
+
+class Dict(_MutableType):
+ """
+ JSON-serializable dict type for SQLAlchemy columns.
+ """
+ @property
+ def python_type(self):
+ return dict
+
+
+class List(_MutableType):
+ """
+ JSON-serializable list type for SQLAlchemy columns.
+ """
+ @property
+ def python_type(self):
+ return list
+
+
+class _StrictDictMixin(object):
+
+ @classmethod
+ def coerce(cls, key, value):
+ """
+ Convert plain dictionaries to MutableDict.
+ """
+ try:
+ if not isinstance(value, cls):
+ if isinstance(value, dict):
+ for k, v in value.items():
+ cls._assert_strict_key(k)
+ cls._assert_strict_value(v)
+ return cls(value)
+ return mutable.MutableDict.coerce(key, value)
+ else:
+ return value
+ except ValueError as e:
+ raise exceptions.ValueFormatException('could not coerce to MutableDict', cause=e)
+
+ def __setitem__(self, key, value):
+ self._assert_strict_key(key)
+ self._assert_strict_value(value)
+ super(_StrictDictMixin, self).__setitem__(key, value)
+
+ def setdefault(self, key, value):
+ self._assert_strict_key(key)
+ self._assert_strict_value(value)
+ super(_StrictDictMixin, self).setdefault(key, value)
+
+ def update(self, *args, **kwargs):
+ for k, v in kwargs.items():
+ self._assert_strict_key(k)
+ self._assert_strict_value(v)
+ super(_StrictDictMixin, self).update(*args, **kwargs)
+
+ @classmethod
+ def _assert_strict_key(cls, key):
+ if cls._key_cls is not None and not isinstance(key, cls._key_cls):
+ raise exceptions.ValueFormatException('key type was set strictly to {0}, but was {1}'
+ .format(cls._key_cls, type(key)))
+
+ @classmethod
+ def _assert_strict_value(cls, value):
+ if cls._value_cls is not None and not isinstance(value, cls._value_cls):
+ raise exceptions.ValueFormatException('value type was set strictly to {0}, but was {1}'
+ .format(cls._value_cls, type(value)))
+
+
+class _MutableDict(mutable.MutableDict):
+ """
+ Enables tracking for dict values.
+ """
+
+ @classmethod
+ def coerce(cls, key, value):
+ """
+ Convert plain dictionaries to MutableDict.
+ """
+ try:
+ return mutable.MutableDict.coerce(key, value)
+ except ValueError as e:
+ raise exceptions.ValueFormatException('could not coerce value', cause=e)
+
+
+class _StrictListMixin(object):
+
+ @classmethod
+ def coerce(cls, key, value):
+ "Convert plain dictionaries to MutableDict."
+ try:
+ if not isinstance(value, cls):
+ if isinstance(value, list):
+ for item in value:
+ cls._assert_item(item)
+ return cls(value)
+ return mutable.MutableList.coerce(key, value)
+ else:
+ return value
+ except ValueError as e:
+ raise exceptions.ValueFormatException('could not coerce to MutableDict', cause=e)
+
+ def __setitem__(self, index, value):
+ """
+ Detect list set events and emit change events.
+ """
+ self._assert_item(value)
+ super(_StrictListMixin, self).__setitem__(index, value)
+
+ def append(self, item):
+ self._assert_item(item)
+ super(_StrictListMixin, self).append(item)
+
+ def extend(self, item):
+ self._assert_item(item)
+ super(_StrictListMixin, self).extend(item)
+
+ def insert(self, index, item):
+ self._assert_item(item)
+ super(_StrictListMixin, self).insert(index, item)
+
+ @classmethod
+ def _assert_item(cls, item):
+ if cls._item_cls is not None and not isinstance(item, cls._item_cls):
+ raise exceptions.ValueFormatException('key type was set strictly to {0}, but was {1}'
+ .format(cls._item_cls, type(item)))
+
+
+class _MutableList(mutable.MutableList):
+
+ @classmethod
+ def coerce(cls, key, value):
+ """
+ Convert plain dictionaries to MutableDict.
+ """
+ try:
+ return mutable.MutableList.coerce(key, value)
+ except ValueError as e:
+ raise exceptions.ValueFormatException('could not coerce to MutableDict', cause=e)
+
+
+_StrictDictID = namedtuple('_StrictDictID', 'key_cls, value_cls')
+_StrictValue = namedtuple('_StrictValue', 'type_cls, listener_cls')
+
+class _StrictDict(object):
+ """
+ This entire class functions as a factory for strict dicts and their listeners. No type class,
+ and no listener type class is created more than once. If a relevant type class exists it is
+ returned.
+ """
+ _strict_map = {}
+
+ def __call__(self, key_cls=None, value_cls=None):
+ strict_dict_map_key = _StrictDictID(key_cls=key_cls, value_cls=value_cls)
+ if strict_dict_map_key not in self._strict_map:
+ key_cls_name = getattr(key_cls, '__name__', str(key_cls))
+ value_cls_name = getattr(value_cls, '__name__', str(value_cls))
+ # Creating the type class itself. this class would be returned (used by the SQLAlchemy
+ # Column).
+ strict_dict_cls = type(
+ 'StrictDict_{0}_{1}'.format(key_cls_name, value_cls_name),
+ (Dict, ),
+ {}
+ )
+ # Creating the type listening class.
+ # The new class inherits from both the _MutableDict class and the _StrictDictMixin,
+ # while setting the necessary _key_cls and _value_cls as class attributes.
+ listener_cls = type(
+ 'StrictMutableDict_{0}_{1}'.format(key_cls_name, value_cls_name),
+ (_StrictDictMixin, _MutableDict),
+ {'_key_cls': key_cls, '_value_cls': value_cls}
+ )
+ yaml.representer.RoundTripRepresenter.add_representer(
+ listener_cls, yaml.representer.RoundTripRepresenter.represent_list)
+ self._strict_map[strict_dict_map_key] = _StrictValue(type_cls=strict_dict_cls,
+ listener_cls=listener_cls)
+
+ return self._strict_map[strict_dict_map_key].type_cls
+
+
+StrictDict = _StrictDict()
+"""
+JSON-serializable strict dict type for SQLAlchemy columns.
+
+:param key_cls:
+:param value_cls:
+"""
+
+
+class _StrictList(object):
+ """
+ This entire class functions as a factory for strict lists and their listeners. No type class,
+ and no listener type class is created more than once. If a relevant type class exists it is
+ returned.
+ """
+ _strict_map = {}
+
+ def __call__(self, item_cls=None):
+
+ if item_cls not in self._strict_map:
+ item_cls_name = getattr(item_cls, '__name__', str(item_cls))
+ # Creating the type class itself. this class would be returned (used by the SQLAlchemy
+ # Column).
+ strict_list_cls = type(
+ 'StrictList_{0}'.format(item_cls_name),
+ (List, ),
+ {}
+ )
+ # Creating the type listening class.
+ # The new class inherits from both the _MutableList class and the _StrictListMixin,
+ # while setting the necessary _item_cls as class attribute.
+ listener_cls = type(
+ 'StrictMutableList_{0}'.format(item_cls_name),
+ (_StrictListMixin, _MutableList),
+ {'_item_cls': item_cls}
+ )
+ yaml.representer.RoundTripRepresenter.add_representer(
+ listener_cls, yaml.representer.RoundTripRepresenter.represent_list)
+ self._strict_map[item_cls] = _StrictValue(type_cls=strict_list_cls,
+ listener_cls=listener_cls)
+
+ return self._strict_map[item_cls].type_cls
+
+
+StrictList = _StrictList()
+"""
+JSON-serializable strict list type for SQLAlchemy columns.
+
+:param item_cls:
+"""
+
+
+def _mutable_association_listener(mapper, cls):
+ strict_dict_type_to_listener = \
+ dict((v.type_cls, v.listener_cls) for v in _StrictDict._strict_map.itervalues())
+
+ strict_list_type_to_listener = \
+ dict((v.type_cls, v.listener_cls) for v in _StrictList._strict_map.itervalues())
+
+ for prop in mapper.column_attrs:
+ column_type = prop.columns[0].type
+ # Dict Listeners
+ if type(column_type) in strict_dict_type_to_listener: # pylint: disable=unidiomatic-typecheck
+ strict_dict_type_to_listener[type(column_type)].associate_with_attribute(
+ getattr(cls, prop.key))
+ elif isinstance(column_type, Dict):
+ _MutableDict.associate_with_attribute(getattr(cls, prop.key))
+
+ # List Listeners
+ if type(column_type) in strict_list_type_to_listener: # pylint: disable=unidiomatic-typecheck
+ strict_list_type_to_listener[type(column_type)].associate_with_attribute(
+ getattr(cls, prop.key))
+ elif isinstance(column_type, List):
+ _MutableList.associate_with_attribute(getattr(cls, prop.key))
+
+
+_LISTENER_ARGS = (mutable.mapper, 'mapper_configured', _mutable_association_listener)
+
+
+def _register_mutable_association_listener():
+ event.listen(*_LISTENER_ARGS)
+
+_register_mutable_association_listener()
diff --git a/azure/aria/aria-extension-cloudify/src/aria/aria/modeling/utils.py b/azure/aria/aria-extension-cloudify/src/aria/aria/modeling/utils.py
new file mode 100644
index 0000000..491b71a
--- /dev/null
+++ b/azure/aria/aria-extension-cloudify/src/aria/aria/modeling/utils.py
@@ -0,0 +1,185 @@
+# 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.
+
+"""
+Miscellaneous modeling utilities.
+"""
+
+import os
+from json import JSONEncoder
+from StringIO import StringIO
+
+from . import exceptions
+from ..utils.type import validate_value_type
+from ..utils.collections import OrderedDict
+from ..utils.formatting import string_list_as_string
+
+
+class ModelJSONEncoder(JSONEncoder):
+ """
+ JSON encoder that automatically unwraps ``value`` attributes.
+ """
+ def __init__(self, *args, **kwargs):
+ # Just here to make sure Sphinx doesn't grab the base constructor's docstring
+ super(ModelJSONEncoder, self).__init__(*args, **kwargs)
+
+ def default(self, o): # pylint: disable=method-hidden
+ from .mixins import ModelMixin
+ if isinstance(o, ModelMixin):
+ if hasattr(o, 'value'):
+ dict_to_return = o.to_dict(fields=('value',))
+ return dict_to_return['value']
+ else:
+ return o.to_dict()
+ else:
+ return JSONEncoder.default(self, o)
+
+
+class NodeTemplateContainerHolder(object):
+ """
+ Wrapper that allows using a :class:`~aria.modeling.models.NodeTemplate` model directly as the
+ ``container_holder`` input 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 validate_no_undeclared_inputs(declared_inputs, supplied_inputs):
+#
+# undeclared_inputs = [input for input in supplied_inputs if input not in declared_inputs]
+# if undeclared_inputs:
+# raise exceptions.UndeclaredInputsException(
+# 'Undeclared inputs have been provided: {0}; Declared inputs: {1}'
+# .format(string_list_as_string(undeclared_inputs),
+# string_list_as_string(declared_inputs.keys())))
+
+
+def validate_required_inputs_are_supplied(declared_inputs, supplied_inputs):
+ required_inputs = [input for input in declared_inputs.values() if input.required]
+ missing_required_inputs = [input for input in required_inputs
+ if input.name not in supplied_inputs and not str(input.value)]
+ if missing_required_inputs:
+ raise exceptions.MissingRequiredInputsException(
+ 'Required inputs {0} have not been provided values'
+ .format(string_list_as_string(missing_required_inputs)))
+
+
+def merge_parameter_values(provided_values, declared_parameters, model_cls=None):
+ """
+ Merges parameter values according to those declared by a type.
+
+ Exceptions will be raised for validation errors.
+
+ :param provided_values: provided parameter values or None
+ :type provided_values: {:obj:`basestring`: object}
+ :param declared_parameters: declared parameters
+ :type declared_parameters: {:obj:`basestring`: :class:`~aria.modeling.models.Parameter`}
+ :param model_cls: the model class that should be created from a provided value
+ :type model_cls: :class:`~aria.modeling.models.Input` or :class:`~aria.modeling.models.Argument`
+ :return: the merged parameters
+ :rtype: {:obj:`basestring`: :class:`~aria.modeling.models.Parameter`}
+ :raises ~aria.modeling.exceptions.UndeclaredInputsException: if a key in
+ ``parameter_values`` does not exist in ``declared_parameters``
+ :raises ~aria.modeling.exceptions.MissingRequiredInputsException: if a key in
+ ``declared_parameters`` does not exist in ``parameter_values`` and also has no default value
+ :raises ~aria.modeling.exceptions.ParametersOfWrongTypeException: if a value in
+ ``parameter_values`` does not match its type in ``declared_parameters``
+ """
+
+ provided_values = provided_values or {}
+ provided_values_of_wrong_type = OrderedDict()
+ model_parameters = OrderedDict()
+ model_cls = model_cls or _get_class_from_sql_relationship(declared_parameters)
+
+ for declared_parameter_name, declared_parameter in declared_parameters.iteritems():
+ if declared_parameter_name in provided_values:
+ # a value has been provided
+ value = provided_values[declared_parameter_name]
+
+ # Validate type
+ type_name = declared_parameter.type_name
+ try:
+ validate_value_type(value, type_name)
+ except ValueError:
+ provided_values_of_wrong_type[declared_parameter_name] = type_name
+ except RuntimeError:
+ # TODO This error shouldn't be raised (or caught), but right now we lack support
+ # for custom data_types, which will raise this error. Skipping their validation.
+ pass
+ model_parameters[declared_parameter_name] = model_cls( # pylint: disable=unexpected-keyword-arg
+ name=declared_parameter_name,
+ type_name=type_name,
+ description=declared_parameter.description,
+ value=value)
+ else:
+ # Copy default value from declaration
+ model_parameters[declared_parameter_name] = model_cls(
+ value=declared_parameter._value,
+ name=declared_parameter.name,
+ type_name=declared_parameter.type_name,
+ description=declared_parameter.description)
+
+ if provided_values_of_wrong_type:
+ error_message = StringIO()
+ for param_name, param_type in provided_values_of_wrong_type.iteritems():
+ error_message.write('Parameter "{0}" is not of declared type "{1}"{2}'
+ .format(param_name, param_type, os.linesep))
+ raise exceptions.ParametersOfWrongTypeException(error_message.getvalue())
+
+ return model_parameters
+
+
+def parameters_as_values(the_dict):
+ return dict((k, v.value) for k, v in the_dict.iteritems())
+
+
+def dict_as_arguments(the_dict):
+ return OrderedDict((name, value.as_argument()) for name, value in the_dict.iteritems())
+
+
+class classproperty(object): # pylint: disable=invalid-name
+ def __init__(self, f):
+ self._func = f
+ self.__doct__ = f.__doc__
+
+ def __get__(self, instance, owner):
+ return self._func(owner)
+
+
+def fix_doc(cls):
+ """
+ Class decorator to use the last base class's docstring and make sure Sphinx doesn't grab the
+ base constructor's docstring.
+ """
+ original_init = cls.__init__
+ def init(*args, **kwargs):
+ original_init(*args, **kwargs)
+
+ cls.__init__ = init
+ cls.__doc__ = cls.__bases__[-1].__doc__
+
+ return cls
+
+
+def _get_class_from_sql_relationship(field):
+ class_ = field._sa_adapter.owner_state.class_
+ prop_name = field._sa_adapter.attr.key
+ return getattr(class_, prop_name).property.mapper.class_