summaryrefslogtreecommitdiffstats
path: root/azure/aria/aria-extension-cloudify/src/aria/aria/modeling/service_instance.py
diff options
context:
space:
mode:
Diffstat (limited to 'azure/aria/aria-extension-cloudify/src/aria/aria/modeling/service_instance.py')
-rw-r--r--azure/aria/aria-extension-cloudify/src/aria/aria/modeling/service_instance.py1695
1 files changed, 1695 insertions, 0 deletions
diff --git a/azure/aria/aria-extension-cloudify/src/aria/aria/modeling/service_instance.py b/azure/aria/aria-extension-cloudify/src/aria/aria/modeling/service_instance.py
new file mode 100644
index 0000000..01c4da9
--- /dev/null
+++ b/azure/aria/aria-extension-cloudify/src/aria/aria/modeling/service_instance.py
@@ -0,0 +1,1695 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+ARIA modeling service instance module
+"""
+
+# pylint: disable=too-many-lines, no-self-argument, no-member, abstract-method
+
+from sqlalchemy import (
+ Column,
+ Text,
+ Integer,
+ Enum,
+ Boolean
+)
+from sqlalchemy import DateTime
+from sqlalchemy.ext.declarative import declared_attr
+from sqlalchemy.ext.orderinglist import ordering_list
+
+from . import (
+ relationship,
+ types as modeling_types
+)
+from .mixins import InstanceModelMixin
+
+from ..utils import (
+ collections,
+ formatting
+)
+
+
+class ServiceBase(InstanceModelMixin):
+ """
+ Usually an instance of a :class:`ServiceTemplate` and its many associated templates (node
+ templates, group templates, policy templates, etc.). However, it can also be created
+ programmatically.
+ """
+
+ __tablename__ = 'service'
+
+ __private_fields__ = ('substitution_fk',
+ 'service_template_fk')
+
+ # region one_to_one relationships
+
+ @declared_attr
+ def substitution(cls):
+ """
+ Exposes the entire service as a single node.
+
+ :type: :class:`Substitution`
+ """
+ return relationship.one_to_one(cls, 'substitution', back_populates=relationship.NO_BACK_POP)
+
+ # endregion
+
+ # region one_to_many relationships
+
+ @declared_attr
+ def outputs(cls):
+ """
+ Output parameters.
+
+ :type: {:obj:`basestring`: :class:`Output`}
+ """
+ return relationship.one_to_many(cls, 'output', dict_key='name')
+
+ @declared_attr
+ def inputs(cls):
+ """
+ Externally provided parameters.
+
+ :type: {:obj:`basestring`: :class:`Input`}
+ """
+ return relationship.one_to_many(cls, 'input', dict_key='name')
+
+ @declared_attr
+ def updates(cls):
+ """
+ Service updates.
+
+ :type: [:class:`ServiceUpdate`]
+ """
+ return relationship.one_to_many(cls, 'service_update')
+
+ @declared_attr
+ def modifications(cls):
+ """
+ Service modifications.
+
+ :type: [:class:`ServiceModification`]
+ """
+ return relationship.one_to_many(cls, 'service_modification')
+
+ @declared_attr
+ def executions(cls):
+ """
+ Executions.
+
+ :type: [:class:`Execution`]
+ """
+ return relationship.one_to_many(cls, 'execution')
+
+ @declared_attr
+ def nodes(cls):
+ """
+ Nodes.
+
+ :type: {:obj:`basestring`, :class:`Node`}
+ """
+ return relationship.one_to_many(cls, 'node', dict_key='name')
+
+ @declared_attr
+ def groups(cls):
+ """
+ Groups.
+
+ :type: {:obj:`basestring`, :class:`Group`}
+ """
+ return relationship.one_to_many(cls, 'group', dict_key='name')
+
+ @declared_attr
+ def policies(cls):
+ """
+ Policies.
+
+ :type: {:obj:`basestring`, :class:`Policy`}
+ """
+ return relationship.one_to_many(cls, 'policy', dict_key='name')
+
+ @declared_attr
+ def workflows(cls):
+ """
+ Workflows.
+
+ :type: {:obj:`basestring`, :class:`Operation`}
+ """
+ return relationship.one_to_many(cls, 'operation', dict_key='name')
+
+ # endregion
+
+ # region many_to_one relationships
+
+ @declared_attr
+ def service_template(cls):
+ """
+ Source service template (can be ``None``).
+
+ :type: :class:`ServiceTemplate`
+ """
+ return relationship.many_to_one(cls, 'service_template')
+
+ # endregion
+
+ # region many_to_many relationships
+
+ @declared_attr
+ def meta_data(cls):
+ """
+ Associated metadata.
+
+ :type: {:obj:`basestring`, :class:`Metadata`}
+ """
+ # Warning! We cannot use the attr name "metadata" because it's used by SQLAlchemy!
+ return relationship.many_to_many(cls, 'metadata', dict_key='name')
+
+ @declared_attr
+ def plugins(cls):
+ """
+ Associated plugins.
+
+ :type: {:obj:`basestring`, :class:`Plugin`}
+ """
+ return relationship.many_to_many(cls, 'plugin', dict_key='name')
+
+ # endregion
+
+ # region association proxies
+
+ @declared_attr
+ def service_template_name(cls):
+ return relationship.association_proxy('service_template', 'name', type=':obj:`basestring`')
+
+ # endregion
+
+ # region foreign keys
+
+ @declared_attr
+ def substitution_fk(cls):
+ """Service one-to-one to Substitution"""
+ return relationship.foreign_key('substitution', nullable=True)
+
+ @declared_attr
+ def service_template_fk(cls):
+ """For Service many-to-one to ServiceTemplate"""
+ return relationship.foreign_key('service_template', nullable=True)
+
+ # endregion
+
+ description = Column(Text, doc="""
+ Human-readable description.
+
+ :type: :obj:`basestring`
+ """)
+
+ created_at = Column(DateTime, nullable=False, index=True, doc="""
+ Creation timestamp.
+
+ :type: :class:`~datetime.datetime`
+ """)
+
+ updated_at = Column(DateTime, doc="""
+ Update timestamp.
+
+ :type: :class:`~datetime.datetime`
+ """)
+
+ def get_node_by_type(self, type_name):
+ """
+ Finds the first node of a type (or descendent type).
+ """
+ service_template = self.service_template
+
+ if service_template is not None:
+ node_types = service_template.node_types
+ if node_types is not None:
+ for node in self.nodes.itervalues():
+ if node_types.is_descendant(type_name, node.type.name):
+ return node
+
+ return None
+
+ def get_policy_by_type(self, type_name):
+ """
+ Finds the first policy of a type (or descendent type).
+ """
+ service_template = self.service_template
+
+ if service_template is not None:
+ policy_types = service_template.policy_types
+ if policy_types is not None:
+ for policy in self.policies.itervalues():
+ if policy_types.is_descendant(type_name, policy.type.name):
+ return policy
+
+ return None
+
+ @property
+ def as_raw(self):
+ return collections.OrderedDict((
+ ('description', self.description),
+ ('metadata', formatting.as_raw_dict(self.meta_data)),
+ ('nodes', formatting.as_raw_list(self.nodes)),
+ ('groups', formatting.as_raw_list(self.groups)),
+ ('policies', formatting.as_raw_list(self.policies)),
+ ('substitution', formatting.as_raw(self.substitution)),
+ ('inputs', formatting.as_raw_dict(self.inputs)),
+ ('outputs', formatting.as_raw_dict(self.outputs)),
+ ('workflows', formatting.as_raw_list(self.workflows))))
+
+
+class NodeBase(InstanceModelMixin):
+ """
+ Typed vertex in the service topology.
+
+ Nodes may have zero or more :class:`Relationship` instances to other nodes, together forming
+ a many-to-many node graph.
+
+ Usually an instance of a :class:`NodeTemplate`.
+ """
+
+ __tablename__ = 'node'
+
+ __private_fields__ = ('type_fk',
+ 'host_fk',
+ 'service_fk',
+ 'node_template_fk')
+
+ INITIAL = 'initial'
+ CREATING = 'creating'
+ CREATED = 'created'
+ CONFIGURING = 'configuring'
+ CONFIGURED = 'configured'
+ STARTING = 'starting'
+ STARTED = 'started'
+ STOPPING = 'stopping'
+ DELETING = 'deleting'
+ DELETED = 'deleted'
+ ERROR = 'error'
+
+ # Note: 'deleted' isn't actually part of the TOSCA spec, since according the description of the
+ # 'deleting' state: "Node is transitioning from its current state to one where it is deleted and
+ # its state is no longer tracked by the instance model." However, we prefer to be able to
+ # retrieve information about deleted nodes, so we chose to add this 'deleted' state to enable us
+ # to do so.
+
+ STATES = (INITIAL, CREATING, CREATED, CONFIGURING, CONFIGURED, STARTING, STARTED, STOPPING,
+ DELETING, DELETED, ERROR)
+
+ _OP_TO_STATE = {'create': {'transitional': CREATING, 'finished': CREATED},
+ 'configure': {'transitional': CONFIGURING, 'finished': CONFIGURED},
+ 'start': {'transitional': STARTING, 'finished': STARTED},
+ 'stop': {'transitional': STOPPING, 'finished': CONFIGURED},
+ 'delete': {'transitional': DELETING, 'finished': DELETED}}
+
+ # region one_to_one relationships
+
+ @declared_attr
+ def host(cls): # pylint: disable=method-hidden
+ """
+ Node in which we are hosted (can be ``None``).
+
+ Normally the host node is found by following the relationship graph (relationships with
+ ``host`` roles) to final nodes (with ``host`` roles).
+
+ :type: :class:`Node`
+ """
+ return relationship.one_to_one_self(cls, 'host_fk')
+
+ # endregion
+
+ # region one_to_many relationships
+
+ @declared_attr
+ def tasks(cls):
+ """
+ Associated tasks.
+
+ :type: [:class:`Task`]
+ """
+ return relationship.one_to_many(cls, 'task')
+
+ @declared_attr
+ def interfaces(cls):
+ """
+ Associated interfaces.
+
+ :type: {:obj:`basestring`: :class:`Interface`}
+ """
+ return relationship.one_to_many(cls, 'interface', dict_key='name')
+
+ @declared_attr
+ def properties(cls):
+ """
+ Associated immutable parameters.
+
+ :type: {:obj:`basestring`: :class:`Property`}
+ """
+ return relationship.one_to_many(cls, 'property', dict_key='name')
+
+ @declared_attr
+ def attributes(cls):
+ """
+ Associated mutable parameters.
+
+ :type: {:obj:`basestring`: :class:`Attribute`}
+ """
+ return relationship.one_to_many(cls, 'attribute', dict_key='name')
+
+ @declared_attr
+ def artifacts(cls):
+ """
+ Associated artifacts.
+
+ :type: {:obj:`basestring`: :class:`Artifact`}
+ """
+ return relationship.one_to_many(cls, 'artifact', dict_key='name')
+
+ @declared_attr
+ def capabilities(cls):
+ """
+ Associated exposed capabilities.
+
+ :type: {:obj:`basestring`: :class:`Capability`}
+ """
+ return relationship.one_to_many(cls, 'capability', dict_key='name')
+
+ @declared_attr
+ def outbound_relationships(cls):
+ """
+ Relationships to other nodes.
+
+ :type: [:class:`Relationship`]
+ """
+ return relationship.one_to_many(
+ cls, 'relationship', other_fk='source_node_fk', back_populates='source_node',
+ rel_kwargs=dict(
+ order_by='Relationship.source_position',
+ collection_class=ordering_list('source_position', count_from=0)
+ )
+ )
+
+ @declared_attr
+ def inbound_relationships(cls):
+ """
+ Relationships from other nodes.
+
+ :type: [:class:`Relationship`]
+ """
+ return relationship.one_to_many(
+ cls, 'relationship', other_fk='target_node_fk', back_populates='target_node',
+ rel_kwargs=dict(
+ order_by='Relationship.target_position',
+ collection_class=ordering_list('target_position', count_from=0)
+ )
+ )
+
+ # endregion
+
+ # region many_to_one relationships
+
+ @declared_attr
+ def service(cls):
+ """
+ Containing service.
+
+ :type: :class:`Service`
+ """
+ return relationship.many_to_one(cls, 'service')
+
+ @declared_attr
+ def node_template(cls):
+ """
+ Source node template (can be ``None``).
+
+ :type: :class:`NodeTemplate`
+ """
+ return relationship.many_to_one(cls, 'node_template')
+
+ @declared_attr
+ def type(cls):
+ """
+ Node type.
+
+ :type: :class:`Type`
+ """
+ return relationship.many_to_one(cls, 'type', back_populates=relationship.NO_BACK_POP)
+
+ # endregion
+
+ # region association proxies
+
+ @declared_attr
+ def service_name(cls):
+ return relationship.association_proxy('service', 'name', type=':obj:`basestring`')
+
+ @declared_attr
+ def node_template_name(cls):
+ return relationship.association_proxy('node_template', 'name', type=':obj:`basestring`')
+
+ # endregion
+
+ # region foreign_keys
+
+ @declared_attr
+ def type_fk(cls):
+ """For Node many-to-one to Type"""
+ return relationship.foreign_key('type')
+
+ @declared_attr
+ def host_fk(cls):
+ """For Node one-to-one to Node"""
+ return relationship.foreign_key('node', nullable=True)
+
+ @declared_attr
+ def service_fk(cls):
+ """For Service one-to-many to Node"""
+ return relationship.foreign_key('service')
+
+ @declared_attr
+ def node_template_fk(cls):
+ """For Node many-to-one to NodeTemplate"""
+ return relationship.foreign_key('node_template')
+
+ # endregion
+
+ description = Column(Text, doc="""
+ Human-readable description.
+
+ :type: :obj:`basestring`
+ """)
+
+ state = Column(Enum(*STATES, name='node_state'), nullable=False, default=INITIAL, doc="""
+ TOSCA state.
+
+ :type: :obj:`basestring`
+ """)
+
+ version = Column(Integer, default=1, doc="""
+ Used by :mod:`aria.storage.instrumentation`.
+
+ :type: :obj:`int`
+ """)
+
+ __mapper_args__ = {'version_id_col': version} # Enable SQLAlchemy automatic version counting
+
+ @classmethod
+ def determine_state(cls, op_name, is_transitional):
+ """
+ :returns the state the node should be in as a result of running the operation on this node.
+
+ E.g. if we are running tosca.interfaces.node.lifecycle.Standard.create, then
+ the resulting state should either 'creating' (if the task just started) or 'created'
+ (if the task ended).
+
+ If the operation is not a standard TOSCA lifecycle operation, then we return None.
+ """
+
+ state_type = 'transitional' if is_transitional else 'finished'
+ try:
+ return cls._OP_TO_STATE[op_name][state_type]
+ except KeyError:
+ return None
+
+ def is_available(self):
+ return self.state not in (self.INITIAL, self.DELETED, self.ERROR)
+
+ def get_outbound_relationship_by_name(self, name):
+ for the_relationship in self.outbound_relationships:
+ if the_relationship.name == name:
+ return the_relationship
+ return None
+
+ def get_inbound_relationship_by_name(self, name):
+ for the_relationship in self.inbound_relationships:
+ if the_relationship.name == name:
+ return the_relationship
+ return None
+
+ @property
+ def host_address(self):
+ if self.host and self.host.attributes:
+ attribute = self.host.attributes.get('ip')
+ if attribute is not None:
+ return attribute.value
+ return None
+
+ @property
+ def as_raw(self):
+ return collections.OrderedDict((
+ ('name', self.name),
+ ('type_name', self.type.name),
+ ('properties', formatting.as_raw_dict(self.properties)),
+ ('attributes', formatting.as_raw_dict(self.properties)),
+ ('interfaces', formatting.as_raw_list(self.interfaces)),
+ ('artifacts', formatting.as_raw_list(self.artifacts)),
+ ('capabilities', formatting.as_raw_list(self.capabilities)),
+ ('relationships', formatting.as_raw_list(self.outbound_relationships))))
+
+
+class GroupBase(InstanceModelMixin):
+ """
+ Typed logical container for zero or more :class:`Node` instances.
+
+ Usually an instance of a :class:`GroupTemplate`.
+ """
+
+ __tablename__ = 'group'
+
+ __private_fields__ = ('type_fk',
+ 'service_fk',
+ 'group_template_fk')
+
+ # region one_to_many relationships
+
+ @declared_attr
+ def properties(cls):
+ """
+ Associated immutable parameters.
+
+ :type: {:obj:`basestring`: :class:`Property`}
+ """
+ return relationship.one_to_many(cls, 'property', dict_key='name')
+
+ @declared_attr
+ def interfaces(cls):
+ """
+ Associated interfaces.
+
+ :type: {:obj:`basestring`: :class:`Interface`}
+ """
+ return relationship.one_to_many(cls, 'interface', dict_key='name')
+
+ # endregion
+
+ # region many_to_one relationships
+
+ @declared_attr
+ def service(cls):
+ """
+ Containing service.
+
+ :type: :class:`Service`
+ """
+ return relationship.many_to_one(cls, 'service')
+
+ @declared_attr
+ def group_template(cls):
+ """
+ Source group template (can be ``None``).
+
+ :type: :class:`GroupTemplate`
+ """
+ return relationship.many_to_one(cls, 'group_template')
+
+ @declared_attr
+ def type(cls):
+ """
+ Group type.
+
+ :type: :class:`Type`
+ """
+ return relationship.many_to_one(cls, 'type', back_populates=relationship.NO_BACK_POP)
+
+ # endregion
+
+ # region many_to_many relationships
+
+ @declared_attr
+ def nodes(cls):
+ """
+ Member nodes.
+
+ :type: [:class:`Node`]
+ """
+ return relationship.many_to_many(cls, 'node')
+
+ # endregion
+
+ # region foreign_keys
+
+ @declared_attr
+ def type_fk(cls):
+ """For Group many-to-one to Type"""
+ return relationship.foreign_key('type')
+
+ @declared_attr
+ def service_fk(cls):
+ """For Service one-to-many to Group"""
+ return relationship.foreign_key('service')
+
+ @declared_attr
+ def group_template_fk(cls):
+ """For Group many-to-one to GroupTemplate"""
+ return relationship.foreign_key('group_template', nullable=True)
+
+ # endregion
+
+ description = Column(Text, doc="""
+ Human-readable description.
+
+ :type: :obj:`basestring`
+ """)
+
+ @property
+ def as_raw(self):
+ return collections.OrderedDict((
+ ('name', self.name),
+ ('properties', formatting.as_raw_dict(self.properties)),
+ ('interfaces', formatting.as_raw_list(self.interfaces))))
+
+
+class PolicyBase(InstanceModelMixin):
+ """
+ Typed set of orchestration hints applied to zero or more :class:`Node` or :class:`Group`
+ instances.
+
+ Usually an instance of a :class:`PolicyTemplate`.
+ """
+
+ __tablename__ = 'policy'
+
+ __private_fields__ = ('type_fk',
+ 'service_fk',
+ 'policy_template_fk')
+
+ # region one_to_many relationships
+
+ @declared_attr
+ def properties(cls):
+ """
+ Associated immutable parameters.
+
+ :type: {:obj:`basestring`: :class:`Property`}
+ """
+ return relationship.one_to_many(cls, 'property', dict_key='name')
+
+ # endregion
+
+ # region many_to_one relationships
+
+ @declared_attr
+ def service(cls):
+ """
+ Containing service.
+
+ :type: :class:`Service`
+ """
+ return relationship.many_to_one(cls, 'service')
+
+ @declared_attr
+ def policy_template(cls):
+ """
+ Source policy template (can be ``None``).
+
+ :type: :class:`PolicyTemplate`
+ """
+ return relationship.many_to_one(cls, 'policy_template')
+
+ @declared_attr
+ def type(cls):
+ """
+ Group type.
+
+ :type: :class:`Type`
+ """
+ return relationship.many_to_one(cls, 'type', back_populates=relationship.NO_BACK_POP)
+
+ # endregion
+
+ # region many_to_many relationships
+
+ @declared_attr
+ def nodes(cls):
+ """
+ Policy is enacted on these nodes.
+
+ :type: {:obj:`basestring`: :class:`Node`}
+ """
+ return relationship.many_to_many(cls, 'node')
+
+ @declared_attr
+ def groups(cls):
+ """
+ Policy is enacted on nodes in these groups.
+
+ :type: {:obj:`basestring`: :class:`Group`}
+ """
+ return relationship.many_to_many(cls, 'group')
+
+ # endregion
+
+ # region foreign_keys
+
+ @declared_attr
+ def type_fk(cls):
+ """For Policy many-to-one to Type"""
+ return relationship.foreign_key('type')
+
+ @declared_attr
+ def service_fk(cls):
+ """For Service one-to-many to Policy"""
+ return relationship.foreign_key('service')
+
+ @declared_attr
+ def policy_template_fk(cls):
+ """For Policy many-to-one to PolicyTemplate"""
+ return relationship.foreign_key('policy_template', nullable=True)
+
+ # endregion
+
+ description = Column(Text, doc="""
+ Human-readable description.
+
+ :type: :obj:`basestring`
+ """)
+
+ @property
+ def as_raw(self):
+ return collections.OrderedDict((
+ ('name', self.name),
+ ('type_name', self.type.name),
+ ('properties', formatting.as_raw_dict(self.properties))))
+
+
+class SubstitutionBase(InstanceModelMixin):
+ """
+ Exposes the entire service as a single node.
+
+ Usually an instance of a :class:`SubstitutionTemplate`.
+ """
+
+ __tablename__ = 'substitution'
+
+ __private_fields__ = ('node_type_fk',
+ 'substitution_template_fk')
+
+ # region one_to_many relationships
+
+ @declared_attr
+ def mappings(cls):
+ """
+ Map requirement and capabilities to exposed node.
+
+ :type: {:obj:`basestring`: :class:`SubstitutionMapping`}
+ """
+ return relationship.one_to_many(cls, 'substitution_mapping', dict_key='name')
+
+ # endregion
+
+ # region many_to_one relationships
+
+ @declared_attr
+ def service(cls):
+ """
+ Containing service.
+
+ :type: :class:`Service`
+ """
+ return relationship.one_to_one(cls, 'service', back_populates=relationship.NO_BACK_POP)
+
+ @declared_attr
+ def substitution_template(cls):
+ """
+ Source substitution template (can be ``None``).
+
+ :type: :class:`SubstitutionTemplate`
+ """
+ return relationship.many_to_one(cls, 'substitution_template')
+
+ @declared_attr
+ def node_type(cls):
+ """
+ Exposed node type.
+
+ :type: :class:`Type`
+ """
+ return relationship.many_to_one(cls, 'type', back_populates=relationship.NO_BACK_POP)
+
+ # endregion
+
+ # region foreign_keys
+
+ @declared_attr
+ def node_type_fk(cls):
+ """For Substitution many-to-one to Type"""
+ return relationship.foreign_key('type')
+
+ @declared_attr
+ def substitution_template_fk(cls):
+ """For Substitution many-to-one to SubstitutionTemplate"""
+ return relationship.foreign_key('substitution_template', nullable=True)
+
+ # endregion
+
+ @property
+ def as_raw(self):
+ return collections.OrderedDict((
+ ('node_type_name', self.node_type.name),
+ ('mappings', formatting.as_raw_dict(self.mappings))))
+
+
+class SubstitutionMappingBase(InstanceModelMixin):
+ """
+ Used by :class:`Substitution` to map a capability or a requirement to the exposed node.
+
+ The :attr:`name` field should match the capability or requirement template name on the exposed
+ node's type.
+
+ Only one of :attr:`capability` and :attr:`requirement_template` can be set. If the latter is
+ set, then :attr:`node` must also be set.
+
+ Usually an instance of a :class:`SubstitutionMappingTemplate`.
+ """
+
+ __tablename__ = 'substitution_mapping'
+
+ __private_fields__ = ('substitution_fk',
+ 'node_fk',
+ 'capability_fk',
+ 'requirement_template_fk')
+
+ # region one_to_one relationships
+
+ @declared_attr
+ def capability(cls):
+ """
+ Capability to expose (can be ``None``).
+
+ :type: :class:`Capability`
+ """
+ return relationship.one_to_one(cls, 'capability', back_populates=relationship.NO_BACK_POP)
+
+ @declared_attr
+ def requirement_template(cls):
+ """
+ Requirement template to expose (can be ``None``).
+
+ :type: :class:`RequirementTemplate`
+ """
+ return relationship.one_to_one(cls, 'requirement_template',
+ back_populates=relationship.NO_BACK_POP)
+
+ @declared_attr
+ def node(cls):
+ """
+ Node for which to expose :attr:`requirement_template` (can be ``None``).
+
+ :type: :class:`Node`
+ """
+ return relationship.one_to_one(cls, 'node', back_populates=relationship.NO_BACK_POP)
+
+ # endregion
+
+ # region many_to_one relationships
+
+ @declared_attr
+ def substitution(cls):
+ """
+ Containing substitution.
+
+ :type: :class:`Substitution`
+ """
+ return relationship.many_to_one(cls, 'substitution', back_populates='mappings')
+
+ # endregion
+
+ # region foreign keys
+
+ @declared_attr
+ def substitution_fk(cls):
+ """For Substitution one-to-many to SubstitutionMapping"""
+ return relationship.foreign_key('substitution')
+
+ @declared_attr
+ def capability_fk(cls):
+ """For Substitution one-to-one to Capability"""
+ return relationship.foreign_key('capability', nullable=True)
+
+ @declared_attr
+ def node_fk(cls):
+ """For Substitution one-to-one to Node"""
+ return relationship.foreign_key('node', nullable=True)
+
+ @declared_attr
+ def requirement_template_fk(cls):
+ """For Substitution one-to-one to RequirementTemplate"""
+ return relationship.foreign_key('requirement_template', nullable=True)
+
+ # endregion
+
+ @property
+ def as_raw(self):
+ return collections.OrderedDict((
+ ('name', self.name),))
+
+
+class RelationshipBase(InstanceModelMixin):
+ """
+ Optionally-typed edge in the service topology, connecting a :class:`Node` to a
+ :class:`Capability` of another node.
+
+ Might be an instance of :class:`RelationshipTemplate` and/or :class:`RequirementTemplate`.
+ """
+
+ __tablename__ = 'relationship'
+
+ __private_fields__ = ('type_fk',
+ 'source_node_fk',
+ 'target_node_fk',
+ 'target_capability_fk',
+ 'requirement_template_fk',
+ 'relationship_template_fk',
+ 'target_position',
+ 'source_position')
+
+ # region one_to_one relationships
+
+ @declared_attr
+ def target_capability(cls):
+ """
+ Target capability.
+
+ :type: :class:`Capability`
+ """
+ return relationship.one_to_one(cls, 'capability', back_populates=relationship.NO_BACK_POP)
+
+ # endregion
+
+ # region one_to_many relationships
+
+ @declared_attr
+ def tasks(cls):
+ """
+ Associated tasks.
+
+ :type: [:class:`Task`]
+ """
+ return relationship.one_to_many(cls, 'task')
+
+ @declared_attr
+ def interfaces(cls):
+ """
+ Associated interfaces.
+
+ :type: {:obj:`basestring`: :class:`Interface`}
+ """
+ return relationship.one_to_many(cls, 'interface', dict_key='name')
+
+ @declared_attr
+ def properties(cls):
+ """
+ Associated immutable parameters.
+
+ :type: {:obj:`basestring`: :class:`Property`}
+ """
+ return relationship.one_to_many(cls, 'property', dict_key='name')
+
+ # endregion
+
+ # region many_to_one relationships
+
+ @declared_attr
+ def source_node(cls):
+ """
+ Source node.
+
+ :type: :class:`Node`
+ """
+ return relationship.many_to_one(
+ cls, 'node', fk='source_node_fk', back_populates='outbound_relationships')
+
+ @declared_attr
+ def target_node(cls):
+ """
+ Target node.
+
+ :type: :class:`Node`
+ """
+ return relationship.many_to_one(
+ cls, 'node', fk='target_node_fk', back_populates='inbound_relationships')
+
+ @declared_attr
+ def relationship_template(cls):
+ """
+ Source relationship template (can be ``None``).
+
+ :type: :class:`RelationshipTemplate`
+ """
+ return relationship.many_to_one(cls, 'relationship_template')
+
+ @declared_attr
+ def requirement_template(cls):
+ """
+ Source requirement template (can be ``None``).
+
+ :type: :class:`RequirementTemplate`
+ """
+ return relationship.many_to_one(cls, 'requirement_template')
+
+ @declared_attr
+ def type(cls):
+ """
+ Relationship type.
+
+ :type: :class:`Type`
+ """
+ return relationship.many_to_one(cls, 'type', back_populates=relationship.NO_BACK_POP)
+
+ # endregion
+
+ # region association proxies
+
+ @declared_attr
+ def source_node_name(cls):
+ return relationship.association_proxy('source_node', 'name')
+
+ @declared_attr
+ def target_node_name(cls):
+ return relationship.association_proxy('target_node', 'name')
+
+ # endregion
+
+ # region foreign keys
+
+ @declared_attr
+ def type_fk(cls):
+ """For Relationship many-to-one to Type"""
+ return relationship.foreign_key('type', nullable=True)
+
+ @declared_attr
+ def source_node_fk(cls):
+ """For Node one-to-many to Relationship"""
+ return relationship.foreign_key('node')
+
+ @declared_attr
+ def target_node_fk(cls):
+ """For Node one-to-many to Relationship"""
+ return relationship.foreign_key('node')
+
+ @declared_attr
+ def target_capability_fk(cls):
+ """For Relationship one-to-one to Capability"""
+ return relationship.foreign_key('capability', nullable=True)
+
+ @declared_attr
+ def requirement_template_fk(cls):
+ """For Relationship many-to-one to RequirementTemplate"""
+ return relationship.foreign_key('requirement_template', nullable=True)
+
+ @declared_attr
+ def relationship_template_fk(cls):
+ """For Relationship many-to-one to RelationshipTemplate"""
+ return relationship.foreign_key('relationship_template', nullable=True)
+
+ # endregion
+
+ source_position = Column(Integer, doc="""
+ Position at source.
+
+ :type: :obj:`int`
+ """)
+
+ target_position = Column(Integer, doc="""
+ Position at target.
+
+ :type: :obj:`int`
+ """)
+
+ @property
+ def as_raw(self):
+ return collections.OrderedDict((
+ ('name', self.name),
+ ('target_node_id', self.target_node.name),
+ ('type_name', self.type.name
+ if self.type is not None else None),
+ ('template_name', self.relationship_template.name
+ if self.relationship_template is not None else None),
+ ('properties', formatting.as_raw_dict(self.properties)),
+ ('interfaces', formatting.as_raw_list(self.interfaces))))
+
+
+class CapabilityBase(InstanceModelMixin):
+ """
+ Typed attachment serving two purposes: to provide extra properties and attributes to a
+ :class:`Node`, and to expose targets for :class:`Relationship` instances from other nodes.
+
+ Usually an instance of a :class:`CapabilityTemplate`.
+ """
+
+ __tablename__ = 'capability'
+
+ __private_fields__ = ('capability_fk',
+ 'node_fk',
+ 'capability_template_fk')
+
+ # region one_to_many relationships
+
+ @declared_attr
+ def properties(cls):
+ """
+ Associated immutable parameters.
+
+ :type: {:obj:`basestring`: :class:`Property`}
+ """
+ return relationship.one_to_many(cls, 'property', dict_key='name')
+
+ # endregion
+
+ # region many_to_one relationships
+
+ @declared_attr
+ def node(cls):
+ """
+ Containing node.
+
+ :type: :class:`Node`
+ """
+ return relationship.many_to_one(cls, 'node')
+
+ @declared_attr
+ def capability_template(cls):
+ """
+ Source capability template (can be ``None``).
+
+ :type: :class:`CapabilityTemplate`
+ """
+ return relationship.many_to_one(cls, 'capability_template')
+
+ @declared_attr
+ def type(cls):
+ """
+ Capability type.
+
+ :type: :class:`Type`
+ """
+ return relationship.many_to_one(cls, 'type', back_populates=relationship.NO_BACK_POP)
+
+ # endregion
+
+ # region foreign_keys
+
+ @declared_attr
+ def type_fk(cls):
+ """For Capability many-to-one to Type"""
+ return relationship.foreign_key('type')
+
+ @declared_attr
+ def node_fk(cls):
+ """For Node one-to-many to Capability"""
+ return relationship.foreign_key('node')
+
+ @declared_attr
+ def capability_template_fk(cls):
+ """For Capability many-to-one to CapabilityTemplate"""
+ return relationship.foreign_key('capability_template', nullable=True)
+
+ # endregion
+
+ min_occurrences = Column(Integer, default=None, doc="""
+ Minimum number of requirement matches required.
+
+ :type: :obj:`int`
+ """)
+
+ max_occurrences = Column(Integer, default=None, doc="""
+ Maximum number of requirement matches allowed.
+
+ :type: :obj:`int`
+ """)
+
+ occurrences = Column(Integer, default=0, doc="""
+ Number of requirement matches.
+
+ :type: :obj:`int`
+ """)
+
+ @property
+ def has_enough_relationships(self):
+ if self.min_occurrences is not None:
+ return self.occurrences >= self.min_occurrences
+ return True
+
+ def relate(self):
+ if self.max_occurrences is not None:
+ if self.occurrences == self.max_occurrences:
+ return False
+ self.occurrences += 1
+ return True
+
+ @property
+ def as_raw(self):
+ return collections.OrderedDict((
+ ('name', self.name),
+ ('type_name', self.type.name),
+ ('properties', formatting.as_raw_dict(self.properties))))
+
+
+class InterfaceBase(InstanceModelMixin):
+ """
+ Typed bundle of :class:`Operation` instances.
+
+ Can be associated with a :class:`Node`, a :class:`Group`, or a :class:`Relationship`.
+
+ Usually an instance of a :class:`InterfaceTemplate`.
+ """
+
+ __tablename__ = 'interface'
+
+ __private_fields__ = ('type_fk',
+ 'node_fk',
+ 'group_fk',
+ 'relationship_fk',
+ 'interface_template_fk')
+
+ # region one_to_many relationships
+
+ @declared_attr
+ def inputs(cls):
+ """
+ Parameters for all operations of the interface.
+
+ :type: {:obj:`basestring`: :class:`Input`}
+ """
+ return relationship.one_to_many(cls, 'input', dict_key='name')
+
+ @declared_attr
+ def operations(cls):
+ """
+ Associated operations.
+
+ :type: {:obj:`basestring`: :class:`Operation`}
+ """
+ return relationship.one_to_many(cls, 'operation', dict_key='name')
+
+ # endregion
+
+ # region many_to_one relationships
+
+ @declared_attr
+ def node(cls):
+ """
+ Containing node (can be ``None``).
+
+ :type: :class:`Node`
+ """
+ return relationship.many_to_one(cls, 'node')
+
+ @declared_attr
+ def group(cls):
+ """
+ Containing group (can be ``None``).
+
+ :type: :class:`Group`
+ """
+ return relationship.many_to_one(cls, 'group')
+
+ @declared_attr
+ def relationship(cls):
+ """
+ Containing relationship (can be ``None``).
+
+ :type: :class:`Relationship`
+ """
+ return relationship.many_to_one(cls, 'relationship')
+
+ @declared_attr
+ def interface_template(cls):
+ """
+ Source interface template (can be ``None``).
+
+ :type: :class:`InterfaceTemplate`
+ """
+ return relationship.many_to_one(cls, 'interface_template')
+
+ @declared_attr
+ def type(cls):
+ """
+ Interface type.
+
+ :type: :class:`Type`
+ """
+ return relationship.many_to_one(cls, 'type', back_populates=relationship.NO_BACK_POP)
+
+ # endregion
+
+ # region foreign_keys
+
+ @declared_attr
+ def type_fk(cls):
+ """For Interface many-to-one to Type"""
+ return relationship.foreign_key('type')
+
+ @declared_attr
+ def node_fk(cls):
+ """For Node one-to-many to Interface"""
+ return relationship.foreign_key('node', nullable=True)
+
+ @declared_attr
+ def group_fk(cls):
+ """For Group one-to-many to Interface"""
+ return relationship.foreign_key('group', nullable=True)
+
+ @declared_attr
+ def relationship_fk(cls):
+ """For Relationship one-to-many to Interface"""
+ return relationship.foreign_key('relationship', nullable=True)
+
+ @declared_attr
+ def interface_template_fk(cls):
+ """For Interface many-to-one to InterfaceTemplate"""
+ return relationship.foreign_key('interface_template', nullable=True)
+
+ # endregion
+
+ description = Column(Text, doc="""
+ Human-readable description.
+
+ :type: :obj:`basestring`
+ """)
+
+ @property
+ def as_raw(self):
+ return collections.OrderedDict((
+ ('name', self.name),
+ ('description', self.description),
+ ('type_name', self.type.name),
+ ('inputs', formatting.as_raw_dict(self.inputs)),
+ ('operations', formatting.as_raw_list(self.operations))))
+
+
+class OperationBase(InstanceModelMixin):
+ """
+ Entry points to Python functions called as part of a workflow execution.
+
+ The operation signature (its :attr:`name` and its :attr:`inputs`'s names and types) is declared
+ by the type of the :class:`Interface`, however each operation can provide its own
+ :attr:`implementation` as well as additional inputs.
+
+ The Python :attr:`function` is usually provided by an associated :class:`Plugin`. Its purpose is
+ to execute the implementation, providing it with both the operation's and interface's inputs.
+ The :attr:`arguments` of the function should be set according to the specific signature of the
+ function.
+
+ Additionally, :attr:`configuration` parameters can be provided as hints to configure the
+ function's behavior. For example, they can be used to configure remote execution credentials.
+
+ Might be an instance of :class:`OperationTemplate`.
+ """
+
+ __tablename__ = 'operation'
+
+ __private_fields__ = ('service_fk',
+ 'interface_fk',
+ 'plugin_fk',
+ 'operation_template_fk')
+
+ # region one_to_one relationships
+
+ @declared_attr
+ def plugin(cls):
+ """
+ Associated plugin.
+
+ :type: :class:`Plugin`
+ """
+ return relationship.one_to_one(cls, 'plugin', back_populates=relationship.NO_BACK_POP)
+
+ # endregion
+
+ # region one_to_many relationships
+
+ @declared_attr
+ def inputs(cls):
+ """
+ Parameters provided to the :attr:`implementation`.
+
+ :type: {:obj:`basestring`: :class:`Input`}
+ """
+ return relationship.one_to_many(cls, 'input', dict_key='name')
+
+ @declared_attr
+ def arguments(cls):
+ """
+ Arguments sent to the Python :attr:`function`.
+
+ :type: {:obj:`basestring`: :class:`Argument`}
+ """
+ return relationship.one_to_many(cls, 'argument', dict_key='name')
+
+ @declared_attr
+ def configurations(cls):
+ """
+ Configuration parameters for the Python :attr:`function`.
+
+ :type: {:obj:`basestring`: :class:`Configuration`}
+ """
+ return relationship.one_to_many(cls, 'configuration', dict_key='name')
+
+ # endregion
+
+ # region many_to_one relationships
+
+ @declared_attr
+ def service(cls):
+ """
+ Containing service (can be ``None``). For workflow operations.
+
+ :type: :class:`Service`
+ """
+ return relationship.many_to_one(cls, 'service', back_populates='workflows')
+
+ @declared_attr
+ def interface(cls):
+ """
+ Containing interface (can be ``None``).
+
+ :type: :class:`Interface`
+ """
+ return relationship.many_to_one(cls, 'interface')
+
+ @declared_attr
+ def operation_template(cls):
+ """
+ Source operation template (can be ``None``).
+
+ :type: :class:`OperationTemplate`
+ """
+ return relationship.many_to_one(cls, 'operation_template')
+
+ # endregion
+
+ # region foreign_keys
+
+ @declared_attr
+ def service_fk(cls):
+ """For Service one-to-many to Operation"""
+ return relationship.foreign_key('service', nullable=True)
+
+ @declared_attr
+ def interface_fk(cls):
+ """For Interface one-to-many to Operation"""
+ return relationship.foreign_key('interface', nullable=True)
+
+ @declared_attr
+ def plugin_fk(cls):
+ """For Operation one-to-one to Plugin"""
+ return relationship.foreign_key('plugin', nullable=True)
+
+ @declared_attr
+ def operation_template_fk(cls):
+ """For Operation many-to-one to OperationTemplate"""
+ return relationship.foreign_key('operation_template', nullable=True)
+
+ # endregion
+
+ description = Column(Text, doc="""
+ Human-readable description.
+
+ :type: :obj:`basestring`
+ """)
+
+ relationship_edge = Column(Boolean, doc="""
+ When ``True`` specifies that the operation is on the relationship's target edge; ``False`` is
+ the source edge (only used by operations on relationships)
+
+ :type: :obj:`bool`
+ """)
+
+ implementation = Column(Text, doc="""
+ Implementation (usually the name of an artifact).
+
+ :type: :obj:`basestring`
+ """)
+
+ dependencies = Column(modeling_types.StrictList(item_cls=basestring), doc="""
+ Dependencies (usually names of artifacts).
+
+ :type: [:obj:`basestring`]
+ """)
+
+ function = Column(Text, doc="""
+ Full path to Python function.
+
+ :type: :obj:`basestring`
+ """)
+
+ executor = Column(Text, doc="""
+ Name of executor.
+
+ :type: :obj:`basestring`
+ """)
+
+ max_attempts = Column(Integer, doc="""
+ Maximum number of attempts allowed in case of task failure.
+
+ :type: :obj:`int`
+ """)
+
+ retry_interval = Column(Integer, doc="""
+ Interval between task retry attempts (in seconds).
+
+ :type: :obj:`float`
+ """)
+
+ @property
+ def as_raw(self):
+ return collections.OrderedDict((
+ ('name', self.name),
+ ('description', self.description),
+ ('implementation', self.implementation),
+ ('dependencies', self.dependencies),
+ ('inputs', formatting.as_raw_dict(self.inputs))))
+
+
+class ArtifactBase(InstanceModelMixin):
+ """
+ Typed file, either provided in a CSAR or downloaded from a repository.
+
+ Usually an instance of :class:`ArtifactTemplate`.
+ """
+
+ __tablename__ = 'artifact'
+
+ __private_fields__ = ('type_fk',
+ 'node_fk',
+ 'artifact_template_fk')
+
+ # region one_to_many relationships
+
+ @declared_attr
+ def properties(cls):
+ """
+ Associated immutable parameters.
+
+ :type: {:obj:`basestring`: :class:`Property`}
+ """
+ return relationship.one_to_many(cls, 'property', dict_key='name')
+
+ # endregion
+
+ # region many_to_one relationships
+
+ @declared_attr
+ def node(cls):
+ """
+ Containing node.
+
+ :type: :class:`Node`
+ """
+ return relationship.many_to_one(cls, 'node')
+
+ @declared_attr
+ def artifact_template(cls):
+ """
+ Source artifact template (can be ``None``).
+
+ :type: :class:`ArtifactTemplate`
+ """
+ return relationship.many_to_one(cls, 'artifact_template')
+
+ @declared_attr
+ def type(cls):
+ """
+ Artifact type.
+
+ :type: :class:`Type`
+ """
+ return relationship.many_to_one(cls, 'type', back_populates=relationship.NO_BACK_POP)
+
+ # endregion
+
+ # region foreign_keys
+
+ @declared_attr
+ def type_fk(cls):
+ """For Artifact many-to-one to Type"""
+ return relationship.foreign_key('type')
+
+ @declared_attr
+ def node_fk(cls):
+ """For Node one-to-many to Artifact"""
+ return relationship.foreign_key('node')
+
+ @declared_attr
+ def artifact_template_fk(cls):
+ """For Artifact many-to-one to ArtifactTemplate"""
+ return relationship.foreign_key('artifact_template', nullable=True)
+
+ # endregion
+
+ description = Column(Text, doc="""
+ Human-readable description.
+
+ :type: :obj:`basestring`
+ """)
+
+ source_path = Column(Text, doc="""
+ Source path (in CSAR or repository).
+
+ :type: :obj:`basestring`
+ """)
+
+ target_path = Column(Text, doc="""
+ Path at which to install at destination.
+
+ :type: :obj:`basestring`
+ """)
+
+ repository_url = Column(Text, doc="""
+ Repository URL.
+
+ :type: :obj:`basestring`
+ """)
+
+ repository_credential = Column(modeling_types.StrictDict(basestring, basestring), doc="""
+ Credentials for accessing the repository.
+
+ :type: {:obj:`basestring`, :obj:`basestring`}
+ """)
+
+ @property
+ def as_raw(self):
+ return collections.OrderedDict((
+ ('name', self.name),
+ ('description', self.description),
+ ('type_name', self.type.name),
+ ('source_path', self.source_path),
+ ('target_path', self.target_path),
+ ('repository_url', self.repository_url),
+ ('repository_credential', formatting.as_agnostic(self.repository_credential)),
+ ('properties', formatting.as_raw_dict(self.properties))))