ariatosca-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From emblempar...@apache.org
Subject [2/4] incubator-ariatosca git commit: Service instance models
Date Fri, 24 Feb 2017 23:13:13 GMT
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/f62cdd4b/aria/modeling/service_template_models.py
----------------------------------------------------------------------
diff --git a/aria/modeling/service_template_models.py b/aria/modeling/service_template_models.py
index 9055c66..d7398ec 100644
--- a/aria/modeling/service_template_models.py
+++ b/aria/modeling/service_template_models.py
@@ -26,18 +26,32 @@ from sqlalchemy import (
 from sqlalchemy.ext.associationproxy import association_proxy
 from sqlalchemy.ext.declarative import declared_attr
 
+from ..storage import exceptions
 from ..parser import validation
+from ..parser.modeling import utils as parser_utils
 from ..utils import collections, formatting, console
+from .service_models import InstanceModelMixin
 from . import (
     utils,
-    structure,
-    type as aria_type
+    type as modeling_type
 )
 
 # pylint: disable=no-self-argument, no-member, abstract-method
 
 
-class ServiceTemplateBase(structure.TemplateModelMixin):
+
+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 ServiceTemplateBase(TemplateModelMixin):
     """
     A service template is a normalized blueprint from which :class:`ServiceInstance` instances can
     be created.
@@ -64,57 +78,64 @@ class ServiceTemplateBase(structure.TemplateModelMixin):
 
     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 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 substitution_template_fk(cls):
-        return cls.foreign_key('substitution_template', nullable=True)
+    def node_templates(cls):
+        return cls.one_to_many_relationship('node_template')
 
-    # endregion
+    @declared_attr
+    def group_templates(cls):
+        return cls.one_to_many_relationship('group_template')
 
-    # region one-to-one relationships
+    @declared_attr
+    def policy_templates(cls):
+        return cls.one_to_many_relationship('policy_template')
 
     @declared_attr
     def substitution_template(cls):
         return cls.one_to_one_relationship('substitution_template')
 
-    # endregion
+    @declared_attr
+    def inputs(cls):
+        return cls.many_to_many_relationship('parameter', table_prefix='inputs',
+                                             key_column_name='name')
 
-    # region one-to-many relationships
+    @declared_attr
+    def outputs(cls):
+        return cls.many_to_many_relationship('parameter', table_prefix='outputs',
+                                             key_column_name='name')
 
     @declared_attr
     def operation_templates(cls):
         return cls.one_to_many_relationship('operation_template', key_column_name='name')
 
-    # endregion
+    # region orchestrator required columns
 
-    # region many-to-many relationships
+    created_at = Column(DateTime, nullable=False, index=True)
+    main_file_name = Column(Text)
+    plan = Column(modeling_type.Dict, nullable=False)
+    updated_at = Column(DateTime)
 
-    @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')
+    # endregion
 
-    @declared_attr
-    def inputs(cls):
-        return cls.many_to_many_relationship('parameter', table_prefix='inputs',
-                                             key_column_name='name')
+    # region foreign keys
 
     @declared_attr
-    def outputs(cls):
-        return cls.many_to_many_relationship('parameter', table_prefix='outputs',
-                                             key_column_name='name')
+    def substitution_template_fk(cls):
+        return cls.foreign_key('substitution_template', nullable=True)
 
     # endregion
+    
+    def get_node_template(self, node_template_name):
+        if self.node_templates:
+            for node_template in self.node_templates:
+                if node_template.name == node_template_name:
+                    return node_template
+        return None
 
     @property
     def as_raw(self):
@@ -131,36 +152,34 @@ class ServiceTemplateBase(structure.TemplateModelMixin):
 
     def instantiate(self, context, container):
         from . import model
-        service_instance = model.ServiceInstance()
-        context.modeling.instance = service_instance
+        service = model.Service(description=deepcopy_with_locators(self.description))
 
-        service_instance.description = deepcopy_with_locators(self.description)
+        context.modeling.instance = service
 
-        utils.instantiate_dict(context, self, service_instance.meta_data, self.meta_data)
+        utils.instantiate_dict(context, self, service.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
+                service.nodes.append(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)
+        utils.instantiate_list(context, self, service.groups, self.group_templates)
+        utils.instantiate_list(context, self, service.policies, self.policy_templates)
+        utils.instantiate_dict(context, self, service.operations, self.operation_templates)
 
         if self.substitution_template is not None:
-            service_instance.substitution = self.substitution_template.instantiate(context,
-                                                                                   container)
+            service.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)
+        utils.instantiate_dict(context, self, service.inputs, self.inputs)
+        utils.instantiate_dict(context, self, service.outputs, self.outputs)
 
         for name, the_input in context.modeling.inputs.iteritems():
-            if name not in service_instance.inputs:
+            if name not in service.inputs:
                 context.validation.report('input "%s" is not supported' % name)
             else:
-                service_instance.inputs[name].value = the_input
+                service.inputs[name].value = the_input
 
-        return service_instance
+        return service
 
     def validate(self, context):
         utils.validate_dict_values(context, self.meta_data)
@@ -188,11 +207,12 @@ class ServiceTemplateBase(structure.TemplateModelMixin):
         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():
+
+        for node_template in self.node_templates:
             node_template.dump(context)
-        for group_template in self.group_templates.all():
+        for group_template in self.group_templates:
             group_template.dump(context)
-        for policy_template in self.policy_templates.all():
+        for policy_template in self.policy_templates:
             policy_template.dump(context)
         if self.substitution_template is not None:
             self.substitution_template.dump(context)
@@ -201,255 +221,199 @@ class ServiceTemplateBase(structure.TemplateModelMixin):
         utils.dump_dict_values(context, self.operation_templates, 'Operation templates')
 
 
-class InterfaceTemplateBase(structure.TemplateModelMixin):
+class NodeTemplateBase(TemplateModelMixin):
     """
-    A typed set of :class:`OperationTemplate`.
+    A template for creating zero or more :class:`Node` instances.
 
     Properties:
 
-    * :code:`name`: Name
+    * :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:`inputs`: Dict of :class:`Parameter`
-    * :code:`operation_templates`: Dict of :class:`OperationTemplate`
+    * :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__ = 'interface_template'
+    __tablename__ = 'node_template'
 
-    __private_fields__ = ['node_template_fk',
-                          'group_template_fk',
-                          'relationship_template_fk']
+    __private_fields__ = ['service_template_fk',
+                          'host_fk']
 
-    # region foreign keys
+    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(modeling_type.StrictList(FunctionType))
 
     @declared_attr
-    def node_template_fk(cls):
-        return cls.foreign_key('node_template', nullable=True)
+    def properties(cls):
+        return cls.many_to_many_relationship('parameter', table_prefix='properties',
+                                             key_column_name='name')
 
     @declared_attr
-    def group_template_fk(cls):
-        return cls.foreign_key('group_template', nullable=True)
+    def interface_templates(cls):
+        return cls.one_to_many_relationship('interface_template', key_column_name='name')
 
     @declared_attr
-    def relationship_template_fk(cls):
-        return cls.foreign_key('relationship_template', nullable=True)
+    def artifact_templates(cls):
+        return cls.one_to_many_relationship('artifact_template', key_column_name='name')
 
-    # endregion
+    @declared_attr
+    def capability_templates(cls):
+        return cls.one_to_many_relationship('capability_template', key_column_name='name')
 
-    description = Column(Text)
-    type_name = Column(Text)
+    @declared_attr
+    def requirement_templates(cls):
+        return cls.one_to_many_relationship('requirement_template')
+
+    # region orchestrator required columns
 
-    # region one-to-many relationships
+    plugins = Column(modeling_type.List)
+    type_hierarchy = Column(modeling_type.List)
 
     @declared_attr
-    def operation_templates(cls):
-        return cls.one_to_many_relationship('operation_template', key_column_name='name')
+    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-many relationships
+    # region foreign_keys
 
     @declared_attr
-    def inputs(cls):
-        return cls.many_to_many_relationship('parameter', table_prefix='inputs',
-                                             key_column_name='name')
+    def service_template_fk(cls):
+        return cls.foreign_key('service_template')
 
     @declared_attr
-    def properties(cls):
-        return cls.many_to_many_relationship('parameter', table_prefix='properties',
-                                             key_column_name='name')
+    def host_fk(cls):
+        return cls.foreign_key('node_template', nullable=True)
 
     # 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),
-            ('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))))
+            ('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, container):
+    def instantiate(self, context, *args, **kwargs):
         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
+        node = model.Node(template_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 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)
+        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.inputs)
-        utils.validate_dict_values(context, self.operation_templates)
+        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, container, self.inputs, report_issues)
-        utils.coerce_dict_values(context, container, self.operation_templates, 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(context.style.node(self.name))
+        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('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')
+            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 OperationTemplateBase(structure.TemplateModelMixin):
+class GroupTemplateBase(TemplateModelMixin):
     """
-    An operation in a :class:`InterfaceTemplate`.
+    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
+    * :code:`name`: Name (will be used as a prefix for group IDs)
     * :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`
+    * :code:`type_name`: Must be represented in the :class:`ModelingContext`
+    * :code:`properties`: Dict of :class:`Parameter`
+    * :code:`interface_templates`: Dict of :class:`InterfaceTemplate`
+    * :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__ = 'operation_template'
-
-    __private_fields__ = ['service_template_fk',
-                          'interface_template_fk']
+    __tablename__ = 'group_template'
 
-    # region foreign keys
+    __private_fields__ = ['service_template_fk']
 
+    description = Column(Text)
+    type_name = Column(Text)
+    
     @declared_attr
-    def service_template_fk(cls):
-        return cls.foreign_key('service_template', nullable=True)
+    def properties(cls):
+        return cls.many_to_many_relationship('parameter', table_prefix='properties',
+                                             key_column_name='name')
 
     @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)
+    def interface_templates(cls):
+        return cls.one_to_many_relationship('interface_template', key_column_name='name')
 
-    # endregion
+    member_node_template_names = Column(modeling_type.StrictList(basestring))
+    member_group_template_names = Column(modeling_type.StrictList(basestring))
 
-    # region many-to-many relationships
+    # region foreign keys
 
     @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')
+    def service_template_fk(cls):
+        return cls.foreign_key('service_template')
 
     # endregion
 
@@ -459,51 +423,57 @@ class ArtifactTemplateBase(structure.TemplateModelMixin):
             ('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()))))
+            ('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, container):
+    def instantiate(self, context, *args, **kwargs):
         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
+        group = model.Group(name=self.name,
+                            type_name=self.type_name)
+        utils.instantiate_dict(context, self, group.properties, self.properties)
+        utils.instantiate_dict(context, self, group.interfaces, self.interface_templates)
+        if self.member_node_template_names:
+            group.member_node_ids = []
+            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)
+        if self.member_group_template_names:
+            group.member_group_ids = []
+            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.artifact_types.get_descendant(self.type_name) is None:
-            context.validation.report('artifact "%s" has an unknown type: %s'
+        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, container, self.properties, 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(context.style.node(self.name))
+        console.puts('Group template: %s' % 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))
+            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 PolicyTemplateBase(structure.TemplateModelMixin):
+class PolicyTemplateBase(TemplateModelMixin):
     """
     Policies can be applied to zero or more :class:`NodeTemplate` or :class:`GroupTemplate`
     instances.
@@ -521,33 +491,22 @@ class PolicyTemplateBase(structure.TemplateModelMixin):
 
     __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')
+    def properties(cls):
+        return cls.many_to_many_relationship('parameter', table_prefix='properties',
+                                             key_column_name='name')
 
-    # endregion
+    target_node_template_names = Column(modeling_type.StrictList(basestring))
+    target_group_template_names = Column(modeling_type.StrictList(basestring))
 
-    # region many-to-many relationships
+    # region foreign keys
 
     @declared_attr
-    def properties(cls):
-        return cls.many_to_many_relationship('parameter', table_prefix='properties',
-                                             key_column_name='name')
+    def service_template_fk(cls):
+        return cls.foreign_key('service_template')
 
     # endregion
 
@@ -563,14 +522,19 @@ class PolicyTemplateBase(structure.TemplateModelMixin):
 
     def instantiate(self, context, *args, **kwargs):
         from . import model
-        policy = model.Policy(self.name, self.type_name)
+        policy = model.Policy(name=self.name,
+                              type_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))
+        if self.target_node_template_names:
+            policy.target_node_ids = []
+            for node_template_name in self.target_node_template_names:
+                policy.target_node_ids += \
+                    context.modeling.instance.get_node_ids(node_template_name)
+        if self.target_group_template_names:
+            policy.target_group_ids = []
+            for group_template_name in self.target_group_template_names:
+                policy.target_group_ids += \
+                    context.modeling.instance.get_group_ids(group_template_name)
         return policy
 
     def validate(self, context):
@@ -599,7 +563,57 @@ class PolicyTemplateBase(structure.TemplateModelMixin):
                     (str(context.style.node(v)) for v in self.target_group_template_names)))
 
 
