X-Git-Url: https://gerrit.onap.org/r/gitweb?a=blobdiff_plain;f=azure%2Faria%2Faria-extension-cloudify%2Fsrc%2Faria%2Faria%2Fmodeling%2Fservice_template.py;fp=azure%2Faria%2Faria-extension-cloudify%2Fsrc%2Faria%2Faria%2Fmodeling%2Fservice_template.py;h=cd0adb4b0cd15599521d7a31d39b06126234a87e;hb=7409dfb144cf2a06210400134d822a1393462b1f;hp=0000000000000000000000000000000000000000;hpb=9e65649dfff8f00dc0a0ef6b10d020ae0e2255ba;p=multicloud%2Fazure.git 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)))