ariatosca-dev mailing list archives

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

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/dd5bfa93/aria/modeling/service_changes.py
----------------------------------------------------------------------
diff --git a/aria/modeling/service_changes.py b/aria/modeling/service_changes.py
new file mode 100644
index 0000000..b83a376
--- /dev/null
+++ b/aria/modeling/service_changes.py
@@ -0,0 +1,219 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+classes:
+    * ServiceUpdate - service update implementation model.
+    * ServiceUpdateStep - service update step implementation model.
+    * ServiceModification - service modification implementation model.
+"""
+
+# pylint: disable=no-self-argument, no-member, abstract-method
+
+from collections import namedtuple
+
+from sqlalchemy import (
+    Column,
+    Text,
+    DateTime,
+    Enum,
+)
+from sqlalchemy.ext.associationproxy import association_proxy
+from sqlalchemy.ext.declarative import declared_attr
+
+from .types import (List, Dict)
+from .mixins import ModelMixin
+
+__all__ = (
+    'ServiceUpdateBase',
+    'ServiceUpdateStepBase',
+    'ServiceModificationBase'
+)
+
+
+class ServiceUpdateBase(ModelMixin):
+    """
+    Deployment update model representation.
+    """
+
+    steps = None
+
+    __tablename__ = 'service_update'
+
+    _private_fields = ['execution_fk',
+                       'service_fk']
+
+    created_at = Column(DateTime, nullable=False, index=True)
+    service_plan = Column(Dict, nullable=False)
+    service_update_nodes = Column(Dict)
+    service_update_service = Column(Dict)
+    service_update_node_templates = Column(List)
+    modified_entity_ids = Column(Dict)
+    state = Column(Text)
+
+    @declared_attr
+    def execution(cls):
+        return cls._create_many_to_one_relationship('execution')
+
+    @declared_attr
+    def execution_name(cls):
+        return association_proxy('execution', cls.name_column_name())
+
+    @declared_attr
+    def service(cls):
+        return cls._create_many_to_one_relationship('service',
+                                                    backreference='updates')
+
+    @declared_attr
+    def service_name(cls):
+        return association_proxy('service', cls.name_column_name())
+
+    # region foreign keys
+
+    __private_fields__ = ['service_fk',
+                          'execution_fk']
+
+    @declared_attr
+    def execution_fk(cls):
+        return cls._create_foreign_key('execution', nullable=True)
+
+    @declared_attr
+    def service_fk(cls):
+        return cls._create_foreign_key('service')
+
+    # endregion
+
+    def to_dict(self, suppress_error=False, **kwargs):
+        dep_update_dict = super(ServiceUpdateBase, self).to_dict(suppress_error)     #pylint: disable=no-member
+        # Taking care of the fact the DeploymentSteps are _BaseModels
+        dep_update_dict['steps'] = [step.to_dict() for step in self.steps]
+        return dep_update_dict
+
+
+class ServiceUpdateStepBase(ModelMixin):
+    """
+    Deployment update step model representation.
+    """
+
+    __tablename__ = 'service_update_step'
+
+    _action_types = namedtuple('ACTION_TYPES', 'ADD, REMOVE, MODIFY')
+    ACTION_TYPES = _action_types(ADD='add', REMOVE='remove', MODIFY='modify')
+
+    _entity_types = namedtuple(
+        'ENTITY_TYPES',
+        'NODE, RELATIONSHIP, PROPERTY, OPERATION, WORKFLOW, OUTPUT, DESCRIPTION, GROUP, PLUGIN')
+    ENTITY_TYPES = _entity_types(
+        NODE='node',
+        RELATIONSHIP='relationship',
+        PROPERTY='property',
+        OPERATION='operation',
+        WORKFLOW='workflow',
+        OUTPUT='output',
+        DESCRIPTION='description',
+        GROUP='group',
+        PLUGIN='plugin'
+    )
+
+    action = Column(Enum(*ACTION_TYPES, name='action_type'), nullable=False)
+    entity_id = Column(Text, nullable=False)
+    entity_type = Column(Enum(*ENTITY_TYPES, name='entity_type'), nullable=False)
+
+    @declared_attr
+    def service_update(cls):
+        return cls._create_many_to_one_relationship('service_update',
+                                                    backreference='steps')
+
+    @declared_attr
+    def service_update_name(cls):
+        return association_proxy('service_update', cls.name_column_name())
+
+    # region foreign keys
+
+    __private_fields__ = ['service_update_fk']
+
+    @declared_attr
+    def service_update_fk(cls):
+        return cls._create_foreign_key('service_update')
+
+    # endregion
+
+    def __hash__(self):
+        return hash((getattr(self, self.id_column_name()), self.entity_id))
+
+    def __lt__(self, other):
+        """
+        the order is 'remove' < 'modify' < 'add'
+        :param other:
+        :return:
+        """
+        if not isinstance(other, self.__class__):
+            return not self >= other
+
+        if self.action != other.action:
+            if self.action == 'remove':
+                return_value = True
+            elif self.action == 'add':
+                return_value = False
+            else:
+                return_value = other.action == 'add'
+            return return_value
+
+        if self.action == 'add':
+            return self.entity_type == 'node' and other.entity_type == 'relationship'
+        if self.action == 'remove':
+            return self.entity_type == 'relationship' and other.entity_type == 'node'
+        return False
+
+
+class ServiceModificationBase(ModelMixin):
+    """
+    Deployment modification model representation.
+    """
+
+    __tablename__ = 'service_modification'
+
+    STARTED = 'started'
+    FINISHED = 'finished'
+    ROLLEDBACK = 'rolledback'
+
+    STATES = [STARTED, FINISHED, ROLLEDBACK]
+    END_STATES = [FINISHED, ROLLEDBACK]
+
+    context = Column(Dict)
+    created_at = Column(DateTime, nullable=False, index=True)
+    ended_at = Column(DateTime, index=True)
+    modified_node_templates = Column(Dict)
+    nodes = Column(Dict)
+    status = Column(Enum(*STATES, name='service_modification_status'))
+
+    @declared_attr
+    def service(cls):
+        return cls._create_many_to_one_relationship('service',
+                                                    backreference='modifications')
+
+    @declared_attr
+    def service_name(cls):
+        return association_proxy('service', cls.name_column_name())
+
+    # region foreign keys
+
+    __private_fields__ = ['service_fk']
+
+    @declared_attr
+    def service_fk(cls):
+        return cls._create_foreign_key('service')
+
+    # endregion

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/dd5bfa93/aria/modeling/service_common.py
----------------------------------------------------------------------
diff --git a/aria/modeling/service_common.py b/aria/modeling/service_common.py
new file mode 100644
index 0000000..b3535a6
--- /dev/null
+++ b/aria/modeling/service_common.py
@@ -0,0 +1,270 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# pylint: disable=no-self-argument, no-member, abstract-method
+
+import cPickle as pickle
+import logging
+
+from sqlalchemy import (
+    Column,
+    Text,
+    Binary,
+)
+from sqlalchemy.ext.declarative import declared_attr
+
+from ..storage import exceptions
+from ..utils import collections, formatting, console
+from .mixins import InstanceModelMixin, TemplateModelMixin
+from .types import List
+from . import utils
+
+
+class ParameterBase(TemplateModelMixin):
+    """
+    Represents a typed value.
+
+    This model is used by both service template and service instance elements.
+
+    :ivar name: Name
+    :ivar type_name: Type name
+    :ivar value: Value
+    :ivar description: Description
+    """
+
+    __tablename__ = 'parameter'
+
+    name = Column(Text)
+    type_name = Column(Text)
+
+    # Check: value type
+    _value = Column(Binary, name='value')
+    description = Column(Text)
+
+    @property
+    def as_raw(self):
+        return collections.OrderedDict((
+            ('name', self.name),
+            ('type_name', self.type_name),
+            ('value', self.value),
+            ('description', self.description)))
+
+    @property
+    def value(self):
+        if self._value is None:
+            return None
+        try:
+            return pickle.loads(self._value)
+        except BaseException:
+            raise exceptions.StorageError('bad format for parameter of type "{0}": {1}'.format(
+                self.type_name, self._value))
+
+    @value.setter
+    def value(self, value):
+        if value is None:
+            self._value = None
+        else:
+            try:
+                self._value = pickle.dumps(value)
+            except (pickle.PicklingError, TypeError):
+                logging.getLogger('aria').warn('Could not pickle parameter of type "{0}": {1}'
+                                               .format(self.type_name, value))
+                self._value = pickle.dumps(str(value))
+
+    def instantiate(self, context, container):
+        from . import models
+        return models.Parameter(name=self.name,
+                                type_name=self.type_name,
+                                _value=self._value,
+                                description=self.description)
+
+    def coerce_values(self, context, container, report_issues):
+        if self.value is not None:
+            self.value = utils.coerce_value(context, container, self.value,
+                                            report_issues)
+
+    def dump(self, context):
+        if self.type_name is not None:
+            console.puts('{0}: {1} ({2})'.format(
+                context.style.property(self.name),
+                context.style.literal(self.value),
+                context.style.type(self.type_name)))
+        else:
+            console.puts('{0}: {1}'.format(
+                context.style.property(self.name),
+                context.style.literal(self.value)))
+        if self.description:
+            console.puts(context.style.meta(self.description))
+
+
+class TypeBase(InstanceModelMixin):
+    """
+    Represents a type and its children.
+    """
+
+    __tablename__ = 'type'
+
+    variant = Column(Text, nullable=False)
+    description = Column(Text)
+    _role = Column(Text, name='role')
+
+    @declared_attr
+    def parent(cls):
+        return cls._create_relationship_to_self('parent_type_fk')
+
+    @declared_attr
+    def children(cls):
+        return cls._create_one_to_many_relationship_to_self('parent_type_fk')
+
+    # region foreign keys
+
+    __private_fields__ = ['parent_type_fk']
+
+    # Type one-to-many to Type
+    @declared_attr
+    def parent_type_fk(cls):
+        return cls._create_foreign_key('type', nullable=True)
+
+    # endregion
+
+    @property
+    def role(self):
+        def get_role(the_type):
+            if the_type is None:
+                return None
+            elif the_type._role is None:
+                return get_role(the_type.parent)
+            return the_type._role
+
+        return get_role(self)
+
+    @role.setter
+    def role(self, value):
+        self._role = value
+
+    def is_descendant(self, base_name, name):
+        base = self.get_descendant(base_name)
+        if base is not None:
+            if base.get_descendant(name) is not None:
+                return True
+        return False
+
+    def get_descendant(self, name):
+        if self.name == name:
+            return self
+        for child in self.children:
+            found = child.get_descendant(name)
+            if found is not None:
+                return found
+        return None
+
+    def iter_descendants(self):
+        for child in self.children:
+            yield child
+            for descendant in child.iter_descendants():
+                yield descendant
+
+    @property
+    def as_raw(self):
+        return collections.OrderedDict((
+            ('name', self.name),
+            ('description', self.description),
+            ('role', self.role)))
+
+    @property
+    def as_raw_all(self):
+        types = []
+        self._append_raw_children(types)
+        return types
+
+    def dump(self, context):
+        if self.name:
+            console.puts(context.style.type(self.name))
+        with context.style.indent:
+            for child in self.children:
+                child.dump(context)
+
+    def _append_raw_children(self, types):
+        for child in self.children:
+            raw_child = formatting.as_raw(child)
+            raw_child['parent'] = self.name
+            types.append(raw_child)
+            child._append_raw_children(types)
+
+
+class MetadataBase(TemplateModelMixin):
+    """
+    Custom values associated with the service.
+
+    This model is used by both service template and service instance elements.
+
+    :ivar name: Name
+    :ivar value: Value
+    """
+
+    __tablename__ = 'metadata'
+
+    value = Column(Text)
+
+    @property
+    def as_raw(self):
+        return collections.OrderedDict((
+            ('name', self.name),
+            ('value', self.value)))
+
+    def instantiate(self, context, container):
+        from . import models
+        return models.Metadata(name=self.name,
+                               value=self.value)
+
+    def dump(self, context):
+        console.puts('{0}: {1}'.format(
+            context.style.property(self.name),
+            context.style.literal(self.value)))
+
+
+class PluginSpecificationBase(InstanceModelMixin):
+    """
+    Plugin specification model representation.
+    """
+
+    __tablename__ = 'plugin_specification'
+
+    archive_name = Column(Text, nullable=False, index=True)
+    distribution = Column(Text)
+    distribution_release = Column(Text)
+    distribution_version = Column(Text)
+    package_name = Column(Text, nullable=False, index=True)
+    package_source = Column(Text)
+    package_version = Column(Text)
+    supported_platform = Column(Text)
+    supported_py_versions = Column(List)
+
+    # region foreign keys
+
+    __private_fields__ = ['service_template_fk']
+
+    @declared_attr
+    def service_template_fk(cls):
+        return cls._create_foreign_key('service_template', nullable=True)
+
+    # endregion
+
+    def find_plugin(self, plugins):
+        # TODO: this should check versions/distribution and other specification
+        for plugin in plugins:
+            if plugin.name == self.name:
+                return plugin
+        return None


Mime
View raw message