blob: 01c4da97601825823c1411e743786b2167b80ff8 [file] [log] [blame]
# 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))))