You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@ariatosca.apache.org by ra...@apache.org on 2017/04/15 14:11:33 UTC

[3/9] incubator-ariatosca git commit: ARIA-92 Automatic operation task configuration

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a7e7826e/extensions/aria_extension_tosca/simple_v1_0/modeling/__init__.py
----------------------------------------------------------------------
diff --git a/extensions/aria_extension_tosca/simple_v1_0/modeling/__init__.py b/extensions/aria_extension_tosca/simple_v1_0/modeling/__init__.py
index 267f6de..0e9177f 100644
--- a/extensions/aria_extension_tosca/simple_v1_0/modeling/__init__.py
+++ b/extensions/aria_extension_tosca/simple_v1_0/modeling/__init__.py
@@ -23,6 +23,8 @@ import re
 from types import FunctionType
 from datetime import datetime
 
+from aria.parser.validation import Issue
+from aria.utils.collections import StrictDict
 from aria.modeling.models import (Type, ServiceTemplate, NodeTemplate,
                                   RequirementTemplate, RelationshipTemplate, CapabilityTemplate,
                                   GroupTemplate, PolicyTemplate, SubstitutionTemplate,
@@ -32,6 +34,11 @@ from aria.modeling.models import (Type, ServiceTemplate, NodeTemplate,
 from ..data_types import coerce_value
 
 
+# These match the first un-escaped ">"
+# See: http://stackoverflow.com/a/11819111/849021
+IMPLEMENTATION_PREFIX_REGEX = re.compile(r'(?<!\\)(?:\\\\)*>')
+
+
 def create_service_template_model(context): # pylint: disable=too-many-locals,too-many-branches
     model = ServiceTemplate(created_at=datetime.now(),
                             main_file_name=str(context.presentation.location))
@@ -352,20 +359,35 @@ def create_interface_template_model(context, service_template, interface):
     return model if model.operation_templates else None
 
 
-def create_operation_template_model(context, service_template, operation): # pylint: disable=unused-argument
+def create_operation_template_model(context, service_template, operation):
     model = OperationTemplate(name=operation._name)
 
     if operation.description:
         model.description = operation.description.value
 
     implementation = operation.implementation
-    if (implementation is not None) and operation.implementation.primary:
-        model.plugin_specification, model.implementation = \
-            parse_implementation_string(context, service_template, operation.implementation.primary)
-
+    if implementation is not None: 
+        primary = implementation.primary
+        parse_implementation_string(context, service_template, operation, model, primary)
+        relationship_edge = operation._get_extensions(context).get('relationship_edge')
+        if relationship_edge is not None:
+            if relationship_edge == 'source':
+                model.relationship_edge = False
+            elif relationship_edge == 'target':
+                model.relationship_edge = True
+            
         dependencies = implementation.dependencies
-        if dependencies is not None:
-            model.dependencies = dependencies
+        if dependencies:
+            for dependency in dependencies:
+                key, value = split_prefix(dependency)
+                if key is not None:
+                    if model.configuration is None:
+                        model.configuration = {}
+                    set_nested(model.configuration, key.split('.'), value)
+                else:
+                    if model.dependencies is None:
+                        model.dependencies = []
+                    model.dependencies.append(dependency)
 
     inputs = operation.inputs
     if inputs:
@@ -441,12 +463,13 @@ def create_substitution_template_model(context, service_template, substitution_m
 def create_plugin_specification_model(context, policy):
     properties = policy.properties
 
-    def get(name):
+    def get(name, default=None):
         prop = properties.get(name)
-        return prop.value if prop is not None else None
+        return prop.value if prop is not None else default
 
     model = PluginSpecification(name=policy._name,
-                                version=get('version'))
+                                version=get('version'),
+                                enabled=get('enabled', True))
 
     return model
 
@@ -461,8 +484,7 @@ def create_workflow_operation_template_model(context, service_template, policy):
     properties = policy._get_property_values(context)
     for prop_name, prop in properties.iteritems():
         if prop_name == 'implementation':
-            model.plugin_specification, model.implementation = \
-                parse_implementation_string(context, service_template, prop.value)
+            parse_implementation_string(context, service_template, policy, model, prop.value)
         elif prop_name == 'dependencies':
             model.dependencies = prop.value
         else:
@@ -677,21 +699,47 @@ def create_constraint_clause_lambda(context, node_filter, constraint_clause, pro
     return None
 
 
-def parse_implementation_string(context, service_template, implementation):
-    if not implementation:
-        return None, ''
+def split_prefix(string):
+    """
+    Splits the prefix on the first unescaped ">".
+    """
 
-    index = implementation.find('>')
-    if index == -1:
-        return None, implementation
-    plugin_name = implementation[:index].strip()
-    
-    if plugin_name == 'execution':
-        plugin_specification = None
-    else:
-        plugin_specification = service_template.plugin_specifications.get(plugin_name)
-        if plugin_specification is None:
-            raise ValueError('unknown plugin: "{0}"'.format(plugin_name))
+    split = IMPLEMENTATION_PREFIX_REGEX.split(string, 2)
+    if len(split) < 2:
+        return None, string
+    return split[0].strip(), split[1].lstrip()
+
+
+def set_nested(the_dict, keys, value):
+    """
+    If the ``keys`` list has just one item, puts the value in the the dict. If there are more items,
+    puts the value in a sub-dict, creating sub-dicts as necessary for each key.
 
-    implementation = implementation[index+1:].strip()
-    return plugin_specification, implementation
+    For example, if ``the_dict`` is an empty dict, keys is ``['first', 'second', 'third']`` and
+    value is ``'value'``, then the_dict will be: ``{'first':{'second':{'third':'value'}}}``.
+
+    :param the_dict: Dict to change
+    :type the_dict: {}
+    :param keys: Keys
+    :type keys: [basestring]
+    :param value: Value
+    """
+    key = keys.pop(0)
+    if len(keys) == 0:
+        the_dict[key] = value
+    else:
+        if key not in the_dict:
+            the_dict[key] = StrictDict(key_class=basestring)
+        set_nested(the_dict[key], keys, value)
+
+
+def parse_implementation_string(context, service_template, presentation, model, implementation):
+    plugin_name, model.implementation = split_prefix(implementation)
+    if plugin_name is not None:
+        model.plugin_specification = service_template.plugin_specifications.get(plugin_name)
+        if model.plugin_specification is None:
+            context.validation.report(
+                'no policy for plugin "{0}" specified in operation implementation: {1}'
+                .format(plugin_name, implementation),
+                locator=presentation._get_child_locator('properties', 'implementation'),
+                level=Issue.BETWEEN_TYPES)

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a7e7826e/extensions/aria_extension_tosca/simple_v1_0/modeling/capabilities.py
----------------------------------------------------------------------
diff --git a/extensions/aria_extension_tosca/simple_v1_0/modeling/capabilities.py b/extensions/aria_extension_tosca/simple_v1_0/modeling/capabilities.py
index d9b9f6b..6df7177 100644
--- a/extensions/aria_extension_tosca/simple_v1_0/modeling/capabilities.py
+++ b/extensions/aria_extension_tosca/simple_v1_0/modeling/capabilities.py
@@ -78,6 +78,11 @@ def get_inherited_capability_definitions(context, presentation, for_presentation
                 #capability_definitions[capability_name] = capability_definition
             else:
                 capability_definition = our_capability_definition._clone(for_presentation)
+                if isinstance(capability_definition._raw, basestring):
+                    # Make sure we have a dict
+                    the_type = capability_definition._raw
+                    capability_definition._raw = OrderedDict()
+                    capability_definition._raw['type'] = the_type
                 capability_definitions[capability_name] = capability_definition
 
             merge_capability_definition_from_type(context, presentation, capability_definition)

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a7e7826e/tests/modeling/test_models.py
----------------------------------------------------------------------
diff --git a/tests/modeling/test_models.py b/tests/modeling/test_models.py
index 8cd00f8..bd4eba4 100644
--- a/tests/modeling/test_models.py
+++ b/tests/modeling/test_models.py
@@ -585,48 +585,49 @@ class TestNode(object):
                    node_template_storage.service.list()[0]
 
 
-class TestNodeIP(object):
+class TestNodeHostAddress(object):
 
-    ip = '1.1.1.1'
+    host_address = '1.1.1.1'
 
-    def test_ip_on_none_hosted_node(self, service_storage):
-        node_template = self._node_template(service_storage, ip='not considered')
+    def test_host_address_on_none_hosted_node(self, service_storage):
+        node_template = self._node_template(service_storage, host_address='not considered')
         node = self._node(service_storage,
                           node_template,
                           is_host=False,
-                          ip='not considered')
-        assert node.ip is None
+                          host_address='not considered')
+        assert node.host_address is None
 
-    def test_property_ip_on_host_node(self, service_storage):
-        node_template = self._node_template(service_storage, ip=self.ip)
-        node = self._node(service_storage, node_template, is_host=True, ip=None)
-        assert node.ip == self.ip
+    def test_property_host_address_on_host_node(self, service_storage):
+        node_template = self._node_template(service_storage, host_address=self.host_address)
+        node = self._node(service_storage, node_template, is_host=True, host_address=None)
+        assert node.host_address == self.host_address
 
-    def test_runtime_property_ip_on_host_node(self, service_storage):
-        node_template = self._node_template(service_storage, ip='not considered')
-        node = self._node(service_storage, node_template, is_host=True, ip=self.ip)
-        assert node.ip == self.ip
+    def test_runtime_property_host_address_on_host_node(self, service_storage):
+        node_template = self._node_template(service_storage, host_address='not considered')
+        node = self._node(service_storage, node_template, is_host=True,
+                          host_address=self.host_address)
+        assert node.host_address == self.host_address
 
-    def test_no_ip_configured_on_host_node(self, service_storage):
-        node_template = self._node_template(service_storage, ip=None)
-        node = self._node(service_storage, node_template, is_host=True, ip=None)
-        assert node.ip is None
+    def test_no_host_address_configured_on_host_node(self, service_storage):
+        node_template = self._node_template(service_storage, host_address=None)
+        node = self._node(service_storage, node_template, is_host=True, host_address=None)
+        assert node.host_address is None
 
     def test_runtime_property_on_hosted_node(self, service_storage):
-        host_node_template = self._node_template(service_storage, ip=None)
+        host_node_template = self._node_template(service_storage, host_address=None)
         host_node = self._node(service_storage,
                                host_node_template,
                                is_host=True,
-                               ip=self.ip)
-        node_template = self._node_template(service_storage, ip=None)
+                               host_address=self.host_address)
+        node_template = self._node_template(service_storage, host_address=None)
         node = self._node(service_storage,
                           node_template,
                           is_host=False,
-                          ip=None,
+                          host_address=None,
                           host_fk=host_node.id)
-        assert node.ip == self.ip
+        assert node.host_address == self.host_address
 
-    def _node_template(self, storage, ip):
+    def _node_template(self, storage, host_address):
         kwargs = dict(
             name='node_template',
             type=storage.type.list()[0],
@@ -635,23 +636,27 @@ class TestNodeIP(object):
             min_instances=1,
             service_template=storage.service_template.list()[0]
         )
-        if ip:
-            kwargs['properties'] = {'ip': Parameter.wrap('ip', ip)}
+        if host_address:
+            kwargs['properties'] = {'host_address': Parameter.wrap('host_address', host_address)}
         node = NodeTemplate(**kwargs)
         storage.node_template.put(node)
         return node
 
-    def _node(self, storage, node, is_host, ip, host_fk=None):
+    def _node(self, storage, node_template, is_host, host_address, host_fk=None):
         kwargs = dict(
             name='node',
-            node_template=node,
+            node_template=node_template,
             type=storage.type.list()[0],
             runtime_properties={},
             state='initial',
             service=storage.service.list()[0]
         )
-        if ip:
-            kwargs['runtime_properties']['ip'] = ip
+        if is_host and (host_address is None):
+            host_address = node_template.properties.get('host_address')
+            if host_address is not None:
+                host_address = host_address.value
+        if host_address:
+            kwargs['runtime_properties']['ip'] = host_address
         if is_host:
             kwargs['host_fk'] = 1
         elif host_fk:

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a7e7826e/tests/orchestrator/context/test_operation.py
----------------------------------------------------------------------
diff --git a/tests/orchestrator/context/test_operation.py b/tests/orchestrator/context/test_operation.py
index f55b83e..af8b454 100644
--- a/tests/orchestrator/context/test_operation.py
+++ b/tests/orchestrator/context/test_operation.py
@@ -230,7 +230,6 @@ def test_plugin_workdir(ctx, thread_executor, tmpdir):
 
     plugin = mock.models.create_plugin()
     ctx.model.plugin.put(plugin)
-    plugin_specification = mock.models.create_plugin_specification()
     node = ctx.model.node.get_by_name(mock.models.DEPENDENCY_NODE_NAME)
     interface = mock.models.create_interface(
         node.service,
@@ -238,7 +237,7 @@ def test_plugin_workdir(ctx, thread_executor, tmpdir):
         operation_name,
         operation_kwargs=dict(
             implementation='{0}.{1}'.format(__name__, _test_plugin_workdir.__name__),
-            plugin_specification=plugin_specification)
+            plugin=plugin)
     )
     node.interfaces[interface.name] = interface
     ctx.model.node.update(node)

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a7e7826e/tests/orchestrator/context/test_serialize.py
----------------------------------------------------------------------
diff --git a/tests/orchestrator/context/test_serialize.py b/tests/orchestrator/context/test_serialize.py
index db45e8e..8b809b3 100644
--- a/tests/orchestrator/context/test_serialize.py
+++ b/tests/orchestrator/context/test_serialize.py
@@ -45,13 +45,12 @@ def _mock_workflow(ctx, graph):
     node = ctx.model.node.get_by_name(mock.models.DEPENDENCY_NODE_NAME)
     plugin = mock.models.create_plugin()
     ctx.model.plugin.put(plugin)
-    plugin_specification = mock.models.create_plugin_specification()
     interface = mock.models.create_interface(
         node.service,
         'test',
         'op',
         operation_kwargs=dict(implementation=_operation_mapping(),
-                              plugin_specification=plugin_specification)
+                              plugin=plugin)
     )
     node.interfaces[interface.name] = interface
     task = api.task.OperationTask.for_node(node=node, interface_name='test', operation_name='op')

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a7e7826e/tests/orchestrator/execution_plugin/test_ssh.py
----------------------------------------------------------------------
diff --git a/tests/orchestrator/execution_plugin/test_ssh.py b/tests/orchestrator/execution_plugin/test_ssh.py
index dd36466..d86b6d2 100644
--- a/tests/orchestrator/execution_plugin/test_ssh.py
+++ b/tests/orchestrator/execution_plugin/test_ssh.py
@@ -292,12 +292,13 @@ class TestFabricEnvHideGroupsAndRunCommands(object):
         assert self.mock.settings_merged['timeout'] == timeout
 
     def test_implicit_host_string(self, mocker):
