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/03/12 09:45:51 UTC

[1/8] incubator-ariatosca git commit: All tests pass :) [Forced Update!]

Repository: incubator-ariatosca
Updated Branches:
  refs/heads/Unified_coerce 2c2fe8688 -> 367b18bc0 (forced update)


All tests pass :)


Project: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/commit/aa01cd4e
Tree: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/tree/aa01cd4e
Diff: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/diff/aa01cd4e

Branch: refs/heads/Unified_coerce
Commit: aa01cd4e9a60507bafb98707c7793c3a89424db8
Parents: dd99f0f
Author: Tal Liron <ta...@gmail.com>
Authored: Tue Mar 7 12:13:28 2017 -0600
Committer: Tal Liron <ta...@gmail.com>
Committed: Tue Mar 7 21:22:45 2017 -0600

----------------------------------------------------------------------
 aria/modeling/bases.py                          |   2 +-
 aria/modeling/misc.py                           |   6 +-
 aria/modeling/orchestration.py                  |   2 +-
 aria/modeling/service.py                        |  12 +-
 aria/modeling/service_template.py               |   8 +-
 aria/orchestrator/workflows/api/task.py         |   2 +-
 aria/orchestrator/workflows/api/task_graph.py   |   2 +-
 .../workflows/builtin/execute_operation.py      |  18 +-
 aria/orchestrator/workflows/builtin/heal.py     | 188 +++++++++----------
 aria/parser/modeling/context.py                 |   5 +-
 aria/utils/uuid.py                              |   6 +-
 docs/requirements.txt                           |   2 +-
 tests/mock/models.py                            |   6 +-
 tests/mock/topology.py                          |   6 +-
 tests/orchestrator/context/test_operation.py    |   9 +-
 .../orchestrator/execution_plugin/test_local.py |  26 +--
 tests/orchestrator/execution_plugin/test_ssh.py |  29 +--
 .../orchestrator/workflows/builtin/test_heal.py |  20 +-
 tests/storage/test_models.py                    |   8 +-
 tests/storage/test_structures.py                |  12 +-
 20 files changed, 187 insertions(+), 182 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/aa01cd4e/aria/modeling/bases.py
----------------------------------------------------------------------
diff --git a/aria/modeling/bases.py b/aria/modeling/bases.py
index a4db320..efcb968 100644
--- a/aria/modeling/bases.py
+++ b/aria/modeling/bases.py
@@ -119,7 +119,7 @@ class ModelMixin(object):
 
         return cls._create_relationship(other_table, backref_kwargs, relationship_kwargs,
                                         backreference, key=key, foreign_key=foreign_key)
-    
+
     @classmethod
     def one_to_many_relationship(cls,
                                  child_table,

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/aa01cd4e/aria/modeling/misc.py
----------------------------------------------------------------------
diff --git a/aria/modeling/misc.py b/aria/modeling/misc.py
index 0bb5cda..105876a 100644
--- a/aria/modeling/misc.py
+++ b/aria/modeling/misc.py
@@ -13,6 +13,8 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+# pylint: disable=no-self-argument, no-member, abstract-method
+
 import cPickle as pickle
 import logging
 
@@ -113,7 +115,7 @@ class TypeBase(InstanceModelMixin):
 
     __tablename__ = 'type'
 
-    variant = Column(Text, nullable=False) 
+    variant = Column(Text, nullable=False)
     description = Column(Text)
     _role = Column(Text, name='role')
 
@@ -135,7 +137,7 @@ class TypeBase(InstanceModelMixin):
         return cls.foreign_key('type', nullable=True)
 
     # endregion
-    
+
     @property
     def role(self):
         def get_role(the_type):

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/aa01cd4e/aria/modeling/orchestration.py
----------------------------------------------------------------------
diff --git a/aria/modeling/orchestration.py b/aria/modeling/orchestration.py
index d9d9908..c842c07 100644
--- a/aria/modeling/orchestration.py
+++ b/aria/modeling/orchestration.py
@@ -152,7 +152,7 @@ class ServiceUpdateBase(ModelMixin):
 
     steps = None
 
-    __tablename__ = 'service_update' 
+    __tablename__ = 'service_update'
 
     _private_fields = ['execution_fk',
                        'service_fk']

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/aa01cd4e/aria/modeling/service.py
----------------------------------------------------------------------
diff --git a/aria/modeling/service.py b/aria/modeling/service.py
index eb8acb5..bf189f7 100644
--- a/aria/modeling/service.py
+++ b/aria/modeling/service.py
@@ -13,7 +13,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-# pylint: disable=no-self-argument, no-member, abstract-method
+# pylint: disable=too-many-lines, no-self-argument, no-member, abstract-method
 
 from sqlalchemy import (
     Column,
@@ -286,7 +286,7 @@ class ServiceBase(InstanceModelMixin): # pylint: disable=too-many-public-methods
                         self._dump_graph_node(context, target_node)
 
 
-class NodeBase(InstanceModelMixin):
+class NodeBase(InstanceModelMixin): # pylint: disable=too-many-public-methods
     """
     Usually an instance of a :class:`NodeTemplate`.
 
@@ -844,7 +844,7 @@ class SubstitutionBase(InstanceModelMixin):
 class SubstitutionMappingBase(InstanceModelMixin):
     """
     Used by :class:`Substitution` to map a capability or a requirement to a node.
-    
+
     Only one of `capability_template` and `requirement_template` can be set.
 
     Usually an instance of a :class:`SubstitutionTemplate`.
@@ -1186,7 +1186,7 @@ class CapabilityBase(InstanceModelMixin):
 class InterfaceBase(InstanceModelMixin):
     """
     A typed set of :class:`Operation`.
-    
+
     Usually an instance of :class:`InterfaceTemplate`.
 
     :ivar name: Name (unique for the node, group, or relationship)
@@ -1296,7 +1296,7 @@ class InterfaceBase(InstanceModelMixin):
 class OperationBase(InstanceModelMixin):
     """
     An operation in a :class:`Interface`.
-    
+
     Might be an instance of :class:`OperationTemplate`.
 
     :ivar name: Name (unique for the interface or service)
@@ -1423,7 +1423,7 @@ class OperationBase(InstanceModelMixin):
 class ArtifactBase(InstanceModelMixin):
     """
     A file associated with a :class:`Node`.
-    
+
     Usually an instance of :class:`ArtifactTemplate`.
 
     :ivar name: Name (unique for the node)

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/aa01cd4e/aria/modeling/service_template.py
----------------------------------------------------------------------
diff --git a/aria/modeling/service_template.py b/aria/modeling/service_template.py
index ebbe904..092de51 100644
--- a/aria/modeling/service_template.py
+++ b/aria/modeling/service_template.py
@@ -37,7 +37,7 @@ from . import (
 )
 
 
-class ServiceTemplateBase(TemplateModelMixin):
+class ServiceTemplateBase(TemplateModelMixin): # pylint: disable=too-many-public-methods
     """
     A service template is a source for creating :class:`Service` instances.
 
@@ -827,7 +827,7 @@ class SubstitutionTemplateBase(TemplateModelMixin):
 class SubstitutionTemplateMappingBase(TemplateModelMixin):
     """
     Used by :class:`SubstitutionTemplate` to map a capability or a requirement to a node.
-    
+
     Only one of `capability_template` and `requirement_template` can be set.
 
     :ivar name: Exposed capability or requirement name
@@ -1120,7 +1120,7 @@ class RelationshipTemplateBase(TemplateModelMixin):
     """
     Optional addition to a :class:`RequirementTemplate` in :class:`NodeTemplate` that can be applied
     when the requirement is matched with a capability.
-    
+
     Note that a relationship template here is not equivalent to a relationship template entity in
     TOSCA. For example, a TOSCA requirement specifying a relationship type instead of a template
     would still be represented here as a relationship template.
@@ -1276,7 +1276,7 @@ class CapabilityTemplateBase(TemplateModelMixin):
     # endregion
 
     def satisfies_requirement(self,
-                              context,
+                              context, # pylint: disable=unused-argument
                               source_node_template,
                               requirement,
                               target_node_template):

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/aa01cd4e/aria/orchestrator/workflows/api/task.py
----------------------------------------------------------------------
diff --git a/aria/orchestrator/workflows/api/task.py b/aria/orchestrator/workflows/api/task.py
index f1812b1..d434da8 100644
--- a/aria/orchestrator/workflows/api/task.py
+++ b/aria/orchestrator/workflows/api/task.py
@@ -61,7 +61,7 @@ class OperationTask(BaseTask):
 
     SOURCE_OPERATION = 'source'
     TARGET_OPERATION = 'target'
-    
+
     NAME_FORMAT = '{type}:{id}->{interface}/{operation}'
 
     def __init__(self,

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/aa01cd4e/aria/orchestrator/workflows/api/task_graph.py
----------------------------------------------------------------------
diff --git a/aria/orchestrator/workflows/api/task_graph.py b/aria/orchestrator/workflows/api/task_graph.py
index 2ead4d0..92a39d2 100644
--- a/aria/orchestrator/workflows/api/task_graph.py
+++ b/aria/orchestrator/workflows/api/task_graph.py
@@ -17,11 +17,11 @@
 Task graph. Used by users to build workflows
 """
 
-from ....utils.uuid import generate_uuid
 from collections import Iterable
 
 from networkx import DiGraph, topological_sort
 
+from ....utils.uuid import generate_uuid
 from . import task as api_task
 
 

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/aa01cd4e/aria/orchestrator/workflows/builtin/execute_operation.py
----------------------------------------------------------------------
diff --git a/aria/orchestrator/workflows/builtin/execute_operation.py b/aria/orchestrator/workflows/builtin/execute_operation.py
index e7c2085..ed4ada3 100644
--- a/aria/orchestrator/workflows/builtin/execute_operation.py
+++ b/aria/orchestrator/workflows/builtin/execute_operation.py
@@ -51,24 +51,24 @@ def execute_operation(
     """
     subgraphs = {}
     # filtering node instances
-    filtered_nodes = list(_filter_node_instances(
+    filtered_nodes = list(_filter_nodes(
         context=ctx,
         node_template_ids=node_template_ids,
         node_ids=node_ids,
         type_names=type_names))
 
     if run_by_dependency_order:
-        filtered_node_instances_ids = set(node_instance.id
+        filtered_node_ids = set(node_instance.id
                                           for node_instance in filtered_nodes)
-        for node in ctx.node_instances:
-            if node.id not in filtered_node_instances_ids:
+        for node in ctx.nodes:
+            if node.id not in filtered_node_ids:
                 subgraphs[node.id] = ctx.task_graph(
                     name='execute_operation_stub_{0}'.format(node.id))
 
     # registering actual tasks to sequences
     for node in filtered_nodes:
         graph.add_tasks(
-            _create_node_instance_task(
+            _create_node_task(
                 node=node,
                 interface_name=interface_name,
                 operation_name=operation_name,
@@ -77,8 +77,8 @@ def execute_operation(
             )
         )
 
-    for _, node_instance_sub_workflow in subgraphs.items():
-        graph.add_tasks(node_instance_sub_workflow)
+    for _, node_sub_workflow in subgraphs.items():
+        graph.add_tasks(node_sub_workflow)
 
     # adding tasks dependencies if required
     if run_by_dependency_order:
@@ -88,7 +88,7 @@ def execute_operation(
                     source_task=subgraphs[node.id], after=[subgraphs[relationship.target_id]])
 
 
-def _filter_node_instances(context, node_template_ids=(), node_ids=(), type_names=()):
+def _filter_nodes(context, node_template_ids=(), node_ids=(), type_names=()):
     def _is_node_template_by_id(node_template_id):
         return not node_template_ids or node_template_id in node_template_ids
 
@@ -105,7 +105,7 @@ def _filter_node_instances(context, node_template_ids=(), node_ids=(), type_name
             yield node
 
 
-def _create_node_instance_task(
+def _create_node_task(
         node,
         interface_name,
         operation_name,

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/aa01cd4e/aria/orchestrator/workflows/builtin/heal.py
----------------------------------------------------------------------
diff --git a/aria/orchestrator/workflows/builtin/heal.py b/aria/orchestrator/workflows/builtin/heal.py
index 2592323..92b96ea 100644
--- a/aria/orchestrator/workflows/builtin/heal.py
+++ b/aria/orchestrator/workflows/builtin/heal.py
@@ -26,156 +26,156 @@ from ..api import task
 
 
 @workflow
-def heal(ctx, graph, node_instance_id):
+def heal(ctx, graph, node_id):
     """
     The heal workflow
 
     :param WorkflowContext ctx: the workflow context
     :param TaskGraph graph: the graph which will describe the workflow.
-    :param node_instance_id: the id of the node instance to heal
+    :param node_id: the id of the node to heal
     :return:
     """
-    failing_node = ctx.model.node.get(node_instance_id)
+    failing_node = ctx.model.node.get(node_id)
     host_node = ctx.model.node.get(failing_node.host.id)
-    failed_node_instance_subgraph = _get_contained_subgraph(ctx, host_node)
-    failed_node_instance_ids = list(n.id for n in failed_node_instance_subgraph)
+    failed_node_subgraph = _get_contained_subgraph(ctx, host_node)
+    failed_node_ids = list(n.id for n in failed_node_subgraph)
 
-    targeted_node_instances = [node_instance for node_instance in ctx.node_instances
-                               if node_instance.id not in failed_node_instance_ids]
+    targeted_nodes = [node for node in ctx.nodes
+                               if node.id not in failed_node_ids]
 
     uninstall_subgraph = task.WorkflowTask(
         heal_uninstall,
-        failing_node_instances=failed_node_instance_subgraph,
-        targeted_node_instances=targeted_node_instances
+        failing_nodes=failed_node_subgraph,
+        targeted_nodes=targeted_nodes
     )
 
     install_subgraph = task.WorkflowTask(
         heal_install,
-        failing_node_instances=failed_node_instance_subgraph,
-        targeted_node_instances=targeted_node_instances)
+        failing_nodes=failed_node_subgraph,
+        targeted_nodes=targeted_nodes)
 
     graph.sequence(uninstall_subgraph, install_subgraph)
 
 
-@workflow(suffix_template='{failing_node_instances}')
-def heal_uninstall(ctx, graph, failing_node_instances, targeted_node_instances):
+@workflow(suffix_template='{failing_nodes}')
+def heal_uninstall(ctx, graph, failing_nodes, targeted_nodes):
     """
     the uninstall part of the heal mechanism
     :param WorkflowContext ctx: the workflow context
     :param TaskGraph graph: the task graph to edit.
-    :param failing_node_instances: the failing nodes to heal.
-    :param targeted_node_instances: the targets of the relationships where the failing node are
+    :param failing_nodes: the failing nodes to heal.
+    :param targeted_nodes: the targets of the relationships where the failing node are
     source
     :return:
     """
-    node_instance_sub_workflows = {}
-
-    # Create install stub workflow for each unaffected node instance
-    for node_instance in targeted_node_instances:
-        node_instance_stub = task.StubTask()
-        node_instance_sub_workflows[node_instance.id] = node_instance_stub
-        graph.add_tasks(node_instance_stub)
-
-    # create install sub workflow for every node instance
-    for node_instance in failing_node_instances:
-        node_instance_sub_workflow = task.WorkflowTask(uninstall_node,
-                                                       node_instance=node_instance)
-        node_instance_sub_workflows[node_instance.id] = node_instance_sub_workflow
-        graph.add_tasks(node_instance_sub_workflow)
-
-    # create dependencies between the node instance sub workflow
-    for node_instance in failing_node_instances:
-        node_instance_sub_workflow = node_instance_sub_workflows[node_instance.id]
-        for relationship_instance in reversed(node_instance.outbound_relationship_instances):
+    node_sub_workflows = {}
+
+    # Create install stub workflow for each unaffected node
+    for node in targeted_nodes:
+        node_stub = task.StubTask()
+        node_sub_workflows[node.id] = node_stub
+        graph.add_tasks(node_stub)
+
+    # create install sub workflow for every node
+    for node in failing_nodes:
+        node_sub_workflow = task.WorkflowTask(uninstall_node,
+                                                       node=node)
+        node_sub_workflows[node.id] = node_sub_workflow
+        graph.add_tasks(node_sub_workflow)
+
+    # create dependencies between the node sub workflow
+    for node in failing_nodes:
+        node_sub_workflow = node_sub_workflows[node.id]
+        for relationship in reversed(node.outbound_relationships):
             graph.add_dependency(
-                node_instance_sub_workflows[relationship_instance.target_node_instance.id],
-                node_instance_sub_workflow)
+                node_sub_workflows[relationship.target_node.id],
+                node_sub_workflow)
 
-    # Add operations for intact nodes depending on a node instance belonging to node_instances
-    for node_instance in targeted_node_instances:
-        node_instance_sub_workflow = node_instance_sub_workflows[node_instance.id]
+    # Add operations for intact nodes depending on a node belonging to nodes
+    for node in targeted_nodes:
+        node_sub_workflow = node_sub_workflows[node.id]
 
-        for relationship_instance in reversed(node_instance.outbound_relationship_instances):
+        for relationship in reversed(node.outbound_relationships):
 
-            target_node_instance = \
-                ctx.model.node.get(relationship_instance.target_node_instance.id)
-            target_node_instance_subgraph = node_instance_sub_workflows[target_node_instance.id]
-            graph.add_dependency(target_node_instance_subgraph, node_instance_sub_workflow)
+            target_node = \
+                ctx.model.node.get(relationship.target_node.id)
+            target_node_subgraph = node_sub_workflows[target_node.id]
+            graph.add_dependency(target_node_subgraph, node_sub_workflow)
 
-            if target_node_instance in failing_node_instances:
+            if target_node in failing_nodes:
                 dependency = relationship_tasks(
-                    relationship_instance=relationship_instance,
+                    relationship=relationship,
                     operation_name='aria.interfaces.relationship_lifecycle.unlink')
                 graph.add_tasks(*dependency)
-                graph.add_dependency(node_instance_sub_workflow, dependency)
+                graph.add_dependency(node_sub_workflow, dependency)
 
 
-@workflow(suffix_template='{failing_node_instances}')
-def heal_install(ctx, graph, failing_node_instances, targeted_node_instances):
+@workflow(suffix_template='{failing_nodes}')
+def heal_install(ctx, graph, failing_nodes, targeted_nodes):
     """
     the install part of the heal mechanism
     :param WorkflowContext ctx: the workflow context
     :param TaskGraph graph: the task graph to edit.
-    :param failing_node_instances: the failing nodes to heal.
-    :param targeted_node_instances: the targets of the relationships where the failing node are
+    :param failing_nodes: the failing nodes to heal.
+    :param targeted_nodes: the targets of the relationships where the failing node are
     source
     :return:
     """
-    node_instance_sub_workflows = {}
+    node_sub_workflows = {}
 
     # Create install sub workflow for each unaffected
-    for node_instance in targeted_node_instances:
-        node_instance_stub = task.StubTask()
-        node_instance_sub_workflows[node_instance.id] = node_instance_stub
-        graph.add_tasks(node_instance_stub)
-
-    # create install sub workflow for every node instance
-    for node_instance in failing_node_instances:
-        node_instance_sub_workflow = task.WorkflowTask(install_node,
-                                                       node_instance=node_instance)
-        node_instance_sub_workflows[node_instance.id] = node_instance_sub_workflow
-        graph.add_tasks(node_instance_sub_workflow)
-
-    # create dependencies between the node instance sub workflow
-    for node_instance in failing_node_instances:
-        node_instance_sub_workflow = node_instance_sub_workflows[node_instance.id]
-        if node_instance.outbound_relationship_instances:
+    for node in targeted_nodes:
+        node_stub = task.StubTask()
+        node_sub_workflows[node.id] = node_stub
+        graph.add_tasks(node_stub)
+
+    # create install sub workflow for every node
+    for node in failing_nodes:
+        node_sub_workflow = task.WorkflowTask(install_node,
+                                                       node=node)
+        node_sub_workflows[node.id] = node_sub_workflow
+        graph.add_tasks(node_sub_workflow)
+
+    # create dependencies between the node sub workflow
+    for node in failing_nodes:
+        node_sub_workflow = node_sub_workflows[node.id]
+        if node.outbound_relationships:
             dependencies = \
-                [node_instance_sub_workflows[relationship_instance.target_node_instance.id]
-                 for relationship_instance in node_instance.outbound_relationship_instances]
-            graph.add_dependency(node_instance_sub_workflow, dependencies)
-
-    # Add operations for intact nodes depending on a node instance
-    # belonging to node_instances
-    for node_instance in targeted_node_instances:
-        node_instance_sub_workflow = node_instance_sub_workflows[node_instance.id]
-
-        for relationship_instance in node_instance.outbound_relationship_instances:
-            target_node_instance = ctx.model.node.get(
-                relationship_instance.target_node_instance.id)
-            target_node_instance_subworkflow = node_instance_sub_workflows[target_node_instance.id]
-            graph.add_dependency(node_instance_sub_workflow, target_node_instance_subworkflow)
-
-            if target_node_instance in failing_node_instances:
+                [node_sub_workflows[relationship.target_node.id]
+                 for relationship in node.outbound_relationships]
+            graph.add_dependency(node_sub_workflow, dependencies)
+
+    # Add operations for intact nodes depending on a node
+    # belonging to nodes
+    for node in targeted_nodes:
+        node_sub_workflow = node_sub_workflows[node.id]
+
+        for relationship in node.outbound_relationships:
+            target_node = ctx.model.node.get(
+                relationship.target_node.id)
+            target_node_subworkflow = node_sub_workflows[target_node.id]
+            graph.add_dependency(node_sub_workflow, target_node_subworkflow)
+
+            if target_node in failing_nodes:
                 dependent = relationship_tasks(
-                    relationship_instance=relationship_instance,
+                    relationship=relationship,
                     operation_name='aria.interfaces.relationship_lifecycle.establish')
                 graph.add_tasks(*dependent)
-                graph.add_dependency(dependent, node_instance_sub_workflow)
+                graph.add_dependency(dependent, node_sub_workflow)
 
 
-def _get_contained_subgraph(context, host_node_instance):
-    contained_instances = [node_instance
-                           for node_instance in context.node_instances
-                           if node_instance.host_fk == host_node_instance.id and
-                           node_instance.host_fk != node_instance.id]
-    result = [host_node_instance]
+def _get_contained_subgraph(context, host_node):
+    contained_instances = [node
+                           for node in context.nodes
+                           if node.host_fk == host_node.id and
+                           node.host_fk != node.id]
+    result = [host_node]
 
     if not contained_instances:
         return result
 
     result.extend(contained_instances)
-    for node_instance in contained_instances:
-        result.extend(_get_contained_subgraph(context, node_instance))
+    for node in contained_instances:
+        result.extend(_get_contained_subgraph(context, node))
 
     return set(result)

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/aa01cd4e/aria/parser/modeling/context.py
----------------------------------------------------------------------
diff --git a/aria/parser/modeling/context.py b/aria/parser/modeling/context.py
index 52e724a..dff5991 100644
--- a/aria/parser/modeling/context.py
+++ b/aria/parser/modeling/context.py
@@ -15,9 +15,7 @@
 
 import itertools
 
-from ...utils.collections import StrictDict, prune, OrderedDict
-from ...utils.formatting import as_raw
-from ...utils.console import puts
+from ...utils.collections import StrictDict, prune
 from ...utils.uuid import generate_uuid
 
 
@@ -51,7 +49,6 @@ class ModelingContext(object):
     """
 
     def __init__(self):
-        from ...modeling.models import Type
         self.template = None
         self.instance = None
         self.node_id_format = '{template}_{id}'

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/aa01cd4e/aria/utils/uuid.py
----------------------------------------------------------------------
diff --git a/aria/utils/uuid.py b/aria/utils/uuid.py
index b5f39f8..1f340c6 100644
--- a/aria/utils/uuid.py
+++ b/aria/utils/uuid.py
@@ -31,10 +31,10 @@ UUID_LOWERCASE_ALPHANUMERIC = ShortUUID(alphabet='abcdefghijklmnopqrstuvwxyz0123
 def generate_uuid(length=None, variant='base57'):
     """
     A random string with varying degrees of guarantee of universal uniqueness.
-    
+
     :param variant: options are:
-                    * 'base57' (the default) uses a mix of upper and lowercase alphanumerics ensuring
-                      no visually ambiguous characters; default length 22
+                    * 'base57' (the default) uses a mix of upper and lowercase alphanumerics
+                      ensuring no visually ambiguous characters; default length 22
                     * 'alphanumeric' uses lowercase alphanumeric; default length 25
                     * 'uuid' user lowercase hexadecimal in the classic UUID format, including
                       dashes; length is always 36

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/aa01cd4e/docs/requirements.txt
----------------------------------------------------------------------
diff --git a/docs/requirements.txt b/docs/requirements.txt
index 7baba04..669522a 100644
--- a/docs/requirements.txt
+++ b/docs/requirements.txt
@@ -11,4 +11,4 @@
 # limitations under the License.
 
 Sphinx==1.5.3
-sphinx_rtd_theme==0.2.0
+sphinx_rtd_theme==0.2.2

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/aa01cd4e/tests/mock/models.py
----------------------------------------------------------------------
diff --git a/tests/mock/models.py b/tests/mock/models.py
index 78e9373..716254e 100644
--- a/tests/mock/models.py
+++ b/tests/mock/models.py
@@ -65,12 +65,12 @@ def create_service(service_template):
 def create_dependency_node_template(service_template):
     node_type = service_template.node_types.get_descendant('test_node_type')
     capability_type = service_template.capability_types.get_descendant('test_capability_type')
-    
+
     capability_template = models.CapabilityTemplate(
         name='capability',
         type=capability_type
     )
-    
+
     node_template = models.NodeTemplate(
         name=DEPENDENCY_NODE_TEMPLATE_NAME,
         type=node_type,
@@ -90,7 +90,7 @@ def create_dependent_node_template(service_template, dependency_node_template):
     operation_templates = dict((op, models.OperationTemplate(
         name=op,
         implementation='test'))
-                                for _, op in operations.NODE_OPERATIONS)
+                               for _, op in operations.NODE_OPERATIONS)
     interface_template = models.InterfaceTemplate(
         type=service_template.interface_types.get_descendant('test_interface_type'),
         operation_templates=operation_templates)

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/aa01cd4e/tests/mock/topology.py
----------------------------------------------------------------------
diff --git a/tests/mock/topology.py b/tests/mock/topology.py
index c7d8087..5f31661 100644
--- a/tests/mock/topology.py
+++ b/tests/mock/topology.py
@@ -31,7 +31,7 @@ def create_simple_topology_single_node(model_storage, create_operation):
             inputs={'key': aria_models.Parameter(name='key', value='create', type_name='string'),
                     'value': aria_models.Parameter(name='value', value=True, type_name='boolean')})
     )
-    node_template.interface_templates[interface_template.name] = interface_template
+    node_template.interface_templates[interface_template.name] = interface_template                 # pylint: disable=unsubscriptable-object
 
     node = models.create_dependency_node(node_template, service)
     interface = models.create_interface(
@@ -42,7 +42,7 @@ def create_simple_topology_single_node(model_storage, create_operation):
             inputs={'key': aria_models.Parameter(name='key', value='create', type_name='string'),
                     'value': aria_models.Parameter(name='value', value=True, type_name='boolean')})
     )
-    node.interfaces[interface.name] = interface
+    node.interfaces[interface.name] = interface                                                     # pylint: disable=unsubscriptable-object
 
     model_storage.service_template.put(service_template)
     model_storage.service.put(service)
@@ -61,7 +61,7 @@ def create_simple_topology_two_nodes(model_storage):
     dependency_node = models.create_dependency_node(dependency_node_template, service)
     dependent_node = models.create_dependent_node(dependent_node_template, service)
 
-    dependent_node.outbound_relationships.append(models.create_relationship(
+    dependent_node.outbound_relationships.append(models.create_relationship(                        # pylint: disable=no-member
         source=dependent_node,
         target=dependency_node
     ))

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/aa01cd4e/tests/orchestrator/context/test_operation.py
----------------------------------------------------------------------
diff --git a/tests/orchestrator/context/test_operation.py b/tests/orchestrator/context/test_operation.py
index ce3bd84..8ac8d49 100644
--- a/tests/orchestrator/context/test_operation.py
+++ b/tests/orchestrator/context/test_operation.py
@@ -101,7 +101,7 @@ def test_node_operation_task_execution(ctx, executor):
     )
     operations = interface.operations
     assert len(operations) == 1
-    assert operation_context.task.implementation == operations.values()[0].implementation
+    assert operation_context.task.implementation == operations.values()[0].implementation           # pylint: disable=no-member
     assert operation_context.task.inputs['putput'].value is True
 
     # Context based attributes (sugaring)
@@ -151,7 +151,7 @@ def test_relationship_operation_task_execution(ctx, executor):
     assert operation_context.task.actor == relationship
     assert interface_name in operation_context.task.name
     operations = interface.operations
-    assert operation_context.task.implementation == operations.values()[0].implementation
+    assert operation_context.task.implementation == operations.values()[0].implementation           # pylint: disable=no-member
     assert operation_context.task.inputs['putput'].value is True
 
     # Context based attributes (sugaring)
@@ -172,7 +172,7 @@ def test_relationship_operation_task_execution(ctx, executor):
 def test_invalid_task_operation_id(ctx, executor):
     """
     Checks that the right id is used. The task created with id == 1, thus running the task on
-    node_instance with id == 2. will check that indeed the node_instance uses the correct id.
+    node with id == 2. will check that indeed the node uses the correct id.
     :param ctx:
     :param executor:
     :return:
@@ -241,8 +241,7 @@ def test_plugin_workdir(ctx, executor, tmpdir):
         graph.add_tasks(api.task.OperationTask.for_node(node=node,
                                                         interface_name=interface_name,
                                                         operation_name=operation_name,
-                                                        inputs=inputs)
-        )
+                                                        inputs=inputs))
 
     execute(workflow_func=basic_workflow, workflow_context=ctx, executor=executor)
     expected_file = tmpdir.join('workdir', 'plugins', str(ctx.service.id),

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/aa01cd4e/tests/orchestrator/execution_plugin/test_local.py
----------------------------------------------------------------------
diff --git a/tests/orchestrator/execution_plugin/test_local.py b/tests/orchestrator/execution_plugin/test_local.py
index 9e9540f..6f146a6 100644
--- a/tests/orchestrator/execution_plugin/test_local.py
+++ b/tests/orchestrator/execution_plugin/test_local.py
@@ -462,7 +462,7 @@ if __name__ == '__main__':
         script_path = os.path.basename(local_script_path) if local_script_path else None
         if script_path:
             workflow_context.resource.deployment.upload(
-                entry_id=str(workflow_context.service_instance.id),
+                entry_id=str(workflow_context.service.id),
                 source=local_script_path,
                 path=script_path)
 
@@ -475,20 +475,20 @@ if __name__ == '__main__':
 
         @workflow
         def mock_workflow(ctx, graph):
-            op = 'test.op'
-            node = ctx.model.node.get_by_name(mock.models.DEPENDENCY_NODE_INSTANCE_NAME)
-            node.interfaces = [mock.models.get_interface(
-                op,
+            node = ctx.model.node.get_by_name(mock.models.DEPENDENCY_NODE_NAME)
+            interface = mock.models.create_interface(
+                node.service,
+                'test',
+                'op',
                 operation_kwargs=dict(implementation='{0}.{1}'.format(
                     operations.__name__,
                     operations.run_script_locally.__name__))
-            )]
-            # node.operations[op] = {
-            #     'operation': '{0}.{1}'.format(operations.__name__,
-            #                                   operations.run_script_locally.__name__)}
-            graph.add_tasks(api.task.OperationTask.node(
-                instance=node,
-                name=op,
+            )
+            node.interfaces[interface.name] = interface
+            graph.add_tasks(api.task.OperationTask.for_node(
+                node=node,
+                interface_name='test',
+                operation_name='op',
                 inputs=inputs))
             return graph
         tasks_graph = mock_workflow(ctx=workflow_context)  # pylint: disable=no-value-for-parameter
@@ -498,7 +498,7 @@ if __name__ == '__main__':
             tasks_graph=tasks_graph)
         eng.execute()
         return workflow_context.model.node.get_by_name(
-            mock.models.DEPENDENCY_NODE_INSTANCE_NAME).runtime_properties
+            mock.models.DEPENDENCY_NODE_NAME).runtime_properties
 
     @pytest.fixture
     def executor(self):

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/aa01cd4e/tests/orchestrator/execution_plugin/test_ssh.py
----------------------------------------------------------------------
diff --git a/tests/orchestrator/execution_plugin/test_ssh.py b/tests/orchestrator/execution_plugin/test_ssh.py
index a65ee34..78341b8 100644
--- a/tests/orchestrator/execution_plugin/test_ssh.py
+++ b/tests/orchestrator/execution_plugin/test_ssh.py
@@ -124,10 +124,10 @@ class TestWithActualSSHServer(object):
 
     def test_run_script_download_resource_and_render(self, tmpdir):
         resource = tmpdir.join('resource')
-        resource.write('{{ctx.service_instance.name}}')
+        resource.write('{{ctx.service.name}}')
         self._upload(str(resource), 'test_resource')
         props = self._execute()
-        assert props['test_value'] == self._workflow_context.service_instance.name
+        assert props['test_value'] == self._workflow_context.service.name
 
     @pytest.mark.parametrize('value', ['string-value', [1, 2, 3], {'key': 'value'}])
     def test_run_script_inputs_as_env_variables_no_override(self, value):
@@ -216,15 +216,20 @@ class TestWithActualSSHServer(object):
 
         @workflow
         def mock_workflow(ctx, graph):
-            op = 'test.op'
-            node = ctx.model.node.get_by_name(mock.models.DEPENDENCY_NODE_INSTANCE_NAME)
-            node.interfaces = [mock.models.get_interface(
-                op,
-                dict(implementation='{0}.{1}'.format(operations.__name__, operation.__name__))
-            )]
+            node = ctx.model.node.get_by_name(mock.models.DEPENDENCY_NODE_NAME)
+            interface = mock.models.create_interface(
+                node.service,
+                'test',
+                'op',
+                operation_kwargs=dict(implementation='{0}.{1}'.format(
+                    operations.__name__,
+                    operation.__name__))
+            )
+            node.interfaces[interface.name] = interface
             graph.sequence(*[api.task.OperationTask.for_node(
                 node=node,
-                name=op,
+                interface_name='test',
+                operation_name='op',
                 inputs={
                     'script_path': script_path,
                     'fabric_env': _FABRIC_ENV,
@@ -243,7 +248,7 @@ class TestWithActualSSHServer(object):
             tasks_graph=tasks_graph)
         eng.execute()
         return self._workflow_context.model.node.get_by_name(
-            mock.models.DEPENDENCY_NODE_INSTANCE_NAME).runtime_properties
+            mock.models.DEPENDENCY_NODE_NAME).runtime_properties
 
     def _execute_and_get_task_exception(self, *args, **kwargs):
         signal = events.on_failure_task_signal
@@ -254,7 +259,7 @@ class TestWithActualSSHServer(object):
 
     def _upload(self, source, path):
         self._workflow_context.resource.deployment.upload(
-            entry_id=str(self._workflow_context.service_instance.id),
+            entry_id=str(self._workflow_context.service.id),
             source=source,
             path=path)
 
@@ -407,7 +412,7 @@ class TestFabricEnvHideGroupsAndRunCommands(object):
         class Stub(object):
             @staticmethod
             def abort(message=None):
-                model.Task.abort(message)
+                models.Task.abort(message)
             ip = None
         task = Stub
         task.runs_on = Stub

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/aa01cd4e/tests/orchestrator/workflows/builtin/test_heal.py
----------------------------------------------------------------------
diff --git a/tests/orchestrator/workflows/builtin/test_heal.py b/tests/orchestrator/workflows/builtin/test_heal.py
index b553049..92fa7ea 100644
--- a/tests/orchestrator/workflows/builtin/test_heal.py
+++ b/tests/orchestrator/workflows/builtin/test_heal.py
@@ -33,11 +33,11 @@ def ctx(tmpdir):
 
 @pytest.mark.skip(reason='heal is not implemented for now')
 def test_heal_dependent_node(ctx):
-    dependent_node_instance = \
-        ctx.model.node.get_by_name(mock.models.DEPENDENT_NODE_INSTANCE_NAME)
-    dependent_node_instance.host_fk = dependent_node_instance.id
-    ctx.model.node.update(dependent_node_instance)
-    heal_graph = task.WorkflowTask(heal, ctx=ctx, node_instance_id=dependent_node_instance.id)
+    dependent_node = \
+        ctx.model.node.get_by_name(mock.models.DEPENDENT_NODE_NAME)
+    dependent_node.host_fk = dependent_node.id
+    ctx.model.node.update(dependent_node)
+    heal_graph = task.WorkflowTask(heal, ctx=ctx, node_id=dependent_node.id)
 
     assert len(list(heal_graph.tasks)) == 2
     uninstall_subgraph, install_subgraph = list(heal_graph.topological_order(reverse=True))
@@ -63,11 +63,11 @@ def test_heal_dependent_node(ctx):
 
 @pytest.mark.skip(reason='heal is not implemented for now')
 def test_heal_dependency_node(ctx):
-    dependency_node_instance = \
-        ctx.model.node.get_by_name(mock.models.DEPENDENCY_NODE_INSTANCE_NAME)
-    dependency_node_instance.host_fk = dependency_node_instance.id
-    ctx.model.node.update(dependency_node_instance)
-    heal_graph = task.WorkflowTask(heal, ctx=ctx, node_instance_id=dependency_node_instance.id)
+    dependency_node = \
+        ctx.model.node.get_by_name(mock.models.DEPENDENCY_NODE_NAME)
+    dependency_node.host_fk = dependency_node.id
+    ctx.model.node.update(dependency_node)
+    heal_graph = task.WorkflowTask(heal, ctx=ctx, node_id=dependency_node.id)
     # both subgraphs should contain un\install for both the dependent and the dependency
     assert len(list(heal_graph.tasks)) == 2
     uninstall_subgraph, install_subgraph = list(heal_graph.topological_order(reverse=True))

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/aa01cd4e/tests/storage/test_models.py
----------------------------------------------------------------------
diff --git a/tests/storage/test_models.py b/tests/storage/test_models.py
index 0088314..c80659b 100644
--- a/tests/storage/test_models.py
+++ b/tests/storage/test_models.py
@@ -662,13 +662,15 @@ class TestNodeInstanceIP(object):
 @pytest.mark.skip('Should be reworked into relationship')
 class TestRelationship(object):
     def test_relationship_model_creation(self, nodes_storage):
+        nodes = nodes_storage.node
+        source_node = nodes.get_by_name(mock.models.DEPENDENT_NODE_NAME)
+        target_node = nodes.get_by_name(mock.models.DEPENDENCY_NODE_NAME)
+
         relationship = mock.models.create_relationship(
+            source=source_node,
             target=nodes_storage.node.get_by_name(mock.models.DEPENDENCY_NODE_NAME)
         )
         nodes_storage.relationship.put(relationship)
-        nodes = nodes_storage.node
-        source_node = nodes.get_by_name(mock.models.DEPENDENT_NODE_NAME)
-        target_node = nodes.get_by_name(mock.models.DEPENDENCY_NODE_NAME)
 
         relationship_instance = _test_model(
             is_valid=True,

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/aa01cd4e/tests/storage/test_structures.py
----------------------------------------------------------------------
diff --git a/tests/storage/test_structures.py b/tests/storage/test_structures.py
index ceaea11..cacec2e 100644
--- a/tests/storage/test_structures.py
+++ b/tests/storage/test_structures.py
@@ -47,7 +47,7 @@ def storage():
 
 @pytest.fixture(scope='module', autouse=True)
 def module_cleanup():
-    modeling.models.aria_declarative_base.metadata.remove(MockModel.__table__)  #pylint: disable=no-member
+    modeling.models.aria_declarative_base.metadata.remove(MockModel.__table__)                      # pylint: disable=no-member
 
 
 @pytest.fixture
@@ -137,7 +137,7 @@ def test_relationship_model_ordering(context):
         target_node=new_node,
     ))
 
-    new_node.outbound_relationships.append(modeling.models.Relationship(
+    new_node.outbound_relationships.append(modeling.models.Relationship(                            # pylint: disable=no-member
         source_node=new_node,
         target_node=target_node,
     ))
@@ -159,12 +159,12 @@ def test_relationship_model_ordering(context):
         relationships = getattr(node, direction + '_relationships')
         assert len(relationships) == 2
 
-        reversed_relationship_instances = list(reversed(relationships))
-        assert relationships != reversed_relationship_instances
+        reversed_relationship = list(reversed(relationships))
+        assert relationships != reversed_relationship
 
-        relationships[:] = reversed_relationship_instances
+        relationships[:] = reversed_relationship
         context.model.node.update(node)
-        assert relationships == reversed_relationship_instances
+        assert relationships == reversed_relationship
 
     flip_and_assert(source_node, 'outbound')
     flip_and_assert(target_node, 'inbound')



[6/8] incubator-ariatosca git commit: Separate plugin specification form plugin; move dry support to CLI; various renames and refactorings

Posted by mx...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/dd5bfa93/aria/modeling/service.py
----------------------------------------------------------------------
diff --git a/aria/modeling/service.py b/aria/modeling/service.py
deleted file mode 100644
index bf189f7..0000000
--- a/aria/modeling/service.py
+++ /dev/null
@@ -1,1529 +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.
-
-# pylint: disable=too-many-lines, no-self-argument, no-member, abstract-method
-
-from sqlalchemy import (
-    Column,
-    Text,
-    Integer
-)
-from sqlalchemy import DateTime
-from sqlalchemy.ext.associationproxy import association_proxy
-from sqlalchemy.ext.declarative import declared_attr
-
-from .bases import InstanceModelMixin
-from ..parser import validation
-from ..utils import collections, formatting, console
-
-from . import (
-    utils,
-    types as modeling_types
-)
-
-
-class ServiceBase(InstanceModelMixin): # pylint: disable=too-many-public-methods
-    """
-    A service is usually an instance of a :class:`ServiceTemplate`.
-
-    You will usually not create it programmatically, but instead instantiate it from a service
-    template.
-
-    :ivar name: Name (unique for this ARIA installation)
-    :vartype name: basestring
-    :ivar service_template: Template from which this service was instantiated (optional)
-    :vartype service_template: :class:`ServiceTemplate`
-    :ivar description: Human-readable description
-    :vartype description: string
-    :ivar meta_data: Custom annotations
-    :vartype meta_data: {basestring: :class:`Metadata`}
-    :ivar node: Nodes
-    :vartype node: [:class:`Node`]
-    :ivar groups: Groups of nodes
-    :vartype groups: [:class:`Group`]
-    :ivar policies: Policies
-    :vartype policies: [:class:`Policy`]
-    :ivar substitution: The entire service can appear as a node
-    :vartype substitution: :class:`Substitution`
-    :ivar inputs: Externally provided parameters
-    :vartype inputs: {basestring: :class:`Parameter`}
-    :ivar outputs: These parameters are filled in after service installation
-    :vartype outputs: {basestring: :class:`Parameter`}
-    :ivar operations: Custom operations that can be performed on the service
-    :vartype operations: {basestring: :class:`Operation`}
-    :ivar plugins: Plugins required to be installed
-    :vartype plugins: {basestring: :class:`Plugin`}
-    :ivar created_at: Creation timestamp
-    :vartype created_at: :class:`datetime.datetime`
-    :ivar updated_at: Update timestamp
-    :vartype updated_at: :class:`datetime.datetime`
-
-    :ivar permalink: ??
-    :vartype permalink: basestring
-    :ivar scaling_groups: ??
-    :vartype scaling_groups: {}
-
-    :ivar modifications: Modifications of this service
-    :vartype modifications: [:class:`ServiceModification`]
-    :ivar updates: Updates of this service
-    :vartype updates: [:class:`ServiceUpdate`]
-    :ivar executions: Executions on this service
-    :vartype executions: [:class:`Execution`]
-    """
-
-    __tablename__ = 'service'
-
-    @declared_attr
-    def service_template(cls):
-        return cls.many_to_one_relationship('service_template')
-
-    description = Column(Text)
-
-    @declared_attr
-    def meta_data(cls):
-        # Warning! We cannot use the attr name "metadata" because it's used by SqlAlchemy!
-        return cls.many_to_many_relationship('metadata', dict_key='name')
-
-    @declared_attr
-    def nodes(cls):
-        return cls.one_to_many_relationship('node')
-
-    @declared_attr
-    def groups(cls):
-        return cls.one_to_many_relationship('group')
-
-    @declared_attr
-    def policies(cls):
-        return cls.one_to_many_relationship('policy')
-
-    @declared_attr
-    def substitution(cls):
-        return cls.one_to_one_relationship('substitution')
-
-    @declared_attr
-    def inputs(cls):
-        return cls.many_to_many_relationship('parameter', table_prefix='inputs',
-                                             dict_key='name')
-
-    @declared_attr
-    def outputs(cls):
-        return cls.many_to_many_relationship('parameter', table_prefix='outputs',
-                                             dict_key='name')
-
-    @declared_attr
-    def operations(cls):
-        return cls.one_to_many_relationship('operation', dict_key='name')
-
-    @declared_attr
-    def plugins(cls):
-        return cls.many_to_many_relationship('plugin')
-
-    created_at = Column(DateTime, nullable=False, index=True)
-    updated_at = Column(DateTime)
-
-    # region orchestration
-
-    permalink = Column(Text)
-    scaling_groups = Column(modeling_types.Dict)
-
-    # endregion
-
-    # region foreign keys
-
-    __private_fields__ = ['substituion_fk',
-                          'service_template_fk']
-
-    # Service one-to-one to Substitution
-    @declared_attr
-    def substitution_fk(cls):
-        return cls.foreign_key('substitution', nullable=True)
-
-    # Service many-to-one to ServiceTemplate
-    @declared_attr
-    def service_template_fk(cls):
-        return cls.foreign_key('service_template', nullable=True)
-
-    # endregion
-
-    def satisfy_requirements(self, context):
-        satisfied = True
-        for node in self.nodes:
-            if not node.satisfy_requirements(context):
-                satisfied = False
-        return satisfied
-
-    def validate_capabilities(self, context):
-        satisfied = True
-        for node in self.nodes:
-            if not node.validate_capabilities(context):
-                satisfied = False
-        return satisfied
-
-    def find_nodes(self, node_template_name):
-        nodes = []
-        for node in self.nodes:
-            if node.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.name for node in self.find_nodes(node_template_name)))
-
-    def find_groups(self, group_template_name):
-        groups = []
-        for group in self.groups:
-            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.name
-                                       for group in self.find_groups(group_template_name)))
-
-    def is_node_a_target(self, context, target_node):
-        for node in self.nodes:
-            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.name:
-                    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
-
-    @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)),
-            ('operations', formatting.as_raw_list(self.operations))))
-
-    def validate(self, context):
-        utils.validate_dict_values(context, self.meta_data)
-        utils.validate_list_values(context, self.nodes)
-        utils.validate_list_values(context, self.groups)
-        utils.validate_list_values(context, self.policies)
-        if self.substitution is not None:
-            self.substitution.validate(context)
-        utils.validate_dict_values(context, self.inputs)
-        utils.validate_dict_values(context, self.outputs)
-        utils.validate_dict_values(context, self.operations)
-
-    def coerce_values(self, context, container, report_issues):
-        utils.coerce_dict_values(context, container, self.meta_data, report_issues)
-        utils.coerce_list_values(context, container, self.nodes, report_issues)
-        utils.coerce_list_values(context, container, self.groups, report_issues)
-        utils.coerce_list_values(context, container, self.policies, report_issues)
-        if self.substitution is not None:
-            self.substitution.coerce_values(context, container, report_issues)
-        utils.coerce_dict_values(context, container, self.inputs, report_issues)
-        utils.coerce_dict_values(context, container, self.outputs, report_issues)
-        utils.coerce_dict_values(context, container, self.operations, report_issues)
-
-    def dump(self, context):
-        if self.description is not None:
-            console.puts(context.style.meta(self.description))
-        utils.dump_dict_values(context, self.meta_data, 'Metadata')
-        for node in self.nodes:
-            node.dump(context)
-        for group in self.groups:
-            group.dump(context)
-        for policy in self.policies:
-            policy.dump(context)
-        if self.substitution is not None:
-            self.substitution.dump(context)
-        utils.dump_dict_values(context, self.inputs, 'Inputs')
-        utils.dump_dict_values(context, self.outputs, 'Outputs')
-        utils.dump_dict_values(context, self.operations, 'Operations')
-
-    def dump_graph(self, context):
-        for node in self.nodes.itervalues():
-            if not self.is_node_a_target(context, node):
-                self._dump_graph_node(context, node)
-
-    def _dump_graph_node(self, context, node):
-        console.puts(context.style.node(node.name))
-        if node.relationships:
-            with context.style.indent:
-                for relationship in node.relationships:
-                    relationship_name = (context.style.node(relationship.template_name)
-                                         if relationship.template_name is not None
-                                         else context.style.type(relationship.type_name))
-                    capability_name = (context.style.node(relationship.target_capability_name)
-                                       if relationship.target_capability_name is not None
-                                       else None)
-                    if capability_name is not None:
-                        console.puts('-> {0} {1}'.format(relationship_name, capability_name))
-                    else:
-                        console.puts('-> {0}'.format(relationship_name))
-                    target_node = self.nodes.get(relationship.target_node_id)
-                    with console.indent(3):
-                        self._dump_graph_node(context, target_node)
-
-
-class NodeBase(InstanceModelMixin): # pylint: disable=too-many-public-methods
-    """
-    Usually an instance of a :class:`NodeTemplate`.
-
-    Nodes may have zero or more :class:`Relationship` instances to other nodes.
-
-    :ivar name: Name (unique for this service)
-    :vartype name: basestring
-    :ivar node_template: Template from which this node was instantiated (optional)
-    :vartype node_template: :class:`NodeTemplate`
-    :ivar type: Node type
-    :vartype type: :class:`Type`
-    :ivar description: Human-readable description
-    :vartype description: string
-    :ivar properties: Associated parameters
-    :vartype properties: {basestring: :class:`Parameter`}
-    :ivar interfaces: Bundles of operations
-    :vartype interfaces: {basestring: :class:`Interface`}
-    :ivar artifacts: Associated files
-    :vartype artifacts: {basestring: :class:`Artifact`}
-    :ivar capabilities: Exposed capabilities
-    :vartype capabilities: {basestring: :class:`Capability`}
-    :ivar outbound_relationships: Relationships to other nodes
-    :vartype outbound_relationships: [:class:`Relationship`]
-    :ivar inbound_relationships: Relationships from other nodes
-    :vartype inbound_relationships: [:class:`Relationship`]
-    :ivar plugins: Plugins required to be installed on the node's host
-    :vartype plugins: {basestring: :class:`Plugin`}
-    :ivar host: Host node (can be self)
-    :vartype host: :class:`Node`
-
-    :ivar runtime_properties: TODO: should be replaced with attributes
-    :vartype runtime_properties: {}
-    :ivar scaling_groups: ??
-    :vartype scaling_groups: []
-    :ivar state: ??
-    :vartype state: basestring
-    :ivar version: ??
-    :vartype version: int
-
-    :ivar service: Containing service
-    :vartype service: :class:`Service`
-    :ivar groups: We are a member of these groups
-    :vartype groups: [:class:`Group`]
-    :ivar policies: Policies enacted on this node
-    :vartype policies: [:class:`Policy`]
-    :ivar substitution_mapping: Our contribution to service substitution
-    :vartype substitution_mapping: :class:`SubstitutionMapping`
-    :ivar tasks: Tasks on this node
-    :vartype tasks: [:class:`Task`]
-    """
-
-    __tablename__ = 'node'
-
-    @declared_attr
-    def node_template(cls):
-        return cls.many_to_one_relationship('node_template')
-
-    @declared_attr
-    def type(cls):
-        return cls.many_to_one_relationship('type')
-
-    description = Column(Text)
-
-    @declared_attr
-    def properties(cls):
-        return cls.many_to_many_relationship('parameter', table_prefix='properties',
-                                             dict_key='name')
-
-    @declared_attr
-    def interfaces(cls):
-        return cls.one_to_many_relationship('interface', dict_key='name')
-
-    @declared_attr
-    def artifacts(cls):
-        return cls.one_to_many_relationship('artifact', dict_key='name')
-
-    @declared_attr
-    def capabilities(cls):
-        return cls.one_to_many_relationship('capability', dict_key='name')
-
-    @declared_attr
-    def outbound_relationships(cls):
-        return cls.one_to_many_relationship('relationship',
-                                            foreign_key='source_node_fk',
-                                            backreference='source_node')
-
-    @declared_attr
-    def inbound_relationships(cls):
-        return cls.one_to_many_relationship('relationship',
-                                            foreign_key='target_node_fk',
-                                            backreference='target_node')
-
-    @declared_attr
-    def plugins(cls):
-        return cls.many_to_many_relationship('plugin')
-
-    @declared_attr
-    def host(cls):
-        return cls.relationship_to_self('host_fk')
-
-    # region orchestration
-
-    runtime_properties = Column(modeling_types.Dict)
-    scaling_groups = Column(modeling_types.List)
-    state = Column(Text, nullable=False)
-    version = Column(Integer, default=1)
-
-    @declared_attr
-    def service_name(cls):
-        return association_proxy('service', 'name')
-
-    @property
-    def ip(self):
-        # TODO: totally broken
-        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 = host_node.properties.get('ip')
-        if host_ip_property:
-            return host_ip_property.value
-        return None
-
-    # endregion
-
-    # region foreign_keys
-
-    __private_fields__ = ['type_fk',
-                          'host_fk',
-                          'service_fk',
-                          'node_template_fk']
-
-    # Node many-to-one to Type
-    @declared_attr
-    def type_fk(cls):
-        return cls.foreign_key('type')
-
-    # Node one-to-one to Node
-    @declared_attr
-    def host_fk(cls):
-        return cls.foreign_key('node', nullable=True)
-
-    # Service one-to-many to Node
-    @declared_attr
-    def service_fk(cls):
-        return cls.foreign_key('service')
-
-    # Node many-to-one to NodeTemplate
-    @declared_attr
-    def node_template_fk(cls):
-        return cls.foreign_key('node_template', nullable=True)
-
-    # endregion
-
-    def satisfy_requirements(self, context):
-        node_template = self.node_template
-        satisfied = True
-        for requirement_template in node_template.requirement_templates:
-            # 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)
-            else:
-                context.validation.report('requirement "{0}" of node "{1}" has no target node '
-                                          'template'.format(requirement_template.name, self.name),
-                                          level=validation.Issue.BETWEEN_INSTANCES)
-                satisfied = False
-        return satisfied
-
-    def _satisfy_capability(self, context, target_node_capability, target_node_template,
-                            requirement_template):
-        from . import models
-        # 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:
-                if requirement_template.relationship_template is not None:
-                    relationship = \
-                        requirement_template.relationship_template.instantiate(context, self)
-                else:
-                    relationship = models.Relationship(target_capability=target_capability)
-                relationship.name = requirement_template.name
-                relationship.requirement_template = requirement_template
-                relationship.target_node = target_node
-                self.outbound_relationships.append(relationship)
-                return True
-            else:
-                context.validation.report('requirement "{0}" of node "{1}" targets node '
-                                          'template "{2}" but its instantiated nodes do not '
-                                          'have enough capacity'.format(
-                                              requirement_template.name,
-                                              self.name,
-                                              target_node_template.name),
-                                          level=validation.Issue.BETWEEN_INSTANCES)
-                return False
-        else:
-            context.validation.report('requirement "{0}" of node "{1}" targets node template '
-                                      '"{2}" but it has no instantiated nodes'.format(
-                                          requirement_template.name,
-                                          self.name,
-                                          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 "{0}" of node "{1}" requires at least {2:d} '
-                                          'relationships but has {3:d}'.format(
-                                              capability.name,
-                                              self.name,
-                                              capability.min_occurrences,
-                                              capability.occurrences),
-                                          level=validation.Issue.BETWEEN_INSTANCES)
-                satisfied = False
-        return satisfied
-
-    @property
-    def as_raw(self):
-        return collections.OrderedDict((
-            ('name', self.name),
-            ('type_name', self.type_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.outbound_relationships))))
-
-    def validate(self, context):
-        if len(self.name) > context.modeling.id_max_length:
-            context.validation.report('"{0}" has an ID longer than the limit of {1:d} characters: '
-                                      '{2:d}'.format(
-                                          self.name,
-                                          context.modeling.id_max_length,
-                                          len(self.name)),
-                                      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.outbound_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.outbound_relationships, report_issues)
-
-    def dump(self, context):
-        console.puts('Node: {0}'.format(context.style.node(self.name)))
-        with context.style.indent:
-            console.puts('Type: {0}'.format(context.style.type(self.type.name)))
-            console.puts('Template: {0}'.format(context.style.node(self.node_template.name)))
-            utils.dump_dict_values(context, self.properties, '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.outbound_relationships, 'Relationships')
-
-
-class GroupBase(InstanceModelMixin):
-    """
-    Usually an instance of a :class:`GroupTemplate`.
-
-    :ivar name: Name (unique for this service)
-    :vartype name: basestring
-    :ivar group_template: Template from which this group was instantiated (optional)
-    :vartype group_template: :class:`GroupTemplate`
-    :ivar type: Group type
-    :vartype type: :class:`Type`
-    :ivar description: Human-readable description
-    :vartype description: string
-    :ivar nodes: Members of this group
-    :vartype nodes: [:class:`Node`]
-    :ivar properties: Associated parameters
-    :vartype properties: {basestring: :class:`Parameter`}
-    :ivar interfaces: Bundles of operations
-    :vartype interfaces: {basestring: :class:`Interface`}
-
-    :ivar service: Containing service
-    :vartype service: :class:`Service`
-    :ivar policies: Policies enacted on this group
-    :vartype policies: [:class:`Policy`]
-    """
-
-    __tablename__ = 'group'
-
-    @declared_attr
-    def group_template(cls):
-        return cls.many_to_one_relationship('group_template')
-
-    @declared_attr
-    def type(cls):
-        return cls.many_to_one_relationship('type')
-
-    description = Column(Text)
-
-    @declared_attr
-    def nodes(cls):
-        return cls.many_to_many_relationship('node')
-
-    @declared_attr
-    def properties(cls):
-        return cls.many_to_many_relationship('parameter', table_prefix='properties',
-                                             dict_key='name')
-
-    @declared_attr
-    def interfaces(cls):
-        return cls.one_to_many_relationship('interface', dict_key='name')
-
-    # region foreign_keys
-
-    __private_fields__ = ['type_fk',
-                          'service_fk',
-                          'group_template_fk']
-
-    # Group many-to-one to Type
-    @declared_attr
-    def type_fk(cls):
-        return cls.foreign_key('type')
-
-    # Service one-to-many to Group
-    @declared_attr
-    def service_fk(cls):
-        return cls.foreign_key('service')
-
-    # Group many-to-one to GroupTemplate
-    @declared_attr
-    def group_template_fk(cls):
-        return cls.foreign_key('group_template', nullable=True)
-
-    # endregion
-
-    @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))))
-
-    def validate(self, context):
-        utils.validate_dict_values(context, self.properties)
-        utils.validate_dict_values(context, self.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.interfaces, report_issues)
-
-    def dump(self, context):
-        console.puts('Group: {0}'.format(context.style.node(self.name)))
-        with context.style.indent:
-            console.puts('Type: {0}'.format(context.style.type(self.type.name)))
-            utils.dump_dict_values(context, self.properties, 'Properties')
-            utils.dump_interfaces(context, self.interfaces)
-            if self.nodes:
-                console.puts('Member nodes:')
-                with context.style.indent:
-                    for node in self.nodes:
-                        console.puts(context.style.node(node.name))
-
-
-class PolicyBase(InstanceModelMixin):
-    """
-    Usually an instance of a :class:`PolicyTemplate`.
-
-    :ivar name: Name (unique for this service)
-    :vartype name: basestring
-    :ivar policy_template: Template from which this policy was instantiated (optional)
-    :vartype policy_template: :class:`PolicyTemplate`
-    :ivar type: Policy type
-    :vartype type: :class:`Type`
-    :ivar description: Human-readable description
-    :vartype description: string
-    :ivar nodes: Policy will be enacted on all these nodes
-    :vartype nodes: [:class:`Node`]
-    :ivar groups: Policy will be enacted on all nodes in these groups
-    :vartype groups: [:class:`Group`]
-    :ivar properties: Associated parameters
-    :vartype properties: {basestring: :class:`Parameter`}
-
-    :ivar service: Containing service
-    :vartype service: :class:`Service`
-    """
-
-    __tablename__ = 'policy'
-
-    @declared_attr
-    def policy_template(cls):
-        return cls.many_to_one_relationship('policy_template')
-
-    @declared_attr
-    def type(cls):
-        return cls.many_to_one_relationship('type')
-
-    description = Column(Text)
-
-    @declared_attr
-    def properties(cls):
-        return cls.many_to_many_relationship('parameter', table_prefix='properties',
-                                             dict_key='name')
-
-    @declared_attr
-    def nodes(cls):
-        return cls.many_to_many_relationship('node')
-
-    @declared_attr
-    def groups(cls):
-        return cls.many_to_many_relationship('group')
-
-    # region foreign_keys
-
-    __private_fields__ = ['type_fk',
-                          'service_fk',
-                          'policy_template_fk']
-
-    # Policy many-to-one to Type
-    @declared_attr
-    def type_fk(cls):
-        return cls.foreign_key('type')
-
-    # Service one-to-many to Policy
-    @declared_attr
-    def service_fk(cls):
-        return cls.foreign_key('service')
-
-    # Policy many-to-one to PolicyTemplate
-    @declared_attr
-    def policy_template_fk(cls):
-        return cls.foreign_key('policy_template', nullable=True)
-
-    # endregion
-
-    @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):
-        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: {0}'.format(context.style.node(self.name)))
-        with context.style.indent:
-            console.puts('Type: {0}'.format(context.style.type(self.type.name)))
-            utils.dump_dict_values(context, self.properties, 'Properties')
-            if self.nodes:
-                console.puts('Target nodes:')
-                with context.style.indent:
-                    for node in self.nodes:
-                        console.puts(context.style.node(node.name))
-            if self.groups:
-                console.puts('Target groups:')
-                with context.style.indent:
-                    for group in self.groups:
-                        console.puts(context.style.node(group.name))
-
-
-class SubstitutionBase(InstanceModelMixin):
-    """
-    Used to substitute a single node for the entire deployment.
-
-    Usually an instance of a :class:`SubstitutionTemplate`.
-
-    :ivar substitution_template: Template from which this substitution was instantiated (optional)
-    :vartype substitution_template: :class:`SubstitutionTemplate`
-    :ivar node_type: Exposed node type
-    :vartype node_type: :class:`Type`
-    :ivar mappings: Requirement and capability mappings
-    :vartype mappings: {basestring: :class:`SubstitutionTemplate`}
-
-    :ivar service: Containing service
-    :vartype service: :class:`Service`
-    """
-
-    __tablename__ = 'substitution'
-
-    @declared_attr
-    def substitution_template(cls):
-        return cls.many_to_one_relationship('substitution_template')
-
-    @declared_attr
-    def node_type(cls):
-        return cls.many_to_one_relationship('type')
-
-    @declared_attr
-    def mappings(cls):
-        return cls.one_to_many_relationship('substitution_mapping', dict_key='name')
-
-    # region foreign_keys
-
-    __private_fields__ = ['node_type_fk',
-                          'substitution_template_fk']
-
-    # Substitution many-to-one to Type
-    @declared_attr
-    def node_type_fk(cls):
-        return cls.foreign_key('type')
-
-    # Substitution many-to-one to SubstitutionTemplate
-    @declared_attr
-    def substitution_template_fk(cls):
-        return cls.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))))
-
-    def validate(self, context):
-        utils.validate_dict_values(context, self.mappings)
-
-    def coerce_values(self, context, container, report_issues):
-        utils.coerce_dict_values(context, container, self.mappings, report_issues)
-
-    def dump(self, context):
-        console.puts('Substitution:')
-        with context.style.indent:
-            console.puts('Node type: {0}'.format(context.style.type(self.node_type.name)))
-            utils.dump_dict_values(context, self.mappings, 'Mappings')
-
-
-class SubstitutionMappingBase(InstanceModelMixin):
-    """
-    Used by :class:`Substitution` to map a capability or a requirement to a node.
-
-    Only one of `capability_template` and `requirement_template` can be set.
-
-    Usually an instance of a :class:`SubstitutionTemplate`.
-
-    :ivar name: Exposed capability or requirement name
-    :vartype name: basestring
-    :ivar node: Node
-    :vartype node: :class:`Node`
-    :ivar capability: Capability in the node
-    :vartype capability: :class:`Capability`
-    :ivar requirement_template: Requirement template in the node template
-    :vartype requirement_template: :class:`RequirementTemplate`
-
-    :ivar substitution: Containing substitution
-    :vartype substitution: :class:`Substitution`
-    """
-
-    __tablename__ = 'substitution_mapping'
-
-    @declared_attr
-    def node(cls):
-        return cls.one_to_one_relationship('node')
-
-    @declared_attr
-    def capability(cls):
-        return cls.one_to_one_relationship('capability')
-
-    @declared_attr
-    def requirement_template(cls):
-        return cls.one_to_one_relationship('requirement_template')
-
-    # region foreign keys
-
-    __private_fields__ = ['substitution_fk',
-                          'node_fk',
-                          'capability_fk',
-                          'requirement_template_fk']
-
-    # Substitution one-to-many to SubstitutionMapping
-    @declared_attr
-    def substitution_fk(cls):
-        return cls.foreign_key('substitution')
-
-    # Substitution one-to-one to NodeTemplate
-    @declared_attr
-    def node_fk(cls):
-        return cls.foreign_key('node')
-
-    # Substitution one-to-one to Capability
-    @declared_attr
-    def capability_fk(cls):
-        return cls.foreign_key('capability', nullable=True)
-
-    # Substitution one-to-one to RequirementTemplate
-    @declared_attr
-    def requirement_template_fk(cls):
-        return cls.foreign_key('requirement_template', nullable=True)
-
-    # endregion
-
-    @property
-    def as_raw(self):
-        return collections.OrderedDict((
-            ('name', self.name)))
-
-    def validate(self, context):
-        if (self.capability is None) and (self.requirement_template is None):
-            context.validation.report('mapping "{0}" refers to neither capability nor a requirement'
-                                      ' in node: {1}'.format(
-                                          self.name,
-                                          formatting.safe_repr(self.node.name)),
-                                      level=validation.Issue.BETWEEN_TYPES)
-
-    def dump(self, context):
-        console.puts('{0} -> {1}.{2}'.format(
-            context.style.node(self.name),
-            context.style.node(self.node.name),
-            context.style.node(self.capability.name
-                               if self.capability
-                               else self.requirement_template.name)))
-
-
-class RelationshipBase(InstanceModelMixin):
-    """
-    Connects :class:`Node` to a capability in another node.
-
-    Might be an instance of a :class:`RelationshipTemplate`.
-
-    :ivar name: Name (usually the name of the requirement at the source node template)
-    :vartype name: basestring
-    :ivar relationship_template: Template from which this relationship was instantiated (optional)
-    :vartype relationship_template: :class:`RelationshipTemplate`
-    :ivar requirement_template: Template from which this relationship was instantiated (optional)
-    :vartype requirement_template: :class:`RequirementTemplate`
-    :ivar type: Relationship type
-    :vartype type: :class:`Type`
-    :ivar target_capability: Capability at the target node (optional)
-    :vartype target_capability: :class:`Capability`
-    :ivar properties: Associated parameters
-    :vartype properties: {basestring: :class:`Parameter`}
-    :ivar interfaces: Bundles of operations
-    :vartype interfaces: {basestring: :class:`Interfaces`}
-
-    :ivar source_position: ??
-    :vartype source_position: int
-    :ivar target_position: ??
-    :vartype target_position: int
-
-    :ivar source_node: Source node
-    :vartype source_node: :class:`Node`
-    :ivar target_node: Target node
-    :vartype target_node: :class:`Node`
-    :ivar tasks: Tasks on this node
-    :vartype tasks: [:class:`Task`]
-    """
-
-    __tablename__ = 'relationship'
-
-    @declared_attr
-    def relationship_template(cls):
-        return cls.many_to_one_relationship('relationship_template')
-
-    @declared_attr
-    def requirement_template(cls):
-        return cls.many_to_one_relationship('requirement_template')
-
-    @declared_attr
-    def type(cls):
-        return cls.many_to_one_relationship('type')
-
-    @declared_attr
-    def target_capability(cls):
-        return cls.one_to_one_relationship('capability')
-
-    @declared_attr
-    def properties(cls):
-        return cls.many_to_many_relationship('parameter', table_prefix='properties',
-                                             dict_key='name')
-
-    @declared_attr
-    def interfaces(cls):
-        return cls.one_to_many_relationship('interface', dict_key='name')
-
-    # region orchestration
-
-    source_position = Column(Integer) # ???
-    target_position = Column(Integer) # ???
-
-    # endregion
-
-    # region foreign keys
-
-    __private_fields__ = ['type_fk',
-                          'source_node_fk',
-                          'target_node_fk',
-                          'target_capability_fk',
-                          'requirement_template_fk',
-                          'relationship_template_fk']
-
-    # Relationship many-to-one to Type
-    @declared_attr
-    def type_fk(cls):
-        return cls.foreign_key('type', nullable=True)
-
-    # Node one-to-many to Relationship
-    @declared_attr
-    def source_node_fk(cls):
-        return cls.foreign_key('node')
-
-    # Node one-to-many to Relationship
-    @declared_attr
-    def target_node_fk(cls):
-        return cls.foreign_key('node')
-
-    # Relationship one-to-one to Capability
-    @declared_attr
-    def target_capability_fk(cls):
-        return cls.foreign_key('capability', nullable=True)
-
-    # Relationship many-to-one to RequirementTemplate
-    @declared_attr
-    def requirement_template_fk(cls):
-        return cls.foreign_key('requirement_template', nullable=True)
-
-    # Relationship many-to-one to RelationshipTemplate
-    @declared_attr
-    def relationship_template_fk(cls):
-        return cls.foreign_key('relationship_template', nullable=True)
-
-    # endregion
-
-    @property
-    def as_raw(self):
-        return collections.OrderedDict((
-            ('name', self.name),
-            ('target_node_id', self.target_node.name),
-            ('type_name', self.type_name),
-            ('template_name', self.template_name),
-            ('properties', formatting.as_raw_dict(self.properties)),
-            ('interfaces', formatting.as_raw_list(self.interfaces))))
-
-    def validate(self, context):
-        utils.validate_dict_values(context, self.properties)
-        utils.validate_dict_values(context, self.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.interfaces, report_issues)
-
-    def dump(self, context):
-        if self.name:
-            console.puts('{0} ->'.format(context.style.node(self.name)))
-        else:
-            console.puts('->')
-        with context.style.indent:
-            console.puts('Node: {0}'.format(context.style.node(self.target_node.name)))
-            if self.target_capability:
-                console.puts('Capability: {0}'.format(context.style.node(
-                    self.target_capability.name)))
-            if self.type is not None:
-                console.puts('Relationship type: {0}'.format(context.style.type(self.type.name)))
-            if (self.relationship_template is not None) and self.relationship_template.name:
-                console.puts('Relationship template: {0}'.format(
-                    context.style.node(self.relationship_template.name)))
-            utils.dump_dict_values(context, self.properties, 'Properties')
-            utils.dump_interfaces(context, self.interfaces, 'Interfaces')
-
-
-class CapabilityBase(InstanceModelMixin):
-    """
-    A capability of a :class:`Node`.
-
-    Usually an instance of a :class:`CapabilityTemplate`.
-
-    :ivar name: Name (unique for the node)
-    :vartype name: basestring
-    :ivar capability_template: Template from which this capability was instantiated (optional)
-    :vartype capability_template: :class:`capabilityTemplate`
-    :ivar type: Capability type
-    :vartype type: :class:`Type`
-    :ivar min_occurrences: Minimum number of requirement matches required
-    :vartype min_occurrences: int
-    :ivar max_occurrences: Maximum number of requirement matches allowed
-    :vartype min_occurrences: int
-    :ivar occurrences: Actual number of requirement matches
-    :vartype occurrences: int
-    :ivar properties: Associated parameters
-    :vartype properties: {basestring: :class:`Parameter`}
-
-    :ivar node: Containing node
-    :vartype node: :class:`Node`
-    :ivar relationship: Available when we are the target of a relationship
-    :vartype relationship: :class:`Relationship`
-    :ivar substitution_mapping: Our contribution to service substitution
-    :vartype substitution_mapping: :class:`SubstitutionMapping`
-    """
-
-    __tablename__ = 'capability'
-
-    @declared_attr
-    def capability_template(cls):
-        return cls.many_to_one_relationship('capability_template')
-
-    @declared_attr
-    def type(cls):
-        return cls.many_to_one_relationship('type')
-
-    min_occurrences = Column(Integer, default=None)
-    max_occurrences = Column(Integer, default=None)
-    occurrences = Column(Integer, default=0)
-
-    @declared_attr
-    def properties(cls):
-        return cls.many_to_many_relationship('parameter', table_prefix='properties',
-                                             dict_key='name')
-
-    # region foreign_keys
-
-    __private_fields__ = ['capability_fk',
-                          'node_fk',
-                          'capability_template_fk']
-
-    # Capability many-to-one to Type
-    @declared_attr
-    def type_fk(cls):
-        return cls.foreign_key('type')
-
-    # Node one-to-many to Capability
-    @declared_attr
-    def node_fk(cls):
-        return cls.foreign_key('node')
-
-    # Capability many-to-one to CapabilityTemplate
-    @declared_attr
-    def capability_template_fk(cls):
-        return cls.foreign_key('capability_template', nullable=True)
-
-    # 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):
-        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: {0}'.format(context.style.type(self.type.name)))
-            console.puts('Occurrences: {0:d} ({1:d}{2})'.format(
-                self.occurrences,
-                self.min_occurrences or 0,
-                ' to {0:d}'.format(self.max_occurrences)
-                if self.max_occurrences is not None
-                else ' or more'))
-            utils.dump_dict_values(context, self.properties, 'Properties')
-
-
-class InterfaceBase(InstanceModelMixin):
-    """
-    A typed set of :class:`Operation`.
-
-    Usually an instance of :class:`InterfaceTemplate`.
-
-    :ivar name: Name (unique for the node, group, or relationship)
-    :vartype name: basestring
-    :ivar interface_template: Template from which this interface was instantiated (optional)
-    :vartype interface_template: :class:`InterfaceTemplate`
-    :ivar type: Interface type
-    :vartype type: :class:`Type`
-    :ivar description: Human-readable description
-    :vartype description: string
-    :ivar inputs: Parameters that can be used by all operations in the interface
-    :vartype inputs: {basestring: :class:`Parameter`}
-    :ivar operations: Operations
-    :vartype operations: {basestring: :class:`Operation`}
-
-    :ivar node: Containing node
-    :vartype node: :class:`Node`
-    :ivar group: Containing group
-    :vartype group: :class:`Group`
-    :ivar relationship: Containing relationship
-    :vartype relationship: :class:`Relationship`
-    """
-
-    __tablename__ = 'interface'
-
-    @declared_attr
-    def interface_template(cls):
-        return cls.many_to_one_relationship('interface_template')
-
-    @declared_attr
-    def type(cls):
-        return cls.many_to_one_relationship('type')
-
-    description = Column(Text)
-
-    @declared_attr
-    def inputs(cls):
-        return cls.many_to_many_relationship('parameter', table_prefix='inputs',
-                                             dict_key='name')
-
-    @declared_attr
-    def operations(cls):
-        return cls.one_to_many_relationship('operation', dict_key='name')
-
-    # region foreign_keys
-
-    __private_fields__ = ['type_fk',
-                          'node_fk',
-                          'group_fk',
-                          'relationship_fk',
-                          'interface_template_fk']
-
-    # Interface many-to-one to Type
-    @declared_attr
-    def type_fk(cls):
-        return cls.foreign_key('type')
-
-    # Node one-to-many to Interface
-    @declared_attr
-    def node_fk(cls):
-        return cls.foreign_key('node', nullable=True)
-
-    # Group one-to-many to Interface
-    @declared_attr
-    def group_fk(cls):
-        return cls.foreign_key('group', nullable=True)
-
-    # Relationship one-to-many to Interface
-    @declared_attr
-    def relationship_fk(cls):
-        return cls.foreign_key('relationship', nullable=True)
-
-    # Interface many-to-one to InterfaceTemplate
-    @declared_attr
-    def interface_template_fk(cls):
-        return cls.foreign_key('interface_template', nullable=True)
-
-    # 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):
-        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: {0}'.format(context.style.type(self.type.name)))
-            utils.dump_dict_values(context, self.inputs, 'Inputs')
-            utils.dump_dict_values(context, self.operations, 'Operations')
-
-
-class OperationBase(InstanceModelMixin):
-    """
-    An operation in a :class:`Interface`.
-
-    Might be an instance of :class:`OperationTemplate`.
-
-    :ivar name: Name (unique for the interface or service)
-    :vartype name: basestring
-    :ivar operation_template: Template from which this operation was instantiated (optional)
-    :vartype operation_template: :class:`OperationTemplate`
-    :ivar description: Human-readable description
-    :vartype description: string
-    :ivar plugin: Associated plugin
-    :vartype plugin: :class:`Plugin`
-    :ivar implementation: Implementation string (interpreted by the plugin)
-    :vartype implementation: basestring
-    :ivar dependencies: Dependency strings (interpreted by the plugin)
-    :vartype dependencies: [basestring]
-    :ivar inputs: Parameters that can be used by this operation
-    :vartype inputs: {basestring: :class:`Parameter`}
-    :ivar executor: Executor name
-    :vartype executor: basestring
-    :ivar max_retries: Maximum number of retries allowed in case of failure
-    :vartype max_retries: int
-    :ivar retry_interval: Interval between retries (in seconds)
-    :vartype retry_interval: int
-
-    :ivar interface: Containing interface
-    :vartype interface: :class:`Interface`
-    :ivar service: Containing service
-    :vartype service: :class:`Service`
-    """
-
-    __tablename__ = 'operation'
-
-    @declared_attr
-    def operation_template(cls):
-        return cls.many_to_one_relationship('operation_template')
-
-    description = Column(Text)
-
-    @declared_attr
-    def plugin(cls):
-        return cls.one_to_one_relationship('plugin')
-
-    implementation = Column(Text)
-    dependencies = Column(modeling_types.StrictList(item_cls=basestring))
-
-    @declared_attr
-    def inputs(cls):
-        return cls.many_to_many_relationship('parameter', table_prefix='inputs',
-                                             dict_key='name')
-
-    executor = Column(Text)
-    max_retries = Column(Integer)
-    retry_interval = Column(Integer)
-
-    # region foreign_keys
-
-    __private_fields__ = ['service_fk',
-                          'interface_fk',
-                          'plugin_fk',
-                          'operation_template_fk']
-
-    # Service one-to-many to Operation
-    @declared_attr
-    def service_fk(cls):
-        return cls.foreign_key('service', nullable=True)
-
-    # Interface one-to-many to Operation
-    @declared_attr
-    def interface_fk(cls):
-        return cls.foreign_key('interface', nullable=True)
-
-    # Operation one-to-one to Plugin
-    @declared_attr
-    def plugin_fk(cls):
-        return cls.foreign_key('plugin', nullable=True)
-
-    # Operation many-to-one to OperationTemplate
-    @declared_attr
-    def operation_template_fk(cls):
-        return cls.foreign_key('operation_template', nullable=True)
-
-    # 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):
-        # TODO must be associated with interface or service
-        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: {0}'.format(
-                    context.style.literal(self.implementation)))
-            if self.dependencies:
-                console.puts(
-                    'Dependencies: {0}'.format(
-                        ', '.join((str(context.style.literal(v)) for v in self.dependencies))))
-            if self.executor is not None:
-                console.puts('Executor: {0}'.format(context.style.literal(self.executor)))
-            if self.max_retries is not None:
-                console.puts('Max retries: {0}'.format(context.style.literal(self.max_retries)))
-            if self.retry_interval is not None:
-                console.puts('Retry interval: {0}'.format(
-                    context.style.literal(self.retry_interval)))
-            utils.dump_dict_values(context, self.inputs, 'Inputs')
-
-
-class ArtifactBase(InstanceModelMixin):
-    """
-    A file associated with a :class:`Node`.
-
-    Usually an instance of :class:`ArtifactTemplate`.
-
-    :ivar name: Name (unique for the node)
-    :vartype name: basestring
-    :ivar artifact_template: Template from which this artifact was instantiated (optional)
-    :vartype artifact_template: :class:`ArtifactTemplate`
-    :ivar type: Artifact type
-    :vartype type: :class:`Type`
-    :ivar description: Human-readable description
-    :vartype description: string
-    :ivar source_path: Source path (CSAR or repository)
-    :vartype source_path: basestring
-    :ivar target_path: Path at destination machine
-    :vartype target_path: basestring
-    :ivar repository_url: Repository URL
-    :vartype repository_path: basestring
-    :ivar repository_credential: Credentials for accessing the repository
-    :vartype repository_credential: {basestring: basestring}
-    :ivar properties: Associated parameters
-    :vartype properties: {basestring: :class:`Parameter`}
-
-    :ivar node: Containing node
-    :vartype node: :class:`Node`
-    """
-
-    __tablename__ = 'artifact'
-
-    @declared_attr
-    def artifact_template(cls):
-        return cls.many_to_one_relationship('artifact_template')
-
-    @declared_attr
-    def type(cls):
-        return cls.many_to_one_relationship('type')
-
-    description = Column(Text)
-    source_path = Column(Text)
-    target_path = Column(Text)
-    repository_url = Column(Text)
-    repository_credential = Column(modeling_types.StrictDict(basestring, basestring))
-
-    @declared_attr
-    def properties(cls):
-        return cls.many_to_many_relationship('parameter', table_prefix='properties',
-                                             dict_key='name')
-
-    # region foreign_keys
-
-    __private_fields__ = ['type_fk',
-                          'node_fk',
-                          'artifact_template_fk']
-
-    # Artifact many-to-one to Type
-    @declared_attr
-    def type_fk(cls):
-        return cls.foreign_key('type')
-
-    # Node one-to-many to Artifact
-    @declared_attr
-    def node_fk(cls):
-        return cls.foreign_key('node')
-
-    # Artifact many-to-one to ArtifactTemplate
-    @declared_attr
-    def artifact_template_fk(cls):
-        return cls.foreign_key('artifact_template', nullable=True)
-
-    # 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):
-        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: {0}'.format(context.style.type(self.type.name)))
-            console.puts('Source path: {0}'.format(context.style.literal(self.source_path)))
-            if self.target_path is not None:
-                console.puts('Target path: {0}'.format(context.style.literal(self.target_path)))
-            if self.repository_url is not None:
-                console.puts('Repository URL: {0}'.format(
-                    context.style.literal(self.repository_url)))
-            if self.repository_credential:
-                console.puts('Repository credential: {0}'.format(
-                    context.style.literal(self.repository_credential)))
-            utils.dump_dict_values(context, self.properties, 'Properties')

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/dd5bfa93/aria/modeling/service_changes.py
----------------------------------------------------------------------
diff --git a/aria/modeling/service_changes.py b/aria/modeling/service_changes.py
new file mode 100644
index 0000000..b83a376
--- /dev/null
+++ b/aria/modeling/service_changes.py
@@ -0,0 +1,219 @@
+# 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.
+
+"""
+classes:
+    * ServiceUpdate - service update implementation model.
+    * ServiceUpdateStep - service update step implementation model.
+    * ServiceModification - service modification implementation model.
+"""
+
+# pylint: disable=no-self-argument, no-member, abstract-method
+
+from collections import namedtuple
+
+from sqlalchemy import (
+    Column,
+    Text,
+    DateTime,
+    Enum,
+)
+from sqlalchemy.ext.associationproxy import association_proxy
+from sqlalchemy.ext.declarative import declared_attr
+
+from .types import (List, Dict)
+from .mixins import ModelMixin
+
+__all__ = (
+    'ServiceUpdateBase',
+    'ServiceUpdateStepBase',
+    'ServiceModificationBase'
+)
+
+
+class ServiceUpdateBase(ModelMixin):
+    """
+    Deployment update model representation.
+    """
+
+    steps = None
+
+    __tablename__ = 'service_update'
+
+    _private_fields = ['execution_fk',
+                       'service_fk']
+
+    created_at = Column(DateTime, nullable=False, index=True)
+    service_plan = Column(Dict, nullable=False)
+    service_update_nodes = Column(Dict)
+    service_update_service = Column(Dict)
+    service_update_node_templates = Column(List)
+    modified_entity_ids = Column(Dict)
+    state = Column(Text)
+
+    @declared_attr
+    def execution(cls):
+        return cls._create_many_to_one_relationship('execution')
+
+    @declared_attr
+    def execution_name(cls):
+        return association_proxy('execution', cls.name_column_name())
+
+    @declared_attr
+    def service(cls):
+        return cls._create_many_to_one_relationship('service',
+                                                    backreference='updates')
+
+    @declared_attr
+    def service_name(cls):
+        return association_proxy('service', cls.name_column_name())
+
+    # region foreign keys
+
+    __private_fields__ = ['service_fk',
+                          'execution_fk']
+
+    @declared_attr
+    def execution_fk(cls):
+        return cls._create_foreign_key('execution', nullable=True)
+
+    @declared_attr
+    def service_fk(cls):
+        return cls._create_foreign_key('service')
+
+    # endregion
+
+    def to_dict(self, suppress_error=False, **kwargs):
+        dep_update_dict = super(ServiceUpdateBase, 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 ServiceUpdateStepBase(ModelMixin):
+    """
+    Deployment update step model representation.
+    """
+
+    __tablename__ = 'service_update_step'
+
+    _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, PLUGIN')
+    ENTITY_TYPES = _entity_types(
+        NODE='node',
+        RELATIONSHIP='relationship',
+        PROPERTY='property',
+        OPERATION='operation',
+        WORKFLOW='workflow',
+        OUTPUT='output',
+        DESCRIPTION='description',
+        GROUP='group',
+        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 service_update(cls):
+        return cls._create_many_to_one_relationship('service_update',
+                                                    backreference='steps')
+
+    @declared_attr
+    def service_update_name(cls):
+        return association_proxy('service_update', cls.name_column_name())
+
+    # region foreign keys
+
+    __private_fields__ = ['service_update_fk']
+
+    @declared_attr
+    def service_update_fk(cls):
+        return cls._create_foreign_key('service_update')
+
+    # endregion
+
+    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 ServiceModificationBase(ModelMixin):
+    """
+    Deployment modification model representation.
+    """
+
+    __tablename__ = 'service_modification'
+
+    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_node_templates = Column(Dict)
+    nodes = Column(Dict)
+    status = Column(Enum(*STATES, name='service_modification_status'))
+
+    @declared_attr
+    def service(cls):
+        return cls._create_many_to_one_relationship('service',
+                                                    backreference='modifications')
+
+    @declared_attr
+    def service_name(cls):
+        return association_proxy('service', cls.name_column_name())
+
+    # region foreign keys
+
+    __private_fields__ = ['service_fk']
+
+    @declared_attr
+    def service_fk(cls):
+        return cls._create_foreign_key('service')
+
+    # endregion

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/dd5bfa93/aria/modeling/service_common.py
----------------------------------------------------------------------
diff --git a/aria/modeling/service_common.py b/aria/modeling/service_common.py
new file mode 100644
index 0000000..b3535a6
--- /dev/null
+++ b/aria/modeling/service_common.py
@@ -0,0 +1,270 @@
+# 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.
+
+# pylint: disable=no-self-argument, no-member, abstract-method
+
+import cPickle as pickle
+import logging
+
+from sqlalchemy import (
+    Column,
+    Text,
+    Binary,
+)
+from sqlalchemy.ext.declarative import declared_attr
+
+from ..storage import exceptions
+from ..utils import collections, formatting, console
+from .mixins import InstanceModelMixin, TemplateModelMixin
+from .types import List
+from . import utils
+
+
+class ParameterBase(TemplateModelMixin):
+    """
+    Represents a typed value.
+
+    This model is used by both service template and service instance elements.
+
+    :ivar name: Name
+    :ivar type_name: Type name
+    :ivar value: Value
+    :ivar description: Description
+    """
+
+    __tablename__ = 'parameter'
+
+    name = Column(Text)
+    type_name = Column(Text)
+
+    # Check: value type
+    _value = Column(Binary, name='value')
+    description = Column(Text)
+
+    @property
+    def as_raw(self):
+        return collections.OrderedDict((
+            ('name', self.name),
+            ('type_name', self.type_name),
+            ('value', self.value),
+            ('description', self.description)))
+
+    @property
+    def value(self):
+        if self._value is None:
+            return None
+        try:
+            return pickle.loads(self._value)
+        except BaseException:
+            raise exceptions.StorageError('bad format for parameter of type "{0}": {1}'.format(
+                self.type_name, self._value))
+
+    @value.setter
+    def value(self, value):
+        if value is None:
+            self._value = None
+        else:
+            try:
+                self._value = pickle.dumps(value)
+            except (pickle.PicklingError, TypeError):
+                logging.getLogger('aria').warn('Could not pickle parameter of type "{0}": {1}'
+                                               .format(self.type_name, value))
+                self._value = pickle.dumps(str(value))
+
+    def instantiate(self, context, container):
+        from . import models
+        return models.Parameter(name=self.name,
+                                type_name=self.type_name,
+                                _value=self._value,
+                                description=self.description)
+
+    def coerce_values(self, context, container, report_issues):
+        if self.value is not None:
+            self.value = utils.coerce_value(context, container, self.value,
+                                            report_issues)
+
+    def dump(self, context):
+        if self.type_name is not None:
+            console.puts('{0}: {1} ({2})'.format(
+                context.style.property(self.name),
+                context.style.literal(self.value),
+                context.style.type(self.type_name)))
+        else:
+            console.puts('{0}: {1}'.format(
+                context.style.property(self.name),
+                context.style.literal(self.value)))
+        if self.description:
+            console.puts(context.style.meta(self.description))
+
+
+class TypeBase(InstanceModelMixin):
+    """
+    Represents a type and its children.
+    """
+
+    __tablename__ = 'type'
+
+    variant = Column(Text, nullable=False)
+    description = Column(Text)
+    _role = Column(Text, name='role')
+
+    @declared_attr
+    def parent(cls):
+        return cls._create_relationship_to_self('parent_type_fk')
+
+    @declared_attr
+    def children(cls):
+        return cls._create_one_to_many_relationship_to_self('parent_type_fk')
+
+    # region foreign keys
+
+    __private_fields__ = ['parent_type_fk']
+
+    # Type one-to-many to Type
+    @declared_attr
+    def parent_type_fk(cls):
+        return cls._create_foreign_key('type', nullable=True)
+
+    # endregion
+
+    @property
+    def role(self):
+        def get_role(the_type):
+            if the_type is None:
+                return None
+            elif the_type._role is None:
+                return get_role(the_type.parent)
+            return the_type._role
+
+        return get_role(self)
+
+    @role.setter
+    def role(self, value):
+        self._role = value
+
+    def is_descendant(self, base_name, name):
+        base = self.get_descendant(base_name)
+        if base is not None:
+            if base.get_descendant(name) is not None:
+                return True
+        return False
+
+    def get_descendant(self, name):
+        if self.name == name:
+            return self
+        for child in self.children:
+            found = child.get_descendant(name)
+            if found is not None:
+                return found
+        return None
+
+    def iter_descendants(self):
+        for child in self.children:
+            yield child
+            for descendant in child.iter_descendants():
+                yield descendant
+
+    @property
+    def as_raw(self):
+        return collections.OrderedDict((
+            ('name', self.name),
+            ('description', self.description),
+            ('role', self.role)))
+
+    @property
+    def as_raw_all(self):
+        types = []
+        self._append_raw_children(types)
+        return types
+
+    def dump(self, context):
+        if self.name:
+            console.puts(context.style.type(self.name))
+        with context.style.indent:
+            for child in self.children:
+                child.dump(context)
+
+    def _append_raw_children(self, types):
+        for child in self.children:
+            raw_child = formatting.as_raw(child)
+            raw_child['parent'] = self.name
+            types.append(raw_child)
+            child._append_raw_children(types)
+
+
+class MetadataBase(TemplateModelMixin):
+    """
+    Custom values associated with the service.
+
+    This model is used by both service template and service instance elements.
+
+    :ivar name: Name
+    :ivar value: Value
+    """
+
+    __tablename__ = 'metadata'
+
+    value = Column(Text)
+
+    @property
+    def as_raw(self):
+        return collections.OrderedDict((
+            ('name', self.name),
+            ('value', self.value)))
+
+    def instantiate(self, context, container):
+        from . import models
+        return models.Metadata(name=self.name,
+                               value=self.value)
+
+    def dump(self, context):
+        console.puts('{0}: {1}'.format(
+            context.style.property(self.name),
+            context.style.literal(self.value)))
+
+
+class PluginSpecificationBase(InstanceModelMixin):
+    """
+    Plugin specification model representation.
+    """
+
+    __tablename__ = 'plugin_specification'
+
+    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)
+
+    # region foreign keys
+
+    __private_fields__ = ['service_template_fk']
+
+    @declared_attr
+    def service_template_fk(cls):
+        return cls._create_foreign_key('service_template', nullable=True)
+
+    # endregion
+
+    def find_plugin(self, plugins):
+        # TODO: this should check versions/distribution and other specification
+        for plugin in plugins:
+            if plugin.name == self.name:
+                return plugin
+        return None


[8/8] incubator-ariatosca git commit: Unified_coerce

Posted by mx...@apache.org.
Unified_coerce


Project: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/commit/367b18bc
Tree: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/tree/367b18bc
Diff: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/diff/367b18bc

Branch: refs/heads/Unified_coerce
Commit: 367b18bc0cc719c13fb19f3c2f486bdd8e4e1785
Parents: dd5bfa9
Author: max-orlov <ma...@gigaspaces.com>
Authored: Wed Mar 8 17:02:34 2017 +0200
Committer: max-orlov <ma...@gigaspaces.com>
Committed: Sun Mar 12 11:01:27 2017 +0200

----------------------------------------------------------------------
 aria/modeling/mixins.py           | 13 ++++++++-
 aria/modeling/service_instance.py | 45 -------------------------------
 aria/modeling/service_template.py | 49 ----------------------------------
 aria/modeling/utils.py            | 13 ---------
 4 files changed, 12 insertions(+), 108 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/367b18bc/aria/modeling/mixins.py
----------------------------------------------------------------------
diff --git a/aria/modeling/mixins.py b/aria/modeling/mixins.py
index 04497b5..c050fa1 100644
--- a/aria/modeling/mixins.py
+++ b/aria/modeling/mixins.py
@@ -388,7 +388,18 @@ class InstanceModelMixin(ModelMixin):
         pass
 
     def coerce_values(self, context, container, report_issues):
-        pass
+        if container is None:
+            return
+        for value in vars(container).values():
+            if value is not None and isinstance(value, ModelMixin):
+                if isinstance(value, dict):
+                    value = value.values()
+
+                if isinstance(value, list):
+                    for item in value:
+                        item.coerce_values(context, container, report_issues)
+                else:
+                    utils.coerce_value(context, container, value, report_issues)
 
     def dump(self, context):
         pass

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/367b18bc/aria/modeling/service_instance.py
----------------------------------------------------------------------
diff --git a/aria/modeling/service_instance.py b/aria/modeling/service_instance.py
index ba18f73..dd579b3 100644
--- a/aria/modeling/service_instance.py
+++ b/aria/modeling/service_instance.py
@@ -234,17 +234,6 @@ class ServiceBase(InstanceModelMixin): # pylint: disable=too-many-public-methods
         utils.validate_dict_values(context, self.outputs)
         utils.validate_dict_values(context, self.workflows)
 
-    def coerce_values(self, context, container, report_issues):
-        utils.coerce_dict_values(context, container, self.meta_data, report_issues)
-        utils.coerce_list_values(context, container, self.nodes, report_issues)
-        utils.coerce_list_values(context, container, self.groups, report_issues)
-        utils.coerce_list_values(context, container, self.policies, report_issues)
-        if self.substitution is not None:
-            self.substitution.coerce_values(context, container, report_issues)
-        utils.coerce_dict_values(context, container, self.inputs, report_issues)
-        utils.coerce_dict_values(context, container, self.outputs, report_issues)
-        utils.coerce_dict_values(context, container, self.workflows, report_issues)
-
     def dump(self, context):
         if self.description is not None:
             console.puts(context.style.meta(self.description))
@@ -553,13 +542,6 @@ class NodeBase(InstanceModelMixin): # pylint: disable=too-many-public-methods
         utils.validate_dict_values(context, self.capabilities)
         utils.validate_list_values(context, self.outbound_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.outbound_relationships, report_issues)
-
     def dump(self, context):
         console.puts('Node: {0}'.format(context.style.node(self.name)))
         with context.style.indent:
@@ -656,10 +638,6 @@ class GroupBase(InstanceModelMixin):
         utils.validate_dict_values(context, self.properties)
         utils.validate_dict_values(context, self.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.interfaces, report_issues)
-
     def dump(self, context):
         console.puts('Group: {0}'.format(context.style.node(self.name)))
         with context.style.indent:
@@ -754,9 +732,6 @@ class PolicyBase(InstanceModelMixin):
     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('Policy: {0}'.format(context.style.node(self.name)))
         with context.style.indent:
@@ -831,9 +806,6 @@ class SubstitutionBase(InstanceModelMixin):
     def validate(self, context):
         utils.validate_dict_values(context, self.mappings)
 
-    def coerce_values(self, context, container, report_issues):
-        utils.coerce_dict_values(context, container, self.mappings, report_issues)
-
     def dump(self, context):
         console.puts('Substitution:')
         with context.style.indent:
@@ -1050,10 +1022,6 @@ class RelationshipBase(InstanceModelMixin):
         utils.validate_dict_values(context, self.properties)
         utils.validate_dict_values(context, self.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.interfaces, report_issues)
-
     def dump(self, context):
         if self.name:
             console.puts('{0} ->'.format(context.style.node(self.name)))
@@ -1167,9 +1135,6 @@ class CapabilityBase(InstanceModelMixin):
     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))
         with context.style.indent:
@@ -1279,10 +1244,6 @@ class InterfaceBase(InstanceModelMixin):
         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:
@@ -1395,9 +1356,6 @@ class OperationBase(InstanceModelMixin):
         # TODO must be associated with interface or service
         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:
@@ -1508,9 +1466,6 @@ class ArtifactBase(InstanceModelMixin):
     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:

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/367b18bc/aria/modeling/service_template.py
----------------------------------------------------------------------
diff --git a/aria/modeling/service_template.py b/aria/modeling/service_template.py
index c9a02eb..9debc1c 100644
--- a/aria/modeling/service_template.py
+++ b/aria/modeling/service_template.py
@@ -328,17 +328,6 @@ class ServiceTemplateBase(TemplateModelMixin): # pylint: disable=too-many-public
         if self.artifact_types is not None:
             self.artifact_types.validate(context)
 
-    def coerce_values(self, context, container, report_issues):
-        utils.coerce_dict_values(context, container, self.meta_data, report_issues)
-        utils.coerce_list_values(context, container, self.node_templates, report_issues)
-        utils.coerce_list_values(context, container, self.group_templates, report_issues)
-        utils.coerce_list_values(context, container, self.policy_templates, report_issues)
-        if self.substitution_template is not None:
-            self.substitution_template.coerce_values(context, container, report_issues)
-        utils.coerce_dict_values(context, container, self.inputs, report_issues)
-        utils.coerce_dict_values(context, container, self.outputs, report_issues)
-        utils.coerce_dict_values(context, container, self.workflow_templates, report_issues)
-
     def dump(self, context):
         if self.description is not None:
             console.puts(context.style.meta(self.description))
@@ -526,13 +515,6 @@ class NodeTemplateBase(TemplateModelMixin):
         utils.validate_dict_values(context, self.capability_templates)
         utils.validate_list_values(context, self.requirement_templates)
 
-    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.interface_templates, report_issues)
-        utils.coerce_dict_values(context, self, self.artifact_templates, report_issues)
-        utils.coerce_dict_values(context, self, self.capability_templates, report_issues)
-        utils.coerce_list_values(context, self, self.requirement_templates, report_issues)
-
     def dump(self, context):
         console.puts('Node template: {0}'.format(context.style.node(self.name)))
         if self.description:
@@ -643,10 +625,6 @@ class GroupTemplateBase(TemplateModelMixin):
         utils.validate_dict_values(context, self.properties)
         utils.validate_dict_values(context, self.interface_templates)
 
-    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.interface_templates, report_issues)
-
     def dump(self, context):
         console.puts('Group template: {0}'.format(context.style.node(self.name)))
         if self.description:
@@ -748,9 +726,6 @@ class PolicyTemplateBase(TemplateModelMixin):
     def validate(self, context):
         utils.validate_dict_values(context, self.properties)
 
-    def coerce_values(self, context, container, report_issues):
-        utils.coerce_dict_values(context, self, self.properties, report_issues)
-
     def dump(self, context):
         console.puts('Policy template: {0}'.format(context.style.node(self.name)))
         if self.description:
@@ -819,9 +794,6 @@ class SubstitutionTemplateBase(TemplateModelMixin):
     def validate(self, context):
         utils.validate_dict_values(context, self.mappings)
 
-    def coerce_values(self, context, container, report_issues):
-        utils.coerce_dict_values(context, self, self.mappings, report_issues)
-
     def dump(self, context):
         console.puts('Substitution template:')
         with context.style.indent:
@@ -1089,10 +1061,6 @@ class RequirementTemplateBase(TemplateModelMixin):
         if self.relationship_template:
             self.relationship_template.validate(context)
 
-    def coerce_values(self, context, container, report_issues):
-        if self.relationship_template is not None:
-            self.relationship_template.coerce_values(context, container, report_issues)
-
     def dump(self, context):
         if self.name:
             console.puts(context.style.node(self.name))
@@ -1200,10 +1168,6 @@ class RelationshipTemplateBase(TemplateModelMixin):
         utils.validate_dict_values(context, self.properties)
         utils.validate_dict_values(context, self.interface_templates)
 
-    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.interface_templates, report_issues)
-
     def dump(self, context):
         if self.type is not None:
             console.puts('Relationship type: {0}'.format(context.style.type(self.type.name)))
@@ -1330,9 +1294,6 @@ class CapabilityTemplateBase(TemplateModelMixin):
     def validate(self, context):
         utils.validate_dict_values(context, self.properties)
 
-    def coerce_values(self, context, container, report_issues):
-        utils.coerce_dict_values(context, self, self.properties, report_issues)
-
     def dump(self, context):
         console.puts(context.style.node(self.name))
         if self.description:
@@ -1446,10 +1407,6 @@ class InterfaceTemplateBase(TemplateModelMixin):
         utils.validate_dict_values(context, self.inputs)
         utils.validate_dict_values(context, self.operation_templates)
 
-    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.operation_templates, report_issues)
-
     def dump(self, context):
         console.puts(context.style.node(self.name))
         if self.description:
@@ -1565,9 +1522,6 @@ class OperationTemplateBase(TemplateModelMixin):
     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:
@@ -1678,9 +1632,6 @@ class ArtifactTemplateBase(TemplateModelMixin):
     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:

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/367b18bc/aria/modeling/utils.py
----------------------------------------------------------------------
diff --git a/aria/modeling/utils.py b/aria/modeling/utils.py
index a071d69..857e81e 100644
--- a/aria/modeling/utils.py
+++ b/aria/modeling/utils.py
@@ -56,19 +56,6 @@ def validate_list_values(context, the_list):
         value.validate(context)
 
 
-def coerce_dict_values(context, container, the_dict, report_issues=False):
-    if not the_dict:
-        return
-    coerce_list_values(context, container, the_dict.itervalues(), report_issues)
-
-
-def coerce_list_values(context, container, the_list, report_issues=False):
-    if not the_list:
-        return
-    for value in the_list:
-        value.coerce_values(context, container, report_issues)
-
-
 def instantiate_dict(context, container, the_dict, from_dict):
     if not from_dict:
         return


[7/8] incubator-ariatosca git commit: Separate plugin specification form plugin; move dry support to CLI; various renames and refactorings

Posted by mx...@apache.org.
Separate plugin specification form plugin; move dry support to CLI; various renames and refactorings


Project: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/commit/dd5bfa93
Tree: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/tree/dd5bfa93
Diff: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/diff/dd5bfa93

Branch: refs/heads/Unified_coerce
Commit: dd5bfa930dede8a81ce382e65cad8fe924d4e99d
Parents: aa01cd4
Author: Tal Liron <ta...@gmail.com>
Authored: Fri Mar 10 16:57:17 2017 -0600
Committer: Tal Liron <ta...@gmail.com>
Committed: Fri Mar 10 16:57:17 2017 -0600

----------------------------------------------------------------------
 aria/cli/args_parser.py                         |    5 +
 aria/cli/commands.py                            |    4 +
 aria/cli/dry.py                                 |   75 +
 aria/modeling/__init__.py                       |   18 +-
 aria/modeling/bases.py                          |  405 -----
 aria/modeling/misc.py                           |  234 ---
 aria/modeling/mixins.py                         |  405 +++++
 aria/modeling/models.py                         |   91 +-
 aria/modeling/orchestration.py                  |  235 +--
 aria/modeling/service.py                        | 1529 ------------------
 aria/modeling/service_changes.py                |  219 +++
 aria/modeling/service_common.py                 |  270 ++++
 aria/modeling/service_instance.py               | 1529 ++++++++++++++++++
 aria/modeling/service_template.py               |  260 +--
 aria/orchestrator/workflows/api/task.py         |   44 +-
 .../workflows/builtin/execute_operation.py      |    3 +-
 aria/orchestrator/workflows/builtin/utils.py    |   13 +-
 .../orchestrator/workflows/builtin/workflows.py |   60 +-
 aria/orchestrator/workflows/core/task.py        |   13 +-
 aria/orchestrator/workflows/dry.py              |   53 -
 .../simple_v1_0/modeling/__init__.py            |   47 +-
 tests/end2end/test_orchestrator.py              |    3 +
 tests/mock/models.py                            |   16 +
 tests/modeling/__init__.py                      |   34 +
 tests/modeling/test_mixins.py                   |  219 +++
 tests/modeling/test_model_storage.py            |  102 ++
 tests/modeling/test_models.py                   |  835 ++++++++++
 tests/orchestrator/context/test_operation.py    |   15 +-
 tests/orchestrator/context/test_serialize.py    |    7 +-
 tests/orchestrator/context/test_toolbelt.py     |    2 +-
 tests/orchestrator/workflows/api/test_task.py   |   33 +-
 .../workflows/builtin/test_execute_operation.py |    2 +-
 tests/orchestrator/workflows/core/test_task.py  |   15 +-
 .../tosca-simple-1.0/node-cellar/workflows.py   |    3 +-
 tests/storage/__init__.py                       |   22 +-
 tests/storage/test_instrumentation.py           |    8 +-
 tests/storage/test_model_storage.py             |  102 --
 tests/storage/test_models.py                    |  833 ----------
 tests/storage/test_structures.py                |  220 ---
 39 files changed, 4069 insertions(+), 3914 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/dd5bfa93/aria/cli/args_parser.py
----------------------------------------------------------------------
diff --git a/aria/cli/args_parser.py b/aria/cli/args_parser.py
index e092ee6..1d18145 100644
--- a/aria/cli/args_parser.py
+++ b/aria/cli/args_parser.py
@@ -137,6 +137,11 @@ def add_workflow_parser(workflow):
         '-w', '--workflow',
         default='install',
         help='The workflow name')
+    workflow.add_flag_argument(
+        'dry',
+        default=True,
+        help_true='dry run',
+        help_false='wet run')
 
 
 @sub_parser_decorator(

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/dd5bfa93/aria/cli/commands.py
----------------------------------------------------------------------
diff --git a/aria/cli/commands.py b/aria/cli/commands.py
index 52d4f14..a9079c5 100644
--- a/aria/cli/commands.py
+++ b/aria/cli/commands.py
@@ -50,6 +50,7 @@ from ..utils.collections import OrderedDict
 from ..orchestrator import WORKFLOW_DECORATOR_RESERVED_ARGUMENTS
 from ..orchestrator.runner import Runner
 from ..orchestrator.workflows.builtin import BUILTIN_WORKFLOWS
+from .dry import convert_to_dry
 
 from .exceptions import (
     AriaCliFormatInputsError,
@@ -212,6 +213,7 @@ class WorkflowCommand(BaseCommand):
 
         context = self._parse(args_namespace.uri)
         workflow_fn, inputs = self._get_workflow(context, args_namespace.workflow)
+        self._dry = args_namespace.dry
         self._run(context, args_namespace.workflow, workflow_fn, inputs)
     
     def _parse(self, uri):
@@ -265,6 +267,8 @@ class WorkflowCommand(BaseCommand):
     def _run(self, context, workflow_name, workflow_fn, inputs):
         # Storage
         def _initialize_storage(model_storage):
+            if self._dry:
+                convert_to_dry(context.modeling.instance)
             context.modeling.store(model_storage)
 
         # Create runner

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/dd5bfa93/aria/cli/dry.py
----------------------------------------------------------------------
diff --git a/aria/cli/dry.py b/aria/cli/dry.py
new file mode 100644
index 0000000..98b7217
--- /dev/null
+++ b/aria/cli/dry.py
@@ -0,0 +1,75 @@
+# 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 threading import RLock
+
+from ..modeling import models
+from ..orchestrator.decorators import operation
+from ..utils.collections import OrderedDict
+from ..utils.console import puts, Colored
+from ..utils.formatting import safe_repr
+
+
+_TERMINAL_LOCK = RLock()
+
+
+def convert_to_dry(service):
+    for workflow in service.workflows:
+        convert_operation_to_dry(workflow)
+
+    for node in service.nodes:
+        for interface in node.interfaces.itervalues():
+            for oper in interface.operations.itervalues():
+                convert_operation_to_dry(oper)
+        for relationship in node.outbound_relationships:
+            for interface in relationship.interfaces.itervalues():
+                for oper in interface.operations.itervalues():
+                    convert_operation_to_dry(oper)
+
+
+def convert_operation_to_dry(oper):
+    plugin = oper.plugin_specification.name \
+        if oper.plugin_specification is not None else None
+    if oper.inputs is None:
+        oper.inputs = OrderedDict()
+    oper.inputs['_implementation'] = models.Parameter(name='_implementation',
+                                                      type_name='string',
+                                                      value=oper.implementation)
+    oper.inputs['_plugin'] = models.Parameter(name='_plugin',
+                                              type_name='string',
+                                              value=plugin)
+    oper.implementation = '{0}.{1}'.format(__name__, 'dry_operation')
+    oper.plugin_specification = None
+
+
+@operation
+def dry_operation(ctx, _plugin, _implementation, **kwargs):
+    with _TERMINAL_LOCK:
+        print ctx.name
+        if hasattr(ctx, 'relationship'):
+            puts('> Relationship: {0} -> {1}'.format(
+                Colored.red(ctx.relationship.source_node.name),
+                Colored.red(ctx.relationship.target_node.name)))
+        else:
+            puts('> Node: {0}'.format(Colored.red(ctx.node.name)))
+        puts('  Operation: {0}'.format(Colored.green(ctx.name)))
+        _dump_implementation(_plugin, _implementation)
+
+
+def _dump_implementation(plugin, implementation):
+    if plugin:
+        puts('  Plugin: {0}'.format(Colored.magenta(plugin, bold=True)))
+    if implementation:
+        puts('  Implementation: {0}'.format(Colored.magenta(safe_repr(implementation))))

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/dd5bfa93/aria/modeling/__init__.py
----------------------------------------------------------------------
diff --git a/aria/modeling/__init__.py b/aria/modeling/__init__.py
index 67b5703..4dfc39d 100644
--- a/aria/modeling/__init__.py
+++ b/aria/modeling/__init__.py
@@ -16,26 +16,32 @@
 from collections import namedtuple
 
 from . import (
-    bases,
+    mixins,
     types,
     models,
     service_template as _service_template_bases,
-    service as _service_bases,
-    orchestration as _orchestration_bases,
+    service_instance as _service_instance_bases,
+    service_changes as _service_changes_bases,
+    service_common as _service_common_bases,
+    orchestration as _orchestration_bases
 )
 
 
 _ModelBasesCls = namedtuple('ModelBase', 'service_template,'
-                                         'service,'
+                                         'service_instance,'
+                                         'service_changes,'
+                                         'service_common,'
                                          'orchestration')
 
 model_bases = _ModelBasesCls(service_template=_service_template_bases,
-                             service=_service_bases,
+                             service_instance=_service_instance_bases,
+                             service_changes=_service_changes_bases,
+                             service_common=_service_common_bases,
                              orchestration=_orchestration_bases)
 
 
 __all__ = (
-    'bases',
+    'mixins',
     'types',
     'models',
     'model_bases',

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/dd5bfa93/aria/modeling/bases.py
----------------------------------------------------------------------
diff --git a/aria/modeling/bases.py b/aria/modeling/bases.py
deleted file mode 100644
index efcb968..0000000
--- a/aria/modeling/bases.py
+++ /dev/null
@@ -1,405 +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.structures module
-Path: aria.storage.structures
-
-models module holds ARIA's models.
-
-classes:
-    * ModelMixin - abstract model implementation.
-    * ModelIDMixin - abstract model implementation with IDs.
-"""
-
-from sqlalchemy.orm import relationship, backref
-from sqlalchemy.orm.collections import attribute_mapped_collection
-from sqlalchemy.ext import associationproxy
-from sqlalchemy import (
-    Column,
-    ForeignKey,
-    Integer,
-    Text,
-    Table,
-)
-
-from . import utils
-from ..utils import formatting
-
-
-class ModelMixin(object):
-
-    @utils.classproperty
-    def __modelname__(cls):                                                                         # pylint: disable=no-self-argument
-        return getattr(cls, '__mapiname__', cls.__tablename__)
-
-    @classmethod
-    def id_column_name(cls):
-        raise NotImplementedError
-
-    @classmethod
-    def name_column_name(cls):
-        raise NotImplementedError
-
-    @classmethod
-    def foreign_key(cls, parent_table, nullable=False):
-        """
-        Return a ForeignKey object.
-
-        :param parent_table: Parent table name
-        :param nullable: Should the column be allowed to remain empty
-        """
-        return Column(Integer,
-                      ForeignKey('{table}.id'.format(table=parent_table),
-                                 ondelete='CASCADE'),
-                      nullable=nullable)
-
-    @classmethod
-    def relationship_to_self(cls,
-                             column_name,
-                             relationship_kwargs=None):
-        relationship_kwargs = relationship_kwargs or {}
-
-        remote_side = '{cls}.{remote_column}'.format(
-            cls=cls.__name__,
-            remote_column=cls.id_column_name()
-        )
-
-        primaryjoin = '{remote_side} == {cls}.{column}'.format(
-            remote_side=remote_side,
-            cls=cls.__name__,
-            column=column_name
-        )
-
-        return relationship(
-            cls._get_cls_by_tablename(cls.__tablename__).__name__,
-            primaryjoin=primaryjoin,
-            remote_side=remote_side,
-            post_update=True,
-            **relationship_kwargs
-        )
-
-    @classmethod
-    def one_to_many_relationship_to_self(cls,
-                                         key,
-                                         dict_key=None,
-                                         relationship_kwargs=None):
-        relationship_kwargs = relationship_kwargs or {}
-
-        relationship_kwargs.setdefault('remote_side', '{cls}.{remote_column}'.format(
-            cls=cls.__name__,
-            remote_column=key
-        ))
-
-        return cls._create_relationship(cls.__tablename__, None, relationship_kwargs,
-                                        backreference='', dict_key=dict_key)
-
-    @classmethod
-    def one_to_one_relationship(cls,
-                                other_table,
-                                key=None,
-                                foreign_key=None,
-                                backreference=None,
-                                backref_kwargs=None,
-                                relationship_kwargs=None):
-        backref_kwargs = backref_kwargs or {}
-        backref_kwargs.setdefault('uselist', False)
-
-        return cls._create_relationship(other_table, backref_kwargs, relationship_kwargs,
-                                        backreference, key=key, foreign_key=foreign_key)
-
-    @classmethod
-    def one_to_many_relationship(cls,
-                                 child_table,
-                                 key=None,
-                                 foreign_key=None,
-                                 dict_key=None,
-                                 backreference=None,
-                                 backref_kwargs=None,
-                                 relationship_kwargs=None):
-        backref_kwargs = backref_kwargs or {}
-        backref_kwargs.setdefault('uselist', False)
-
-        return cls._create_relationship(child_table, backref_kwargs, relationship_kwargs,
-                                        backreference, key=key, foreign_key=foreign_key,
-                                        dict_key=dict_key)
-
-    @classmethod
-    def many_to_one_relationship(cls,
-                                 parent_table,
-                                 key=None,
-                                 foreign_key=None,
-                                 backreference=None,
-                                 backref_kwargs=None,
-                                 relationship_kwargs=None):
-        """
-        Return a one-to-many SQL relationship object
-        Meant to be used from inside the *child* object
-
-        :param parent_table: Name of the parent table
-        :param foreign_key: The column of the foreign key (from the child table)
-        :param backreference: The name to give to the reference to the child (on the parent table)
-        """
-
-        if backreference is None:
-            backreference = formatting.pluralize(cls.__tablename__)
-
-        backref_kwargs = backref_kwargs or {}
-        backref_kwargs.setdefault('uselist', True)
-        backref_kwargs.setdefault('lazy', 'dynamic')
-        # The following line make sure that when the *parent* is deleted, all its connected children
-        # are deleted as well
-        backref_kwargs.setdefault('cascade', 'all')
-
-        return cls._create_relationship(parent_table, backref_kwargs, relationship_kwargs,
-                                        backreference, key=key, foreign_key=foreign_key)
-
-    @classmethod
-    def many_to_many_relationship(cls,
-                                  other_table,
-                                  table_prefix=None,
-                                  key=None,
-                                  dict_key=None,
-                                  backreference=None,
-                                  backref_kwargs=None,
-                                  relationship_kwargs=None):
-        """
-        Return a many-to-many SQL relationship object
-
-        Notes:
-
-        1. The backreference name is the current table's table name
-        2. This method creates a new helper table in the DB
-
-        :param cls: The class of the table we're connecting from
-        :param other_table: The class of the table we're connecting to
-        :param table_prefix: Custom prefix for the helper table name and the backreference name
-        :param dict_key: If provided, will use a dict class with this column as the key
-        """
-
-        this_table = cls.__tablename__
-        this_column_name = '{0}_id'.format(this_table)
-        this_foreign_key = '{0}.id'.format(this_table)
-
-        other_column_name = '{0}_id'.format(other_table)
-        other_foreign_key = '{0}.id'.format(other_table)
-
-        helper_table = '{0}_{1}'.format(this_table, other_table)
-
-        if backreference is None:
-            backreference = formatting.pluralize(this_table)
-            if table_prefix:
-                helper_table = '{0}_{1}'.format(table_prefix, helper_table)
-                backreference = '{0}_{1}'.format(table_prefix, backreference)
-
-        backref_kwargs = backref_kwargs or {}
-        backref_kwargs.setdefault('uselist', True)
-
-        relationship_kwargs = relationship_kwargs or {}
-        relationship_kwargs.setdefault('secondary', cls._get_secondary_table(
-            cls.metadata,
-            helper_table,
-            this_column_name,
-            other_column_name,
-            this_foreign_key,
-            other_foreign_key
-        ))
-
-        return cls._create_relationship(other_table, backref_kwargs, relationship_kwargs,
-                                        backreference, key=key, dict_key=dict_key)
-
-    def to_dict(self, fields=None, suppress_error=False):
-        """
-        Return a dict representation of the model
-
-        :param suppress_error: If set to True, sets `None` to attributes that
-                               it's unable to retrieve (e.g., if a relationship wasn't established
-                               yet, and so it's impossible to access a property through it)
-        """
-
-        res = dict()
-        fields = fields or self.fields()
-        for field in fields:
-            try:
-                field_value = getattr(self, field)
-            except AttributeError:
-                if suppress_error:
-                    field_value = None
-                else:
-                    raise
-            if isinstance(field_value, list):
-                field_value = list(field_value)
-            elif isinstance(field_value, dict):
-                field_value = dict(field_value)
-            elif isinstance(field_value, ModelMixin):
-                field_value = field_value.to_dict()
-            res[field] = field_value
-
-        return res
-
-    @classmethod
-    def _create_relationship(cls, table, backref_kwargs, relationship_kwargs, backreference,
-                             key=None, foreign_key=None, dict_key=None):
-        relationship_kwargs = relationship_kwargs or {}
-
-        if key:
-            relationship_kwargs.setdefault('foreign_keys',
-                                           lambda: getattr(
-                                               cls._get_cls_by_tablename(cls.__tablename__),
-                                               key))
-
-        elif foreign_key:
-            relationship_kwargs.setdefault('foreign_keys',
-                                           lambda: getattr(
-                                               cls._get_cls_by_tablename(table),
-                                               foreign_key))
-
-        if dict_key:
-            relationship_kwargs.setdefault('collection_class',
-                                           attribute_mapped_collection(dict_key))
-
-        if backreference == '':
-            return relationship(
-                lambda: cls._get_cls_by_tablename(table),
-                **relationship_kwargs
-            )
-        else:
-            if backreference is None:
-                backreference = cls.__tablename__
-            backref_kwargs = backref_kwargs or {}
-            return relationship(
-                lambda: cls._get_cls_by_tablename(table),
-                backref=backref(backreference, **backref_kwargs),
-                **relationship_kwargs
-            )
-
-    @staticmethod
-    def _get_secondary_table(metadata,
-                             helper_table,
-                             first_column,
-                             second_column,
-                             first_foreign_key,
-                             second_foreign_key):
-        """
-        Create a helper table for a many-to-many relationship
-
-        :param helper_table: The name of the table
-        :param first_column_name: The name of the first column in the table
-        :param second_column_name: The name of the second column in the table
-        :param first_foreign_key: The string representing the first foreign key,
-               for example `blueprint.storage_id`, or `tenants.id`
-        :param second_foreign_key: The string representing the second foreign key
-        :return: A Table object
-        """
-
-        return Table(
-            helper_table,
-            metadata,
-            Column(
-                first_column,
-                Integer,
-                ForeignKey(first_foreign_key)
-            ),
-            Column(
-                second_column,
-                Integer,
-                ForeignKey(second_foreign_key)
-            )
-        )
-
-    @classmethod
-    def _association_proxies(cls):
-        for col, value in vars(cls).items():
-            if isinstance(value, associationproxy.AssociationProxy):
-                yield col
-
-    @classmethod
-    def fields(cls):
-        """
-        Return the list of field names for this table
-
-        Mostly for backwards compatibility in the code (that uses `fields`)
-        """
-
-        fields = set(cls._association_proxies())
-        fields.update(cls.__table__.columns.keys())
-        return fields - set(getattr(cls, '__private_fields__', []))
-
-    @classmethod
-    def _get_cls_by_tablename(cls, tablename):
-        """
-        Return class reference mapped to table.
-
-        :param tablename: String with name of table.
-        :return: Class reference or None.
-        """
-
-        if tablename in (cls.__name__, cls.__tablename__):
-            return cls
-
-        for table_cls in cls._decl_class_registry.values():
-            if tablename == getattr(table_cls, '__tablename__', None):
-                return table_cls
-
-    def __repr__(self):
-        return '<{cls} id=`{id}`>'.format(
-            cls=self.__class__.__name__,
-            id=getattr(self, self.name_column_name()))
-
-
-class ModelIDMixin(object):
-    id = Column(Integer, primary_key=True, autoincrement=True)
-    name = Column(Text, index=True)
-
-    @classmethod
-    def id_column_name(cls):
-        return 'id'
-
-    @classmethod
-    def name_column_name(cls):
-        return 'name'
-
-
-class InstanceModelMixin(ModelMixin):
-    """
-    Mixin for :class:`ServiceInstance` models.
-
-    All models support validation, diagnostic dumping, and representation as
-    raw data (which can be translated into JSON or YAML) via :code:`as_raw`.
-    """
-
-    @property
-    def as_raw(self):
-        raise NotImplementedError
-
-    def validate(self, context):
-        pass
-
-    def coerce_values(self, context, container, report_issues):
-        pass
-
-    def dump(self, context):
-        pass
-
-
-class TemplateModelMixin(InstanceModelMixin):
-    """
-    Mixin for :class:`ServiceTemplate` models.
-
-    All model models can be instantiated into :class:`ServiceInstance` models.
-    """
-
-    def instantiate(self, context, container):
-        raise NotImplementedError

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/dd5bfa93/aria/modeling/misc.py
----------------------------------------------------------------------
diff --git a/aria/modeling/misc.py b/aria/modeling/misc.py
deleted file mode 100644
index 105876a..0000000
--- a/aria/modeling/misc.py
+++ /dev/null
@@ -1,234 +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.
-
-# pylint: disable=no-self-argument, no-member, abstract-method
-
-import cPickle as pickle
-import logging
-
-from sqlalchemy import (
-    Column,
-    Text,
-    Binary
-)
-from sqlalchemy.ext.declarative import declared_attr
-
-from ..storage import exceptions
-from ..utils import collections, formatting, console
-from .bases import InstanceModelMixin, TemplateModelMixin
-from . import utils
-
-
-class ParameterBase(TemplateModelMixin):
-    """
-    Represents a typed value.
-
-    This class is used by both service template and service instance elements.
-
-    :ivar name: Name
-    :ivar type_name: Type name
-    :ivar value: Value
-    :ivar description: Description
-    """
-
-    __tablename__ = 'parameter'
-
-    name = Column(Text)
-    type_name = Column(Text)
-
-    # Check: value type
-    _value = Column(Binary, name='value')
-    description = Column(Text)
-
-    @property
-    def as_raw(self):
-        return collections.OrderedDict((
-            ('name', self.name),
-            ('type_name', self.type_name),
-            ('value', self.value),
-            ('description', self.description)))
-
-    @property
-    def value(self):
-        if self._value is None:
-            return None
-        try:
-            return pickle.loads(self._value)
-        except BaseException:
-            raise exceptions.StorageError('bad format for parameter of type "{0}": {1}'.format(
-                self.type_name, self._value))
-
-    @value.setter
-    def value(self, value):
-        if value is None:
-            self._value = None
-        else:
-            try:
-                self._value = pickle.dumps(value)
-            except (pickle.PicklingError, TypeError):
-                logging.getLogger('aria').warn('Could not pickle parameter of type "{0}": {1}'
-                                               .format(self.type_name, value))
-                self._value = pickle.dumps(str(value))
-
-    def instantiate(self, context, container):
-        from . import models
-        return models.Parameter(name=self.name,
-                                type_name=self.type_name,
-                                _value=self._value,
-                                description=self.description)
-
-    def coerce_values(self, context, container, report_issues):
-        if self.value is not None:
-            self.value = utils.coerce_value(context, container, self.value,
-                                            report_issues)
-
-    def dump(self, context):
-        if self.type_name is not None:
-            console.puts('{0}: {1} ({2})'.format(
-                context.style.property(self.name),
-                context.style.literal(self.value),
-                context.style.type(self.type_name)))
-        else:
-            console.puts('{0}: {1}'.format(
-                context.style.property(self.name),
-                context.style.literal(self.value)))
-        if self.description:
-            console.puts(context.style.meta(self.description))
-
-
-class TypeBase(InstanceModelMixin):
-    """
-    Represents a type and its children.
-    """
-
-    __tablename__ = 'type'
-
-    variant = Column(Text, nullable=False)
-    description = Column(Text)
-    _role = Column(Text, name='role')
-
-    @declared_attr
-    def parent(cls):
-        return cls.relationship_to_self('parent_type_fk')
-
-    @declared_attr
-    def children(cls):
-        return cls.one_to_many_relationship_to_self('parent_type_fk')
-
-    # region foreign keys
-
-    __private_fields__ = ['parent_type_fk']
-
-    # Type one-to-many to Type
-    @declared_attr
-    def parent_type_fk(cls):
-        return cls.foreign_key('type', nullable=True)
-
-    # endregion
-
-    @property
-    def role(self):
-        def get_role(the_type):
-            if the_type is None:
-                return None
-            elif the_type._role is None:
-                return get_role(the_type.parent)
-            return the_type._role
-
-        return get_role(self)
-
-    @role.setter
-    def role(self, value):
-        self._role = value
-
-    def is_descendant(self, base_name, name):
-        base = self.get_descendant(base_name)
-        if base is not None:
-            if base.get_descendant(name) is not None:
-                return True
-        return False
-
-    def get_descendant(self, name):
-        if self.name == name:
-            return self
-        for child in self.children:
-            found = child.get_descendant(name)
-            if found is not None:
-                return found
-        return None
-
-    def iter_descendants(self):
-        for child in self.children:
-            yield child
-            for descendant in child.iter_descendants():
-                yield descendant
-
-    @property
-    def as_raw(self):
-        return collections.OrderedDict((
-            ('name', self.name),
-            ('description', self.description),
-            ('role', self.role)))
-
-    @property
-    def as_raw_all(self):
-        types = []
-        self._append_raw_children(types)
-        return types
-
-    def dump(self, context):
-        if self.name:
-            console.puts(context.style.type(self.name))
-        with context.style.indent:
-            for child in self.children:
-                child.dump(context)
-
-    def _append_raw_children(self, types):
-        for child in self.children:
-            raw_child = formatting.as_raw(child)
-            raw_child['parent'] = self.name
-            types.append(raw_child)
-            child._append_raw_children(types)
-
-
-class MetadataBase(TemplateModelMixin):
-    """
-    Custom values associated with the service.
-
-    This class is used by both service template and service instance elements.
-
-    :ivar name: Name
-    :ivar value: Value
-    """
-
-    __tablename__ = 'metadata'
-
-    value = Column(Text)
-
-    @property
-    def as_raw(self):
-        return collections.OrderedDict((
-            ('name', self.name),
-            ('value', self.value)))
-
-    def instantiate(self, context, container):
-        from . import models
-        return models.Metadata(name=self.name,
-                               value=self.value)
-
-    def dump(self, context):
-        console.puts('{0}: {1}'.format(
-            context.style.property(self.name),
-            context.style.literal(self.value)))

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/dd5bfa93/aria/modeling/mixins.py
----------------------------------------------------------------------
diff --git a/aria/modeling/mixins.py b/aria/modeling/mixins.py
new file mode 100644
index 0000000..04497b5
--- /dev/null
+++ b/aria/modeling/mixins.py
@@ -0,0 +1,405 @@
+# 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.structures module
+Path: aria.storage.structures
+
+models module holds ARIA's models.
+
+classes:
+    * ModelMixin - abstract model implementation.
+    * ModelIDMixin - abstract model implementation with IDs.
+"""
+
+from sqlalchemy.orm import relationship, backref
+from sqlalchemy.orm.collections import attribute_mapped_collection
+from sqlalchemy.ext import associationproxy
+from sqlalchemy import (
+    Column,
+    ForeignKey,
+    Integer,
+    Text,
+    Table,
+)
+
+from . import utils
+from ..utils import formatting
+
+
+class ModelMixin(object):
+
+    @utils.classproperty
+    def __modelname__(cls):                                                                         # pylint: disable=no-self-argument
+        return getattr(cls, '__mapiname__', cls.__tablename__)
+
+    @classmethod
+    def id_column_name(cls):
+        raise NotImplementedError
+
+    @classmethod
+    def name_column_name(cls):
+        raise NotImplementedError
+
+    def to_dict(self, fields=None, suppress_error=False):
+        """
+        Return a dict representation of the model
+
+        :param suppress_error: If set to True, sets `None` to attributes that
+                               it's unable to retrieve (e.g., if a relationship wasn't established
+                               yet, and so it's impossible to access a property through it)
+        """
+
+        res = dict()
+        fields = fields or self.fields()
+        for field in fields:
+            try:
+                field_value = getattr(self, field)
+            except AttributeError:
+                if suppress_error:
+                    field_value = None
+                else:
+                    raise
+            if isinstance(field_value, list):
+                field_value = list(field_value)
+            elif isinstance(field_value, dict):
+                field_value = dict(field_value)
+            elif isinstance(field_value, ModelMixin):
+                field_value = field_value.to_dict()
+            res[field] = field_value
+
+        return res
+
+    @classmethod
+    def fields(cls):
+        """
+        Return the list of field names for this table
+
+        Mostly for backwards compatibility in the code (that uses `fields`)
+        """
+
+        fields = set(cls._iter_association_proxies())
+        fields.update(cls.__table__.columns.keys())
+        return fields - set(getattr(cls, '__private_fields__', []))
+
+    @classmethod
+    def _create_foreign_key(cls, parent_table, nullable=False):
+        """
+        Return a ForeignKey object.
+
+        :param parent_table: Parent table name
+        :param nullable: Should the column be allowed to remain empty
+        """
+        return Column(Integer,
+                      ForeignKey('{table}.id'.format(table=parent_table),
+                                 ondelete='CASCADE'),
+                      nullable=nullable)
+
+    @classmethod
+    def _create_relationship_to_self(cls,
+                                     column_name,
+                                     relationship_kwargs=None):
+        relationship_kwargs = relationship_kwargs or {}
+
+        remote_side = '{cls}.{remote_column}'.format(
+            cls=cls.__name__,
+            remote_column=cls.id_column_name()
+        )
+
+        primaryjoin = '{remote_side} == {cls}.{column}'.format(
+            remote_side=remote_side,
+            cls=cls.__name__,
+            column=column_name
+        )
+
+        return relationship(
+            cls._get_cls_by_tablename(cls.__tablename__).__name__,
+            primaryjoin=primaryjoin,
+            remote_side=remote_side,
+            post_update=True,
+            **relationship_kwargs
+        )
+
+    @classmethod
+    def _create_one_to_many_relationship_to_self(cls,
+                                                 key,
+                                                 dict_key=None,
+                                                 relationship_kwargs=None):
+        relationship_kwargs = relationship_kwargs or {}
+
+        relationship_kwargs.setdefault('remote_side', '{cls}.{remote_column}'.format(
+            cls=cls.__name__,
+            remote_column=key
+        ))
+
+        return cls._create_relationship(cls.__tablename__, None, relationship_kwargs,
+                                        backreference='', dict_key=dict_key)
+
+    @classmethod
+    def _create_one_to_one_relationship(cls,
+                                        other_table,
+                                        key=None,
+                                        foreign_key=None,
+                                        backreference=None,
+                                        backref_kwargs=None,
+                                        relationship_kwargs=None):
+        backref_kwargs = backref_kwargs or {}
+        backref_kwargs.setdefault('uselist', False)
+
+        return cls._create_relationship(other_table, backref_kwargs, relationship_kwargs,
+                                        backreference, key=key, foreign_key=foreign_key)
+
+    @classmethod
+    def _create_one_to_many_relationship(cls,
+                                         child_table,
+                                         key=None,
+                                         foreign_key=None,
+                                         dict_key=None,
+                                         backreference=None,
+                                         backref_kwargs=None,
+                                         relationship_kwargs=None):
+        backref_kwargs = backref_kwargs or {}
+        backref_kwargs.setdefault('uselist', False)
+
+        return cls._create_relationship(child_table, backref_kwargs, relationship_kwargs,
+                                        backreference, key=key, foreign_key=foreign_key,
+                                        dict_key=dict_key)
+
+    @classmethod
+    def _create_many_to_one_relationship(cls,
+                                         parent_table,
+                                         key=None,
+                                         foreign_key=None,
+                                         backreference=None,
+                                         backref_kwargs=None,
+                                         relationship_kwargs=None):
+        """
+        Return a one-to-many SQL relationship object
+        Meant to be used from inside the *child* object
+
+        :param parent_table: Name of the parent table
+        :param foreign_key: The column of the foreign key (from the child table)
+        :param backreference: The name to give to the reference to the child (on the parent table)
+        """
+
+        if backreference is None:
+            backreference = formatting.pluralize(cls.__tablename__)
+
+        backref_kwargs = backref_kwargs or {}
+        backref_kwargs.setdefault('uselist', True)
+        backref_kwargs.setdefault('lazy', 'dynamic')
+        # The following line make sure that when the *parent* is deleted, all its connected children
+        # are deleted as well
+        backref_kwargs.setdefault('cascade', 'all')
+
+        return cls._create_relationship(parent_table, backref_kwargs, relationship_kwargs,
+                                        backreference, key=key, foreign_key=foreign_key)
+
+    @classmethod
+    def _create_many_to_many_relationship(cls,
+                                          other_table,
+                                          table_prefix=None,
+                                          key=None,
+                                          dict_key=None,
+                                          backreference=None,
+                                          backref_kwargs=None,
+                                          relationship_kwargs=None):
+        """
+        Return a many-to-many SQL relationship object
+
+        Notes:
+
+        1. The backreference name is the current table's table name
+        2. This method creates a new helper table in the DB
+
+        :param cls: The class of the table we're connecting from
+        :param other_table: The class of the table we're connecting to
+        :param table_prefix: Custom prefix for the helper table name and the backreference name
+        :param dict_key: If provided, will use a dict class with this column as the key
+        """
+
+        this_table = cls.__tablename__
+        this_column_name = '{0}_id'.format(this_table)
+        this_foreign_key = '{0}.id'.format(this_table)
+
+        other_column_name = '{0}_id'.format(other_table)
+        other_foreign_key = '{0}.id'.format(other_table)
+
+        helper_table = '{0}_{1}'.format(this_table, other_table)
+
+        if backreference is None:
+            backreference = formatting.pluralize(this_table)
+            if table_prefix:
+                helper_table = '{0}_{1}'.format(table_prefix, helper_table)
+                backreference = '{0}_{1}'.format(table_prefix, backreference)
+
+        backref_kwargs = backref_kwargs or {}
+        backref_kwargs.setdefault('uselist', True)
+
+        relationship_kwargs = relationship_kwargs or {}
+        relationship_kwargs.setdefault('secondary', cls._get_secondary_table(
+            cls.metadata,
+            helper_table,
+            this_column_name,
+            other_column_name,
+            this_foreign_key,
+            other_foreign_key
+        ))
+
+        return cls._create_relationship(other_table, backref_kwargs, relationship_kwargs,
+                                        backreference, key=key, dict_key=dict_key)
+
+    @classmethod
+    def _create_relationship(cls, table, backref_kwargs, relationship_kwargs, backreference,
+                             key=None, foreign_key=None, dict_key=None):
+        relationship_kwargs = relationship_kwargs or {}
+
+        if key:
+            relationship_kwargs.setdefault('foreign_keys',
+                                           lambda: getattr(
+                                               cls._get_cls_by_tablename(cls.__tablename__),
+                                               key))
+
+        elif foreign_key:
+            relationship_kwargs.setdefault('foreign_keys',
+                                           lambda: getattr(
+                                               cls._get_cls_by_tablename(table),
+                                               foreign_key))
+
+        if dict_key:
+            relationship_kwargs.setdefault('collection_class',
+                                           attribute_mapped_collection(dict_key))
+
+        if backreference == '':
+            return relationship(
+                lambda: cls._get_cls_by_tablename(table),
+                **relationship_kwargs
+            )
+        else:
+            if backreference is None:
+                backreference = cls.__tablename__
+            backref_kwargs = backref_kwargs or {}
+            return relationship(
+                lambda: cls._get_cls_by_tablename(table),
+                backref=backref(backreference, **backref_kwargs),
+                **relationship_kwargs
+            )
+
+    @staticmethod
+    def _get_secondary_table(metadata,
+                             helper_table,
+                             first_column,
+                             second_column,
+                             first_foreign_key,
+                             second_foreign_key):
+        """
+        Create a helper table for a many-to-many relationship
+
+        :param helper_table: The name of the table
+        :param first_column_name: The name of the first column in the table
+        :param second_column_name: The name of the second column in the table
+        :param first_foreign_key: The string representing the first foreign key,
+               for example `blueprint.storage_id`, or `tenants.id`
+        :param second_foreign_key: The string representing the second foreign key
+        :return: A Table object
+        """
+
+        return Table(
+            helper_table,
+            metadata,
+            Column(
+                first_column,
+                Integer,
+                ForeignKey(first_foreign_key)
+            ),
+            Column(
+                second_column,
+                Integer,
+                ForeignKey(second_foreign_key)
+            )
+        )
+
+    @classmethod
+    def _iter_association_proxies(cls):
+        for col, value in vars(cls).items():
+            if isinstance(value, associationproxy.AssociationProxy):
+                yield col
+
+    @classmethod
+    def _get_cls_by_tablename(cls, tablename):
+        """
+        Return class reference mapped to table.
+
+        :param tablename: String with name of table.
+        :return: Class reference or None.
+        """
+
+        if tablename in (cls.__name__, cls.__tablename__):
+            return cls
+
+        for table_cls in cls._decl_class_registry.values():
+            if tablename == getattr(table_cls, '__tablename__', None):
+                return table_cls
+
+    def __repr__(self):
+        return '<{cls} id=`{id}`>'.format(
+            cls=self.__class__.__name__,
+            id=getattr(self, self.name_column_name()))
+
+
+class ModelIDMixin(object):
+    id = Column(Integer, primary_key=True, autoincrement=True)
+    name = Column(Text, index=True)
+
+    @classmethod
+    def id_column_name(cls):
+        return 'id'
+
+    @classmethod
+    def name_column_name(cls):
+        return 'name'
+
+
+class InstanceModelMixin(ModelMixin):
+    """
+    Mixin for :class:`ServiceInstance` models.
+
+    All models support validation, diagnostic dumping, and representation as
+    raw data (which can be translated into JSON or YAML) via :code:`as_raw`.
+    """
+
+    @property
+    def as_raw(self):
+        raise NotImplementedError
+
+    def validate(self, context):
+        pass
+
+    def coerce_values(self, context, container, report_issues):
+        pass
+
+    def dump(self, context):
+        pass
+
+
+class TemplateModelMixin(InstanceModelMixin):
+    """
+    Mixin for :class:`ServiceTemplate` models.
+
+    All model models can be instantiated into :class:`ServiceInstance` models.
+    """
+
+    def instantiate(self, context, container):
+        raise NotImplementedError

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/dd5bfa93/aria/modeling/models.py
----------------------------------------------------------------------
diff --git a/aria/modeling/models.py b/aria/modeling/models.py
index fc2c669..90238e6 100644
--- a/aria/modeling/models.py
+++ b/aria/modeling/models.py
@@ -19,14 +19,15 @@ from sqlalchemy.ext.declarative import declarative_base
 
 from . import (
     service_template,
-    service,
+    service_instance,
+    service_changes,
+    service_common,
     orchestration,
-    misc,
-    bases,
+    mixins,
 )
 
 
-aria_declarative_base = declarative_base(cls=bases.ModelIDMixin)
+aria_declarative_base = declarative_base(cls=mixins.ModelIDMixin)
 
 
 # region service template models
@@ -84,91 +85,99 @@ class ArtifactTemplate(aria_declarative_base, service_template.ArtifactTemplateB
 
 # region service instance models
 
-class Service(aria_declarative_base, service.ServiceBase):
+class Service(aria_declarative_base, service_instance.ServiceBase):
     pass
 
 
-class Node(aria_declarative_base, service.NodeBase):
+class Node(aria_declarative_base, service_instance.NodeBase):
     pass
 
 
-class Group(aria_declarative_base, service.GroupBase):
+class Group(aria_declarative_base, service_instance.GroupBase):
     pass
 
 
-class Policy(aria_declarative_base, service.PolicyBase):
+class Policy(aria_declarative_base, service_instance.PolicyBase):
     pass
 
 
-class Substitution(aria_declarative_base, service.SubstitutionBase):
+class Substitution(aria_declarative_base, service_instance.SubstitutionBase):
     pass
 
 
-class SubstitutionMapping(aria_declarative_base, service.SubstitutionMappingBase):
+class SubstitutionMapping(aria_declarative_base, service_instance.SubstitutionMappingBase):
     pass
 
 
-class Relationship(aria_declarative_base, service.RelationshipBase):
+class Relationship(aria_declarative_base, service_instance.RelationshipBase):
     pass
 
 
-class Capability(aria_declarative_base, service.CapabilityBase):
+class Capability(aria_declarative_base, service_instance.CapabilityBase):
     pass
 
 
-class Interface(aria_declarative_base, service.InterfaceBase):
+class Interface(aria_declarative_base, service_instance.InterfaceBase):
     pass
 
 
-class Operation(aria_declarative_base, service.OperationBase):
+class Operation(aria_declarative_base, service_instance.OperationBase):
     pass
 
 
-class Artifact(aria_declarative_base, service.ArtifactBase):
+class Artifact(aria_declarative_base, service_instance.ArtifactBase):
     pass
 
 # endregion
 
 
-# region orchestration models
+# region service changes models
 
-class Execution(aria_declarative_base, orchestration.Execution):
+class ServiceUpdate(aria_declarative_base, service_changes.ServiceUpdateBase):
     pass
 
 
-class ServiceUpdate(aria_declarative_base, orchestration.ServiceUpdateBase):
+class ServiceUpdateStep(aria_declarative_base, service_changes.ServiceUpdateStepBase):
     pass
 
 
-class ServiceUpdateStep(aria_declarative_base, orchestration.ServiceUpdateStepBase):
+class ServiceModification(aria_declarative_base, service_changes.ServiceModificationBase):
     pass
 
+# endregion
+
+
+# region common service models
 
-class ServiceModification(aria_declarative_base, orchestration.ServiceModificationBase):
+class Parameter(aria_declarative_base, service_common.ParameterBase):
     pass
 
 
-class Plugin(aria_declarative_base, orchestration.PluginBase):
+class Type(aria_declarative_base, service_common.TypeBase):
     pass
 
 
-class Task(aria_declarative_base, orchestration.TaskBase):
+class Metadata(aria_declarative_base, service_common.MetadataBase):
+    pass
+
+
+class PluginSpecification(aria_declarative_base, service_common.PluginSpecificationBase):
     pass
 
 # endregion
 
 
-# region misc models
+# region orchestration models
 
-class Parameter(aria_declarative_base, misc.ParameterBase):
+class Execution(aria_declarative_base, orchestration.ExecutionBase):
     pass
 
 
-class Type(aria_declarative_base, misc.TypeBase):
+class Plugin(aria_declarative_base, orchestration.PluginBase):
     pass
 
 
-class Metadata(aria_declarative_base, misc.MetadataBase):
+class Task(aria_declarative_base, orchestration.TaskBase):
     pass
 
 # endregion
@@ -202,18 +211,21 @@ models_to_register = [
     Operation,
     Artifact,
 
-    # Orchestration models
-    Execution,
+    # Service changes models
     ServiceUpdate,
     ServiceUpdateStep,
     ServiceModification,
-    Plugin,
-    Task,
 
-    # Misc models
+    # Common service models
     Parameter,
     Type,
-    Metadata
+    Metadata,
+    PluginSpecification,
+
+    # Orchestration models
+    Execution,
+    Plugin,
+    Task
 ]
 
 __all__ = (
@@ -247,16 +259,19 @@ __all__ = (
     'Operation',
     'Artifact',
 
-    # Orchestration models
-    'Execution',
+    # Service changes models
     'ServiceUpdate',
     'ServiceUpdateStep',
     'ServiceModification',
-    'Plugin',
-    'Task',
 
-    # Misc models
+    # Common service models
     'Parameter',
     'Type',
-    'Metadata'
+    'Metadata',
+    'PluginSpecification',
+
+    # Orchestration models
+    'Execution',
+    'Plugin',
+    'Task'
 )

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/dd5bfa93/aria/modeling/orchestration.py
----------------------------------------------------------------------
diff --git a/aria/modeling/orchestration.py b/aria/modeling/orchestration.py
index c842c07..d8bdd3c 100644
--- a/aria/modeling/orchestration.py
+++ b/aria/modeling/orchestration.py
@@ -14,23 +14,14 @@
 # limitations under the License.
 
 """
-ARIA's storage.models module
-Path: aria.storage.models
-
-models module holds ARIA's models.
-
 classes:
     * Execution - execution implementation model.
-    * ServiceUpdate - service update implementation model.
-    * ServiceUpdateStep - service update step implementation model.
-    * ServiceModification - service modification implementation model.
     * Plugin - plugin implementation model.
     * Task - a task
 """
 
 # pylint: disable=no-self-argument, no-member, abstract-method
 
-from collections import namedtuple
 from datetime import datetime
 
 from sqlalchemy import (
@@ -49,19 +40,16 @@ from sqlalchemy.ext.declarative import declared_attr
 
 from ..orchestrator.exceptions import (TaskAbortException, TaskRetryException)
 from .types import (List, Dict)
-from .bases import ModelMixin
+from .mixins import ModelMixin
 
 __all__ = (
-    'Execution',
-    'ServiceUpdateBase',
-    'ServiceUpdateStepBase',
-    'ServiceModificationBase',
+    'ExecutionBase',
     'PluginBase',
     'TaskBase'
 )
 
 
-class Execution(ModelMixin):
+class ExecutionBase(ModelMixin):
     """
     Execution model representation.
     """
@@ -117,7 +105,7 @@ class Execution(ModelMixin):
 
     @declared_attr
     def service(cls):
-        return cls.many_to_one_relationship('service')
+        return cls._create_many_to_one_relationship('service')
 
     @declared_attr
     def service_name(cls):
@@ -133,7 +121,7 @@ class Execution(ModelMixin):
 
     @declared_attr
     def service_fk(cls):
-        return cls.foreign_key('service')
+        return cls._create_foreign_key('service')
 
     # endregion
 
@@ -145,182 +133,6 @@ class Execution(ModelMixin):
         )
 
 
-class ServiceUpdateBase(ModelMixin):
-    """
-    Deployment update model representation.
-    """
-
-    steps = None
-
-    __tablename__ = 'service_update'
-
-    _private_fields = ['execution_fk',
-                       'service_fk']
-
-    created_at = Column(DateTime, nullable=False, index=True)
-    service_plan = Column(Dict, nullable=False)
-    service_update_nodes = Column(Dict)
-    service_update_service = Column(Dict)
-    service_update_node_templates = Column(List)
-    modified_entity_ids = Column(Dict)
-    state = Column(Text)
-
-    @declared_attr
-    def execution(cls):
-        return cls.many_to_one_relationship('execution')
-
-    @declared_attr
-    def execution_name(cls):
-        return association_proxy('execution', cls.name_column_name())
-
-    @declared_attr
-    def service(cls):
-        return cls.many_to_one_relationship('service',
-                                            backreference='updates')
-
-    @declared_attr
-    def service_name(cls):
-        return association_proxy('service', cls.name_column_name())
-
-    # region foreign keys
-
-    __private_fields__ = ['service_fk',
-                          'execution_fk']
-
-    @declared_attr
-    def execution_fk(cls):
-        return cls.foreign_key('execution', nullable=True)
-
-    @declared_attr
-    def service_fk(cls):
-        return cls.foreign_key('service')
-
-    # endregion
-
-    def to_dict(self, suppress_error=False, **kwargs):
-        dep_update_dict = super(ServiceUpdateBase, 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 ServiceUpdateStepBase(ModelMixin):
-    """
-    Deployment update step model representation.
-    """
-
-    __tablename__ = 'service_update_step'
-
-    _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, PLUGIN')
-    ENTITY_TYPES = _entity_types(
-        NODE='node',
-        RELATIONSHIP='relationship',
-        PROPERTY='property',
-        OPERATION='operation',
-        WORKFLOW='workflow',
-        OUTPUT='output',
-        DESCRIPTION='description',
-        GROUP='group',
-        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 service_update(cls):
-        return cls.many_to_one_relationship('service_update',
-                                            backreference='steps')
-
-    @declared_attr
-    def service_update_name(cls):
-        return association_proxy('service_update', cls.name_column_name())
-
-    # region foreign keys
-
-    __private_fields__ = ['service_update_fk']
-
-    @declared_attr
-    def service_update_fk(cls):
-        return cls.foreign_key('service_update')
-
-    # endregion
-
-    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 ServiceModificationBase(ModelMixin):
-    """
-    Deployment modification model representation.
-    """
-
-    __tablename__ = 'service_modification'
-
-    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_node_templates = Column(Dict)
-    nodes = Column(Dict)
-    status = Column(Enum(*STATES, name='service_modification_status'))
-
-    @declared_attr
-    def service(cls):
-        return cls.many_to_one_relationship('service',
-                                            backreference='modifications')
-
-    @declared_attr
-    def service_name(cls):
-        return association_proxy('service', cls.name_column_name())
-
-    # region foreign keys
-
-    __private_fields__ = ['service_fk']
-
-    @declared_attr
-    def service_fk(cls):
-        return cls.foreign_key('service')
-
-    # endregion
-
-
 class PluginBase(ModelMixin):
     """
     Plugin model representation.
@@ -340,16 +152,6 @@ class PluginBase(ModelMixin):
     uploaded_at = Column(DateTime, nullable=False, index=True)
     wheels = Column(List, nullable=False)
 
-    # region foreign keys
-
-    __private_fields__ = ['service_template_fk']
-
-    @declared_attr
-    def service_template_fk(cls):
-        return cls.foreign_key('service_template', nullable=True)
-
-    # endregion
-
 
 class TaskBase(ModelMixin):
     """
@@ -389,7 +191,7 @@ class TaskBase(ModelMixin):
 
     @declared_attr
     def node(cls):
-        return cls.many_to_one_relationship('node')
+        return cls._create_many_to_one_relationship('node')
 
     @declared_attr
     def relationship_name(cls):
@@ -397,15 +199,15 @@ class TaskBase(ModelMixin):
 
     @declared_attr
     def relationship(cls):
-        return cls.many_to_one_relationship('relationship')
+        return cls._create_many_to_one_relationship('relationship')
 
     @declared_attr
     def plugin(cls):
-        return cls.many_to_one_relationship('plugin')
+        return cls._create_many_to_one_relationship('plugin')
 
     @declared_attr
     def execution(cls):
-        return cls.many_to_one_relationship('execution')
+        return cls._create_many_to_one_relationship('execution')
 
     @declared_attr
     def execution_name(cls):
@@ -413,8 +215,8 @@ class TaskBase(ModelMixin):
 
     @declared_attr
     def inputs(cls):
-        return cls.many_to_many_relationship('parameter', table_prefix='inputs',
-                                             dict_key='name')
+        return cls._create_many_to_many_relationship('parameter', table_prefix='inputs',
+                                                     dict_key='name')
 
     status = Column(Enum(*STATES, name='status'), default=PENDING)
 
@@ -428,9 +230,6 @@ class TaskBase(ModelMixin):
 
     # Operation specific fields
     implementation = Column(String)
-    # This is unrelated to the plugin of the task. This field is related to the plugin name
-    # received from the blueprint.
-    plugin_name = Column(String)
     _runs_on = Column(Enum(*RUNS_ON, name='runs_on'), name='runs_on')
 
     @property
@@ -468,28 +267,28 @@ class TaskBase(ModelMixin):
 
     @declared_attr
     def node_fk(cls):
-        return cls.foreign_key('node', nullable=True)
+        return cls._create_foreign_key('node', nullable=True)
 
     @declared_attr
     def relationship_fk(cls):
-        return cls.foreign_key('relationship', nullable=True)
+        return cls._create_foreign_key('relationship', nullable=True)
 
     @declared_attr
     def plugin_fk(cls):
-        return cls.foreign_key('plugin', nullable=True)
+        return cls._create_foreign_key('plugin', nullable=True)
 
     @declared_attr
     def execution_fk(cls):
-        return cls.foreign_key('execution', nullable=True)
+        return cls._create_foreign_key('execution', nullable=True)
 
     # endregion
 
     @classmethod
-    def as_node_task(cls, instance, runs_on, **kwargs):
+    def for_node(cls, instance, runs_on, **kwargs):
         return cls(node=instance, _runs_on=runs_on, **kwargs)
 
     @classmethod
-    def as_relationship_task(cls, instance, runs_on, **kwargs):
+    def for_relationship(cls, instance, runs_on, **kwargs):
         return cls(relationship=instance, _runs_on=runs_on, **kwargs)
 
     @staticmethod


[5/8] incubator-ariatosca git commit: Separate plugin specification form plugin; move dry support to CLI; various renames and refactorings

Posted by mx...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/dd5bfa93/aria/modeling/service_instance.py
----------------------------------------------------------------------
diff --git a/aria/modeling/service_instance.py b/aria/modeling/service_instance.py
new file mode 100644
index 0000000..ba18f73
--- /dev/null
+++ b/aria/modeling/service_instance.py
@@ -0,0 +1,1529 @@
+# 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.
+
+# pylint: disable=too-many-lines, no-self-argument, no-member, abstract-method
+
+from sqlalchemy import (
+    Column,
+    Text,
+    Integer
+)
+from sqlalchemy import DateTime
+from sqlalchemy.ext.associationproxy import association_proxy
+from sqlalchemy.ext.declarative import declared_attr
+
+from .mixins import InstanceModelMixin
+from ..parser import validation
+from ..utils import collections, formatting, console
+
+from . import (
+    utils,
+    types as modeling_types
+)
+
+
+class ServiceBase(InstanceModelMixin): # pylint: disable=too-many-public-methods
+    """
+    A service is usually an instance of a :class:`ServiceTemplate`.
+
+    You will usually not create it programmatically, but instead instantiate it from a service
+    template.
+
+    :ivar name: Name (unique for this ARIA installation)
+    :vartype name: basestring
+    :ivar service_template: Template from which this service was instantiated (optional)
+    :vartype service_template: :class:`ServiceTemplate`
+    :ivar description: Human-readable description
+    :vartype description: string
+    :ivar meta_data: Custom annotations
+    :vartype meta_data: {basestring: :class:`Metadata`}
+    :ivar node: Nodes
+    :vartype node: [:class:`Node`]
+    :ivar groups: Groups of nodes
+    :vartype groups: [:class:`Group`]
+    :ivar policies: Policies
+    :vartype policies: [:class:`Policy`]
+    :ivar substitution: The entire service can appear as a node
+    :vartype substitution: :class:`Substitution`
+    :ivar inputs: Externally provided parameters
+    :vartype inputs: {basestring: :class:`Parameter`}
+    :ivar outputs: These parameters are filled in after service installation
+    :vartype outputs: {basestring: :class:`Parameter`}
+    :ivar workflows: Custom workflows that can be performed on the service
+    :vartype workflows: {basestring: :class:`Operation`}
+    :ivar plugin_specifications: Plugins required to be installed
+    :vartype plugin_specifications: {basestring: :class:`PluginSpecification`}
+    :ivar created_at: Creation timestamp
+    :vartype created_at: :class:`datetime.datetime`
+    :ivar updated_at: Update timestamp
+    :vartype updated_at: :class:`datetime.datetime`
+
+    :ivar permalink: ??
+    :vartype permalink: basestring
+    :ivar scaling_groups: ??
+    :vartype scaling_groups: {}
+
+    :ivar modifications: Modifications of this service
+    :vartype modifications: [:class:`ServiceModification`]
+    :ivar updates: Updates of this service
+    :vartype updates: [:class:`ServiceUpdate`]
+    :ivar executions: Executions on this service
+    :vartype executions: [:class:`Execution`]
+    """
+
+    __tablename__ = 'service'
+
+    @declared_attr
+    def service_template(cls):
+        return cls._create_many_to_one_relationship('service_template')
+
+    description = Column(Text)
+
+    @declared_attr
+    def meta_data(cls):
+        # Warning! We cannot use the attr name "metadata" because it's used by SqlAlchemy!
+        return cls._create_many_to_many_relationship('metadata', dict_key='name')
+
+    @declared_attr
+    def nodes(cls):
+        return cls._create_one_to_many_relationship('node')
+
+    @declared_attr
+    def groups(cls):
+        return cls._create_one_to_many_relationship('group')
+
+    @declared_attr
+    def policies(cls):
+        return cls._create_one_to_many_relationship('policy')
+
+    @declared_attr
+    def substitution(cls):
+        return cls._create_one_to_one_relationship('substitution')
+
+    @declared_attr
+    def inputs(cls):
+        return cls._create_many_to_many_relationship('parameter', table_prefix='inputs',
+                                                     dict_key='name')
+
+    @declared_attr
+    def outputs(cls):
+        return cls._create_many_to_many_relationship('parameter', table_prefix='outputs',
+                                                     dict_key='name')
+
+    @declared_attr
+    def workflows(cls):
+        return cls._create_one_to_many_relationship('operation', dict_key='name')
+
+    @declared_attr
+    def plugin_specifications(cls):
+        return cls._create_many_to_many_relationship('plugin_specification')
+
+    created_at = Column(DateTime, nullable=False, index=True)
+    updated_at = Column(DateTime)
+
+    # region orchestration
+
+    permalink = Column(Text)
+    scaling_groups = Column(modeling_types.Dict)
+
+    # endregion
+
+    # region foreign keys
+
+    __private_fields__ = ['substituion_fk',
+                          'service_template_fk']
+
+    # Service one-to-one to Substitution
+    @declared_attr
+    def substitution_fk(cls):
+        return cls._create_foreign_key('substitution', nullable=True)
+
+    # Service many-to-one to ServiceTemplate
+    @declared_attr
+    def service_template_fk(cls):
+        return cls._create_foreign_key('service_template', nullable=True)
+
+    # endregion
+
+    def satisfy_requirements(self, context):
+        satisfied = True
+        for node in self.nodes:
+            if not node.satisfy_requirements(context):
+                satisfied = False
+        return satisfied
+
+    def validate_capabilities(self, context):
+        satisfied = True
+        for node in self.nodes:
+            if not node.validate_capabilities(context):
+                satisfied = False
+        return satisfied
+
+    def find_nodes(self, node_template_name):
+        nodes = []
+        for node in self.nodes:
+            if node.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.name for node in self.find_nodes(node_template_name)))
+
+    def find_groups(self, group_template_name):
+        groups = []
+        for group in self.groups:
+            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.name
+                                       for group in self.find_groups(group_template_name)))
+
+    def is_node_a_target(self, context, target_node):
+        for node in self.nodes:
+            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.name:
+                    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
+
+    @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))))
+
+    def validate(self, context):
+        utils.validate_dict_values(context, self.meta_data)
+        utils.validate_list_values(context, self.nodes)
+        utils.validate_list_values(context, self.groups)
+        utils.validate_list_values(context, self.policies)
+        if self.substitution is not None:
+            self.substitution.validate(context)
+        utils.validate_dict_values(context, self.inputs)
+        utils.validate_dict_values(context, self.outputs)
+        utils.validate_dict_values(context, self.workflows)
+
+    def coerce_values(self, context, container, report_issues):
+        utils.coerce_dict_values(context, container, self.meta_data, report_issues)
+        utils.coerce_list_values(context, container, self.nodes, report_issues)
+        utils.coerce_list_values(context, container, self.groups, report_issues)
+        utils.coerce_list_values(context, container, self.policies, report_issues)
+        if self.substitution is not None:
+            self.substitution.coerce_values(context, container, report_issues)
+        utils.coerce_dict_values(context, container, self.inputs, report_issues)
+        utils.coerce_dict_values(context, container, self.outputs, report_issues)
+        utils.coerce_dict_values(context, container, self.workflows, report_issues)
+
+    def dump(self, context):
+        if self.description is not None:
+            console.puts(context.style.meta(self.description))
+        utils.dump_dict_values(context, self.meta_data, 'Metadata')
+        for node in self.nodes:
+            node.dump(context)
+        for group in self.groups:
+            group.dump(context)
+        for policy in self.policies:
+            policy.dump(context)
+        if self.substitution is not None:
+            self.substitution.dump(context)
+        utils.dump_dict_values(context, self.inputs, 'Inputs')
+        utils.dump_dict_values(context, self.outputs, 'Outputs')
+        utils.dump_dict_values(context, self.workflows, 'Workflows')
+
+    def dump_graph(self, context):
+        for node in self.nodes.itervalues():
+            if not self.is_node_a_target(context, node):
+                self._dump_graph_node(context, node)
+
+    def _dump_graph_node(self, context, node):
+        console.puts(context.style.node(node.name))
+        if node.relationships:
+            with context.style.indent:
+                for relationship in node.relationships:
+                    relationship_name = (context.style.node(relationship.template_name)
+                                         if relationship.template_name is not None
+                                         else context.style.type(relationship.type_name))
+                    capability_name = (context.style.node(relationship.target_capability_name)
+                                       if relationship.target_capability_name is not None
+                                       else None)
+                    if capability_name is not None:
+                        console.puts('-> {0} {1}'.format(relationship_name, capability_name))
+                    else:
+                        console.puts('-> {0}'.format(relationship_name))
+                    target_node = self.nodes.get(relationship.target_node_id)
+                    with console.indent(3):
+                        self._dump_graph_node(context, target_node)
+
+
+class NodeBase(InstanceModelMixin): # pylint: disable=too-many-public-methods
+    """
+    Usually an instance of a :class:`NodeTemplate`.
+
+    Nodes may have zero or more :class:`Relationship` instances to other nodes.
+
+    :ivar name: Name (unique for this service)
+    :vartype name: basestring
+    :ivar node_template: Template from which this node was instantiated (optional)
+    :vartype node_template: :class:`NodeTemplate`
+    :ivar type: Node type
+    :vartype type: :class:`Type`
+    :ivar description: Human-readable description
+    :vartype description: string
+    :ivar properties: Associated parameters
+    :vartype properties: {basestring: :class:`Parameter`}
+    :ivar interfaces: Bundles of operations
+    :vartype interfaces: {basestring: :class:`Interface`}
+    :ivar artifacts: Associated files
+    :vartype artifacts: {basestring: :class:`Artifact`}
+    :ivar capabilities: Exposed capabilities
+    :vartype capabilities: {basestring: :class:`Capability`}
+    :ivar outbound_relationships: Relationships to other nodes
+    :vartype outbound_relationships: [:class:`Relationship`]
+    :ivar inbound_relationships: Relationships from other nodes
+    :vartype inbound_relationships: [:class:`Relationship`]
+    :ivar plugin_specifications: Plugins required to be installed on the node's host
+    :vartype plugin_specifications: {basestring: :class:`PluginSpecification`}
+    :ivar host: Host node (can be self)
+    :vartype host: :class:`Node`
+
+    :ivar runtime_properties: TODO: should be replaced with attributes
+    :vartype runtime_properties: {}
+    :ivar scaling_groups: ??
+    :vartype scaling_groups: []
+    :ivar state: ??
+    :vartype state: basestring
+    :ivar version: ??
+    :vartype version: int
+
+    :ivar service: Containing service
+    :vartype service: :class:`Service`
+    :ivar groups: We are a member of these groups
+    :vartype groups: [:class:`Group`]
+    :ivar policies: Policies enacted on this node
+    :vartype policies: [:class:`Policy`]
+    :ivar substitution_mapping: Our contribution to service substitution
+    :vartype substitution_mapping: :class:`SubstitutionMapping`
+    :ivar tasks: Tasks on this node
+    :vartype tasks: [:class:`Task`]
+    """
+
+    __tablename__ = 'node'
+
+    @declared_attr
+    def node_template(cls):
+        return cls._create_many_to_one_relationship('node_template')
+
+    @declared_attr
+    def type(cls):
+        return cls._create_many_to_one_relationship('type')
+
+    description = Column(Text)
+
+    @declared_attr
+    def properties(cls):
+        return cls._create_many_to_many_relationship('parameter', table_prefix='properties',
+                                                     dict_key='name')
+
+    @declared_attr
+    def interfaces(cls):
+        return cls._create_one_to_many_relationship('interface', dict_key='name')
+
+    @declared_attr
+    def artifacts(cls):
+        return cls._create_one_to_many_relationship('artifact', dict_key='name')
+
+    @declared_attr
+    def capabilities(cls):
+        return cls._create_one_to_many_relationship('capability', dict_key='name')
+
+    @declared_attr
+    def outbound_relationships(cls):
+        return cls._create_one_to_many_relationship('relationship',
+                                                    foreign_key='source_node_fk',
+                                                    backreference='source_node')
+
+    @declared_attr
+    def inbound_relationships(cls):
+        return cls._create_one_to_many_relationship('relationship',
+                                                    foreign_key='target_node_fk',
+                                                    backreference='target_node')
+
+    @declared_attr
+    def plugin_specifications(cls):
+        return cls._create_many_to_many_relationship('plugin_specification')
+
+    @declared_attr
+    def host(cls):
+        return cls._create_relationship_to_self('host_fk')
+
+    # region orchestration
+
+    runtime_properties = Column(modeling_types.Dict)
+    scaling_groups = Column(modeling_types.List)
+    state = Column(Text, nullable=False)
+    version = Column(Integer, default=1)
+
+    @declared_attr
+    def service_name(cls):
+        return association_proxy('service', 'name')
+
+    @property
+    def ip(self):
+        # TODO: totally broken
+        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 = host_node.properties.get('ip')
+        if host_ip_property:
+            return host_ip_property.value
+        return None
+
+    # endregion
+
+    # region foreign_keys
+
+    __private_fields__ = ['type_fk',
+                          'host_fk',
+                          'service_fk',
+                          'node_template_fk']
+
+    # Node many-to-one to Type
+    @declared_attr
+    def type_fk(cls):
+        return cls._create_foreign_key('type')
+
+    # Node one-to-one to Node
+    @declared_attr
+    def host_fk(cls):
+        return cls._create_foreign_key('node', nullable=True)
+
+    # Service one-to-many to Node
+    @declared_attr
+    def service_fk(cls):
+        return cls._create_foreign_key('service')
+
+    # Node many-to-one to NodeTemplate
+    @declared_attr
+    def node_template_fk(cls):
+        return cls._create_foreign_key('node_template', nullable=True)
+
+    # endregion
+
+    def satisfy_requirements(self, context):
+        node_template = self.node_template
+        satisfied = True
+        for requirement_template in node_template.requirement_templates:
+            # 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)
+            else:
+                context.validation.report('requirement "{0}" of node "{1}" has no target node '
+                                          'template'.format(requirement_template.name, self.name),
+                                          level=validation.Issue.BETWEEN_INSTANCES)
+                satisfied = False
+        return satisfied
+
+    def _satisfy_capability(self, context, target_node_capability, target_node_template,
+                            requirement_template):
+        from . import models
+        # 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:
+                if requirement_template.relationship_template is not None:
+                    relationship = \
+                        requirement_template.relationship_template.instantiate(context, self)
+                else:
+                    relationship = models.Relationship(target_capability=target_capability)
+                relationship.name = requirement_template.name
+                relationship.requirement_template = requirement_template
+                relationship.target_node = target_node
+                self.outbound_relationships.append(relationship)
+                return True
+            else:
+                context.validation.report('requirement "{0}" of node "{1}" targets node '
+                                          'template "{2}" but its instantiated nodes do not '
+                                          'have enough capacity'.format(
+                                              requirement_template.name,
+                                              self.name,
+                                              target_node_template.name),
+                                          level=validation.Issue.BETWEEN_INSTANCES)
+                return False
+        else:
+            context.validation.report('requirement "{0}" of node "{1}" targets node template '
+                                      '"{2}" but it has no instantiated nodes'.format(
+                                          requirement_template.name,
+                                          self.name,
+                                          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 "{0}" of node "{1}" requires at least {2:d} '
+                                          'relationships but has {3:d}'.format(
+                                              capability.name,
+                                              self.name,
+                                              capability.min_occurrences,
+                                              capability.occurrences),
+                                          level=validation.Issue.BETWEEN_INSTANCES)
+                satisfied = False
+        return satisfied
+
+    @property
+    def as_raw(self):
+        return collections.OrderedDict((
+            ('name', self.name),
+            ('type_name', self.type_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.outbound_relationships))))
+
+    def validate(self, context):
+        if len(self.name) > context.modeling.id_max_length:
+            context.validation.report('"{0}" has an ID longer than the limit of {1:d} characters: '
+                                      '{2:d}'.format(
+                                          self.name,
+                                          context.modeling.id_max_length,
+                                          len(self.name)),
+                                      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.outbound_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.outbound_relationships, report_issues)
+
+    def dump(self, context):
+        console.puts('Node: {0}'.format(context.style.node(self.name)))
+        with context.style.indent:
+            console.puts('Type: {0}'.format(context.style.type(self.type.name)))
+            console.puts('Template: {0}'.format(context.style.node(self.node_template.name)))
+            utils.dump_dict_values(context, self.properties, '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.outbound_relationships, 'Relationships')
+
+
+class GroupBase(InstanceModelMixin):
+    """
+    Usually an instance of a :class:`GroupTemplate`.
+
+    :ivar name: Name (unique for this service)
+    :vartype name: basestring
+    :ivar group_template: Template from which this group was instantiated (optional)
+    :vartype group_template: :class:`GroupTemplate`
+    :ivar type: Group type
+    :vartype type: :class:`Type`
+    :ivar description: Human-readable description
+    :vartype description: string
+    :ivar nodes: Members of this group
+    :vartype nodes: [:class:`Node`]
+    :ivar properties: Associated parameters
+    :vartype properties: {basestring: :class:`Parameter`}
+    :ivar interfaces: Bundles of operations
+    :vartype interfaces: {basestring: :class:`Interface`}
+
+    :ivar service: Containing service
+    :vartype service: :class:`Service`
+    :ivar policies: Policies enacted on this group
+    :vartype policies: [:class:`Policy`]
+    """
+
+    __tablename__ = 'group'
+
+    @declared_attr
+    def group_template(cls):
+        return cls._create_many_to_one_relationship('group_template')
+
+    @declared_attr
+    def type(cls):
+        return cls._create_many_to_one_relationship('type')
+
+    description = Column(Text)
+
+    @declared_attr
+    def nodes(cls):
+        return cls._create_many_to_many_relationship('node')
+
+    @declared_attr
+    def properties(cls):
+        return cls._create_many_to_many_relationship('parameter', table_prefix='properties',
+                                                     dict_key='name')
+
+    @declared_attr
+    def interfaces(cls):
+        return cls._create_one_to_many_relationship('interface', dict_key='name')
+
+    # region foreign_keys
+
+    __private_fields__ = ['type_fk',
+                          'service_fk',
+                          'group_template_fk']
+
+    # Group many-to-one to Type
+    @declared_attr
+    def type_fk(cls):
+        return cls._create_foreign_key('type')
+
+    # Service one-to-many to Group
+    @declared_attr
+    def service_fk(cls):
+        return cls._create_foreign_key('service')
+
+    # Group many-to-one to GroupTemplate
+    @declared_attr
+    def group_template_fk(cls):
+        return cls._create_foreign_key('group_template', nullable=True)
+
+    # endregion
+
+    @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))))
+
+    def validate(self, context):
+        utils.validate_dict_values(context, self.properties)
+        utils.validate_dict_values(context, self.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.interfaces, report_issues)
+
+    def dump(self, context):
+        console.puts('Group: {0}'.format(context.style.node(self.name)))
+        with context.style.indent:
+            console.puts('Type: {0}'.format(context.style.type(self.type.name)))
+            utils.dump_dict_values(context, self.properties, 'Properties')
+            utils.dump_interfaces(context, self.interfaces)
+            if self.nodes:
+                console.puts('Member nodes:')
+                with context.style.indent:
+                    for node in self.nodes:
+                        console.puts(context.style.node(node.name))
+
+
+class PolicyBase(InstanceModelMixin):
+    """
+    Usually an instance of a :class:`PolicyTemplate`.
+
+    :ivar name: Name (unique for this service)
+    :vartype name: basestring
+    :ivar policy_template: Template from which this policy was instantiated (optional)
+    :vartype policy_template: :class:`PolicyTemplate`
+    :ivar type: Policy type
+    :vartype type: :class:`Type`
+    :ivar description: Human-readable description
+    :vartype description: string
+    :ivar nodes: Policy will be enacted on all these nodes
+    :vartype nodes: [:class:`Node`]
+    :ivar groups: Policy will be enacted on all nodes in these groups
+    :vartype groups: [:class:`Group`]
+    :ivar properties: Associated parameters
+    :vartype properties: {basestring: :class:`Parameter`}
+
+    :ivar service: Containing service
+    :vartype service: :class:`Service`
+    """
+
+    __tablename__ = 'policy'
+
+    @declared_attr
+    def policy_template(cls):
+        return cls._create_many_to_one_relationship('policy_template')
+
+    @declared_attr
+    def type(cls):
+        return cls._create_many_to_one_relationship('type')
+
+    description = Column(Text)
+
+    @declared_attr
+    def properties(cls):
+        return cls._create_many_to_many_relationship('parameter', table_prefix='properties',
+                                                     dict_key='name')
+
+    @declared_attr
+    def nodes(cls):
+        return cls._create_many_to_many_relationship('node')
+
+    @declared_attr
+    def groups(cls):
+        return cls._create_many_to_many_relationship('group')
+
+    # region foreign_keys
+
+    __private_fields__ = ['type_fk',
+                          'service_fk',
+                          'policy_template_fk']
+
+    # Policy many-to-one to Type
+    @declared_attr
+    def type_fk(cls):
+        return cls._create_foreign_key('type')
+
+    # Service one-to-many to Policy
+    @declared_attr
+    def service_fk(cls):
+        return cls._create_foreign_key('service')
+
+    # Policy many-to-one to PolicyTemplate
+    @declared_attr
+    def policy_template_fk(cls):
+        return cls._create_foreign_key('policy_template', nullable=True)
+
+    # endregion
+
+    @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):
+        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: {0}'.format(context.style.node(self.name)))
+        with context.style.indent:
+            console.puts('Type: {0}'.format(context.style.type(self.type.name)))
+            utils.dump_dict_values(context, self.properties, 'Properties')
+            if self.nodes:
+                console.puts('Target nodes:')
+                with context.style.indent:
+                    for node in self.nodes:
+                        console.puts(context.style.node(node.name))
+            if self.groups:
+                console.puts('Target groups:')
+                with context.style.indent:
+                    for group in self.groups:
+                        console.puts(context.style.node(group.name))
+
+
+class SubstitutionBase(InstanceModelMixin):
+    """
+    Used to substitute a single node for the entire deployment.
+
+    Usually an instance of a :class:`SubstitutionTemplate`.
+
+    :ivar substitution_template: Template from which this substitution was instantiated (optional)
+    :vartype substitution_template: :class:`SubstitutionTemplate`
+    :ivar node_type: Exposed node type
+    :vartype node_type: :class:`Type`
+    :ivar mappings: Requirement and capability mappings
+    :vartype mappings: {basestring: :class:`SubstitutionTemplate`}
+
+    :ivar service: Containing service
+    :vartype service: :class:`Service`
+    """
+
+    __tablename__ = 'substitution'
+
+    @declared_attr
+    def substitution_template(cls):
+        return cls._create_many_to_one_relationship('substitution_template')
+
+    @declared_attr
+    def node_type(cls):
+        return cls._create_many_to_one_relationship('type')
+
+    @declared_attr
+    def mappings(cls):
+        return cls._create_one_to_many_relationship('substitution_mapping', dict_key='name')
+
+    # region foreign_keys
+
+    __private_fields__ = ['node_type_fk',
+                          'substitution_template_fk']
+
+    # Substitution many-to-one to Type
+    @declared_attr
+    def node_type_fk(cls):
+        return cls._create_foreign_key('type')
+
+    # Substitution many-to-one to SubstitutionTemplate
+    @declared_attr
+    def substitution_template_fk(cls):
+        return cls._create_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))))
+
+    def validate(self, context):
+        utils.validate_dict_values(context, self.mappings)
+
+    def coerce_values(self, context, container, report_issues):
+        utils.coerce_dict_values(context, container, self.mappings, report_issues)
+
+    def dump(self, context):
+        console.puts('Substitution:')
+        with context.style.indent:
+            console.puts('Node type: {0}'.format(context.style.type(self.node_type.name)))
+            utils.dump_dict_values(context, self.mappings, 'Mappings')
+
+
+class SubstitutionMappingBase(InstanceModelMixin):
+    """
+    Used by :class:`Substitution` to map a capability or a requirement to a node.
+
+    Only one of `capability_template` and `requirement_template` can be set.
+
+    Usually an instance of a :class:`SubstitutionTemplate`.
+
+    :ivar name: Exposed capability or requirement name
+    :vartype name: basestring
+    :ivar node: Node
+    :vartype node: :class:`Node`
+    :ivar capability: Capability in the node
+    :vartype capability: :class:`Capability`
+    :ivar requirement_template: Requirement template in the node template
+    :vartype requirement_template: :class:`RequirementTemplate`
+
+    :ivar substitution: Containing substitution
+    :vartype substitution: :class:`Substitution`
+    """
+
+    __tablename__ = 'substitution_mapping'
+
+    @declared_attr
+    def node(cls):
+        return cls._create_one_to_one_relationship('node')
+
+    @declared_attr
+    def capability(cls):
+        return cls._create_one_to_one_relationship('capability')
+
+    @declared_attr
+    def requirement_template(cls):
+        return cls._create_one_to_one_relationship('requirement_template')
+
+    # region foreign keys
+
+    __private_fields__ = ['substitution_fk',
+                          'node_fk',
+                          'capability_fk',
+                          'requirement_template_fk']
+
+    # Substitution one-to-many to SubstitutionMapping
+    @declared_attr
+    def substitution_fk(cls):
+        return cls._create_foreign_key('substitution')
+
+    # Substitution one-to-one to NodeTemplate
+    @declared_attr
+    def node_fk(cls):
+        return cls._create_foreign_key('node')
+
+    # Substitution one-to-one to Capability
+    @declared_attr
+    def capability_fk(cls):
+        return cls._create_foreign_key('capability', nullable=True)
+
+    # Substitution one-to-one to RequirementTemplate
+    @declared_attr
+    def requirement_template_fk(cls):
+        return cls._create_foreign_key('requirement_template', nullable=True)
+
+    # endregion
+
+    @property
+    def as_raw(self):
+        return collections.OrderedDict((
+            ('name', self.name)))
+
+    def validate(self, context):
+        if (self.capability is None) and (self.requirement_template is None):
+            context.validation.report('mapping "{0}" refers to neither capability nor a requirement'
+                                      ' in node: {1}'.format(
+                                          self.name,
+                                          formatting.safe_repr(self.node.name)),
+                                      level=validation.Issue.BETWEEN_TYPES)
+
+    def dump(self, context):
+        console.puts('{0} -> {1}.{2}'.format(
+            context.style.node(self.name),
+            context.style.node(self.node.name),
+            context.style.node(self.capability.name
+                               if self.capability
+                               else self.requirement_template.name)))
+
+
+class RelationshipBase(InstanceModelMixin):
+    """
+    Connects :class:`Node` to a capability in another node.
+
+    Might be an instance of a :class:`RelationshipTemplate`.
+
+    :ivar name: Name (usually the name of the requirement at the source node template)
+    :vartype name: basestring
+    :ivar relationship_template: Template from which this relationship was instantiated (optional)
+    :vartype relationship_template: :class:`RelationshipTemplate`
+    :ivar requirement_template: Template from which this relationship was instantiated (optional)
+    :vartype requirement_template: :class:`RequirementTemplate`
+    :ivar type: Relationship type
+    :vartype type: :class:`Type`
+    :ivar target_capability: Capability at the target node (optional)
+    :vartype target_capability: :class:`Capability`
+    :ivar properties: Associated parameters
+    :vartype properties: {basestring: :class:`Parameter`}
+    :ivar interfaces: Bundles of operations
+    :vartype interfaces: {basestring: :class:`Interfaces`}
+
+    :ivar source_position: ??
+    :vartype source_position: int
+    :ivar target_position: ??
+    :vartype target_position: int
+
+    :ivar source_node: Source node
+    :vartype source_node: :class:`Node`
+    :ivar target_node: Target node
+    :vartype target_node: :class:`Node`
+    :ivar tasks: Tasks on this node
+    :vartype tasks: [:class:`Task`]
+    """
+
+    __tablename__ = 'relationship'
+
+    @declared_attr
+    def relationship_template(cls):
+        return cls._create_many_to_one_relationship('relationship_template')
+
+    @declared_attr
+    def requirement_template(cls):
+        return cls._create_many_to_one_relationship('requirement_template')
+
+    @declared_attr
+    def type(cls):
+        return cls._create_many_to_one_relationship('type')
+
+    @declared_attr
+    def target_capability(cls):
+        return cls._create_one_to_one_relationship('capability')
+
+    @declared_attr
+    def properties(cls):
+        return cls._create_many_to_many_relationship('parameter', table_prefix='properties',
+                                                     dict_key='name')
+
+    @declared_attr
+    def interfaces(cls):
+        return cls._create_one_to_many_relationship('interface', dict_key='name')
+
+    # region orchestration
+
+    source_position = Column(Integer) # ???
+    target_position = Column(Integer) # ???
+
+    # endregion
+
+    # region foreign keys
+
+    __private_fields__ = ['type_fk',
+                          'source_node_fk',
+                          'target_node_fk',
+                          'target_capability_fk',
+                          'requirement_template_fk',
+                          'relationship_template_fk']
+
+    # Relationship many-to-one to Type
+    @declared_attr
+    def type_fk(cls):
+        return cls._create_foreign_key('type', nullable=True)
+
+    # Node one-to-many to Relationship
+    @declared_attr
+    def source_node_fk(cls):
+        return cls._create_foreign_key('node')
+
+    # Node one-to-many to Relationship
+    @declared_attr
+    def target_node_fk(cls):
+        return cls._create_foreign_key('node')
+
+    # Relationship one-to-one to Capability
+    @declared_attr
+    def target_capability_fk(cls):
+        return cls._create_foreign_key('capability', nullable=True)
+
+    # Relationship many-to-one to RequirementTemplate
+    @declared_attr
+    def requirement_template_fk(cls):
+        return cls._create_foreign_key('requirement_template', nullable=True)
+
+    # Relationship many-to-one to RelationshipTemplate
+    @declared_attr
+    def relationship_template_fk(cls):
+        return cls._create_foreign_key('relationship_template', nullable=True)
+
+    # endregion
+
+    @property
+    def as_raw(self):
+        return collections.OrderedDict((
+            ('name', self.name),
+            ('target_node_id', self.target_node.name),
+            ('type_name', self.type_name),
+            ('template_name', self.template_name),
+            ('properties', formatting.as_raw_dict(self.properties)),
+            ('interfaces', formatting.as_raw_list(self.interfaces))))
+
+    def validate(self, context):
+        utils.validate_dict_values(context, self.properties)
+        utils.validate_dict_values(context, self.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.interfaces, report_issues)
+
+    def dump(self, context):
+        if self.name:
+            console.puts('{0} ->'.format(context.style.node(self.name)))
+        else:
+            console.puts('->')
+        with context.style.indent:
+            console.puts('Node: {0}'.format(context.style.node(self.target_node.name)))
+            if self.target_capability:
+                console.puts('Capability: {0}'.format(context.style.node(
+                    self.target_capability.name)))
+            if self.type is not None:
+                console.puts('Relationship type: {0}'.format(context.style.type(self.type.name)))
+            if (self.relationship_template is not None) and self.relationship_template.name:
+                console.puts('Relationship template: {0}'.format(
+                    context.style.node(self.relationship_template.name)))
+            utils.dump_dict_values(context, self.properties, 'Properties')
+            utils.dump_interfaces(context, self.interfaces, 'Interfaces')
+
+
+class CapabilityBase(InstanceModelMixin):
+    """
+    A capability of a :class:`Node`.
+
+    Usually an instance of a :class:`CapabilityTemplate`.
+
+    :ivar name: Name (unique for the node)
+    :vartype name: basestring
+    :ivar capability_template: Template from which this capability was instantiated (optional)
+    :vartype capability_template: :class:`capabilityTemplate`
+    :ivar type: Capability type
+    :vartype type: :class:`Type`
+    :ivar min_occurrences: Minimum number of requirement matches required
+    :vartype min_occurrences: int
+    :ivar max_occurrences: Maximum number of requirement matches allowed
+    :vartype min_occurrences: int
+    :ivar occurrences: Actual number of requirement matches
+    :vartype occurrences: int
+    :ivar properties: Associated parameters
+    :vartype properties: {basestring: :class:`Parameter`}
+
+    :ivar node: Containing node
+    :vartype node: :class:`Node`
+    :ivar relationship: Available when we are the target of a relationship
+    :vartype relationship: :class:`Relationship`
+    :ivar substitution_mapping: Our contribution to service substitution
+    :vartype substitution_mapping: :class:`SubstitutionMapping`
+    """
+
+    __tablename__ = 'capability'
+
+    @declared_attr
+    def capability_template(cls):
+        return cls._create_many_to_one_relationship('capability_template')
+
+    @declared_attr
+    def type(cls):
+        return cls._create_many_to_one_relationship('type')
+
+    min_occurrences = Column(Integer, default=None)
+    max_occurrences = Column(Integer, default=None)
+    occurrences = Column(Integer, default=0)
+
+    @declared_attr
+    def properties(cls):
+        return cls._create_many_to_many_relationship('parameter', table_prefix='properties',
+                                                     dict_key='name')
+
+    # region foreign_keys
+
+    __private_fields__ = ['capability_fk',
+                          'node_fk',
+                          'capability_template_fk']
+
+    # Capability many-to-one to Type
+    @declared_attr
+    def type_fk(cls):
+        return cls._create_foreign_key('type')
+
+    # Node one-to-many to Capability
+    @declared_attr
+    def node_fk(cls):
+        return cls._create_foreign_key('node')
+
+    # Capability many-to-one to CapabilityTemplate
+    @declared_attr
+    def capability_template_fk(cls):
+        return cls._create_foreign_key('capability_template', nullable=True)
+
+    # 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):
+        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: {0}'.format(context.style.type(self.type.name)))
+            console.puts('Occurrences: {0:d} ({1:d}{2})'.format(
+                self.occurrences,
+                self.min_occurrences or 0,
+                ' to {0:d}'.format(self.max_occurrences)
+                if self.max_occurrences is not None
+                else ' or more'))
+            utils.dump_dict_values(context, self.properties, 'Properties')
+
+
+class InterfaceBase(InstanceModelMixin):
+    """
+    A typed set of :class:`Operation`.
+
+    Usually an instance of :class:`InterfaceTemplate`.
+
+    :ivar name: Name (unique for the node, group, or relationship)
+    :vartype name: basestring
+    :ivar interface_template: Template from which this interface was instantiated (optional)
+    :vartype interface_template: :class:`InterfaceTemplate`
+    :ivar type: Interface type
+    :vartype type: :class:`Type`
+    :ivar description: Human-readable description
+    :vartype description: string
+    :ivar inputs: Parameters that can be used by all operations in the interface
+    :vartype inputs: {basestring: :class:`Parameter`}
+    :ivar operations: Operations
+    :vartype operations: {basestring: :class:`Operation`}
+
+    :ivar node: Containing node
+    :vartype node: :class:`Node`
+    :ivar group: Containing group
+    :vartype group: :class:`Group`
+    :ivar relationship: Containing relationship
+    :vartype relationship: :class:`Relationship`
+    """
+
+    __tablename__ = 'interface'
+
+    @declared_attr
+    def interface_template(cls):
+        return cls._create_many_to_one_relationship('interface_template')
+
+    @declared_attr
+    def type(cls):
+        return cls._create_many_to_one_relationship('type')
+
+    description = Column(Text)
+
+    @declared_attr
+    def inputs(cls):
+        return cls._create_many_to_many_relationship('parameter', table_prefix='inputs',
+                                                     dict_key='name')
+
+    @declared_attr
+    def operations(cls):
+        return cls._create_one_to_many_relationship('operation', dict_key='name')
+
+    # region foreign_keys
+
+    __private_fields__ = ['type_fk',
+                          'node_fk',
+                          'group_fk',
+                          'relationship_fk',
+                          'interface_template_fk']
+
+    # Interface many-to-one to Type
+    @declared_attr
+    def type_fk(cls):
+        return cls._create_foreign_key('type')
+
+    # Node one-to-many to Interface
+    @declared_attr
+    def node_fk(cls):
+        return cls._create_foreign_key('node', nullable=True)
+
+    # Group one-to-many to Interface
+    @declared_attr
+    def group_fk(cls):
+        return cls._create_foreign_key('group', nullable=True)
+
+    # Relationship one-to-many to Interface
+    @declared_attr
+    def relationship_fk(cls):
+        return cls._create_foreign_key('relationship', nullable=True)
+
+    # Interface many-to-one to InterfaceTemplate
+    @declared_attr
+    def interface_template_fk(cls):
+        return cls._create_foreign_key('interface_template', nullable=True)
+
+    # 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):
+        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: {0}'.format(context.style.type(self.type.name)))
+            utils.dump_dict_values(context, self.inputs, 'Inputs')
+            utils.dump_dict_values(context, self.operations, 'Operations')
+
+
+class OperationBase(InstanceModelMixin):
+    """
+    An operation in a :class:`Interface`.
+
+    Might be an instance of :class:`OperationTemplate`.
+
+    :ivar name: Name (unique for the interface or service)
+    :vartype name: basestring
+    :ivar operation_template: Template from which this operation was instantiated (optional)
+    :vartype operation_template: :class:`OperationTemplate`
+    :ivar description: Human-readable description
+    :vartype description: string
+    :ivar plugin_specification: Associated plugin
+    :vartype plugin_specification: :class:`PluginSpecification`
+    :ivar implementation: Implementation string (interpreted by the plugin)
+    :vartype implementation: basestring
+    :ivar dependencies: Dependency strings (interpreted by the plugin)
+    :vartype dependencies: [basestring]
+    :ivar inputs: Parameters that can be used by this operation
+    :vartype inputs: {basestring: :class:`Parameter`}
+    :ivar executor: Executor name
+    :vartype executor: basestring
+    :ivar max_retries: Maximum number of retries allowed in case of failure
+    :vartype max_retries: int
+    :ivar retry_interval: Interval between retries (in seconds)
+    :vartype retry_interval: int
+
+    :ivar interface: Containing interface
+    :vartype interface: :class:`Interface`
+    :ivar service: Containing service
+    :vartype service: :class:`Service`
+    """
+
+    __tablename__ = 'operation'
+
+    @declared_attr
+    def operation_template(cls):
+        return cls._create_many_to_one_relationship('operation_template')
+
+    description = Column(Text)
+
+    @declared_attr
+    def plugin_specification(cls):
+        return cls._create_one_to_one_relationship('plugin_specification')
+
+    implementation = Column(Text)
+    dependencies = Column(modeling_types.StrictList(item_cls=basestring))
+
+    @declared_attr
+    def inputs(cls):
+        return cls._create_many_to_many_relationship('parameter', table_prefix='inputs',
+                                                     dict_key='name')
+
+    executor = Column(Text)
+    max_retries = Column(Integer)
+    retry_interval = Column(Integer)
+
+    # region foreign_keys
+
+    __private_fields__ = ['service_fk',
+                          'interface_fk',
+                          'plugin_fk',
+                          'operation_template_fk']
+
+    # Service one-to-many to Operation
+    @declared_attr
+    def service_fk(cls):
+        return cls._create_foreign_key('service', nullable=True)
+
+    # Interface one-to-many to Operation
+    @declared_attr
+    def interface_fk(cls):
+        return cls._create_foreign_key('interface', nullable=True)
+
+    # Operation one-to-one to PluginSpecification
+    @declared_attr
+    def plugin_specification_fk(cls):
+        return cls._create_foreign_key('plugin_specification', nullable=True)
+
+    # Operation many-to-one to OperationTemplate
+    @declared_attr
+    def operation_template_fk(cls):
+        return cls._create_foreign_key('operation_template', nullable=True)
+
+    # 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):
+        # TODO must be associated with interface or service
+        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: {0}'.format(
+                    context.style.literal(self.implementation)))
+            if self.dependencies:
+                console.puts(
+                    'Dependencies: {0}'.format(
+                        ', '.join((str(context.style.literal(v)) for v in self.dependencies))))
+            if self.executor is not None:
+                console.puts('Executor: {0}'.format(context.style.literal(self.executor)))
+            if self.max_retries is not None:
+                console.puts('Max retries: {0}'.format(context.style.literal(self.max_retries)))
+            if self.retry_interval is not None:
+                console.puts('Retry interval: {0}'.format(
+                    context.style.literal(self.retry_interval)))
+            utils.dump_dict_values(context, self.inputs, 'Inputs')
+
+
+class ArtifactBase(InstanceModelMixin):
+    """
+    A file associated with a :class:`Node`.
+
+    Usually an instance of :class:`ArtifactTemplate`.
+
+    :ivar name: Name (unique for the node)
+    :vartype name: basestring
+    :ivar artifact_template: Template from which this artifact was instantiated (optional)
+    :vartype artifact_template: :class:`ArtifactTemplate`
+    :ivar type: Artifact type
+    :vartype type: :class:`Type`
+    :ivar description: Human-readable description
+    :vartype description: string
+    :ivar source_path: Source path (CSAR or repository)
+    :vartype source_path: basestring
+    :ivar target_path: Path at destination machine
+    :vartype target_path: basestring
+    :ivar repository_url: Repository URL
+    :vartype repository_path: basestring
+    :ivar repository_credential: Credentials for accessing the repository
+    :vartype repository_credential: {basestring: basestring}
+    :ivar properties: Associated parameters
+    :vartype properties: {basestring: :class:`Parameter`}
+
+    :ivar node: Containing node
+    :vartype node: :class:`Node`
+    """
+
+    __tablename__ = 'artifact'
+
+    @declared_attr
+    def artifact_template(cls):
+        return cls._create_many_to_one_relationship('artifact_template')
+
+    @declared_attr
+    def type(cls):
+        return cls._create_many_to_one_relationship('type')
+
+    description = Column(Text)
+    source_path = Column(Text)
+    target_path = Column(Text)
+    repository_url = Column(Text)
+    repository_credential = Column(modeling_types.StrictDict(basestring, basestring))
+
+    @declared_attr
+    def properties(cls):
+        return cls._create_many_to_many_relationship('parameter', table_prefix='properties',
+                                                     dict_key='name')
+
+    # region foreign_keys
+
+    __private_fields__ = ['type_fk',
+                          'node_fk',
+                          'artifact_template_fk']
+
+    # Artifact many-to-one to Type
+    @declared_attr
+    def type_fk(cls):
+        return cls._create_foreign_key('type')
+
+    # Node one-to-many to Artifact
+    @declared_attr
+    def node_fk(cls):
+        return cls._create_foreign_key('node')
+
+    # Artifact many-to-one to ArtifactTemplate
+    @declared_attr
+    def artifact_template_fk(cls):
+        return cls._create_foreign_key('artifact_template', nullable=True)
+
+    # 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):
+        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: {0}'.format(context.style.type(self.type.name)))
+            console.puts('Source path: {0}'.format(context.style.literal(self.source_path)))
+            if self.target_path is not None:
+                console.puts('Target path: {0}'.format(context.style.literal(self.target_path)))
+            if self.repository_url is not None:
+                console.puts('Repository URL: {0}'.format(
+                    context.style.literal(self.repository_url)))
+            if self.repository_credential:
+                console.puts('Repository credential: {0}'.format(
+                    context.style.literal(self.repository_credential)))
+            utils.dump_dict_values(context, self.properties, 'Properties')


[4/8] incubator-ariatosca git commit: Separate plugin specification form plugin; move dry support to CLI; various renames and refactorings

Posted by mx...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/dd5bfa93/aria/modeling/service_template.py
----------------------------------------------------------------------
diff --git a/aria/modeling/service_template.py b/aria/modeling/service_template.py
index 092de51..c9a02eb 100644
--- a/aria/modeling/service_template.py
+++ b/aria/modeling/service_template.py
@@ -30,7 +30,7 @@ from sqlalchemy.ext.declarative import declared_attr
 
 from ..parser import validation
 from ..utils import collections, formatting, console
-from .bases import TemplateModelMixin
+from .mixins import TemplateModelMixin
 from . import (
     utils,
     types as modeling_types
@@ -64,10 +64,10 @@ class ServiceTemplateBase(TemplateModelMixin): # pylint: disable=too-many-public
     :vartype inputs: {basestring: :class:`Parameter`}
     :ivar outputs: These parameters are filled in after service installation
     :vartype outputs: {basestring: :class:`Parameter`}
-    :ivar operation_templates: Custom operations that can be performed on the service
-    :vartype operation_templates: {basestring: :class:`OperationTemplate`}
-    :ivar plugins: Plugins required by services
-    :vartype plugins: {basestring: :class:`Plugin`}
+    :ivar workflow_templates: Custom workflows that can be performed on the service
+    :vartype workflow_templates: {basestring: :class:`OperationTemplate`}
+    :ivar plugin_specifications: Plugins required by services
+    :vartype plugin_specifications: {basestring: :class:`PluginSpecification`}
     :ivar node_types: Base for the node type hierarchy
     :vartype node_types: :class:`Type`
     :ivar group_types: Base for the group type hierarchy
@@ -82,8 +82,8 @@ class ServiceTemplateBase(TemplateModelMixin): # pylint: disable=too-many-public
     :vartype interface_types: :class:`Type`
     :ivar artifact_types: Base for the artifact type hierarchy
     :vartype artifact_types: :class:`Type`
-    :ivar plugins: Plugins required to be installed
-    :vartype plugins: {basestring: :class:`Plugin`}
+    :ivar plugin_specifications: Plugins required to be installed
+    :vartype plugin_specifications: {basestring: :class:`PluginSpecification`}
     :ivar created_at: Creation timestamp
     :vartype created_at: :class:`datetime.datetime`
     :ivar updated_at: Update timestamp
@@ -101,69 +101,73 @@ class ServiceTemplateBase(TemplateModelMixin): # pylint: disable=too-many-public
     @declared_attr
     def meta_data(cls):
         # Warning! We cannot use the attr name "metadata" because it's used by SqlAlchemy!
-        return cls.many_to_many_relationship('metadata', dict_key='name')
+        return cls._create_many_to_many_relationship('metadata', dict_key='name')
 
     @declared_attr
     def node_templates(cls):
-        return cls.one_to_many_relationship('node_template')
+        return cls._create_one_to_many_relationship('node_template')
 
     @declared_attr
     def group_templates(cls):
-        return cls.one_to_many_relationship('group_template')
+        return cls._create_one_to_many_relationship('group_template')
 
     @declared_attr
     def policy_templates(cls):
-        return cls.one_to_many_relationship('policy_template')
+        return cls._create_one_to_many_relationship('policy_template')
 
     @declared_attr
     def substitution_template(cls):
-        return cls.one_to_one_relationship('substitution_template')
+        return cls._create_one_to_one_relationship('substitution_template')
 
     @declared_attr
     def inputs(cls):
-        return cls.many_to_many_relationship('parameter', table_prefix='inputs',
-                                             dict_key='name')
+        return cls._create_many_to_many_relationship('parameter', table_prefix='inputs',
+                                                     dict_key='name')
 
     @declared_attr
     def outputs(cls):
-        return cls.many_to_many_relationship('parameter', table_prefix='outputs',
-                                             dict_key='name')
+        return cls._create_many_to_many_relationship('parameter', table_prefix='outputs',
+                                                     dict_key='name')
 
     @declared_attr
-    def operation_templates(cls):
-        return cls.one_to_many_relationship('operation_template', dict_key='name')
+    def workflow_templates(cls):
+        return cls._create_one_to_many_relationship('operation_template', dict_key='name')
 
     @declared_attr
-    def plugins(cls):
-        return cls.one_to_many_relationship('plugin')
+    def plugin_specifications(cls):
+        return cls._create_one_to_many_relationship('plugin_specification')
 
     @declared_attr
     def node_types(cls):
-        return cls.one_to_one_relationship('type', key='node_type_fk', backreference='')
+        return cls._create_one_to_one_relationship('type', key='node_type_fk', backreference='')
 
     @declared_attr
     def group_types(cls):
-        return cls.one_to_one_relationship('type', key='group_type_fk', backreference='')
+        return cls._create_one_to_one_relationship('type', key='group_type_fk', backreference='')
 
     @declared_attr
     def policy_types(cls):
-        return cls.one_to_one_relationship('type', key='policy_type_fk', backreference='')
+        return cls._create_one_to_one_relationship('type', key='policy_type_fk', backreference='')
 
     @declared_attr
     def relationship_types(cls):
-        return cls.one_to_one_relationship('type', key='relationship_type_fk', backreference='')
+        return cls._create_one_to_one_relationship('type', key='relationship_type_fk',
+                                                   backreference='')
 
     @declared_attr
     def capability_types(cls):
-        return cls.one_to_one_relationship('type', key='capability_type_fk', backreference='')
+        return cls._create_one_to_one_relationship('type', key='capability_type_fk',
+                                                   backreference='')
 
     @declared_attr
     def interface_types(cls):
-        return cls.one_to_one_relationship('type', key='interface_type_fk', backreference='')
+        return cls._create_one_to_one_relationship('type', key='interface_type_fk',
+                                                   backreference='')
 
     @declared_attr
     def artifact_types(cls):
-        return cls.one_to_one_relationship('type', key='artifact_type_fk', backreference='')
+        return cls._create_one_to_one_relationship('type', key='artifact_type_fk',
+                                                   backreference='')
 
     # region orchestration
 
@@ -186,42 +190,42 @@ class ServiceTemplateBase(TemplateModelMixin): # pylint: disable=too-many-public
     # ServiceTemplate one-to-one to SubstitutionTemplate
     @declared_attr
     def substitution_template_fk(cls):
-        return cls.foreign_key('substitution_template', nullable=True)
+        return cls._create_foreign_key('substitution_template', nullable=True)
 
     # ServiceTemplate one-to-one to Type
     @declared_attr
     def node_type_fk(cls):
-        return cls.foreign_key('type', nullable=True)
+        return cls._create_foreign_key('type', nullable=True)
 
     # ServiceTemplate one-to-one to Type
     @declared_attr
     def group_type_fk(cls):
-        return cls.foreign_key('type', nullable=True)
+        return cls._create_foreign_key('type', nullable=True)
 
     # ServiceTemplate one-to-one to Type
     @declared_attr
     def policy_type_fk(cls):
-        return cls.foreign_key('type', nullable=True)
+        return cls._create_foreign_key('type', nullable=True)
 
     # ServiceTemplate one-to-one to Type
     @declared_attr
     def relationship_type_fk(cls):
-        return cls.foreign_key('type', nullable=True)
+        return cls._create_foreign_key('type', nullable=True)
 
     # ServiceTemplate one-to-one to Type
     @declared_attr
     def capability_type_fk(cls):
-        return cls.foreign_key('type', nullable=True)
+        return cls._create_foreign_key('type', nullable=True)
 
     # ServiceTemplate one-to-one to Type
     @declared_attr
     def interface_type_fk(cls):
-        return cls.foreign_key('type', nullable=True)
+        return cls._create_foreign_key('type', nullable=True)
 
     # ServiceTemplate one-to-one to Type
     @declared_attr
     def artifact_type_fk(cls):
-        return cls.foreign_key('type', nullable=True)
+        return cls._create_foreign_key('type', nullable=True)
 
     # endregion
 
@@ -250,7 +254,7 @@ class ServiceTemplateBase(TemplateModelMixin): # pylint: disable=too-many-public
             ('substitution_template', formatting.as_raw(self.substitution_template)),
             ('inputs', formatting.as_raw_dict(self.inputs)),
             ('outputs', formatting.as_raw_dict(self.outputs)),
-            ('operation_templates', formatting.as_raw_list(self.operation_templates))))
+            ('workflow_templates', formatting.as_raw_list(self.workflow_templates))))
 
     @property
     def types_as_raw(self):
@@ -283,7 +287,7 @@ class ServiceTemplateBase(TemplateModelMixin): # pylint: disable=too-many-public
 
         utils.instantiate_list(context, self, service.groups, self.group_templates)
         utils.instantiate_list(context, self, service.policies, self.policy_templates)
-        utils.instantiate_dict(context, self, service.operations, self.operation_templates)
+        utils.instantiate_dict(context, self, service.workflows, self.workflow_templates)
 
         if self.substitution_template is not None:
             service.substitution = self.substitution_template.instantiate(context, container)
@@ -308,7 +312,7 @@ class ServiceTemplateBase(TemplateModelMixin): # pylint: disable=too-many-public
             self.substitution_template.validate(context)
         utils.validate_dict_values(context, self.inputs)
         utils.validate_dict_values(context, self.outputs)
-        utils.validate_dict_values(context, self.operation_templates)
+        utils.validate_dict_values(context, self.workflow_templates)
         if self.node_types is not None:
             self.node_types.validate(context)
         if self.group_types is not None:
@@ -333,7 +337,7 @@ class ServiceTemplateBase(TemplateModelMixin): # pylint: disable=too-many-public
             self.substitution_template.coerce_values(context, container, report_issues)
         utils.coerce_dict_values(context, container, self.inputs, report_issues)
         utils.coerce_dict_values(context, container, self.outputs, report_issues)
-        utils.coerce_dict_values(context, container, self.operation_templates, report_issues)
+        utils.coerce_dict_values(context, container, self.workflow_templates, report_issues)
 
     def dump(self, context):
         if self.description is not None:
@@ -349,7 +353,7 @@ class ServiceTemplateBase(TemplateModelMixin): # pylint: disable=too-many-public
             self.substitution_template.dump(context)
         utils.dump_dict_values(context, self.inputs, 'Inputs')
         utils.dump_dict_values(context, self.outputs, 'Outputs')
-        utils.dump_dict_values(context, self.operation_templates, 'Operation templates')
+        utils.dump_dict_values(context, self.workflow_templates, 'Workflow templates')
 
     def dump_types(self, context):
         if self.node_types.children:
@@ -407,8 +411,8 @@ class NodeTemplateBase(TemplateModelMixin):
     :vartype requirement_templates: [:class:`RequirementTemplate`]
     :ivar target_node_template_constraints: Constraints for filtering relationship targets
     :vartype target_node_template_constraints: [:class:`FunctionType`]
-    :ivar plugins: Plugins required to be installed on the node's host
-    :vartype plugins: {basestring: :class:`Plugin`}
+    :ivar plugin_specifications: Plugins required to be installed on the node's host
+    :vartype plugin_specifications: {basestring: :class:`PluginSpecification`}
 
     :ivar service_template: Containing service template
     :vartype service_template: :class:`ServiceTemplate`
@@ -426,7 +430,7 @@ class NodeTemplateBase(TemplateModelMixin):
 
     @declared_attr
     def type(cls):
-        return cls.many_to_one_relationship('type')
+        return cls._create_many_to_one_relationship('type')
 
     description = Column(Text)
     default_instances = Column(Integer, default=1)
@@ -435,32 +439,32 @@ class NodeTemplateBase(TemplateModelMixin):
 
     @declared_attr
     def properties(cls):
-        return cls.many_to_many_relationship('parameter', table_prefix='properties',
-                                             dict_key='name')
+        return cls._create_many_to_many_relationship('parameter', table_prefix='properties',
+                                                     dict_key='name')
 
     @declared_attr
     def interface_templates(cls):
-        return cls.one_to_many_relationship('interface_template', dict_key='name')
+        return cls._create_one_to_many_relationship('interface_template', dict_key='name')
 
     @declared_attr
     def artifact_templates(cls):
-        return cls.one_to_many_relationship('artifact_template', dict_key='name')
+        return cls._create_one_to_many_relationship('artifact_template', dict_key='name')
 
     @declared_attr
     def capability_templates(cls):
-        return cls.one_to_many_relationship('capability_template', dict_key='name')
+        return cls._create_one_to_many_relationship('capability_template', dict_key='name')
 
     @declared_attr
     def requirement_templates(cls):
-        return cls.one_to_many_relationship('requirement_template',
-                                            foreign_key='node_template_fk',
-                                            backreference='node_template')
+        return cls._create_one_to_many_relationship('requirement_template',
+                                                    foreign_key='node_template_fk',
+                                                    backreference='node_template')
 
     target_node_template_constraints = Column(modeling_types.StrictList(FunctionType))
 
     @declared_attr
-    def plugins(cls):
-        return cls.many_to_many_relationship('plugin')
+    def plugin_specifications(cls):
+        return cls._create_many_to_many_relationship('plugin_specification')
 
     # region foreign_keys
 
@@ -470,12 +474,12 @@ class NodeTemplateBase(TemplateModelMixin):
     # NodeTemplate many-to-one to Type
     @declared_attr
     def type_fk(cls):
-        return cls.foreign_key('type')
+        return cls._create_foreign_key('type')
 
     # ServiceTemplate one-to-many to NodeTemplate
     @declared_attr
     def service_template_fk(cls):
-        return cls.foreign_key('service_template')
+        return cls._create_foreign_key('service_template')
 
     # endregion
 
@@ -579,22 +583,22 @@ class GroupTemplateBase(TemplateModelMixin):
 
     @declared_attr
     def type(cls):
-        return cls.many_to_one_relationship('type')
+        return cls._create_many_to_one_relationship('type')
 
     description = Column(Text)
 
     @declared_attr
     def node_templates(cls):
-        return cls.many_to_many_relationship('node_template')
+        return cls._create_many_to_many_relationship('node_template')
 
     @declared_attr
     def properties(cls):
-        return cls.many_to_many_relationship('parameter', table_prefix='properties',
-                                             dict_key='name')
+        return cls._create_many_to_many_relationship('parameter', table_prefix='properties',
+                                                     dict_key='name')
 
     @declared_attr
     def interface_templates(cls):
-        return cls.one_to_many_relationship('interface_template', dict_key='name')
+        return cls._create_one_to_many_relationship('interface_template', dict_key='name')
 
     # region foreign keys
 
@@ -604,12 +608,12 @@ class GroupTemplateBase(TemplateModelMixin):
     # GroupTemplate many-to-one to Type
     @declared_attr
     def type_fk(cls):
-        return cls.foreign_key('type')
+        return cls._create_foreign_key('type')
 
     # ServiceTemplate one-to-many to GroupTemplate
     @declared_attr
     def service_template_fk(cls):
-        return cls.foreign_key('service_template')
+        return cls._create_foreign_key('service_template')
 
     # endregion
 
@@ -684,22 +688,22 @@ class PolicyTemplateBase(TemplateModelMixin):
 
     @declared_attr
     def type(cls):
-        return cls.many_to_one_relationship('type')
+        return cls._create_many_to_one_relationship('type')
 
     description = Column(Text)
 
     @declared_attr
     def node_templates(cls):
-        return cls.many_to_many_relationship('node_template')
+        return cls._create_many_to_many_relationship('node_template')
 
     @declared_attr
     def group_templates(cls):
-        return cls.many_to_many_relationship('group_template')
+        return cls._create_many_to_many_relationship('group_template')
 
     @declared_attr
     def properties(cls):
-        return cls.many_to_many_relationship('parameter', table_prefix='properties',
-                                             dict_key='name')
+        return cls._create_many_to_many_relationship('parameter', table_prefix='properties',
+                                                     dict_key='name')
 
     # region foreign keys
 
@@ -709,12 +713,12 @@ class PolicyTemplateBase(TemplateModelMixin):
     # PolicyTemplate many-to-one to Type
     @declared_attr
     def type_fk(cls):
-        return cls.foreign_key('type')
+        return cls._create_foreign_key('type')
 
     # ServiceTemplate one-to-many to PolicyTemplate
     @declared_attr
     def service_template_fk(cls):
-        return cls.foreign_key('service_template')
+        return cls._create_foreign_key('service_template')
 
     # endregion
 
@@ -781,11 +785,12 @@ class SubstitutionTemplateBase(TemplateModelMixin):
 
     @declared_attr
     def node_type(cls):
-        return cls.many_to_one_relationship('type')
+        return cls._create_many_to_one_relationship('type')
 
     @declared_attr
     def mappings(cls):
-        return cls.one_to_many_relationship('substitution_template_mapping', dict_key='name')
+        return cls._create_one_to_many_relationship('substitution_template_mapping',
+                                                    dict_key='name')
 
     # region foreign keys
 
@@ -794,7 +799,7 @@ class SubstitutionTemplateBase(TemplateModelMixin):
     # SubstitutionTemplate many-to-one to Type
     @declared_attr
     def node_type_fk(cls):
-        return cls.foreign_key('type')
+        return cls._create_foreign_key('type')
 
     # endregion
 
@@ -847,15 +852,15 @@ class SubstitutionTemplateMappingBase(TemplateModelMixin):
 
     @declared_attr
     def node_template(cls):
-        return cls.one_to_one_relationship('node_template')
+        return cls._create_one_to_one_relationship('node_template')
 
     @declared_attr
     def capability_template(cls):
-        return cls.one_to_one_relationship('capability_template')
+        return cls._create_one_to_one_relationship('capability_template')
 
     @declared_attr
     def requirement_template(cls):
-        return cls.one_to_one_relationship('requirement_template')
+        return cls._create_one_to_one_relationship('requirement_template')
 
     # region foreign keys
 
@@ -867,22 +872,22 @@ class SubstitutionTemplateMappingBase(TemplateModelMixin):
     # SubstitutionTemplate one-to-many to SubstitutionTemplateMapping
     @declared_attr
     def substitution_template_fk(cls):
-        return cls.foreign_key('substitution_template')
+        return cls._create_foreign_key('substitution_template')
 
     # SubstitutionTemplate one-to-one to NodeTemplate
     @declared_attr
     def node_template_fk(cls):
-        return cls.foreign_key('node_template')
+        return cls._create_foreign_key('node_template')
 
     # SubstitutionTemplate one-to-one to CapabilityTemplate
     @declared_attr
     def capability_template_fk(cls):
-        return cls.foreign_key('capability_template', nullable=True)
+        return cls._create_foreign_key('capability_template', nullable=True)
 
     # SubstitutionTemplate one-to-one to RequirementTemplate
     @declared_attr
     def requirement_template_fk(cls):
-        return cls.foreign_key('requirement_template', nullable=True)
+        return cls._create_foreign_key('requirement_template', nullable=True)
 
     # endregion
 
@@ -965,24 +970,25 @@ class RequirementTemplateBase(TemplateModelMixin):
 
     @declared_attr
     def target_node_type(cls):
-        return cls.many_to_one_relationship('type', key='target_node_type_fk', backreference='')
+        return cls._create_many_to_one_relationship('type', key='target_node_type_fk',
+                                                    backreference='')
 
     @declared_attr
     def target_node_template(cls):
-        return cls.one_to_one_relationship('node_template', key='target_node_template_fk',
-                                           backreference='')
+        return cls._create_one_to_one_relationship('node_template', key='target_node_template_fk',
+                                                   backreference='')
 
     @declared_attr
     def target_capability_type(cls):
-        return cls.one_to_one_relationship('type', key='target_capability_type_fk',
-                                           backreference='')
+        return cls._create_one_to_one_relationship('type', key='target_capability_type_fk',
+                                                   backreference='')
 
     target_capability_name = Column(Text)
     target_node_template_constraints = Column(modeling_types.StrictList(FunctionType))
 
     @declared_attr
     def relationship_template(cls):
-        return cls.one_to_one_relationship('relationship_template')
+        return cls._create_one_to_one_relationship('relationship_template')
 
     # region foreign keys
 
@@ -995,27 +1001,27 @@ class RequirementTemplateBase(TemplateModelMixin):
     # RequirementTemplate many-to-one to Type
     @declared_attr
     def target_node_type_fk(cls):
-        return cls.foreign_key('type', nullable=True)
+        return cls._create_foreign_key('type', nullable=True)
 
     # RequirementTemplate one-to-one to NodeTemplate
     @declared_attr
     def target_node_template_fk(cls):
-        return cls.foreign_key('node_template', nullable=True)
+        return cls._create_foreign_key('node_template', nullable=True)
 
     # RequirementTemplate one-to-one to NodeTemplate
     @declared_attr
     def target_capability_type_fk(cls):
-        return cls.foreign_key('type', nullable=True)
+        return cls._create_foreign_key('type', nullable=True)
 
     # NodeTemplate one-to-many to RequirementTemplate
     @declared_attr
     def node_template_fk(cls):
-        return cls.foreign_key('node_template')
+        return cls._create_foreign_key('node_template')
 
     # RequirementTemplate one-to-one to RelationshipTemplate
     @declared_attr
     def relationship_template_fk(cls):
-        return cls.foreign_key('relationship_template', nullable=True)
+        return cls._create_foreign_key('relationship_template', nullable=True)
 
     # endregion
 
@@ -1146,18 +1152,18 @@ class RelationshipTemplateBase(TemplateModelMixin):
 
     @declared_attr
     def type(cls):
-        return cls.many_to_one_relationship('type')
+        return cls._create_many_to_one_relationship('type')
 
     description = Column(Text)
 
     @declared_attr
     def properties(cls):
-        return cls.many_to_many_relationship('parameter', table_prefix='properties',
-                                             dict_key='name')
+        return cls._create_many_to_many_relationship('parameter', table_prefix='properties',
+                                                     dict_key='name')
 
     @declared_attr
     def interface_templates(cls):
-        return cls.one_to_many_relationship('interface_template', dict_key='name')
+        return cls._create_one_to_many_relationship('interface_template', dict_key='name')
 
     # region foreign keys
 
@@ -1166,7 +1172,7 @@ class RelationshipTemplateBase(TemplateModelMixin):
     # RelationshipTemplate many-to-one to Type
     @declared_attr
     def type_fk(cls):
-        return cls.foreign_key('type', nullable=True)
+        return cls._create_foreign_key('type', nullable=True)
 
     # endregion
 
@@ -1243,7 +1249,7 @@ class CapabilityTemplateBase(TemplateModelMixin):
 
     @declared_attr
     def type(cls):
-        return cls.many_to_one_relationship('type')
+        return cls._create_many_to_one_relationship('type')
 
     description = Column(Text)
     min_occurrences = Column(Integer, default=None)  # optional
@@ -1251,12 +1257,12 @@ class CapabilityTemplateBase(TemplateModelMixin):
 
     @declared_attr
     def valid_source_node_types(cls):
-        return cls.many_to_many_relationship('type', table_prefix='valid_sources')
+        return cls._create_many_to_many_relationship('type', table_prefix='valid_sources')
 
     @declared_attr
     def properties(cls):
-        return cls.many_to_many_relationship('parameter', table_prefix='properties',
-                                             dict_key='name')
+        return cls._create_many_to_many_relationship('parameter', table_prefix='properties',
+                                                     dict_key='name')
 
     # region foreign keys
 
@@ -1266,12 +1272,12 @@ class CapabilityTemplateBase(TemplateModelMixin):
     # CapabilityTemplate many-to-one to Type
     @declared_attr
     def type_fk(cls):
-        return cls.foreign_key('type')
+        return cls._create_foreign_key('type')
 
     # NodeTemplate one-to-many to CapabilityTemplate
     @declared_attr
     def node_template_fk(cls):
-        return cls.foreign_key('node_template')
+        return cls._create_foreign_key('node_template')
 
     # endregion
 
@@ -1375,17 +1381,17 @@ class InterfaceTemplateBase(TemplateModelMixin):
 
     @declared_attr
     def type(cls):
-        return cls.many_to_one_relationship('type')
+        return cls._create_many_to_one_relationship('type')
 
     description = Column(Text)
 
     @declared_attr
     def inputs(cls):
-        return cls.many_to_many_relationship('parameter', table_prefix='inputs',
-                                             dict_key='name')
+        return cls._create_many_to_many_relationship('parameter', table_prefix='inputs',
+                                                     dict_key='name')
     @declared_attr
     def operation_templates(cls):
-        return cls.one_to_many_relationship('operation_template', dict_key='name')
+        return cls._create_one_to_many_relationship('operation_template', dict_key='name')
 
     # region foreign keys
 
@@ -1397,22 +1403,22 @@ class InterfaceTemplateBase(TemplateModelMixin):
     # InterfaceTemplate many-to-one to Type
     @declared_attr
     def type_fk(cls):
-        return cls.foreign_key('type')
+        return cls._create_foreign_key('type')
 
     # NodeTemplate one-to-many to InterfaceTemplate
     @declared_attr
     def node_template_fk(cls):
-        return cls.foreign_key('node_template', nullable=True)
+        return cls._create_foreign_key('node_template', nullable=True)
 
     # GroupTemplate one-to-many to InterfaceTemplate
     @declared_attr
     def group_template_fk(cls):
-        return cls.foreign_key('group_template', nullable=True)
+        return cls._create_foreign_key('group_template', nullable=True)
 
     # RelationshipTemplate one-to-many to InterfaceTemplate
     @declared_attr
     def relationship_template_fk(cls):
-        return cls.foreign_key('relationship_template', nullable=True)
+        return cls._create_foreign_key('relationship_template', nullable=True)
 
     # endregion
 
@@ -1458,14 +1464,14 @@ class OperationTemplateBase(TemplateModelMixin):
     """
     An operation in a :class:`InterfaceTemplate`.
 
-    Operations are executed by an associated :class:`Plugin` via an executor.
+    Operations are executed by an associated :class:`PluginSpecification` via an executor.
 
     :ivar name: Name (unique for the interface or service template)
     :vartype name: basestring
     :ivar description: Human-readable description
     :vartype description: basestring
-    :ivar plugin: Associated plugin
-    :vartype plugin: :class:`Plugin`
+    :ivar plugin_specification: Associated plugin
+    :vartype plugin_specification: :class:`PluginSpecification`
     :ivar implementation: Implementation string (interpreted by the plugin)
     :vartype implementation: basestring
     :ivar dependencies: Dependency strings (interpreted by the plugin)
@@ -1492,16 +1498,16 @@ class OperationTemplateBase(TemplateModelMixin):
     description = Column(Text)
 
     @declared_attr
-    def plugin(cls):
-        return cls.one_to_one_relationship('plugin')
+    def plugin_specification(cls):
+        return cls._create_one_to_one_relationship('plugin_specification')
 
     implementation = Column(Text)
     dependencies = Column(modeling_types.StrictList(item_cls=basestring))
 
     @declared_attr
     def inputs(cls):
-        return cls.many_to_many_relationship('parameter', table_prefix='inputs',
-                                             dict_key='name')
+        return cls._create_many_to_many_relationship('parameter', table_prefix='inputs',
+                                                     dict_key='name')
 
     executor = Column(Text)
     max_retries = Column(Integer)
@@ -1516,17 +1522,17 @@ class OperationTemplateBase(TemplateModelMixin):
     # ServiceTemplate one-to-many to OperationTemplate
     @declared_attr
     def service_template_fk(cls):
-        return cls.foreign_key('service_template', nullable=True)
+        return cls._create_foreign_key('service_template', nullable=True)
 
     # InterfaceTemplate one-to-many to OperationTemplate
     @declared_attr
     def interface_template_fk(cls):
-        return cls.foreign_key('interface_template', nullable=True)
+        return cls._create_foreign_key('interface_template', nullable=True)
 
-    # OperationTemplate one-to-one to Plugin
+    # OperationTemplate one-to-one to PluginSpecification
     @declared_attr
-    def plugin_fk(cls):
-        return cls.foreign_key('plugin', nullable=True)
+    def plugin_specification_fk(cls):
+        return cls._create_foreign_key('plugin_specification', nullable=True)
 
     # endregion
 
@@ -1548,7 +1554,7 @@ class OperationTemplateBase(TemplateModelMixin):
                                      description=utils.deepcopy_with_locators(self.description),
                                      implementation=self.implementation,
                                      dependencies=self.dependencies,
-                                     plugin=self.plugin,
+                                     plugin_specification=self.plugin_specification,
                                      executor=self.executor,
                                      max_retries=self.max_retries,
                                      retry_interval=self.retry_interval,
@@ -1614,7 +1620,7 @@ class ArtifactTemplateBase(TemplateModelMixin):
 
     @declared_attr
     def type(cls):
-        return cls.many_to_one_relationship('type')
+        return cls._create_many_to_one_relationship('type')
 
     description = Column(Text)
     source_path = Column(Text)
@@ -1624,8 +1630,8 @@ class ArtifactTemplateBase(TemplateModelMixin):
 
     @declared_attr
     def properties(cls):
-        return cls.many_to_many_relationship('parameter', table_prefix='properties',
-                                             dict_key='name')
+        return cls._create_many_to_many_relationship('parameter', table_prefix='properties',
+                                                     dict_key='name')
 
     # region foreign keys
 
@@ -1635,12 +1641,12 @@ class ArtifactTemplateBase(TemplateModelMixin):
     # ArtifactTemplate many-to-one to Type
     @declared_attr
     def type_fk(cls):
-        return cls.foreign_key('type')
+        return cls._create_foreign_key('type')
 
     # NodeTemplate one-to-many to ArtifactTemplate
     @declared_attr
     def node_template_fk(cls):
-        return cls.foreign_key('node_template')
+        return cls._create_foreign_key('node_template')
 
     # endregion
 

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/dd5bfa93/aria/orchestrator/workflows/api/task.py
----------------------------------------------------------------------
diff --git a/aria/orchestrator/workflows/api/task.py b/aria/orchestrator/workflows/api/task.py
index d434da8..744d1b4 100644
--- a/aria/orchestrator/workflows/api/task.py
+++ b/aria/orchestrator/workflows/api/task.py
@@ -59,10 +59,7 @@ class OperationTask(BaseTask):
     Represents an operation task in the task_graph
     """
 
-    SOURCE_OPERATION = 'source'
-    TARGET_OPERATION = 'target'
-
-    NAME_FORMAT = '{type}:{id}->{interface}/{operation}'
+    NAME_FORMAT = '{interface}:{operation}@{type}:{id}'
 
     def __init__(self,
                  name,
@@ -73,8 +70,7 @@ class OperationTask(BaseTask):
                  ignore_failure=None,
                  inputs=None,
                  plugin=None,
-                 runs_on=None,
-                 dry=False):
+                 runs_on=None):
         """
         Creates an operation task using the name, details, node instance and any additional kwargs.
 
@@ -84,12 +80,9 @@ class OperationTask(BaseTask):
         """
 
         assert isinstance(actor, (models.Node, models.Relationship))
+        assert (runs_on is None) or (runs_on in models.Task.RUNS_ON)
         super(OperationTask, self).__init__()
 
-        if dry:
-            from ..dry import convert_to_dry
-            plugin, implementation, inputs = convert_to_dry(plugin, implementation, inputs)
-
         # Coerce inputs
         if inputs is None:
             inputs = {}
@@ -131,13 +124,21 @@ class OperationTask(BaseTask):
                 'Could not find operation "{0}" on interface "{1}" for node "{2}"'.format(
                     operation_name, interface_name, node.name))
 
+        plugin = None
+        if operation.plugin_specification:
+            plugin = cls._find_plugin(operation.plugin_specification, kwargs)
+            if plugin is None:
+                raise exceptions.TaskException(
+                    'Could not find plugin of operation "{0}" on interface "{1}" for node "{2}"'
+                    .format(operation_name, interface_name, node.name))
+
         return cls(
             actor=node,
             name=cls.NAME_FORMAT.format(type='node',
-                                        id=node.id,
+                                        id=node.name,
                                         interface=interface_name,
                                         operation=operation_name),
-            plugin=operation.plugin,
+            plugin=plugin,
             implementation=operation.implementation,
             inputs=cls._merge_inputs(operation.inputs, inputs),
             runs_on=models.Task.RUNS_ON_NODE,
@@ -165,13 +166,21 @@ class OperationTask(BaseTask):
                 'Could not find operation "{0}" on interface "{1}" for relationship "{2}"'.format(
                     operation_name, interface_name, relationship.name))
 
+        plugin = None
+        if operation.plugin_specification:
+            plugin = cls._find_plugin(operation.plugin_specification, kwargs)
+            if plugin is None:
+                raise exceptions.TaskException(
+                    'Could not find plugin of operation "{0}" on interface "{1}" for relationship '
+                    '"{2}"'.format(operation_name, interface_name, relationship.name))
+
         return cls(
             actor=relationship,
             name=cls.NAME_FORMAT.format(type='relationship',
-                                        id=relationship.id,
+                                        id=relationship.name,
                                         interface=interface_name,
                                         operation=operation_name),
-            plugin=operation.plugin,
+            plugin=plugin,
             implementation=operation.implementation,
             inputs=cls._merge_inputs(operation.inputs, inputs),
             runs_on=runs_on,
@@ -186,6 +195,13 @@ class OperationTask(BaseTask):
         return None
 
     @classmethod
+    def _find_plugin(cls, plugin_specification, kwargs):
+        workflow_context = kwargs.get('ctx') if kwargs else None
+        if workflow_context is None:
+            workflow_context = context.workflow.current.get()
+        return plugin_specification.find_plugin(workflow_context.model.plugin.list())
+
+    @classmethod
     def _merge_inputs(cls, operation_inputs, override_inputs=None):
         final_inputs = OrderedDict(operation_inputs)
         if override_inputs:

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/dd5bfa93/aria/orchestrator/workflows/builtin/execute_operation.py
----------------------------------------------------------------------
diff --git a/aria/orchestrator/workflows/builtin/execute_operation.py b/aria/orchestrator/workflows/builtin/execute_operation.py
index ed4ada3..348f47a 100644
--- a/aria/orchestrator/workflows/builtin/execute_operation.py
+++ b/aria/orchestrator/workflows/builtin/execute_operation.py
@@ -58,8 +58,7 @@ def execute_operation(
         type_names=type_names))
 
     if run_by_dependency_order:
-        filtered_node_ids = set(node_instance.id
-                                          for node_instance in filtered_nodes)
+        filtered_node_ids = set(node_instance.id for node_instance in filtered_nodes)
         for node in ctx.nodes:
             if node.id not in filtered_node_ids:
                 subgraphs[node.id] = ctx.task_graph(

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/dd5bfa93/aria/orchestrator/workflows/builtin/utils.py
----------------------------------------------------------------------
diff --git a/aria/orchestrator/workflows/builtin/utils.py b/aria/orchestrator/workflows/builtin/utils.py
index 045d47b..84d8293 100644
--- a/aria/orchestrator/workflows/builtin/utils.py
+++ b/aria/orchestrator/workflows/builtin/utils.py
@@ -17,7 +17,7 @@ from ..api.task import OperationTask
 from .. import exceptions
 
 
-def create_node_task(interface_name, operation_name, node, dry=False):
+def create_node_task(interface_name, operation_name, node):
     """
     Returns a new operation task if the operation exists in the node, otherwise returns None.
     """
@@ -25,14 +25,12 @@ def create_node_task(interface_name, operation_name, node, dry=False):
     try:
         return OperationTask.for_node(node=node,
                                       interface_name=interface_name,
-                                      operation_name=operation_name,
-                                      dry=dry)
+                                      operation_name=operation_name)
     except exceptions.TaskException:
-        pass
-    return None
+        return None
 
 
-def create_relationship_tasks(interface_name, operation_name, runs_on, node, dry=False):
+def create_relationship_tasks(interface_name, operation_name, runs_on, node):
     """
     Returns a list of operation tasks for each outbound relationship of the node if the operation
     exists there.
@@ -45,8 +43,7 @@ def create_relationship_tasks(interface_name, operation_name, runs_on, node, dry
                 OperationTask.for_relationship(relationship=relationship,
                                                interface_name=interface_name,
                                                operation_name=operation_name,
-                                               runs_on=runs_on,
-                                               dry=dry))
+                                               runs_on=runs_on))
         except exceptions.TaskException:
             pass
     return sequence

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/dd5bfa93/aria/orchestrator/workflows/builtin/workflows.py
----------------------------------------------------------------------
diff --git a/aria/orchestrator/workflows/builtin/workflows.py b/aria/orchestrator/workflows/builtin/workflows.py
index f19c031..6065343 100644
--- a/aria/orchestrator/workflows/builtin/workflows.py
+++ b/aria/orchestrator/workflows/builtin/workflows.py
@@ -67,128 +67,108 @@ __all__ = (
 
 @workflow(suffix_template='{node.name}')
 def install_node(graph, node, **kwargs):
-    dry = kwargs.get('dry', True)
-
     sequence = []
 
     # Create
     sequence.append(
         create_node_task(
             NORMATIVE_STANDARD_INTERFACE, NORMATIVE_CREATE,
-            node,
-            dry))
+            node))
 
     # Configure
     sequence += \
         create_relationship_tasks(
             NORMATIVE_CONFIGURE_INTERFACE, NORMATIVE_PRE_CONFIGURE_SOURCE,
             Task.RUNS_ON_SOURCE,
-            node,
-            dry)
+            node)
     sequence += \
         create_relationship_tasks(
             NORMATIVE_CONFIGURE_INTERFACE, NORMATIVE_PRE_CONFIGURE_TARGET,
             Task.RUNS_ON_TARGET,
-            node,
-            dry)
+            node)
     sequence.append(
         create_node_task(
             NORMATIVE_STANDARD_INTERFACE, NORMATIVE_CONFIGURE,
-            node,
-            dry))
+            node))
     sequence += \
         create_relationship_tasks(
             NORMATIVE_CONFIGURE_INTERFACE, NORMATIVE_POST_CONFIGURE_SOURCE,
             Task.RUNS_ON_SOURCE,
-            node,
-            dry)
+            node)
     sequence += \
         create_relationship_tasks(
             NORMATIVE_CONFIGURE_INTERFACE, NORMATIVE_POST_CONFIGURE_TARGET,
             Task.RUNS_ON_TARGET,
-            node,
-            dry)
+            node)
 
     # Start
-    sequence += _create_start_tasks(node, dry)
+    sequence += _create_start_tasks(node)
 
     graph.sequence(*sequence)
 
 
 @workflow(suffix_template='{node.name}')
 def uninstall_node(graph, node, **kwargs):
-    dry = kwargs.get('dry', True)
-
     # Stop
-    sequence = _create_stop_tasks(node, dry)
+    sequence = _create_stop_tasks(node)
 
     # Delete
     sequence.append(
         create_node_task(
             NORMATIVE_STANDARD_INTERFACE, NORMATIVE_DELETE,
-            node,
-            dry))
+            node))
 
     graph.sequence(*sequence)
 
 
 @workflow(suffix_template='{node.name}')
 def start_node(graph, node, **kwargs):
-    dry = kwargs.get('dry', True)
-    graph.sequence(*_create_start_tasks(node, dry))
+    graph.sequence(*_create_start_tasks(node))
 
 
 @workflow(suffix_template='{node.name}')
 def stop_node(graph, node, **kwargs):
-    dry = kwargs.get('dry', True)
-    graph.sequence(*_create_stop_tasks(node, dry))
+    graph.sequence(*_create_stop_tasks(node))
 
 
-def _create_start_tasks(node, dry):
+def _create_start_tasks(node):
     sequence = []
     sequence.append(
         create_node_task(
             NORMATIVE_STANDARD_INTERFACE, NORMATIVE_START,
-            node,
-            dry))
+            node))
     sequence += \
         create_relationship_tasks(
             NORMATIVE_CONFIGURE_INTERFACE, NORMATIVE_ADD_SOURCE,
             Task.RUNS_ON_SOURCE,
-            node,
-            dry)
+            node)
     sequence += \
         create_relationship_tasks(
             NORMATIVE_CONFIGURE_INTERFACE, NORMATIVE_ADD_TARGET,
             Task.RUNS_ON_TARGET,
-            node,
-            dry)
+            node)
     sequence += \
         create_relationship_tasks(
             NORMATIVE_CONFIGURE_INTERFACE, NORMATIVE_TARGET_CHANGED,
             Task.RUNS_ON_TARGET,
-            node,
-            dry)
+            node)
     return sequence
 
 
-def _create_stop_tasks(node, dry):
+def _create_stop_tasks(node):
     sequence = []
     sequence += \
         create_relationship_tasks(
             NORMATIVE_CONFIGURE_INTERFACE, NORMATIVE_REMOVE_TARGET,
             Task.RUNS_ON_TARGET,
-            node,
-            dry)
+            node)
     sequence += \
         create_relationship_tasks(
             NORMATIVE_CONFIGURE_INTERFACE, NORMATIVE_TARGET_CHANGED,
             Task.RUNS_ON_TARGET,
-            node,
-            dry)
+            node)
     sequence.append(
         create_node_task(
             NORMATIVE_STANDARD_INTERFACE, NORMATIVE_STOP,
-            node,
-            dry))
+            node))
     return sequence

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/dd5bfa93/aria/orchestrator/workflows/core/task.py
----------------------------------------------------------------------
diff --git a/aria/orchestrator/workflows/core/task.py b/aria/orchestrator/workflows/core/task.py
index 7d8380c..64f2818 100644
--- a/aria/orchestrator/workflows/core/task.py
+++ b/aria/orchestrator/workflows/core/task.py
@@ -113,15 +113,15 @@ class OperationTask(BaseTask):
         base_task_model = model_storage.task.model_cls
         if isinstance(api_task.actor, models.Node):
             context_cls = operation_context.NodeOperationContext
-            task_model_cls = base_task_model.as_node_task
+            create_task_model = base_task_model.for_node
         elif isinstance(api_task.actor, models.Relationship):
             context_cls = operation_context.RelationshipOperationContext
-            task_model_cls = base_task_model.as_relationship_task
+            create_task_model = base_task_model.for_relationship
         else:
             raise RuntimeError('No operation context could be created for {actor.model_cls}'
                                .format(actor=api_task.actor))
 
-        operation_task = task_model_cls(
+        task_model = create_task_model(
             name=api_task.name,
             implementation=api_task.implementation,
             instance=api_task.actor,
@@ -131,20 +131,19 @@ class OperationTask(BaseTask):
             retry_interval=api_task.retry_interval,
             ignore_failure=api_task.ignore_failure,
             plugin=plugin,
-            plugin_name=plugin.name if plugin is not None else 'execution',
             execution=self._workflow_context.execution,
             runs_on=api_task.runs_on
         )
-        self._workflow_context.model.task.put(operation_task)
+        self._workflow_context.model.task.put(task_model)
 
         self._ctx = context_cls(name=api_task.name,
                                 model_storage=self._workflow_context.model,
                                 resource_storage=self._workflow_context.resource,
                                 service_id=self._workflow_context._service_id,
-                                task_id=operation_task.id,
+                                task_id=task_model.id,
                                 actor_id=api_task.actor.id,
                                 workdir=self._workflow_context._workdir)
-        self._task_id = operation_task.id
+        self._task_id = task_model.id
         self._update_fields = None
 
     @contextmanager

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/dd5bfa93/aria/orchestrator/workflows/dry.py
----------------------------------------------------------------------
diff --git a/aria/orchestrator/workflows/dry.py b/aria/orchestrator/workflows/dry.py
deleted file mode 100644
index 766ea0c..0000000
--- a/aria/orchestrator/workflows/dry.py
+++ /dev/null
@@ -1,53 +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.
-
-from threading import RLock
-
-from ..decorators import operation
-from ...utils.collections import OrderedDict
-from ...utils.console import puts, Colored
-from ...utils.formatting import safe_repr
-
-
-_TERMINAL_LOCK = RLock()
-
-
-def convert_to_dry(plugin, implementation, inputs): # pylint: disable=unused-argument
-    dry_implementation = '{0}.{1}'.format(__name__, 'dry_operation')
-    dry_inputs = OrderedDict()
-    dry_inputs['_implementation'] = implementation
-    dry_inputs['_plugin'] = plugin.name if plugin is not None else None
-    return None, dry_implementation, dry_inputs
-
-
-@operation
-def dry_operation(ctx, _plugin, _implementation, **kwargs):
-    with _TERMINAL_LOCK:
-        print ctx.name
-        if hasattr(ctx, 'relationship'):
-            puts('> Relationship: {0} -> {1}'.format(
-                Colored.red(ctx.relationship.source_node.name),
-                Colored.red(ctx.relationship.target_node.name)))
-        else:
-            puts('> Node: {0}'.format(Colored.red(ctx.node.name)))
-        puts('  Operation: {0}'.format(Colored.green(ctx.name)))
-        _dump_implementation(_plugin, _implementation)
-
-
-def _dump_implementation(plugin, implementation):
-    if plugin:
-        puts('  Plugin: {0}'.format(Colored.magenta(plugin, bold=True)))
-    if implementation:
-        puts('  Implementation: {0}'.format(Colored.magenta(safe_repr(implementation))))

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/dd5bfa93/extensions/aria_extension_tosca/simple_v1_0/modeling/__init__.py
----------------------------------------------------------------------
diff --git a/extensions/aria_extension_tosca/simple_v1_0/modeling/__init__.py b/extensions/aria_extension_tosca/simple_v1_0/modeling/__init__.py
index 0206e03..a7b2a11 100644
--- a/extensions/aria_extension_tosca/simple_v1_0/modeling/__init__.py
+++ b/extensions/aria_extension_tosca/simple_v1_0/modeling/__init__.py
@@ -27,7 +27,7 @@ from aria.modeling.models import (Type, ServiceTemplate, NodeTemplate,
                                   RequirementTemplate, RelationshipTemplate, CapabilityTemplate,
                                   GroupTemplate, PolicyTemplate, SubstitutionTemplate,
                                   SubstitutionTemplateMapping, InterfaceTemplate, OperationTemplate,
-                                  ArtifactTemplate, Metadata, Parameter, Plugin)
+                                  ArtifactTemplate, Metadata, Parameter, PluginSpecification)
 
 from ..data_types import coerce_value
 
@@ -81,12 +81,13 @@ def create_service_template_model(context): # pylint: disable=too-many-locals,to
         create_parameter_models_from_values(model.outputs,
                                             topology_template._get_output_values(context))
 
-    # Plugins
+    # Plugin specifications
     policies = context.presentation.get('service_template', 'topology_template', 'policies')
     if policies:
         for policy in policies.itervalues():
             if model.policy_types.get_descendant(policy.type).role == 'plugin':
-                model.plugins.append(create_plugin_model(context, policy))
+                model.plugin_specifications.append(
+                    create_plugin_specification_model(context, policy))
 
     # Node templates
     node_templates = context.presentation.get('service_template', 'topology_template',
@@ -349,7 +350,7 @@ def create_operation_template_model(context, service_template, operation): # pyl
 
     implementation = operation.implementation
     if (implementation is not None) and operation.implementation.primary:
-        model.plugin, model.implementation = \
+        model.plugin_specification, model.implementation = \
             parse_implementation_string(context, service_template, operation.implementation.primary)
 
         dependencies = implementation.dependencies
@@ -427,7 +428,7 @@ def create_substitution_template_model(context, service_template, substitution_m
     return model
 
 
-def create_plugin_model(context, policy):
+def create_plugin_specification_model(context, policy):
     properties = policy.properties
 
     def get(name):
@@ -436,18 +437,16 @@ def create_plugin_model(context, policy):
 
     now = datetime.now()
 
-    model = Plugin(name=policy._name,
-                   archive_name=get('archive_name') or '',
-                   distribution=get('distribution'),
-                   distribution_release=get('distribution_release'),
-                   distribution_version=get('distribution_version'),
-                   package_name=get('package_name') or '',
-                   package_source=get('package_source'),
-                   package_version=get('package_version'),
-                   supported_platform=get('supported_platform'),
-                   supported_py_versions=get('supported_py_versions'),
-                   uploaded_at=now,
-                   wheels=get('wheels') or [])
+    model = PluginSpecification(name=policy._name,
+                                archive_name=get('archive_name') or '',
+                                distribution=get('distribution'),
+                                distribution_release=get('distribution_release'),
+                                distribution_version=get('distribution_version'),
+                                package_name=get('package_name') or '',
+                                package_source=get('package_source'),
+                                package_version=get('package_version'),
+                                supported_platform=get('supported_platform'),
+                                supported_py_versions=get('supported_py_versions'))
 
     return model
 
@@ -665,15 +664,15 @@ def parse_implementation_string(context, service_template, implementation):
     plugin_name = implementation[:index].strip()
     
     if plugin_name == 'execution':
-        plugin = None
+        plugin_specification = None
     else:
-        plugin = None
-        for the_plugin in service_template.plugins:
-            if the_plugin.name == plugin_name:
-                plugin = the_plugin
+        plugin_specification = None
+        for a_plugin_specification in service_template.plugin_specifications:
+            if a_plugin_specification.name == plugin_name:
+                plugin_specification = a_plugin_specification
                 break
-        if plugin is None:
+        if plugin_specification is None:
             raise ValueError('unknown plugin: "{0}"'.format(plugin_name))
 
     implementation = implementation[index+1:].strip()
-    return plugin, implementation
+    return plugin_specification, implementation

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/dd5bfa93/tests/end2end/test_orchestrator.py
----------------------------------------------------------------------
diff --git a/tests/end2end/test_orchestrator.py b/tests/end2end/test_orchestrator.py
index fac6207..f25134f 100644
--- a/tests/end2end/test_orchestrator.py
+++ b/tests/end2end/test_orchestrator.py
@@ -19,6 +19,7 @@ from aria.orchestrator.runner import Runner
 from aria.orchestrator.workflows.builtin import BUILTIN_WORKFLOWS
 from aria.utils.imports import import_fullname
 from aria.utils.collections import OrderedDict
+from aria.cli.dry import convert_to_dry
 
 from tests.parser.service_templates import consume_node_cellar
 
@@ -37,6 +38,8 @@ def test_custom():
 def _workflow(workflow_name):
     context, _ = consume_node_cellar()
 
+    convert_to_dry(context.modeling.instance)
+
     # TODO: this logic will eventually stabilize and be part of the ARIA API,
     # likely somewhere in aria.orchestrator.workflows
     if workflow_name in BUILTIN_WORKFLOWS:

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/dd5bfa93/tests/mock/models.py
----------------------------------------------------------------------
diff --git a/tests/mock/models.py b/tests/mock/models.py
index 716254e..483da7d 100644
--- a/tests/mock/models.py
+++ b/tests/mock/models.py
@@ -191,6 +191,7 @@ def create_execution(service):
 
 def create_plugin(package_name='package', package_version='0.1'):
     return models.Plugin(
+        name='test_plugin',
         archive_name='archive_name',
         distribution='distribution',
         distribution_release='dist_release',
@@ -205,5 +206,20 @@ def create_plugin(package_name='package', package_version='0.1'):
     )
 
 
+def create_plugin_specification(package_name='package', package_version='0.1'):
+    return models.PluginSpecification(
+        name='test_plugin',
+        archive_name='archive_name',
+        distribution='distribution',
+        distribution_release='dist_release',
+        distribution_version='dist_version',
+        package_name=package_name,
+        package_source='source',
+        package_version=package_version,
+        supported_platform='any',
+        supported_py_versions=['python27']
+    )
+
+
 def _dictify(item):
     return dict(((item.name, item),))

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/dd5bfa93/tests/modeling/__init__.py
----------------------------------------------------------------------
diff --git a/tests/modeling/__init__.py b/tests/modeling/__init__.py
new file mode 100644
index 0000000..072ef54
--- /dev/null
+++ b/tests/modeling/__init__.py
@@ -0,0 +1,34 @@
+# 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,
+)
+
+from aria.modeling import (
+    models,
+    types as modeling_types,
+    mixins
+)
+
+
+class MockModel(models.aria_declarative_base, mixins.ModelMixin): #pylint: disable=abstract-method
+    __tablename__ = 'mock_model'
+    model_dict = Column(modeling_types.Dict)
+    model_list = Column(modeling_types.List)
+    value = Column(Integer)
+    name = Column(Text)

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/dd5bfa93/tests/modeling/test_mixins.py
----------------------------------------------------------------------
diff --git a/tests/modeling/test_mixins.py b/tests/modeling/test_mixins.py
new file mode 100644
index 0000000..a60412f
--- /dev/null
+++ b/tests/modeling/test_mixins.py
@@ -0,0 +1,219 @@
+# 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.
+
+import pytest
+
+import sqlalchemy
+
+from aria.storage import (
+    ModelStorage,
+    sql_mapi,
+    exceptions
+)
+from aria import modeling
+
+from ..storage import (
+    release_sqlite_storage,
+    init_inmemory_model_storage
+)
+from . import MockModel
+from ..mock import (
+    models,
+    context as mock_context
+)
+
+
+@pytest.fixture
+def storage():
+    base_storage = ModelStorage(sql_mapi.SQLAlchemyModelAPI,
+                                initiator=init_inmemory_model_storage)
+    base_storage.register(MockModel)
+    yield base_storage
+    release_sqlite_storage(base_storage)
+
+
+@pytest.fixture(scope='module', autouse=True)
+def module_cleanup():
+    modeling.models.aria_declarative_base.metadata.remove(MockModel.__table__)                      # pylint: disable=no-member
+
+
+@pytest.fixture
+def context(tmpdir):
+    ctx = mock_context.simple(str(tmpdir))
+    yield ctx
+    release_sqlite_storage(ctx.model)
+
+
+def test_inner_dict_update(storage):
+    inner_dict = {'inner_value': 1}
+
+    mock_model = MockModel(model_dict={'inner_dict': inner_dict, 'value': 0})
+    storage.mock_model.put(mock_model)
+
+    storage_mm = storage.mock_model.get(mock_model.id)
+    assert storage_mm == mock_model
+
+    storage_mm.model_dict['inner_dict']['inner_value'] = 2
+    storage_mm.model_dict['value'] = -1
+    storage.mock_model.update(storage_mm)
+    storage_mm = storage.mock_model.get(storage_mm.id)
+
+    assert storage_mm.model_dict['inner_dict']['inner_value'] == 2
+    assert storage_mm.model_dict['value'] == -1
+
+
+def test_inner_list_update(storage):
+    mock_model = MockModel(model_list=[0, [1]])
+    storage.mock_model.put(mock_model)
+
+    storage_mm = storage.mock_model.get(mock_model.id)
+    assert storage_mm == mock_model
+
+    storage_mm.model_list[1][0] = 'new_inner_value'
+    storage_mm.model_list[0] = 'new_value'
+    storage.mock_model.update(storage_mm)
+    storage_mm = storage.mock_model.get(storage_mm.id)
+
+    assert storage_mm.model_list[1][0] == 'new_inner_value'
+    assert storage_mm.model_list[0] == 'new_value'
+
+
+def test_model_to_dict(context):
+    service = context.service
+    service = service.to_dict()
+
+    expected_keys = [
+        'description',
+        'created_at',
+        'permalink',
+        'scaling_groups',
+        'updated_at'
+    ]
+
+    for expected_key in expected_keys:
+        assert expected_key in service
+
+
+def test_relationship_model_ordering(context):
+    service = context.model.service.get_by_name(models.SERVICE_NAME)
+    source_node = context.model.node.get_by_name(models.DEPENDENT_NODE_NAME)
+    target_node = context.model.node.get_by_name(models.DEPENDENCY_NODE_NAME)
+
+    new_node_template = modeling.models.NodeTemplate(
+        name='new_node_template',
+        type=source_node.type,
+        default_instances=1,
+        min_instances=1,
+        max_instances=1,
+        service_template=service.service_template
+    )
+
+    new_node = modeling.models.Node(
+        name='new_node',
+        type=source_node.type,
+        runtime_properties={},
+        service=service,
+        version=None,
+        node_template=new_node_template,
+        state='',
+        scaling_groups=[]
+    )
+
+    source_node.outbound_relationships.append(modeling.models.Relationship(
+        source_node=source_node,
+        target_node=new_node,
+    ))
+
+    new_node.outbound_relationships.append(modeling.models.Relationship(                            # pylint: disable=no-member
+        source_node=new_node,
+        target_node=target_node,
+    ))
+
+    context.model.node_template.put(new_node_template)
+    context.model.node.put(new_node)
+    context.model.node.refresh(source_node)
+    context.model.node.refresh(target_node)
+
+    def flip_and_assert(node, direction):
+        """
+        Reversed the order of relationships and assert effects took place.
+        :param node: the node instance to operate on
+        :param direction: the type of relationships to flip (inbound/outbound)
+        :return:
+        """
+        assert direction in ('inbound', 'outbound')
+
+        relationships = getattr(node, direction + '_relationships')
+        assert len(relationships) == 2
+
+        reversed_relationship = list(reversed(relationships))
+        assert relationships != reversed_relationship
+
+        relationships[:] = reversed_relationship
+        context.model.node.update(node)
+        assert relationships == reversed_relationship
+
+    flip_and_assert(source_node, 'outbound')
+    flip_and_assert(target_node, 'inbound')
+
+
+class StrictClass(modeling.models.aria_declarative_base, modeling.mixins.ModelMixin):
+    __tablename__ = 'strict_class'
+
+    strict_dict = sqlalchemy.Column(modeling.types.StrictDict(basestring, basestring))
+    strict_list = sqlalchemy.Column(modeling.types.StrictList(basestring))
+
+
+def test_strict_dict():
+
+    strict_class = StrictClass()
+
+    def assert_strict(sc):
+        with pytest.raises(exceptions.StorageError):
+            sc.strict_dict = {'key': 1}
+
+        with pytest.raises(exceptions.StorageError):
+            sc.strict_dict = {1: 'value'}
+
+        with pytest.raises(exceptions.StorageError):
+            sc.strict_dict = {1: 1}
+
+    assert_strict(strict_class)
+    strict_class.strict_dict = {'key': 'value'}
+    assert strict_class.strict_dict == {'key': 'value'}
+
+    assert_strict(strict_class)
+    with pytest.raises(exceptions.StorageError):
+        strict_class.strict_dict['key'] = 1
+    with pytest.raises(exceptions.StorageError):
+        strict_class.strict_dict[1] = 'value'
+    with pytest.raises(exceptions.StorageError):
+        strict_class.strict_dict[1] = 1
+
+
+def test_strict_list():
+    strict_class = StrictClass()
+
+    def assert_strict(sc):
+        with pytest.raises(exceptions.StorageError):
+            sc.strict_list = [1]
+
+    assert_strict(strict_class)
+    strict_class.strict_list = ['item']
+    assert strict_class.strict_list == ['item']
+
+    assert_strict(strict_class)
+    with pytest.raises(exceptions.StorageError):
+        strict_class.strict_list[0] = 1

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/dd5bfa93/tests/modeling/test_model_storage.py
----------------------------------------------------------------------
diff --git a/tests/modeling/test_model_storage.py b/tests/modeling/test_model_storage.py
new file mode 100644
index 0000000..bb778d4
--- /dev/null
+++ b/tests/modeling/test_model_storage.py
@@ -0,0 +1,102 @@
+# 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.
+
+import pytest
+
+from aria.storage import (
+    ModelStorage,
+    exceptions,
+    sql_mapi
+)
+from aria import (application_model_storage, modeling)
+from ..storage import (release_sqlite_storage, init_inmemory_model_storage)
+
+from . import MockModel
+
+
+@pytest.fixture
+def storage():
+    base_storage = ModelStorage(sql_mapi.SQLAlchemyModelAPI,
+                                initiator=init_inmemory_model_storage)
+    base_storage.register(MockModel)
+    yield base_storage
+    release_sqlite_storage(base_storage)
+
+
+@pytest.fixture(scope='module', autouse=True)
+def module_cleanup():
+    modeling.models.aria_declarative_base.metadata.remove(MockModel.__table__)  #pylint: disable=no-member
+
+
+def test_storage_base(storage):
+    with pytest.raises(AttributeError):
+        storage.non_existent_attribute()
+
+
+def test_model_storage(storage):
+    mock_model = MockModel(value=0, name='model_name')
+    storage.mock_model.put(mock_model)
+
+    assert storage.mock_model.get_by_name('model_name') == mock_model
+
+    assert [mm_from_storage for mm_from_storage in storage.mock_model.iter()] == [mock_model]
+    assert [mm_from_storage for mm_from_storage in storage.mock_model] == [mock_model]
+
+    storage.mock_model.delete(mock_model)
+    with pytest.raises(exceptions.StorageError):
+        storage.mock_model.get(mock_model.id)
+
+
+def test_application_storage_factory():
+    storage = application_model_storage(sql_mapi.SQLAlchemyModelAPI,
+                                        initiator=init_inmemory_model_storage)
+
+    assert storage.service_template
+    assert storage.node_template
+    assert storage.group_template
+    assert storage.policy_template
+    assert storage.substitution_template
+    assert storage.substitution_template_mapping
+    assert storage.requirement_template
+    assert storage.relationship_template
+    assert storage.capability_template
+    assert storage.interface_template
+    assert storage.operation_template
+    assert storage.artifact_template
+
+    assert storage.service
+    assert storage.node
+    assert storage.group
+    assert storage.policy
+    assert storage.substitution
+    assert storage.substitution_mapping
+    assert storage.relationship
+    assert storage.capability
+    assert storage.interface
+    assert storage.operation
+    assert storage.artifact
+
+    assert storage.execution
+    assert storage.service_update
+    assert storage.service_update_step
+    assert storage.service_modification
+    assert storage.plugin
+    assert storage.task
+
+    assert storage.parameter
+    assert storage.type
+    assert storage.metadata
+
+    release_sqlite_storage(storage)


[2/8] incubator-ariatosca git commit: Separate plugin specification form plugin; move dry support to CLI; various renames and refactorings

Posted by mx...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/dd5bfa93/tests/storage/test_models.py
----------------------------------------------------------------------
diff --git a/tests/storage/test_models.py b/tests/storage/test_models.py
deleted file mode 100644
index c80659b..0000000
--- a/tests/storage/test_models.py
+++ /dev/null
@@ -1,833 +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.
-
-from datetime import datetime
-from contextlib import contextmanager
-
-import pytest
-
-from aria import application_model_storage
-from aria.storage import (
-    exceptions,
-    sql_mapi,
-)
-from aria.modeling.models import (
-    ServiceTemplate,
-    Service,
-    ServiceUpdate,
-    ServiceUpdateStep,
-    ServiceModification,
-    Execution,
-    Task,
-    Plugin,
-    Relationship,
-    NodeTemplate,
-    Node,
-    Parameter,
-    Type
-)
-
-from tests import mock
-from ..storage import release_sqlite_storage, init_inmemory_model_storage
-
-
-@contextmanager
-def sql_storage(storage_func):
-    storage = None
-    try:
-        storage = storage_func()
-        yield storage
-    finally:
-        if storage:
-            release_sqlite_storage(storage)
-
-
-def _empty_storage():
-    return application_model_storage(sql_mapi.SQLAlchemyModelAPI,
-                                     initiator=init_inmemory_model_storage)
-
-
-def _service_template_storage():
-    storage = _empty_storage()
-    service_template = mock.models.create_service_template()
-    storage.service_template.put(service_template)
-    storage.type.put(Type(variant='node'))
-    return storage
-
-
-def _service_storage():
-    storage = _service_template_storage()
-    service = mock.models.create_service(
-        storage.service_template.get_by_name(mock.models.SERVICE_TEMPLATE_NAME))
-    storage.service.put(service)
-    return storage
-
-
-def _service_update_storage():
-    storage = _service_storage()
-    service_update = ServiceUpdate(
-        service=storage.service.list()[0],
-        created_at=now,
-        service_plan={},
-    )
-    storage.service_update.put(service_update)
-    return storage
-
-
-def _node_template_storage():
-    storage = _service_storage()
-    service_template = storage.service_template.list()[0]
-    node_template = mock.models.create_dependency_node_template(service_template)
-    storage.node_template.put(node_template)
-    return storage
-
-
-def _nodes_storage():
-    storage = _nodes_storage() # ???
-    service = storage.service.get_by_name(mock.models.SERVICE_NAME)
-    dependent_node_template = storage.node_template.get_by_name(mock.models.DEPENDENT_NODE_NAME)
-    dependency_node_template = storage.node_template.get_by_name(mock.models.DEPENDENCY_NODE_NAME)
-    dependency_node = mock.models.create_dependency_node(dependency_node_template, service)
-    dependent_node = mock.models.create_dependent_node(dependent_node_template, service)
-    storage.node.put(dependency_node)
-    storage.node.put(dependent_node)
-    return storage
-
-
-def _execution_storage():
-    storage = _service_storage()
-    execution = mock.models.create_execution(storage.service.list()[0])
-    plugin = mock.models.create_plugin()
-    storage.execution.put(execution)
-    storage.plugin.put(plugin)
-    return storage
-
-
-@pytest.fixture
-def empty_storage():
-    with sql_storage(_empty_storage) as storage:
-        yield storage
-
-
-@pytest.fixture
-def service_template_storage():
-    with sql_storage(_service_template_storage) as storage:
-        yield storage
-
-
-@pytest.fixture
-def service_storage():
-    with sql_storage(_service_storage) as storage:
-        yield storage
-
-
-@pytest.fixture
-def service_update_storage():
-    with sql_storage(_service_update_storage) as storage:
-        yield storage
-
-
-@pytest.fixture
-def node_template_storage():
-    with sql_storage(_node_template_storage) as storage:
-        yield storage
-
-
-@pytest.fixture
-def nodes_storage():
-    with sql_storage(_nodes_storage) as storage:
-        yield storage
-
-
-@pytest.fixture
-def execution_storage():
-    with sql_storage(_execution_storage) as storage:
-        yield storage
-
-
-m_cls = type('MockClass')
-now = datetime.utcnow()
-
-
-def _test_model(is_valid, storage, model_cls, model_kwargs):
-    if is_valid:
-        model = model_cls(**model_kwargs)
-        getattr(storage, model_cls.__modelname__).put(model)
-        return model
-    else:
-        with pytest.raises((exceptions.StorageError, TypeError),):
-            getattr(storage, model_cls.__modelname__).put(model_cls(**model_kwargs))
-
-
-class TestServiceTemplate(object):
-
-    @pytest.mark.parametrize(
-        'is_valid, description, created_at, updated_at, main_file_name',
-        [
-            (True, 'description', now, now, '/path'),
-            (False, {}, now, now, '/path'),
-            (False, 'description', 'error', now, '/path'),
-            (False, 'description', now, 'error', '/path'),
-            (False, 'description', now, now, {}),
-            (True, 'description', now, now, '/path'),
-        ]
-    )
-
-    def test_service_template_model_creation(self, empty_storage, is_valid, description, created_at,
-                                             updated_at, main_file_name):
-        _test_model(is_valid=is_valid,
-                    storage=empty_storage,
-                    model_cls=ServiceTemplate,
-                    model_kwargs=dict(
-                        description=description,
-                        created_at=created_at,
-                        updated_at=updated_at,
-                        main_file_name=main_file_name)
-                   )
-
-
-class TestService(object):
-
-    @pytest.mark.parametrize(
-        'is_valid, name, created_at, description, inputs, permalink, '
-        'outputs, scaling_groups, updated_at',
-        [
-            (False, m_cls, now, 'desc', {}, 'perlnk', {}, {}, now),
-            (False, 'name', m_cls, 'desc', {}, 'perlnk', {}, {}, now),
-            (False, 'name', now, m_cls, {}, 'perlnk', {}, {}, now),
-            (False, 'name', now, 'desc', {}, m_cls, {}, {}, now),
-            (False, 'name', now, 'desc', {}, 'perlnk', m_cls, {}, now),
-            (False, 'name', now, 'desc', {}, 'perlnk', {}, m_cls, now),
-            (False, 'name', now, 'desc', {}, 'perlnk', {}, {}, m_cls),
-
-            (True, 'name', now, 'desc', {}, 'perlnk', {}, {}, now),
-            (True, None, now, 'desc', {}, 'perlnk', {}, {}, now),
-            (True, 'name', now, 'desc', {}, 'perlnk', {}, {}, now),
-            (True, 'name', now, None, {}, 'perlnk', {}, {}, now),
-            (True, 'name', now, 'desc', {}, 'perlnk', {}, {}, now),
-            (True, 'name', now, 'desc', {}, None, {}, {}, now),
-            (True, 'name', now, 'desc', {}, 'perlnk', {}, {}, now),
-            (True, 'name', now, 'desc', {}, 'perlnk', {}, None, now),
-            (True, 'name', now, 'desc', {}, 'perlnk', {}, {}, None),
-            (True, 'name', now, 'desc', {}, 'perlnk', {}, {}, now),
-        ]
-    )
-    def test_service_model_creation(self, service_storage, is_valid, name, created_at, description,
-                                    inputs, permalink, outputs, scaling_groups, updated_at):
-        service = _test_model(
-            is_valid=is_valid,
-            storage=service_storage,
-            model_cls=Service,
-            model_kwargs=dict(
-                name=name,
-                service_template=service_storage.service_template.list()[0],
-                created_at=created_at,
-                description=description,
-                inputs=inputs,
-                permalink=permalink,
-                outputs=outputs,
-                scaling_groups=scaling_groups,
-                updated_at=updated_at
-            ))
-        if is_valid:
-            assert service.service_template == \
-                   service_storage.service_template.list()[0]
-
-
-class TestExecution(object):
-
-    @pytest.mark.parametrize(
-        'is_valid, created_at, started_at, ended_at, error, is_system_workflow, parameters, '
-        'status, workflow_name',
-        [
-            (False, m_cls, now, now, 'error', False, {}, Execution.STARTED, 'wf_name'),
-            (False, now, m_cls, now, 'error', False, {}, Execution.STARTED, 'wf_name'),
-            (False, now, now, m_cls, 'error', False, {}, Execution.STARTED, 'wf_name'),
-            (False, now, now, now, m_cls, False, {}, Execution.STARTED, 'wf_name'),
-            (False, now, now, now, 'error', False, m_cls, Execution.STARTED, 'wf_name'),
-            (False, now, now, now, 'error', False, {}, m_cls, 'wf_name'),
-            (False, now, now, now, 'error', False, {}, Execution.STARTED, m_cls),
-
-            (True, now, now, now, 'error', False, {}, Execution.STARTED, 'wf_name'),
-            (True, now, None, now, 'error', False, {}, Execution.STARTED, 'wf_name'),
-            (True, now, now, None, 'error', False, {}, Execution.STARTED, 'wf_name'),
-            (True, now, now, now, None, False, {}, Execution.STARTED, 'wf_name'),
-            (True, now, now, now, 'error', False, None, Execution.STARTED, 'wf_name'),
-        ]
-    )
-    def test_execution_model_creation(self, service_storage, is_valid, created_at, started_at,
-                                      ended_at, error, is_system_workflow, parameters, status,
-                                      workflow_name):
-        execution = _test_model(
-            is_valid=is_valid,
-            storage=service_storage,
-            model_cls=Execution,
-            model_kwargs=dict(
-                service=service_storage.service.list()[0],
-                created_at=created_at,
-                started_at=started_at,
-                ended_at=ended_at,
-                error=error,
-                is_system_workflow=is_system_workflow,
-                parameters=parameters,
-                status=status,
-                workflow_name=workflow_name,
-            ))
-        if is_valid:
-            assert execution.service == service_storage.service.list()[0]
-            assert execution.service_template == service_storage.service_template.list()[0]
-
-    def test_execution_status_transition(self):
-        def create_execution(status):
-            execution = Execution(
-                id='e_id',
-                workflow_name='w_name',
-                status=status,
-                parameters={},
-                created_at=now,
-            )
-            return execution
-
-        valid_transitions = {
-            Execution.PENDING: [Execution.STARTED,
-                                Execution.CANCELLED,
-                                Execution.PENDING],
-            Execution.STARTED: [Execution.FAILED,
-                                Execution.TERMINATED,
-                                Execution.CANCELLED,
-                                Execution.CANCELLING,
-                                Execution.STARTED],
-            Execution.CANCELLING: [Execution.FAILED,
-                                   Execution.TERMINATED,
-                                   Execution.CANCELLED,
-                                   Execution.CANCELLING],
-            Execution.FAILED: [Execution.FAILED],
-            Execution.TERMINATED: [Execution.TERMINATED],
-            Execution.CANCELLED: [Execution.CANCELLED]
-        }
-
-        invalid_transitions = {
-            Execution.PENDING: [Execution.FAILED,
-                                Execution.TERMINATED,
-                                Execution.CANCELLING],
-            Execution.STARTED: [Execution.PENDING],
-            Execution.CANCELLING: [Execution.PENDING,
-                                   Execution.STARTED],
-            Execution.FAILED: [Execution.PENDING,
-                               Execution.STARTED,
-                               Execution.TERMINATED,
-                               Execution.CANCELLED,
-                               Execution.CANCELLING],
-            Execution.TERMINATED: [Execution.PENDING,
-                                   Execution.STARTED,
-                                   Execution.FAILED,
-                                   Execution.CANCELLED,
-                                   Execution.CANCELLING],
-            Execution.CANCELLED: [Execution.PENDING,
-                                  Execution.STARTED,
-                                  Execution.FAILED,
-                                  Execution.TERMINATED,
-                                  Execution.CANCELLING],
-        }
-
-        for current_status, valid_transitioned_statues in valid_transitions.items():
-            for transitioned_status in valid_transitioned_statues:
-                execution = create_execution(current_status)
-                execution.status = transitioned_status
-
-        for current_status, invalid_transitioned_statues in invalid_transitions.items():
-            for transitioned_status in invalid_transitioned_statues:
-                execution = create_execution(current_status)
-                with pytest.raises(ValueError):
-                    execution.status = transitioned_status
-
-
-class TestServiceUpdate(object):
-    @pytest.mark.parametrize(
-        'is_valid, created_at, service_plan, service_update_nodes, '
-        'service_update_service, service_update_node_templates, '
-        'modified_entity_ids, state',
-        [
-            (False, m_cls, {}, {}, {}, [], {}, 'state'),
-            (False, now, m_cls, {}, {}, [], {}, 'state'),
-            (False, now, {}, m_cls, {}, [], {}, 'state'),
-            (False, now, {}, {}, m_cls, [], {}, 'state'),
-            (False, now, {}, {}, {}, m_cls, {}, 'state'),
-            (False, now, {}, {}, {}, [], m_cls, 'state'),
-            (False, now, {}, {}, {}, [], {}, m_cls),
-
-            (True, now, {}, {}, {}, [], {}, 'state'),
-            (True, now, {}, None, {}, [], {}, 'state'),
-            (True, now, {}, {}, None, [], {}, 'state'),
-            (True, now, {}, {}, {}, None, {}, 'state'),
-            (True, now, {}, {}, {}, [], None, 'state'),
-            (True, now, {}, {}, {}, [], {}, None),
-        ]
-    )
-    def test_service_update_model_creation(self, service_storage, is_valid, created_at,
-                                           service_plan, service_update_nodes,
-                                           service_update_service, service_update_node_templates,
-                                           modified_entity_ids, state):
-        service_update = _test_model(
-            is_valid=is_valid,
-            storage=service_storage,
-            model_cls=ServiceUpdate,
-            model_kwargs=dict(
-                service=service_storage.service.list()[0],
-                created_at=created_at,
-                service_plan=service_plan,
-                service_update_nodes=service_update_nodes,
-                service_update_service=service_update_service,
-                service_update_node_templates=service_update_node_templates,
-                modified_entity_ids=modified_entity_ids,
-                state=state
-            ))
-        if is_valid:
-            assert service_update.service == \
-                   service_storage.service.list()[0]
-
-
-class TestServiceUpdateStep(object):
-
-    @pytest.mark.parametrize(
-        'is_valid, action, entity_id, entity_type',
-        [
-            (False, m_cls, 'id', ServiceUpdateStep.ENTITY_TYPES.NODE),
-            (False, ServiceUpdateStep.ACTION_TYPES.ADD, m_cls,
-             ServiceUpdateStep.ENTITY_TYPES.NODE),
-            (False, ServiceUpdateStep.ACTION_TYPES.ADD, 'id', m_cls),
-
-            (True, ServiceUpdateStep.ACTION_TYPES.ADD, 'id',
-             ServiceUpdateStep.ENTITY_TYPES.NODE)
-        ]
-    )
-    def test_service_update_step_model_creation(self, service_update_storage, is_valid, action,
-                                                entity_id, entity_type):
-        service_update_step = _test_model(
-            is_valid=is_valid,
-            storage=service_update_storage,
-            model_cls=ServiceUpdateStep,
-            model_kwargs=dict(
-                service_update=
-                service_update_storage.service_update.list()[0],
-                action=action,
-                entity_id=entity_id,
-                entity_type=entity_type
-            ))
-        if is_valid:
-            assert service_update_step.service_update == \
-                   service_update_storage.service_update.list()[0]
-
-    def test_service_update_step_order(self):
-        add_node = ServiceUpdateStep(
-            id='add_step',
-            action='add',
-            entity_type='node',
-            entity_id='node_id')
-
-        modify_node = ServiceUpdateStep(
-            id='modify_step',
-            action='modify',
-            entity_type='node',
-            entity_id='node_id')
-
-        remove_node = ServiceUpdateStep(
-            id='remove_step',
-            action='remove',
-            entity_type='node',
-            entity_id='node_id')
-
-        for step in (add_node, modify_node, remove_node):
-            assert hash((step.id, step.entity_id)) == hash(step)
-
-        assert remove_node < modify_node < add_node
-        assert not remove_node > modify_node > add_node
-
-        add_rel = ServiceUpdateStep(
-            id='add_step',
-            action='add',
-            entity_type='relationship',
-            entity_id='relationship_id')
-
-        remove_rel = ServiceUpdateStep(
-            id='remove_step',
-            action='remove',
-            entity_type='relationship',
-            entity_id='relationship_id')
-
-        assert remove_rel < remove_node < add_node < add_rel
-        assert not add_node < None
-
-
-class TestServiceModification(object):
-    @pytest.mark.parametrize(
-        'is_valid, context, created_at, ended_at, modified_node_templates, nodes, status',
-        [
-            (False, m_cls, now, now, {}, {}, ServiceModification.STARTED),
-            (False, {}, m_cls, now, {}, {}, ServiceModification.STARTED),
-            (False, {}, now, m_cls, {}, {}, ServiceModification.STARTED),
-            (False, {}, now, now, m_cls, {}, ServiceModification.STARTED),
-            (False, {}, now, now, {}, m_cls, ServiceModification.STARTED),
-            (False, {}, now, now, {}, {}, m_cls),
-
-            (True, {}, now, now, {}, {}, ServiceModification.STARTED),
-            (True, {}, now, None, {}, {}, ServiceModification.STARTED),
-            (True, {}, now, now, None, {}, ServiceModification.STARTED),
-            (True, {}, now, now, {}, None, ServiceModification.STARTED),
-        ]
-    )
-    def test_service_modification_model_creation(self, service_storage, is_valid, context,
-                                                 created_at, ended_at, modified_node_templates,
-                                                 nodes, status):
-        service_modification = _test_model(
-            is_valid=is_valid,
-            storage=service_storage,
-            model_cls=ServiceModification,
-            model_kwargs=dict(
-                service=service_storage.service.list()[0],
-                context=context,
-                created_at=created_at,
-                ended_at=ended_at,
-                modified_node_templates=modified_node_templates,
-                nodes=nodes,
-                status=status,
-            ))
-        if is_valid:
-            assert service_modification.service == \
-                   service_storage.service.list()[0]
-
-
-class TestNodeTemplate(object):
-    @pytest.mark.parametrize(
-        'is_valid, name, default_instances, max_instances, min_instances, plugins, properties',
-        [
-            (False, m_cls, 1, 1, 1, [], {}),
-            (False, 'name', m_cls, 1, 1, [], {}),
-            (False, 'name', 1, m_cls, 1, [], {}),
-            (False, 'name', 1, 1, m_cls, [], {}),
-            (False, 'name', 1, 1, 1, m_cls, {}),
-            (False, 'name', 1, 1, 1, None, {}),
-
-            (True, 'name', 1, 1, 1, [], {}),
-        ]
-    )
-    def test_node_template_model_creation(self, service_storage, is_valid, name, default_instances,
-                                          max_instances, min_instances, plugins, properties):
-        node_template = _test_model(
-            is_valid=is_valid,
-            storage=service_storage,
-            model_cls=NodeTemplate,
-            model_kwargs=dict(
-                name=name,
-                type=service_storage.type.list()[0],
-                default_instances=default_instances,
-                max_instances=max_instances,
-                min_instances=min_instances,
-                plugins=plugins,
-                properties=properties,
-                service_template=service_storage.service_template.list()[0]
-            ))
-        if is_valid:
-            assert node_template.service_template == \
-                   service_storage.service_template.list()[0]
-
-
-class TestNode(object):
-    @pytest.mark.parametrize(
-        'is_valid, name, runtime_properties, scaling_groups, state, version',
-        [
-            (False, m_cls, {}, [], 'state', 1),
-            (False, 'name', m_cls, [], 'state', 1),
-            (False, 'name', {}, m_cls, 'state', 1),
-            (False, 'name', {}, [], m_cls, 1),
-            (False, m_cls, {}, [], 'state', m_cls),
-
-            (True, 'name', {}, [], 'state', 1),
-            (True, None, {}, [], 'state', 1),
-            (True, 'name', None, [], 'state', 1),
-            (True, 'name', {}, None, 'state', 1),
-            (True, 'name', {}, [], 'state', None),
-        ]
-    )
-    def test_node_model_creation(self, node_template_storage, is_valid, name, runtime_properties,
-                                 scaling_groups, state, version):
-        node = _test_model(
-            is_valid=is_valid,
-            storage=node_template_storage,
-            model_cls=Node,
-            model_kwargs=dict(
-                node_template=node_template_storage.node_template.list()[0],
-                type=node_template_storage.type.list()[0],
-                name=name,
-                runtime_properties=runtime_properties,
-                scaling_groups=scaling_groups,
-                state=state,
-                version=version,
-                service=node_template_storage.service.list()[0]
-            ))
-        if is_valid:
-            assert node.node_template == node_template_storage.node_template.list()[0]
-            assert node.service == \
-                   node_template_storage.service.list()[0]
-
-
-class TestNodeInstanceIP(object):
-
-    ip = '1.1.1.1'
-
-    def test_ip_on_none_hosted_node(self, service_storage):
-        node_template = self._node_template(service_storage, ip='not considered')
-        node = self._node(service_storage,
-                          node_template,
-                          is_host=False,
-                          ip='not considered')
-        assert node.ip is None
-
-    def test_property_ip_on_host_node(self, service_storage):
-        node_template = self._node_template(service_storage, ip=self.ip)
-        node = self._node(service_storage, node_template, is_host=True, ip=None)
-        assert node.ip == self.ip
-
-    def test_runtime_property_ip_on_host_node(self, service_storage):
-        node_template = self._node_template(service_storage, ip='not considered')
-        node = self._node(service_storage, node_template, is_host=True, ip=self.ip)
-        assert node.ip == self.ip
-
-    def test_no_ip_configured_on_host_node(self, service_storage):
-        node_template = self._node_template(service_storage, ip=None)
-        node = self._node(service_storage, node_template, is_host=True, ip=None)
-        assert node.ip is None
-
-    def test_runtime_property_on_hosted_node(self, service_storage):
-        host_node_template = self._node_template(service_storage, ip=None)
-        host_node = self._node(service_storage,
-                               host_node_template,
-                               is_host=True,
-                               ip=self.ip)
-        node_template = self._node_template(service_storage, ip=None)
-        node = self._node(service_storage,
-                          node_template,
-                          is_host=False,
-                          ip=None,
-                          host_fk=host_node.id)
-        assert node.ip == self.ip
-
-    def _node_template(self, storage, ip):
-        kwargs = dict(
-            name='node_template',
-            type=storage.type.list()[0],
-            default_instances=1,
-            max_instances=1,
-            min_instances=1,
-            service_template=storage.service_template.list()[0]
-        )
-        if ip:
-            kwargs['properties'] = {'ip': Parameter(name='ip', type_name='string', value=ip)}
-        node = NodeTemplate(**kwargs)
-        storage.node_template.put(node)
-        return node
-
-    def _node(self, storage, node, is_host, ip, host_fk=None):
-        kwargs = dict(
-            name='node',
-            node_template=node,
-            type=storage.type.list()[0],
-            runtime_properties={},
-            state='',
-            service=storage.service.list()[0]
-        )
-        if ip:
-            kwargs['runtime_properties']['ip'] = ip
-        if is_host:
-            kwargs['host_fk'] = 1
-        elif host_fk:
-            kwargs['host_fk'] = host_fk
-        node = Node(**kwargs)
-        storage.node.put(node)
-        return node
-
-
-@pytest.mark.skip('Should be reworked into relationship')
-class TestRelationship(object):
-    def test_relationship_model_creation(self, nodes_storage):
-        nodes = nodes_storage.node
-        source_node = nodes.get_by_name(mock.models.DEPENDENT_NODE_NAME)
-        target_node = nodes.get_by_name(mock.models.DEPENDENCY_NODE_NAME)
-
-        relationship = mock.models.create_relationship(
-            source=source_node,
-            target=nodes_storage.node.get_by_name(mock.models.DEPENDENCY_NODE_NAME)
-        )
-        nodes_storage.relationship.put(relationship)
-
-        relationship_instance = _test_model(
-            is_valid=True,
-            storage=nodes_storage,
-            model_cls=Relationship,
-            model_kwargs=dict(
-                relationship=relationship,
-                source_node=source_node,
-                target_node=target_node
-            ))
-        assert relationship_instance.relationship == relationship
-        assert relationship_instance.source_node == source_node
-        assert relationship_instance.target_node == target_node
-
-
-class TestPlugin(object):
-    @pytest.mark.parametrize(
-        'is_valid, archive_name, distribution, distribution_release, '
-        'distribution_version, package_name, package_source, '
-        'package_version, supported_platform, supported_py_versions, uploaded_at, wheels',
-        [
-            (False, m_cls, 'dis_name', 'dis_rel', 'dis_ver', 'pak_name', 'pak_src', 'pak_ver',
-             'sup_plat', [], now, []),
-            (False, 'arc_name', m_cls, 'dis_rel', 'dis_ver', 'pak_name', 'pak_src', 'pak_ver',
-             'sup_plat', [], now, []),
-            (False, 'arc_name', 'dis_name', m_cls, 'dis_ver', 'pak_name', 'pak_src', 'pak_ver',
-             'sup_plat', [], now, []),
-            (False, 'arc_name', 'dis_name', 'dis_rel', m_cls, 'pak_name', 'pak_src', 'pak_ver',
-             'sup_plat', [], now, []),
-            (False, 'arc_name', 'dis_name', 'dis_rel', 'dis_ver', m_cls, 'pak_src', 'pak_ver',
-             'sup_plat', [], now, []),
-            (False, 'arc_name', 'dis_name', 'dis_rel', 'dis_ver', 'pak_name', m_cls, 'pak_ver',
-             'sup_plat', [], now, []),
-            (False, 'arc_name', 'dis_name', 'dis_rel', 'dis_ver', 'pak_name', 'pak_src', m_cls,
-             'sup_plat', [], now, []),
-            (False, 'arc_name', 'dis_name', 'dis_rel', 'dis_ver', 'pak_name', 'pak_src',
-             'pak_ver', m_cls, [], now, []),
-            (False, 'arc_name', 'dis_name', 'dis_rel', 'dis_ver', 'pak_name', 'pak_src',
-             'pak_ver', 'sup_plat', m_cls, now, []),
-            (False, 'arc_name', 'dis_name', 'dis_rel', 'dis_ver', 'pak_name', 'pak_src',
-             'pak_ver', 'sup_plat', [], m_cls, []),
-            (False, 'arc_name', 'dis_name', 'dis_rel', 'dis_ver', 'pak_name', 'pak_src',
-             'pak_ver', 'sup_plat', [], now, m_cls),
-
-            (True, 'arc_name', 'dis_name', 'dis_rel', 'dis_ver', 'pak_name', 'pak_src', 'pak_ver',
-             'sup_plat', [], now, []),
-            (True, 'arc_name', None, 'dis_rel', 'dis_ver', 'pak_name', 'pak_src', 'pak_ver',
-             'sup_plat', [], now, []),
-            (True, 'arc_name', 'dis_name', None, 'dis_ver', 'pak_name', 'pak_src', 'pak_ver',
-             'sup_plat', [], now, []),
-            (True, 'arc_name', 'dis_name', 'dis_rel', None, 'pak_name', 'pak_src', 'pak_ver',
-             'sup_plat', [], now, []),
-            (True, 'arc_name', 'dis_name', 'dis_rel', 'dis_ver', 'pak_name', 'pak_src',
-             'pak_ver', 'sup_plat', [], now, []),
-            (True, 'arc_name', 'dis_name', 'dis_rel', 'dis_ver', 'pak_name', None, 'pak_ver',
-             'sup_plat', [], now, []),
-            (True, 'arc_name', 'dis_name', 'dis_rel', 'dis_ver', 'pak_name', 'pak_src', None,
-             'sup_plat', [], now, []),
-            (True, 'arc_name', 'dis_name', 'dis_rel', 'dis_ver', 'pak_name', 'pak_src',
-             'pak_ver', None, [], now, []),
-            (True, 'arc_name', 'dis_name', 'dis_rel', 'dis_ver', 'pak_name', 'pak_src',
-             'pak_ver', 'sup_plat', None, now, []),
-            (True, 'arc_name', 'dis_name', 'dis_rel', 'dis_ver', 'pak_name', 'pak_src',
-             'pak_ver', 'sup_plat', [], now, []),
-        ]
-    )
-    def test_plugin_model_creation(self, empty_storage, is_valid, archive_name, distribution,
-                                   distribution_release, distribution_version, package_name,
-                                   package_source, package_version, supported_platform,
-                                   supported_py_versions, uploaded_at, wheels):
-        _test_model(is_valid=is_valid,
-                    storage=empty_storage,
-                    model_cls=Plugin,
-                    model_kwargs=dict(
-                        archive_name=archive_name,
-                        distribution=distribution,
-                        distribution_release=distribution_release,
-                        distribution_version=distribution_version,
-                        package_name=package_name,
-                        package_source=package_source,
-                        package_version=package_version,
-                        supported_platform=supported_platform,
-                        supported_py_versions=supported_py_versions,
-                        uploaded_at=uploaded_at,
-                        wheels=wheels,
-                    ))
-
-
-class TestTask(object):
-
-    @pytest.mark.parametrize(
-        'is_valid, status, due_at, started_at, ended_at, max_attempts, retry_count, '
-        'retry_interval, ignore_failure, name, operation_mapping, inputs, plugin_id',
-        [
-            (False, m_cls, now, now, now, 1, 1, 1, True, 'name', 'map', {}, '1'),
-            (False, Task.STARTED, m_cls, now, now, 1, 1, 1, True, 'name', 'map', {}, '1'),
-            (False, Task.STARTED, now, m_cls, now, 1, 1, 1, True, 'name', 'map', {}, '1'),
-            (False, Task.STARTED, now, now, m_cls, 1, 1, 1, True, 'name', 'map', {}, '1'),
-            (False, Task.STARTED, now, now, now, m_cls, 1, 1, True, 'name', 'map', {}, '1'),
-            (False, Task.STARTED, now, now, now, 1, m_cls, 1, True, 'name', 'map', {}, '1'),
-            (False, Task.STARTED, now, now, now, 1, 1, m_cls, True, 'name', 'map', {}, '1'),
-            (False, Task.STARTED, now, now, now, 1, 1, 1, True, m_cls, 'map', {}, '1'),
-            (False, Task.STARTED, now, now, now, 1, 1, 1, True, 'name', m_cls, {}, '1'),
-            (False, Task.STARTED, now, now, now, 1, 1, 1, True, 'name', 'map', m_cls, '1'),
-            (False, Task.STARTED, now, now, now, 1, 1, 1, True, 'name', 'map', {}, m_cls),
-            (False, Task.STARTED, now, now, now, 1, 1, 1, True, 'name', 'map', None, '1'),
-
-            (True, Task.STARTED, now, now, now, 1, 1, 1, True, 'name', 'map', {}, '1'),
-            (True, Task.STARTED, None, now, now, 1, 1, 1, True, 'name', 'map', {}, '1'),
-            (True, Task.STARTED, now, None, now, 1, 1, 1, True, 'name', 'map', {}, '1'),
-            (True, Task.STARTED, now, now, None, 1, 1, 1, True, 'name', 'map', {}, '1'),
-            (True, Task.STARTED, now, now, now, 1, None, 1, True, 'name', 'map', {}, '1'),
-            (True, Task.STARTED, now, now, now, 1, 1, None, True, 'name', 'map', {}, '1'),
-            (True, Task.STARTED, now, now, now, 1, 1, 1, None, 'name', 'map', {}, '1'),
-            (True, Task.STARTED, now, now, now, 1, 1, 1, True, None, 'map', {}, '1'),
-            (True, Task.STARTED, now, now, now, 1, 1, 1, True, 'name', None, {}, '1'),
-            (True, Task.STARTED, now, now, now, 1, 1, 1, True, 'name', 'map', {}, None),
-        ]
-    )
-    def test_task_model_creation(self, execution_storage, is_valid, status, due_at, started_at,
-                                 ended_at, max_attempts, retry_count, retry_interval,
-                                 ignore_failure, name, operation_mapping, inputs, plugin_id):
-        task = _test_model(
-            is_valid=is_valid,
-            storage=execution_storage,
-            model_cls=Task,
-            model_kwargs=dict(
-                status=status,
-                execution=execution_storage.execution.list()[0],
-                due_at=due_at,
-                started_at=started_at,
-                ended_at=ended_at,
-                max_attempts=max_attempts,
-                retry_count=retry_count,
-                retry_interval=retry_interval,
-                ignore_failure=ignore_failure,
-                name=name,
-                implementation=operation_mapping,
-                inputs=inputs,
-                plugin_fk=plugin_id,
-            ))
-        if is_valid:
-            assert task.execution == execution_storage.execution.list()[0]
-            if task.plugin:
-                assert task.plugin == execution_storage.plugin.list()[0]
-
-    def test_task_max_attempts_validation(self):
-        def create_task(max_attempts):
-            Task(execution_fk='eid',
-                 name='name',
-                 implementation='',
-                 inputs={},
-                 max_attempts=max_attempts)
-        create_task(max_attempts=1)
-        create_task(max_attempts=2)
-        create_task(max_attempts=Task.INFINITE_RETRIES)
-        with pytest.raises(ValueError):
-            create_task(max_attempts=0)
-        with pytest.raises(ValueError):
-            create_task(max_attempts=-2)

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/dd5bfa93/tests/storage/test_structures.py
----------------------------------------------------------------------
diff --git a/tests/storage/test_structures.py b/tests/storage/test_structures.py
deleted file mode 100644
index cacec2e..0000000
--- a/tests/storage/test_structures.py
+++ /dev/null
@@ -1,220 +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.
-
-import pytest
-
-import sqlalchemy
-
-from aria.storage import (
-    ModelStorage,
-    sql_mapi,
-    exceptions
-)
-from aria import modeling
-
-from ..storage import (
-    release_sqlite_storage,
-    bases,
-    init_inmemory_model_storage
-)
-from . import MockModel
-from ..mock import (
-    models,
-    context as mock_context
-)
-
-
-@pytest.fixture
-def storage():
-    base_storage = ModelStorage(sql_mapi.SQLAlchemyModelAPI,
-                                initiator=init_inmemory_model_storage)
-    base_storage.register(MockModel)
-    yield base_storage
-    release_sqlite_storage(base_storage)
-
-
-@pytest.fixture(scope='module', autouse=True)
-def module_cleanup():
-    modeling.models.aria_declarative_base.metadata.remove(MockModel.__table__)                      # pylint: disable=no-member
-
-
-@pytest.fixture
-def context(tmpdir):
-    ctx = mock_context.simple(str(tmpdir))
-    yield ctx
-    release_sqlite_storage(ctx.model)
-
-
-def test_inner_dict_update(storage):
-    inner_dict = {'inner_value': 1}
-
-    mock_model = MockModel(model_dict={'inner_dict': inner_dict, 'value': 0})
-    storage.mock_model.put(mock_model)
-
-    storage_mm = storage.mock_model.get(mock_model.id)
-    assert storage_mm == mock_model
-
-    storage_mm.model_dict['inner_dict']['inner_value'] = 2
-    storage_mm.model_dict['value'] = -1
-    storage.mock_model.update(storage_mm)
-    storage_mm = storage.mock_model.get(storage_mm.id)
-
-    assert storage_mm.model_dict['inner_dict']['inner_value'] == 2
-    assert storage_mm.model_dict['value'] == -1
-
-
-def test_inner_list_update(storage):
-    mock_model = MockModel(model_list=[0, [1]])
-    storage.mock_model.put(mock_model)
-
-    storage_mm = storage.mock_model.get(mock_model.id)
-    assert storage_mm == mock_model
-
-    storage_mm.model_list[1][0] = 'new_inner_value'
-    storage_mm.model_list[0] = 'new_value'
-    storage.mock_model.update(storage_mm)
-    storage_mm = storage.mock_model.get(storage_mm.id)
-
-    assert storage_mm.model_list[1][0] == 'new_inner_value'
-    assert storage_mm.model_list[0] == 'new_value'
-
-
-def test_model_to_dict(context):
-    service = context.service
-    service = service.to_dict()
-
-    expected_keys = [
-        'description',
-        'created_at',
-        'permalink',
-        'scaling_groups',
-        'updated_at'
-    ]
-
-    for expected_key in expected_keys:
-        assert expected_key in service
-
-
-def test_relationship_model_ordering(context):
-    service = context.model.service.get_by_name(models.SERVICE_NAME)
-    source_node = context.model.node.get_by_name(models.DEPENDENT_NODE_NAME)
-    target_node = context.model.node.get_by_name(models.DEPENDENCY_NODE_NAME)
-
-    new_node_template = modeling.models.NodeTemplate(
-        name='new_node_template',
-        type=source_node.type,
-        default_instances=1,
-        min_instances=1,
-        max_instances=1,
-        service_template=service.service_template
-    )
-
-    new_node = modeling.models.Node(
-        name='new_node',
-        type=source_node.type,
-        runtime_properties={},
-        service=service,
-        version=None,
-        node_template=new_node_template,
-        state='',
-        scaling_groups=[]
-    )
-
-    source_node.outbound_relationships.append(modeling.models.Relationship(
-        source_node=source_node,
-        target_node=new_node,
-    ))
-
-    new_node.outbound_relationships.append(modeling.models.Relationship(                            # pylint: disable=no-member
-        source_node=new_node,
-        target_node=target_node,
-    ))
-
-    context.model.node_template.put(new_node_template)
-    context.model.node.put(new_node)
-    context.model.node.refresh(source_node)
-    context.model.node.refresh(target_node)
-
-    def flip_and_assert(node, direction):
-        """
-        Reversed the order of relationships and assert effects took place.
-        :param node: the node instance to operate on
-        :param direction: the type of relationships to flip (inbound/outbound)
-        :return:
-        """
-        assert direction in ('inbound', 'outbound')
-
-        relationships = getattr(node, direction + '_relationships')
-        assert len(relationships) == 2
-
-        reversed_relationship = list(reversed(relationships))
-        assert relationships != reversed_relationship
-
-        relationships[:] = reversed_relationship
-        context.model.node.update(node)
-        assert relationships == reversed_relationship
-
-    flip_and_assert(source_node, 'outbound')
-    flip_and_assert(target_node, 'inbound')
-
-
-class StrictClass(modeling.models.aria_declarative_base, bases.ModelMixin):
-    __tablename__ = 'strict_class'
-
-    strict_dict = sqlalchemy.Column(modeling.types.StrictDict(basestring, basestring))
-    strict_list = sqlalchemy.Column(modeling.types.StrictList(basestring))
-
-
-def test_strict_dict():
-
-    strict_class = StrictClass()
-
-    def assert_strict(sc):
-        with pytest.raises(exceptions.StorageError):
-            sc.strict_dict = {'key': 1}
-
-        with pytest.raises(exceptions.StorageError):
-            sc.strict_dict = {1: 'value'}
-
-        with pytest.raises(exceptions.StorageError):
-            sc.strict_dict = {1: 1}
-
-    assert_strict(strict_class)
-    strict_class.strict_dict = {'key': 'value'}
-    assert strict_class.strict_dict == {'key': 'value'}
-
-    assert_strict(strict_class)
-    with pytest.raises(exceptions.StorageError):
-        strict_class.strict_dict['key'] = 1
-    with pytest.raises(exceptions.StorageError):
-        strict_class.strict_dict[1] = 'value'
-    with pytest.raises(exceptions.StorageError):
-        strict_class.strict_dict[1] = 1
-
-
-def test_strict_list():
-    strict_class = StrictClass()
-
-    def assert_strict(sc):
-        with pytest.raises(exceptions.StorageError):
-            sc.strict_list = [1]
-
-    assert_strict(strict_class)
-    strict_class.strict_list = ['item']
-    assert strict_class.strict_list == ['item']
-
-    assert_strict(strict_class)
-    with pytest.raises(exceptions.StorageError):
-        strict_class.strict_list[0] = 1



[3/8] incubator-ariatosca git commit: Separate plugin specification form plugin; move dry support to CLI; various renames and refactorings

Posted by mx...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/dd5bfa93/tests/modeling/test_models.py
----------------------------------------------------------------------
diff --git a/tests/modeling/test_models.py b/tests/modeling/test_models.py
new file mode 100644
index 0000000..c8d36e0
--- /dev/null
+++ b/tests/modeling/test_models.py
@@ -0,0 +1,835 @@
+# 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 datetime import datetime
+from contextlib import contextmanager
+
+import pytest
+
+from aria import application_model_storage
+from aria.storage import (
+    exceptions,
+    sql_mapi,
+)
+from aria.modeling.models import (
+    ServiceTemplate,
+    Service,
+    ServiceUpdate,
+    ServiceUpdateStep,
+    ServiceModification,
+    Execution,
+    Task,
+    Plugin,
+    Relationship,
+    NodeTemplate,
+    Node,
+    Parameter,
+    Type
+)
+
+from tests import mock
+from ..storage import release_sqlite_storage, init_inmemory_model_storage
+
+
+@contextmanager
+def sql_storage(storage_func):
+    storage = None
+    try:
+        storage = storage_func()
+        yield storage
+    finally:
+        if storage:
+            release_sqlite_storage(storage)
+
+
+def _empty_storage():
+    return application_model_storage(sql_mapi.SQLAlchemyModelAPI,
+                                     initiator=init_inmemory_model_storage)
+
+
+def _service_template_storage():
+    storage = _empty_storage()
+    service_template = mock.models.create_service_template()
+    storage.service_template.put(service_template)
+    storage.type.put(Type(variant='node'))
+    return storage
+
+
+def _service_storage():
+    storage = _service_template_storage()
+    service = mock.models.create_service(
+        storage.service_template.get_by_name(mock.models.SERVICE_TEMPLATE_NAME))
+    storage.service.put(service)
+    return storage
+
+
+def _service_update_storage():
+    storage = _service_storage()
+    service_update = ServiceUpdate(
+        service=storage.service.list()[0],
+        created_at=now,
+        service_plan={},
+    )
+    storage.service_update.put(service_update)
+    return storage
+
+
+def _node_template_storage():
+    storage = _service_storage()
+    service_template = storage.service_template.list()[0]
+    node_template = mock.models.create_dependency_node_template(service_template)
+    storage.node_template.put(node_template)
+    return storage
+
+
+def _nodes_storage():
+    storage = _nodes_storage() # ???
+    service = storage.service.get_by_name(mock.models.SERVICE_NAME)
+    dependent_node_template = storage.node_template.get_by_name(mock.models.DEPENDENT_NODE_NAME)
+    dependency_node_template = storage.node_template.get_by_name(mock.models.DEPENDENCY_NODE_NAME)
+    dependency_node = mock.models.create_dependency_node(dependency_node_template, service)
+    dependent_node = mock.models.create_dependent_node(dependent_node_template, service)
+    storage.node.put(dependency_node)
+    storage.node.put(dependent_node)
+    return storage
+
+
+def _execution_storage():
+    storage = _service_storage()
+    execution = mock.models.create_execution(storage.service.list()[0])
+    plugin = mock.models.create_plugin()
+    storage.execution.put(execution)
+    storage.plugin.put(plugin)
+    return storage
+
+
+@pytest.fixture
+def empty_storage():
+    with sql_storage(_empty_storage) as storage:
+        yield storage
+
+
+@pytest.fixture
+def service_template_storage():
+    with sql_storage(_service_template_storage) as storage:
+        yield storage
+
+
+@pytest.fixture
+def service_storage():
+    with sql_storage(_service_storage) as storage:
+        yield storage
+
+
+@pytest.fixture
+def service_update_storage():
+    with sql_storage(_service_update_storage) as storage:
+        yield storage
+
+
+@pytest.fixture
+def node_template_storage():
+    with sql_storage(_node_template_storage) as storage:
+        yield storage
+
+
+@pytest.fixture
+def nodes_storage():
+    with sql_storage(_nodes_storage) as storage:
+        yield storage
+
+
+@pytest.fixture
+def execution_storage():
+    with sql_storage(_execution_storage) as storage:
+        yield storage
+
+
+m_cls = type('MockClass')
+now = datetime.utcnow()
+
+
+def _test_model(is_valid, storage, model_cls, model_kwargs):
+    if is_valid:
+        model = model_cls(**model_kwargs)
+        getattr(storage, model_cls.__modelname__).put(model)
+        return model
+    else:
+        with pytest.raises((exceptions.StorageError, TypeError),):
+            getattr(storage, model_cls.__modelname__).put(model_cls(**model_kwargs))
+
+
+class TestServiceTemplate(object):
+
+    @pytest.mark.parametrize(
+        'is_valid, description, created_at, updated_at, main_file_name',
+        [
+            (True, 'description', now, now, '/path'),
+            (False, {}, now, now, '/path'),
+            (False, 'description', 'error', now, '/path'),
+            (False, 'description', now, 'error', '/path'),
+            (False, 'description', now, now, {}),
+            (True, 'description', now, now, '/path'),
+        ]
+    )
+
+    def test_service_template_model_creation(self, empty_storage, is_valid, description, created_at,
+                                             updated_at, main_file_name):
+        _test_model(is_valid=is_valid,
+                    storage=empty_storage,
+                    model_cls=ServiceTemplate,
+                    model_kwargs=dict(
+                        description=description,
+                        created_at=created_at,
+                        updated_at=updated_at,
+                        main_file_name=main_file_name)
+                   )
+
+
+class TestService(object):
+
+    @pytest.mark.parametrize(
+        'is_valid, name, created_at, description, inputs, permalink, '
+        'outputs, scaling_groups, updated_at',
+        [
+            (False, m_cls, now, 'desc', {}, 'perlnk', {}, {}, now),
+            (False, 'name', m_cls, 'desc', {}, 'perlnk', {}, {}, now),
+            (False, 'name', now, m_cls, {}, 'perlnk', {}, {}, now),
+            (False, 'name', now, 'desc', {}, m_cls, {}, {}, now),
+            (False, 'name', now, 'desc', {}, 'perlnk', m_cls, {}, now),
+            (False, 'name', now, 'desc', {}, 'perlnk', {}, m_cls, now),
+            (False, 'name', now, 'desc', {}, 'perlnk', {}, {}, m_cls),
+
+            (True, 'name', now, 'desc', {}, 'perlnk', {}, {}, now),
+            (True, None, now, 'desc', {}, 'perlnk', {}, {}, now),
+            (True, 'name', now, 'desc', {}, 'perlnk', {}, {}, now),
+            (True, 'name', now, None, {}, 'perlnk', {}, {}, now),
+            (True, 'name', now, 'desc', {}, 'perlnk', {}, {}, now),
+            (True, 'name', now, 'desc', {}, None, {}, {}, now),
+            (True, 'name', now, 'desc', {}, 'perlnk', {}, {}, now),
+            (True, 'name', now, 'desc', {}, 'perlnk', {}, None, now),
+            (True, 'name', now, 'desc', {}, 'perlnk', {}, {}, None),
+            (True, 'name', now, 'desc', {}, 'perlnk', {}, {}, now),
+        ]
+    )
+    def test_service_model_creation(self, service_storage, is_valid, name, created_at, description,
+                                    inputs, permalink, outputs, scaling_groups, updated_at):
+        service = _test_model(
+            is_valid=is_valid,
+            storage=service_storage,
+            model_cls=Service,
+            model_kwargs=dict(
+                name=name,
+                service_template=service_storage.service_template.list()[0],
+                created_at=created_at,
+                description=description,
+                inputs=inputs,
+                permalink=permalink,
+                outputs=outputs,
+                scaling_groups=scaling_groups,
+                updated_at=updated_at
+            ))
+        if is_valid:
+            assert service.service_template == \
+                   service_storage.service_template.list()[0]
+
+
+class TestExecution(object):
+
+    @pytest.mark.parametrize(
+        'is_valid, created_at, started_at, ended_at, error, is_system_workflow, parameters, '
+        'status, workflow_name',
+        [
+            (False, m_cls, now, now, 'error', False, {}, Execution.STARTED, 'wf_name'),
+            (False, now, m_cls, now, 'error', False, {}, Execution.STARTED, 'wf_name'),
+            (False, now, now, m_cls, 'error', False, {}, Execution.STARTED, 'wf_name'),
+            (False, now, now, now, m_cls, False, {}, Execution.STARTED, 'wf_name'),
+            (False, now, now, now, 'error', False, m_cls, Execution.STARTED, 'wf_name'),
+            (False, now, now, now, 'error', False, {}, m_cls, 'wf_name'),
+            (False, now, now, now, 'error', False, {}, Execution.STARTED, m_cls),
+
+            (True, now, now, now, 'error', False, {}, Execution.STARTED, 'wf_name'),
+            (True, now, None, now, 'error', False, {}, Execution.STARTED, 'wf_name'),
+            (True, now, now, None, 'error', False, {}, Execution.STARTED, 'wf_name'),
+            (True, now, now, now, None, False, {}, Execution.STARTED, 'wf_name'),
+            (True, now, now, now, 'error', False, None, Execution.STARTED, 'wf_name'),
+        ]
+    )
+    def test_execution_model_creation(self, service_storage, is_valid, created_at, started_at,
+                                      ended_at, error, is_system_workflow, parameters, status,
+                                      workflow_name):
+        execution = _test_model(
+            is_valid=is_valid,
+            storage=service_storage,
+            model_cls=Execution,
+            model_kwargs=dict(
+                service=service_storage.service.list()[0],
+                created_at=created_at,
+                started_at=started_at,
+                ended_at=ended_at,
+                error=error,
+                is_system_workflow=is_system_workflow,
+                parameters=parameters,
+                status=status,
+                workflow_name=workflow_name,
+            ))
+        if is_valid:
+            assert execution.service == service_storage.service.list()[0]
+            assert execution.service_template == service_storage.service_template.list()[0]
+
+    def test_execution_status_transition(self):
+        def create_execution(status):
+            execution = Execution(
+                id='e_id',
+                workflow_name='w_name',
+                status=status,
+                parameters={},
+                created_at=now,
+            )
+            return execution
+
+        valid_transitions = {
+            Execution.PENDING: [Execution.STARTED,
+                                Execution.CANCELLED,
+                                Execution.PENDING],
+            Execution.STARTED: [Execution.FAILED,
+                                Execution.TERMINATED,
+                                Execution.CANCELLED,
+                                Execution.CANCELLING,
+                                Execution.STARTED],
+            Execution.CANCELLING: [Execution.FAILED,
+                                   Execution.TERMINATED,
+                                   Execution.CANCELLED,
+                                   Execution.CANCELLING],
+            Execution.FAILED: [Execution.FAILED],
+            Execution.TERMINATED: [Execution.TERMINATED],
+            Execution.CANCELLED: [Execution.CANCELLED]
+        }
+
+        invalid_transitions = {
+            Execution.PENDING: [Execution.FAILED,
+                                Execution.TERMINATED,
+                                Execution.CANCELLING],
+            Execution.STARTED: [Execution.PENDING],
+            Execution.CANCELLING: [Execution.PENDING,
+                                   Execution.STARTED],
+            Execution.FAILED: [Execution.PENDING,
+                               Execution.STARTED,
+                               Execution.TERMINATED,
+                               Execution.CANCELLED,
+                               Execution.CANCELLING],
+            Execution.TERMINATED: [Execution.PENDING,
+                                   Execution.STARTED,
+                                   Execution.FAILED,
+                                   Execution.CANCELLED,
+                                   Execution.CANCELLING],
+            Execution.CANCELLED: [Execution.PENDING,
+                                  Execution.STARTED,
+                                  Execution.FAILED,
+                                  Execution.TERMINATED,
+                                  Execution.CANCELLING],
+        }
+
+        for current_status, valid_transitioned_statues in valid_transitions.items():
+            for transitioned_status in valid_transitioned_statues:
+                execution = create_execution(current_status)
+                execution.status = transitioned_status
+
+        for current_status, invalid_transitioned_statues in invalid_transitions.items():
+            for transitioned_status in invalid_transitioned_statues:
+                execution = create_execution(current_status)
+                with pytest.raises(ValueError):
+                    execution.status = transitioned_status
+
+
+class TestServiceUpdate(object):
+    @pytest.mark.parametrize(
+        'is_valid, created_at, service_plan, service_update_nodes, '
+        'service_update_service, service_update_node_templates, '
+        'modified_entity_ids, state',
+        [
+            (False, m_cls, {}, {}, {}, [], {}, 'state'),
+            (False, now, m_cls, {}, {}, [], {}, 'state'),
+            (False, now, {}, m_cls, {}, [], {}, 'state'),
+            (False, now, {}, {}, m_cls, [], {}, 'state'),
+            (False, now, {}, {}, {}, m_cls, {}, 'state'),
+            (False, now, {}, {}, {}, [], m_cls, 'state'),
+            (False, now, {}, {}, {}, [], {}, m_cls),
+
+            (True, now, {}, {}, {}, [], {}, 'state'),
+            (True, now, {}, None, {}, [], {}, 'state'),
+            (True, now, {}, {}, None, [], {}, 'state'),
+            (True, now, {}, {}, {}, None, {}, 'state'),
+            (True, now, {}, {}, {}, [], None, 'state'),
+            (True, now, {}, {}, {}, [], {}, None),
+        ]
+    )
+    def test_service_update_model_creation(self, service_storage, is_valid, created_at,
+                                           service_plan, service_update_nodes,
+                                           service_update_service, service_update_node_templates,
+                                           modified_entity_ids, state):
+        service_update = _test_model(
+            is_valid=is_valid,
+            storage=service_storage,
+            model_cls=ServiceUpdate,
+            model_kwargs=dict(
+                service=service_storage.service.list()[0],
+                created_at=created_at,
+                service_plan=service_plan,
+                service_update_nodes=service_update_nodes,
+                service_update_service=service_update_service,
+                service_update_node_templates=service_update_node_templates,
+                modified_entity_ids=modified_entity_ids,
+                state=state
+            ))
+        if is_valid:
+            assert service_update.service == \
+                   service_storage.service.list()[0]
+
+
+class TestServiceUpdateStep(object):
+
+    @pytest.mark.parametrize(
+        'is_valid, action, entity_id, entity_type',
+        [
+            (False, m_cls, 'id', ServiceUpdateStep.ENTITY_TYPES.NODE),
+            (False, ServiceUpdateStep.ACTION_TYPES.ADD, m_cls,
+             ServiceUpdateStep.ENTITY_TYPES.NODE),
+            (False, ServiceUpdateStep.ACTION_TYPES.ADD, 'id', m_cls),
+
+            (True, ServiceUpdateStep.ACTION_TYPES.ADD, 'id',
+             ServiceUpdateStep.ENTITY_TYPES.NODE)
+        ]
+    )
+    def test_service_update_step_model_creation(self, service_update_storage, is_valid, action,
+                                                entity_id, entity_type):
+        service_update_step = _test_model(
+            is_valid=is_valid,
+            storage=service_update_storage,
+            model_cls=ServiceUpdateStep,
+            model_kwargs=dict(
+                service_update=
+                service_update_storage.service_update.list()[0],
+                action=action,
+                entity_id=entity_id,
+                entity_type=entity_type
+            ))
+        if is_valid:
+            assert service_update_step.service_update == \
+                   service_update_storage.service_update.list()[0]
+
+    def test_service_update_step_order(self):
+        add_node = ServiceUpdateStep(
+            id='add_step',
+            action='add',
+            entity_type='node',
+            entity_id='node_id')
+
+        modify_node = ServiceUpdateStep(
+            id='modify_step',
+            action='modify',
+            entity_type='node',
+            entity_id='node_id')
+
+        remove_node = ServiceUpdateStep(
+            id='remove_step',
+            action='remove',
+            entity_type='node',
+            entity_id='node_id')
+
+        for step in (add_node, modify_node, remove_node):
+            assert hash((step.id, step.entity_id)) == hash(step)
+
+        assert remove_node < modify_node < add_node
+        assert not remove_node > modify_node > add_node
+
+        add_rel = ServiceUpdateStep(
+            id='add_step',
+            action='add',
+            entity_type='relationship',
+            entity_id='relationship_id')
+
+        remove_rel = ServiceUpdateStep(
+            id='remove_step',
+            action='remove',
+            entity_type='relationship',
+            entity_id='relationship_id')
+
+        assert remove_rel < remove_node < add_node < add_rel
+        assert not add_node < None
+
+
+class TestServiceModification(object):
+    @pytest.mark.parametrize(
+        'is_valid, context, created_at, ended_at, modified_node_templates, nodes, status',
+        [
+            (False, m_cls, now, now, {}, {}, ServiceModification.STARTED),
+            (False, {}, m_cls, now, {}, {}, ServiceModification.STARTED),
+            (False, {}, now, m_cls, {}, {}, ServiceModification.STARTED),
+            (False, {}, now, now, m_cls, {}, ServiceModification.STARTED),
+            (False, {}, now, now, {}, m_cls, ServiceModification.STARTED),
+            (False, {}, now, now, {}, {}, m_cls),
+
+            (True, {}, now, now, {}, {}, ServiceModification.STARTED),
+            (True, {}, now, None, {}, {}, ServiceModification.STARTED),
+            (True, {}, now, now, None, {}, ServiceModification.STARTED),
+            (True, {}, now, now, {}, None, ServiceModification.STARTED),
+        ]
+    )
+    def test_service_modification_model_creation(self, service_storage, is_valid, context,
+                                                 created_at, ended_at, modified_node_templates,
+                                                 nodes, status):
+        service_modification = _test_model(
+            is_valid=is_valid,
+            storage=service_storage,
+            model_cls=ServiceModification,
+            model_kwargs=dict(
+                service=service_storage.service.list()[0],
+                context=context,
+                created_at=created_at,
+                ended_at=ended_at,
+                modified_node_templates=modified_node_templates,
+                nodes=nodes,
+                status=status,
+            ))
+        if is_valid:
+            assert service_modification.service == \
+                   service_storage.service.list()[0]
+
+
+class TestNodeTemplate(object):
+    @pytest.mark.parametrize(
+        'is_valid, name, default_instances, max_instances, min_instances, plugin_specifications, '
+        'properties',
+        [
+            (False, m_cls, 1, 1, 1, [], {}),
+            (False, 'name', m_cls, 1, 1, [], {}),
+            (False, 'name', 1, m_cls, 1, [], {}),
+            (False, 'name', 1, 1, m_cls, [], {}),
+            (False, 'name', 1, 1, 1, m_cls, {}),
+            (False, 'name', 1, 1, 1, None, {}),
+
+            (True, 'name', 1, 1, 1, [], {}),
+        ]
+    )
+    def test_node_template_model_creation(self, service_storage, is_valid, name, default_instances,
+                                          max_instances, min_instances, plugin_specifications,
+                                          properties):
+        node_template = _test_model(
+            is_valid=is_valid,
+            storage=service_storage,
+            model_cls=NodeTemplate,
+            model_kwargs=dict(
+                name=name,
+                type=service_storage.type.list()[0],
+                default_instances=default_instances,
+                max_instances=max_instances,
+                min_instances=min_instances,
+                plugin_specifications=plugin_specifications,
+                properties=properties,
+                service_template=service_storage.service_template.list()[0]
+            ))
+        if is_valid:
+            assert node_template.service_template == \
+                   service_storage.service_template.list()[0]
+
+
+class TestNode(object):
+    @pytest.mark.parametrize(
+        'is_valid, name, runtime_properties, scaling_groups, state, version',
+        [
+            (False, m_cls, {}, [], 'state', 1),
+            (False, 'name', m_cls, [], 'state', 1),
+            (False, 'name', {}, m_cls, 'state', 1),
+            (False, 'name', {}, [], m_cls, 1),
+            (False, m_cls, {}, [], 'state', m_cls),
+
+            (True, 'name', {}, [], 'state', 1),
+            (True, None, {}, [], 'state', 1),
+            (True, 'name', None, [], 'state', 1),
+            (True, 'name', {}, None, 'state', 1),
+            (True, 'name', {}, [], 'state', None),
+        ]
+    )
+    def test_node_model_creation(self, node_template_storage, is_valid, name, runtime_properties,
+                                 scaling_groups, state, version):
+        node = _test_model(
+            is_valid=is_valid,
+            storage=node_template_storage,
+            model_cls=Node,
+            model_kwargs=dict(
+                node_template=node_template_storage.node_template.list()[0],
+                type=node_template_storage.type.list()[0],
+                name=name,
+                runtime_properties=runtime_properties,
+                scaling_groups=scaling_groups,
+                state=state,
+                version=version,
+                service=node_template_storage.service.list()[0]
+            ))
+        if is_valid:
+            assert node.node_template == node_template_storage.node_template.list()[0]
+            assert node.service == \
+                   node_template_storage.service.list()[0]
+
+
+class TestNodeInstanceIP(object):
+
+    ip = '1.1.1.1'
+
+    def test_ip_on_none_hosted_node(self, service_storage):
+        node_template = self._node_template(service_storage, ip='not considered')
+        node = self._node(service_storage,
+                          node_template,
+                          is_host=False,
+                          ip='not considered')
+        assert node.ip is None
+
+    def test_property_ip_on_host_node(self, service_storage):
+        node_template = self._node_template(service_storage, ip=self.ip)
+        node = self._node(service_storage, node_template, is_host=True, ip=None)
+        assert node.ip == self.ip
+
+    def test_runtime_property_ip_on_host_node(self, service_storage):
+        node_template = self._node_template(service_storage, ip='not considered')
+        node = self._node(service_storage, node_template, is_host=True, ip=self.ip)
+        assert node.ip == self.ip
+
+    def test_no_ip_configured_on_host_node(self, service_storage):
+        node_template = self._node_template(service_storage, ip=None)
+        node = self._node(service_storage, node_template, is_host=True, ip=None)
+        assert node.ip is None
+
+    def test_runtime_property_on_hosted_node(self, service_storage):
+        host_node_template = self._node_template(service_storage, ip=None)
+        host_node = self._node(service_storage,
+                               host_node_template,
+                               is_host=True,
+                               ip=self.ip)
+        node_template = self._node_template(service_storage, ip=None)
+        node = self._node(service_storage,
+                          node_template,
+                          is_host=False,
+                          ip=None,
+                          host_fk=host_node.id)
+        assert node.ip == self.ip
+
+    def _node_template(self, storage, ip):
+        kwargs = dict(
+            name='node_template',
+            type=storage.type.list()[0],
+            default_instances=1,
+            max_instances=1,
+            min_instances=1,
+            service_template=storage.service_template.list()[0]
+        )
+        if ip:
+            kwargs['properties'] = {'ip': Parameter(name='ip', type_name='string', value=ip)}
+        node = NodeTemplate(**kwargs)
+        storage.node_template.put(node)
+        return node
+
+    def _node(self, storage, node, is_host, ip, host_fk=None):
+        kwargs = dict(
+            name='node',
+            node_template=node,
+            type=storage.type.list()[0],
+            runtime_properties={},
+            state='',
+            service=storage.service.list()[0]
+        )
+        if ip:
+            kwargs['runtime_properties']['ip'] = ip
+        if is_host:
+            kwargs['host_fk'] = 1
+        elif host_fk:
+            kwargs['host_fk'] = host_fk
+        node = Node(**kwargs)
+        storage.node.put(node)
+        return node
+
+
+@pytest.mark.skip('Should be reworked into relationship')
+class TestRelationship(object):
+    def test_relationship_model_creation(self, nodes_storage):
+        nodes = nodes_storage.node
+        source_node = nodes.get_by_name(mock.models.DEPENDENT_NODE_NAME)
+        target_node = nodes.get_by_name(mock.models.DEPENDENCY_NODE_NAME)
+
+        relationship = mock.models.create_relationship(
+            source=source_node,
+            target=nodes_storage.node.get_by_name(mock.models.DEPENDENCY_NODE_NAME)
+        )
+        nodes_storage.relationship.put(relationship)
+
+        relationship_instance = _test_model(
+            is_valid=True,
+            storage=nodes_storage,
+            model_cls=Relationship,
+            model_kwargs=dict(
+                relationship=relationship,
+                source_node=source_node,
+                target_node=target_node
+            ))
+        assert relationship_instance.relationship == relationship
+        assert relationship_instance.source_node == source_node
+        assert relationship_instance.target_node == target_node
+
+
+class TestPlugin(object):
+    @pytest.mark.parametrize(
+        'is_valid, archive_name, distribution, distribution_release, '
+        'distribution_version, package_name, package_source, '
+        'package_version, supported_platform, supported_py_versions, uploaded_at, wheels',
+        [
+            (False, m_cls, 'dis_name', 'dis_rel', 'dis_ver', 'pak_name', 'pak_src', 'pak_ver',
+             'sup_plat', [], now, []),
+            (False, 'arc_name', m_cls, 'dis_rel', 'dis_ver', 'pak_name', 'pak_src', 'pak_ver',
+             'sup_plat', [], now, []),
+            (False, 'arc_name', 'dis_name', m_cls, 'dis_ver', 'pak_name', 'pak_src', 'pak_ver',
+             'sup_plat', [], now, []),
+            (False, 'arc_name', 'dis_name', 'dis_rel', m_cls, 'pak_name', 'pak_src', 'pak_ver',
+             'sup_plat', [], now, []),
+            (False, 'arc_name', 'dis_name', 'dis_rel', 'dis_ver', m_cls, 'pak_src', 'pak_ver',
+             'sup_plat', [], now, []),
+            (False, 'arc_name', 'dis_name', 'dis_rel', 'dis_ver', 'pak_name', m_cls, 'pak_ver',
+             'sup_plat', [], now, []),
+            (False, 'arc_name', 'dis_name', 'dis_rel', 'dis_ver', 'pak_name', 'pak_src', m_cls,
+             'sup_plat', [], now, []),
+            (False, 'arc_name', 'dis_name', 'dis_rel', 'dis_ver', 'pak_name', 'pak_src',
+             'pak_ver', m_cls, [], now, []),
+            (False, 'arc_name', 'dis_name', 'dis_rel', 'dis_ver', 'pak_name', 'pak_src',
+             'pak_ver', 'sup_plat', m_cls, now, []),
+            (False, 'arc_name', 'dis_name', 'dis_rel', 'dis_ver', 'pak_name', 'pak_src',
+             'pak_ver', 'sup_plat', [], m_cls, []),
+            (False, 'arc_name', 'dis_name', 'dis_rel', 'dis_ver', 'pak_name', 'pak_src',
+             'pak_ver', 'sup_plat', [], now, m_cls),
+
+            (True, 'arc_name', 'dis_name', 'dis_rel', 'dis_ver', 'pak_name', 'pak_src', 'pak_ver',
+             'sup_plat', [], now, []),
+            (True, 'arc_name', None, 'dis_rel', 'dis_ver', 'pak_name', 'pak_src', 'pak_ver',
+             'sup_plat', [], now, []),
+            (True, 'arc_name', 'dis_name', None, 'dis_ver', 'pak_name', 'pak_src', 'pak_ver',
+             'sup_plat', [], now, []),
+            (True, 'arc_name', 'dis_name', 'dis_rel', None, 'pak_name', 'pak_src', 'pak_ver',
+             'sup_plat', [], now, []),
+            (True, 'arc_name', 'dis_name', 'dis_rel', 'dis_ver', 'pak_name', 'pak_src',
+             'pak_ver', 'sup_plat', [], now, []),
+            (True, 'arc_name', 'dis_name', 'dis_rel', 'dis_ver', 'pak_name', None, 'pak_ver',
+             'sup_plat', [], now, []),
+            (True, 'arc_name', 'dis_name', 'dis_rel', 'dis_ver', 'pak_name', 'pak_src', None,
+             'sup_plat', [], now, []),
+            (True, 'arc_name', 'dis_name', 'dis_rel', 'dis_ver', 'pak_name', 'pak_src',
+             'pak_ver', None, [], now, []),
+            (True, 'arc_name', 'dis_name', 'dis_rel', 'dis_ver', 'pak_name', 'pak_src',
+             'pak_ver', 'sup_plat', None, now, []),
+            (True, 'arc_name', 'dis_name', 'dis_rel', 'dis_ver', 'pak_name', 'pak_src',
+             'pak_ver', 'sup_plat', [], now, []),
+        ]
+    )
+    def test_plugin_model_creation(self, empty_storage, is_valid, archive_name, distribution,
+                                   distribution_release, distribution_version, package_name,
+                                   package_source, package_version, supported_platform,
+                                   supported_py_versions, uploaded_at, wheels):
+        _test_model(is_valid=is_valid,
+                    storage=empty_storage,
+                    model_cls=Plugin,
+                    model_kwargs=dict(
+                        archive_name=archive_name,
+                        distribution=distribution,
+                        distribution_release=distribution_release,
+                        distribution_version=distribution_version,
+                        package_name=package_name,
+                        package_source=package_source,
+                        package_version=package_version,
+                        supported_platform=supported_platform,
+                        supported_py_versions=supported_py_versions,
+                        uploaded_at=uploaded_at,
+                        wheels=wheels,
+                    ))
+
+
+class TestTask(object):
+
+    @pytest.mark.parametrize(
+        'is_valid, status, due_at, started_at, ended_at, max_attempts, retry_count, '
+        'retry_interval, ignore_failure, name, operation_mapping, inputs, plugin_id',
+        [
+            (False, m_cls, now, now, now, 1, 1, 1, True, 'name', 'map', {}, '1'),
+            (False, Task.STARTED, m_cls, now, now, 1, 1, 1, True, 'name', 'map', {}, '1'),
+            (False, Task.STARTED, now, m_cls, now, 1, 1, 1, True, 'name', 'map', {}, '1'),
+            (False, Task.STARTED, now, now, m_cls, 1, 1, 1, True, 'name', 'map', {}, '1'),
+            (False, Task.STARTED, now, now, now, m_cls, 1, 1, True, 'name', 'map', {}, '1'),
+            (False, Task.STARTED, now, now, now, 1, m_cls, 1, True, 'name', 'map', {}, '1'),
+            (False, Task.STARTED, now, now, now, 1, 1, m_cls, True, 'name', 'map', {}, '1'),
+            (False, Task.STARTED, now, now, now, 1, 1, 1, True, m_cls, 'map', {}, '1'),
+            (False, Task.STARTED, now, now, now, 1, 1, 1, True, 'name', m_cls, {}, '1'),
+            (False, Task.STARTED, now, now, now, 1, 1, 1, True, 'name', 'map', m_cls, '1'),
+            (False, Task.STARTED, now, now, now, 1, 1, 1, True, 'name', 'map', {}, m_cls),
+            (False, Task.STARTED, now, now, now, 1, 1, 1, True, 'name', 'map', None, '1'),
+
+            (True, Task.STARTED, now, now, now, 1, 1, 1, True, 'name', 'map', {}, '1'),
+            (True, Task.STARTED, None, now, now, 1, 1, 1, True, 'name', 'map', {}, '1'),
+            (True, Task.STARTED, now, None, now, 1, 1, 1, True, 'name', 'map', {}, '1'),
+            (True, Task.STARTED, now, now, None, 1, 1, 1, True, 'name', 'map', {}, '1'),
+            (True, Task.STARTED, now, now, now, 1, None, 1, True, 'name', 'map', {}, '1'),
+            (True, Task.STARTED, now, now, now, 1, 1, None, True, 'name', 'map', {}, '1'),
+            (True, Task.STARTED, now, now, now, 1, 1, 1, None, 'name', 'map', {}, '1'),
+            (True, Task.STARTED, now, now, now, 1, 1, 1, True, None, 'map', {}, '1'),
+            (True, Task.STARTED, now, now, now, 1, 1, 1, True, 'name', None, {}, '1'),
+            (True, Task.STARTED, now, now, now, 1, 1, 1, True, 'name', 'map', {}, None),
+        ]
+    )
+    def test_task_model_creation(self, execution_storage, is_valid, status, due_at, started_at,
+                                 ended_at, max_attempts, retry_count, retry_interval,
+                                 ignore_failure, name, operation_mapping, inputs, plugin_id):
+        task = _test_model(
+            is_valid=is_valid,
+            storage=execution_storage,
+            model_cls=Task,
+            model_kwargs=dict(
+                status=status,
+                execution=execution_storage.execution.list()[0],
+                due_at=due_at,
+                started_at=started_at,
+                ended_at=ended_at,
+                max_attempts=max_attempts,
+                retry_count=retry_count,
+                retry_interval=retry_interval,
+                ignore_failure=ignore_failure,
+                name=name,
+                implementation=operation_mapping,
+                inputs=inputs,
+                plugin_fk=plugin_id,
+            ))
+        if is_valid:
+            assert task.execution == execution_storage.execution.list()[0]
+            if task.plugin:
+                assert task.plugin == execution_storage.plugin.list()[0]
+
+    def test_task_max_attempts_validation(self):
+        def create_task(max_attempts):
+            Task(execution_fk='eid',
+                 name='name',
+                 implementation='',
+                 inputs={},
+                 max_attempts=max_attempts)
+        create_task(max_attempts=1)
+        create_task(max_attempts=2)
+        create_task(max_attempts=Task.INFINITE_RETRIES)
+        with pytest.raises(ValueError):
+            create_task(max_attempts=0)
+        with pytest.raises(ValueError):
+            create_task(max_attempts=-2)

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/dd5bfa93/tests/orchestrator/context/test_operation.py
----------------------------------------------------------------------
diff --git a/tests/orchestrator/context/test_operation.py b/tests/orchestrator/context/test_operation.py
index 8ac8d49..7817a90 100644
--- a/tests/orchestrator/context/test_operation.py
+++ b/tests/orchestrator/context/test_operation.py
@@ -84,7 +84,7 @@ def test_node_operation_task_execution(ctx, executor):
 
     operation_context = global_test_holder[api.task.OperationTask.NAME_FORMAT.format(
         type='node',
-        id=node.id,
+        id=node.name,
         interface=interface_name,
         operation=operation_name
     )]
@@ -95,7 +95,7 @@ def test_node_operation_task_execution(ctx, executor):
     assert operation_context.task.actor == node
     assert operation_context.task.name == api.task.OperationTask.NAME_FORMAT.format(
         type='node',
-        id=node.id,
+        id=node.name,
         interface=interface_name,
         operation=operation_name
     )
@@ -140,7 +140,7 @@ def test_relationship_operation_task_execution(ctx, executor):
 
     operation_context = global_test_holder[api.task.OperationTask.NAME_FORMAT.format(
         type='relationship',
-        id=relationship.id,
+        id=relationship.name,
         interface=interface_name,
         operation=operation_name
     )]
@@ -205,7 +205,7 @@ def test_invalid_task_operation_id(ctx, executor):
 
     op_node_id = global_test_holder[api.task.OperationTask.NAME_FORMAT.format(
         type='node',
-        id=node.id,
+        id=node.name,
         interface=interface_name,
         operation=operation_name
     )]
@@ -218,7 +218,8 @@ def test_plugin_workdir(ctx, executor, tmpdir):
     operation_name = 'create'
 
     plugin = mock.models.create_plugin()
-    plugin.name = 'mock_plugin'
+    ctx.model.plugin.put(plugin)
+    plugin_specification = mock.models.create_plugin_specification()
     node = ctx.model.node.get_by_name(mock.models.DEPENDENCY_NODE_NAME)
     interface = mock.models.create_interface(
         node.service,
@@ -226,10 +227,10 @@ def test_plugin_workdir(ctx, executor, tmpdir):
         operation_name,
         operation_kwargs=dict(
             implementation='{0}.{1}'.format(__name__, _test_plugin_workdir.__name__),
-            plugin=plugin)
+            plugin_specification=plugin_specification)
     )
     node.interfaces[interface.name] = interface
-    node.plugins = [plugin]
+    node.plugin_specifications = [plugin_specification]
     ctx.model.node.update(node)
 
     filename = 'test_file'

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/dd5bfa93/tests/orchestrator/context/test_serialize.py
----------------------------------------------------------------------
diff --git a/tests/orchestrator/context/test_serialize.py b/tests/orchestrator/context/test_serialize.py
index 9e5a0b4..2ec999c 100644
--- a/tests/orchestrator/context/test_serialize.py
+++ b/tests/orchestrator/context/test_serialize.py
@@ -44,14 +44,17 @@ def test_serialize_operation_context(context, executor, tmpdir):
 def _mock_workflow(ctx, graph):
     node = ctx.model.node.get_by_name(mock.models.DEPENDENCY_NODE_NAME)
     plugin = mock.models.create_plugin()
+    ctx.model.plugin.put(plugin)
+    plugin_specification = mock.models.create_plugin_specification()
     interface = mock.models.create_interface(
         node.service,
         'test',
         'op',
-        operation_kwargs=dict(implementation=_operation_mapping(), plugin=plugin)
+        operation_kwargs=dict(implementation=_operation_mapping(),
+                              plugin_specification=plugin_specification)
     )
     node.interfaces[interface.name] = interface
-    node.plugins = [plugin]
+    node.plugin_specifications = [plugin_specification]
     task = api.task.OperationTask.for_node(node=node, interface_name='test', operation_name='op')
     graph.add_tasks(task)
     return graph

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/dd5bfa93/tests/orchestrator/context/test_toolbelt.py
----------------------------------------------------------------------
diff --git a/tests/orchestrator/context/test_toolbelt.py b/tests/orchestrator/context/test_toolbelt.py
index f04fd4b..0e35a26 100644
--- a/tests/orchestrator/context/test_toolbelt.py
+++ b/tests/orchestrator/context/test_toolbelt.py
@@ -132,7 +132,7 @@ def test_relationship_tool_belt(workflow_context, executor):
 
     assert isinstance(global_test_holder.get(api.task.OperationTask.NAME_FORMAT.format(
         type='relationship',
-        id=relationship.id,
+        id=relationship.name,
         interface=interface_name,
         operation=operation_name
     )), RelationshipToolBelt)

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/dd5bfa93/tests/orchestrator/workflows/api/test_task.py
----------------------------------------------------------------------
diff --git a/tests/orchestrator/workflows/api/test_task.py b/tests/orchestrator/workflows/api/test_task.py
index 4f5671b..139d239 100644
--- a/tests/orchestrator/workflows/api/test_task.py
+++ b/tests/orchestrator/workflows/api/test_task.py
@@ -43,17 +43,20 @@ class TestOperationTask(object):
         operation_name = 'create'
 
         plugin = mock.models.create_plugin('package', '0.1')
-        plugin.name = 'test_plugin'
+        ctx.model.node.update(plugin)
+
+        plugin_specification = mock.models.create_plugin_specification('package', '0.1')
 
         interface = mock.models.create_interface(
             ctx.service,
             interface_name,
             operation_name,
-            operation_kwargs=dict(plugin=plugin, implementation='op_path'))
+            operation_kwargs=dict(plugin_specification=plugin_specification,
+                                  implementation='op_path'))
 
         node = ctx.model.node.get_by_name(mock.models.DEPENDENT_NODE_NAME)
         node.interfaces = {interface.name: interface}
-        node.plugins = [plugin]
+        node.plugin_specifications = [plugin_specification]
         ctx.model.node.update(node)
         inputs = {'test_input': True}
         max_attempts = 10
@@ -72,7 +75,7 @@ class TestOperationTask(object):
 
         assert api_task.name == api.task.OperationTask.NAME_FORMAT.format(
             type='node',
-            id=node.id,
+            id=node.name,
             interface=interface_name,
             operation=operation_name
         )
@@ -90,18 +93,21 @@ class TestOperationTask(object):
         operation_name = 'preconfigure'
 
         plugin = mock.models.create_plugin('package', '0.1')
-        plugin.name = 'test_plugin'
+        ctx.model.node.update(plugin)
+
+        plugin_specification = mock.models.create_plugin_specification('package', '0.1')
 
         interface = mock.models.create_interface(
             ctx.service,
             interface_name,
             operation_name,
-            operation_kwargs=dict(plugin=plugin, implementation='op_path')
+            operation_kwargs=dict(plugin_specification=plugin_specification,
+                                  implementation='op_path')
         )
 
         relationship = ctx.model.relationship.list()[0]
         relationship.interfaces[interface.name] = interface
-        relationship.source_node.plugins = [plugin]
+        relationship.source_node.plugin_specifications = [plugin_specification]
         inputs = {'test_input': True}
         max_attempts = 10
         retry_interval = 10
@@ -117,7 +123,7 @@ class TestOperationTask(object):
 
         assert api_task.name == api.task.OperationTask.NAME_FORMAT.format(
             type='relationship',
-            id=relationship.id,
+            id=relationship.name,
             interface=interface_name,
             operation=operation_name
         )
@@ -134,18 +140,21 @@ class TestOperationTask(object):
         operation_name = 'preconfigure'
 
         plugin = mock.models.create_plugin('package', '0.1')
-        plugin.name = 'test_plugin'
+        ctx.model.node.update(plugin)
+
+        plugin_specification = mock.models.create_plugin_specification('package', '0.1')
 
         interface = mock.models.create_interface(
             ctx.service,
             interface_name,
             operation_name,
-            operation_kwargs=dict(plugin=plugin, implementation='op_path')
+            operation_kwargs=dict(plugin_specification=plugin_specification,
+                                  implementation='op_path')
         )
 
         relationship = ctx.model.relationship.list()[0]
         relationship.interfaces[interface.name] = interface
-        relationship.target_node.plugins = [plugin]
+        relationship.target_node.plugin_specifications = [plugin_specification]
         inputs = {'test_input': True}
         max_attempts = 10
         retry_interval = 10
@@ -162,7 +171,7 @@ class TestOperationTask(object):
 
         assert api_task.name == api.task.OperationTask.NAME_FORMAT.format(
             type='relationship',
-            id=relationship.id,
+            id=relationship.name,
             interface=interface_name,
             operation=operation_name
         )

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/dd5bfa93/tests/orchestrator/workflows/builtin/test_execute_operation.py
----------------------------------------------------------------------
diff --git a/tests/orchestrator/workflows/builtin/test_execute_operation.py b/tests/orchestrator/workflows/builtin/test_execute_operation.py
index 0e5a506..e2a8ef0 100644
--- a/tests/orchestrator/workflows/builtin/test_execute_operation.py
+++ b/tests/orchestrator/workflows/builtin/test_execute_operation.py
@@ -57,7 +57,7 @@ def test_execute_operation(ctx):
     assert len(execute_tasks) == 1
     assert execute_tasks[0].name == task.OperationTask.NAME_FORMAT.format(
         type='node',
-        id=node.id,
+        id=node.name,
         interface=interface_name,
         operation=operation_name
     )

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/dd5bfa93/tests/orchestrator/workflows/core/test_task.py
----------------------------------------------------------------------
diff --git a/tests/orchestrator/workflows/core/test_task.py b/tests/orchestrator/workflows/core/test_task.py
index d03597d..49f020f 100644
--- a/tests/orchestrator/workflows/core/test_task.py
+++ b/tests/orchestrator/workflows/core/test_task.py
@@ -83,18 +83,23 @@ class TestOperationTask(object):
         return api_task, core_task
 
     def test_node_operation_task_creation(self, ctx):
-        storage_plugin = mock.models.create_plugin(package_name='p1', package_version='0.1')
-        storage_plugin_other = mock.models.create_plugin(package_name='p0', package_version='0.0')
-        ctx.model.plugin.put(storage_plugin_other)
+        storage_plugin = mock.models.create_plugin(
+            package_name='p1', package_version='0.1')
+        storage_plugin_specification = mock.models.create_plugin_specification(
+            package_name='p1', package_version='0.1')
+        storage_plugin_specification_other = mock.models.create_plugin(
+            package_name='p0', package_version='0.0')
         ctx.model.plugin.put(storage_plugin)
+        ctx.model.plugin_specification.put(storage_plugin_specification_other)
+        ctx.model.plugin_specification.put(storage_plugin_specification)
         node = ctx.model.node.get_by_name(mock.models.DEPENDENCY_NODE_NAME)
         node_template = node.node_template
-        node_template.plugins = [storage_plugin]
+        node_template.plugin_specifications = [storage_plugin_specification]
         interface = mock.models.create_interface(
             node.service,
             NODE_INTERFACE_NAME,
             NODE_OPERATION_NAME,
-            operation_kwargs=dict(plugin=storage_plugin)
+            operation_kwargs=dict(plugin_specification=storage_plugin_specification)
         )
         node.interfaces[interface.name] = interface
         ctx.model.node_template.update(node_template)

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/dd5bfa93/tests/resources/service-templates/tosca-simple-1.0/node-cellar/workflows.py
----------------------------------------------------------------------
diff --git a/tests/resources/service-templates/tosca-simple-1.0/node-cellar/workflows.py b/tests/resources/service-templates/tosca-simple-1.0/node-cellar/workflows.py
index 4f73c50..94ee824 100644
--- a/tests/resources/service-templates/tosca-simple-1.0/node-cellar/workflows.py
+++ b/tests/resources/service-templates/tosca-simple-1.0/node-cellar/workflows.py
@@ -20,7 +20,6 @@ def maintenance(ctx, graph, enabled):
             graph.add_tasks(OperationTask.for_node(node=node,
                                                    interface_name=INTERFACE_NAME,
                                                    operation_name=ENABLE_OPERATION_NAME if enabled
-                                                   else DISABLE_OPERATION_NAME,
-                                                   dry=True))
+                                                   else DISABLE_OPERATION_NAME))
         except TaskException:
             pass

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/dd5bfa93/tests/storage/__init__.py
----------------------------------------------------------------------
diff --git a/tests/storage/__init__.py b/tests/storage/__init__.py
index f33205f..e836e2a 100644
--- a/tests/storage/__init__.py
+++ b/tests/storage/__init__.py
@@ -12,35 +12,17 @@
 # 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.
-import os
-import platform
+
 from shutil import rmtree
 from tempfile import mkdtemp
 
 from sqlalchemy import (
     create_engine,
     orm,
-    Column,
-    Text,
-    Integer,
     pool
 )
 
-
-from aria.modeling import (
-    models,
-    types as modeling_types,
-    bases
-)
-from aria import modeling
-
-
-class MockModel(models.aria_declarative_base, bases.ModelMixin): #pylint: disable=abstract-method
-    __tablename__ = 'mock_model'
-    model_dict = Column(modeling_types.Dict)
-    model_list = Column(modeling_types.List)
-    value = Column(Integer)
-    name = Column(Text)
+from aria.modeling import models
 
 
 class TestFileSystem(object):

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/dd5bfa93/tests/storage/test_instrumentation.py
----------------------------------------------------------------------
diff --git a/tests/storage/test_instrumentation.py b/tests/storage/test_instrumentation.py
index 3ef74e4..4188fcd 100644
--- a/tests/storage/test_instrumentation.py
+++ b/tests/storage/test_instrumentation.py
@@ -17,7 +17,7 @@ import pytest
 from sqlalchemy import Column, Text, Integer, event
 
 from aria.modeling import (
-    bases,
+    mixins,
     types as modeling_types,
     models
 )
@@ -28,7 +28,7 @@ from aria.storage import (
     instrumentation
 )
 
-from ..storage import release_sqlite_storage, init_inmemory_model_storage
+from . import release_sqlite_storage, init_inmemory_model_storage
 
 STUB = instrumentation._STUB
 Value = instrumentation._Value
@@ -337,7 +337,7 @@ def storage():
     release_sqlite_storage(result)
 
 
-class _MockModel(bases.ModelMixin):
+class _MockModel(mixins.ModelMixin):
     name = Column(Text)
     dict1 = Column(modeling_types.Dict)
     dict2 = Column(modeling_types.Dict)
@@ -356,7 +356,7 @@ class MockModel2(_MockModel, models.aria_declarative_base):
     __tablename__ = 'mock_model_2'
 
 
-class StrictMockModel(bases.ModelMixin, models.aria_declarative_base):
+class StrictMockModel(mixins.ModelMixin, models.aria_declarative_base):
     __tablename__ = 'strict_mock_model'
 
     strict_dict = Column(modeling_types.StrictDict(basestring, basestring))

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/dd5bfa93/tests/storage/test_model_storage.py
----------------------------------------------------------------------
diff --git a/tests/storage/test_model_storage.py b/tests/storage/test_model_storage.py
deleted file mode 100644
index bb778d4..0000000
--- a/tests/storage/test_model_storage.py
+++ /dev/null
@@ -1,102 +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.
-
-import pytest
-
-from aria.storage import (
-    ModelStorage,
-    exceptions,
-    sql_mapi
-)
-from aria import (application_model_storage, modeling)
-from ..storage import (release_sqlite_storage, init_inmemory_model_storage)
-
-from . import MockModel
-
-
-@pytest.fixture
-def storage():
-    base_storage = ModelStorage(sql_mapi.SQLAlchemyModelAPI,
-                                initiator=init_inmemory_model_storage)
-    base_storage.register(MockModel)
-    yield base_storage
-    release_sqlite_storage(base_storage)
-
-
-@pytest.fixture(scope='module', autouse=True)
-def module_cleanup():
-    modeling.models.aria_declarative_base.metadata.remove(MockModel.__table__)  #pylint: disable=no-member
-
-
-def test_storage_base(storage):
-    with pytest.raises(AttributeError):
-        storage.non_existent_attribute()
-
-
-def test_model_storage(storage):
-    mock_model = MockModel(value=0, name='model_name')
-    storage.mock_model.put(mock_model)
-
-    assert storage.mock_model.get_by_name('model_name') == mock_model
-
-    assert [mm_from_storage for mm_from_storage in storage.mock_model.iter()] == [mock_model]
-    assert [mm_from_storage for mm_from_storage in storage.mock_model] == [mock_model]
-
-    storage.mock_model.delete(mock_model)
-    with pytest.raises(exceptions.StorageError):
-        storage.mock_model.get(mock_model.id)
-
-
-def test_application_storage_factory():
-    storage = application_model_storage(sql_mapi.SQLAlchemyModelAPI,
-                                        initiator=init_inmemory_model_storage)
-
-    assert storage.service_template
-    assert storage.node_template
-    assert storage.group_template
-    assert storage.policy_template
-    assert storage.substitution_template
-    assert storage.substitution_template_mapping
-    assert storage.requirement_template
-    assert storage.relationship_template
-    assert storage.capability_template
-    assert storage.interface_template
-    assert storage.operation_template
-    assert storage.artifact_template
-
-    assert storage.service
-    assert storage.node
-    assert storage.group
-    assert storage.policy
-    assert storage.substitution
-    assert storage.substitution_mapping
-    assert storage.relationship
-    assert storage.capability
-    assert storage.interface
-    assert storage.operation
-    assert storage.artifact
-
-    assert storage.execution
-    assert storage.service_update
-    assert storage.service_update_step
-    assert storage.service_modification
-    assert storage.plugin
-    assert storage.task
-
-    assert storage.parameter
-    assert storage.type
-    assert storage.metadata
-
-    release_sqlite_storage(storage)