You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@ariatosca.apache.org by mx...@apache.org on 2017/02/12 16:14:07 UTC
[5/6] incubator-ariatosca git commit: ARIA-44 Merge parser and
storage model
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/b6193359/aria/storage/base_model.py
----------------------------------------------------------------------
diff --git a/aria/storage/base_model.py b/aria/storage/base_model.py
deleted file mode 100644
index f7d0e5b..0000000
--- a/aria/storage/base_model.py
+++ /dev/null
@@ -1,757 +0,0 @@
-# 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's storage.models module
-Path: aria.storage.models
-
-models module holds aria's models.
-
-classes:
- * Field - represents a single field.
- * IterField - represents an iterable field.
- * Model - abstract model implementation.
- * Snapshot - snapshots implementation model.
- * Deployment - deployment implementation model.
- * DeploymentUpdateStep - deployment update step implementation model.
- * DeploymentUpdate - deployment update implementation model.
- * DeploymentModification - deployment modification implementation model.
- * Execution - execution implementation model.
- * Node - node implementation model.
- * Relationship - relationship implementation model.
- * NodeInstance - node instance implementation model.
- * RelationshipInstance - relationship instance implementation model.
- * Plugin - plugin implementation model.
-"""
-from collections import namedtuple
-from datetime import datetime
-
-from sqlalchemy.ext.associationproxy import association_proxy
-from sqlalchemy.ext.declarative import declared_attr
-from sqlalchemy import (
- Column,
- Integer,
- Text,
- DateTime,
- Boolean,
- Enum,
- String,
- Float,
- orm,
-)
-from sqlalchemy.ext.orderinglist import ordering_list
-
-from ..orchestrator.exceptions import TaskAbortException, TaskRetryException
-from .structure import ModelMixin
-from .type import (
- List,
- Dict
-)
-
-__all__ = (
- 'BlueprintBase',
- 'DeploymentBase',
- 'DeploymentUpdateStepBase',
- 'DeploymentUpdateBase',
- 'DeploymentModificationBase',
- 'ExecutionBase',
- 'NodeBase',
- 'RelationshipBase',
- 'NodeInstanceBase',
- 'RelationshipInstanceBase',
- 'PluginBase',
- 'TaskBase'
-)
-
-#pylint: disable=no-self-argument, abstract-method
-
-
-class BlueprintBase(ModelMixin):
- """
- Blueprint model representation.
- """
- __tablename__ = 'blueprints'
-
- created_at = Column(DateTime, nullable=False, index=True)
- main_file_name = Column(Text, nullable=False)
- plan = Column(Dict, nullable=False)
- updated_at = Column(DateTime)
- description = Column(Text)
-
-
-class DeploymentBase(ModelMixin):
- """
- Deployment model representation.
- """
- __tablename__ = 'deployments'
-
- _private_fields = ['blueprint_fk']
-
- created_at = Column(DateTime, nullable=False, index=True)
- description = Column(Text)
- inputs = Column(Dict)
- groups = Column(Dict)
- permalink = Column(Text)
- policy_triggers = Column(Dict)
- policy_types = Column(Dict)
- outputs = Column(Dict)
- scaling_groups = Column(Dict)
- updated_at = Column(DateTime)
- workflows = Column(Dict)
-
- @declared_attr
- def blueprint_fk(cls):
- return cls.foreign_key(BlueprintBase, nullable=False)
-
- @declared_attr
- def blueprint(cls):
- return cls.one_to_many_relationship('blueprint_fk')
-
- @declared_attr
- def blueprint_name(cls):
- return association_proxy('blueprint', cls.name_column_name())
-
-
-class ExecutionBase(ModelMixin):
- """
- Execution model representation.
- """
- # Needed only for pylint. the id will be populated by sqlalcehmy and the proper column.
- __tablename__ = 'executions'
- _private_fields = ['deployment_fk']
-
- TERMINATED = 'terminated'
- FAILED = 'failed'
- CANCELLED = 'cancelled'
- PENDING = 'pending'
- STARTED = 'started'
- CANCELLING = 'cancelling'
- FORCE_CANCELLING = 'force_cancelling'
-
- STATES = [TERMINATED, FAILED, CANCELLED, PENDING, STARTED, CANCELLING, FORCE_CANCELLING]
- END_STATES = [TERMINATED, FAILED, CANCELLED]
- ACTIVE_STATES = [state for state in STATES if state not in END_STATES]
-
- VALID_TRANSITIONS = {
- PENDING: [STARTED, CANCELLED],
- STARTED: END_STATES + [CANCELLING],
- CANCELLING: END_STATES + [FORCE_CANCELLING]
- }
-
- @orm.validates('status')
- def validate_status(self, key, value):
- """Validation function that verifies execution status transitions are OK"""
- try:
- current_status = getattr(self, key)
- except AttributeError:
- return
- valid_transitions = self.VALID_TRANSITIONS.get(current_status, [])
- if all([current_status is not None,
- current_status != value,
- value not in valid_transitions]):
- raise ValueError('Cannot change execution status from {current} to {new}'.format(
- current=current_status,
- new=value))
- return value
-
- created_at = Column(DateTime, index=True)
- started_at = Column(DateTime, nullable=True, index=True)
- ended_at = Column(DateTime, nullable=True, index=True)
- error = Column(Text, nullable=True)
- is_system_workflow = Column(Boolean, nullable=False, default=False)
- parameters = Column(Dict)
- status = Column(Enum(*STATES, name='execution_status'), default=PENDING)
- workflow_name = Column(Text)
-
- @declared_attr
- def blueprint(cls):
- return association_proxy('deployment', 'blueprint')
-
- @declared_attr
- def deployment_fk(cls):
- return cls.foreign_key(DeploymentBase, nullable=True)
-
- @declared_attr
- def deployment(cls):
- return cls.one_to_many_relationship('deployment_fk')
-
- @declared_attr
- def deployment_name(cls):
- return association_proxy('deployment', cls.name_column_name())
-
- @declared_attr
- def blueprint_name(cls):
- return association_proxy('deployment', 'blueprint_name')
-
- def __str__(self):
- return '<{0} id=`{1}` (status={2})>'.format(
- self.__class__.__name__,
- getattr(self, self.name_column_name()),
- self.status
- )
-
-
-class DeploymentUpdateBase(ModelMixin):
- """
- Deployment update model representation.
- """
- # Needed only for pylint. the id will be populated by sqlalcehmy and the proper column.
- steps = None
-
- __tablename__ = 'deployment_updates'
-
- _private_fields = ['execution_fk', 'deployment_fk']
-
- created_at = Column(DateTime, nullable=False, index=True)
- deployment_plan = Column(Dict, nullable=False)
- deployment_update_node_instances = Column(Dict)
- deployment_update_deployment = Column(Dict)
- deployment_update_nodes = Column(List)
- modified_entity_ids = Column(Dict)
- state = Column(Text)
-
- @declared_attr
- def execution_fk(cls):
- return cls.foreign_key(ExecutionBase, nullable=True)
-
- @declared_attr
- def execution(cls):
- return cls.one_to_many_relationship('execution_fk')
-
- @declared_attr
- def execution_name(cls):
- return association_proxy('execution', cls.name_column_name())
-
- @declared_attr
- def deployment_fk(cls):
- return cls.foreign_key(DeploymentBase)
-
- @declared_attr
- def deployment(cls):
- return cls.one_to_many_relationship('deployment_fk')
-
- @declared_attr
- def deployment_name(cls):
- return association_proxy('deployment', cls.name_column_name())
-
- def to_dict(self, suppress_error=False, **kwargs):
- dep_update_dict = super(DeploymentUpdateBase, self).to_dict(suppress_error) #pylint: disable=no-member
- # Taking care of the fact the DeploymentSteps are _BaseModels
- dep_update_dict['steps'] = [step.to_dict() for step in self.steps]
- return dep_update_dict
-
-
-class DeploymentUpdateStepBase(ModelMixin):
- """
- Deployment update step model representation.
- """
- # Needed only for pylint. the id will be populated by sqlalcehmy and the proper column.
- __tablename__ = 'deployment_update_steps'
- _private_fields = ['deployment_update_fk']
-
- _action_types = namedtuple('ACTION_TYPES', 'ADD, REMOVE, MODIFY')
- ACTION_TYPES = _action_types(ADD='add', REMOVE='remove', MODIFY='modify')
- _entity_types = namedtuple(
- 'ENTITY_TYPES',
- 'NODE, RELATIONSHIP, PROPERTY, OPERATION, WORKFLOW, OUTPUT, DESCRIPTION, GROUP, '
- 'POLICY_TYPE, POLICY_TRIGGER, PLUGIN')
- ENTITY_TYPES = _entity_types(
- NODE='node',
- RELATIONSHIP='relationship',
- PROPERTY='property',
- OPERATION='operation',
- WORKFLOW='workflow',
- OUTPUT='output',
- DESCRIPTION='description',
- GROUP='group',
- POLICY_TYPE='policy_type',
- POLICY_TRIGGER='policy_trigger',
- PLUGIN='plugin'
- )
-
- action = Column(Enum(*ACTION_TYPES, name='action_type'), nullable=False)
- entity_id = Column(Text, nullable=False)
- entity_type = Column(Enum(*ENTITY_TYPES, name='entity_type'), nullable=False)
-
- @declared_attr
- def deployment_update_fk(cls):
- return cls.foreign_key(DeploymentUpdateBase)
-
- @declared_attr
- def deployment_update(cls):
- return cls.one_to_many_relationship('deployment_update_fk', backreference='steps')
-
- @declared_attr
- def deployment_update_name(cls):
- return association_proxy('deployment_update', cls.name_column_name())
-
- def __hash__(self):
- return hash((getattr(self, self.id_column_name()), self.entity_id))
-
- def __lt__(self, other):
- """
- the order is 'remove' < 'modify' < 'add'
- :param other:
- :return:
- """
- if not isinstance(other, self.__class__):
- return not self >= other
-
- if self.action != other.action:
- if self.action == 'remove':
- return_value = True
- elif self.action == 'add':
- return_value = False
- else:
- return_value = other.action == 'add'
- return return_value
-
- if self.action == 'add':
- return self.entity_type == 'node' and other.entity_type == 'relationship'
- if self.action == 'remove':
- return self.entity_type == 'relationship' and other.entity_type == 'node'
- return False
-
-
-class DeploymentModificationBase(ModelMixin):
- """
- Deployment modification model representation.
- """
- __tablename__ = 'deployment_modifications'
- _private_fields = ['deployment_fk']
-
- STARTED = 'started'
- FINISHED = 'finished'
- ROLLEDBACK = 'rolledback'
-
- STATES = [STARTED, FINISHED, ROLLEDBACK]
- END_STATES = [FINISHED, ROLLEDBACK]
-
- context = Column(Dict)
- created_at = Column(DateTime, nullable=False, index=True)
- ended_at = Column(DateTime, index=True)
- modified_nodes = Column(Dict)
- node_instances = Column(Dict)
- status = Column(Enum(*STATES, name='deployment_modification_status'))
-
- @declared_attr
- def deployment_fk(cls):
- return cls.foreign_key(DeploymentBase)
-
- @declared_attr
- def deployment(cls):
- return cls.one_to_many_relationship('deployment_fk', backreference='modifications')
-
- @declared_attr
- def deployment_name(cls):
- return association_proxy('deployment', cls.name_column_name())
-
-
-class NodeBase(ModelMixin):
- """
- Node model representation.
- """
- __tablename__ = 'nodes'
-
- # See base class for an explanation on these properties
- is_id_unique = False
-
- _private_fields = ['blueprint_fk', 'host_fk']
-
- @declared_attr
- def host_fk(cls):
- return cls.foreign_key(NodeBase, nullable=True)
-
- @declared_attr
- def host(cls):
- return cls.relationship_to_self('host_fk')
-
- @declared_attr
- def host_name(cls):
- return association_proxy('host', cls.name_column_name())
-
- @declared_attr
- def deployment_fk(cls):
- return cls.foreign_key(DeploymentBase)
-
- @declared_attr
- def deployment(cls):
- return cls.one_to_many_relationship('deployment_fk')
-
- @declared_attr
- def deployment_name(cls):
- return association_proxy('deployment', cls.name_column_name())
-
- @declared_attr
- def blueprint_name(cls):
- return association_proxy('deployment', 'blueprint_{0}'.format(cls.name_column_name()))
-
- deploy_number_of_instances = Column(Integer, nullable=False)
- max_number_of_instances = Column(Integer, nullable=False)
- min_number_of_instances = Column(Integer, nullable=False)
- number_of_instances = Column(Integer, nullable=False)
- planned_number_of_instances = Column(Integer, nullable=False)
- plugins = Column(List)
- properties = Column(Dict)
- operations = Column(Dict)
- type = Column(Text, nullable=False, index=True)
- type_hierarchy = Column(List)
-
-
-class RelationshipBase(ModelMixin):
- """
- Relationship model representation.
- """
- __tablename__ = 'relationships'
-
- _private_fields = ['source_node_fk', 'target_node_fk', 'source_position', 'target_position']
-
- source_position = Column(Integer)
- target_position = Column(Integer)
-
- @declared_attr
- def deployment_id(self):
- return association_proxy('source_node', 'deployment_id')
-
- @declared_attr
- def source_node_fk(cls):
- return cls.foreign_key(NodeBase)
-
- @declared_attr
- def source_node(cls):
- return cls.one_to_many_relationship(
- 'source_node_fk',
- backreference='outbound_relationships',
- backref_kwargs=dict(
- order_by=cls.source_position,
- collection_class=ordering_list('source_position', count_from=0)
- )
- )
-
- @declared_attr
- def source_name(cls):
- return association_proxy('source_node', cls.name_column_name())
-
- @declared_attr
- def target_node_fk(cls):
- return cls.foreign_key(NodeBase, nullable=True)
-
- @declared_attr
- def target_node(cls):
- return cls.one_to_many_relationship(
- 'target_node_fk',
- backreference='inbound_relationships',
- backref_kwargs=dict(
- order_by=cls.target_position,
- collection_class=ordering_list('target_position', count_from=0)
- )
- )
-
- @declared_attr
- def target_name(cls):
- return association_proxy('target_node', cls.name_column_name())
-
- source_interfaces = Column(Dict)
- source_operations = Column(Dict, nullable=False)
- target_interfaces = Column(Dict)
- target_operations = Column(Dict, nullable=False)
- type = Column(String, nullable=False)
- type_hierarchy = Column(List)
- properties = Column(Dict)
-
-
-class NodeInstanceBase(ModelMixin):
- """
- Node instance model representation.
- """
- __tablename__ = 'node_instances'
- _private_fields = ['node_fk', 'host_fk']
-
- runtime_properties = Column(Dict)
- scaling_groups = Column(List)
- state = Column(Text, nullable=False)
- version = Column(Integer, default=1)
-
- @declared_attr
- def host_fk(cls):
- return cls.foreign_key(NodeInstanceBase, nullable=True)
-
- @declared_attr
- def host(cls):
- return cls.relationship_to_self('host_fk')
-
- @declared_attr
- def host_name(cls):
- return association_proxy('host', cls.name_column_name())
-
- @declared_attr
- def deployment(cls):
- return association_proxy('node', 'deployment')
-
- @declared_attr
- def deployment_name(cls):
- return association_proxy('node', 'deployment_name')
-
- @declared_attr
- def node_fk(cls):
- return cls.foreign_key(NodeBase, nullable=True)
-
- @declared_attr
- def node(cls):
- return cls.one_to_many_relationship('node_fk')
-
- @declared_attr
- def node_name(cls):
- return association_proxy('node', cls.name_column_name())
-
- @property
- def ip(self):
- if not self.host_fk:
- return None
- host_node_instance = self.host
- if 'ip' in host_node_instance.runtime_properties: # pylint: disable=no-member
- return host_node_instance.runtime_properties['ip'] # pylint: disable=no-member
- host_node = host_node_instance.node # pylint: disable=no-member
- if 'ip' in host_node.properties:
- return host_node.properties['ip']
- return None
-
-
-class RelationshipInstanceBase(ModelMixin):
- """
- Relationship instance model representation.
- """
- __tablename__ = 'relationship_instances'
- _private_fields = ['relationship_storage_fk',
- 'source_node_instance_fk',
- 'target_node_instance_fk',
- 'source_position',
- 'target_position']
-
- source_position = Column(Integer)
- target_position = Column(Integer)
-
- @declared_attr
- def source_node_instance_fk(cls):
- return cls.foreign_key(NodeInstanceBase, nullable=True)
-
- @declared_attr
- def source_node_instance(cls):
- return cls.one_to_many_relationship(
- 'source_node_instance_fk',
- backreference='outbound_relationship_instances',
- backref_kwargs=dict(
- order_by=cls.source_position,
- collection_class=ordering_list('source_position', count_from=0)
- )
- )
-
- @declared_attr
- def source_node_instance_name(cls):
- return association_proxy('source_node_instance', 'node_{0}'.format(cls.name_column_name()))
-
- @declared_attr
- def source_node_name(cls):
- return association_proxy('source_node_instance', cls.name_column_name())
-
- @declared_attr
- def target_node_instance_fk(cls):
- return cls.foreign_key(NodeInstanceBase, nullable=True)
-
- @declared_attr
- def target_node_instance(cls):
- return cls.one_to_many_relationship(
- 'target_node_instance_fk',
- backreference='inbound_relationship_instances',
- backref_kwargs=dict(
- order_by=cls.target_position,
- collection_class=ordering_list('target_position', count_from=0)
- )
- )
-
- @declared_attr
- def target_node_instance_name(cls):
- return association_proxy('target_node_instance', cls.name_column_name())
-
- @declared_attr
- def target_node_name(cls):
- return association_proxy('target_node_instance', 'node_{0}'.format(cls.name_column_name()))
-
- @declared_attr
- def relationship_fk(cls):
- return cls.foreign_key(RelationshipBase)
-
- @declared_attr
- def relationship(cls):
- return cls.one_to_many_relationship('relationship_fk')
-
- @declared_attr
- def relationship_name(cls):
- return association_proxy('relationship', cls.name_column_name())
-
-
-
-class PluginBase(ModelMixin):
- """
- Plugin model representation.
- """
- __tablename__ = 'plugins'
-
- archive_name = Column(Text, nullable=False, index=True)
- distribution = Column(Text)
- distribution_release = Column(Text)
- distribution_version = Column(Text)
- package_name = Column(Text, nullable=False, index=True)
- package_source = Column(Text)
- package_version = Column(Text)
- supported_platform = Column(Text)
- supported_py_versions = Column(List)
- uploaded_at = Column(DateTime, nullable=False, index=True)
- wheels = Column(List, nullable=False)
-
-
-class TaskBase(ModelMixin):
- """
- A Model which represents an task
- """
- __tablename__ = 'tasks'
- _private_fields = ['node_instance_fk', 'relationship_instance_fk', 'execution_fk']
-
- @declared_attr
- def node_instance_fk(cls):
- return cls.foreign_key(NodeInstanceBase, nullable=True)
-
- @declared_attr
- def node_instance_name(cls):
- return association_proxy('node_instance', cls.name_column_name())
-
- @declared_attr
- def node_instance(cls):
- return cls.one_to_many_relationship('node_instance_fk')
-
- @declared_attr
- def relationship_instance_fk(cls):
- return cls.foreign_key(RelationshipInstanceBase, nullable=True)
-
- @declared_attr
- def relationship_instance_name(cls):
- return association_proxy('relationship_instance', cls.name_column_name())
-
- @declared_attr
- def relationship_instance(cls):
- return cls.one_to_many_relationship('relationship_instance_fk')
-
- @declared_attr
- def plugin_fk(cls):
- return cls.foreign_key(PluginBase, nullable=True)
-
- @declared_attr
- def plugin(cls):
- return cls.one_to_many_relationship('plugin_fk')
-
- @declared_attr
- def execution_fk(cls):
- return cls.foreign_key(ExecutionBase, nullable=True)
-
- @declared_attr
- def execution(cls):
- return cls.one_to_many_relationship('execution_fk')
-
- @declared_attr
- def execution_name(cls):
- return association_proxy('execution', cls.name_column_name())
-
- PENDING = 'pending'
- RETRYING = 'retrying'
- SENT = 'sent'
- STARTED = 'started'
- SUCCESS = 'success'
- FAILED = 'failed'
- STATES = (
- PENDING,
- RETRYING,
- SENT,
- STARTED,
- SUCCESS,
- FAILED,
- )
-
- WAIT_STATES = [PENDING, RETRYING]
- END_STATES = [SUCCESS, FAILED]
-
- RUNS_ON_SOURCE = 'source'
- RUNS_ON_TARGET = 'target'
- RUNS_ON_NODE_INSTANCE = 'node_instance'
- RUNS_ON = (RUNS_ON_NODE_INSTANCE, RUNS_ON_SOURCE, RUNS_ON_TARGET)
-
- @orm.validates('max_attempts')
- def validate_max_attempts(self, _, value): # pylint: disable=no-self-use
- """Validates that max attempts is either -1 or a positive number"""
- if value < 1 and value != TaskBase.INFINITE_RETRIES:
- raise ValueError('Max attempts can be either -1 (infinite) or any positive number. '
- 'Got {value}'.format(value=value))
- return value
-
- INFINITE_RETRIES = -1
-
- status = Column(Enum(*STATES, name='status'), default=PENDING)
-
- due_at = Column(DateTime, default=datetime.utcnow)
- started_at = Column(DateTime, default=None)
- ended_at = Column(DateTime, default=None)
- max_attempts = Column(Integer, default=1)
- retry_count = Column(Integer, default=0)
- retry_interval = Column(Float, default=0)
- ignore_failure = Column(Boolean, default=False)
-
- # Operation specific fields
- operation_mapping = Column(String)
- inputs = Column(Dict)
- plugin_name = Column(String)
- _runs_on = Column(Enum(*RUNS_ON, name='runs_on'), name='runs_on')
-
- @property
- def actor(self):
- """
- Return the actor of the task
- :return:
- """
- return self.node_instance or self.relationship_instance
-
- @property
- def runs_on(self):
- if self._runs_on == self.RUNS_ON_NODE_INSTANCE:
- return self.node_instance
- elif self._runs_on == self.RUNS_ON_SOURCE:
- return self.relationship_instance.source_node_instance # pylint: disable=no-member
- elif self._runs_on == self.RUNS_ON_TARGET:
- return self.relationship_instance.target_node_instance # pylint: disable=no-member
- return None
-
- @classmethod
- def as_node_instance(cls, instance, runs_on, **kwargs):
- return cls(node_instance=instance, _runs_on=runs_on, **kwargs)
-
- @classmethod
- def as_relationship_instance(cls, instance, runs_on, **kwargs):
- return cls(relationship_instance=instance, _runs_on=runs_on, **kwargs)
-
- @staticmethod
- def abort(message=None):
- raise TaskAbortException(message)
-
- @staticmethod
- def retry(message=None, retry_interval=None):
- raise TaskRetryException(message, retry_interval=retry_interval)
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/b6193359/aria/storage/core.py
----------------------------------------------------------------------
diff --git a/aria/storage/core.py b/aria/storage/core.py
index 3878dca..0e189e6 100644
--- a/aria/storage/core.py
+++ b/aria/storage/core.py
@@ -39,10 +39,7 @@ API:
"""
from aria.logger import LoggerMixin
-from . import (
- api as storage_api,
- sql_mapi
-)
+from . import sql_mapi
__all__ = (
'Storage',
@@ -146,7 +143,7 @@ class ModelStorage(Storage):
:param model_cls: the model to register.
:return:
"""
- model_name = storage_api.generate_lower_name(model_cls)
+ model_name = model_cls.__modelname__
if model_name in self.registered:
self.logger.debug('{name} in already storage {self!r}'.format(name=model_name,
self=self))
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/b6193359/aria/storage/instrumentation.py
----------------------------------------------------------------------
diff --git a/aria/storage/instrumentation.py b/aria/storage/instrumentation.py
index 537dbb5..57fe9bd 100644
--- a/aria/storage/instrumentation.py
+++ b/aria/storage/instrumentation.py
@@ -17,12 +17,11 @@ import copy
import sqlalchemy.event
-from . import api
-from . import model as _model
+from .modeling import model as _model
_STUB = object()
_INSTRUMENTED = {
- _model.NodeInstance.runtime_properties: dict
+ _model.Node.runtime_properties: dict
}
@@ -75,7 +74,7 @@ class _Instrumentation(object):
def _register_set_attribute_listener(self, instrumented_attribute, attribute_type):
def listener(target, value, *_):
- mapi_name = self._mapi_name(target.__class__)
+ mapi_name = target.__modelname__
tracked_instances = self.tracked_changes.setdefault(mapi_name, {})
tracked_attributes = tracked_instances.setdefault(target.id, {})
if value is None:
@@ -90,7 +89,7 @@ class _Instrumentation(object):
def _register_instance_listeners(self, instrumented_class, instrumented_attributes):
def listener(target, *_):
- mapi_name = self._mapi_name(instrumented_class)
+ mapi_name = instrumented_class.__modelname__
tracked_instances = self.tracked_changes.setdefault(mapi_name, {})
tracked_attributes = tracked_instances.setdefault(target.id, {})
for attribute_name, attribute_type in instrumented_attributes.items():
@@ -110,7 +109,7 @@ class _Instrumentation(object):
def clear(self, target=None):
if target:
- mapi_name = self._mapi_name(target.__class__)
+ mapi_name = target.__modelname__
tracked_instances = self.tracked_changes.setdefault(mapi_name, {})
tracked_instances.pop(target.id, None)
else:
@@ -128,10 +127,6 @@ class _Instrumentation(object):
def __exit__(self, exc_type, exc_val, exc_tb):
self.restore()
- @staticmethod
- def _mapi_name(instrumented_class):
- return api.generate_lower_name(instrumented_class)
-
class _Value(object):
# You may wonder why is this a full blown class and not a named tuple. The reason is that
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/b6193359/aria/storage/model.py
----------------------------------------------------------------------
diff --git a/aria/storage/model.py b/aria/storage/model.py
deleted file mode 100644
index afca3e4..0000000
--- a/aria/storage/model.py
+++ /dev/null
@@ -1,110 +0,0 @@
-# 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's storage.models module
-Path: aria.storage.models
-
-models module holds aria's models.
-
-classes:
- * Field - represents a single field.
- * IterField - represents an iterable field.
- * Model - abstract model implementation.
- * Snapshot - snapshots implementation model.
- * Deployment - deployment implementation model.
- * DeploymentUpdateStep - deployment update step implementation model.
- * DeploymentUpdate - deployment update implementation model.
- * DeploymentModification - deployment modification implementation model.
- * Execution - execution implementation model.
- * Node - node implementation model.
- * Relationship - relationship implementation model.
- * NodeInstance - node instance implementation model.
- * RelationshipInstance - relationship instance implementation model.
- * ProviderContext - provider context implementation model.
- * Plugin - plugin implementation model.
-"""
-from sqlalchemy.ext.declarative import declarative_base
-
-from . import structure
-from . import base_model as base
-
-__all__ = (
- 'Blueprint',
- 'Deployment',
- 'DeploymentUpdateStep',
- 'DeploymentUpdate',
- 'DeploymentModification',
- 'Execution',
- 'Node',
- 'Relationship',
- 'NodeInstance',
- 'RelationshipInstance',
- 'Plugin',
-)
-
-
-#pylint: disable=abstract-method
-# The required abstract method implementation are implemented in the ModelIDMixin, which is used as
-# a base to the DeclerativeBase.
-DeclarativeBase = declarative_base(cls=structure.ModelIDMixin)
-
-
-class Blueprint(DeclarativeBase, base.BlueprintBase):
- pass
-
-
-class Deployment(DeclarativeBase, base.DeploymentBase):
- pass
-
-
-class Execution(DeclarativeBase, base.ExecutionBase):
- pass
-
-
-class DeploymentUpdate(DeclarativeBase, base.DeploymentUpdateBase):
- pass
-
-
-class DeploymentUpdateStep(DeclarativeBase, base.DeploymentUpdateStepBase):
- pass
-
-
-class DeploymentModification(DeclarativeBase, base.DeploymentModificationBase):
- pass
-
-
-class Node(DeclarativeBase, base.NodeBase):
- pass
-
-
-class Relationship(DeclarativeBase, base.RelationshipBase):
- pass
-
-
-class NodeInstance(DeclarativeBase, base.NodeInstanceBase):
- pass
-
-
-class RelationshipInstance(DeclarativeBase, base.RelationshipInstanceBase):
- pass
-
-
-class Plugin(DeclarativeBase, base.PluginBase):
- pass
-
-
-class Task(DeclarativeBase, base.TaskBase):
- pass
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/b6193359/aria/storage/modeling/__init__.py
----------------------------------------------------------------------
diff --git a/aria/storage/modeling/__init__.py b/aria/storage/modeling/__init__.py
new file mode 100644
index 0000000..697ed09
--- /dev/null
+++ b/aria/storage/modeling/__init__.py
@@ -0,0 +1,35 @@
+# 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.
+
+from collections import namedtuple
+
+from . import (
+ model,
+ instance_elements as _instance_base,
+ orchestrator_elements as _orchestrator_base,
+ template_elements as _template_base,
+)
+
+_ModelBaseCls = namedtuple('ModelBase', 'instance_elements,'
+ 'orchestrator_elements,'
+ 'template_elements')
+model_base = _ModelBaseCls(instance_elements=_instance_base,
+ orchestrator_elements=_orchestrator_base,
+ template_elements=_template_base)
+
+__all__ = (
+ 'model',
+ 'model_base',
+)
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/b6193359/aria/storage/modeling/elements.py
----------------------------------------------------------------------
diff --git a/aria/storage/modeling/elements.py b/aria/storage/modeling/elements.py
new file mode 100644
index 0000000..8c720b9
--- /dev/null
+++ b/aria/storage/modeling/elements.py
@@ -0,0 +1,106 @@
+# 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.
+
+from sqlalchemy import (
+ Column,
+ Text
+)
+
+from ...parser.modeling import utils
+from ...utils.collections import OrderedDict
+from ...utils.console import puts
+from .. import exceptions
+
+from . import structure
+from . import type
+
+# pylint: disable=no-self-argument, no-member, abstract-method
+
+
+class ParameterBase(structure.ModelMixin):
+ """
+ Represents a typed value.
+
+ This class is used by both service model and service instance elements.
+ """
+ __tablename__ = 'parameter'
+ name = Column(Text, nullable=False)
+ type = Column(Text, nullable=False)
+
+ # Check: value type
+ str_value = Column(Text)
+ description = Column(Text)
+
+ @property
+ def as_raw(self):
+ return OrderedDict((
+ ('name', self.name),
+ ('type_name', self.type),
+ ('value', self.value),
+ ('description', self.description)))
+
+ @property
+ def value(self):
+ if self.type is None:
+ return
+ try:
+ if self.type.lower() in ['str', 'unicode']:
+ return self.str_value.decode('utf-8')
+ elif self.type.lower() == 'int':
+ return int(self.str_value)
+ elif self.type.lower() == 'bool':
+ return bool(self.str_value)
+ elif self.type.lower() == 'float':
+ return float(self.str_value)
+ else:
+ raise exceptions.StorageError('No supported type_name was provided')
+ except ValueError:
+ raise exceptions.StorageError('Trying to cast {0} to {1} failed'.format(self.str_value,
+ self.type))
+
+ def instantiate(self, context, container):
+ return ParameterBase(self.type, self.str_value, self.description)
+
+ def coerce_values(self, context, container, report_issues):
+ if self.str_value is not None:
+ self.str_value = utils.coerce_value(context, container, self.str_value, report_issues)
+
+
+class MetadataBase(structure.ModelMixin):
+ """
+ Custom values associated with the deployment template and its plans.
+
+ This class is used by both service model and service instance elements.
+
+ Properties:
+
+ * :code:`values`: Dict of custom values
+ """
+ values = Column(type.StrictDict(key_cls=basestring))
+
+ @property
+ def as_raw(self):
+ return self.values
+
+ def instantiate(self, context, container):
+ metadata = MetadataBase()
+ metadata.values.update(self.values)
+ return metadata
+
+ def dump(self, context):
+ puts('Metadata:')
+ with context.style.indent:
+ for name, value in self.values.iteritems():
+ puts('%s: %s' % (name, context.style.meta(value)))
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/b6193359/aria/storage/modeling/instance_elements.py
----------------------------------------------------------------------
diff --git a/aria/storage/modeling/instance_elements.py b/aria/storage/modeling/instance_elements.py
new file mode 100644
index 0000000..0666c8a
--- /dev/null
+++ b/aria/storage/modeling/instance_elements.py
@@ -0,0 +1,1286 @@
+# 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.
+
+from sqlalchemy import (
+ Column,
+ Text,
+ Integer,
+ Boolean,
+)
+from sqlalchemy import DateTime
+from sqlalchemy.ext.associationproxy import association_proxy
+from sqlalchemy.ext.declarative import declared_attr
+from sqlalchemy.ext.orderinglist import ordering_list
+
+from aria.parser import validation
+from aria.utils import collections, formatting, console
+
+from . import (
+ utils,
+ structure,
+ type as aria_types
+)
+
+# pylint: disable=no-self-argument, no-member, abstract-method
+
+# region Element instances
+
+
+class ServiceInstanceBase(structure.ModelMixin):
+ __tablename__ = 'service_instance'
+
+ __private_fields__ = ['substituion_fk',
+ 'service_template_fk']
+
+ description = Column(Text)
+ _metadata = Column(Text)
+
+ # region orchestrator required columns
+
+ created_at = Column(DateTime, nullable=False, index=True)
+ permalink = Column(Text)
+ policy_triggers = Column(aria_types.Dict)
+ policy_types = Column(aria_types.Dict)
+ scaling_groups = Column(aria_types.Dict)
+ updated_at = Column(DateTime)
+ workflows = Column(aria_types.Dict)
+
+ @declared_attr
+ def service_template_name(cls):
+ return association_proxy('service_template', 'name')
+
+ # endregion
+
+ # region foreign keys
+ @declared_attr
+ def substitution_fk(cls):
+ return cls.foreign_key('substitution', nullable=True)
+
+ @declared_attr
+ def service_template_fk(cls):
+ return cls.foreign_key('service_template')
+
+ # endregion
+
+ # region one-to-one relationships
+ @declared_attr
+ def substitution(cls):
+ return cls.one_to_one_relationship('substitution')
+ # endregion
+
+ # region many-to-one relationships
+ @declared_attr
+ def service_template(cls):
+ return cls.many_to_one_relationship('service_template')
+
+ # endregion
+
+ # region many-to-many relationships
+ @declared_attr
+ def inputs(cls):
+ return cls.many_to_many_relationship('parameter', table_prefix='inputs')
+
+ @declared_attr
+ def outputs(cls):
+ return cls.many_to_many_relationship('parameter', table_prefix='outputs')
+
+ # endregion
+
+ # association proxies
+
+ def satisfy_requirements(self, context):
+ satisfied = True
+ for node in self.nodes.all():
+ if not node.satisfy_requirements(context):
+ satisfied = False
+ return satisfied
+
+ def validate_capabilities(self, context):
+ satisfied = True
+ for node in self.nodes.all():
+ if not node.validate_capabilities(context):
+ satisfied = False
+ return satisfied
+
+ def find_nodes(self, node_template_name):
+ nodes = []
+ for node in self.nodes.all():
+ if node.template_name == node_template_name:
+ nodes.append(node)
+ return collections.FrozenList(nodes)
+
+ def get_node_ids(self, node_template_name):
+ return collections.FrozenList((node.id for node in self.find_nodes(node_template_name)))
+
+ def find_groups(self, group_template_name):
+ groups = []
+ for group in self.groups.all():
+ if group.template_name == group_template_name:
+ groups.append(group)
+ return collections.FrozenList(groups)
+
+ def get_group_ids(self, group_template_name):
+ return collections.FrozenList((group.id for group in self.find_groups(group_template_name)))
+
+ def is_node_a_target(self, context, target_node):
+ for node in self.nodes.all():
+ if self._is_node_a_target(context, node, target_node):
+ return True
+ return False
+
+ def _is_node_a_target(self, context, source_node, target_node):
+ if source_node.relationships:
+ for relationship in source_node.relationships:
+ if relationship.target_node_id == target_node.id:
+ return True
+ else:
+ node = context.modeling.instance.nodes.get(relationship.target_node_id)
+ if node is not None:
+ if self._is_node_a_target(context, node, target_node):
+ return True
+ return False
+
+
+class OperationBase(structure.ModelMixin):
+ """
+ An operation in a :class:`Interface`.
+
+ Properties:
+
+ * :code:`name`: Name
+ * :code:`description`: Description
+ * :code:`implementation`: Implementation string (interpreted by the orchestrator)
+ * :code:`dependencies`: List of strings (interpreted by the orchestrator)
+ * :code:`executor`: Executor string (interpreted by the orchestrator)
+ * :code:`max_retries`: Maximum number of retries allowed in case of failure
+ * :code:`retry_interval`: Interval between retries
+ * :code:`inputs`: Dict of :class:`Parameter`
+ """
+ __tablename__ = 'operation'
+
+ __private_fields__ = ['service_template_fk',
+ 'interface_instance_fk']
+
+ # region foreign_keys
+
+ @declared_attr
+ def service_instance_fk(cls):
+ return cls.foreign_key('service_instance', nullable=True)
+
+ @declared_attr
+ def interface_instance_fk(cls):
+ return cls.foreign_key('interface', nullable=True)
+
+ # endregion
+ description = Column(Text)
+ implementation = Column(Text)
+ dependencies = Column(aria_types.StrictList(item_cls=basestring))
+
+ executor = Column(Text)
+ max_retries = Column(Integer, default=None)
+ retry_interval = Column(Integer, default=None)
+ plugin = Column(Text)
+ operation = Column(Boolean)
+
+ # region many-to-one relationships
+ @declared_attr
+ def service_instance(cls):
+ return cls.many_to_one_relationship('service_instance')
+
+ @declared_attr
+ def interface(cls):
+ return cls.many_to_one_relationship('interface')
+ # region many-to-many relationships
+
+ @declared_attr
+ def inputs(cls):
+ return cls.many_to_many_relationship('parameter', table_prefix='inputs')
+
+ # endregion
+
+ @property
+ def as_raw(self):
+ return collections.OrderedDict((
+ ('name', self.name),
+ ('description', self.description),
+ ('implementation', self.implementation),
+ ('dependencies', self.dependencies),
+ ('executor', self.executor),
+ ('max_retries', self.max_retries),
+ ('retry_interval', self.retry_interval),
+ ('inputs', formatting.as_raw_dict(self.inputs))))
+
+ def validate(self, context):
+ utils.validate_dict_values(context, self.inputs)
+
+ def coerce_values(self, context, container, report_issues):
+ utils.coerce_dict_values(context, container, self.inputs, report_issues)
+
+ def dump(self, context):
+ console.puts(context.style.node(self.name))
+ if self.description:
+ console.puts(context.style.meta(self.description))
+ with context.style.indent:
+ if self.implementation is not None:
+ console.puts('Implementation: %s' % context.style.literal(self.implementation))
+ if self.dependencies:
+ console.puts(
+ 'Dependencies: %s'
+ % ', '.join((str(context.style.literal(v)) for v in self.dependencies)))
+ if self.executor is not None:
+ console.puts('Executor: %s' % context.style.literal(self.executor))
+ if self.max_retries is not None:
+ console.puts('Max retries: %s' % context.style.literal(self.max_retries))
+ if self.retry_interval is not None:
+ console.puts('Retry interval: %s' % context.style.literal(self.retry_interval))
+ utils.dump_parameters(context, self.inputs, 'Inputs')
+
+
+class InterfaceBase(structure.ModelMixin):
+ """
+ A typed set of :class:`Operation`.
+
+ Properties:
+
+ * :code:`name`: Name
+ * :code:`description`: Description
+ * :code:`type_name`: Must be represented in the :class:`ModelingContext`
+ * :code:`inputs`: Dict of :class:`Parameter`
+ * :code:`operations`: Dict of :class:`Operation`
+ """
+ __tablename__ = 'interface'
+
+ __private_fields__ = ['group_fk',
+ 'node_fk',
+ 'relationship_fk']
+
+
+ # region foreign_keys
+ @declared_attr
+ def group_fk(cls):
+ return cls.foreign_key('group', nullable=True)
+
+ @declared_attr
+ def node_fk(cls):
+ return cls.foreign_key('node', nullable=True)
+
+ @declared_attr
+ def relationship_fk(cls):
+ return cls.foreign_key('relationship', nullable=True)
+
+ # endregion
+
+ description = Column(Text)
+ type_name = Column(Text)
+ edge = Column(Text)
+
+ # region many-to-one relationships
+
+ @declared_attr
+ def node(cls):
+ return cls.many_to_one_relationship('node')
+
+ @declared_attr
+ def relationship(cls):
+ return cls.many_to_one_relationship('relationship')
+
+ @declared_attr
+ def group(cls):
+ return cls.many_to_one_relationship('group')
+
+ # endregion
+
+ # region many-to-many relationships
+
+ @declared_attr
+ def inputs(cls):
+ return cls.many_to_many_relationship('parameter', table_prefix='inputs')
+
+ # endregion
+
+ @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))))
+
+ def validate(self, context):
+ if self.type_name:
+ if context.modeling.interface_types.get_descendant(self.type_name) is None:
+ context.validation.report('interface "%s" has an unknown type: %s'
+ % (self.name,
+ formatting.safe_repr(self.type_name)),
+ level=validation.Issue.BETWEEN_TYPES)
+
+ utils.validate_dict_values(context, self.inputs)
+ utils.validate_dict_values(context, self.operations)
+
+ def coerce_values(self, context, container, report_issues):
+ utils.coerce_dict_values(context, container, self.inputs, report_issues)
+ utils.coerce_dict_values(context, container, self.operations, report_issues)
+
+ def dump(self, context):
+ console.puts(context.style.node(self.name))
+ if self.description:
+ console.puts(context.style.meta(self.description))
+ with context.style.indent:
+ console.puts('Interface type: %s' % context.style.type(self.type_name))
+ utils.dump_parameters(context, self.inputs, 'Inputs')
+ utils.dump_dict_values(context, self.operations, 'Operations')
+
+
+class CapabilityBase(structure.ModelMixin):
+ """
+ A capability of a :class:`Node`.
+
+ An instance of a :class:`CapabilityTemplate`.
+
+ Properties:
+
+ * :code:`name`: Name
+ * :code:`type_name`: Must be represented in the :class:`ModelingContext`
+ * :code:`min_occurrences`: Minimum number of requirement matches required
+ * :code:`max_occurrences`: Maximum number of requirement matches allowed
+ * :code:`properties`: Dict of :class:`Parameter`
+ """
+ __tablename__ = 'capability'
+
+ __private_fields__ = ['node_fk']
+
+ # region foreign_keys
+ @declared_attr
+ def node_fk(cls):
+ return cls.foreign_key('node')
+
+ # endregion
+ type_name = Column(Text)
+
+ min_occurrences = Column(Integer, default=None) # optional
+ max_occurrences = Column(Integer, default=None) # optional
+ occurrences = Column(Integer, default=0)
+
+ # region many-to-one relationships
+ @declared_attr
+ def node(cls):
+ return cls.many_to_one_relationship('node')
+
+ # endregion
+
+
+ # region many-to-many relationships
+ @declared_attr
+ def properties(cls):
+ return cls.many_to_many_relationship('parameter', table_prefix='properties')
+
+ # endregion
+
+ @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))))
+
+ def validate(self, context):
+ if context.modeling.capability_types.get_descendant(self.type_name) is None:
+ context.validation.report('capability "%s" has an unknown type: %s'
+ % (self.name,
+ formatting.safe_repr(self.type_name)),
+ level=validation.Issue.BETWEEN_TYPES)
+
+ utils.validate_dict_values(context, self.properties)
+
+ def coerce_values(self, context, container, report_issues):
+ utils.coerce_dict_values(context, container, self.properties, report_issues)
+
+ def dump(self, context):
+ console.puts(context.style.node(self.name))
+ with context.style.indent:
+ console.puts('Type: %s' % context.style.type(self.type_name))
+ console.puts('Occurrences: %s (%s%s)'
+ % (self.occurrences,
+ self.min_occurrences or 0,
+ (' to %d' % self.max_occurrences)
+ if self.max_occurrences is not None
+ else ' or more'))
+ utils.dump_parameters(context, self.properties)
+
+
+class ArtifactBase(structure.ModelMixin):
+ """
+ A file associated with a :class:`Node`.
+
+ Properties:
+
+ * :code:`name`: Name
+ * :code:`description`: Description
+ * :code:`type_name`: Must be represented in the :class:`ModelingContext`
+ * :code:`source_path`: Source path (CSAR or repository)
+ * :code:`target_path`: Path at destination machine
+ * :code:`repository_url`: Repository URL
+ * :code:`repository_credential`: Dict of string
+ * :code:`properties`: Dict of :class:`Parameter`
+ """
+ __tablename__ = 'artifact'
+
+ __private_fields__ = ['node_fk']
+
+ # region foreign_keys
+
+ @declared_attr
+ def node_fk(cls):
+ return cls.foreign_key('node')
+
+ # endregion
+
+ description = Column(Text)
+ type_name = Column(Text)
+ source_path = Column(Text)
+ target_path = Column(Text)
+ repository_url = Column(Text)
+ repository_credential = Column(aria_types.StrictDict(basestring, basestring))
+
+ # region many-to-one relationships
+ @declared_attr
+ def node(cls):
+ return cls.many_to_one_relationship('node')
+
+ # endregion
+
+
+ # region many-to-many relationships
+
+ @declared_attr
+ def properties(cls):
+ return cls.many_to_many_relationship('parameter', table_prefix='properties')
+
+ # endregion
+
+ @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))))
+
+ def validate(self, context):
+ if context.modeling.artifact_types.get_descendant(self.type_name) is None:
+ context.validation.report('artifact "%s" has an unknown type: %s'
+ % (self.name,
+ formatting.safe_repr(self.type_name)),
+ level=validation.Issue.BETWEEN_TYPES)
+ utils.validate_dict_values(context, self.properties)
+
+ def coerce_values(self, context, container, report_issues):
+ utils.coerce_dict_values(context, container, self.properties, report_issues)
+
+ def dump(self, context):
+ console.puts(context.style.node(self.name))
+ if self.description:
+ console.puts(context.style.meta(self.description))
+ with context.style.indent:
+ console.puts('Artifact type: %s' % context.style.type(self.type_name))
+ console.puts('Source path: %s' % context.style.literal(self.source_path))
+ if self.target_path is not None:
+ console.puts('Target path: %s' % context.style.literal(self.target_path))
+ if self.repository_url is not None:
+ console.puts('Repository URL: %s' % context.style.literal(self.repository_url))
+ if self.repository_credential:
+ console.puts('Repository credential: %s'
+ % context.style.literal(self.repository_credential))
+ utils.dump_parameters(context, self.properties)
+
+
+class PolicyBase(structure.ModelMixin):
+ """
+ An instance of a :class:`PolicyTemplate`.
+
+ Properties:
+
+ * :code:`name`: Name
+ * :code:`type_name`: Must be represented in the :class:`ModelingContext`
+ * :code:`properties`: Dict of :class:`Parameter`
+ * :code:`target_node_ids`: Must be represented in the :class:`ServiceInstance`
+ * :code:`target_group_ids`: Must be represented in the :class:`ServiceInstance`
+ """
+ __tablename__ = 'policy'
+
+ __private_fields__ = ['service_instance_fk']
+
+ # region foreign_keys
+
+ @declared_attr
+ def service_instance_fk(cls):
+ return cls.foreign_key('service_instance')
+
+ # endregion
+ type_name = Column(Text)
+ target_node_ids = Column(aria_types.StrictList(basestring))
+ target_group_ids = Column(aria_types.StrictList(basestring))
+
+ # region many-to-one relationships
+ @declared_attr
+ def service_instnce(cls):
+ return cls.many_to_one_relationship('service_instance')
+
+ # region many-to-many relationships
+
+ @declared_attr
+ def properties(cls):
+ return cls.many_to_many_relationship('parameter', table_prefix='properties')
+
+ # endregion
+
+ @property
+ def as_raw(self):
+ return collections.OrderedDict((
+ ('name', self.name),
+ ('type_name', self.type_name),
+ ('properties', formatting.as_raw_dict(self.properties)),
+ ('target_node_ids', self.target_node_ids),
+ ('target_group_ids', self.target_group_ids)))
+
+ def validate(self, context):
+ if context.modeling.policy_types.get_descendant(self.type_name) is None:
+ context.validation.report('policy "%s" has an unknown type: %s'
+ % (self.name, utils.safe_repr(self.type_name)),
+ level=validation.Issue.BETWEEN_TYPES)
+
+ utils.validate_dict_values(context, self.properties)
+
+ def coerce_values(self, context, container, report_issues):
+ utils.coerce_dict_values(context, container, self.properties, report_issues)
+
+ def dump(self, context):
+ console.puts('Policy: %s' % context.style.node(self.name))
+ with context.style.indent:
+ console.puts('Type: %s' % context.style.type(self.type_name))
+ utils.dump_parameters(context, self.properties)
+ if self.target_node_ids:
+ console.puts('Target nodes:')
+ with context.style.indent:
+ for node_id in self.target_node_ids:
+ console.puts(context.style.node(node_id))
+ if self.target_group_ids:
+ console.puts('Target groups:')
+ with context.style.indent:
+ for group_id in self.target_group_ids:
+ console.puts(context.style.node(group_id))
+
+
+class GroupPolicyBase(structure.ModelMixin):
+ """
+ Policies applied to groups.
+
+ Properties:
+
+ * :code:`name`: Name
+ * :code:`description`: Description
+ * :code:`type_name`: Must be represented in the :class:`ModelingContext`
+ * :code:`properties`: Dict of :class:`Parameter`
+ * :code:`triggers`: Dict of :class:`GroupPolicyTrigger`
+ """
+ __tablename__ = 'group_policy'
+
+ __private_fields__ = ['group_fk']
+
+ # region foreign_keys
+
+ @declared_attr
+ def group_fk(cls):
+ return cls.foreign_key('group')
+
+ # endregion
+
+ description = Column(Text)
+ type_name = Column(Text)
+
+ # region many-to-one relationships
+ @declared_attr
+ def group(cls):
+ return cls.many_to_one_relationship('group')
+
+ # end region
+
+ # region many-to-many relationships
+ @declared_attr
+ def properties(cls):
+ return cls.many_to_many_relationship('parameter', table_prefix='properties')
+
+ # endregion
+
+ @property
+ def as_raw(self):
+ return collections.OrderedDict((
+ ('name', self.name),
+ ('description', self.description),
+ ('type_name', self.type_name),
+ ('properties', formatting.as_raw_dict(self.properties)),
+ ('triggers', formatting.as_raw_list(self.triggers))))
+
+ def validate(self, context):
+ if context.modeling.policy_types.get_descendant(self.type_name) is None:
+ context.validation.report(
+ 'group policy "%s" has an unknown type: %s'
+ % (self.name,
+ formatting.safe_repr(self.type_name)),
+ level=validation.Issue.BETWEEN_TYPES)
+
+ utils.validate_dict_values(context, self.properties)
+ utils.validate_dict_values(context, self.triggers)
+
+ def coerce_values(self, context, container, report_issues):
+ utils.coerce_dict_values(context, container, self.properties, report_issues)
+ utils.coerce_dict_values(context, container, self.triggers, report_issues)
+
+ def dump(self, context):
+ console.puts(context.style.node(self.name))
+ if self.description:
+ console.puts(context.style.meta(self.description))
+ with context.style.indent:
+ console.puts('Group policy type: %s' % context.style.type(self.type_name))
+ utils.dump_parameters(context, self.properties)
+ utils.dump_dict_values(context, self.triggers, 'Triggers')
+
+
+class GroupPolicyTriggerBase(structure.ModelMixin):
+ """
+ Triggers for :class:`GroupPolicy`.
+
+ Properties:
+
+ * :code:`name`: Name
+ * :code:`description`: Description
+ * :code:`implementation`: Implementation string (interpreted by the orchestrator)
+ * :code:`properties`: Dict of :class:`Parameter`
+ """
+ __tablename__ = 'group_policy_trigger'
+
+ __private_fields__ = ['group_policy_fk']
+
+ # region foreign keys
+
+ @declared_attr
+ def group_policy_fk(cls):
+ return cls.foreign_key('group_policy')
+
+ # endregion
+
+ description = Column(Text)
+ implementation = Column(Text)
+
+ # region many-to-one relationships
+
+ @declared_attr
+ def group_policy(cls):
+ return cls.many_to_one_relationship('group_policy')
+
+ # endregion
+
+ # region many-to-many relationships
+
+ @declared_attr
+ def properties(cls):
+ return cls.many_to_many_relationship('parameter', table_prefix='properties')
+
+ # endregion
+
+ @property
+ def as_raw(self):
+ return collections.OrderedDict((
+ ('name', self.name),
+ ('description', self.description),
+ ('implementation', self.implementation),
+ ('properties', formatting.as_raw_dict(self.properties))))
+
+ def validate(self, context):
+ utils.validate_dict_values(context, self.properties)
+
+ def coerce_values(self, context, container, report_issues):
+ utils.coerce_dict_values(context, container, self.properties, report_issues)
+
+ def dump(self, context):
+ console.puts(context.style.node(self.name))
+ if self.description:
+ console.puts(context.style.meta(self.description))
+ with context.style.indent:
+ console.puts('Implementation: %s' % context.style.literal(self.implementation))
+ utils.dump_parameters(context, self.properties)
+
+
+class MappingBase(structure.ModelMixin):
+ """
+ An instance of a :class:`MappingTemplate`.
+
+ Properties:
+
+ * :code:`mapped_name`: Exposed capability or requirement name
+ * :code:`node_id`: Must be represented in the :class:`ServiceInstance`
+ * :code:`name`: Name of capability or requirement at the node
+ """
+ __tablename__ = 'mapping'
+
+ mapped_name = Column(Text)
+ node_id = Column(Text)
+
+ @property
+ def as_raw(self):
+ return collections.OrderedDict((
+ ('mapped_name', self.mapped_name),
+ ('node_id', self.node_id),
+ ('name', self.name)))
+
+ def dump(self, context):
+ console.puts('%s -> %s.%s'
+ % (context.style.node(self.mapped_name),
+ context.style.node(self.node_id),
+ context.style.node(self.name)))
+
+
+class SubstitutionBase(structure.ModelMixin):
+ """
+ An instance of a :class:`SubstitutionTemplate`.
+
+ Properties:
+
+ * :code:`node_type_name`: Must be represented in the :class:`ModelingContext`
+ * :code:`capabilities`: Dict of :class:`Mapping`
+ * :code:`requirements`: Dict of :class:`Mapping`
+ """
+ __tablename__ = 'substitution'
+
+ node_type_name = Column(Text)
+
+ # region many-to-many relationships
+
+ @declared_attr
+ def capabilities(cls):
+ return cls.many_to_many_relationship('mapping', table_prefix='capabilities')
+
+ @declared_attr
+ def requirements(cls):
+ return cls.many_to_many_relationship('mapping',
+ table_prefix='requirements',
+ relationship_kwargs=dict(lazy='dynamic'))
+
+
+ # endregion
+
+ @property
+ def as_raw(self):
+ return collections.OrderedDict((
+ ('node_type_name', self.node_type_name),
+ ('capabilities', formatting.as_raw_list(self.capabilities)),
+ ('requirements', formatting.as_raw_list(self.requirements))))
+
+ def validate(self, context):
+ if context.modeling.node_types.get_descendant(self.node_type_name) is None:
+ context.validation.report('substitution "%s" has an unknown type: %s'
+ % (self.name, # pylint: disable=no-member
+ # TODO fix self.name reference
+ formatting.safe_repr(self.node_type_name)),
+ level=validation.Issue.BETWEEN_TYPES)
+
+ utils.validate_dict_values(context, self.capabilities)
+ utils.validate_dict_values(context, self.requirements)
+
+ def coerce_values(self, context, container, report_issues):
+ utils.coerce_dict_values(context, container, self.capabilities, report_issues)
+ utils.coerce_dict_values(context, container, self.requirements, report_issues)
+
+ def dump(self, context):
+ console.puts('Substitution:')
+ with context.style.indent:
+ console.puts('Node type: %s' % context.style.type(self.node_type_name))
+ utils.dump_dict_values(context, self.capabilities, 'Capability mappings')
+ utils.dump_dict_values(context, self.requirements, 'Requirement mappings')
+
+
+# endregion
+
+# region Node instances
+
+class NodeBase(structure.ModelMixin):
+ """
+ An instance of a :class:`NodeTemplate`.
+
+ Nodes may have zero or more :class:`Relationship` instances to other nodes.
+
+ Properties:
+
+ * :code:`id`: Unique ID (prefixed with the template name)
+ * :code:`type_name`: Must be represented in the :class:`ModelingContext`
+ * :code:`template_name`: Must be represented in the :class:`ServiceModel`
+ * :code:`properties`: Dict of :class:`Parameter`
+ * :code:`interfaces`: Dict of :class:`Interface`
+ * :code:`artifacts`: Dict of :class:`Artifact`
+ * :code:`capabilities`: Dict of :class:`CapabilityTemplate`
+ * :code:`relationships`: List of :class:`Relationship`
+ """
+ __tablename__ = 'node'
+
+ __private_fields__ = ['service_instance_fk',
+ 'host_fk',
+ 'node_template_fk']
+
+ # region foreign_keys
+ @declared_attr
+ def service_instance_fk(cls):
+ return cls.foreign_key('service_instance')
+
+ @declared_attr
+ def host_fk(cls):
+ return cls.foreign_key('node', nullable=True)
+
+ @declared_attr
+ def node_template_fk(cls):
+ return cls.foreign_key('node_template')
+
+ # endregion
+
+ type_name = Column(Text)
+ template_name = Column(Text)
+
+ # region orchestrator required columns
+ runtime_properties = Column(aria_types.Dict)
+ scaling_groups = Column(aria_types.List)
+ state = Column(Text, nullable=False)
+ version = Column(Integer, default=1)
+
+ @declared_attr
+ def plugins(cls):
+ return association_proxy('node_template', 'plugins')
+
+ @declared_attr
+ def host(cls):
+ return cls.relationship_to_self('host_fk')
+
+ @declared_attr
+ def service_instance_name(cls):
+ return association_proxy('service_instance', 'name')
+
+ @property
+ def ip(self):
+ if not self.host_fk:
+ return None
+ host_node = self.host
+ if 'ip' in host_node.runtime_properties: # pylint: disable=no-member
+ return host_node.runtime_properties['ip'] # pylint: disable=no-member
+ host_node = host_node.node_template # pylint: disable=no-member
+ host_ip_property = [prop for prop in host_node.properties if prop.name == 'ip']
+ if host_ip_property:
+ return host_ip_property[0].value
+ return None
+
+ @declared_attr
+ def node_template(cls):
+ return cls.many_to_one_relationship('node_template')
+
+ @declared_attr
+ def service_template(cls):
+ return association_proxy('service_instance', 'service_template')
+ # endregion
+
+ # region many-to-one relationships
+ @declared_attr
+ def service_instance(cls):
+ return cls.many_to_one_relationship('service_instance')
+
+ # endregion
+
+ # region many-to-many relationships
+
+ @declared_attr
+ def properties(cls):
+ return cls.many_to_many_relationship('parameter', table_prefix='properties')
+
+ # endregion
+
+ def satisfy_requirements(self, context):
+ node_template = context.modeling.model.node_templates.get(self.template_name)
+ satisfied = True
+ for i in range(len(node_template.requirement_templates)):
+ requirement_template = node_template.requirement_templates[i]
+
+ # Find target template
+ target_node_template, target_node_capability = \
+ requirement_template.find_target(context, node_template)
+ if target_node_template is not None:
+ satisfied = self._satisfy_capability(context,
+ target_node_capability,
+ target_node_template,
+ requirement_template,
+ requirement_template_index=i)
+ else:
+ context.validation.report('requirement "%s" of node "%s" has no target node '
+ 'template' % (requirement_template.name,
+ self.id),
+ level=validation.Issue.BETWEEN_INSTANCES)
+ satisfied = False
+ return satisfied
+
+ def _satisfy_capability(self, context, target_node_capability, target_node_template,
+ requirement_template, requirement_template_index):
+ # Find target nodes
+ target_nodes = context.modeling.instance.find_nodes(target_node_template.name)
+ if target_nodes:
+ target_node = None
+ target_capability = None
+
+ if target_node_capability is not None:
+ # Relate to the first target node that has capacity
+ for node in target_nodes:
+ target_capability = node.capabilities.get(target_node_capability.name)
+ if target_capability.relate():
+ target_node = node
+ break
+ else:
+ # Use first target node
+ target_node = target_nodes[0]
+
+ if target_node is not None:
+ relationship = RelationshipBase(
+ name=requirement_template.name,
+ source_requirement_index=requirement_template_index,
+ target_node_id=target_node.id,
+ target_capability_name=target_capability.name
+ )
+ self.relationships.append(relationship)
+ else:
+ context.validation.report('requirement "%s" of node "%s" targets node '
+ 'template "%s" but its instantiated nodes do not '
+ 'have enough capacity'
+ % (requirement_template.name,
+ self.id,
+ target_node_template.name),
+ level=validation.Issue.BETWEEN_INSTANCES)
+ return False
+ else:
+ context.validation.report('requirement "%s" of node "%s" targets node template '
+ '"%s" but it has no instantiated nodes'
+ % (requirement_template.name,
+ self.id,
+ target_node_template.name),
+ level=validation.Issue.BETWEEN_INSTANCES)
+ return False
+
+ def validate_capabilities(self, context):
+ satisfied = False
+ for capability in self.capabilities.itervalues():
+ if not capability.has_enough_relationships:
+ context.validation.report('capability "%s" of node "%s" requires at least %d '
+ 'relationships but has %d'
+ % (capability.name,
+ self.id,
+ capability.min_occurrences,
+ capability.occurrences),
+ level=validation.Issue.BETWEEN_INSTANCES)
+ satisfied = False
+ return satisfied
+
+ @property
+ def as_raw(self):
+ return collections.OrderedDict((
+ ('id', self.id),
+ ('type_name', self.type_name),
+ ('template_name', self.template_name),
+ ('properties', 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.relationships))))
+
+ def validate(self, context):
+ if len(self.id) > context.modeling.id_max_length:
+ context.validation.report('"%s" has an ID longer than the limit of %d characters: %d'
+ % (self.id,
+ context.modeling.id_max_length,
+ len(self.id)),
+ level=validation.Issue.BETWEEN_INSTANCES)
+
+ # TODO: validate that node template is of type?
+
+ utils.validate_dict_values(context, self.properties)
+ utils.validate_dict_values(context, self.interfaces)
+ utils.validate_dict_values(context, self.artifacts)
+ utils.validate_dict_values(context, self.capabilities)
+ utils.validate_list_values(context, self.relationships)
+
+ def coerce_values(self, context, container, report_issues):
+ utils.coerce_dict_values(context, self, self.properties, report_issues)
+ utils.coerce_dict_values(context, self, self.interfaces, report_issues)
+ utils.coerce_dict_values(context, self, self.artifacts, report_issues)
+ utils.coerce_dict_values(context, self, self.capabilities, report_issues)
+ utils.coerce_list_values(context, self, self.relationships, report_issues)
+
+ def dump(self, context):
+ console.puts('Node: %s' % context.style.node(self.id))
+ with context.style.indent:
+ console.puts('Template: %s' % context.style.node(self.template_name))
+ console.puts('Type: %s' % context.style.type(self.type_name))
+ utils.dump_parameters(context, self.properties)
+ utils.dump_interfaces(context, self.interfaces)
+ utils.dump_dict_values(context, self.artifacts, 'Artifacts')
+ utils.dump_dict_values(context, self.capabilities, 'Capabilities')
+ utils.dump_list_values(context, self.relationships, 'Relationships')
+
+
+class GroupBase(structure.ModelMixin):
+ """
+ An instance of a :class:`GroupTemplate`.
+
+ Properties:
+
+ * :code:`id`: Unique ID (prefixed with the template name)
+ * :code:`type_name`: Must be represented in the :class:`ModelingContext`
+ * :code:`template_name`: Must be represented in the :class:`ServiceModel`
+ * :code:`properties`: Dict of :class:`Parameter`
+ * :code:`interfaces`: Dict of :class:`Interface`
+ * :code:`policies`: Dict of :class:`GroupPolicy`
+ * :code:`member_node_ids`: Must be represented in the :class:`ServiceInstance`
+ * :code:`member_group_ids`: Must be represented in the :class:`ServiceInstance`
+ """
+ __tablename__ = 'group'
+
+ __private_fields__ = ['service_instance_fk']
+
+ # region foreign_keys
+
+ @declared_attr
+ def service_instance_fk(cls):
+ return cls.foreign_key('service_instance')
+
+ # endregion
+
+ type_name = Column(Text)
+ template_name = Column(Text)
+ member_node_ids = Column(aria_types.StrictList(basestring))
+ member_group_ids = Column(aria_types.StrictList(basestring))
+
+ # region many-to-one relationships
+ @declared_attr
+ def service_instance(cls):
+ return cls.many_to_one_relationship('service_instance')
+
+ # region many-to-many relationships
+ @declared_attr
+ def properties(cls):
+ return cls.many_to_many_relationship('parameter', table_prefix='properties')
+
+ # endregion
+
+ @property
+ def as_raw(self):
+ return collections.OrderedDict((
+ ('id', self.id),
+ ('type_name', self.type_name),
+ ('template_name', self.template_name),
+ ('properties', formatting.as_raw_dict(self.properties)),
+ ('interfaces', formatting.as_raw_list(self.interfaces)),
+ ('policies', formatting.as_raw_list(self.policies)),
+ ('member_node_ids', self.member_node_ids),
+ ('member_group_ids', self.member_group_ids)))
+
+ def validate(self, context):
+ if context.modeling.group_types.get_descendant(self.type_name) is None:
+ context.validation.report('group "%s" has an unknown type: %s'
+ % (self.name, # pylint: disable=no-member
+ # TODO fix self.name reference
+ formatting.safe_repr(self.type_name)),
+ level=validation.Issue.BETWEEN_TYPES)
+
+ utils.validate_dict_values(context, self.properties)
+ utils.validate_dict_values(context, self.interfaces)
+ utils.validate_dict_values(context, self.policies)
+
+ def coerce_values(self, context, container, report_issues):
+ utils.coerce_dict_values(context, container, self.properties, report_issues)
+ utils.coerce_dict_values(context, container, self.interfaces, report_issues)
+ utils.coerce_dict_values(context, container, self.policies, report_issues)
+
+ def dump(self, context):
+ console.puts('Group: %s' % context.style.node(self.id))
+ with context.style.indent:
+ console.puts('Type: %s' % context.style.type(self.type_name))
+ console.puts('Template: %s' % context.style.type(self.template_name))
+ utils.dump_parameters(context, self.properties)
+ utils.dump_interfaces(context, self.interfaces)
+ utils.dump_dict_values(context, self.policies, 'Policies')
+ if self.member_node_ids:
+ console.puts('Member nodes:')
+ with context.style.indent:
+ for node_id in self.member_node_ids:
+ console.puts(context.style.node(node_id))
+
+# endregion
+
+# region Relationship instances
+
+
+class RelationshipBase(structure.ModelMixin):
+ """
+ Connects :class:`Node` to another node.
+
+ An instance of a :class:`RelationshipTemplate`.
+
+ Properties:
+
+ * :code:`name`: Name (usually the name of the requirement at the source node template)
+ * :code:`source_requirement_index`: Must be represented in the source node template
+ * :code:`target_node_id`: Must be represented in the :class:`ServiceInstance`
+ * :code:`target_capability_name`: Matches the capability at the target node
+ * :code:`type_name`: Must be represented in the :class:`ModelingContext`
+ * :code:`template_name`: Must be represented in the :class:`ServiceModel`
+ * :code:`properties`: Dict of :class:`Parameter`
+ * :code:`source_interfaces`: Dict of :class:`Interface`
+ * :code:`target_interfaces`: Dict of :class:`Interface`
+ """
+ __tablename__ = 'relationship'
+
+ __private_fields__ = ['source_node_fk',
+ 'target_node_fk']
+
+ source_requirement_index = Column(Integer)
+ target_node_id = Column(Text)
+ target_capability_name = Column(Text)
+ type_name = Column(Text)
+ template_name = Column(Text)
+
+ # # region orchestrator required columns
+ source_position = Column(Integer)
+ target_position = Column(Integer)
+
+ @declared_attr
+ def source_node_fk(cls):
+ return cls.foreign_key('node', nullable=True)
+
+ @declared_attr
+ def source_node(cls):
+ return cls.many_to_one_relationship(
+ 'node',
+ 'source_node_fk',
+ backreference='outbound_relationships',
+ backref_kwargs=dict(
+ order_by=cls.source_position,
+ collection_class=ordering_list('source_position', count_from=0),
+ )
+ )
+
+ @declared_attr
+ def source_node_name(cls):
+ return association_proxy('source_node', cls.name_column_name())
+
+ @declared_attr
+ def target_node_fk(cls):
+ return cls.foreign_key('node', nullable=True)
+
+ @declared_attr
+ def target_node(cls):
+ return cls.many_to_one_relationship(
+ 'node',
+ 'target_node_fk',
+ backreference='inbound_relationships',
+ backref_kwargs=dict(
+ order_by=cls.target_position,
+ collection_class=ordering_list('target_position', count_from=0),
+ )
+ )
+
+ @declared_attr
+ def target_node_name(cls):
+ return association_proxy('target_node', cls.name_column_name())
+ # endregion
+
+ # region many-to-many relationship
+
+ @declared_attr
+ def properties(cls):
+ return cls.many_to_many_relationship('parameter', table_prefix='properties')
+
+ # endregion
+
+ @property
+ def as_raw(self):
+ return collections.OrderedDict((
+ ('name', self.name),
+ ('source_requirement_index', self.source_requirement_index),
+ ('target_node_id', self.target_node_id),
+ ('target_capability_name', self.target_capability_name),
+ ('type_name', self.type_name),
+ ('template_name', self.template_name),
+ ('properties', formatting.as_raw_dict(self.properties)),
+ ('source_interfaces', formatting.as_raw_list(self.source_interfaces)),
+ ('target_interfaces', formatting.as_raw_list(self.target_interfaces))))
+
+ def validate(self, context):
+ if self.type_name:
+ if context.modeling.relationship_types.get_descendant(self.type_name) is None:
+ context.validation.report('relationship "%s" has an unknown type: %s'
+ % (self.name,
+ formatting.safe_repr(self.type_name)),
+ level=validation.Issue.BETWEEN_TYPES)
+ utils.validate_dict_values(context, self.properties)
+ utils.validate_dict_values(context, self.source_interfaces)
+ utils.validate_dict_values(context, self.target_interfaces)
+
+ def coerce_values(self, context, container, report_issues):
+ utils.coerce_dict_values(context, container, self.properties, report_issues)
+ utils.coerce_dict_values(context, container, self.source_interfaces, report_issues)
+ utils.coerce_dict_values(context, container, self.target_interfaces, report_issues)
+
+ def dump(self, context):
+ if self.name:
+ if self.source_requirement_index is not None:
+ console.puts('%s (%d) ->' % (
+ context.style.node(self.name),
+ self.source_requirement_index))
+ else:
+ console.puts('%s ->' % context.style.node(self.name))
+ else:
+ console.puts('->')
+ with context.style.indent:
+ console.puts('Node: %s' % context.style.node(self.target_node_id))
+ if self.target_capability_name is not None:
+ console.puts('Capability: %s' % context.style.node(self.target_capability_name))
+ if self.type_name is not None:
+ console.puts('Relationship type: %s' % context.style.type(self.type_name))
+ if self.template_name is not None:
+ console.puts('Relationship template: %s' % context.style.node(self.template_name))
+ utils.dump_parameters(context, self.properties)
+ utils.dump_interfaces(context, self.source_interfaces, 'Source interfaces')
+ utils.dump_interfaces(context, self.target_interfaces, 'Target interfaces')
+
+# endregion