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