You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@ariatosca.apache.org by em...@apache.org on 2017/02/24 03:18:07 UTC

[1/4] incubator-ariatosca git commit: Modeling refactoring; improved metadata; work on instance models

Repository: incubator-ariatosca
Updated Branches:
  refs/heads/ARIA-105-integrate-modeling 1a0b85ab1 -> f1074fee2


http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/f1074fee/aria/modeling/template_elements.py
----------------------------------------------------------------------
diff --git a/aria/modeling/template_elements.py b/aria/modeling/template_elements.py
deleted file mode 100644
index fded72c..0000000
--- a/aria/modeling/template_elements.py
+++ /dev/null
@@ -1,1426 +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 copy import deepcopy
-from types import FunctionType
-
-from sqlalchemy import (
-    Column,
-    Text,
-    Integer,
-    DateTime,
-    Boolean,
-)
-from sqlalchemy.ext.associationproxy import association_proxy
-from sqlalchemy.ext.declarative import declared_attr
-
-from ..parser import validation
-from ..utils import collections, formatting, console
-from . import (
-    utils,
-    instance_elements,
-    structure,
-    type as aria_type
-)
-
-
-# pylint: disable=no-self-argument, no-member, abstract-method
-
-# region Element templates
-
-class ServiceTemplateBase(structure.ModelMixin):
-    """
-    A service model is a normalized service template from which :class:`ServiceInstance` instances
-    can be created.
-
-    It is usually created by various DSL parsers, such as ARIA's TOSCA extension. However, it
-    can also be created programmatically.
-
-    Properties:
-
-    * :code:`description`: Human-readable description
-    * :code:`meta_data`: :class:`Metadata`
-    * :code:`node_templates`: Dict of :class:`NodeTemplate`
-    * :code:`group_templates`: Dict of :class:`GroupTemplate`
-    * :code:`policy_templates`: Dict of :class:`PolicyTemplate`
-    * :code:`substitution_template`: :class:`SubstituionTemplate`
-    * :code:`inputs`: Dict of :class:`Parameter`
-    * :code:`outputs`: Dict of :class:`Parameter`
-    * :code:`operation_templates`: Dict of :class:`Operation`
-    """
-
-    __tablename__ = 'service_template'
-
-    __private_fields__ = ['substitution_template_fk']
-
-    description = Column(Text)
-
-    # region orchestrator required columns
-
-    created_at = Column(DateTime, nullable=False, index=True)
-    main_file_name = Column(Text)
-    plan = Column(aria_type.Dict, nullable=False)
-    updated_at = Column(DateTime)
-
-    # endregion
-
-    # region foreign keys
-
-    @declared_attr
-    def substitution_template_fk(cls):
-        return cls.foreign_key('substitution_template', nullable=True)
-
-    @declared_attr
-    def meta_data_fk(cls):
-        return cls.foreign_key('meta_data', nullable=True)
-
-    # endregion
-
-    # region one-to-one relationships
-
-    @declared_attr
-    def substitution_template(cls):
-        return cls.one_to_one_relationship('substitution_template')
-
-    @declared_attr
-    def meta_data(cls):
-        return cls.one_to_one_relationship('meta_data')
-
-    # endregion
-
-    # region one-to-many relationships
-
-    @declared_attr
-    def operation_templates(cls):
-        return cls.one_to_many_relationship('operation_template', key_column_name='name')
-
-    # endregion
-
-    # region many-to-many relationships
-
-    @declared_attr
-    def inputs(cls):
-        return cls.many_to_many_relationship('parameter', table_prefix='inputs',
-                                             key_column_name='name')
-
-    @declared_attr
-    def outputs(cls):
-        return cls.many_to_many_relationship('parameter', table_prefix='outputs',
-                                             key_column_name='name')
-
-    # endregion
-
-    @property
-    def as_raw(self):
-        return collections.OrderedDict((
-            ('description', self.description),
-            ('metadata', formatting.as_raw(self.meta_data)),
-            ('node_templates', formatting.as_raw_list(self.node_templates)),
-            ('group_templates', formatting.as_raw_list(self.group_templates)),
-            ('policy_templates', formatting.as_raw_list(self.policy_templates)),
-            ('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))))
-
-    def instantiate(self, context, container):
-        service_instance = instance_elements.ServiceInstanceBase()
-        context.modeling.instance = service_instance
-
-        service_instance.description = deepcopy_with_locators(self.description)
-
-        if self.meta_data is not None:
-            service_instance.meta_data = self.meta_data.instantiate(context, container)
-
-        for node_template in self.node_templates.itervalues():
-            for _ in range(node_template.default_instances):
-                node = node_template.instantiate(context, container)
-                service_instance.nodes[node.id] = node
-
-        utils.instantiate_dict(context, self, service_instance.groups, self.group_templates)
-        utils.instantiate_dict(context, self, service_instance.policies, self.policy_templates)
-        utils.instantiate_dict(context, self, service_instance.operations, self.operation_templates)
-
-        if self.substitution_template is not None:
-            service_instance.substitution = self.substitution_template.instantiate(context,
-                                                                                   container)
-
-        utils.instantiate_dict(context, self, service_instance.inputs, self.inputs)
-        utils.instantiate_dict(context, self, service_instance.outputs, self.outputs)
-
-        for name, the_input in context.modeling.inputs.iteritems():
-            if name not in service_instance.inputs:
-                context.validation.report('input "%s" is not supported' % name)
-            else:
-                service_instance.inputs[name].value = the_input
-
-        return service_instance
-
-    def validate(self, context):
-        if self.meta_data is not None:
-            self.meta_data.validate(context)
-        utils.validate_list_values(context, self.node_templates)
-        utils.validate_list_values(context, self.group_templates)
-        utils.validate_list_values(context, self.policy_templates)
-        if self.substitution_template is not None:
-            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)
-
-    def coerce_values(self, context, container, report_issues):
-        if self.meta_data is not None:
-            self.meta_data.coerce_values(context, container, 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.operation_templates, report_issues)
-
-    def dump(self, context):
-        if self.description is not None:
-            console.puts(context.style.meta(self.description))
-        if self.meta_data is not None:
-            self.meta_data.dump(context)
-        for node_template in self.node_templates.all():
-            node_template.dump(context)
-        for group_template in self.group_templates.all():
-            group_template.dump(context)
-        for policy_template in self.policy_templates.all():
-            policy_template.dump(context)
-        if self.substitution_template is not None:
-            self.substitution_template.dump(context)
-        dump_parameters(context, self.inputs, 'Inputs')
-        dump_parameters(context, self.outputs, 'Outputs')
-        utils.dump_dict_values(context, self.operation_templates, 'Operation templates')
-
-
-class InterfaceTemplateBase(structure.ModelMixin):
-    """
-    A typed set of :class:`OperationTemplate`.
-
-    Properties:
-
-    * :code:`name`: Name
-    * :code:`description`: Description
-    * :code:`type_name`: Must be represented in the :class:`ModelingContext`
-    * :code:`inputs`: Dict of :class:`Parameter`
-    * :code:`operation_templates`: Dict of :class:`OperationTemplate`
-    """
-
-    __tablename__ = 'interface_template'
-
-    __private_fields__ = ['node_template_fk',
-                          'group_template_fk',
-                          'relationship_template_fk']
-
-    # region foreign keys
-
-    @declared_attr
-    def node_template_fk(cls):
-        return cls.foreign_key('node_template', nullable=True)
-
-    @declared_attr
-    def group_template_fk(cls):
-        return cls.foreign_key('group_template', nullable=True)
-
-    @declared_attr
-    def relationship_template_fk(cls):
-        return cls.foreign_key('relationship_template', nullable=True)
-
-    # endregion
-
-    description = Column(Text)
-    type_name = Column(Text)
-
-    # region one-to-many relationships
-
-    @declared_attr
-    def operation_templates(cls):
-        return cls.one_to_many_relationship('operation_template', key_column_name='name')
-
-    # endregion
-
-    # region many-to-many relationships
-
-    @declared_attr
-    def inputs(cls):
-        return cls.many_to_many_relationship('parameter', table_prefix='inputs',
-                                             key_column_name='name')
-
-    @declared_attr
-    def properties(cls):
-        return cls.many_to_many_relationship('parameter', table_prefix='properties',
-                                             key_column_name='name')
-
-    # 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.properties)),  # pylint: disable=no-member
-            # TODO fix self.properties reference
-            ('operation_templates', formatting.as_raw_list(self.operation_templates))))
-
-    def instantiate(self, context, container):
-        interface = instance_elements.InterfaceBase(self.name, self.type_name)
-        interface.description = deepcopy_with_locators(self.description)
-        utils.instantiate_dict(context, container, interface.inputs, self.inputs)
-        utils.instantiate_dict(context, container, interface.operations, self.operation_templates)
-        return interface
-
-    def validate(self, context):
-        if self.type_name:
-            if context.modeling.interface_types.get_descendant(self.type_name) is None:
-                context.validation.report('interface "%s" has an unknown type: %s'
-                                          % (self.name, formatting.safe_repr(self.type_name)),
-                                          level=validation.Issue.BETWEEN_TYPES)
-
-        utils.validate_dict_values(context, self.inputs)
-        utils.validate_dict_values(context, self.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:
-            console.puts(context.style.meta(self.description))
-        with context.style.indent:
-            console.puts('Interface type: %s' % context.style.type(self.type_name))
-            dump_parameters(context, self.inputs, 'Inputs')
-            utils.dump_dict_values(context, self.operation_templates, 'Operation templates')
-
-
-class OperationTemplateBase(structure.ModelMixin):
-    """
-    An operation in a :class:`InterfaceTemplate`.
-
-    Properties:
-
-    * :code:`name`: Name
-    * :code:`description`: Description
-    * :code:`implementation`: Implementation string (interpreted by the orchestrator)
-    * :code:`dependencies`: List of strings (interpreted by the orchestrator)
-    * :code:`executor`: Executor string (interpreted by the orchestrator)
-    * :code:`max_retries`: Maximum number of retries allowed in case of failure
-    * :code:`retry_interval`: Interval between retries
-    * :code:`inputs`: Dict of :class:`Parameter`
-    """
-
-    __tablename__ = 'operation_template'
-
-    __private_fields__ = ['service_template_fk',
-                          'interface_template_fk']
-
-    # region foreign keys
-
-    @declared_attr
-    def service_template_fk(cls):
-        return cls.foreign_key('service_template', nullable=True)
-
-    @declared_attr
-    def interface_template_fk(cls):
-        return cls.foreign_key('interface_template', nullable=True)
-
-    # endregion
-
-    description = Column(Text)
-    implementation = Column(Text)
-    dependencies = Column(aria_type.StrictList(item_cls=basestring))
-    executor = Column(Text)
-    max_retries = Column(Integer)
-    retry_interval = Column(Integer)
-
-    # region orchestrator required columns
-
-    plugin = Column(Text)
-    operation = Column(Boolean)
-
-    # endregion
-
-    # region many-to-many relationships
-
-    @declared_attr
-    def inputs(cls):
-        return cls.many_to_many_relationship('parameter', table_prefix='inputs',
-                                             key_column_name='name')
-
-    # 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 instantiate(self, context, container):
-        operation = instance_elements.OperationBase(self.name)
-        operation.description = deepcopy_with_locators(self.description)
-        operation.implementation = self.implementation
-        operation.dependencies = self.dependencies
-        operation.executor = self.executor
-        operation.max_retries = self.max_retries
-        operation.retry_interval = self.retry_interval
-        utils.instantiate_dict(context, container, operation.inputs, self.inputs)
-        return operation
-
-    def validate(self, context):
-        utils.validate_dict_values(context, self.inputs)
-
-    def coerce_values(self, context, container, report_issues):
-        utils.coerce_dict_values(context, container, self.inputs, report_issues)
-
-    def dump(self, context):
-        console.puts(context.style.node(self.name))
-        if self.description:
-            console.puts(context.style.meta(self.description))
-        with context.style.indent:
-            if self.implementation is not None:
-                console.puts('Implementation: %s' % context.style.literal(self.implementation))
-            if self.dependencies:
-                console.puts('Dependencies: %s' % ', '.join(
-                    (str(context.style.literal(v)) for v in self.dependencies)))
-            if self.executor is not None:
-                console.puts('Executor: %s' % context.style.literal(self.executor))
-            if self.max_retries is not None:
-                console.puts('Max retries: %s' % context.style.literal(self.max_retries))
-            if self.retry_interval is not None:
-                console.puts('Retry interval: %s' % context.style.literal(self.retry_interval))
-            dump_parameters(context, self.inputs, 'Inputs')
-
-
-class ArtifactTemplateBase(structure.ModelMixin):
-    """
-    A file associated with a :class:`NodeTemplate`.
-
-    Properties:
-
-    * :code:`name`: Name
-    * :code:`description`: Description
-    * :code:`type_name`: Must be represented in the :class:`ModelingContext`
-    * :code:`source_path`: Source path (CSAR or repository)
-    * :code:`target_path`: Path at destination machine
-    * :code:`repository_url`: Repository URL
-    * :code:`repository_credential`: Dict of string
-    * :code:`properties`: Dict of :class:`Parameter`
-    """
-
-    __tablename__ = 'artifact_template'
-
-    __private_fields__ = ['node_template_fk']
-
-    # region foreign keys
-
-    @declared_attr
-    def node_template_fk(cls):
-        return cls.foreign_key('node_template')
-
-    # endregion
-
-    description = Column(Text)
-    type_name = Column(Text)
-    source_path = Column(Text)
-    target_path = Column(Text)
-    repository_url = Column(Text)
-    repository_credential = Column(aria_type.StrictDict(basestring, basestring))
-
-    # region many-to-many relationships
-
-    @declared_attr
-    def properties(cls):
-        return cls.many_to_many_relationship('parameter', table_prefix='properties',
-                                             key_column_name='name')
-
-    # 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.iteritems()))))
-
-    def instantiate(self, context, container):
-        artifact = instance_elements.ArtifactBase(self.name, self.type_name, self.source_path)
-        artifact.description = deepcopy_with_locators(self.description)
-        artifact.target_path = self.target_path
-        artifact.repository_url = self.repository_url
-        artifact.repository_credential = self.repository_credential
-        utils.instantiate_dict(context, container, artifact.properties, self.properties)
-        return artifact
-
-    def validate(self, context):
-        if context.modeling.artifact_types.get_descendant(self.type_name) is None:
-            context.validation.report('artifact "%s" has an unknown type: %s'
-                                      % (self.name, formatting.safe_repr(self.type_name)),
-                                      level=validation.Issue.BETWEEN_TYPES)
-
-        utils.validate_dict_values(context, self.properties)
-
-    def coerce_values(self, context, container, report_issues):
-        utils.coerce_dict_values(context, container, self.properties, report_issues)
-
-    def dump(self, context):
-        console.puts(context.style.node(self.name))
-        if self.description:
-            console.puts(context.style.meta(self.description))
-        with context.style.indent:
-            console.puts('Artifact type: %s' % context.style.type(self.type_name))
-            console.puts('Source path: %s' % context.style.literal(self.source_path))
-            if self.target_path is not None:
-                console.puts('Target path: %s' % context.style.literal(self.target_path))
-            if self.repository_url is not None:
-                console.puts('Repository URL: %s' % context.style.literal(self.repository_url))
-            if self.repository_credential:
-                console.puts('Repository credential: %s'
-                             % context.style.literal(self.repository_credential))
-            dump_parameters(context, self.properties)
-
-
-class PolicyTemplateBase(structure.ModelMixin):
-    """
-    Policies can be applied to zero or more :class:`NodeTemplate` or :class:`GroupTemplate`
-    instances.
-
-    Properties:
-
-    * :code:`name`: Name
-    * :code:`description`: Description
-    * :code:`type_name`: Must be represented in the :class:`ModelingContext`
-    * :code:`properties`: Dict of :class:`Parameter`
-    * :code:`target_node_template_names`: Must be represented in the :class:`ServiceModel`
-    * :code:`target_group_template_names`: Must be represented in the :class:`ServiceModel`
-    """
-    __tablename__ = 'policy_template'
-
-    __private_fields__ = ['service_template_fk']
-
-    # region foreign keys
-
-    @declared_attr
-    def service_template_fk(cls):
-        return cls.foreign_key('service_template')
-
-    # endregion
-
-    description = Column(Text)
-    type_name = Column(Text)
-    target_node_template_names = Column(aria_type.StrictList(basestring))
-    target_group_template_names = Column(aria_type.StrictList(basestring))
-
-    # region many-to-one relationships
-
-    @declared_attr
-    def service_template(cls):
-        return cls.many_to_one_relationship('service_template')
-
-    # endregion
-
-    # region many-to-many relationships
-
-    @declared_attr
-    def properties(cls):
-        return cls.many_to_many_relationship('parameter', table_prefix='properties',
-                                             key_column_name='name')
-
-    # endregion
-
-    @property
-    def as_raw(self):
-        return collections.OrderedDict((
-            ('name', self.name),
-            ('description', self.description),
-            ('type_name', self.type_name),
-            ('properties', formatting.as_raw_dict(self.properties)),
-            ('target_node_template_names', self.target_node_template_names),
-            ('target_group_template_names', self.target_group_template_names)))
-
-    def instantiate(self, context, *args, **kwargs):
-        policy = instance_elements.PolicyBase(self.name, self.type_name)
-        utils.instantiate_dict(context, self, policy.properties, self.properties)
-        for node_template_name in self.target_node_template_names:
-            policy.target_node_ids.extend(
-                context.modeling.instance.get_node_ids(node_template_name))
-        for group_template_name in self.target_group_template_names:
-            policy.target_group_ids.extend(
-                context.modeling.instance.get_group_ids(group_template_name))
-        return policy
-
-    def validate(self, context):
-        if context.modeling.policy_types.get_descendant(self.type_name) is None:
-            context.validation.report('policy template "%s" has an unknown type: %s'
-                                      % (self.name, formatting.safe_repr(self.type_name)),
-                                      level=validation.Issue.BETWEEN_TYPES)
-
-        utils.validate_dict_values(context, self.properties)
-
-    def coerce_values(self, context, container, report_issues):
-        utils.coerce_dict_values(context, self, self.properties, report_issues)
-
-    def dump(self, context):
-        console.puts('Policy template: %s' % context.style.node(self.name))
-        if self.description:
-            console.puts(context.style.meta(self.description))
-        with context.style.indent:
-            console.puts('Type: %s' % context.style.type(self.type_name))
-            dump_parameters(context, self.properties)
-            if self.target_node_template_names:
-                console.puts('Target node templates: %s' % ', '.join(
-                    (str(context.style.node(v)) for v in self.target_node_template_names)))
-            if self.target_group_template_names:
-                console.puts('Target group templates: %s' % ', '.join(
-                    (str(context.style.node(v)) for v in self.target_group_template_names)))
-
-
-class MappingTemplateBase(structure.ModelMixin):
-    """
-    Used by :class:`SubstitutionTemplate` to map a capability or a requirement to a node.
-
-    Properties:
-
-    * :code:`mapped_name`: Exposed capability or requirement name
-    * :code:`node_template_name`: Must be represented in the :class:`ServiceModel`
-    * :code:`name`: Name of capability or requirement at the node template
-    """
-
-    __tablename__ = 'mapping_template'
-
-    __private_fields__ = ['substitution_template_fk']
-
-    mapped_name = Column(Text)
-    node_template_name = Column(Text)
-
-    # region foreign keys
-
-    @declared_attr
-    def substitution_template_fk(cls):
-        return cls.foreign_key('substitution_template', nullable=True)
-
-    # endregion
-
-    @property
-    def as_raw(self):
-        return collections.OrderedDict((
-            ('mapped_name', self.mapped_name),
-            ('node_template_name', self.node_template_name),
-            ('name', self.name)))
-
-    def instantiate(self, context, *args, **kwargs):
-        nodes = context.modeling.instance.find_nodes(self.node_template_name)
-        if len(nodes) == 0:
-            context.validation.report(
-                'mapping "%s" refer to node template "%s" but there are no '
-                'node instances' % (self.mapped_name,
-                                    self.node_template_name),
-                level=validation.Issue.BETWEEN_INSTANCES)
-            return None
-        return instance_elements.MappingBase(self.mapped_name, nodes[0].id, self.name)
-
-    def validate(self, context):
-        if not utils.query_has_item_named(context.modeling.model.node_templates,
-                                          self.node_template_name):
-            context.validation.report('mapping "%s" refers to an unknown node template: %s'
-                                      % (
-                                          self.mapped_name,
-                                          formatting.safe_repr(self.node_template_name)),
-                                      level=validation.Issue.BETWEEN_TYPES)
-
-    def dump(self, context):
-        console.puts('%s -> %s.%s' % (context.style.node(self.mapped_name),
-                                      context.style.node(self.node_template_name),
-                                      context.style.node(self.name)))
-
-
-class SubstitutionTemplateBase(structure.ModelMixin):
-    """
-    Used to substitute a single node for the entire deployment.
-
-    Properties:
-
-    * :code:`node_type_name`: Must be represented in the :class:`ModelingContext`
-    * :code:`capability_templates`: Dict of :class:`MappingTemplate`
-    * :code:`requirement_templates`: Dict of :class:`MappingTemplate`
-    """
-
-    __tablename__ = 'substitution_template'
-
-    node_type_name = Column(Text)
-
-    # region one-to-many relationships
-
-    @declared_attr
-    def mappings(cls):
-        return cls.one_to_many_relationship('mapping_template', key_column_name='name')
-
-    # endregion
-
-    @property
-    def as_raw(self):
-        return collections.OrderedDict((
-            ('node_type_name', self.node_type_name),
-            ('mappings', formatting.as_raw_list(self.mappings))))
-
-    def instantiate(self, context, container):
-        substitution = instance_elements.SubstitutionBase(self.node_type_name)
-        utils.instantiate_dict(context, container, substitution.mappings,
-                               self.mappings)
-        return substitution
-
-    def validate(self, context):
-        if context.modeling.node_types.get_descendant(self.node_type_name) is None:
-            context.validation.report('substitution template has an unknown type: %s'
-                                      % formatting.safe_repr(self.node_type_name),
-                                      level=validation.Issue.BETWEEN_TYPES)
-
-        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:
-            console.puts('Node type: %s' % context.style.type(self.node_type_name))
-            utils.dump_dict_values(context, self.mappings,
-                                   'Mappings')
-
-
-# endregion
-
-# region Node templates
-
-class NodeTemplateBase(structure.ModelMixin):
-    """
-    A template for creating zero or more :class:`Node` instances.
-
-    Properties:
-
-    * :code:`name`: Name (will be used as a prefix for node IDs)
-    * :code:`description`: Description
-    * :code:`type_name`: Must be represented in the :class:`ModelingContext`
-    * :code:`default_instances`: Default number nodes that will appear in the deployment plan
-    * :code:`min_instances`: Minimum number nodes that will appear in the deployment plan
-    * :code:`max_instances`: Maximum number nodes that will appear in the deployment plan
-    * :code:`properties`: Dict of :class:`Parameter`
-    * :code:`interface_templates`: Dict of :class:`InterfaceTemplate`
-    * :code:`artifact_templates`: Dict of :class:`ArtifactTemplate`
-    * :code:`capability_templates`: Dict of :class:`CapabilityTemplate`
-    * :code:`requirement_templates`: List of :class:`RequirementTemplate`
-    * :code:`target_node_template_constraints`: List of :class:`FunctionType`
-    """
-
-    __tablename__ = 'node_template'
-
-    __private_fields__ = ['service_template_fk',
-                          'host_fk']
-
-    # region foreign_keys
-
-    @declared_attr
-    def service_template_fk(cls):
-        return cls.foreign_key('service_template')
-
-    @declared_attr
-    def host_fk(cls):
-        return cls.foreign_key('node_template', nullable=True)
-
-    # endregion
-
-    description = Column(Text)
-    type_name = Column(Text)
-    default_instances = Column(Integer, default=1)
-    min_instances = Column(Integer, default=0)
-    max_instances = Column(Integer, default=None)
-    target_node_template_constraints = Column(aria_type.StrictList(FunctionType))
-
-    # region orchestrator required columns
-
-    plugins = Column(aria_type.List)
-    type_hierarchy = Column(aria_type.List)
-
-    @declared_attr
-    def host(cls):
-        return cls.relationship_to_self('host_fk')
-
-    @declared_attr
-    def service_template_name(cls):
-        return association_proxy('service_template', cls.name_column_name())
-
-    # endregion
-
-    # region many-to-one relationships
-
-    @declared_attr
-    def service_template(cls):
-        return cls.many_to_one_relationship('service_template')
-
-    # endregion
-
-    # region one-to-many relationships
-
-    @declared_attr
-    def interface_templates(cls):
-        return cls.one_to_many_relationship('interface_template', key_column_name='name')
-
-    @declared_attr
-    def artifact_templates(cls):
-        return cls.one_to_many_relationship('artifact_template', key_column_name='name')
-
-    @declared_attr
-    def capability_templates(cls):
-        return cls.one_to_many_relationship('capability_template', key_column_name='name')
-
-    # endregion
-
-    # region many-to-many relationships
-
-    @declared_attr
-    def properties(cls):
-        return cls.many_to_many_relationship('parameter', table_prefix='properties',
-                                             key_column_name='name')
-
-    # endregion
-
-    def is_target_node_valid(self, target_node_template):
-        if self.target_node_template_constraints:
-            for node_type_constraint in self.target_node_template_constraints:
-                if not node_type_constraint(target_node_template, self):
-                    return False
-        return True
-
-    @property
-    def as_raw(self):
-        return collections.OrderedDict((
-            ('name', self.name),
-            ('description', self.description),
-            ('type_name', self.type_name),
-            ('default_instances', self.default_instances),
-            ('min_instances', self.min_instances),
-            ('max_instances', self.max_instances),
-            ('properties', formatting.as_raw_dict(self.properties)),
-            ('interface_templates', formatting.as_raw_list(self.interface_templates)),
-            ('artifact_templates', formatting.as_raw_list(self.artifact_templates)),
-            ('capability_templates', formatting.as_raw_list(self.capability_templates)),
-            ('requirement_templates', formatting.as_raw_list(self.requirement_templates))))
-
-    def instantiate(self, context, *args, **kwargs):
-        node = instance_elements.NodeBase(context, self.type_name, self.name)
-        utils.instantiate_dict(context, node, node.properties, self.properties)
-        utils.instantiate_dict(context, node, node.interfaces, self.interface_templates)
-        utils.instantiate_dict(context, node, node.artifacts, self.artifact_templates)
-        utils.instantiate_dict(context, node, node.capabilities, self.capability_templates)
-        return node
-
-    def validate(self, context):
-        if context.modeling.node_types.get_descendant(self.type_name) is None:
-            context.validation.report('node template "%s" has an unknown type: %s'
-                                      % (self.name,
-                                         formatting.safe_repr(self.type_name)),
-                                      level=validation.Issue.BETWEEN_TYPES)
-
-        utils.validate_dict_values(context, self.properties)
-        utils.validate_dict_values(context, self.interface_templates)
-        utils.validate_dict_values(context, self.artifact_templates)
-        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: %s' % context.style.node(self.name))
-        if self.description:
-            console.puts(context.style.meta(self.description))
-        with context.style.indent:
-            console.puts('Type: %s' % context.style.type(self.type_name))
-            console.puts('Instances: %d (%d%s)'
-                         % (self.default_instances,
-                            self.min_instances,
-                            (' to %d' % self.max_instances
-                             if self.max_instances is not None
-                             else ' or more')))
-            dump_parameters(context, self.properties)
-            utils.dump_interfaces(context, self.interface_templates)
-            utils.dump_dict_values(context, self.artifact_templates, 'Artifact tempaltes')
-            utils.dump_dict_values(context, self.capability_templates, 'Capability templates')
-            utils.dump_list_values(context, self.requirement_templates, 'Requirement templates')
-
-
-class GroupTemplateBase(structure.ModelMixin):
-    """
-    A template for creating zero or more :class:`Group` instances.
-
-    Groups are logical containers for zero or more nodes that allow applying zero or more
-    :class:`GroupPolicy` instances to the nodes together.
-
-    Properties:
-
-    * :code:`name`: Name (will be used as a prefix for group IDs)
-    * :code:`description`: Description
-    * :code:`type_name`: Must be represented in the :class:`ModelingContext`
-    * :code:`properties`: Dict of :class:`Parameter`
-    * :code:`interface_templates`: Dict of :class:`InterfaceTemplate`
-    * :code:`policy_templates`: Dict of :class:`GroupPolicyTemplate`
-    * :code:`member_node_template_names`: Must be represented in the :class:`ServiceModel`
-    * :code:`member_group_template_names`: Must be represented in the :class:`ServiceModel`
-    """
-
-    __tablename__ = 'group_template'
-
-    __private_fields__ = ['service_template_fk']
-
-    # region foreign keys
-
-    @declared_attr
-    def service_template_fk(cls):
-        return cls.foreign_key('service_template')
-
-    # endregion
-
-    description = Column(Text)
-    type_name = Column(Text)
-    member_node_template_names = Column(aria_type.StrictList(basestring))
-    member_group_template_names = Column(aria_type.StrictList(basestring))
-
-    # region many-to-one relationships
-
-    @declared_attr
-    def service_template(cls):
-        return cls.many_to_one_relationship('service_template')
-
-    # endregion
-
-    # region one-to-many relationships
-
-    @declared_attr
-    def interface_templates(cls):
-        return cls.one_to_many_relationship('interface_template', key_column_name='name')
-
-    # endregion
-
-    # region many-to-many relationships
-
-    @declared_attr
-    def properties(cls):
-        return cls.many_to_many_relationship('parameter', table_prefix='properties',
-                                             key_column_name='name')
-
-    # endregion
-
-    @property
-    def as_raw(self):
-        return collections.OrderedDict((
-            ('name', self.name),
-            ('description', self.description),
-            ('type_name', self.type_name),
-            ('properties', formatting.as_raw_dict(self.properties)),
-            ('interface_templates', formatting.as_raw_list(self.interface_templates)),
-            ('member_node_template_names', self.member_node_template_names),
-            ('member_group_template_names', self.member_group_template_names1)))
-
-    def instantiate(self, context, *args, **kwargs):
-        group = instance_elements.GroupBase(context, self.type_name, self.name)
-        utils.instantiate_dict(context, self, group.properties, self.properties)
-        utils.instantiate_dict(context, self, group.interfaces, self.interface_templates)
-        utils.instantiate_dict(context, self, group.policies, self.policy_templates)
-        for member_node_template_name in self.member_node_template_names:
-            group.member_node_ids += \
-                context.modeling.instance.get_node_ids(member_node_template_name)
-        for member_group_template_name in self.member_group_template_names:
-            group.member_group_ids += \
-                context.modeling.instance.get_group_ids(member_group_template_name)
-        return group
-
-    def validate(self, context):
-        if context.modeling.group_types.get_descendant(self.type_name) is None:
-            context.validation.report('group template "%s" has an unknown type: %s'
-                                      % (self.name, formatting.safe_repr(self.type_name)),
-                                      level=validation.Issue.BETWEEN_TYPES)
-
-        utils.validate_dict_values(context, self.properties)
-        utils.validate_dict_values(context, self.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: %s' % context.style.node(self.name))
-        if self.description:
-            console.puts(context.style.meta(self.description))
-        with context.style.indent:
-            if self.type_name:
-                console.puts('Type: %s' % context.style.type(self.type_name))
-            dump_parameters(context, self.properties)
-            utils.dump_interfaces(context, self.interface_templates)
-            if self.member_node_template_names:
-                console.puts('Member node templates: %s' % ', '.join(
-                    (str(context.style.node(v)) for v in self.member_node_template_names)))
-
-
-# endregion
-
-# region Relationship templates
-
-class RequirementTemplateBase(structure.ModelMixin):
-    """
-    A requirement for a :class:`NodeTemplate`. During instantiation will be matched with a
-    capability of another
-    node.
-
-    Requirements may optionally contain a :class:`RelationshipTemplate` that will be created between
-    the nodes.
-
-    Properties:
-
-    * :code:`name`: Name
-    * :code:`target_node_type_name`: Must be represented in the :class:`ModelingContext`
-    * :code:`target_node_template_name`: Must be represented in the :class:`ServiceModel`
-    * :code:`target_node_template_constraints`: List of :class:`FunctionType`
-    * :code:`target_capability_type_name`: Type of capability in target node
-    * :code:`target_capability_name`: Name of capability in target node
-    * :code:`relationship_template`: :class:`RelationshipTemplate`
-    """
-
-    __tablename__ = 'requirement_template'
-
-    __private_fields__ = ['node_template_fk']
-
-    # region foreign keys
-
-    @declared_attr
-    def node_template_fk(cls):
-        return cls.foreign_key('node_template', nullable=True)
-
-    # endregion
-
-    target_node_type_name = Column(Text)
-    target_node_template_name = Column(Text)
-    target_node_template_constraints = Column(aria_type.StrictList(FunctionType))
-    target_capability_type_name = Column(Text)
-    target_capability_name = Column(Text)
-    # CHECK: ???
-    relationship_template = Column(Text)  # optional
-
-    # region many-to-one relationships
-
-    @declared_attr
-    def node_template(cls):
-        return cls.many_to_one_relationship('node_template')
-
-    # endregion
-
-    def instantiate(self, context, container):
-        raise NotImplementedError
-
-    def find_target(self, context, source_node_template):
-        # We might already have a specific node template, so we'll just verify it
-        if self.target_node_template_name is not None:
-            target_node_template = \
-                context.modeling.model.node_templates.get(self.target_node_template_name)
-
-            if not source_node_template.is_target_node_valid(target_node_template):
-                context.validation.report('requirement "%s" of node template "%s" is for node '
-                                          'template "%s" but it does not match constraints'
-                                          % (self.name,
-                                             self.target_node_template_name,
-                                             source_node_template.name),
-                                          level=validation.Issue.BETWEEN_TYPES)
-                return None, None
-
-            if self.target_capability_type_name is not None \
-                    or self.target_capability_name is not None:
-                target_node_capability = self.find_target_capability(context,
-                                                                     source_node_template,
-                                                                     target_node_template)
-                if target_node_capability is None:
-                    return None, None
-            else:
-                target_node_capability = None
-
-            return target_node_template, target_node_capability
-
-        # Find first node that matches the type
-        elif self.target_node_type_name is not None:
-            for target_node_template in context.modeling.model.node_templates.itervalues():
-                if not context.modeling.node_types.is_descendant(self.target_node_type_name,
-                                                                 target_node_template.type_name):
-                    continue
-
-                if not source_node_template.is_target_node_valid(target_node_template):
-                    continue
-
-                target_node_capability = self.find_target_capability(context,
-                                                                     source_node_template,
-                                                                     target_node_template)
-                if target_node_capability is None:
-                    continue
-
-                return target_node_template, target_node_capability
-
-        return None, None
-
-    def find_target_capability(self, context, source_node_template, target_node_template):
-        for capability_template in target_node_template.capability_templates.itervalues():
-            if capability_template.satisfies_requirement(context,
-                                                         source_node_template,
-                                                         self,
-                                                         target_node_template):
-                return capability_template
-        return None
-
-    @property
-    def as_raw(self):
-        return collections.OrderedDict((
-            ('name', self.name),
-            ('target_node_type_name', self.target_node_type_name),
-            ('target_node_template_name', self.target_node_template_name),
-            ('target_capability_type_name', self.target_capability_type_name),
-            ('target_capability_name', self.target_capability_name),
-            ('relationship_template', formatting.as_raw(self.relationship_template))))
-
-    def validate(self, context):
-        node_types = context.modeling.node_types
-        capability_types = context.modeling.capability_types
-        if self.target_node_type_name \
-                and node_types.get_descendant(self.target_node_type_name) is None:
-            context.validation.report('requirement "%s" refers to an unknown node type: %s'
-                                      % (self.name,
-                                         formatting.safe_repr(self.target_node_type_name)),
-                                      level=validation.Issue.BETWEEN_TYPES)
-        if self.target_capability_type_name and \
-                capability_types.get_descendant(self.target_capability_type_name is None):
-            context.validation.report('requirement "%s" refers to an unknown capability type: %s'
-                                      % (self.name,
-                                         formatting.safe_repr(self.target_capability_type_name)),
-                                      level=validation.Issue.BETWEEN_TYPES)
-        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))
-        else:
-            console.puts('Requirement:')
-        with context.style.indent:
-            if self.target_node_type_name is not None:
-                console.puts('Target node type: %s'
-                             % context.style.type(self.target_node_type_name))
-            elif self.target_node_template_name is not None:
-                console.puts('Target node template: %s'
-                             % context.style.node(self.target_node_template_name))
-            if self.target_capability_type_name is not None:
-                console.puts('Target capability type: %s'
-                             % context.style.type(self.target_capability_type_name))
-            elif self.target_capability_name is not None:
-                console.puts('Target capability name: %s'
-                             % context.style.node(self.target_capability_name))
-            if self.target_node_template_constraints:
-                console.puts('Target node template constraints:')
-                with context.style.indent:
-                    for constraint in self.target_node_template_constraints:
-                        console.puts(context.style.literal(constraint))
-            if self.relationship_template:
-                console.puts('Relationship:')
-                with context.style.indent:
-                    self.relationship_template.dump(context)
-
-
-class CapabilityTemplateBase(structure.ModelMixin):
-    """
-    A capability of a :class:`NodeTemplate`. Nodes expose zero or more capabilities that can be
-    matched with :class:`Requirement` instances of other nodes.
-
-    Properties:
-
-    * :code:`name`: Name
-    * :code:`description`: Description
-    * :code:`type_name`: Must be represented in the :class:`ModelingContext`
-    * :code:`min_occurrences`: Minimum number of requirement matches required
-    * :code:`max_occurrences`: Maximum number of requirement matches allowed
-    * :code:`valid_source_node_type_names`: Must be represented in the :class:`ModelingContext`
-    * :code:`properties`: Dict of :class:`Parameter`
-    """
-
-    __tablename__ = 'capability_template'
-
-    __private_fields__ = ['node_template_fk']
-
-    # region foreign keys
-
-    @declared_attr
-    def node_template_fk(cls):
-        return cls.foreign_key('node_template', nullable=True)
-
-    # endregion
-
-    description = Column(Text)
-    type_name = Column(Text)
-    min_occurrences = Column(Integer, default=None)  # optional
-    max_occurrences = Column(Integer, default=None)  # optional
-    # CHECK: type?
-    valid_source_node_type_names = Column(Text)
-
-    # region many-to-one relationships
-
-    #@declared_attr
-    #def node_template(cls):
-    #    return cls.many_to_one_relationship('node_template')
-
-    # endregion
-
-    # region many-to-many relationships
-
-    @declared_attr
-    def properties(cls):
-        return cls.many_to_many_relationship('parameter', table_prefix='properties',
-                                             key_column_name='name')
-
-    # endregion
-
-    def satisfies_requirement(self,
-                              context,
-                              source_node_template,
-                              requirement,
-                              target_node_template):
-        # Do we match the required capability type?
-        capability_types = context.modeling.capability_types
-        if not capability_types.is_descendant(requirement.target_capability_type_name,
-                                              self.type_name):
-            return False
-
-        # Are we in valid_source_node_type_names?
-        if self.valid_source_node_type_names:
-            for valid_source_node_type_name in self.valid_source_node_type_names:
-                if not context.modeling.node_types.is_descendant(valid_source_node_type_name,
-                                                                 source_node_template.type_name):
-                    return False
-
-        # Apply requirement constraints
-        if requirement.target_node_template_constraints:
-            for node_type_constraint in requirement.target_node_template_constraints:
-                if not node_type_constraint(target_node_template, source_node_template):
-                    return False
-
-        return True
-
-    @property
-    def as_raw(self):
-        return collections.OrderedDict((
-            ('name', self.name),
-            ('description', self.description),
-            ('type_name', self.type_name),
-            ('min_occurrences', self.min_occurrences),
-            ('max_occurrences', self.max_occurrences),
-            ('valid_source_node_type_names', self.valid_source_node_type_names),
-            ('properties', formatting.as_raw_dict(self.properties))))
-
-    def instantiate(self, context, container):
-        capability = instance_elements.CapabilityBase(self.name, self.type_name)
-        capability.min_occurrences = self.min_occurrences
-        capability.max_occurrences = self.max_occurrences
-        utils.instantiate_dict(context, container, capability.properties, self.properties)
-        return capability
-
-    def validate(self, context):
-        if context.modeling.capability_types.get_descendant(self.type_name) is None:
-            context.validation.report('capability "%s" refers to an unknown type: %s'
-                                      % (self.name, formatting.safe_repr(self.type)),  # pylint: disable=no-member
-                                      #  TODO fix self.type reference
-                                      level=validation.Issue.BETWEEN_TYPES)
-
-        utils.validate_dict_values(context, self.properties)
-
-    def coerce_values(self, context, container, report_issues):
-        utils.coerce_dict_values(context, self, 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('Type: %s' % context.style.type(self.type_name))
-            console.puts(
-                'Occurrences: %d%s'
-                % (self.min_occurrences or 0, (' to %d' % self.max_occurrences)
-                   if self.max_occurrences is not None else ' or more'))
-            if self.valid_source_node_type_names:
-                console.puts('Valid source node types: %s'
-                             % ', '.join((str(context.style.type(v))
-                                          for v in self.valid_source_node_type_names)))
-            dump_parameters(context, self.properties)
-
-
-class RelationshipTemplateBase(structure.ModelMixin):
-    """
-    Optional addition to a :class:`Requirement` in :class:`NodeTemplate` that can be applied when
-    the requirement is matched with a capability.
-
-    Properties:
-
-    * :code:`type_name`: Must be represented in the :class:`ModelingContext`
-    * :code:`template_name`: Must be represented in the :class:`ServiceModel`
-    * :code:`description`: Description
-    * :code:`properties`: Dict of :class:`Parameter`
-    * :code:`source_interface_templates`: Dict of :class:`InterfaceTemplate`
-    * :code:`target_interface_templates`: Dict of :class:`InterfaceTemplate`
-    """
-
-    __tablename__ = 'relationship_template'
-
-    description = Column(Text)
-    type_name = Column(Text)
-
-    # region many-to-many relationships
-
-    @declared_attr
-    def properties(cls):
-        return cls.many_to_many_relationship('parameter', table_prefix='properties',
-                                             key_column_name='name')
-
-    # endregion
-
-    # region one-to-many relationships
-
-    @declared_attr
-    def interface_templates(cls):
-        return cls.one_to_many_relationship('interface_template', key_column_name='name')
-
-    # endregion
-
-    @property
-    def as_raw(self):
-        return collections.OrderedDict((
-            ('type_name', self.type_name),
-            ('template_name', self.template_name),
-            ('description', self.description),
-            ('properties', formatting.as_raw_dict(self.properties)),
-            ('interface_templates', formatting.as_raw_list(self.interface_templates))))
-
-    def instantiate(self, context, container):
-        relationship = instance_elements.RelationshipBase(name=self.template_name,
-                                                          type_name=self.type_name)
-        utils.instantiate_dict(context, container,
-                               relationship.properties, self.properties)
-        utils.instantiate_dict(context, container,
-                               relationship.interfaces, self.interface_templates)
-        return relationship
-
-    def validate(self, context):
-        if context.modeling.relationship_types.get_descendant(self.type_name) is None:
-            context.validation.report(
-                'relationship template "{0}" has an unknown type: {1}'.format(
-                    self.name,
-                    formatting.safe_repr(self.type_name)),  # pylint: disable=no-member
-                # TODO fix self.name reference
-                level=validation.Issue.BETWEEN_TYPES)
-
-        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_name is not None:
-            console.puts('Relationship type: {0}'.format(context.style.type(self.type_name)))
-        else:
-            console.puts('Relationship template: {0}'.format(
-                context.style.node(self.template_name)))
-        if self.description:
-            console.puts(context.style.meta(self.description))
-        with context.style.indent:
-            utils.dump_parameters(context, self.properties)
-            utils.dump_interfaces(context, self.interface_templates, 'Interface templates')
-
-# endregion
-
-
-def dump_parameters(context, parameters, name='Properties'):
-    if not parameters:
-        return
-    console.puts('%s:' % name)
-    with context.style.indent:
-        for parameter_name, parameter in parameters.items():
-            if parameter.type_name is not None:
-                console.puts('%s = %s (%s)' % (context.style.property(parameter_name),
-                                               context.style.literal(parameter.value),
-                                               context.style.type(parameter.type_name)))
-            else:
-                console.puts('%s = %s' % (context.style.property(parameter_name),
-                                          context.style.literal(parameter.value)))
-            if parameter.description:
-                console.puts(context.style.meta(parameter.description))
-
-
-# TODO (left for tal): Move following two methods to some place parser specific
-def deepcopy_with_locators(value):
-    """
-    Like :code:`deepcopy`, but also copies over locators.
-    """
-
-    res = deepcopy(value)
-    copy_locators(res, value)
-    return res
-
-
-def copy_locators(target, source):
-    """
-    Copies over :code:`_locator` for all elements, recursively.
-
-    Assumes that target and source have exactly the same list/dict structure.
-    """
-
-    locator = getattr(source, '_locator', None)
-    if locator is not None:
-        try:
-            setattr(target, '_locator', locator)
-        except AttributeError:
-            pass
-
-    if isinstance(target, list) and isinstance(source, list):
-        for i, _ in enumerate(target):
-            copy_locators(target[i], source[i])
-    elif isinstance(target, dict) and isinstance(source, dict):
-        for k, v in target.items():
-            copy_locators(v, source[k])

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/f1074fee/aria/modeling/utils.py
----------------------------------------------------------------------
diff --git a/aria/modeling/utils.py b/aria/modeling/utils.py
index 944f413..c60ca8f 100644
--- a/aria/modeling/utils.py
+++ b/aria/modeling/utils.py
@@ -83,6 +83,15 @@ def instantiate_dict(context, container, the_dict, from_dict):
             the_dict[name] = value
 
 
+def instantiate_list(context, container, the_list, from_list):
+    if not from_list:
+        return
+    for value in from_list.iteritems():
+        value = value.instantiate(context, container)
+        if value is not None:
+            the_list.append(value)
+
+
 def dump_list_values(context, the_list, name):
     if not the_list:
         return

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/f1074fee/docs/requirements.txt
----------------------------------------------------------------------
diff --git a/docs/requirements.txt b/docs/requirements.txt
index dfd5f79..2a7d4a7 100644
--- a/docs/requirements.txt
+++ b/docs/requirements.txt
@@ -10,5 +10,5 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-Sphinx==1.5b1
-sphinx_rtd_theme==0.1.10a0
+Sphinx==1.5.2
+sphinx_rtd_theme==0.1.10b0

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/f1074fee/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 291d81f..06497ae 100644
--- a/extensions/aria_extension_tosca/simple_v1_0/modeling/__init__.py
+++ b/extensions/aria_extension_tosca/simple_v1_0/modeling/__init__.py
@@ -40,16 +40,14 @@ def create_service_model(context): # pylint: disable=too-many-locals,too-many-br
 
     # Metadata
     metadata = context.presentation.get('service_template', 'metadata')
-    if metadata is not None and False: # TODO
-        metadata_model = Metadata()
-        metadata_model.values['template_name'] = metadata.template_name
-        metadata_model.values['template_author'] = metadata.template_author
-        metadata_model.values['template_version'] = metadata.template_version
+    if metadata is not None:
+        model.meta_data['template_name'] = Metadata(value=metadata.template_name)
+        model.meta_data['template_author'] = Metadata(value=metadata.template_author)
+        model.meta_data['template_version'] = Metadata(value=metadata.template_version)
         custom = metadata.custom
         if custom:
             for name, v in custom.iteritems():
-                metadata_model.values[name] = v
-        model.metadata = metadata_model
+                model.meta_data[name] = Metadata(value=v)
 
     # Types
     create_types(context,


[4/4] incubator-ariatosca git commit: Modeling refactoring; improved metadata; work on instance models

Posted by em...@apache.org.
Modeling refactoring; improved metadata; work on instance models


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

Branch: refs/heads/ARIA-105-integrate-modeling
Commit: f1074fee24d3a9895ad7e35dd8d58a7ab7265b99
Parents: 1a0b85a
Author: Tal Liron <ta...@gmail.com>
Authored: Thu Feb 23 17:07:03 2017 -0600
Committer: Tal Liron <ta...@gmail.com>
Committed: Thu Feb 23 17:07:03 2017 -0600

----------------------------------------------------------------------
 aria/modeling/__init__.py                       |   19 +-
 aria/modeling/elements.py                       |  113 --
 aria/modeling/instance_elements.py              | 1170 --------------
 aria/modeling/model.py                          |   78 +-
 aria/modeling/orchestrator_elements.py          |  468 ------
 aria/modeling/orchestrator_models.py            |  468 ++++++
 aria/modeling/service_instance_models.py        | 1225 +++++++++++++++
 aria/modeling/service_template_models.py        | 1423 +++++++++++++++++
 aria/modeling/shared_service_models.py          |  109 ++
 aria/modeling/structure.py                      |   73 +-
 aria/modeling/template_elements.py              | 1426 ------------------
 aria/modeling/utils.py                          |    9 +
 docs/requirements.txt                           |    4 +-
 .../simple_v1_0/modeling/__init__.py            |   12 +-
 14 files changed, 3325 insertions(+), 3272 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/f1074fee/aria/modeling/__init__.py
----------------------------------------------------------------------
diff --git a/aria/modeling/__init__.py b/aria/modeling/__init__.py
index d0d1c09..393a8cd 100644
--- a/aria/modeling/__init__.py
+++ b/aria/modeling/__init__.py
@@ -19,18 +19,19 @@ from . import (
     structure,
     type,
     model,
-    instance_elements as _instance_base,
-    orchestrator_elements as _orchestrator_base,
-    template_elements as _template_base,
+    service_template_models as _service_template_models_base,
+    service_instance_models as _service_instance_models_base,
+    orchestrator_models as _orchestrator_models_base,
 )
 
 
-_ModelBaseCls = namedtuple('ModelBase', 'instance_elements,'
-                                        'orchestrator_elements,'
-                                        'template_elements')
-model_base = _ModelBaseCls(instance_elements=_instance_base,
-                           orchestrator_elements=_orchestrator_base,
-                           template_elements=_template_base)
+_ModelBaseCls = namedtuple('ModelBase', 'service_template_models,'
+                                        'service_instance_models,'
+                                        'orchestrator_models')
+model_base = _ModelBaseCls(service_template_models=_service_template_models_base,
+                           service_instance_models=_service_instance_models_base,
+                           orchestrator_models=_orchestrator_models_base)
+                           
 
 __all__ = (
     'structure',

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/f1074fee/aria/modeling/elements.py
----------------------------------------------------------------------
diff --git a/aria/modeling/elements.py b/aria/modeling/elements.py
deleted file mode 100644
index 6ca9471..0000000
--- a/aria/modeling/elements.py
+++ /dev/null
@@ -1,113 +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 sqlalchemy import (
-    Column,
-    Text
-)
-
-from ..parser.modeling import utils
-from ..storage import exceptions
-from ..utils.collections import OrderedDict
-from ..utils.console import puts
-
-from . import structure
-from . import type
-
-# pylint: disable=no-self-argument, no-member, abstract-method
-
-
-class ParameterBase(structure.ModelMixin):
-    """
-    Represents a typed value.
-
-    This class is used by both service model and service instance elements.
-    """
-    __tablename__ = 'parameter'
-
-    name = Column(Text, nullable=False)
-    type_name = Column(Text, nullable=False)
-
-    # Check: value type
-    str_value = Column(Text)
-    description = Column(Text)
-
-    @property
-    def as_raw(self):
-        return OrderedDict((
-            ('name', self.name),
-            ('type_name', self.type_name),
-            ('value', self.value),
-            ('description', self.description)))
-
-    @property
-    def value(self):
-        if self.type_name is None:
-            return
-        try:
-            if self.type_name.lower() in ('str', 'unicode'):
-                return self.str_value.decode('utf-8')
-            elif self.type_name.lower() == 'int':
-                return int(self.str_value)
-            elif self.type_name.lower() == 'bool':
-                return bool(self.str_value)
-            elif self.type_name.lower() == 'float':
-                return float(self.str_value)
-            else:
-                return self.str_value
-        except ValueError:
-            raise exceptions.StorageError('Trying to cast {0} to {1} failed'.format(self.str_value,
-                                                                                    self.type))
-
-    @value.setter
-    def value(self, value):
-        self.str_value = unicode(value)
-
-    def instantiate(self, context, container):
-        return ParameterBase(self.type_name, self.str_value, self.description)
-
-    def coerce_values(self, context, container, report_issues):
-        if self.str_value is not None:
-            self.str_value = utils.coerce_value(context, container, self.str_value, report_issues)
-
-
-class MetadataBase(structure.ModelMixin):
-    """
-    Custom values associated with the deployment template and its plans.
-
-    This class is used by both service model and service instance elements.
-
-    Properties:
-
-    * :code:`values`: Dict of custom values
-    """
-    __tablename__ = 'meta_data'
-
-    values = Column(type.StrictDict(key_cls=basestring))
-
-    @property
-    def as_raw(self):
-        return self.values
-
-    def instantiate(self, context, container):
-        metadata = MetadataBase()
-        metadata.values.update(self.values)
-        return metadata
-
-    def dump(self, context):
-        puts('Metadata:')
-        with context.style.indent:
-            for name, value in self.values.iteritems():
-                puts('%s: %s' % (name, context.style.meta(value)))

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/f1074fee/aria/modeling/instance_elements.py
----------------------------------------------------------------------
diff --git a/aria/modeling/instance_elements.py b/aria/modeling/instance_elements.py
deleted file mode 100644
index 1d72fbe..0000000
--- a/aria/modeling/instance_elements.py
+++ /dev/null
@@ -1,1170 +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 sqlalchemy import (
-    Column,
-    Text,
-    Integer,
-    Boolean,
-)
-from sqlalchemy import DateTime
-from sqlalchemy.ext.associationproxy import association_proxy
-from sqlalchemy.ext.declarative import declared_attr
-from sqlalchemy.ext.orderinglist import ordering_list
-
-from aria.parser import validation
-from aria.utils import collections, formatting, console
-
-from . import (
-    utils,
-    structure,
-    type as aria_types
-)
-
-
-# pylint: disable=no-self-argument, no-member, abstract-method
-
-# region Element instances
-
-class ServiceInstanceBase(structure.ModelMixin):
-    __tablename__ = 'service_instance'
-
-    __private_fields__ = ['substituion_fk',
-                          'service_template_fk']
-
-    description = Column(Text)
-    _metadata = Column(Text)
-
-    # region orchestrator required columns
-
-    created_at = Column(DateTime, nullable=False, index=True)
-    permalink = Column(Text)
-    policy_triggers = Column(aria_types.Dict)
-    policy_types = Column(aria_types.Dict)
-    scaling_groups = Column(aria_types.Dict)
-    updated_at = Column(DateTime)
-    workflows = Column(aria_types.Dict)
-
-    @declared_attr
-    def service_template_name(cls):
-        return association_proxy('service_template', 'name')
-
-    # endregion
-
-    # region foreign keys
-
-    @declared_attr
-    def substitution_fk(cls):
-        return cls.foreign_key('substitution', nullable=True)
-
-    @declared_attr
-    def service_template_fk(cls):
-        return cls.foreign_key('service_template')
-
-    # endregion
-
-    # region one-to-one relationships
-
-    @declared_attr
-    def substitution(cls):
-        return cls.one_to_one_relationship('substitution')
-
-    # endregion
-
-    # region many-to-one relationships
-
-    @declared_attr
-    def service_template(cls):
-        return cls.many_to_one_relationship('service_template')
-
-    # endregion
-
-    # region many-to-many relationships
-    @declared_attr
-    def inputs(cls):
-        return cls.many_to_many_relationship('parameter', table_prefix='inputs')
-
-    @declared_attr
-    def outputs(cls):
-        return cls.many_to_many_relationship('parameter', table_prefix='outputs')
-
-    # endregion
-
-    # association proxies
-
-    def satisfy_requirements(self, context):
-        satisfied = True
-        for node in self.nodes.all():
-            if not node.satisfy_requirements(context):
-                satisfied = False
-        return satisfied
-
-    def validate_capabilities(self, context):
-        satisfied = True
-        for node in self.nodes.all():
-            if not node.validate_capabilities(context):
-                satisfied = False
-        return satisfied
-
-    def find_nodes(self, node_template_name):
-        nodes = []
-        for node in self.nodes.all():
-            if node.template_name == node_template_name:
-                nodes.append(node)
-        return collections.FrozenList(nodes)
-
-    def get_node_ids(self, node_template_name):
-        return collections.FrozenList((node.id for node in self.find_nodes(node_template_name)))
-
-    def find_groups(self, group_template_name):
-        groups = []
-        for group in self.groups.all():
-            if group.template_name == group_template_name:
-                groups.append(group)
-        return collections.FrozenList(groups)
-
-    def get_group_ids(self, group_template_name):
-        return collections.FrozenList((group.id for group in self.find_groups(group_template_name)))
-
-    def is_node_a_target(self, context, target_node):
-        for node in self.nodes.all():
-            if self._is_node_a_target(context, node, target_node):
-                return True
-        return False
-
-    def _is_node_a_target(self, context, source_node, target_node):
-        if source_node.relationships:
-            for relationship in source_node.relationships:
-                if relationship.target_node_id == target_node.id:
-                    return True
-                else:
-                    node = context.modeling.instance.nodes.get(relationship.target_node_id)
-                    if node is not None:
-                        if self._is_node_a_target(context, node, target_node):
-                            return True
-        return False
-
-
-class OperationBase(structure.ModelMixin):
-    """
-    An operation in a :class:`Interface`.
-
-    Properties:
-
-    * :code:`name`: Name
-    * :code:`description`: Description
-    * :code:`implementation`: Implementation string (interpreted by the orchestrator)
-    * :code:`dependencies`: List of strings (interpreted by the orchestrator)
-    * :code:`executor`: Executor string (interpreted by the orchestrator)
-    * :code:`max_retries`: Maximum number of retries allowed in case of failure
-    * :code:`retry_interval`: Interval between retries
-    * :code:`inputs`: Dict of :class:`Parameter`
-    """
-    __tablename__ = 'operation'
-
-    __private_fields__ = ['service_template_fk',
-                          'interface_instance_fk']
-
-    # region foreign_keys
-
-    @declared_attr
-    def service_instance_fk(cls):
-        return cls.foreign_key('service_instance', nullable=True)
-
-    @declared_attr
-    def interface_instance_fk(cls):
-        return cls.foreign_key('interface', nullable=True)
-
-    # endregion
-
-    description = Column(Text)
-    implementation = Column(Text)
-    dependencies = Column(aria_types.StrictList(item_cls=basestring))
-
-    executor = Column(Text)
-    max_retries = Column(Integer, default=None)
-    retry_interval = Column(Integer, default=None)
-    plugin = Column(Text)
-    operation = Column(Boolean)
-
-    # region many-to-one relationships
-
-    @declared_attr
-    def service_instance(cls):
-        return cls.many_to_one_relationship('service_instance')
-
-    @declared_attr
-    def interface(cls):
-        return cls.many_to_one_relationship('interface')
-
-    # endregion
-
-    # region many-to-many relationships
-
-    @declared_attr
-    def inputs(cls):
-        return cls.many_to_many_relationship('parameter', table_prefix='inputs')
-
-    # endregion
-
-    @property
-    def as_raw(self):
-        return collections.OrderedDict((
-            ('name', self.name),
-            ('description', self.description),
-            ('implementation', self.implementation),
-            ('dependencies', self.dependencies),
-            ('executor', self.executor),
-            ('max_retries', self.max_retries),
-            ('retry_interval', self.retry_interval),
-            ('inputs', formatting.as_raw_dict(self.inputs))))
-
-    def validate(self, context):
-        utils.validate_dict_values(context, self.inputs)
-
-    def coerce_values(self, context, container, report_issues):
-        utils.coerce_dict_values(context, container, self.inputs, report_issues)
-
-    def dump(self, context):
-        console.puts(context.style.node(self.name))
-        if self.description:
-            console.puts(context.style.meta(self.description))
-        with context.style.indent:
-            if self.implementation is not None:
-                console.puts('Implementation: %s' % context.style.literal(self.implementation))
-            if self.dependencies:
-                console.puts(
-                    'Dependencies: %s'
-                    % ', '.join((str(context.style.literal(v)) for v in self.dependencies)))
-            if self.executor is not None:
-                console.puts('Executor: %s' % context.style.literal(self.executor))
-            if self.max_retries is not None:
-                console.puts('Max retries: %s' % context.style.literal(self.max_retries))
-            if self.retry_interval is not None:
-                console.puts('Retry interval: %s' % context.style.literal(self.retry_interval))
-            utils.dump_parameters(context, self.inputs, 'Inputs')
-
-
-class InterfaceBase(structure.ModelMixin):
-    """
-    A typed set of :class:`Operation`.
-
-    Properties:
-
-    * :code:`name`: Name
-    * :code:`description`: Description
-    * :code:`type_name`: Must be represented in the :class:`ModelingContext`
-    * :code:`inputs`: Dict of :class:`Parameter`
-    * :code:`operations`: Dict of :class:`Operation`
-    """
-    __tablename__ = 'interface'
-
-    __private_fields__ = ['group_fk',
-                          'node_fk',
-                          'relationship_fk']
-
-
-    # region foreign_keys
-
-    @declared_attr
-    def group_fk(cls):
-        return cls.foreign_key('group', nullable=True)
-
-    @declared_attr
-    def node_fk(cls):
-        return cls.foreign_key('node', nullable=True)
-
-    @declared_attr
-    def relationship_fk(cls):
-        return cls.foreign_key('relationship', nullable=True)
-
-    # endregion
-
-    description = Column(Text)
-    type_name = Column(Text)
-    edge = Column(Text)
-
-    # region many-to-one relationships
-
-    @declared_attr
-    def node(cls):
-        return cls.many_to_one_relationship('node')
-
-    @declared_attr
-    def relationship(cls):
-        return cls.many_to_one_relationship('relationship')
-
-    @declared_attr
-    def group(cls):
-        return cls.many_to_one_relationship('group')
-
-    # endregion
-
-    # region many-to-many relationships
-
-    @declared_attr
-    def inputs(cls):
-        return cls.many_to_many_relationship('parameter', table_prefix='inputs')
-
-    # endregion
-
-    @property
-    def as_raw(self):
-        return collections.OrderedDict((
-            ('name', self.name),
-            ('description', self.description),
-            ('type_name', self.type_name),
-            ('inputs', formatting.as_raw_dict(self.inputs)),
-            ('operations', formatting.as_raw_list(self.operations))))
-
-    def validate(self, context):
-        if self.type_name:
-            if context.modeling.interface_types.get_descendant(self.type_name) is None:
-                context.validation.report('interface "%s" has an unknown type: %s'
-                                          % (self.name,
-                                             formatting.safe_repr(self.type_name)),
-                                          level=validation.Issue.BETWEEN_TYPES)
-
-        utils.validate_dict_values(context, self.inputs)
-        utils.validate_dict_values(context, self.operations)
-
-    def coerce_values(self, context, container, report_issues):
-        utils.coerce_dict_values(context, container, self.inputs, report_issues)
-        utils.coerce_dict_values(context, container, self.operations, report_issues)
-
-    def dump(self, context):
-        console.puts(context.style.node(self.name))
-        if self.description:
-            console.puts(context.style.meta(self.description))
-        with context.style.indent:
-            console.puts('Interface type: %s' % context.style.type(self.type_name))
-            utils.dump_parameters(context, self.inputs, 'Inputs')
-            utils.dump_dict_values(context, self.operations, 'Operations')
-
-
-class CapabilityBase(structure.ModelMixin):
-    """
-    A capability of a :class:`Node`.
-
-    An instance of a :class:`CapabilityTemplate`.
-
-    Properties:
-
-    * :code:`name`: Name
-    * :code:`type_name`: Must be represented in the :class:`ModelingContext`
-    * :code:`min_occurrences`: Minimum number of requirement matches required
-    * :code:`max_occurrences`: Maximum number of requirement matches allowed
-    * :code:`properties`: Dict of :class:`Parameter`
-    """
-    __tablename__ = 'capability'
-
-    __private_fields__ = ['node_fk']
-
-    # region foreign_keys
-
-    @declared_attr
-    def node_fk(cls):
-        return cls.foreign_key('node')
-
-    # endregion
-
-    type_name = Column(Text)
-
-    min_occurrences = Column(Integer, default=None) # optional
-    max_occurrences = Column(Integer, default=None) # optional
-    occurrences = Column(Integer, default=0)
-
-    # region many-to-one relationships
-
-    @declared_attr
-    def node(cls):
-        return cls.many_to_one_relationship('node')
-
-    # endregion
-
-    # region many-to-many relationships
-
-    @declared_attr
-    def properties(cls):
-        return cls.many_to_many_relationship('parameter', table_prefix='properties')
-
-    # endregion
-
-    @property
-    def has_enough_relationships(self):
-        if self.min_occurrences is not None:
-            return self.occurrences >= self.min_occurrences
-        return True
-
-    def relate(self):
-        if self.max_occurrences is not None:
-            if self.occurrences == self.max_occurrences:
-                return False
-        self.occurrences += 1
-        return True
-
-    @property
-    def as_raw(self):
-        return collections.OrderedDict((
-            ('name', self.name),
-            ('type_name', self.type_name),
-            ('properties', formatting.as_raw_dict(self.properties))))
-
-    def validate(self, context):
-        if context.modeling.capability_types.get_descendant(self.type_name) is None:
-            context.validation.report('capability "%s" has an unknown type: %s'
-                                      % (self.name,
-                                         formatting.safe_repr(self.type_name)),
-                                      level=validation.Issue.BETWEEN_TYPES)
-
-        utils.validate_dict_values(context, self.properties)
-
-    def coerce_values(self, context, container, report_issues):
-        utils.coerce_dict_values(context, container, self.properties, report_issues)
-
-    def dump(self, context):
-        console.puts(context.style.node(self.name))
-        with context.style.indent:
-            console.puts('Type: %s' % context.style.type(self.type_name))
-            console.puts('Occurrences: %s (%s%s)'
-                         % (self.occurrences,
-                            self.min_occurrences or 0,
-                            (' to %d' % self.max_occurrences)
-                            if self.max_occurrences is not None
-                            else ' or more'))
-            utils.dump_parameters(context, self.properties)
-
-
-class ArtifactBase(structure.ModelMixin):
-    """
-    A file associated with a :class:`Node`.
-
-    Properties:
-
-    * :code:`name`: Name
-    * :code:`description`: Description
-    * :code:`type_name`: Must be represented in the :class:`ModelingContext`
-    * :code:`source_path`: Source path (CSAR or repository)
-    * :code:`target_path`: Path at destination machine
-    * :code:`repository_url`: Repository URL
-    * :code:`repository_credential`: Dict of string
-    * :code:`properties`: Dict of :class:`Parameter`
-    """
-    __tablename__ = 'artifact'
-
-    __private_fields__ = ['node_fk']
-
-    # region foreign_keys
-
-    @declared_attr
-    def node_fk(cls):
-        return cls.foreign_key('node')
-
-    # endregion
-
-    description = Column(Text)
-    type_name = Column(Text)
-    source_path = Column(Text)
-    target_path = Column(Text)
-    repository_url = Column(Text)
-    repository_credential = Column(aria_types.StrictDict(basestring, basestring))
-
-    # region many-to-one relationships
-
-    @declared_attr
-    def node(cls):
-        return cls.many_to_one_relationship('node')
-
-    # endregion
-
-    # region many-to-many relationships
-
-    @declared_attr
-    def properties(cls):
-        return cls.many_to_many_relationship('parameter', table_prefix='properties')
-
-    # endregion
-
-    @property
-    def as_raw(self):
-        return collections.OrderedDict((
-            ('name', self.name),
-            ('description', self.description),
-            ('type_name', self.type_name),
-            ('source_path', self.source_path),
-            ('target_path', self.target_path),
-            ('repository_url', self.repository_url),
-            ('repository_credential', formatting.as_agnostic(self.repository_credential)),
-            ('properties', formatting.as_raw_dict(self.properties))))
-
-    def validate(self, context):
-        if context.modeling.artifact_types.get_descendant(self.type_name) is None:
-            context.validation.report('artifact "%s" has an unknown type: %s'
-                                      % (self.name,
-                                         formatting.safe_repr(self.type_name)),
-                                      level=validation.Issue.BETWEEN_TYPES)
-        utils.validate_dict_values(context, self.properties)
-
-    def coerce_values(self, context, container, report_issues):
-        utils.coerce_dict_values(context, container, self.properties, report_issues)
-
-    def dump(self, context):
-        console.puts(context.style.node(self.name))
-        if self.description:
-            console.puts(context.style.meta(self.description))
-        with context.style.indent:
-            console.puts('Artifact type: %s' % context.style.type(self.type_name))
-            console.puts('Source path: %s' % context.style.literal(self.source_path))
-            if self.target_path is not None:
-                console.puts('Target path: %s' % context.style.literal(self.target_path))
-            if self.repository_url is not None:
-                console.puts('Repository URL: %s' % context.style.literal(self.repository_url))
-            if self.repository_credential:
-                console.puts('Repository credential: %s'
-                             % context.style.literal(self.repository_credential))
-            utils.dump_parameters(context, self.properties)
-
-
-class PolicyBase(structure.ModelMixin):
-    """
-    An instance of a :class:`PolicyTemplate`.
-
-    Properties:
-
-    * :code:`name`: Name
-    * :code:`type_name`: Must be represented in the :class:`ModelingContext`
-    * :code:`properties`: Dict of :class:`Parameter`
-    * :code:`target_node_ids`: Must be represented in the :class:`ServiceInstance`
-    * :code:`target_group_ids`: Must be represented in the :class:`ServiceInstance`
-    """
-    __tablename__ = 'policy'
-
-    __private_fields__ = ['service_instance_fk']
-
-    # region foreign_keys
-
-    @declared_attr
-    def service_instance_fk(cls):
-        return cls.foreign_key('service_instance')
-
-    # endregion
-
-    type_name = Column(Text)
-    target_node_ids = Column(aria_types.StrictList(basestring))
-    target_group_ids = Column(aria_types.StrictList(basestring))
-
-    # region many-to-one relationships
-
-    @declared_attr
-    def service_instnce(cls):
-        return cls.many_to_one_relationship('service_instance')
-
-    # region many-to-many relationships
-
-    @declared_attr
-    def properties(cls):
-        return cls.many_to_many_relationship('parameter', table_prefix='properties')
-
-    # endregion
-
-    @property
-    def as_raw(self):
-        return collections.OrderedDict((
-            ('name', self.name),
-            ('type_name', self.type_name),
-            ('properties', formatting.as_raw_dict(self.properties)),
-            ('target_node_ids', self.target_node_ids),
-            ('target_group_ids', self.target_group_ids)))
-
-    def validate(self, context):
-        if context.modeling.policy_types.get_descendant(self.type_name) is None:
-            context.validation.report('policy "%s" has an unknown type: %s'
-                                      % (self.name, utils.safe_repr(self.type_name)),
-                                      level=validation.Issue.BETWEEN_TYPES)
-
-        utils.validate_dict_values(context, self.properties)
-
-    def coerce_values(self, context, container, report_issues):
-        utils.coerce_dict_values(context, container, self.properties, report_issues)
-
-    def dump(self, context):
-        console.puts('Policy: %s' % context.style.node(self.name))
-        with context.style.indent:
-            console.puts('Type: %s' % context.style.type(self.type_name))
-            utils.dump_parameters(context, self.properties)
-            if self.target_node_ids:
-                console.puts('Target nodes:')
-                with context.style.indent:
-                    for node_id in self.target_node_ids:
-                        console.puts(context.style.node(node_id))
-            if self.target_group_ids:
-                console.puts('Target groups:')
-                with context.style.indent:
-                    for group_id in self.target_group_ids:
-                        console.puts(context.style.node(group_id))
-
-
-class MappingBase(structure.ModelMixin):
-    """
-    An instance of a :class:`MappingTemplate`.
-
-    Properties:
-
-    * :code:`mapped_name`: Exposed capability or requirement name
-    * :code:`node_id`: Must be represented in the :class:`ServiceInstance`
-    * :code:`name`: Name of capability or requirement at the node
-    """
-    __tablename__ = 'mapping'
-
-    mapped_name = Column(Text)
-    node_id = Column(Text)
-
-    @property
-    def as_raw(self):
-        return collections.OrderedDict((
-            ('mapped_name', self.mapped_name),
-            ('node_id', self.node_id),
-            ('name', self.name)))
-
-    def dump(self, context):
-        console.puts('%s -> %s.%s'
-                     % (context.style.node(self.mapped_name),
-                        context.style.node(self.node_id),
-                        context.style.node(self.name)))
-
-
-class SubstitutionBase(structure.ModelMixin):
-    """
-    An instance of a :class:`SubstitutionTemplate`.
-
-    Properties:
-
-    * :code:`node_type_name`: Must be represented in the :class:`ModelingContext`
-    * :code:`capabilities`: Dict of :class:`Mapping`
-    * :code:`requirements`: Dict of :class:`Mapping`
-    """
-    __tablename__ = 'substitution'
-
-    node_type_name = Column(Text)
-
-    # region many-to-many relationships
-
-    @declared_attr
-    def capabilities(cls):
-        return cls.many_to_many_relationship('mapping', table_prefix='capabilities')
-
-    @declared_attr
-    def requirements(cls):
-        return cls.many_to_many_relationship('mapping',
-                                             table_prefix='requirements',
-                                             relationship_kwargs=dict(lazy='dynamic'))
-
-    # endregion
-
-    @property
-    def as_raw(self):
-        return collections.OrderedDict((
-            ('node_type_name', self.node_type_name),
-            ('capabilities', formatting.as_raw_list(self.capabilities)),
-            ('requirements', formatting.as_raw_list(self.requirements))))
-
-    def validate(self, context):
-        if context.modeling.node_types.get_descendant(self.node_type_name) is None:
-            context.validation.report('substitution "%s" has an unknown type: %s'
-                                      % (self.name,  # pylint: disable=no-member
-                                         # TODO fix self.name reference
-                                         formatting.safe_repr(self.node_type_name)),
-                                      level=validation.Issue.BETWEEN_TYPES)
-
-        utils.validate_dict_values(context, self.capabilities)
-        utils.validate_dict_values(context, self.requirements)
-
-    def coerce_values(self, context, container, report_issues):
-        utils.coerce_dict_values(context, container, self.capabilities, report_issues)
-        utils.coerce_dict_values(context, container, self.requirements, report_issues)
-
-    def dump(self, context):
-        console.puts('Substitution:')
-        with context.style.indent:
-            console.puts('Node type: %s' % context.style.type(self.node_type_name))
-            utils.dump_dict_values(context, self.capabilities, 'Capability mappings')
-            utils.dump_dict_values(context, self.requirements, 'Requirement mappings')
-
-# endregion
-
-
-# region Node instances
-
-class NodeBase(structure.ModelMixin):
-    """
-    An instance of a :class:`NodeTemplate`.
-
-    Nodes may have zero or more :class:`Relationship` instances to other nodes.
-
-    Properties:
-
-    * :code:`id`: Unique ID (prefixed with the template name)
-    * :code:`type_name`: Must be represented in the :class:`ModelingContext`
-    * :code:`template_name`: Must be represented in the :class:`ServiceModel`
-    * :code:`properties`: Dict of :class:`Parameter`
-    * :code:`interfaces`: Dict of :class:`Interface`
-    * :code:`artifacts`: Dict of :class:`Artifact`
-    * :code:`capabilities`: Dict of :class:`CapabilityTemplate`
-    * :code:`relationships`: List of :class:`Relationship`
-    """
-    __tablename__ = 'node'
-
-    __private_fields__ = ['service_instance_fk',
-                          'host_fk',
-                          'node_template_fk']
-
-    # region foreign_keys
-
-    @declared_attr
-    def service_instance_fk(cls):
-        return cls.foreign_key('service_instance')
-
-    @declared_attr
-    def host_fk(cls):
-        return cls.foreign_key('node', nullable=True)
-
-    @declared_attr
-    def node_template_fk(cls):
-        return cls.foreign_key('node_template')
-
-    # endregion
-
-    type_name = Column(Text)
-    template_name = Column(Text)
-
-    # region orchestrator required columns
-
-    runtime_properties = Column(aria_types.Dict)
-    scaling_groups = Column(aria_types.List)
-    state = Column(Text, nullable=False)
-    version = Column(Integer, default=1)
-
-    @declared_attr
-    def plugins(cls):
-        return association_proxy('node_template', 'plugins')
-
-    @declared_attr
-    def host(cls):
-        return cls.relationship_to_self('host_fk')
-
-    @declared_attr
-    def service_instance_name(cls):
-        return association_proxy('service_instance', 'name')
-
-    @property
-    def ip(self):
-        if not self.host_fk:
-            return None
-        host_node = self.host
-        if 'ip' in host_node.runtime_properties:  # pylint: disable=no-member
-            return host_node.runtime_properties['ip']  # pylint: disable=no-member
-        host_node = host_node.node_template  # pylint: disable=no-member
-        host_ip_property = [prop for prop in host_node.properties if prop.name == 'ip']
-        if host_ip_property:
-            return host_ip_property[0].value
-        return None
-
-    @declared_attr
-    def node_template(cls):
-        return cls.many_to_one_relationship('node_template')
-
-    @declared_attr
-    def service_template(cls):
-        return association_proxy('service_instance', 'service_template')
-
-    # endregion
-
-    # region many-to-one relationships
-
-    @declared_attr
-    def service_instance(cls):
-        return cls.many_to_one_relationship('service_instance')
-
-    # endregion
-
-    # region many-to-many relationships
-
-    @declared_attr
-    def properties(cls):
-        return cls.many_to_many_relationship('parameter', table_prefix='properties')
-
-    # endregion
-
-    def satisfy_requirements(self, context):
-        node_template = context.modeling.model.node_templates.get(self.template_name)
-        satisfied = True
-        for i in range(len(node_template.requirement_templates)):
-            requirement_template = node_template.requirement_templates[i]
-
-            # Find target template
-            target_node_template, target_node_capability = \
-                requirement_template.find_target(context, node_template)
-            if target_node_template is not None:
-                satisfied = self._satisfy_capability(context,
-                                                     target_node_capability,
-                                                     target_node_template,
-                                                     requirement_template,
-                                                     requirement_template_index=i)
-            else:
-                context.validation.report('requirement "%s" of node "%s" has no target node '
-                                          'template' % (requirement_template.name,
-                                                        self.id),
-                                          level=validation.Issue.BETWEEN_INSTANCES)
-                satisfied = False
-        return satisfied
-
-    def _satisfy_capability(self, context, target_node_capability, target_node_template,
-                            requirement_template, requirement_template_index):
-        # Find target nodes
-        target_nodes = context.modeling.instance.find_nodes(target_node_template.name)
-        if target_nodes:
-            target_node = None
-            target_capability = None
-
-            if target_node_capability is not None:
-                # Relate to the first target node that has capacity
-                for node in target_nodes:
-                    target_capability = node.capabilities.get(target_node_capability.name)
-                    if target_capability.relate():
-                        target_node = node
-                        break
-            else:
-                # Use first target node
-                target_node = target_nodes[0]
-
-            if target_node is not None:
-                relationship = RelationshipBase(
-                    name=requirement_template.name,
-                    source_requirement_index=requirement_template_index,
-                    target_node_id=target_node.id,
-                    target_capability_name=target_capability.name
-                )
-                self.relationships.append(relationship)
-            else:
-                context.validation.report('requirement "%s" of node "%s" targets node '
-                                          'template "%s" but its instantiated nodes do not '
-                                          'have enough capacity'
-                                          % (requirement_template.name,
-                                             self.id,
-                                             target_node_template.name),
-                                          level=validation.Issue.BETWEEN_INSTANCES)
-                return False
-        else:
-            context.validation.report('requirement "%s" of node "%s" targets node template '
-                                      '"%s" but it has no instantiated nodes'
-                                      % (requirement_template.name,
-                                         self.id,
-                                         target_node_template.name),
-                                      level=validation.Issue.BETWEEN_INSTANCES)
-            return False
-
-    def validate_capabilities(self, context):
-        satisfied = False
-        for capability in self.capabilities.itervalues():
-            if not capability.has_enough_relationships:
-                context.validation.report('capability "%s" of node "%s" requires at least %d '
-                                          'relationships but has %d'
-                                          % (capability.name,
-                                             self.id,
-                                             capability.min_occurrences,
-                                             capability.occurrences),
-                                          level=validation.Issue.BETWEEN_INSTANCES)
-                satisfied = False
-        return satisfied
-
-    @property
-    def as_raw(self):
-        return collections.OrderedDict((
-            ('id', self.id),
-            ('type_name', self.type_name),
-            ('template_name', self.template_name),
-            ('properties', formatting.as_raw_dict(self.properties)),
-            ('interfaces', formatting.as_raw_list(self.interfaces)),
-            ('artifacts', formatting.as_raw_list(self.artifacts)),
-            ('capabilities', formatting.as_raw_list(self.capabilities)),
-            ('relationships', formatting.as_raw_list(self.relationships))))
-
-    def validate(self, context):
-        if len(self.id) > context.modeling.id_max_length:
-            context.validation.report('"%s" has an ID longer than the limit of %d characters: %d'
-                                      % (self.id,
-                                         context.modeling.id_max_length,
-                                         len(self.id)),
-                                      level=validation.Issue.BETWEEN_INSTANCES)
-
-        # TODO: validate that node template is of type?
-
-        utils.validate_dict_values(context, self.properties)
-        utils.validate_dict_values(context, self.interfaces)
-        utils.validate_dict_values(context, self.artifacts)
-        utils.validate_dict_values(context, self.capabilities)
-        utils.validate_list_values(context, self.relationships)
-
-    def coerce_values(self, context, container, report_issues):
-        utils.coerce_dict_values(context, self, self.properties, report_issues)
-        utils.coerce_dict_values(context, self, self.interfaces, report_issues)
-        utils.coerce_dict_values(context, self, self.artifacts, report_issues)
-        utils.coerce_dict_values(context, self, self.capabilities, report_issues)
-        utils.coerce_list_values(context, self, self.relationships, report_issues)
-
-    def dump(self, context):
-        console.puts('Node: %s' % context.style.node(self.id))
-        with context.style.indent:
-            console.puts('Template: %s' % context.style.node(self.template_name))
-            console.puts('Type: %s' % context.style.type(self.type_name))
-            utils.dump_parameters(context, self.properties)
-            utils.dump_interfaces(context, self.interfaces)
-            utils.dump_dict_values(context, self.artifacts, 'Artifacts')
-            utils.dump_dict_values(context, self.capabilities, 'Capabilities')
-            utils.dump_list_values(context, self.relationships, 'Relationships')
-
-
-class GroupBase(structure.ModelMixin):
-    """
-    An instance of a :class:`GroupTemplate`.
-
-    Properties:
-
-    * :code:`id`: Unique ID (prefixed with the template name)
-    * :code:`type_name`: Must be represented in the :class:`ModelingContext`
-    * :code:`template_name`: Must be represented in the :class:`ServiceModel`
-    * :code:`properties`: Dict of :class:`Parameter`
-    * :code:`interfaces`: Dict of :class:`Interface`
-    * :code:`policies`: Dict of :class:`GroupPolicy`
-    * :code:`member_node_ids`: Must be represented in the :class:`ServiceInstance`
-    * :code:`member_group_ids`: Must be represented in the :class:`ServiceInstance`
-    """
-    __tablename__ = 'group'
-
-    __private_fields__ = ['service_instance_fk']
-
-    # region foreign_keys
-
-    @declared_attr
-    def service_instance_fk(cls):
-        return cls.foreign_key('service_instance')
-
-    # endregion
-
-    type_name = Column(Text)
-    template_name = Column(Text)
-    member_node_ids = Column(aria_types.StrictList(basestring))
-    member_group_ids = Column(aria_types.StrictList(basestring))
-
-    # region many-to-one relationships
-
-    @declared_attr
-    def service_instance(cls):
-        return cls.many_to_one_relationship('service_instance')
-
-    # endregion
-
-    # region many-to-many relationships
-
-    @declared_attr
-    def properties(cls):
-        return cls.many_to_many_relationship('parameter', table_prefix='properties')
-
-    # endregion
-
-    @property
-    def as_raw(self):
-        return collections.OrderedDict((
-            ('id', self.id),
-            ('type_name', self.type_name),
-            ('template_name', self.template_name),
-            ('properties', formatting.as_raw_dict(self.properties)),
-            ('interfaces', formatting.as_raw_list(self.interfaces)),
-            ('policies', formatting.as_raw_list(self.policies)),
-            ('member_node_ids', self.member_node_ids),
-            ('member_group_ids', self.member_group_ids)))
-
-    def validate(self, context):
-        if context.modeling.group_types.get_descendant(self.type_name) is None:
-            context.validation.report('group "%s" has an unknown type: %s'
-                                      % (self.name,  # pylint: disable=no-member
-                                         # TODO fix self.name reference
-                                         formatting.safe_repr(self.type_name)),
-                                      level=validation.Issue.BETWEEN_TYPES)
-
-        utils.validate_dict_values(context, self.properties)
-        utils.validate_dict_values(context, self.interfaces)
-        utils.validate_dict_values(context, self.policies)
-
-    def coerce_values(self, context, container, report_issues):
-        utils.coerce_dict_values(context, container, self.properties, report_issues)
-        utils.coerce_dict_values(context, container, self.interfaces, report_issues)
-        utils.coerce_dict_values(context, container, self.policies, report_issues)
-
-    def dump(self, context):
-        console.puts('Group: %s' % context.style.node(self.id))
-        with context.style.indent:
-            console.puts('Type: %s' % context.style.type(self.type_name))
-            console.puts('Template: %s' % context.style.type(self.template_name))
-            utils.dump_parameters(context, self.properties)
-            utils.dump_interfaces(context, self.interfaces)
-            utils.dump_dict_values(context, self.policies, 'Policies')
-            if self.member_node_ids:
-                console.puts('Member nodes:')
-                with context.style.indent:
-                    for node_id in self.member_node_ids:
-                        console.puts(context.style.node(node_id))
-
-# endregion
-
-
-# region Relationship instances
-
-class RelationshipBase(structure.ModelMixin):
-    """
-    Connects :class:`Node` to another node.
-
-    An instance of a :class:`RelationshipTemplate`.
-
-    Properties:
-
-    * :code:`name`: Name (usually the name of the requirement at the source node template)
-    * :code:`source_requirement_index`: Must be represented in the source node template
-    * :code:`target_node_id`: Must be represented in the :class:`ServiceInstance`
-    * :code:`target_capability_name`: Matches the capability at the target node
-    * :code:`type_name`: Must be represented in the :class:`ModelingContext`
-    * :code:`template_name`: Must be represented in the :class:`ServiceModel`
-    * :code:`properties`: Dict of :class:`Parameter`
-    * :code:`source_interfaces`: Dict of :class:`Interface`
-    * :code:`target_interfaces`: Dict of :class:`Interface`
-    """
-    __tablename__ = 'relationship'
-
-    __private_fields__ = ['source_node_fk',
-                          'target_node_fk']
-
-    source_requirement_index = Column(Integer)
-    target_node_id = Column(Text)
-    target_capability_name = Column(Text)
-    type_name = Column(Text)
-    template_name = Column(Text)
-
-    # region orchestrator required columns
-
-    source_position = Column(Integer)
-    target_position = Column(Integer)
-
-    @declared_attr
-    def source_node_fk(cls):
-        return cls.foreign_key('node', nullable=True)
-
-    @declared_attr
-    def source_node(cls):
-        return cls.many_to_one_relationship(
-            'node',
-            'source_node_fk',
-            backreference='outbound_relationships',
-            backref_kwargs=dict(
-                order_by=cls.source_position,
-                collection_class=ordering_list('source_position', count_from=0),
-            )
-        )
-
-    @declared_attr
-    def source_node_name(cls):
-        return association_proxy('source_node', cls.name_column_name())
-
-    @declared_attr
-    def target_node_fk(cls):
-        return cls.foreign_key('node', nullable=True)
-
-    @declared_attr
-    def target_node(cls):
-        return cls.many_to_one_relationship(
-            'node',
-            'target_node_fk',
-            backreference='inbound_relationships',
-            backref_kwargs=dict(
-                order_by=cls.target_position,
-                collection_class=ordering_list('target_position', count_from=0),
-            )
-        )
-
-    @declared_attr
-    def target_node_name(cls):
-        return association_proxy('target_node', cls.name_column_name())
-
-    # endregion
-
-    # region many-to-many relationships
-
-    @declared_attr
-    def properties(cls):
-        return cls.many_to_many_relationship('parameter', table_prefix='properties')
-
-    # endregion
-
-    @property
-    def as_raw(self):
-        return collections.OrderedDict((
-            ('name', self.name),
-            ('source_requirement_index', self.source_requirement_index),
-            ('target_node_id', self.target_node_id),
-            ('target_capability_name', self.target_capability_name),
-            ('type_name', self.type_name),
-            ('template_name', self.template_name),
-            ('properties', formatting.as_raw_dict(self.properties)),
-            ('source_interfaces', formatting.as_raw_list(self.source_interfaces)),
-            ('target_interfaces', formatting.as_raw_list(self.target_interfaces))))
-
-    def validate(self, context):
-        if self.type_name:
-            if context.modeling.relationship_types.get_descendant(self.type_name) is None:
-                context.validation.report('relationship "%s" has an unknown type: %s'
-                                          % (self.name,
-                                             formatting.safe_repr(self.type_name)),
-                                          level=validation.Issue.BETWEEN_TYPES)
-        utils.validate_dict_values(context, self.properties)
-        utils.validate_dict_values(context, self.source_interfaces)
-        utils.validate_dict_values(context, self.target_interfaces)
-
-    def coerce_values(self, context, container, report_issues):
-        utils.coerce_dict_values(context, container, self.properties, report_issues)
-        utils.coerce_dict_values(context, container, self.source_interfaces, report_issues)
-        utils.coerce_dict_values(context, container, self.target_interfaces, report_issues)
-
-    def dump(self, context):
-        if self.name:
-            if self.source_requirement_index is not None:
-                console.puts('%s (%d) ->' % (
-                    context.style.node(self.name),
-                    self.source_requirement_index))
-            else:
-                console.puts('%s ->' % context.style.node(self.name))
-        else:
-            console.puts('->')
-        with context.style.indent:
-            console.puts('Node: %s' % context.style.node(self.target_node_id))
-            if self.target_capability_name is not None:
-                console.puts('Capability: %s' % context.style.node(self.target_capability_name))
-            if self.type_name is not None:
-                console.puts('Relationship type: %s' % context.style.type(self.type_name))
-            if self.template_name is not None:
-                console.puts('Relationship template: %s' % context.style.node(self.template_name))
-            utils.dump_parameters(context, self.properties)
-            utils.dump_interfaces(context, self.source_interfaces, 'Source interfaces')
-            utils.dump_interfaces(context, self.target_interfaces, 'Target interfaces')
-
-# endregion

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/f1074fee/aria/modeling/model.py
----------------------------------------------------------------------
diff --git a/aria/modeling/model.py b/aria/modeling/model.py
index 33a7f6a..8459325 100644
--- a/aria/modeling/model.py
+++ b/aria/modeling/model.py
@@ -16,10 +16,10 @@
 from sqlalchemy.ext.declarative import declarative_base
 
 from . import (
-    template_elements,
-    instance_elements,
-    orchestrator_elements,
-    elements,
+    service_template_models,
+    service_instance_models,
+    orchestrator_models,
+    shared_service_models,
     structure,
 )
 
@@ -66,146 +66,144 @@ aria_declarative_base = declarative_base(cls=structure.ModelIDMixin)
 # pylint: disable=abstract-method
 
 
-# region elements
+# region shared service models
 
-class Parameter(aria_declarative_base, elements.ParameterBase):
+class Parameter(aria_declarative_base, shared_service_models.ParameterBase):
     pass
 
 
-class Metadata(aria_declarative_base, elements.MetadataBase):
+class Metadata(aria_declarative_base, shared_service_models.MetadataBase):
     pass
 
-
 # endregion
 
-# region template models
 
+# region service template models
 
-class MappingTemplate(aria_declarative_base, template_elements.MappingTemplateBase):
+class MappingTemplate(aria_declarative_base, service_template_models.MappingTemplateBase):
     pass
 
 
-class SubstitutionTemplate(aria_declarative_base, template_elements.SubstitutionTemplateBase):
+class SubstitutionTemplate(aria_declarative_base, service_template_models.SubstitutionTemplateBase):
     pass
 
 
-class InterfaceTemplate(aria_declarative_base, template_elements.InterfaceTemplateBase):
+class InterfaceTemplate(aria_declarative_base, service_template_models.InterfaceTemplateBase):
     pass
 
 
-class OperationTemplate(aria_declarative_base, template_elements.OperationTemplateBase):
+class OperationTemplate(aria_declarative_base, service_template_models.OperationTemplateBase):
     pass
 
 
-class ServiceTemplate(aria_declarative_base, template_elements.ServiceTemplateBase):
+class ServiceTemplate(aria_declarative_base, service_template_models.ServiceTemplateBase):
     pass
 
 
-class NodeTemplate(aria_declarative_base, template_elements.NodeTemplateBase):
+class NodeTemplate(aria_declarative_base, service_template_models.NodeTemplateBase):
     pass
 
 
-class GroupTemplate(aria_declarative_base, template_elements.GroupTemplateBase):
+class GroupTemplate(aria_declarative_base, service_template_models.GroupTemplateBase):
     pass
 
 
-class ArtifactTemplate(aria_declarative_base, template_elements.ArtifactTemplateBase):
+class ArtifactTemplate(aria_declarative_base, service_template_models.ArtifactTemplateBase):
     pass
 
 
-class PolicyTemplate(aria_declarative_base, template_elements.PolicyTemplateBase):
+class PolicyTemplate(aria_declarative_base, service_template_models.PolicyTemplateBase):
     pass
 
 
-class RequirementTemplate(aria_declarative_base, template_elements.RequirementTemplateBase):
+class RequirementTemplate(aria_declarative_base, service_template_models.RequirementTemplateBase):
     pass
 
 
-class CapabilityTemplate(aria_declarative_base, template_elements.CapabilityTemplateBase):
+class CapabilityTemplate(aria_declarative_base, service_template_models.CapabilityTemplateBase):
     pass
 
 
-class RelationshipTemplate(aria_declarative_base, template_elements.RelationshipTemplateBase):
+class RelationshipTemplate(aria_declarative_base, service_template_models.RelationshipTemplateBase):
     pass
 
 # endregion
 
 
-# region instance models
+# region service instance models
 
-class Mapping(aria_declarative_base, instance_elements.MappingBase):
+class Mapping(aria_declarative_base, service_instance_models.MappingBase):
     pass
 
 
-class Substitution(aria_declarative_base, instance_elements.SubstitutionBase):
+class Substitution(aria_declarative_base, service_instance_models.SubstitutionBase):
     pass
 
 
-class ServiceInstance(aria_declarative_base, instance_elements.ServiceInstanceBase):
+class ServiceInstance(aria_declarative_base, service_instance_models.ServiceInstanceBase):
     pass
 
 
-class Node(aria_declarative_base, instance_elements.NodeBase):
+class Node(aria_declarative_base, service_instance_models.NodeBase):
     pass
 
 
-class Relationship(aria_declarative_base, instance_elements.RelationshipBase):
+class Relationship(aria_declarative_base, service_instance_models.RelationshipBase):
     pass
 
 
-class Artifact(aria_declarative_base, instance_elements.ArtifactBase):
+class Artifact(aria_declarative_base, service_instance_models.ArtifactBase):
     pass
 
 
-class Group(aria_declarative_base, instance_elements.GroupBase):
+class Group(aria_declarative_base, service_instance_models.GroupBase):
     pass
 
 
-class Interface(aria_declarative_base, instance_elements.InterfaceBase):
+class Interface(aria_declarative_base, service_instance_models.InterfaceBase):
     pass
 
 
-class Operation(aria_declarative_base, instance_elements.OperationBase):
+class Operation(aria_declarative_base, service_instance_models.OperationBase):
     pass
 
 
-class Capability(aria_declarative_base, instance_elements.CapabilityBase):
+class Capability(aria_declarative_base, service_instance_models.CapabilityBase):
     pass
 
 
-class Policy(aria_declarative_base, instance_elements.PolicyBase):
+class Policy(aria_declarative_base, service_instance_models.PolicyBase):
     pass
 
-
 # endregion
 
 
 # region orchestrator models
 
-class Execution(aria_declarative_base, orchestrator_elements.Execution):
+class Execution(aria_declarative_base, orchestrator_models.Execution):
     pass
 
 
 class ServiceInstanceUpdate(aria_declarative_base,
-                            orchestrator_elements.ServiceInstanceUpdateBase):
+                            orchestrator_models.ServiceInstanceUpdateBase):
     pass
 
 
 class ServiceInstanceUpdateStep(aria_declarative_base,
-                                orchestrator_elements.ServiceInstanceUpdateStepBase):
+                                orchestrator_models.ServiceInstanceUpdateStepBase):
     pass
 
 
 class ServiceInstanceModification(aria_declarative_base,
-                                  orchestrator_elements.ServiceInstanceModificationBase):
+                                  orchestrator_models.ServiceInstanceModificationBase):
     pass
 
 