-        expected_ip = '1.1.1.1'
-        mocker.patch.object(self._Ctx.task.runs_on, 'ip', expected_ip)
+        expected_host_address = '1.1.1.1'
+        mocker.patch.object(self._Ctx.task.actor, 'host')
+        mocker.patch.object(self._Ctx.task.actor.host, 'host_address', expected_host_address)
         fabric_env = self.default_fabric_env.copy()
         del fabric_env['host_string']
         self._run(fabric_env=fabric_env)
-        assert self.mock.settings_merged['host_string'] == expected_ip
+        assert self.mock.settings_merged['host_string'] == expected_host_address
 
     def test_explicit_host_string(self):
         fabric_env = self.default_fabric_env.copy()
@@ -409,13 +410,15 @@ class TestFabricEnvHideGroupsAndRunCommands(object):
             raise RuntimeError
 
     class _Ctx(object):
-        class Stub(object):
+        class Task(object):
             @staticmethod
             def abort(message=None):
                 models.Task.abort(message)
-            ip = None
-        task = Stub
-        task.runs_on = Stub
+            actor = None
+        class Actor(object):
+            host = None
+        task = Task
+        task.actor = Actor
         logger = logging.getLogger()
 
     @staticmethod

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a7e7826e/tests/orchestrator/workflows/api/test_task.py
----------------------------------------------------------------------
diff --git a/tests/orchestrator/workflows/api/test_task.py b/tests/orchestrator/workflows/api/test_task.py
index 80d2351..a705199 100644
--- a/tests/orchestrator/workflows/api/test_task.py
+++ b/tests/orchestrator/workflows/api/test_task.py
@@ -18,7 +18,6 @@ import pytest
 
 from aria.orchestrator import context
 from aria.orchestrator.workflows import api