-class MappingTemplateBase(structure.TemplateModelMixin):
+class SubstitutionTemplateBase(TemplateModelMixin):
+    """
+    Used to substitute a single node for the entire deployment.
+
+    Properties:
+
+    * :code:`node_type_name`: Must be represented in the :class:`ModelingContext`
+    * :code:`mappings`: Dict of :class:` SubstitutionTemplateMapping`
+    """
+
+    __tablename__ = 'substitution_template'
+
+    node_type_name = Column(Text)
+
+    @declared_attr
+    def mappings(cls):
+        return cls.one_to_many_relationship('substitution_template_mapping', key_column_name='name')
+
+    @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(node_type_name=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 SubstitutionTemplateMappingBase(TemplateModelMixin):
     """
     Used by :class:`SubstitutionTemplate` to map a capability or a requirement to a node.
 
@@ -610,7 +624,7 @@ class MappingTemplateBase(structure.TemplateModelMixin):
     * :code:`name`: Name of capability or requirement at the node template
     """
 
-    __tablename__ = 'mapping_template'
+    __tablename__ = 'substitution_template_mapping'
 
     __private_fields__ = ['substitution_template_fk']
 
@@ -637,18 +651,17 @@ class MappingTemplateBase(structure.TemplateModelMixin):
         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 '
+                'mapping "%s" refers 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)
+        return model.SubstitutionMapping(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):
+        if self.node_template_name not in (v.name for v in context.modeling.model.node_templates):
             context.validation.report('mapping "%s" refers to an unknown node template: %s'
                                       % (
                                           self.mapped_name,
@@ -661,284 +674,305 @@ class MappingTemplateBase(structure.TemplateModelMixin):
                                       context.style.node(self.name)))
 
 
-class SubstitutionTemplateBase(structure.TemplateModelMixin):
+class RequirementTemplateBase(TemplateModelMixin):
     """
-    Used to substitute a single node for the entire deployment.
+    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:`node_type_name`: Must be represented in the :class:`ModelingContext`
-    * :code:`capability_templates`: Dict of :class:`MappingTemplate`
-    * :code:`requirement_templates`: Dict of :class:`MappingTemplate`
+    * :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__ = 'substitution_template'
+    __tablename__ = 'requirement_template'
 
-    node_type_name = Column(Text)
+    __private_fields__ = ['node_template_fk']
 
-    # region one-to-many relationships
+    target_node_type_name = Column(Text)
+    target_node_template_name = Column(Text)
+    target_node_template_constraints = Column(modeling_type.StrictList(FunctionType))
+    target_capability_type_name = Column(Text)
+    target_capability_name = Column(Text)
+    relationship_template = Column(Text) # TODO
+
+    # region foreign keys
 
     @declared_attr
-    def mappings(cls):
-        return cls.one_to_many_relationship('mapping_template', key_column_name='name')
+    def node_template_fk(cls):
+        return cls.foreign_key('node_template', nullable=True)
 
     # 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
+        raise NotImplementedError
 
-    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)
+    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.get_node_template(self.target_node_template_name)
 
-        utils.validate_dict_values(context, self.mappings)
+            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
 
-    def coerce_values(self, context, container, report_issues):
-        utils.coerce_dict_values(context, self, self.mappings, report_issues)
+            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
 
-    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)
+            return target_node_template, target_node_capability
 
-    # endregion
+        # 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:
+                if not context.modeling.node_types.is_descendant(self.target_node_type_name,
+                                                                 target_node_template.type_name):
+                    continue
 
-    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))
+                if not source_node_template.is_target_node_valid(target_node_template):
+                    continue
 
-    # region orchestrator required columns
+                target_node_capability = self.find_target_capability(context,
+                                                                     source_node_template,
+                                                                     target_node_template)
+                if target_node_capability is None:
+                    continue
 
-    plugins = Column(aria_type.List)
-    type_hierarchy = Column(aria_type.List)
+                return target_node_template, target_node_capability
 
-    @declared_attr
-    def host(cls):
-        return cls.relationship_to_self('host_fk')
+        return None, None
 
-    @declared_attr
-    def service_template_name(cls):
-        return association_proxy('service_template', cls.name_column_name())
+    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
 
-    # endregion
+    @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))))
 
-    # region many-to-one relationships
+    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)
 
-    @declared_attr
-    def service_template(cls):
-        return cls.many_to_one_relationship('service_template')
+    def coerce_values(self, context, container, report_issues):
+        if self.relationship_template is not None:
+            self.relationship_template.coerce_values(context, container, report_issues)
 
-    # endregion
+    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)
 
-    # region one-to-many relationships
 
-    @declared_attr
-    def interface_templates(cls):
-        return cls.one_to_many_relationship('interface_template', key_column_name='name')
+class RelationshipTemplateBase(TemplateModelMixin):
+    """
+    Optional addition to a :class:`Requirement` in :class:`NodeTemplate` that can be applied when
+    the requirement is matched with a capability.
 
-    @declared_attr
-    def artifact_templates(cls):
-        return cls.one_to_many_relationship('artifact_template', key_column_name='name')
+    Properties:
 
-    @declared_attr
-    def capability_templates(cls):
-        return cls.one_to_many_relationship('capability_template', key_column_name='name')
+    * :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:`interface_templates`: Dict of :class:`InterfaceTemplate`
+    """
 
-    # endregion
+    __tablename__ = 'relationship_template'
 
-    # region many-to-many relationships
+    type_name = Column(Text)
+    description = Column(Text)
 
     @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
+    @declared_attr
+    def interface_templates(cls):
+        return cls.one_to_many_relationship('interface_template', key_column_name='name')
 
     @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),
+            ('template_name', self.template_name),
+            ('description', self.description),
             ('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))))
+            ('interface_templates', formatting.as_raw_list(self.interface_templates))))
 
-    def instantiate(self, context, *args, **kwargs):
+    def instantiate(self, context, container):
         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
+        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.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)
+        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)
-        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.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:
-            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')
+            utils.dump_parameters(context, self.properties)
+            utils.dump_interfaces(context, self.interface_templates, 'Interface templates')
 
 
-class GroupTemplateBase(structure.TemplateModelMixin):
+class CapabilityTemplateBase(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.
+    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 (will be used as a prefix for group IDs)
+    * :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`
-    * :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')
+    __tablename__ = 'capability_template'
 
-    # endregion
+    __private_fields__ = ['node_template_fk']
 
     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
+    min_occurrences = Column(Integer, default=None)  # optional
+    max_occurrences = Column(Integer, default=None)  # optional
+    valid_source_node_type_names = Column(Text)
 
     @declared_attr
-    def service_template(cls):
-        return cls.many_to_one_relationship('service_template')
-
-    # endregion
+    def properties(cls):
+        return cls.many_to_many_relationship('parameter', table_prefix='properties',
+                                             key_column_name='name')
 
-    # region one-to-many relationships
+    # region foreign keys
 
     @declared_attr
-    def interface_templates(cls):
-        return cls.one_to_many_relationship('interface_template', key_column_name='name')
+    def node_template_fk(cls):
+        return cls.foreign_key('node_template', nullable=True)
 
     # endregion
 
-    # region many-to-many relationships
+    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
 
-    @declared_attr
-    def properties(cls):
-        return cls.many_to_many_relationship('parameter', table_prefix='properties',
-                                             key_column_name='name')
+        # 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
 
-    # endregion
+        # 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):
@@ -946,75 +980,79 @@ class GroupTemplateBase(structure.TemplateModelMixin):
             ('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)))
+            ('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, *args, **kwargs):
+    def instantiate(self, context, container):
         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
+        capability = model.Capability(name=self.name,
+                                      type_name=self.type_name,
+                                      min_occurrences=self.min_occurrences,
+                                      max_occurrences=self.max_occurrences,
+                                      occurrences=0)
+        utils.instantiate_dict(context, container, capability.properties, self.properties)
+        return capability
 
     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)),
+        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)
-        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))
+        console.puts(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))
+            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)
-            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):
+class InterfaceTemplateBase(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.
+    A typed set of :class:`OperationTemplate`.
 
     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`
+    * :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__ = 'requirement_template'
+    __tablename__ = 'interface_template'
 
-    __private_fields__ = ['node_template_fk']
+    __private_fields__ = ['node_template_fk',
+                          'group_template_fk',
+                          'relationship_template_fk']
+
+    description = Column(Text)
+    type_name = Column(Text)
+
+    @declared_attr
+    def inputs(cls):
+        return cls.many_to_many_relationship('parameter', table_prefix='inputs',
+                                             key_column_name='name')
+    @declared_attr
+    def operation_templates(cls):
+        return cls.one_to_many_relationship('operation_template', key_column_name='name')
 
     # region foreign keys
 
@@ -1022,355 +1060,352 @@ class RequirementTemplateBase(structure.TemplateModelMixin):
     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 group_template_fk(cls):
+        return cls.foreign_key('group_template', nullable=True)
 
     @declared_attr
-    def node_template(cls):
-        return cls.many_to_one_relationship('node_template')
+    def relationship_template_fk(cls):
+        return cls.foreign_key('relationship_template', nullable=True)
 
     # 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))))
+            ('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,
+                                    description=deepcopy_with_locators(self.description),
+                                    type_name=self.type_name)
+        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):
-        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)
+        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):
-        if self.relationship_template is not None:
-            self.relationship_template.coerce_values(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):
-        if self.name:
-            console.puts(context.style.node(self.name))
-        else:
-            console.puts('Requirement:')
+        console.puts(context.style.node(self.name))
+        if self.description:
+            console.puts(context.style.meta(self.description))
         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)
+            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 CapabilityTemplateBase(structure.TemplateModelMixin):
+class OperationTemplateBase(TemplateModelMixin):
     """
-    A capability of a :class:`NodeTemplate`. Nodes expose zero or more capabilities that can be
-    matched with :class:`Requirement` instances of other nodes.
+    An operation in a :class:`InterfaceTemplate`.
 
     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`
+    * :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__ = 'capability_template'
+    __tablename__ = 'operation_template'
 
-    __private_fields__ = ['node_template_fk']
+    __private_fields__ = ['service_template_fk',
+                          'interface_template_fk']
 
-    # region foreign keys
+    description = Column(Text)
+    implementation = Column(Text)
+    dependencies = Column(modeling_type.StrictList(item_cls=basestring))
+    executor = Column(Text)
+    max_retries = Column(Integer)
+    retry_interval = Column(Integer)
 
     @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)
+    def inputs(cls):
+        return cls.many_to_many_relationship('parameter', table_prefix='inputs',
+                                             key_column_name='name')
 
-    # region many-to-one relationships
+    # region orchestrator required columns
 
-    #@declared_attr
-    #def node_template(cls):
-    #    return cls.many_to_one_relationship('node_template')
+    plugin = Column(Text)
+    operation = Column(Boolean)
 
     # endregion
 
-    # region many-to-many relationships
+    # region foreign keys
 
     @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
+    def service_template_fk(cls):
+        return cls.foreign_key('service_template', nullable=True)
 
-        # 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
+    @declared_attr
+    def interface_template_fk(cls):
+        return cls.foreign_key('interface_template', nullable=True)
 
-        return True
+    # endregion
 
     @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))))
+            ('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
-        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
+        operation = model.Operation(description=deepcopy_with_locators(self.description),
+                                    implementation=self.implementation,
+                                    dependencies=self.dependencies,
+                                    executor=self.executor,
+                                    max_retries=self.max_retries,
+                                    retry_interval=self.retry_interval)
+        utils.instantiate_dict(context, container, operation.inputs, self.inputs)
+        return operation
 
     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)
+        utils.validate_dict_values(context, self.inputs)
 
     def coerce_values(self, context, container, report_issues):
-        utils.coerce_dict_values(context, self, self.properties, 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:
-            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)
+            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 RelationshipTemplateBase(structure.TemplateModelMixin):
+class ArtifactTemplateBase(TemplateModelMixin):
     """
-    Optional addition to a :class:`Requirement` in :class:`NodeTemplate` that can be applied when
-    the requirement is matched with a capability.
+    A file associated with a :class:`NodeTemplate`.
 
     Properties:
 
-    * :code:`type_name`: Must be represented in the :class:`ModelingContext`
-    * :code:`template_name`: Must be represented in the :class:`ServiceTemplate`
+    * :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`
-    * :code:`source_interface_templates`: Dict of :class:`InterfaceTemplate`
-    * :code:`target_interface_templates`: Dict of :class:`InterfaceTemplate`
     """
 
-    __tablename__ = 'relationship_template'
+    __tablename__ = 'artifact_template'
+
+    __private_fields__ = ['node_template_fk']
 
     description = Column(Text)
     type_name = Column(Text)
-
-    # region many-to-many relationships
+    source_path = Column(Text)
+    target_path = Column(Text)
+    repository_url = Column(Text)
+    repository_credential = Column(modeling_type.StrictDict(basestring, basestring))
 
     @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
+    # region foreign keys
 
     @declared_attr
-    def interface_templates(cls):
-        return cls.one_to_many_relationship('interface_template', key_column_name='name')
+    def node_template_fk(cls):
+        return cls.foreign_key('node_template')
 
     # endregion
 
     @property
     def as_raw(self):
         return collections.OrderedDict((
-            ('type_name', self.type_name),
-            ('template_name', self.template_name),
+            ('name', self.name),
             ('description', self.description),
-            ('properties', formatting.as_raw_dict(self.properties)),
-            ('interface_templates', formatting.as_raw_list(self.interface_templates))))
+            ('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
-        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
+        artifact = model.Artifact(name=self.name,
+                                  type_name=self.type_name,
+                                  source_path=self.source_path,
+                                  description=deepcopy_with_locators(self.description),
+                                  target_path=self.target_path,
+                                  repository_url=self.repository_url,
+                                  repository_credential=self.repository_credential)
+        utils.instantiate_dict(context, container, artifact.properties, self.properties)
+        return artifact
 
     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_rep

<TRUNCATED>


Mime
View raw message