-class Plugin(aria_declarative_base, orchestrator_elements.PluginBase):
+class Plugin(aria_declarative_base, orchestrator_models.PluginBase):
     pass
 
 
-class Task(aria_declarative_base, orchestrator_elements.TaskBase):
+class Task(aria_declarative_base, orchestrator_models.TaskBase):
     pass
 
 # endregion

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/f1074fee/aria/modeling/orchestrator_elements.py
----------------------------------------------------------------------
diff --git a/aria/modeling/orchestrator_elements.py b/aria/modeling/orchestrator_elements.py
deleted file mode 100644
index 5f7a3f2..0000000
--- a/aria/modeling/orchestrator_elements.py
+++ /dev/null
@@ -1,468 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements.  See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License.  You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-"""
-Aria's storage.models module
-Path: aria.storage.models
-
-models module holds aria's models.
-
-classes:
-    * Field - represents a single field.
-    * IterField - represents an iterable field.
-    * Model - abstract model implementation.
-    * Snapshot - snapshots implementation model.
-    * Deployment - deployment implementation model.
-    * DeploymentUpdateStep - deployment update step implementation model.
-    * DeploymentUpdate - deployment update implementation model.
-    * DeploymentModification - deployment modification implementation model.
-    * Execution - execution implementation model.
-    * Node - node implementation model.
-    * Relationship - relationship implementation model.
-    * NodeInstance - node instance implementation model.
-    * RelationshipInstance - relationship instance implementation model.
-    * Plugin - plugin implementation model.
-"""
-from collections import namedtuple
-from datetime import datetime
-
-from sqlalchemy import (
-    Column,
-    Integer,
-    Text,
-    DateTime,
-    Boolean,
-    Enum,
-    String,
-    Float,
-    orm,
-)
-from sqlalchemy.ext.associationproxy import association_proxy
-from sqlalchemy.ext.declarative import declared_attr
-
-from aria.orchestrator.exceptions import TaskAbortException, TaskRetryException
-
-from .type import List, Dict
-from .structure import ModelMixin
-
-__all__ = (
-    'ServiceInstanceUpdateStepBase',
-    'ServiceInstanceUpdateBase',
-    'ServiceInstanceModificationBase',
-    'Execution',
-    'PluginBase',
-    'TaskBase'
-)
-
-# pylint: disable=no-self-argument, no-member, abstract-method
-
-
-class Execution(ModelMixin):
-    """
-    Execution model representation.
-    """
-    # Needed only for pylint. the id will be populated by sqlalcehmy and the proper column.
-    __tablename__ = 'execution'
-
-    __private_fields__ = ['service_instance_fk']
-
-    TERMINATED = 'terminated'
-    FAILED = 'failed'
-    CANCELLED = 'cancelled'
-    PENDING = 'pending'
-    STARTED = 'started'
-    CANCELLING = 'cancelling'
-    FORCE_CANCELLING = 'force_cancelling'
-
-    STATES = [TERMINATED, FAILED, CANCELLED, PENDING, STARTED, CANCELLING, FORCE_CANCELLING]
-    END_STATES = [TERMINATED, FAILED, CANCELLED]
-    ACTIVE_STATES = [state for state in STATES if state not in END_STATES]
-
-    VALID_TRANSITIONS = {
-        PENDING: [STARTED, CANCELLED],
-        STARTED: END_STATES + [CANCELLING],
-        CANCELLING: END_STATES + [FORCE_CANCELLING]
-    }
-
-    @orm.validates('status')
-    def validate_status(self, key, value):
-        """Validation function that verifies execution status transitions are OK"""
-        try:
-            current_status = getattr(self, key)
-        except AttributeError:
-            return
-        valid_transitions = self.VALID_TRANSITIONS.get(current_status, [])
-        if all([current_status is not None,
-                current_status != value,
-                value not in valid_transitions]):
-            raise ValueError('Cannot change execution status from {current} to {new}'.format(
-                current=current_status,
-                new=value))
-        return value
-
-    created_at = Column(DateTime, index=True)
-    started_at = Column(DateTime, nullable=True, index=True)
-    ended_at = Column(DateTime, nullable=True, index=True)
-    error = Column(Text, nullable=True)
-    is_system_workflow = Column(Boolean, nullable=False, default=False)
-    parameters = Column(Dict)
-    status = Column(Enum(*STATES, name='execution_status'), default=PENDING)
-    workflow_name = Column(Text)
-
-    @declared_attr
-    def service_template(cls):
-        return association_proxy('service_instance', 'service_template')
-
-    @declared_attr
-    def service_instance_fk(cls):
-        return cls.foreign_key('service_instance')
-
-    @declared_attr
-    def service_instance(cls):
-        return cls.many_to_one_relationship('service_instance')
-
-    @declared_attr
-    def service_instance_name(cls):
-        return association_proxy('service_instance', cls.name_column_name())
-
-    @declared_attr
-    def service_template_name(cls):
-        return association_proxy('service_instance', 'service_template_name')
-
-    def __str__(self):
-        return '<{0} id=`{1}` (status={2})>'.format(
-            self.__class__.__name__,
-            getattr(self, self.name_column_name()),
-            self.status
-        )
-
-
-class ServiceInstanceUpdateBase(ModelMixin):
-    """
-    Deployment update model representation.
-    """
-    # Needed only for pylint. the id will be populated by sqlalcehmy and the proper column.
-    steps = None
-
-    __tablename__ = 'service_instance_update'
-    __private_fields__ = ['service_instance_fk',
-                          'execution_fk']
-
-    _private_fields = ['execution_fk', 'deployment_fk']
-
-    created_at = Column(DateTime, nullable=False, index=True)
-    service_instance_plan = Column(Dict, nullable=False)
-    service_instance_update_node_instances = Column(Dict)
-    service_instance_update_service_instance = Column(Dict)
-    service_instance_update_nodes = Column(List)
-    modified_entity_ids = Column(Dict)
-    state = Column(Text)
-
-    @declared_attr
-    def execution_fk(cls):
-        return cls.foreign_key('execution', nullable=True)
-
-    @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_instance_fk(cls):
-        return cls.foreign_key('service_instance')
-
-    @declared_attr
-    def service_instance(cls):
-        return cls.many_to_one_relationship('service_instance')
-
-    @declared_attr
-    def service_instance_name(cls):
-        return association_proxy('service_instance', cls.name_column_name())
-
-    def to_dict(self, suppress_error=False, **kwargs):
-        dep_update_dict = super(ServiceInstanceUpdateBase, 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 ServiceInstanceUpdateStepBase(ModelMixin):
-    """
-    Deployment update step model representation.
-    """
-    # Needed only for pylint. the id will be populated by sqlalcehmy and the proper column.
-    __tablename__ = 'service_instance_update_step'
-    __private_fields__ = ['service_instance_update_fk']
-
-    _action_types = namedtuple('ACTION_TYPES', 'ADD, REMOVE, MODIFY')
-    ACTION_TYPES = _action_types(ADD='add', REMOVE='remove', MODIFY='modify')
-    _entity_types = namedtuple(
-        'ENTITY_TYPES',
-        'NODE, RELATIONSHIP, PROPERTY, OPERATION, WORKFLOW, OUTPUT, DESCRIPTION, GROUP, '
-        'POLICY_TYPE, POLICY_TRIGGER, PLUGIN')
-    ENTITY_TYPES = _entity_types(
-        NODE='node',
-        RELATIONSHIP='relationship',
-        PROPERTY='property',
-        OPERATION='operation',
-        WORKFLOW='workflow',
-        OUTPUT='output',
-        DESCRIPTION='description',
-        GROUP='group',
-        POLICY_TYPE='policy_type',
-        POLICY_TRIGGER='policy_trigger',
-        PLUGIN='plugin'
-    )
-
-    action = Column(Enum(*ACTION_TYPES, name='action_type'), nullable=False)
-    entity_id = Column(Text, nullable=False)
-    entity_type = Column(Enum(*ENTITY_TYPES, name='entity_type'), nullable=False)
-
-    @declared_attr
-    def service_instance_update_fk(cls):
-        return cls.foreign_key('service_instance_update')
-
-    @declared_attr
-    def service_instance_update(cls):
-        return cls.many_to_one_relationship('service_instance_update',
-                                            backreference='steps')
-
-    @declared_attr
-    def deployment_update_name(cls):
-        return association_proxy('deployment_update', cls.name_column_name())
-
-    def __hash__(self):
-        return hash((getattr(self, self.id_column_name()), self.entity_id))
-
-    def __lt__(self, other):
-        """
-        the order is 'remove' < 'modify' < 'add'
-        :param other:
-        :return:
-        """
-        if not isinstance(other, self.__class__):
-            return not self >= other
-
-        if self.action != other.action:
-            if self.action == 'remove':
-                return_value = True
-            elif self.action == 'add':
-                return_value = False
-            else:
-                return_value = other.action == 'add'
-            return return_value
-
-        if self.action == 'add':
-            return self.entity_type == 'node' and other.entity_type == 'relationship'
-        if self.action == 'remove':
-            return self.entity_type == 'relationship' and other.entity_type == 'node'
-        return False
-
-
-class ServiceInstanceModificationBase(ModelMixin):
-    """
-    Deployment modification model representation.
-    """
-    __tablename__ = 'service_instance_modification'
-    __private_fields__ = ['service_instance_fk']
-
-    STARTED = 'started'
-    FINISHED = 'finished'
-    ROLLEDBACK = 'rolledback'
-
-    STATES = [STARTED, FINISHED, ROLLEDBACK]
-    END_STATES = [FINISHED, ROLLEDBACK]
-
-    context = Column(Dict)
-    created_at = Column(DateTime, nullable=False, index=True)
-    ended_at = Column(DateTime, index=True)
-    modified_nodes = Column(Dict)
-    node_instances = Column(Dict)
-    status = Column(Enum(*STATES, name='deployment_modification_status'))
-
-    @declared_attr
-    def service_instance_fk(cls):
-        return cls.foreign_key('service_instance')
-
-    @declared_attr
-    def service_instance(cls):
-        return cls.many_to_one_relationship('service_instance',
-                                            backreference='modifications')
-
-    @declared_attr
-    def service_instance_name(cls):
-        return association_proxy('service_instance', cls.name_column_name())
-
-
-class PluginBase(ModelMixin):
-    """
-    Plugin model representation.
-    """
-    __tablename__ = 'plugin'
-
-    archive_name = Column(Text, nullable=False, index=True)
-    distribution = Column(Text)
-    distribution_release = Column(Text)
-    distribution_version = Column(Text)
-    package_name = Column(Text, nullable=False, index=True)
-    package_source = Column(Text)
-    package_version = Column(Text)
-    supported_platform = Column(Text)
-    supported_py_versions = Column(List)
-    uploaded_at = Column(DateTime, nullable=False, index=True)
-    wheels = Column(List, nullable=False)
-
-
-class TaskBase(ModelMixin):
-    """
-    A Model which represents an task
-    """
-    __tablename__ = 'task'
-    __private_fields__ = ['node_fk',
-                          'relationship_fk',
-                          'execution_fk',
-                          'plugin_fk']
-
-    @declared_attr
-    def node_fk(cls):
-        return cls.foreign_key('node', nullable=True)
-
-    @declared_attr
-    def node_name(cls):
-        return association_proxy('node', cls.name_column_name())
-
-    @declared_attr
-    def node(cls):
-        return cls.many_to_one_relationship('node')
-
-    @declared_attr
-    def relationship_fk(cls):
-        return cls.foreign_key('relationship', nullable=True)
-
-    @declared_attr
-    def relationship_name(cls):
-        return association_proxy('relationships', cls.name_column_name())
-
-    @declared_attr
-    def relationship(cls):
-        return cls.many_to_one_relationship('relationship')
-
-    @declared_attr
-    def plugin_fk(cls):
-        return cls.foreign_key('plugin', nullable=True)
-
-    @declared_attr
-    def plugin(cls):
-        return cls.many_to_one_relationship('plugin')
-
-    @declared_attr
-    def execution_fk(cls):
-        return cls.foreign_key('execution', nullable=True)
-
-    @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())
-
-    PENDING = 'pending'
-    RETRYING = 'retrying'
-    SENT = 'sent'
-    STARTED = 'started'
-    SUCCESS = 'success'
-    FAILED = 'failed'
-    STATES = (
-        PENDING,
-        RETRYING,
-        SENT,
-        STARTED,
-        SUCCESS,
-        FAILED,
-    )
-
-    WAIT_STATES = [PENDING, RETRYING]
-    END_STATES = [SUCCESS, FAILED]
-
-    RUNS_ON_SOURCE = 'source'
-    RUNS_ON_TARGET = 'target'
-    RUNS_ON_NODE_INSTANCE = 'node_instance'
-    RUNS_ON = (RUNS_ON_NODE_INSTANCE, RUNS_ON_SOURCE, RUNS_ON_TARGET)
-
-    @orm.validates('max_attempts')
-    def validate_max_attempts(self, _, value):                                  # pylint: disable=no-self-use
-        """Validates that max attempts is either -1 or a positive number"""
-        if value < 1 and value != TaskBase.INFINITE_RETRIES:
-            raise ValueError('Max attempts can be either -1 (infinite) or any positive number. '
-                             'Got {value}'.format(value=value))
-        return value
-
-    INFINITE_RETRIES = -1
-
-    status = Column(Enum(*STATES, name='status'), default=PENDING)
-
-    due_at = Column(DateTime, default=datetime.utcnow)
-    started_at = Column(DateTime, default=None)
-    ended_at = Column(DateTime, default=None)
-    max_attempts = Column(Integer, default=1)
-    retry_count = Column(Integer, default=0)
-    retry_interval = Column(Float, default=0)
-    ignore_failure = Column(Boolean, default=False)
-
-    # Operation specific fields
-    implementation = Column(String)
-    inputs = Column(Dict)
-    # 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
-    def runs_on(self):
-        if self._runs_on == self.RUNS_ON_NODE_INSTANCE:
-            return self.node
-        elif self._runs_on == self.RUNS_ON_SOURCE:
-            return self.relationship.source_node  # pylint: disable=no-member
-        elif self._runs_on == self.RUNS_ON_TARGET:
-            return self.relationship.target_node  # pylint: disable=no-member
-        return None
-
-    @property
-    def actor(self):
-        """
-        Return the actor of the task
-        :return:
-        """
-        return self.node or self.relationship
-
-    @classmethod
-    def as_node_instance(cls, instance, runs_on, **kwargs):
-        return cls(node=instance, _runs_on=runs_on, **kwargs)
-
-    @classmethod
-    def as_relationship_instance(cls, instance, runs_on, **kwargs):
-        return cls(relationship=instance, _runs_on=runs_on, **kwargs)
-
-    @staticmethod
-    def abort(message=None):
-        raise TaskAbortException(message)
-
-    @staticmethod
-    def retry(message=None, retry_interval=None):
-        raise TaskRetryException(message, retry_interval=retry_interval)


