# 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)))