-from aria.modeling import models
 
 from tests import mock, storage
 
@@ -45,13 +44,11 @@ class TestOperationTask(object):
         plugin = mock.models.create_plugin('test_plugin', '0.1')
         ctx.model.node.update(plugin)
 
-        plugin_specification = mock.models.create_plugin_specification('test_plugin', '0.1')
-
         interface = mock.models.create_interface(
             ctx.service,
             interface_name,
             operation_name,
-            operation_kwargs=dict(plugin_specification=plugin_specification,
+            operation_kwargs=dict(plugin=plugin,
                                   implementation='op_path'))
 
         node = ctx.model.node.get_by_name(mock.models.DEPENDENT_NODE_NAME)
@@ -85,7 +82,6 @@ class TestOperationTask(object):
         assert api_task.max_attempts == max_attempts
         assert api_task.ignore_failure == ignore_failure
         assert api_task.plugin.name == 'test_plugin'
-        assert api_task.runs_on == models.Task.RUNS_ON_NODE
 
     def test_source_relationship_operation_task_creation(self, ctx):
         interface_name = 'test_interface'
@@ -94,13 +90,11 @@ class TestOperationTask(object):
         plugin = mock.models.create_plugin('test_plugin', '0.1')
         ctx.model.plugin.update(plugin)
 
-        plugin_specification = mock.models.create_plugin_specification('test_plugin', '0.1')
-
         interface = mock.models.create_interface(
             ctx.service,
             interface_name,
             operation_name,
-            operation_kwargs=dict(plugin_specification=plugin_specification,
+            operation_kwargs=dict(plugin=plugin,
                                   implementation='op_path')
         )
 
@@ -131,7 +125,6 @@ class TestOperationTask(object):
         assert api_task.retry_interval == retry_interval
         assert api_task.max_attempts == max_attempts
         assert api_task.plugin.name == 'test_plugin'
-        assert api_task.runs_on == models.Task.RUNS_ON_SOURCE
 
     def test_target_relationship_operation_task_creation(self, ctx):
         interface_name = 'test_interface'
@@ -140,13 +133,11 @@ class TestOperationTask(object):
         plugin = mock.models.create_plugin('test_plugin', '0.1')
         ctx.model.node.update(plugin)
 
-        plugin_specification = mock.models.create_plugin_specification('test_plugin', '0.1')
-
         interface = mock.models.create_interface(
             ctx.service,
             interface_name,
             operation_name,
-            operation_kwargs=dict(plugin_specification=plugin_specification,
+            operation_kwargs=dict(plugin=plugin,
                                   implementation='op_path')
         )
 
@@ -163,8 +154,7 @@ class TestOperationTask(object):
                 operation_name=operation_name,
                 inputs=inputs,
                 max_attempts=max_attempts,
-                retry_interval=retry_interval,
-                runs_on=models.Task.RUNS_ON_TARGET)
+                retry_interval=retry_interval)
 
         assert api_task.name == api.task.OperationTask.NAME_FORMAT.format(
             type='relationship',
@@ -178,7 +168,6 @@ class TestOperationTask(object):
         assert api_task.retry_interval == retry_interval
         assert api_task.max_attempts == max_attempts
         assert api_task.plugin.name == 'test_plugin'
-        assert api_task.runs_on == models.Task.RUNS_ON_TARGET
 
     def test_operation_task_default_values(self, ctx):
         interface_name = 'test_interface'
@@ -187,15 +176,13 @@ class TestOperationTask(object):
         plugin = mock.models.create_plugin('package', '0.1')
         ctx.model.node.update(plugin)
 
-        plugin_specification = mock.models.create_plugin_specification('package', '0.1')
-
         dependency_node = ctx.model.node.get_by_name(mock.models.DEPENDENCY_NODE_NAME)
 
         interface = mock.models.create_interface(
             ctx.service,
             interface_name,
             operation_name,
-            operation_kwargs=dict(plugin_specification=plugin_specification,
+            operation_kwargs=dict(plugin=plugin,
                                   implementation='op_path'))
         dependency_node.interfaces[interface_name] = interface
 
@@ -210,7 +197,6 @@ class TestOperationTask(object):
         assert task.max_attempts == ctx._task_max_attempts
         assert task.ignore_failure == ctx._task_ignore_failure
         assert task.plugin is plugin
-        assert task.runs_on == models.Task.RUNS_ON_NODE
 
 
 class TestWorkflowTask(object):

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a7e7826e/tests/orchestrator/workflows/builtin/__init__.py
----------------------------------------------------------------------
diff --git a/tests/orchestrator/workflows/builtin/__init__.py b/tests/orchestrator/workflows/builtin/__init__.py
index 8a67247..1809f82 100644
--- a/tests/orchestrator/workflows/builtin/__init__.py
+++ b/tests/orchestrator/workflows/builtin/__init__.py
@@ -31,16 +31,13 @@ def _assert_relationships(operations, expected_op_full_name, relationships=0):
         # suffix once
         operation = next(operations)
         relationship_id_1 = operation.actor.id
-        edge1 = operation.runs_on
         _assert_cfg_interface_op(operation, expected_op_name)
 
         operation = next(operations)
         relationship_id_2 = operation.actor.id
-        edge2 = operation.runs_on
         _assert_cfg_interface_op(operation, expected_op_name)
 
         assert relationship_id_1 == relationship_id_2
-        assert edge1 != edge2
 
 
 def assert_node_install_operations(operations, relationships=0):

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a7e7826e/tests/orchestrator/workflows/builtin/test_execute_operation.py
----------------------------------------------------------------------
diff --git a/tests/orchestrator/workflows/builtin/test_execute_operation.py b/tests/orchestrator/workflows/builtin/test_execute_operation.py
index 360e17d..4cddbe6 100644
--- a/tests/orchestrator/workflows/builtin/test_execute_operation.py
+++ b/tests/orchestrator/workflows/builtin/test_execute_operation.py
@@ -34,7 +34,8 @@ def test_execute_operation(ctx):
     interface = mock.models.create_interface(
         ctx.service,
         interface_name,
-        operation_name
+        operation_name,
+        operation_kwargs={'implementation': 'test'}
     )
     node.interfaces[interface.name] = interface
     ctx.model.node.update(node)

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a7e7826e/tests/orchestrator/workflows/core/test_task.py
----------------------------------------------------------------------
diff --git a/tests/orchestrator/workflows/core/test_task.py b/tests/orchestrator/workflows/core/test_task.py
index 18ca056..8dda209 100644
--- a/tests/orchestrator/workflows/core/test_task.py
+++ b/tests/orchestrator/workflows/core/test_task.py
@@ -19,7 +19,6 @@ from datetime import (
 
 import pytest
 
-from aria.modeling import models
 from aria.orchestrator.context import workflow as workflow_context
 from aria.orchestrator.workflows import (
     api,
@@ -43,7 +42,8 @@ def ctx(tmpdir):
     interface = mock.models.create_interface(
         relationship.source_node.service,
         RELATIONSHIP_INTERFACE_NAME,
-        RELATIONSHIP_OPERATION_NAME
+        RELATIONSHIP_OPERATION_NAME,
+        operation_kwargs={'implementation': 'test'}
     )
     relationship.interfaces[interface.name] = interface
     context.model.relationship.update(relationship)
@@ -52,7 +52,8 @@ def ctx(tmpdir):
     interface = mock.models.create_interface(
         node.service,
         NODE_INTERFACE_NAME,
-        NODE_OPERATION_NAME
+        NODE_OPERATION_NAME,
+        operation_kwargs={'implementation': 'test'}
     )
     node.interfaces[interface.name] = interface
     context.model.node.update(node)
@@ -72,13 +73,12 @@ class TestOperationTask(object):
             core_task = core.task.OperationTask(api_task=api_task)
         return api_task, core_task
 
-    def _create_relationship_operation_task(self, ctx, relationship, runs_on):
+    def _create_relationship_operation_task(self, ctx, relationship):
         with workflow_context.current.push(ctx):
             api_task = api.task.OperationTask.for_relationship(
                 relationship=relationship,
                 interface_name=RELATIONSHIP_INTERFACE_NAME,
-                operation_name=RELATIONSHIP_OPERATION_NAME,
-                runs_on=runs_on)
+                operation_name=RELATIONSHIP_OPERATION_NAME)
             core_task = core.task.OperationTask(api_task=api_task)
         return api_task, core_task
 
@@ -88,12 +88,11 @@ class TestOperationTask(object):
         ctx.model.plugin.put(storage_plugin)
         ctx.model.plugin.put(storage_plugin_other)
         node = ctx.model.node.get_by_name(mock.models.DEPENDENCY_NODE_NAME)
-        storage_plugin_specification = mock.models.create_plugin_specification('p1', '0.1')
         interface = mock.models.create_interface(
             node.service,
             NODE_INTERFACE_NAME,
             NODE_OPERATION_NAME,
-            operation_kwargs=dict(plugin_specification=storage_plugin_specification)
+            operation_kwargs=dict(plugin=storage_plugin, implementation='test')
         )
         node.interfaces[interface.name] = interface
         ctx.model.node.update(node)
@@ -101,7 +100,7 @@ class TestOperationTask(object):
         storage_task = ctx.model.task.get_by_name(core_task.name)
         assert storage_task.plugin is storage_plugin
         assert storage_task.execution_name == ctx.execution.name
-        assert storage_task.runs_on == core_task.context.node
+        assert storage_task.actor == core_task.context.node
         assert core_task.model_task == storage_task
         assert core_task.name == api_task.name
         assert core_task.implementation == api_task.implementation
@@ -109,18 +108,12 @@ class TestOperationTask(object):
         assert core_task.inputs == api_task.inputs == storage_task.inputs
         assert core_task.plugin == storage_plugin
 
-    def test_source_relationship_operation_task_creation(self, ctx):
+    def test_relationship_operation_task_creation(self, ctx):
         relationship = ctx.model.relationship.list()[0]
         ctx.model.relationship.update(relationship)
         _, core_task = self._create_relationship_operation_task(
-            ctx, relationship, models.Task.RUNS_ON_SOURCE)
-        assert core_task.model_task.runs_on == relationship.source_node
-
-    def test_target_relationship_operation_task_creation(self, ctx):
-        relationship = ctx.model.relationship.list()[0]
-        _, core_task = self._create_relationship_operation_task(
-            ctx, relationship, models.Task.RUNS_ON_TARGET)
-        assert core_task.model_task.runs_on == relationship.target_node
+            ctx, relationship)
+        assert core_task.model_task.actor == relationship
 
     def test_operation_task_edit_locked_attribute(self, ctx):
         node = ctx.model.node.get_by_name(mock.models.DEPENDENCY_NODE_NAME)

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a7e7826e/tests/orchestrator/workflows/core/test_task_graph_into_exececution_graph.py
----------------------------------------------------------------------
diff --git a/tests/orchestrator/workflows/core/test_task_graph_into_exececution_graph.py b/tests/orchestrator/workflows/core/test_task_graph_into_exececution_graph.py
index 0a95d43..514bce9 100644
--- a/tests/orchestrator/workflows/core/test_task_graph_into_exececution_graph.py
+++ b/tests/orchestrator/workflows/core/test_task_graph_into_exececution_graph.py
@@ -30,7 +30,8 @@ def test_task_graph_into_execution_graph(tmpdir):
     interface = mock.models.create_interface(
         node.service,
         interface_name,
-        operation_name
+        operation_name,
+        operation_kwargs={'implementation': 'test'}
     )
     node.interfaces[interface.name] = interface
     task_context.model.node.update(node)

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a7e7826e/tests/orchestrator/workflows/executor/test_executor.py
----------------------------------------------------------------------
diff --git a/tests/orchestrator/workflows/executor/test_executor.py b/tests/orchestrator/workflows/executor/test_executor.py
index d84d1ec..a7619de 100644
--- a/tests/orchestrator/workflows/executor/test_executor.py
+++ b/tests/orchestrator/workflows/executor/test_executor.py
@@ -119,6 +119,7 @@ class MockTask(object):
         self.ignore_failure = False
         self.interface_name = 'interface_name'
         self.operation_name = 'operation_name'
+        self.model_task = None
 
         for state in models.Task.STATES:
             setattr(self, state.upper(), state)

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a7e7826e/tests/orchestrator/workflows/executor/test_process_executor.py
----------------------------------------------------------------------
diff --git a/tests/orchestrator/workflows/executor/test_process_executor.py b/tests/orchestrator/workflows/executor/test_process_executor.py
index 436e7b6..502c9fd 100644
--- a/tests/orchestrator/workflows/executor/test_process_executor.py
+++ b/tests/orchestrator/workflows/executor/test_process_executor.py
@@ -142,6 +142,7 @@ class MockTask(object):
         self.ignore_failure = False
         self.interface_name = 'interface_name'
         self.operation_name = 'operation_name'
+        self.model_task = None
 
         for state in aria_models.Task.STATES:
             setattr(self, state.upper(), state)

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a7e7826e/tests/resources/service-templates/tosca-simple-1.0/node-cellar/node-cellar.yaml
----------------------------------------------------------------------
diff --git a/tests/resources/service-templates/tosca-simple-1.0/node-cellar/node-cellar.yaml b/tests/resources/service-templates/tosca-simple-1.0/node-cellar/node-cellar.yaml
index 3b4f371..349a166 100644
--- a/tests/resources/service-templates/tosca-simple-1.0/node-cellar/node-cellar.yaml
+++ b/tests/resources/service-templates/tosca-simple-1.0/node-cellar/node-cellar.yaml
@@ -154,14 +154,33 @@ topology_template:
                 - scalable:
                     properties:
                       - max_instances: { greater_or_equal: 8 }
-    
+            relationship:
+              interfaces:
+                Configure:
+                  target_changed:
+                    implementation:
+                      primary: changed.sh
+                      dependencies:
+                        #- { concat: [ process.args.1 >, mongodb ] }
+                        - process.args.1 > mongodb
+                        - process.args.2 > host
+                        - ssh.user > admin
+                        - ssh.password > 1234
+                        - ssh.use_sudo > true
+
+    nginx:
+      type: nginx.Nginx
+      requirements:
+        - host: loadbalancer_host
+        - feature: loadbalancer
+
+    # Features
+
     loadbalancer:
       type: nginx.LoadBalancer
       properties:
-        algorithm: round-robin      
-      requirements:
-        - host: loadbalancer_host
-    
+        algorithm: round-robin   
+
     # Hosts
 
     loadbalancer_host:
@@ -177,7 +196,11 @@ topology_template:
         Standard:
           inputs:
             openstack_credential: { get_input: openstack_credential }
-          configure: juju > charm.loadbalancer
+          configure:
+            implementation:
+              primary: juju > run_charm
+              dependencies:
+                - charm > loadbalancer
 
     application_host:
       copy: loadbalancer_host
@@ -253,6 +276,7 @@ topology_template:
       type: aria.Plugin
       properties:
         version: 1.0
+        enabled: false
 
     maintenance_on:
       type: MaintenanceWorkflow

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a7e7826e/tests/resources/service-templates/tosca-simple-1.0/node-cellar/types/nginx.yaml
----------------------------------------------------------------------
diff --git a/tests/resources/service-templates/tosca-simple-1.0/node-cellar/types/nginx.yaml b/tests/resources/service-templates/tosca-simple-1.0/node-cellar/types/nginx.yaml
index eab130f..3621360 100644
--- a/tests/resources/service-templates/tosca-simple-1.0/node-cellar/types/nginx.yaml
+++ b/tests/resources/service-templates/tosca-simple-1.0/node-cellar/types/nginx.yaml
@@ -15,12 +15,15 @@
 
 node_types:
 
+  nginx.Nginx:
+    description: >-
+      Nginx instance.
+    derived_from: tosca.nodes.SoftwareComponent
+    requirements:
+      - feature:
+          capability: tosca.capabilities.Node
+
   nginx.LoadBalancer:
     description: >-
-      Nginx as a loadbalancer.
+      Nginx loadbalancer feature.
     derived_from: tosca.nodes.LoadBalancer
-    requirements:
-      - host:
-          capability: tosca.capabilities.Container
-          node: tosca.nodes.Compute
-          relationship: tosca.relationships.HostedOn