[2/4] incubator-ariatosca git commit: Modeling refactoring; improved metadata; work on instance models

Posted by em...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/f1074fee/aria/modeling/service_template_models.py
----------------------------------------------------------------------
diff --git a/aria/modeling/service_template_models.py b/aria/modeling/service_template_models.py
new file mode 100644
index 0000000..9055c66
--- /dev/null
+++ b/aria/modeling/service_template_models.py
@@ -0,0 +1,1423 @@
+# 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 copy import deepcopy
+from types import FunctionType
+
+from sqlalchemy import (
+    Column,
+    Text,
+    Integer,
+    DateTime,
+    Boolean,
+)
+from sqlalchemy.ext.associationproxy import association_proxy
+from sqlalchemy.ext.declarative import declared_attr
+
+from ..parser import validation
+from ..utils import collections, formatting, console
+from . import (
+    utils,
+    structure,
+    type as aria_type
+)
+
+# pylint: disable=no-self-argument, no-member, abstract-method
+
+
+class ServiceTemplateBase(structure.TemplateModelMixin):
+    """
+    A service template is a normalized blueprint from which :class:`ServiceInstance` instances can
+    be created.
+
+    It is usually created by various DSL parsers, such as ARIA's TOSCA extension. However, it
+    can also be created programmatically.
+
+    Properties:
+
+    * :code:`description`: Human-readable description
+    * :code:`meta_data`: Dict of :class:`Metadata`
+    * :code:`node_templates`: List of :class:`NodeTemplate`
+    * :code:`group_templates`: List of :class:`GroupTemplate`
+    * :code:`policy_templates`: List of :class:`PolicyTemplate`
+    * :code:`substitution_template`: :class:`SubstituionTemplate`
+    * :code:`inputs`: Dict of :class:`Parameter`
+    * :code:`outputs`: Dict of :class:`Parameter`
+    * :code:`operation_templates`: Dict of :class:`OperationTemplate`
+    """
+
+    __tablename__ = 'service_template'
+
+    __private_fields__ = ['substitution_template_fk']
+
+    description = Column(Text)
+
+    # region orchestrator required columns
+
+    created_at = Column(DateTime, nullable=False, index=True)
+    main_file_name = Column(Text)
+    plan = Column(aria_type.Dict, nullable=False)
+    updated_at = Column(DateTime)
+
+    # endregion
+
+    # region foreign keys
+
+    @declared_attr
+    def substitution_template_fk(cls):
+        return cls.foreign_key('substitution_template', nullable=True)
+
+    # endregion
+
+    # region one-to-one relationships
+
+    @declared_attr
+    def substitution_template(cls):
+        return cls.one_to_one_relationship('substitution_template')
+
+    # endregion
+
+    # region one-to-many relationships
+
+    @declared_attr
+    def operation_templates(cls):
+        return cls.one_to_many_relationship('operation_template', key_column_name='name')
+
+    # endregion
+
+    # region many-to-many relationships
+
+    @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', key_column_name='name')
+
+    @declared_attr
+    def inputs(cls):
+        return cls.many_to_many_relationship('parameter', table_prefix='inputs',
+                                             key_column_name='name')
+
+    @declared_attr
+    def outputs(cls):
+        return cls.many_to_many_relationship('parameter', table_prefix='outputs',
+                                             key_column_name='name')
+
+    # endregion
+
+    @property
+    def as_raw(self):
+        return collections.OrderedDict((
+            ('description', self.description),
+            ('metadata', formatting.as_raw_dict(self.meta_data)),
+            ('node_templates', formatting.as_raw_list(self.node_templates)),
+            ('group_templates', formatting.as_raw_list(self.group_templates)),
+            ('policy_templates', formatting.as_raw_list(self.policy_templates)),
+            ('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))))
+
+    def instantiate(self, context, container):
+        from . import model
+        service_instance = model.ServiceInstance()
+        context.modeling.instance = service_instance
+
+        service_instance.description = deepcopy_with_locators(self.description)
+
+        utils.instantiate_dict(context, self, service_instance.meta_data, self.meta_data)
+
+        for node_template in self.node_templates:
+            for _ in range(node_template.default_instances):
+                node = node_template.instantiate(context, container)
+                service_instance.nodes[node.id] = node
+
+        utils.instantiate_list(context, self, service_instance.groups, self.group_templates)
+        utils.instantiate_list(context, self, service_instance.policies, self.policy_templates)
+        utils.instantiate_dict(context, self, service_instance.operations, self.operation_templates)
+
+        if self.substitution_template is not None:
+            service_instance.substitution = self.substitution_template.instantiate(context,
+                                                                                   container)
+
+        utils.instantiate_dict(context, self, service_instance.inputs, self.inputs)
+        utils.instantiate_dict(context, self, service_instance.outputs, self.outputs)
+
+        for name, the_input in context.modeling.inputs.iteritems():
+            if name not in service_instance.inputs:
+                context.validation.report('input "%s" is not supported' % name)
+            else:
+                service_instance.inputs[name].value = the_input
+
+        return service_instance
+
+    def validate(self, context):
+        utils.validate_dict_values(context, self.meta_data)
+        utils.validate_list_values(context, self.node_templates)
+        utils.validate_list_values(context, self.group_templates)
+        utils.validate_list_values(context, self.policy_templates)
+        if self.substitution_template is not None:
+            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)
+
+    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.operation_templates, report_issues)
+
+    def dump(self, context):
+        if self.description is not None:
+            console.puts(context.style.meta(self.description))
+        dump_parameters(context, self.meta_data, 'Metadata')
+        for node_template in self.node_templates.all():
+            node_template.dump(context)
+        for group_template in self.group_templates.all():
+            group_template.dump(context)
+        for policy_template in self.policy_templates.all():
+            policy_template.dump(context)
+        if self.substitution_template is not None:
+            self.substitution_template.dump(context)
+        dump_parameters(context, self.inputs, 'Inputs')
+        dump_parameters(context, self.outputs, 'Outputs')
+        utils.dump_dict_values(context, self.operation_templates, 'Operation templates')
+
+
+class InterfaceTemplateBase(structure.TemplateModelMixin):
+    """
+    A typed set of :class:`OperationTemplate`.
+
+    Properties:
+
+    * :code:`name`: Name
+    * :code:`description`: Description
+    * :code:`type_name`: Must be represented in the :class:`ModelingContext`
+    * :code:`inputs`: Dict of :class:`Parameter`
+    * :code:`operation_templates`: Dict of :class:`OperationTemplate`
+    """
+
+    __tablename__ = 'interface_template'
+
+    __private_fields__ = ['node_template_fk',
+                          'group_template_fk',
+                          'relationship_template_fk']
+
+    # region foreign keys
+
+    @declared_attr
+    def node_template_fk(cls):
+        return cls.foreign_key('node_template', nullable=True)
+
+    @declared_attr
+    def group_template_fk(cls):
+        return cls.foreign_key('group_template', nullable=True)
+
+    @declared_attr
+    def relationship_template_fk(cls):
+        return cls.foreign_key('relationship_template', nullable=True)
+
+    # endregion
+
+    description = Column(Text)
+    type_name = Column(Text)
+
+    # region one-to-many relationships
+
+    @declared_attr
+    def operation_templates(cls):
+        return cls.one_to_many_relationship('operation_template', key_column_name='name')
+
+    # endregion
+
+    # region many-to-many relationships
+
+    @declared_attr
+    def inputs(cls):
+        return cls.many_to_many_relationship('parameter', table_prefix='inputs',
+                                             key_column_name='name')
+
+    @declared_attr
+    def properties(cls):
+        return cls.many_to_many_relationship('parameter', table_prefix='properties',
+                                             key_column_name='name')
+
+    # 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.properties)),  # pylint: disable=no-member
+            # TODO fix self.properties reference
+            ('operation_templates', formatting.as_raw_list(self.operation_templates))))
+
+    def instantiate(self, context, container):
+        from . import model
+        interface = model.Interface(name=self.name,
+                                    type_name=self.type_name)
+        interface.description = deepcopy_with_locators(self.description)
+        utils.instantiate_dict(context, container, interface.inputs, self.inputs)
+        utils.instantiate_dict(context, container, interface.operations, self.operation_templates)
+        return interface
+
+    def validate(self, context):
+        if self.type_name:
+            if context.modeling.interface_types.get_descendant(self.type_name) is None:
+                context.validation.report('interface "%s" has an unknown type: %s'
+                                          % (self.name, formatting.safe_repr(self.type_name)),
+                                          level=validation.Issue.BETWEEN_TYPES)
+
+        utils.validate_dict_values(context, self.inputs)
+        utils.validate_dict_values(context, self.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:
+            console.puts(context.style.meta(self.description))
+        with context.style.indent:
+            console.puts('Interface type: %s' % context.style.type(self.type_name))
+            dump_parameters(context, self.inputs, 'Inputs')
+            utils.dump_dict_values(context, self.operation_templates, 'Operation templates')
+
+
+class OperationTemplateBase(structure.TemplateModelMixin):
+    """
+    An operation in a :class:`InterfaceTemplate`.
+
+    Properties:
+
+    * :code:`name`: Name
+    * :code:`description`: Description
+    * :code:`implementation`: Implementation string (interpreted by the orchestrator)
+    * :code:`dependencies`: List of strings (interpreted by the orchestrator)
+    * :code:`executor`: Executor string (interpreted by the orchestrator)
+    * :code:`max_retries`: Maximum number of retries allowed in case of failure
+    * :code:`retry_interval`: Interval between retries
+    * :code:`inputs`: Dict of :class:`Parameter`
+    """
+
+    __tablename__ = 'operation_template'
+
+    __private_fields__ = ['service_template_fk',
+                          'interface_template_fk']
+
+    # region foreign keys
+
+    @declared_attr
+    def service_template_fk(cls):
+        return cls.foreign_key('service_template', nullable=True)
+
+    @declared_attr
+    def interface_template_fk(cls):
+        return cls.foreign_key('interface_template', nullable=True)
+
+    # endregion
+
+    description = Column(Text)
+    implementation = Column(Text)
+    dependencies = Column(aria_type.StrictList(item_cls=basestring))
+    executor = Column(Text)
+    max_retries = Column(Integer)
+    retry_interval = Column(Integer)
+
+    # region orchestrator required columns
+
+    plugin = Column(Text)
+    operation = Column(Boolean)
+
+    # endregion
+
+    # region many-to-many relationships
+
+    @declared_attr
+    def inputs(cls):
+        return cls.many_to_many_relationship('parameter', table_prefix='inputs',
+                                             key_column_name='name')
+
+    # 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 instantiate(self, context, container):
+        from . import model
+        operation = model.Operation()
+        operation.description = deepcopy_with_locators(self.description)
+        operation.implementation = self.implementation
+        operation.dependencies = self.dependencies
+        operation.executor = self.executor
+        operation.max_retries = self.max_retries
+        operation.retry_interval = self.retry_interval
+        utils.instantiate_dict(context, container, operation.inputs, self.inputs)
+        return operation
+
+    def validate(self, context):
+        utils.validate_dict_values(context, self.inputs)
+
+    def coerce_values(self, context, container, report_issues):
+        utils.coerce_dict_values(context, container, self.inputs, report_issues)
+
+    def dump(self, context):
+        console.puts(context.style.node(self.name))
+        if self.description:
+            console.puts(context.style.meta(self.description))
+        with context.style.indent:
+            if self.implementation is not None:
+                console.puts('Implementation: %s' % context.style.literal(self.implementation))
+            if self.dependencies:
+                console.puts('Dependencies: %s' % ', '.join(
+                    (str(context.style.literal(v)) for v in self.dependencies)))
+            if self.executor is not None:
+                console.puts('Executor: %s' % context.style.literal(self.executor))
+            if self.max_retries is not None:
+                console.puts('Max retries: %s' % context.style.literal(self.max_retries))
+            if self.retry_interval is not None:
+                console.puts('Retry interval: %s' % context.style.literal(self.retry_interval))
+            dump_parameters(context, self.inputs, 'Inputs')
+
+
+class ArtifactTemplateBase(structure.TemplateModelMixin):
+    """
+    A file associated with a :class:`NodeTemplate`.
+
+    Properties:
+
+    * :code:`name`: Name
+    * :code:`description`: Description
+    * :code:`type_name`: Must be represented in the :class:`ModelingContext`
+    * :code:`source_path`: Source path (CSAR or repository)
+    * :code:`target_path`: Path at destination machine
+    * :code:`repository_url`: Repository URL
+    * :code:`repository_credential`: Dict of string
+    * :code:`properties`: Dict of :class:`Parameter`
+    """
+
+    __tablename__ = 'artifact_template'
+
+    __private_fields__ = ['node_template_fk']
+
+    # region foreign keys
+
+    @declared_attr
+    def node_template_fk(cls):
+        return cls.foreign_key('node_template')
+
+    # endregion
+
+    description = Column(Text)
+    type_name = Column(Text)
+    source_path = Column(Text)
+    target_path = Column(Text)
+    repository_url = Column(Text)
+    repository_credential = Column(aria_type.StrictDict(basestring, basestring))
+
+    # region many-to-many relationships
+
+    @declared_attr
+    def properties(cls):
+        return cls.many_to_many_relationship('parameter', table_prefix='properties',
+                                             key_column_name='name')
+
+    # 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.iteritems()))))
+
+    def instantiate(self, context, container):
+        from . import model
+        artifact = model.Artifact(self.name, self.type_name, self.source_path)
+        artifact.description = deepcopy_with_locators(self.description)
+        artifact.target_path = self.target_path
+        artifact.repository_url = self.repository_url
+        artifact.repository_credential = self.repository_credential
+        utils.instantiate_dict(context, container, artifact.properties, self.properties)
+        return artifact
+
+    def validate(self, context):
+        if context.modeling.artifact_types.get_descendant(self.type_name) is None:
+            context.validation.report('artifact "%s" has an unknown type: %s'
+                                      % (self.name, formatting.safe_repr(self.type_name)),
+                                      level=validation.Issue.BETWEEN_TYPES)
+
+        utils.validate_dict_values(context, self.properties)
+
+    def coerce_values(self, context, container, report_issues):
+        utils.coerce_dict_values(context, container, self.properties, report_issues)
+
+    def dump(self, context):
+        console.puts(context.style.node(self.name))
+        if self.description:
+            console.puts(context.style.meta(self.description))
+        with context.style.indent:
+            console.puts('Artifact type: %s' % context.style.type(self.type_name))
+            console.puts('Source path: %s' % context.style.literal(self.source_path))
+            if self.target_path is not None:
+                console.puts('Target path: %s' % context.style.literal(self.target_path))
+            if self.repository_url is not None:
+                console.puts('Repository URL: %s' % context.style.literal(self.repository_url))
+            if self.repository_credential:
+                console.puts('Repository credential: %s'
+                             % context.style.literal(self.repository_credential))
+            dump_parameters(context, self.properties)
+
+
+class PolicyTemplateBase(structure.TemplateModelMixin):
+    """
+    Policies can be applied to zero or more :class:`NodeTemplate` or :class:`GroupTemplate`
+    instances.
+
+    Properties:
+
+    * :code:`name`: Name
+    * :code:`description`: Description
+    * :code:`type_name`: Must be represented in the :class:`ModelingContext`
+    * :code:`properties`: Dict of :class:`Parameter`
+    * :code:`target_node_template_names`: Must be represented in the :class:`ServiceTemplate`
+    * :code:`target_group_template_names`: Must be represented in the :class:`ServiceTemplate`
+    """
+    __tablename__ = 'policy_template'
+
+    __private_fields__ = ['service_template_fk']
+
+    # region foreign keys
+
+    @declared_attr
+    def service_template_fk(cls):
+        return cls.foreign_key('service_template')
+
+    # endregion
+
+    description = Column(Text)
+    type_name = Column(Text)
+    target_node_template_names = Column(aria_type.StrictList(basestring))
+    target_group_template_names = Column(aria_type.StrictList(basestring))
+
+    # region many-to-one relationships
+
+    @declared_attr
+    def service_template(cls):
+        return cls.many_to_one_relationship('service_template')
+
+    # endregion
+
+    # region many-to-many relationships
+
+    @declared_attr
+    def properties(cls):
+        return cls.many_to_many_relationship('parameter', table_prefix='properties',
+                                             key_column_name='name')
+
+    # endregion
+
+    @property
+    def as_raw(self):
+        return collections.OrderedDict((
+            ('name', self.name),
+            ('description', self.description),
+            ('type_name', self.type_name),
+            ('properties', formatting.as_raw_dict(self.properties)),
+            ('target_node_template_names', self.target_node_template_names),
+            ('target_group_template_names', self.target_group_template_names)))
+
+    def instantiate(self, context, *args, **kwargs):
+        from . import model
+        policy = model.Policy(self.name, self.type_name)
+        utils.instantiate_dict(context, self, policy.properties, self.properties)
+        for node_template_name in self.target_node_template_names:
+            policy.target_node_ids.extend(
+                context.modeling.instance.get_node_ids(node_template_name))
+        for group_template_name in self.target_group_template_names:
+            policy.target_group_ids.extend(
+                context.modeling.instance.get_group_ids(group_template_name))
+        return policy
+
+    def validate(self, context):
+        if context.modeling.policy_types.get_descendant(self.type_name) is None:
+            context.validation.report('policy template "%s" has an unknown type: %s'
+                                      % (self.name, formatting.safe_repr(self.type_name)),
+                                      level=validation.Issue.BETWEEN_TYPES)
+
+        utils.validate_dict_values(context, self.properties)
+
+    def coerce_values(self, context, container, report_issues):
+        utils.coerce_dict_values(context, self, self.properties, report_issues)
+
+    def dump(self, context):
+        console.puts('Policy template: %s' % context.style.node(self.name))
+        if self.description:
+            console.puts(context.style.meta(self.description))
+        with context.style.indent:
+            console.puts('Type: %s' % context.style.type(self.type_name))
+            dump_parameters(context, self.properties)
+            if self.target_node_template_names:
+                console.puts('Target node templates: %s' % ', '.join(
+                    (str(context.style.node(v)) for v in self.target_node_template_names)))
+            if self.target_group_template_names:
+                console.puts('Target group templates: %s' % ', '.join(
+                    (str(context.style.node(v)) for v in self.target_group_template_names)))
+
+
+class MappingTemplateBase(structure.TemplateModelMixin):
+    """
+    Used by :class:`SubstitutionTemplate` to map a capability or a requirement to a node.
+
+    Properties:
+
+    * :code:`mapped_name`: Exposed capability or requirement name
+    * :code:`node_template_name`: Must be represented in the :class:`ServiceTemplate`
+    * :code:`name`: Name of capability or requirement at the node template
+    """
+
+    __tablename__ = 'mapping_template'
+
+    __private_fields__ = ['substitution_template_fk']
+
+    mapped_name = Column(Text)
+    node_template_name = Column(Text)
+
+    # region foreign keys
+
+    @declared_attr
+    def substitution_template_fk(cls):
+        return cls.foreign_key('substitution_template', nullable=True)
+
+    # endregion
+
+    @property
+    def as_raw(self):
+        return collections.OrderedDict((
+            ('mapped_name', self.mapped_name),
+            ('node_template_name', self.node_template_name),
+            ('name', self.name)))
+
+    def instantiate(self, context, *args, **kwargs):
+        from . import model
+        nodes = context.modeling.instance.find_nodes(self.node_template_name)
+        if len(nodes) == 0:
+            context.validation.report(
+                'mapping "%s" refer to node template "%s" but there are no '
+                'node instances' % (self.mapped_name,
+                                    self.node_template_name),
+                level=validation.Issue.BETWEEN_INSTANCES)
+            return None
+        return model.Mapping(mapped_name=self.mapped_name,
+                             node_id=nodes[0].id,
+                             name=self.name)
+
+    def validate(self, context):
+        if not utils.query_has_item_named(context.modeling.model.node_templates,
+                                          self.node_template_name):
+            context.validation.report('mapping "%s" refers to an unknown node template: %s'
+                                      % (
+                                          self.mapped_name,
+                                          formatting.safe_repr(self.node_template_name)),
+                                      level=validation.Issue.BETWEEN_TYPES)
+
+    def dump(self, context):
+        console.puts('%s -> %s.%s' % (context.style.node(self.mapped_name),
+                                      context.style.node(self.node_template_name),
+                                      context.style.node(self.name)))
+
+
+class SubstitutionTemplateBase(structure.TemplateModelMixin):
+    """
+    Used to substitute a single node for the entire deployment.
+
+    Properties:
+
+    * :code:`node_type_name`: Must be represented in the :class:`ModelingContext`
+    * :code:`capability_templates`: Dict of :class:`MappingTemplate`
+    * :code:`requirement_templates`: Dict of :class:`MappingTemplate`
+    """
+
+    __tablename__ = 'substitution_template'
+
+    node_type_name = Column(Text)
+
+    # region one-to-many relationships
+
+    @declared_attr
+    def mappings(cls):
+        return cls.one_to_many_relationship('mapping_template', key_column_name='name')
+
+    # endregion
+
+    @property
+    def as_raw(self):
+        return collections.OrderedDict((
+            ('node_type_name', self.node_type_name),
+            ('mappings', formatting.as_raw_list(self.mappings))))
+
+    def instantiate(self, context, container):
+        from . import model
+        substitution = model.Substitution(self.node_type_name)
+        utils.instantiate_dict(context, container, substitution.mappings,
+                               self.mappings)
+        return substitution
+
+    def validate(self, context):
+        if context.modeling.node_types.get_descendant(self.node_type_name) is None:
+            context.validation.report('substitution template has an unknown type: %s'
+                                      % formatting.safe_repr(self.node_type_name),
+                                      level=validation.Issue.BETWEEN_TYPES)
+
+        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:
+            console.puts('Node type: %s' % context.style.type(self.node_type_name))
+            utils.dump_dict_values(context, self.mappings,
+                                   'Mappings')
+
+
+class NodeTemplateBase(structure.TemplateModelMixin):
+    """
+    A template for creating zero or more :class:`Node` instances.
+
+    Properties:
+
+    * :code:`name`: Name (will be used as a prefix for node IDs)
+    * :code:`description`: Description
+    * :code:`type_name`: Must be represented in the :class:`ModelingContext`
+    * :code:`default_instances`: Default number nodes that will appear in the deployment plan
+    * :code:`min_instances`: Minimum number nodes that will appear in the deployment plan
+    * :code:`max_instances`: Maximum number nodes that will appear in the deployment plan
+    * :code:`properties`: Dict of :class:`Parameter`
+    * :code:`interface_templates`: Dict of :class:`InterfaceTemplate`
+    * :code:`artifact_templates`: Dict of :class:`ArtifactTemplate`
+    * :code:`capability_templates`: Dict of :class:`CapabilityTemplate`
+    * :code:`requirement_templates`: List of :class:`RequirementTemplate`
+    * :code:`target_node_template_constraints`: List of :class:`FunctionType`
+    """
+
+    __tablename__ = 'node_template'
+
+    __private_fields__ = ['service_template_fk',
+                          'host_fk']
+
+    # region foreign_keys
+
+    @declared_attr
+    def service_template_fk(cls):
+        return cls.foreign_key('service_template')
+
+    @declared_attr
+    def host_fk(cls):
+        return cls.foreign_key('node_template', nullable=True)
+
+    # endregion
+
+    description = Column(Text)
+    type_name = Column(Text)
+    default_instances = Column(Integer, default=1)
+    min_instances = Column(Integer, default=0)
+    max_instances = Column(Integer, default=None)
+    target_node_template_constraints = Column(aria_type.StrictList(FunctionType))
+
+    # region orchestrator required columns
+
+    plugins = Column(aria_type.List)
+    type_hierarchy = Column(aria_type.List)
+
+    @declared_attr
+    def host(cls):
+        return cls.relationship_to_self('host_fk')
+
+    @declared_attr
+    def service_template_name(cls):
+        return association_proxy('service_template', cls.name_column_name())
+
+    # endregion
+
+    # region many-to-one relationships
+
+    @declared_attr
+    def service_template(cls):
+        return cls.many_to_one_relationship('service_template')
+
+    # endregion
+
+    # region one-to-many relationships
+
+    @declared_attr
+    def interface_templates(cls):
+        return cls.one_to_many_relationship('interface_template', key_column_name='name')
+
+    @declared_attr
+    def artifact_templates(cls):
+        return cls.one_to_many_relationship('artifact_template', key_column_name='name')
+
+    @declared_attr
+    def capability_templates(cls):
+        return cls.one_to_many_relationship('capability_template', key_column_name='name')
+
+    # endregion
+
+    # region many-to-many relationships
+
+    @declared_attr
+    def properties(cls):
+        return cls.many_to_many_relationship('parameter', table_prefix='properties',
+                                             key_column_name='name')
+
+    # endregion
+
+    def is_target_node_valid(self, target_node_template):
+        if self.target_node_template_constraints:
+            for node_type_constraint in self.target_node_template_constraints:
+                if not node_type_constraint(target_node_template, self):
+                    return False
+        return True
+
+    @property
+    def as_raw(self):
+        return collections.OrderedDict((
+            ('name', self.name),
+            ('description', self.description),
+            ('type_name', self.type_name),
+            ('default_instances', self.default_instances),
+            ('min_instances', self.min_instances),
+            ('max_instances', self.max_instances),
+            ('properties', formatting.as_raw_dict(self.properties)),
+            ('interface_templates', formatting.as_raw_list(self.interface_templates)),
+            ('artifact_templates', formatting.as_raw_list(self.artifact_templates)),
+            ('capability_templates', formatting.as_raw_list(self.capability_templates)),
+            ('requirement_templates', formatting.as_raw_list(self.requirement_templates))))
+
+    def instantiate(self, context, *args, **kwargs):
+        from . import model
+        node = model.Node(name=self.name,
+                          type_name=self.type_name)
+        utils.instantiate_dict(context, node, node.properties, self.properties)
+        utils.instantiate_dict(context, node, node.interfaces, self.interface_templates)
+        utils.instantiate_dict(context, node, node.artifacts, self.artifact_templates)
+        utils.instantiate_dict(context, node, node.capabilities, self.capability_templates)
+        return node
+
+    def validate(self, context):
+        if context.modeling.node_types.get_descendant(self.type_name) is None:
+            context.validation.report('node template "%s" has an unknown type: %s'
+                                      % (self.name,
+                                         formatting.safe_repr(self.type_name)),
+                                      level=validation.Issue.BETWEEN_TYPES)
+
+        utils.validate_dict_values(context, self.properties)
+        utils.validate_dict_values(context, self.interface_templates)
+        utils.validate_dict_values(context, self.artifact_templates)
+        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: %s' % context.style.node(self.name))
+        if self.description:
+            console.puts(context.style.meta(self.description))
+        with context.style.indent:
+            console.puts('Type: %s' % context.style.type(self.type_name))
+            console.puts('Instances: %d (%d%s)'
+                         % (self.default_instances,
+                            self.min_instances,
+                            (' to %d' % self.max_instances
+                             if self.max_instances is not None
+                             else ' or more')))
+            dump_parameters(context, self.properties)
+            utils.dump_interfaces(context, self.interface_templates)
+            utils.dump_dict_values(context, self.artifact_templates, 'Artifact tempaltes')
+            utils.dump_dict_values(context, self.capability_templates, 'Capability templates')
+            utils.dump_list_values(context, self.requirement_templates, 'Requirement templates')
+
+
+class GroupTemplateBase(structure.TemplateModelMixin):
+    """
+    A template for creating zero or more :class:`Group` instances.
+
+    Groups are logical containers for zero or more nodes that allow applying zero or more
+    :class:`GroupPolicy` instances to the nodes together.
+
+    Properties:
+
+    * :code:`name`: Name (will be used as a prefix for group IDs)
+    * :code:`description`: Description
+    * :code:`type_name`: Must be represented in the :class:`ModelingContext`
+    * :code:`properties`: Dict of :class:`Parameter`
+    * :code:`interface_templates`: Dict of :class:`InterfaceTemplate`
+    * :code:`policy_templates`: Dict of :class:`GroupPolicyTemplate`
+    * :code:`member_node_template_names`: Must be represented in the :class:`ServiceTemplate`
+    * :code:`member_group_template_names`: Must be represented in the :class:`ServiceTemplate`
+    """
+
+    __tablename__ = 'group_template'
+
+    __private_fields__ = ['service_template_fk']
+
+    # region foreign keys
+
+    @declared_attr
+    def service_template_fk(cls):
+        return cls.foreign_key('service_template')
+
+    # endregion
+
+    description = Column(Text)
+    type_name = Column(Text)
+    member_node_template_names = Column(aria_type.StrictList(basestring))
+    member_group_template_names = Column(aria_type.StrictList(basestring))
+
+    # region many-to-one relationships
+
+    @declared_attr
+    def service_template(cls):
+        return cls.many_to_one_relationship('service_template')
+
+    # endregion
+
+    # region one-to-many relationships
+
+    @declared_attr
+    def interface_templates(cls):
+        return cls.one_to_many_relationship('interface_template', key_column_name='name')
+
+    # endregion
+
+    # region many-to-many relationships
+
+    @declared_attr
+    def properties(cls):
+        return cls.many_to_many_relationship('parameter', table_prefix='properties',
+                                             key_column_name='name')
+
+    # endregion
+
+    @property
+    def as_raw(self):
+        return collections.OrderedDict((
+            ('name', self.name),
+            ('description', self.description),
+            ('type_name', self.type_name),
+            ('properties', formatting.as_raw_dict(self.properties)),
+            ('interface_templates', formatting.as_raw_list(self.interface_templates)),
+            ('member_node_template_names', self.member_node_template_names),
+            ('member_group_template_names', self.member_group_template_names1)))
+
+    def instantiate(self, context, *args, **kwargs):
+        from . import model
+        group = model.Group(context, self.type_name, self.name)
+        utils.instantiate_dict(context, self, group.properties, self.properties)
+        utils.instantiate_dict(context, self, group.interfaces, self.interface_templates)
+        utils.instantiate_dict(context, self, group.policies, self.policy_templates)
+        for member_node_template_name in self.member_node_template_names:
+            group.member_node_ids += \
+                context.modeling.instance.get_node_ids(member_node_template_name)
+        for member_group_template_name in self.member_group_template_names:
+            group.member_group_ids += \
+                context.modeling.instance.get_group_ids(member_group_template_name)
+        return group
+
+    def validate(self, context):
+        if context.modeling.group_types.get_descendant(self.type_name) is None:
+            context.validation.report('group template "%s" has an unknown type: %s'
+                                      % (self.name, formatting.safe_repr(self.type_name)),
+                                      level=validation.Issue.BETWEEN_TYPES)
+
+        utils.validate_dict_values(context, self.properties)
+        utils.validate_dict_values(context, self.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: %s' % context.style.node(self.name))
+        if self.description:
+            console.puts(context.style.meta(self.description))
+        with context.style.indent:
+            if self.type_name:
+                console.puts('Type: %s' % context.style.type(self.type_name))
+            dump_parameters(context, self.properties)
+            utils.dump_interfaces(context, self.interface_templates)
+            if self.member_node_template_names:
+                console.puts('Member node templates: %s' % ', '.join(
+                    (str(context.style.node(v)) for v in self.member_node_template_names)))
+
+
+class RequirementTemplateBase(structure.TemplateModelMixin):
+    """
+    A requirement for a :class:`NodeTemplate`. During instantiation will be matched with a
+    capability of another
+    node.
+
+    Requirements may optionally contain a :class:`RelationshipTemplate` that will be created between
+    the nodes.
+
+    Properties:
+
+    * :code:`name`: Name
+    * :code:`target_node_type_name`: Must be represented in the :class:`ModelingContext`
+    * :code:`target_node_template_name`: Must be represented in the :class:`ServiceTemplate`
+    * :code:`target_node_template_constraints`: List of :class:`FunctionType`
+    * :code:`target_capability_type_name`: Type of capability in target node
+    * :code:`target_capability_name`: Name of capability in target node
+    * :code:`relationship_template`: :class:`RelationshipTemplate`
+    """
+
+    __tablename__ = 'requirement_template'
+
+    __private_fields__ = ['node_template_fk']
+
+    # region foreign keys
+
+    @declared_attr
+    def node_template_fk(cls):
+        return cls.foreign_key('node_template', nullable=True)
+
+    # endregion
+
+    target_node_type_name = Column(Text)
+    target_node_template_name = Column(Text)
+    target_node_template_constraints = Column(aria_type.StrictList(FunctionType))
+    target_capability_type_name = Column(Text)
+    target_capability_name = Column(Text)
+    # CHECK: ???
+    relationship_template = Column(Text)  # optional
+
+    # region many-to-one relationships
+
+    @declared_attr
+    def node_template(cls):
+        return cls.many_to_one_relationship('node_template')
+
+    # endregion
+
+    def instantiate(self, context, container):
+        raise NotImplementedError
+
+    def find_target(self, context, source_node_template):
+        # We might already have a specific node template, so we'll just verify it
+        if self.target_node_template_name is not None:
+            target_node_template = \
+                context.modeling.model.node_templates.get(self.target_node_template_name)
+
+            if not source_node_template.is_target_node_valid(target_node_template):
+                context.validation.report('requirement "%s" of node template "%s" is for node '
+                                          'template "%s" but it does not match constraints'
+                                          % (self.name,
+                                             self.target_node_template_name,
+                                             source_node_template.name),
+                                          level=validation.Issue.BETWEEN_TYPES)
+                return None, None
+
+            if self.target_capability_type_name is not None \
+                    or self.target_capability_name is not None:
+                target_node_capability = self.find_target_capability(context,
+                                                                     source_node_template,
+                                                                     target_node_template)
+                if target_node_capability is None:
+                    return None, None
+            else:
+                target_node_capability = None
+
+            return target_node_template, target_node_capability
+
+        # Find first node that matches the type
+        elif self.target_node_type_name is not None:
+            for target_node_template in context.modeling.model.node_templates.itervalues():
+                if not context.modeling.node_types.is_descendant(self.target_node_type_name,
+                                                                 target_node_template.type_name):
+                    continue
+
+                if not source_node_template.is_target_node_valid(target_node_template):
+                    continue
+
+                target_node_capability = self.find_target_capability(context,
+                                                                     source_node_template,
+                                                                     target_node_template)
+                if target_node_capability is None:
+                    continue
+
+                return target_node_template, target_node_capability
+
+        return None, None
+
+    def find_target_capability(self, context, source_node_template, target_node_template):
+        for capability_template in target_node_template.capability_templates.itervalues():
+            if capability_template.satisfies_requirement(context,
+                                                         source_node_template,
+                                                         self,
+                                                         target_node_template):
+                return capability_template
+        return None
+
+    @property
+    def as_raw(self):
+        return collections.OrderedDict((
+            ('name', self.name),
+            ('target_node_type_name', self.target_node_type_name),
+            ('target_node_template_name', self.target_node_template_name),
+            ('target_capability_type_name', self.target_capability_type_name),
+            ('target_capability_name', self.target_capability_name),
+            ('relationship_template', formatting.as_raw(self.relationship_template))))
+
+    def validate(self, context):
+        node_types = context.modeling.node_types
+        capability_types = context.modeling.capability_types
+        if self.target_node_type_name \
+                and node_types.get_descendant(self.target_node_type_name) is None:
+            context.validation.report('requirement "%s" refers to an unknown node type: %s'
+                                      % (self.name,
+                                         formatting.safe_repr(self.target_node_type_name)),
+                                      level=validation.Issue.BETWEEN_TYPES)
+        if self.target_capability_type_name and \
+                capability_types.get_descendant(self.target_capability_type_name is None):
+            context.validation.report('requirement "%s" refers to an unknown capability type: %s'
+                                      % (self.name,
+                                         formatting.safe_repr(self.target_capability_type_name)),
+                                      level=validation.Issue.BETWEEN_TYPES)
+        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))
+        else:
+            console.puts('Requirement:')
+        with context.style.indent:
+            if self.target_node_type_name is not None:
+                console.puts('Target node type: %s'
+                             % context.style.type(self.target_node_type_name))
+            elif self.target_node_template_name is not None:
+                console.puts('Target node template: %s'
+                             % context.style.node(self.target_node_template_name))
+            if self.target_capability_type_name is not None:
+                console.puts('Target capability type: %s'
+                             % context.style.type(self.target_capability_type_name))
+            elif self.target_capability_name is not None:
+                console.puts('Target capability name: %s'
+                             % context.style.node(self.target_capability_name))
+            if self.target_node_template_constraints:
+                console.puts('Target node template constraints:')
+                with context.style.indent:
+                    for constraint in self.target_node_template_constraints:
+                        console.puts(context.style.literal(constraint))
+            if self.relationship_template:
+                console.puts('Relationship:')
+                with context.style.indent:
+                    self.relationship_template.dump(context)
+
+
+class CapabilityTemplateBase(structure.TemplateModelMixin):
+    """
+    A capability of a :class:`NodeTemplate`. Nodes expose zero or more capabilities that can be
+    matched with :class:`Requirement` instances of other nodes.
+
+    Properties:
+
+    * :code:`name`: Name
+    * :code:`description`: Description
+    * :code:`type_name`: Must be represented in the :class:`ModelingContext`
+    * :code:`min_occurrences`: Minimum number of requirement matches required
+    * :code:`max_occurrences`: Maximum number of requirement matches allowed
+    * :code:`valid_source_node_type_names`: Must be represented in the :class:`ModelingContext`
+    * :code:`properties`: Dict of :class:`Parameter`
+    """
+
+    __tablename__ = 'capability_template'
+
+    __private_fields__ = ['node_template_fk']
+
+    # region foreign keys
+
+    @declared_attr
+    def node_template_fk(cls):
+        return cls.foreign_key('node_template', nullable=True)
+
+    # endregion
+
+    description = Column(Text)
+    type_name = Column(Text)
+    min_occurrences = Column(Integer, default=None)  # optional
+    max_occurrences = Column(Integer, default=None)  # optional
+    # CHECK: type?
+    valid_source_node_type_names = Column(Text)
+
+    # region many-to-one relationships
+
+    #@declared_attr
+    #def node_template(cls):
+    #    return cls.many_to_one_relationship('node_template')
+
+    # endregion
+
+    # region many-to-many relationships
+
+    @declared_attr
+    def properties(cls):
+        return cls.many_to_many_relationship('parameter', table_prefix='properties',
+                                             key_column_name='name')
+
+    # endregion
+
+    def satisfies_requirement(self,
+                              context,
+                              source_node_template,
+                              requirement,
+                              target_node_template):
+        # Do we match the required capability type?
+        capability_types = context.modeling.capability_types
+        if not capability_types.is_descendant(requirement.target_capability_type_name,
+                                              self.type_name):
+            return False
+
+        # Are we in valid_source_node_type_names?
+        if self.valid_source_node_type_names:
+            for valid_source_node_type_name in self.valid_source_node_type_names:
+                if not context.modeling.node_types.is_descendant(valid_source_node_type_name,
+                                                                 source_node_template.type_name):
+                    return False
+
+        # Apply requirement constraints
+        if requirement.target_node_template_constraints:
+            for node_type_constraint in requirement.target_node_template_constraints:
+                if not node_type_constraint(target_node_template, source_node_template):
+                    return False
+
+        return True
+
+    @property
+    def as_raw(self):
+        return collections.OrderedDict((
+            ('name', self.name),
+            ('description', self.description),
+            ('type_name', self.type_name),
+            ('min_occurrences', self.min_occurrences),
+            ('max_occurrences', self.max_occurrences),
+            ('valid_source_node_type_names', self.valid_source_node_type_names),
+            ('properties', formatting.as_raw_dict(self.properties))))
+
+    def instantiate(self, context, container):
+        from . import model
+        capability = model.Capability(self.name, self.type_name)
+        capability.min_occurrences = self.min_occurrences
+        capability.max_occurrences = self.max_occurrences
+        utils.instantiate_dict(context, container, capability.properties, self.properties)
+        return capability
+
+    def validate(self, context):
+        if context.modeling.capability_types.get_descendant(self.type_name) is None:
+            context.validation.report('capability "%s" refers to an unknown type: %s'
+                                      % (self.name, formatting.safe_repr(self.type)),  # pylint: disable=no-member
+                                      #  TODO fix self.type reference
+                                      level=validation.Issue.BETWEEN_TYPES)
+
+        utils.validate_dict_values(context, self.properties)
+
+    def coerce_values(self, context, container, report_issues):
+        utils.coerce_dict_values(context, self, 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('Type: %s' % context.style.type(self.type_name))
+            console.puts(
+                'Occurrences: %d%s'
+                % (self.min_occurrences or 0, (' to %d' % self.max_occurrences)
+                   if self.max_occurrences is not None else ' or more'))
+            if self.valid_source_node_type_names:
+                console.puts('Valid source node types: %s'
+                             % ', '.join((str(context.style.type(v))
+                                          for v in self.valid_source_node_type_names)))
+            dump_parameters(context, self.properties)
+
+
+class RelationshipTemplateBase(structure.TemplateModelMixin):
+    """
+    Optional addition to a :class:`Requirement` in :class:`NodeTemplate` that can be applied when
+    the requirement is matched with a capability.
+
+    Properties:
+
+    * :code:`type_name`: Must be represented in the :class:`ModelingContext`
+    * :code:`template_name`: Must be represented in the :class:`ServiceTemplate`
+    * :code:`description`: Description
+    * :code:`properties`: Dict of :class:`Parameter`
+    * :code:`source_interface_templates`: Dict of :class:`InterfaceTemplate`
+    * :code:`target_interface_templates`: Dict of :class:`InterfaceTemplate`
+    """
+
+    __tablename__ = 'relationship_template'
+
+    description = Column(Text)
+    type_name = Column(Text)
+
+    # region many-to-many relationships
+
+    @declared_attr
+    def properties(cls):
+        return cls.many_to_many_relationship('parameter', table_prefix='properties',
+                                             key_column_name='name')
+
+    # endregion
+
+    # region one-to-many relationships
+
+    @declared_attr
+    def interface_templates(cls):
+        return cls.one_to_many_relationship('interface_template', key_column_name='name')
+
+    # endregion
+
+    @property
+    def as_raw(self):
+        return collections.OrderedDict((
+            ('type_name', self.type_name),
+            ('template_name', self.template_name),
+            ('description', self.description),
+            ('properties', formatting.as_raw_dict(self.properties)),
+            ('interface_templates', formatting.as_raw_list(self.interface_templates))))
+
+    def instantiate(self, context, container):
+        from . import model
+        relationship = model.Relationship(name=self.template_name,
+                                          type_name=self.type_name)
+        utils.instantiate_dict(context, container,
+                               relationship.properties, self.properties)
+        utils.instantiate_dict(context, container,
+                               relationship.interfaces, self.interface_templates)
+        return relationship
+
+    def validate(self, context):
+        if context.modeling.relationship_types.get_descendant(self.type_name) is None:
+            context.validation.report(
+                'relationship template "{0}" has an unknown type: {1}'.format(
+                    self.name,
+                    formatting.safe_repr(self.type_name)),  # pylint: disable=no-member
+                # TODO fix self.name reference
+                level=validation.Issue.BETWEEN_TYPES)
+
+        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_name is not None:
+            console.puts('Relationship type: {0}'.format(context.style.type(self.type_name)))
+        else:
+            console.puts('Relationship template: {0}'.format(
+                context.style.node(self.template_name)))
+        if self.description:
+            console.puts(context.style.meta(self.description))
+        with context.style.indent:
+            utils.dump_parameters(context, self.properties)
+            utils.dump_interfaces(context, self.interface_templates, 'Interface templates')
+
+# endregion
+
+
+def dump_parameters(context, parameters, name='Properties'):
+    if not parameters:
+        return
+    console.puts('%s:' % name)
+    with context.style.indent:
+        for parameter_name, parameter in parameters.items():
+            if hasattr(parameter, 'type_name') and (parameter.type_name is not None):
+                console.puts('%s = %s (%s)' % (context.style.property(parameter_name),
+                                               context.style.literal(parameter.value),
+                                               context.style.type(parameter.type_name)))
+            else:
+                console.puts('%s = %s' % (context.style.property(parameter_name),
+                                          context.style.literal(parameter.value)))
+            if hasattr(parameter, 'description') and parameter.description:
+                console.puts(context.style.meta(parameter.description))
+
+
+# TODO (left for tal): Move following two methods to some place parser specific
+def deepcopy_with_locators(value):
+    """
+    Like :code:`deepcopy`, but also copies over locators.
+    """
+
+    res = deepcopy(value)
+    copy_locators(res, value)
+    return res
+
+
+def copy_locators(target, source):
+    """
+    Copies over :code:`_locator` for all elements, recursively.
+
+    Assumes that target and source have exactly the same list/dict structure.
+    """
+
+    locator = getattr(source, '_locator', None)
+    if locator is not None:
+        try:
+            setattr(target, '_locator', locator)
+        except AttributeError:
+            pass
+
+    if isinstance(target, list) and isinstance(source, list):
+        for i, _ in enumerate(target):
+            copy_locators(target[i], source[i])
+    elif isinstance(target, dict) and isinstance(source, dict):
+        for k, v in target.items():
+            copy_locators(v, source[k])

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/f1074fee/aria/modeling/shared_service_models.py
----------------------------------------------------------------------
diff --git a/aria/modeling/shared_service_models.py b/aria/modeling/shared_service_models.py
new file mode 100644
index 0000000..c5676b3
--- /dev/null
+++ b/aria/modeling/shared_service_models.py
@@ -0,0 +1,109 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from sqlalchemy import (
+    Column,
+    Text
+)
+
+from ..parser.modeling import utils
+from ..storage import exceptions
+from ..utils.collections import OrderedDict
+
+from . import structure
+
+# pylint: disable=no-self-argument, no-member, abstract-method
+
+
+class ParameterBase(structure.TemplateModelMixin):
+    """
+    Represents a typed value.
+
+    This class is used by both service template and service instance elements.
+    """
+
+    __tablename__ = 'parameter'
+
+    name = Column(Text, nullable=False)
+    type_name = Column(Text, nullable=False)
+
+    # Check: value type
+    str_value = Column(Text)
+    description = Column(Text)
+
+    @property
+    def as_raw(self):
+        return OrderedDict((
+            ('name', self.name),
+            ('type_name', self.type_name),
+            ('value', self.value),
+            ('description', self.description)))
+
+    @property
+    def value(self):
+        if self.type_name is None:
+            return
+        try:
+            if self.type_name.lower() in ('str', 'unicode'):
+                return self.str_value.decode('utf-8')
+            elif self.type_name.lower() == 'int':
+                return int(self.str_value)
+            elif self.type_name.lower() == 'bool':
+                return bool(self.str_value)
+            elif self.type_name.lower() == 'float':
+                return float(self.str_value)
+            else:
+                return self.str_value
+        except ValueError:
+            raise exceptions.StorageError('Trying to cast {0} to {1} failed'.format(self.str_value,
+                                                                                    self.type))
+
+    @value.setter
+    def value(self, value):
+        self.str_value = unicode(value)
+
+    def instantiate(self, context, container):
+        from . import model
+        return model.Parameter(type_name=self.type_name,
+                               str_value=self.str_value,
+                               description=self.description)
+
+    def coerce_values(self, context, container, report_issues):
+        if self.str_value is not None:
+            self.str_value = utils.coerce_value(context, container, self.str_value, report_issues)
+
+
+class MetadataBase(structure.TemplateModelMixin):
+    """
+    Custom values associated with the service.
+
+    This class is used by both service template and service instance elements.
+    """
+
+    __tablename__ = 'metadata'
+
+    name = Column(Text, nullable=False)
+    value = Column(Text)
+
+    @property
+    def as_raw(self):
+        return OrderedDict((
+            ('name', self.name),
+            ('value', self.value)))
+
+    def instantiate(self, context, container):
+        from . import model
+        return model.Metadata(name=self.name,
+                              value=self.value)

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/f1074fee/aria/modeling/structure.py
----------------------------------------------------------------------
diff --git a/aria/modeling/structure.py b/aria/modeling/structure.py
index c13572c..719257a 100644
--- a/aria/modeling/structure.py
+++ b/aria/modeling/structure.py
@@ -45,8 +45,7 @@ class Function(object):
     """
     An intrinsic function.
 
-    Serves as a placeholder for a value that should eventually be derived
-    by calling the function.
+    Serves as a placeholder for a value that should eventually be derived by calling the function.
     """
 
     @property
@@ -61,40 +60,7 @@ class Function(object):
         return self
 
 
-class ElementBase(object):
-    """
-    Base class for :class:`ServiceInstance` elements.
-
-    All elements 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 ModelElementBase(ElementBase):
-    """
-    Base class for :class:`ServiceModel` elements.
-
-    All model elements can be instantiated into :class:`ServiceInstance` elements.
-    """
-
-    def instantiate(self, context, container):
-        raise NotImplementedError
-
-
-class ModelMixin(ModelElementBase):
+class ModelMixin(object):
 
     @utils.classproperty
     def __modelname__(cls):                                                                         # pylint: disable=no-self-argument
@@ -209,7 +175,7 @@ class ModelMixin(ModelElementBase):
                             **relationship_kwargs)
 
     @classmethod
-    def many_to_many_relationship(cls, other_table_name, table_prefix, key_column_name=None,
+    def many_to_many_relationship(cls, other_table_name, table_prefix=None, key_column_name=None,
                                   relationship_kwargs=None):
         """Return a many-to-many SQL relationship object
 
@@ -339,6 +305,39 @@ class ModelMixin(ModelElementBase):
             id=getattr(self, self.name_column_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
+
+
 class ModelIDMixin(object):
     id = Column(Integer, primary_key=True, autoincrement=True)
     name = Column(Text, nullable=True, index=True)


[3/4] incubator-ariatosca git commit: Modeling refactoring; improved metadata; work on instance models

Posted by em...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/f1074fee/aria/modeling/orchestrator_models.py
----------------------------------------------------------------------
diff --git a/aria/modeling/orchestrator_models.py b/aria/modeling/orchestrator_models.py
new file mode 100644
index 0000000..d76c234
--- /dev/null
+++ b/aria/modeling/orchestrator_models.py
@@ -0,0 +1,468 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+Aria's storage.models module
+Path: aria.storage.models
+
+models module holds aria's models.
+
+classes:
+    * Field - represents a single field.
+    * IterField - represents an iterable field.
+    * Model - abstract model implementation.
+    * Snapshot - snapshots implementation model.
+    * Deployment - deployment implementation model.
+    * DeploymentUpdateStep - deployment update step implementation model.
+    * DeploymentUpdate - deployment update implementation model.
+    * DeploymentModification - deployment modification implementation model.
+    * Execution - execution implementation model.
+    * Node - node implementation model.
+    * Relationship - relationship implementation model.
+    * NodeInstance - node instance implementation model.
+    * RelationshipInstance - relationship instance implementation model.
+    * Plugin - plugin implementation model.
+"""
+from collections import namedtuple
+from datetime import datetime
+
+from sqlalchemy import (
+    Column,
+    Integer,
+    Text,
+    DateTime,
+    Boolean,
+    Enum,
+    String,
+    Float,
+    orm,
+)
+from sqlalchemy.ext.associationproxy import association_proxy
+from sqlalchemy.ext.declarative import declared_attr
+
+from ..orchestrator.exceptions import TaskAbortException, TaskRetryException
+
+from .type import List, Dict
+from .structure import ModelMixin
+
+__all__ = (
+    'ServiceInstanceUpdateStepBase',
+    'ServiceInstanceUpdateBase',
+    'ServiceInstanceModificationBase',
+    'Execution',
+    'PluginBase',
+    'TaskBase'
+)
+
+# pylint: disable=no-self-argument, no-member, abstract-method
+
+
+class Execution(ModelMixin):
+    """
+    Execution model representation.
+    """
+    # Needed only for pylint. the id will be populated by sqlalcehmy and the proper column.
+    __tablename__ = 'execution'
+
+    __private_fields__ = ['service_instance_fk']
+
+    TERMINATED = 'terminated'
+    FAILED = 'failed'
+    CANCELLED = 'cancelled'
+    PENDING = 'pending'
+    STARTED = 'started'
+    CANCELLING = 'cancelling'
+    FORCE_CANCELLING = 'force_cancelling'
+
+    STATES = [TERMINATED, FAILED, CANCELLED, PENDING, STARTED, CANCELLING, FORCE_CANCELLING]
+    END_STATES = [TERMINATED, FAILED, CANCELLED]
+    ACTIVE_STATES = [state for state in STATES if state not in END_STATES]
+
+    VALID_TRANSITIONS = {
+        PENDING: [STARTED, CANCELLED],
+        STARTED: END_STATES + [CANCELLING],
+        CANCELLING: END_STATES + [FORCE_CANCELLING]
+    }
+
+    @orm.validates('status')
+    def validate_status(self, key, value):
+        """Validation function that verifies execution status transitions are OK"""
+        try:
+            current_status = getattr(self, key)
+        except AttributeError:
+            return
+        valid_transitions = self.VALID_TRANSITIONS.get(current_status, [])
+        if all([current_status is not None,
+                current_status != value,
+                value not in valid_transitions]):
+            raise ValueError('Cannot change execution status from {current} to {new}'.format(
+                current=current_status,
+                new=value))
+        return value
+
+    created_at = Column(DateTime, index=True)
+    started_at = Column(DateTime, nullable=True, index=True)
+    ended_at = Column(DateTime, nullable=True, index=True)
+    error = Column(Text, nullable=True)
+    is_system_workflow = Column(Boolean, nullable=False, default=False)
+    parameters = Column(Dict)
+    status = Column(Enum(*STATES, name='execution_status'), default=PENDING)
+    workflow_name = Column(Text)
+
+    @declared_attr
+    def service_template(cls):
+        return association_proxy('service_instance', 'service_template')
+
+    @declared_attr
+    def service_instance_fk(cls):
+        return cls.foreign_key('service_instance')
+
+    @declared_attr
+    def service_instance(cls):
+        return cls.many_to_one_relationship('service_instance')
+
+    @declared_attr
+    def service_instance_name(cls):
+        return association_proxy('service_instance', cls.name_column_name())
+
+    @declared_attr
+    def service_template_name(cls):
+        return association_proxy('service_instance', 'service_template_name')
+
+    def __str__(self):
+        return '<{0} id=`{1}` (status={2})>'.format(
+            self.__class__.__name__,
+            getattr(self, self.name_column_name()),
+            self.status
+        )
+
+
+class ServiceInstanceUpdateBase(ModelMixin):
+    """
+    Deployment update model representation.
+    """
+    # Needed only for pylint. the id will be populated by sqlalcehmy and the proper column.
+    steps = None
+
+    __tablename__ = 'service_instance_update'
+    __private_fields__ = ['service_instance_fk',
+                          'execution_fk']
+
+    _private_fields = ['execution_fk', 'deployment_fk']
+
+    created_at = Column(DateTime, nullable=False, index=True)
+    service_instance_plan = Column(Dict, nullable=False)
+    service_instance_update_node_instances = Column(Dict)
+    service_instance_update_service_instance = Column(Dict)
+    service_instance_update_nodes = Column(List)
+    modified_entity_ids = Column(Dict)
+    state = Column(Text)
+
+    @declared_attr
+    def execution_fk(cls):
+        return cls.foreign_key('execution', nullable=True)
+
+    @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_instance_fk(cls):
+        return cls.foreign_key('service_instance')
+
+    @declared_attr
+    def service_instance(cls):
+        return cls.many_to_one_relationship('service_instance')
+
+    @declared_attr
+    def service_instance_name(cls):
+        return association_proxy('service_instance', cls.name_column_name())
+
+    def to_dict(self, suppress_error=False, **kwargs):
+        dep_update_dict = super(ServiceInstanceUpdateBase, 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 ServiceInstanceUpdateStepBase(ModelMixin):
+    """
+    Deployment update step model representation.
+    """
+    # Needed only for pylint. the id will be populated by sqlalcehmy and the proper column.
+    __tablename__ = 'service_instance_update_step'
+    __private_fields__ = ['service_instance_update_fk']
+
+    _action_types = namedtuple('ACTION_TYPES', 'ADD, REMOVE, MODIFY')
+    ACTION_TYPES = _action_types(ADD='add', REMOVE='remove', MODIFY='modify')
+    _entity_types = namedtuple(
+        'ENTITY_TYPES',
+        'NODE, RELATIONSHIP, PROPERTY, OPERATION, WORKFLOW, OUTPUT, DESCRIPTION, GROUP, '
+        'POLICY_TYPE, POLICY_TRIGGER, PLUGIN')
+    ENTITY_TYPES = _entity_types(
+        NODE='node',
+        RELATIONSHIP='relationship',
+        PROPERTY='property',
+        OPERATION='operation',
+        WORKFLOW='workflow',
+        OUTPUT='output',
+        DESCRIPTION='description',
+        GROUP='group',
+        POLICY_TYPE='policy_type',
+        POLICY_TRIGGER='policy_trigger',
+        PLUGIN='plugin'
+    )
+
+    action = Column(Enum(*ACTION_TYPES, name='action_type'), nullable=False)
+    entity_id = Column(Text, nullable=False)
+    entity_type = Column(Enum(*ENTITY_TYPES, name='entity_type'), nullable=False)
+
+    @declared_attr
+    def service_instance_update_fk(cls):
+        return cls.foreign_key('service_instance_update')
+
+    @declared_attr
+    def service_instance_update(cls):
+        return cls.many_to_one_relationship('service_instance_update',
+                                            backreference='steps')
+
+    @declared_attr
+    def deployment_update_name(cls):
+        return association_proxy('deployment_update', cls.name_column_name())
+
+    def __hash__(self):
+        return hash((getattr(self, self.id_column_name()), self.entity_id))
+
+    def __lt__(self, other):
+        """
+        the order is 'remove' < 'modify' < 'add'
+        :param other:
+        :return:
+        """
+        if not isinstance(other, self.__class__):
+            return not self >= other
+
+        if self.action != other.action:
+            if self.action == 'remove':
+                return_value = True
+            elif self.action == 'add':
+                return_value = False
+            else:
+                return_value = other.action == 'add'
+            return return_value
+
+        if self.action == 'add':
+            return self.entity_type == 'node' and other.entity_type == 'relationship'
+        if self.action == 'remove':
+            return self.entity_type == 'relationship' and other.entity_type == 'node'
+        return False
+
+
+class ServiceInstanceModificationBase(ModelMixin):
+    """
+    Deployment modification model representation.
+    """
+    __tablename__ = 'service_instance_modification'
+    __private_fields__ = ['service_instance_fk']
+
+    STARTED = 'started'
+    FINISHED = 'finished'
+    ROLLEDBACK = 'rolledback'
+
+    STATES = [STARTED, FINISHED, ROLLEDBACK]
+    END_STATES = [FINISHED, ROLLEDBACK]
+
+    context = Column(Dict)
+    created_at = Column(DateTime, nullable=False, index=True)
+    ended_at = Column(DateTime, index=True)
+    modified_nodes = Column(Dict)
+    node_instances = Column(Dict)
+    status = Column(Enum(*STATES, name='deployment_modification_status'))
+
+    @declared_attr
+    def service_instance_fk(cls):
+        return cls.foreign_key('service_instance')
+
+    @declared_attr
+    def service_instance(cls):
+        return cls.many_to_one_relationship('service_instance',
+                                            backreference='modifications')
+
+    @declared_attr
+    def service_instance_name(cls):
+        return association_proxy('service_instance', cls.name_column_name())
+
+
+class PluginBase(ModelMixin):
+    """
+    Plugin model representation.
+    """
+    __tablename__ = 'plugin'
+
+    archive_name = Column(Text, nullable=False, index=True)
+    distribution = Column(Text)
+    distribution_release = Column(Text)
+    distribution_version = Column(Text)
+    package_name = Column(Text, nullable=False, index=True)
+    package_source = Column(Text)
+    package_version = Column(Text)
+    supported_platform = Column(Text)
+    supported_py_versions = Column(List)
+    uploaded_at = Column(DateTime, nullable=False, index=True)
+    wheels = Column(List, nullable=False)
+
+
+class TaskBase(ModelMixin):
+    """
+    A Model which represents an task
+    """
+    __tablename__ = 'task'
+    __private_fields__ = ['node_fk',
+                          'relationship_fk',
+                          'execution_fk',
+                          'plugin_fk']
+
+    @declared_attr
+    def node_fk(cls):
+        return cls.foreign_key('node', nullable=True)
+
+    @declared_attr
+    def node_name(cls):
+        return association_proxy('node', cls.name_column_name())
+
+    @declared_attr
+    def node(cls):
+        return cls.many_to_one_relationship('node')
+
+    @declared_attr
+    def relationship_fk(cls):
+        return cls.foreign_key('relationship', nullable=True)
+
+    @declared_attr
+    def relationship_name(cls):
+        return association_proxy('relationships', cls.name_column_name())
+
+    @declared_attr
+    def relationship(cls):
+        return cls.many_to_one_relationship('relationship')
+
+    @declared_attr
+    def plugin_fk(cls):
+        return cls.foreign_key('plugin', nullable=True)
+
+    @declared_attr
+    def plugin(cls):
+        return cls.many_to_one_relationship('plugin')
+
+    @declared_attr
+    def execution_fk(cls):
+        return cls.foreign_key('execution', nullable=True)
+
+    @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())
+
+    PENDING = 'pending'
+    RETRYING = 'retrying'
+    SENT = 'sent'
+    STARTED = 'started'
+    SUCCESS = 'success'
+    FAILED = 'failed'
+    STATES = (
+        PENDING,
+        RETRYING,
+        SENT,
+        STARTED,
+        SUCCESS,
+        FAILED,
+    )
+
+    WAIT_STATES = [PENDING, RETRYING]
+    END_STATES = [SUCCESS, FAILED]
+
+    RUNS_ON_SOURCE = 'source'
+    RUNS_ON_TARGET = 'target'
+    RUNS_ON_NODE_INSTANCE = 'node_instance'
+    RUNS_ON = (RUNS_ON_NODE_INSTANCE, RUNS_ON_SOURCE, RUNS_ON_TARGET)
+
+    @orm.validates('max_attempts')
+    def validate_max_attempts(self, _, value):                                  # pylint: disable=no-self-use
+        """Validates that max attempts is either -1 or a positive number"""
+        if value < 1 and value != TaskBase.INFINITE_RETRIES:
+            raise ValueError('Max attempts can be either -1 (infinite) or any positive number. '
+                             'Got {value}'.format(value=value))
+        return value
+
+    INFINITE_RETRIES = -1
+
+    status = Column(Enum(*STATES, name='status'), default=PENDING)
+
+    due_at = Column(DateTime, default=datetime.utcnow)
+    started_at = Column(DateTime, default=None)
+    ended_at = Column(DateTime, default=None)
+    max_attempts = Column(Integer, default=1)
+    retry_count = Column(Integer, default=0)
+    retry_interval = Column(Float, default=0)
+    ignore_failure = Column(Boolean, default=False)
+
+    # Operation specific fields
+    implementation = Column(String)
+    inputs = Column(Dict)
+    # 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
+    def runs_on(self):
+        if self._runs_on == self.RUNS_ON_NODE_INSTANCE:
+            return self.node
+        elif self._runs_on == self.RUNS_ON_SOURCE:
+            return self.relationship.source_node  # pylint: disable=no-member
+        elif self._runs_on == self.RUNS_ON_TARGET:
+            return self.relationship.target_node  # pylint: disable=no-member
+        return None
+
+    @property
+    def actor(self):
+        """
+        Return the actor of the task
+        :return:
+        """
+        return self.node or self.relationship
+
+    @classmethod
+    def as_node_instance(cls, instance, runs_on, **kwargs):
+        return cls(node=instance, _runs_on=runs_on, **kwargs)
+
+    @classmethod
+    def as_relationship_instance(cls, instance, runs_on, **kwargs):
+        return cls(relationship=instance, _runs_on=runs_on, **kwargs)
+
+    @staticmethod
+    def abort(message=None):
+        raise TaskAbortException(message)
+
+    @staticmethod
+    def retry(message=None, retry_interval=None):
+        raise TaskRetryException(message, retry_interval=retry_interval)

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/f1074fee/aria/modeling/service_instance_models.py
----------------------------------------------------------------------
diff --git a/aria/modeling/service_instance_models.py b/aria/modeling/service_instance_models.py
new file mode 100644
index 0000000..52b88af
--- /dev/null
+++ b/aria/modeling/service_instance_models.py
@@ -0,0 +1,1225 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from sqlalchemy import (
+    Column,
+    Text,
+    Integer,
+    Boolean,
+)
+from sqlalchemy import DateTime
+from sqlalchemy.ext.associationproxy import association_proxy
+from sqlalchemy.ext.declarative import declared_attr
+from sqlalchemy.ext.orderinglist import ordering_list
+
+from ..parser import validation
+from ..utils import collections, formatting, console
+
+from . import (
+    utils,
+    structure,
+    type as aria_types
+)
+
+# pylint: disable=no-self-argument, no-member, abstract-method
+
+
+class ServiceInstanceBase(structure.InstanceModelMixin):
+    """
+    A service instance is an instance of a :class:`ServiceTemplate`.
+
+    You will usually not create it programmatically, but instead instantiate it from the template.
+
+    Properties:
+
+    * :code:`description`: Human-readable description
+    * :code:`meta_data`: Dict of :class:`Metadata`
+    * :code:`nodes`: Dict of :class:`Node`
+    * :code:`groups`: Dict of :class:`Group`
+    * :code:`policies`: Dict of :class:`Policy`
+    * :code:`substitution`: :class:`Substitution`
+    * :code:`inputs`: Dict of :class:`Parameter`
+    * :code:`outputs`: Dict of :class:`Parameter`
+    * :code:`operations`: Dict of :class:`Operation`
+    """
+
+    __tablename__ = 'service_instance'
+
+    __private_fields__ = ['substituion_fk',
+                          'service_template_fk']
+
+    description = Column(Text)
+
+    # region orchestrator required columns
+
+    created_at = Column(DateTime, nullable=False, index=True)
+    permalink = Column(Text)
+    policy_triggers = Column(aria_types.Dict)
+    policy_types = Column(aria_types.Dict)
+    scaling_groups = Column(aria_types.Dict)
+    updated_at = Column(DateTime)
+    workflows = Column(aria_types.Dict)
+
+    @declared_attr
+    def service_template_name(cls):
+        return association_proxy('service_template', 'name')
+
+    # endregion
+
+    # region foreign keys
+
+    @declared_attr
+    def substitution_fk(cls):
+        return cls.foreign_key('substitution', nullable=True)
+
+    @declared_attr
+    def service_template_fk(cls):
+        return cls.foreign_key('service_template')
+
+    # endregion
+
+    # region one-to-one relationships
+
+    @declared_attr
+    def substitution(cls):
+        return cls.one_to_one_relationship('substitution')
+
+    # endregion
+
+    # region one-to-many relationships
+
+    @declared_attr
+    def interfaces(cls):
+        return cls.one_to_many_relationship('interfaces', key_column_name='name')
+
+    # endregion
+
+    # region many-to-one relationships
+
+    @declared_attr
+    def service_template(cls):
+        return cls.many_to_one_relationship('service_template')
+
+    # endregion
+
+    # region many-to-many relationships
+
+    @declared_attr
+    def meta_data(cls):
+        # Warning! We cannot use the attr name "metadata" because it's used by SqlAlchemy!
+        return cls.many_to_many_relationship('metadata', key_column_name='name')
+    
+    @declared_attr
+    def inputs(cls):
+        return cls.many_to_many_relationship('parameter', table_prefix='inputs')
+
+    @declared_attr
+    def outputs(cls):
+        return cls.many_to_many_relationship('parameter', table_prefix='outputs')
+
+    # endregion
+
+    # association proxies
+
+    def satisfy_requirements(self, context):
+        satisfied = True
+        for node in self.nodes.all():
+            if not node.satisfy_requirements(context):
+                satisfied = False
+        return satisfied
+
+    def validate_capabilities(self, context):
+        satisfied = True
+        for node in self.nodes.all():
+            if not node.validate_capabilities(context):
+                satisfied = False
+        return satisfied
+
+    def find_nodes(self, node_template_name):
+        nodes = []
+        for node in self.nodes.all():
+            if node.template_name == node_template_name:
+                nodes.append(node)
+        return collections.FrozenList(nodes)
+
+    def get_node_ids(self, node_template_name):
+        return collections.FrozenList((node.id for node in self.find_nodes(node_template_name)))
+
+    def find_groups(self, group_template_name):
+        groups = []
+        for group in self.groups.all():
+            if group.template_name == group_template_name:
+                groups.append(group)
+        return collections.FrozenList(groups)
+
+    def get_group_ids(self, group_template_name):
+        return collections.FrozenList((group.id for group in self.find_groups(group_template_name)))
+
+    def is_node_a_target(self, context, target_node):
+        for node in self.nodes.all():
+            if self._is_node_a_target(context, node, target_node):
+                return True
+        return False
+
+    def _is_node_a_target(self, context, source_node, target_node):
+        if source_node.relationships:
+            for relationship in source_node.relationships:
+                if relationship.target_node_id == target_node.id:
+                    return True
+                else:
+                    node = context.modeling.instance.nodes.get(relationship.target_node_id)
+                    if node is not None:
+                        if self._is_node_a_target(context, node, target_node):
+                            return True
+        return False
+
+
+class OperationBase(structure.InstanceModelMixin):
+    """
+    An operation in a :class:`Interface`.
+
+    Properties:
+
+    * :code:`name`: Name
+    * :code:`description`: Description
+    * :code:`implementation`: Implementation string (interpreted by the orchestrator)
+    * :code:`dependencies`: List of strings (interpreted by the orchestrator)
+    * :code:`executor`: Executor string (interpreted by the orchestrator)
+    * :code:`max_retries`: Maximum number of retries allowed in case of failure
+    * :code:`retry_interval`: Interval between retries
+    * :code:`inputs`: Dict of :class:`Parameter`
+    """
+
+    __tablename__ = 'operation'
+
+    __private_fields__ = ['service_template_fk',
+                          'interface_instance_fk']
+
+    # region foreign_keys
+
+    @declared_attr
+    def service_instance_fk(cls):
+        return cls.foreign_key('service_instance', nullable=True)
+
+    @declared_attr
+    def interface_instance_fk(cls):
+        return cls.foreign_key('interface', nullable=True)
+
+    # endregion
+
+    description = Column(Text)
+    implementation = Column(Text)
+    dependencies = Column(aria_types.StrictList(item_cls=basestring))
+
+    executor = Column(Text)
+    max_retries = Column(Integer, default=None)
+    retry_interval = Column(Integer, default=None)
+    plugin = Column(Text)
+    operation = Column(Boolean)
+
+    # region many-to-one relationships
+
+    @declared_attr
+    def service_instance(cls):
+        return cls.many_to_one_relationship('service_instance')
+
+    @declared_attr
+    def interface(cls):
+        return cls.many_to_one_relationship('interface')
+
+    # endregion
+
+    # region many-to-many relationships
+
+    @declared_attr
+    def inputs(cls):
+        return cls.many_to_many_relationship('parameter', table_prefix='inputs')
+
+    # endregion
+
+    @property
+    def as_raw(self):
+        return collections.OrderedDict((
+            ('name', self.name),
+            ('description', self.description),
+            ('implementation', self.implementation),
+            ('dependencies', self.dependencies),
+            ('executor', self.executor),
+            ('max_retries', self.max_retries),
+            ('retry_interval', self.retry_interval),
+            ('inputs', formatting.as_raw_dict(self.inputs))))
+
+    def validate(self, context):
+        utils.validate_dict_values(context, self.inputs)
+
+    def coerce_values(self, context, container, report_issues):
+        utils.coerce_dict_values(context, container, self.inputs, report_issues)
+
+    def dump(self, context):
+        console.puts(context.style.node(self.name))
+        if self.description:
+            console.puts(context.style.meta(self.description))
+        with context.style.indent:
+            if self.implementation is not None:
+                console.puts('Implementation: %s' % context.style.literal(self.implementation))
+            if self.dependencies:
+                console.puts(
+                    'Dependencies: %s'
+                    % ', '.join((str(context.style.literal(v)) for v in self.dependencies)))
+            if self.executor is not None:
+                console.puts('Executor: %s' % context.style.literal(self.executor))
+            if self.max_retries is not None:
+                console.puts('Max retries: %s' % context.style.literal(self.max_retries))
+            if self.retry_interval is not None:
+                console.puts('Retry interval: %s' % context.style.literal(self.retry_interval))
+            utils.dump_parameters(context, self.inputs, 'Inputs')
+
+
+class InterfaceBase(structure.InstanceModelMixin):
+    """
+    A typed set of :class:`Operation`.
+
+    Properties:
+
+    * :code:`name`: Name
+    * :code:`description`: Description
+    * :code:`type_name`: Must be represented in the :class:`ModelingContext`
+    * :code:`inputs`: Dict of :class:`Parameter`
+    * :code:`operations`: Dict of :class:`Operation`
+    """
+
+    __tablename__ = 'interface'
+
+    __private_fields__ = ['group_fk',
+                          'node_fk',
+                          'relationship_fk']
+
+    # region foreign_keys
+
+    @declared_attr
+    def group_fk(cls):
+        return cls.foreign_key('group', nullable=True)
+
+    @declared_attr
+    def node_fk(cls):
+        return cls.foreign_key('node', nullable=True)
+
+    @declared_attr
+    def relationship_fk(cls):
+        return cls.foreign_key('relationship', nullable=True)
+
+    # endregion
+
+    description = Column(Text)
+    type_name = Column(Text)
+    edge = Column(Text)
+
+    # region many-to-one relationships
+
+    @declared_attr
+    def node(cls):
+        return cls.many_to_one_relationship('node')
+
+    @declared_attr
+    def relationship(cls):
+        return cls.many_to_one_relationship('relationship')
+
+    @declared_attr
+    def group(cls):
+        return cls.many_to_one_relationship('group')
+
+    # endregion
+
+    # region many-to-many relationships
+
+    @declared_attr
+    def inputs(cls):
+        return cls.many_to_many_relationship('parameter', table_prefix='inputs',
+                                             key_column_name='name')
+
+    # endregion
+
+    @property
+    def as_raw(self):
+        return collections.OrderedDict((
+            ('name', self.name),
+            ('description', self.description),
+            ('type_name', self.type_name),
+            ('inputs', formatting.as_raw_dict(self.inputs)),
+            ('operations', formatting.as_raw_list(self.operations))))
+
+    def validate(self, context):
+        if self.type_name:
+            if context.modeling.interface_types.get_descendant(self.type_name) is None:
+                context.validation.report('interface "%s" has an unknown type: %s'
+                                          % (self.name,
+                                             formatting.safe_repr(self.type_name)),
+                                          level=validation.Issue.BETWEEN_TYPES)
+
+        utils.validate_dict_values(context, self.inputs)
+        utils.validate_dict_values(context, self.operations)
+
+    def coerce_values(self, context, container, report_issues):
+        utils.coerce_dict_values(context, container, self.inputs, report_issues)
+        utils.coerce_dict_values(context, container, self.operations, report_issues)
+
+    def dump(self, context):
+        console.puts(context.style.node(self.name))
+        if self.description:
+            console.puts(context.style.meta(self.description))
+        with context.style.indent:
+            console.puts('Interface type: %s' % context.style.type(self.type_name))
+            utils.dump_parameters(context, self.inputs, 'Inputs')
+            utils.dump_dict_values(context, self.operations, 'Operations')
+
+
+class CapabilityBase(structure.InstanceModelMixin):
+    """
+    A capability of a :class:`Node`.
+
+    An instance of a :class:`CapabilityTemplate`.
+
+    Properties:
+
+    * :code:`name`: Name
+    * :code:`type_name`: Must be represented in the :class:`ModelingContext`
+    * :code:`min_occurrences`: Minimum number of requirement matches required
+    * :code:`max_occurrences`: Maximum number of requirement matches allowed
+    * :code:`properties`: Dict of :class:`Parameter`
+    """
+
+    __tablename__ = 'capability'
+
+    __private_fields__ = ['node_fk']
+
+    # region foreign_keys
+
+    @declared_attr
+    def node_fk(cls):
+        return cls.foreign_key('node')
+
+    # endregion
+
+    type_name = Column(Text)
+
+    min_occurrences = Column(Integer, default=None) # optional
+    max_occurrences = Column(Integer, default=None) # optional
+    occurrences = Column(Integer, default=0)
+
+    # region many-to-one relationships
+
+    @declared_attr
+    def node(cls):
+        return cls.many_to_one_relationship('node')
+
+    # endregion
+
+    # region many-to-many relationships
+
+    @declared_attr
+    def properties(cls):
+        return cls.many_to_many_relationship('parameter', table_prefix='properties',
+                                             key_column_name='name')
+
+    # endregion
+
+    @property
+    def has_enough_relationships(self):
+        if self.min_occurrences is not None:
+            return self.occurrences >= self.min_occurrences
+        return True
+
+    def relate(self):
+        if self.max_occurrences is not None:
+            if self.occurrences == self.max_occurrences:
+                return False
+        self.occurrences += 1
+        return True
+
+    @property
+    def as_raw(self):
+        return collections.OrderedDict((
+            ('name', self.name),
+            ('type_name', self.type_name),
+            ('properties', formatting.as_raw_dict(self.properties))))
+
+    def validate(self, context):
+        if context.modeling.capability_types.get_descendant(self.type_name) is None:
+            context.validation.report('capability "%s" has an unknown type: %s'
+                                      % (self.name,
+                                         formatting.safe_repr(self.type_name)),
+                                      level=validation.Issue.BETWEEN_TYPES)
+
+        utils.validate_dict_values(context, self.properties)
+
+    def coerce_values(self, context, container, report_issues):
+        utils.coerce_dict_values(context, container, self.properties, report_issues)
+
+    def dump(self, context):
+        console.puts(context.style.node(self.name))
+        with context.style.indent:
+            console.puts('Type: %s' % context.style.type(self.type_name))
+            console.puts('Occurrences: %s (%s%s)'
+                         % (self.occurrences,
+                            self.min_occurrences or 0,
+                            (' to %d' % self.max_occurrences)
+                            if self.max_occurrences is not None
+                            else ' or more'))
+            utils.dump_parameters(context, self.properties)
+
+
+class ArtifactBase(structure.InstanceModelMixin):
+    """
+    A file associated with a :class:`Node`.
+
+    Properties:
+
+    * :code:`name`: Name
+    * :code:`description`: Description
+    * :code:`type_name`: Must be represented in the :class:`ModelingContext`
+    * :code:`source_path`: Source path (CSAR or repository)
+    * :code:`target_path`: Path at destination machine
+    * :code:`repository_url`: Repository URL
+    * :code:`repository_credential`: Dict of string
+    * :code:`properties`: Dict of :class:`Parameter`
+    """
+
+    __tablename__ = 'artifact'
+
+    __private_fields__ = ['node_fk']
+
+    # region foreign_keys
+
+    @declared_attr
+    def node_fk(cls):
+        return cls.foreign_key('node')
+
+    # endregion
+
+    description = Column(Text)
+    type_name = Column(Text)
+    source_path = Column(Text)
+    target_path = Column(Text)
+    repository_url = Column(Text)
+    repository_credential = Column(aria_types.StrictDict(basestring, basestring))
+
+    # region many-to-one relationships
+
+    @declared_attr
+    def node(cls):
+        return cls.many_to_one_relationship('node')
+
+    # endregion
+
+    # region many-to-many relationships
+
+    @declared_attr
+    def properties(cls):
+        return cls.many_to_many_relationship('parameter', table_prefix='properties',
+                                             key_column_name='name')
+
+    # endregion
+
+    @property
+    def as_raw(self):
+        return collections.OrderedDict((
+            ('name', self.name),
+            ('description', self.description),
+            ('type_name', self.type_name),
+            ('source_path', self.source_path),
+            ('target_path', self.target_path),
+            ('repository_url', self.repository_url),
+            ('repository_credential', formatting.as_agnostic(self.repository_credential)),
+            ('properties', formatting.as_raw_dict(self.properties))))
+
+    def validate(self, context):
+        if context.modeling.artifact_types.get_descendant(self.type_name) is None:
+            context.validation.report('artifact "%s" has an unknown type: %s'
+                                      % (self.name,
+                                         formatting.safe_repr(self.type_name)),
+                                      level=validation.Issue.BETWEEN_TYPES)
+        utils.validate_dict_values(context, self.properties)
+
+    def coerce_values(self, context, container, report_issues):
+        utils.coerce_dict_values(context, container, self.properties, report_issues)
+
+    def dump(self, context):
+        console.puts(context.style.node(self.name))
+        if self.description:
+            console.puts(context.style.meta(self.description))
+        with context.style.indent:
+            console.puts('Artifact type: %s' % context.style.type(self.type_name))
+            console.puts('Source path: %s' % context.style.literal(self.source_path))
+            if self.target_path is not None:
+                console.puts('Target path: %s' % context.style.literal(self.target_path))
+            if self.repository_url is not None:
+                console.puts('Repository URL: %s' % context.style.literal(self.repository_url))
+            if self.repository_credential:
+                console.puts('Repository credential: %s'
+                             % context.style.literal(self.repository_credential))
+            utils.dump_parameters(context, self.properties)
+
+
+class PolicyBase(structure.InstanceModelMixin):
+    """
+    An instance of a :class:`PolicyTemplate`.
+
+    Properties:
+
+    * :code:`name`: Name
+    * :code:`type_name`: Must be represented in the :class:`ModelingContext`
+    * :code:`properties`: Dict of :class:`Parameter`
+    * :code:`target_node_ids`: Must be represented in the :class:`ServiceInstance`
+    * :code:`target_group_ids`: Must be represented in the :class:`ServiceInstance`
+    """
+
+    __tablename__ = 'policy'
+
+    __private_fields__ = ['service_instance_fk']
+
+    # region foreign_keys
+
+    @declared_attr
+    def service_instance_fk(cls):
+        return cls.foreign_key('service_instance')
+
+    # endregion
+
+    type_name = Column(Text)
+    target_node_ids = Column(aria_types.StrictList(basestring))
+    target_group_ids = Column(aria_types.StrictList(basestring))
+
+    # region many-to-one relationships
+
+    @declared_attr
+    def service_instnce(cls):
+        return cls.many_to_one_relationship('service_instance')
+
+    # region many-to-many relationships
+
+    @declared_attr
+    def properties(cls):
+        return cls.many_to_many_relationship('parameter', table_prefix='properties',
+                                             key_column_name='name')
+
+    # endregion
+
+    @property
+    def as_raw(self):
+        return collections.OrderedDict((
+            ('name', self.name),
+            ('type_name', self.type_name),
+            ('properties', formatting.as_raw_dict(self.properties)),
+            ('target_node_ids', self.target_node_ids),
+            ('target_group_ids', self.target_group_ids)))
+
+    def validate(self, context):
+        if context.modeling.policy_types.get_descendant(self.type_name) is None:
+            context.validation.report('policy "%s" has an unknown type: %s'
+                                      % (self.name, utils.safe_repr(self.type_name)),
+                                      level=validation.Issue.BETWEEN_TYPES)
+
+        utils.validate_dict_values(context, self.properties)
+
+    def coerce_values(self, context, container, report_issues):
+        utils.coerce_dict_values(context, container, self.properties, report_issues)
+
+    def dump(self, context):
+        console.puts('Policy: %s' % context.style.node(self.name))
+        with context.style.indent:
+            console.puts('Type: %s' % context.style.type(self.type_name))
+            utils.dump_parameters(context, self.properties)
+            if self.target_node_ids:
+                console.puts('Target nodes:')
+                with context.style.indent:
+                    for node_id in self.target_node_ids:
+                        console.puts(context.style.node(node_id))
+            if self.target_group_ids:
+                console.puts('Target groups:')
+                with context.style.indent:
+                    for group_id in self.target_group_ids:
+                        console.puts(context.style.node(group_id))
+
+
+class MappingBase(structure.InstanceModelMixin):
+    """
+    An instance of a :class:`MappingTemplate`.
+
+    Properties:
+
+    * :code:`mapped_name`: Exposed capability or requirement name
+    * :code:`node_id`: Must be represented in the :class:`ServiceInstance`
+    * :code:`name`: Name of capability or requirement at the node
+    """
+
+    __tablename__ = 'mapping'
+
+    mapped_name = Column(Text)
+    node_id = Column(Text)
+
+    @property
+    def as_raw(self):
+        return collections.OrderedDict((
+            ('mapped_name', self.mapped_name),
+            ('node_id', self.node_id),
+            ('name', self.name)))
+
+    def dump(self, context):
+        console.puts('%s -> %s.%s'
+                     % (context.style.node(self.mapped_name),
+                        context.style.node(self.node_id),
+                        context.style.node(self.name)))
+
+
+class SubstitutionBase(structure.InstanceModelMixin):
+    """
+    An instance of a :class:`SubstitutionTemplate`.
+
+    Properties:
+
+    * :code:`node_type_name`: Must be represented in the :class:`ModelingContext`
+    * :code:`capabilities`: Dict of :class:`Mapping`
+    * :code:`requirements`: Dict of :class:`Mapping`
+    """
+
+    __tablename__ = 'substitution'
+
+    node_type_name = Column(Text)
+
+    # region many-to-many relationships
+
+    @declared_attr
+    def capabilities(cls):
+        return cls.many_to_many_relationship('mapping', table_prefix='capabilities')
+
+    @declared_attr
+    def requirements(cls):
+        return cls.many_to_many_relationship('mapping',
+                                             table_prefix='requirements',
+                                             relationship_kwargs=dict(lazy='dynamic'))
+
+    # endregion
+
+    @property
+    def as_raw(self):
+        return collections.OrderedDict((
+            ('node_type_name', self.node_type_name),
+            ('capabilities', formatting.as_raw_list(self.capabilities)),
+            ('requirements', formatting.as_raw_list(self.requirements))))
+
+    def validate(self, context):
+        if context.modeling.node_types.get_descendant(self.node_type_name) is None:
+            context.validation.report('substitution "%s" has an unknown type: %s'
+                                      % (self.name,  # pylint: disable=no-member
+                                         # TODO fix self.name reference
+                                         formatting.safe_repr(self.node_type_name)),
+                                      level=validation.Issue.BETWEEN_TYPES)
+
+        utils.validate_dict_values(context, self.capabilities)
+        utils.validate_dict_values(context, self.requirements)
+
+    def coerce_values(self, context, container, report_issues):
+        utils.coerce_dict_values(context, container, self.capabilities, report_issues)
+        utils.coerce_dict_values(context, container, self.requirements, report_issues)
+
+    def dump(self, context):
+        console.puts('Substitution:')
+        with context.style.indent:
+            console.puts('Node type: %s' % context.style.type(self.node_type_name))
+            utils.dump_dict_values(context, self.capabilities, 'Capability mappings')
+            utils.dump_dict_values(context, self.requirements, 'Requirement mappings')
+
+
+class NodeBase(structure.InstanceModelMixin):
+    """
+    An instance of a :class:`NodeTemplate`.
+
+    Nodes may have zero or more :class:`Relationship` instances to other nodes.
+
+    Properties:
+
+    * :code:`id`: Unique ID (prefixed with the template name)
+    * :code:`type_name`: Must be represented in the :class:`ModelingContext`
+    * :code:`template_name`: Must be represented in the :class:`ServiceTemplate`
+    * :code:`properties`: Dict of :class:`Parameter`
+    * :code:`interfaces`: Dict of :class:`Interface`
+    * :code:`artifacts`: Dict of :class:`Artifact`
+    * :code:`capabilities`: Dict of :class:`CapabilityTemplate`
+    * :code:`relationships`: List of :class:`Relationship`
+    """
+
+    __tablename__ = 'node'
+
+    __private_fields__ = ['service_instance_fk',
+                          'host_fk',
+                          'node_template_fk']
+
+    # region foreign_keys
+
+    @declared_attr
+    def service_instance_fk(cls):
+        return cls.foreign_key('service_instance')
+
+    @declared_attr
+    def host_fk(cls):
+        return cls.foreign_key('node', nullable=True)
+
+    @declared_attr
+    def node_template_fk(cls):
+        return cls.foreign_key('node_template')
+
+    # endregion
+
+    type_name = Column(Text)
+    template_name = Column(Text)
+
+    # region orchestrator required columns
+
+    runtime_properties = Column(aria_types.Dict)
+    scaling_groups = Column(aria_types.List)
+    state = Column(Text, nullable=False)
+    version = Column(Integer, default=1)
+
+    @declared_attr
+    def plugins(cls):
+        return association_proxy('node_template', 'plugins')
+
+    @declared_attr
+    def host(cls):
+        return cls.relationship_to_self('host_fk')
+
+    @declared_attr
+    def service_instance_name(cls):
+        return association_proxy('service_instance', 'name')
+
+    @property
+    def ip(self):
+        if not self.host_fk:
+            return None
+        host_node = self.host
+        if 'ip' in host_node.runtime_properties:  # pylint: disable=no-member
+            return host_node.runtime_properties['ip']  # pylint: disable=no-member
+        host_node = host_node.node_template  # pylint: disable=no-member
+        host_ip_property = [prop for prop in host_node.properties if prop.name == 'ip']
+        if host_ip_property:
+            return host_ip_property[0].value
+        return None
+
+    @declared_attr
+    def node_template(cls):
+        return cls.many_to_one_relationship('node_template')
+
+    @declared_attr
+    def service_template(cls):
+        return association_proxy('service_instance', 'service_template')
+
+    # endregion
+
+    # region one-to-many relationships
+
+    @declared_attr
+    def interfaces(cls):
+        return cls.one_to_many_relationship('interfaces', key_column_name='name')
+
+    # endregion
+
+    # region many-to-one relationships
+
+    @declared_attr
+    def service_instance(cls):
+        return cls.many_to_one_relationship('service_instance')
+
+    # endregion
+
+    # region many-to-many relationships
+
+    @declared_attr
+    def properties(cls):
+        return cls.many_to_many_relationship('parameter', table_prefix='properties',
+                                             key_column_name='name')
+
+    # endregion
+
+    def satisfy_requirements(self, context):
+        node_template = context.modeling.model.node_templates.get(self.template_name)
+        satisfied = True
+        for i in range(len(node_template.requirement_templates)):
+            requirement_template = node_template.requirement_templates[i]
+
+            # Find target template
+            target_node_template, target_node_capability = \
+                requirement_template.find_target(context, node_template)
+            if target_node_template is not None:
+                satisfied = self._satisfy_capability(context,
+                                                     target_node_capability,
+                                                     target_node_template,
+                                                     requirement_template,
+                                                     requirement_template_index=i)
+            else:
+                context.validation.report('requirement "%s" of node "%s" has no target node '
+                                          'template' % (requirement_template.name,
+                                                        self.id),
+                                          level=validation.Issue.BETWEEN_INSTANCES)
+                satisfied = False
+        return satisfied
+
+    def _satisfy_capability(self, context, target_node_capability, target_node_template,
+                            requirement_template, requirement_template_index):
+        # Find target nodes
+        target_nodes = context.modeling.instance.find_nodes(target_node_template.name)
+        if target_nodes:
+            target_node = None
+            target_capability = None
+
+            if target_node_capability is not None:
+                # Relate to the first target node that has capacity
+                for node in target_nodes:
+                    target_capability = node.capabilities.get(target_node_capability.name)
+                    if target_capability.relate():
+                        target_node = node
+                        break
+            else:
+                # Use first target node
+                target_node = target_nodes[0]
+
+            if target_node is not None:
+                relationship = RelationshipBase(
+                    name=requirement_template.name,
+                    source_requirement_index=requirement_template_index,
+                    target_node_id=target_node.id,
+                    target_capability_name=target_capability.name
+                )
+                self.relationships.append(relationship)
+            else:
+                context.validation.report('requirement "%s" of node "%s" targets node '
+                                          'template "%s" but its instantiated nodes do not '
+                                          'have enough capacity'
+                                          % (requirement_template.name,
+                                             self.id,
+                                             target_node_template.name),
+                                          level=validation.Issue.BETWEEN_INSTANCES)
+                return False
+        else:
+            context.validation.report('requirement "%s" of node "%s" targets node template '
+                                      '"%s" but it has no instantiated nodes'
+                                      % (requirement_template.name,
+                                         self.id,
+                                         target_node_template.name),
+                                      level=validation.Issue.BETWEEN_INSTANCES)
+            return False
+
+    def validate_capabilities(self, context):
+        satisfied = False
+        for capability in self.capabilities.itervalues():
+            if not capability.has_enough_relationships:
+                context.validation.report('capability "%s" of node "%s" requires at least %d '
+                                          'relationships but has %d'
+                                          % (capability.name,
+                                             self.id,
+                                             capability.min_occurrences,
+                                             capability.occurrences),
+                                          level=validation.Issue.BETWEEN_INSTANCES)
+                satisfied = False
+        return satisfied
+
+    @property
+    def as_raw(self):
+        return collections.OrderedDict((
+            ('id', self.id),
+            ('type_name', self.type_name),
+            ('template_name', self.template_name),
+            ('properties', formatting.as_raw_dict(self.properties)),
+            ('interfaces', formatting.as_raw_list(self.interfaces)),
+            ('artifacts', formatting.as_raw_list(self.artifacts)),
+            ('capabilities', formatting.as_raw_list(self.capabilities)),
+            ('relationships', formatting.as_raw_list(self.relationships))))
+
+    def validate(self, context):
+        if len(self.id) > context.modeling.id_max_length:
+            context.validation.report('"%s" has an ID longer than the limit of %d characters: %d'
+                                      % (self.id,
+                                         context.modeling.id_max_length,
+                                         len(self.id)),
+                                      level=validation.Issue.BETWEEN_INSTANCES)
+
+        # TODO: validate that node template is of type?
+
+        utils.validate_dict_values(context, self.properties)
+        utils.validate_dict_values(context, self.interfaces)
+        utils.validate_dict_values(context, self.artifacts)
+        utils.validate_dict_values(context, self.capabilities)
+        utils.validate_list_values(context, self.relationships)
+
+    def coerce_values(self, context, container, report_issues):
+        utils.coerce_dict_values(context, self, self.properties, report_issues)
+        utils.coerce_dict_values(context, self, self.interfaces, report_issues)
+        utils.coerce_dict_values(context, self, self.artifacts, report_issues)
+        utils.coerce_dict_values(context, self, self.capabilities, report_issues)
+        utils.coerce_list_values(context, self, self.relationships, report_issues)
+
+    def dump(self, context):
+        console.puts('Node: %s' % context.style.node(self.id))
+        with context.style.indent:
+            console.puts('Template: %s' % context.style.node(self.template_name))
+            console.puts('Type: %s' % context.style.type(self.type_name))
+            utils.dump_parameters(context, self.properties)
+            utils.dump_interfaces(context, self.interfaces)
+            utils.dump_dict_values(context, self.artifacts, 'Artifacts')
+            utils.dump_dict_values(context, self.capabilities, 'Capabilities')
+            utils.dump_list_values(context, self.relationships, 'Relationships')
+
+
+class GroupBase(structure.InstanceModelMixin):
+    """
+    An instance of a :class:`GroupTemplate`.
+
+    Properties:
+
+    * :code:`id`: Unique ID (prefixed with the template name)
+    * :code:`type_name`: Must be represented in the :class:`ModelingContext`
+    * :code:`template_name`: Must be represented in the :class:`ServiceTemplate`
+    * :code:`properties`: Dict of :class:`Parameter`
+    * :code:`interfaces`: Dict of :class:`Interface`
+    * :code:`policies`: Dict of :class:`GroupPolicy`
+    * :code:`member_node_ids`: Must be represented in the :class:`ServiceInstance`
+    * :code:`member_group_ids`: Must be represented in the :class:`ServiceInstance`
+    """
+
+    __tablename__ = 'group'
+
+    __private_fields__ = ['service_instance_fk']
+
+    # region foreign_keys
+
+    @declared_attr
+    def service_instance_fk(cls):
+        return cls.foreign_key('service_instance')
+
+    # endregion
+
+    type_name = Column(Text)
+    template_name = Column(Text)
+    member_node_ids = Column(aria_types.StrictList(basestring))
+    member_group_ids = Column(aria_types.StrictList(basestring))
+
+    # region one-to-many relationships
+
+    @declared_attr
+    def interfaces(cls):
+        return cls.one_to_many_relationship('interfaces', key_column_name='name')
+
+    # endregion
+
+    # region many-to-one relationships
+
+    @declared_attr
+    def service_instance(cls):
+        return cls.many_to_one_relationship('service_instance')
+
+    # endregion
+
+    # region many-to-many relationships
+
+    @declared_attr
+    def properties(cls):
+        return cls.many_to_many_relationship('parameter', table_prefix='properties',
+                                             key_column_name='name')
+
+    # endregion
+
+    @property
+    def as_raw(self):
+        return collections.OrderedDict((
+            ('id', self.id),
+            ('type_name', self.type_name),
+            ('template_name', self.template_name),
+            ('properties', formatting.as_raw_dict(self.properties)),
+            ('interfaces', formatting.as_raw_list(self.interfaces)),
+            ('policies', formatting.as_raw_list(self.policies)),
+            ('member_node_ids', self.member_node_ids),
+            ('member_group_ids', self.member_group_ids)))
+
+    def validate(self, context):
+        if context.modeling.group_types.get_descendant(self.type_name) is None:
+            context.validation.report('group "%s" has an unknown type: %s'
+                                      % (self.name,  # pylint: disable=no-member
+                                         # TODO fix self.name reference
+                                         formatting.safe_repr(self.type_name)),
+                                      level=validation.Issue.BETWEEN_TYPES)
+
+        utils.validate_dict_values(context, self.properties)
+        utils.validate_dict_values(context, self.interfaces)
+        utils.validate_dict_values(context, self.policies)
+
+    def coerce_values(self, context, container, report_issues):
+        utils.coerce_dict_values(context, container, self.properties, report_issues)
+        utils.coerce_dict_values(context, container, self.interfaces, report_issues)
+        utils.coerce_dict_values(context, container, self.policies, report_issues)
+
+    def dump(self, context):
+        console.puts('Group: %s' % context.style.node(self.id))
+        with context.style.indent:
+            console.puts('Type: %s' % context.style.type(self.type_name))
+            console.puts('Template: %s' % context.style.type(self.template_name))
+            utils.dump_parameters(context, self.properties)
+            utils.dump_interfaces(context, self.interfaces)
+            utils.dump_dict_values(context, self.policies, 'Policies')
+            if self.member_node_ids:
+                console.puts('Member nodes:')
+                with context.style.indent:
+                    for node_id in self.member_node_ids:
+                        console.puts(context.style.node(node_id))
+
+
+class RelationshipBase(structure.InstanceModelMixin):
+    """
+    Connects :class:`Node` to another node.
+
+    An instance of a :class:`RelationshipTemplate`.
+
+    Properties:
+
+    * :code:`name`: Name (usually the name of the requirement at the source node template)
+    * :code:`source_requirement_index`: Must be represented in the source node template
+    * :code:`target_node_id`: Must be represented in the :class:`ServiceInstance`
+    * :code:`target_capability_name`: Matches the capability at the target node
+    * :code:`type_name`: Must be represented in the :class:`ModelingContext`
+    * :code:`template_name`: Must be represented in the :class:`ServiceTemplate`
+    * :code:`properties`: Dict of :class:`Parameter`
+    * :code:`source_interfaces`: Dict of :class:`Interface`
+    * :code:`target_interfaces`: Dict of :class:`Interface`
+    """
+
+    __tablename__ = 'relationship'
+
+    __private_fields__ = ['source_node_fk',
+                          'target_node_fk']
+
+    source_requirement_index = Column(Integer)
+    target_node_id = Column(Text)
+    target_capability_name = Column(Text)
+    type_name = Column(Text)
+    template_name = Column(Text)
+
+    # region orchestrator required columns
+
+    source_position = Column(Integer)
+    target_position = Column(Integer)
+
+    @declared_attr
+    def source_node_fk(cls):
+        return cls.foreign_key('node', nullable=True)
+
+    @declared_attr
+    def source_node(cls):
+        return cls.many_to_one_relationship(
+            'node',
+            'source_node_fk',
+            backreference='outbound_relationships',
+            backref_kwargs=dict(
+                order_by=cls.source_position,
+                collection_class=ordering_list('source_position', count_from=0),
+            )
+        )
+
+    @declared_attr
+    def source_node_name(cls):
+        return association_proxy('source_node', cls.name_column_name())
+
+    @declared_attr
+    def target_node_fk(cls):
+        return cls.foreign_key('node', nullable=True)
+
+    @declared_attr
+    def target_node(cls):
+        return cls.many_to_one_relationship(
+            'node',
+            'target_node_fk',
+            backreference='inbound_relationships',
+            backref_kwargs=dict(
+                order_by=cls.target_position,
+                collection_class=ordering_list('target_position', count_from=0),
+            )
+        )
+
+    @declared_attr
+    def target_node_name(cls):
+        return association_proxy('target_node', cls.name_column_name())
+
+    # endregion
+
+    # region one-to-many relationships
+
+    @declared_attr
+    def interfaces(cls):
+        return cls.one_to_many_relationship('interfaces', key_column_name='name')
+
+    # endregion
+
+    # region many-to-many relationships
+
+    @declared_attr
+    def properties(cls):
+        return cls.many_to_many_relationship('parameter', table_prefix='properties',
+                                             key_column_name='name')
+
+    # endregion
+
+    @property
+    def as_raw(self):
+        return collections.OrderedDict((
+            ('name', self.name),
+            ('source_requirement_index', self.source_requirement_index),
+            ('target_node_id', self.target_node_id),
+            ('target_capability_name', self.target_capability_name),
+            ('type_name', self.type_name),
+            ('template_name', self.template_name),
+            ('properties', formatting.as_raw_dict(self.properties)),
+            ('interfaces', formatting.as_raw_list(self.interfaces))))
+
+    def validate(self, context):
+        if self.type_name:
+            if context.modeling.relationship_types.get_descendant(self.type_name) is None:
+                context.validation.report('relationship "%s" has an unknown type: %s'
+                                          % (self.name,
+                                             formatting.safe_repr(self.type_name)),
+                                          level=validation.Issue.BETWEEN_TYPES)
+        utils.validate_dict_values(context, self.properties)
+        utils.validate_dict_values(context, self.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:
+            if self.source_requirement_index is not None:
+                console.puts('%s (%d) ->' % (
+                    context.style.node(self.name),
+                    self.source_requirement_index))
+            else:
+                console.puts('%s ->' % context.style.node(self.name))
+        else:
+            console.puts('->')
+        with context.style.indent:
+            console.puts('Node: %s' % context.style.node(self.target_node_id))
+            if self.target_capability_name is not None:
+                console.puts('Capability: %s' % context.style.node(self.target_capability_name))
+            if self.type_name is not None:
+                console.puts('Relationship type: %s' % context.style.type(self.type_name))
+            if self.template_name is not None:
+                console.puts('Relationship template: %s' % context.style.node(self.template_name))
+            utils.dump_parameters(context, self.properties)
+            utils.dump_interfaces(context, self.interfaces, 'Interfaces')