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

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

ARIA-92 Automatic operation task configuration


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

Branch: refs/heads/ARIA-92-plugin-in-implementation-string
Commit: 73c1d0297812a421eee2176fcff5e5ae73980ccb
Parents: b3cf69a
Author: Tal Liron <ta...@gmail.com>
Authored: Fri Mar 24 16:33:11 2017 -0500
Committer: Tal Liron <ta...@gmail.com>
Committed: Tue Apr 4 17:57:56 2017 -0500

----------------------------------------------------------------------
 aria/cli/dry.py                                 |   4 +-
 aria/modeling/orchestration.py                  |  80 +++++---
 aria/modeling/service_instance.py               | 173 +++++++++++------
 aria/modeling/service_template.py               |  28 ++-
 aria/orchestrator/execution_plugin/__init__.py  |   1 +
 .../execution_plugin/instantiation.py           | 188 +++++++++++++++++++
 .../execution_plugin/ssh/operations.py          |   4 +-
 aria/orchestrator/workflows/api/task.py         | 111 +++++------
 aria/orchestrator/workflows/builtin/utils.py    |   5 +-
 .../orchestrator/workflows/builtin/workflows.py |  10 -
 aria/orchestrator/workflows/core/task.py        |   3 +-
 aria/orchestrator/workflows/exceptions.py       |  10 +-
 aria/parser/consumption/modeling.py             |  22 ++-
 .../custom_types/elasticsearch.yaml             |   2 +
 .../multi-tier-1/custom_types/kibana.yaml       |   2 +
 .../multi-tier-1/custom_types/logstash.yaml     |   2 +
 .../paypalpizzastore_nodejs_app.yaml            |   2 +-
 .../webserver-dbms-2/webserver-dbms-2.yaml      |   6 +-
 .../profiles/aria-1.0/aria-1.0.yaml             |   4 +
 .../profiles/tosca-simple-1.0/capabilities.yaml |   2 +
 .../profiles/tosca-simple-1.0/interfaces.yaml   |  24 +++
 .../profiles/tosca-simple-1.0/nodes.yaml        |   1 +
 .../simple_v1_0/assignments.py                  |  49 +++--
 .../simple_v1_0/modeling/__init__.py            |  76 +++++---
 .../simple_v1_0/modeling/capabilities.py        |   5 +
 tests/modeling/test_models.py                   |  65 ++++---
 tests/orchestrator/context/test_operation.py    |   3 +-
 tests/orchestrator/context/test_serialize.py    |   3 +-
 tests/orchestrator/execution_plugin/test_ssh.py |  17 +-
 tests/orchestrator/workflows/api/test_task.py   |  24 +--
 .../workflows/builtin/test_execute_operation.py |   3 +-
 tests/orchestrator/workflows/core/test_task.py  |  29 ++-
 .../test_task_graph_into_exececution_graph.py   |   3 +-
 .../node-cellar/node-cellar.yaml                |  36 +++-
 .../node-cellar/types/nginx.yaml                |  15 +-
 35 files changed, 696 insertions(+), 316 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/73c1d029/aria/cli/dry.py
----------------------------------------------------------------------
diff --git a/aria/cli/dry.py b/aria/cli/dry.py
index 82faf42..9c38a85 100644
--- a/aria/cli/dry.py
+++ b/aria/cli/dry.py
@@ -49,8 +49,8 @@ def convert_operation_to_dry(oper):
     Converts a single :class:`Operation` to run dryly.
     """
 
-    plugin = oper.plugin_specification.name \
-        if oper.plugin_specification is not None else None
+    plugin = oper.plugin.name \
+        if oper.plugin is not None else None
     if oper.inputs is None:
         oper.inputs = OrderedDict()
     oper.inputs['_implementation'] = models.Parameter(name='_implementation',

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/73c1d029/aria/modeling/orchestration.py
----------------------------------------------------------------------
diff --git a/aria/modeling/orchestration.py b/aria/modeling/orchestration.py
index 2d58671..d29f9f8 100644
--- a/aria/modeling/orchestration.py
+++ b/aria/modeling/orchestration.py
@@ -200,7 +200,44 @@ class PluginBase(ModelMixin):
 
 class TaskBase(ModelMixin):
     """
-    A Model which represents an task
+    Represents the smallest unit of stateful execution in ARIA. The task state includes inputs,
+    outputs, as well as an atomic status, ensuring that the task can only be running once at any
+    given time.
+
+    Tasks may be "one shot" or may be configured to run repeatedly in the case of failure.
+
+    Tasks are often based on :class:`Operation`, and thus act on either a :class:`Node` or a
+    :class:`Relationship`, however this is not required.
+
+    :ivar node: The node actor (optional)
+    :vartype node: :class:`Node`
+    :ivar relationship: The relationship actor (optional)
+    :vartype relationship: :class:`Relationship`
+    :ivar plugin: The implementing plugin (set to None for default execution plugin)
+    :vartype plugin: :class:`Plugin`
+    :ivar inputs: Parameters that can be used by this task
+    :vartype inputs: {basestring: :class:`Parameter`}
+    :ivar implementation: Python path to an ``@operation`` function
+    :vartype implementation: basestring
+    :ivar max_attempts: Maximum number of retries allowed in case of failure
+    :vartype max_attempts: int
+    :ivar retry_interval: Interval between retries (in seconds)
+    :vartype retry_interval: int
+    :ivar ignore_failure: Set to True to ignore failures
+    :vartype ignore_failure: bool
+    :ivar due_at: Timestamp to start the task
+    :vartype due_at: datetime
+    :ivar execution: Assigned execution
+    :vartype execution: :class:`Execution`
+    :ivar status: Current atomic status ('pending', 'retrying', 'sent', 'started', 'success',
+                  'failed')
+    :vartype status: basestring
+    :ivar started_at: Timestamp for when task started
+    :vartype started_at: datetime
+    :ivar ended_at: Timestamp for when task ended
+    :vartype ended_at: datetime
+    :ivar retry_count: How many retries occurred
+    :vartype retry_count: int
     """
 
     __tablename__ = 'task'
@@ -208,7 +245,7 @@ class TaskBase(ModelMixin):
     __private_fields__ = ['node_fk',
                           'relationship_fk',
                           'plugin_fk',
-                          'execution_fk',
+                          'execution_fk'
                           'node_name',
                           'relationship_name',
                           'execution_name']
@@ -231,11 +268,6 @@ class TaskBase(ModelMixin):
     WAIT_STATES = [PENDING, RETRYING]
     END_STATES = [SUCCESS, FAILED]
 
-    RUNS_ON_SOURCE = 'source'
-    RUNS_ON_TARGET = 'target'
-    RUNS_ON_NODE = 'node'
-    RUNS_ON = (RUNS_ON_NODE, RUNS_ON_SOURCE, RUNS_ON_TARGET)
-
     INFINITE_RETRIES = -1
 
     @declared_attr
@@ -258,29 +290,17 @@ class TaskBase(ModelMixin):
     def inputs(cls):
         return relationship.many_to_many(cls, 'parameter', prefix='inputs', dict_key='name')
 
-    status = Column(Enum(*STATES, name='status'), default=PENDING)
-
-    due_at = Column(DateTime, nullable=False, index=True, default=datetime.utcnow())
-    started_at = Column(DateTime, default=None)
-    ended_at = Column(DateTime, default=None)
+    implementation = Column(String)
     max_attempts = Column(Integer, default=1)
-    retry_count = Column(Integer, default=0)
     retry_interval = Column(Float, default=0)
     ignore_failure = Column(Boolean, default=False)
+    due_at = Column(DateTime, nullable=False, index=True, default=datetime.utcnow())
 
-    # Operation specific fields
-    implementation = Column(String)
-    _runs_on = Column(Enum(*RUNS_ON, name='runs_on'), name='runs_on')
-
-    @property
-    def runs_on(self):
-        if self._runs_on == self.RUNS_ON_NODE:
-            return self.node
-        elif self._runs_on == self.RUNS_ON_SOURCE:
-            return self.relationship.source_node  # pylint: disable=no-member
-        elif self._runs_on == self.RUNS_ON_TARGET:
-            return self.relationship.target_node  # pylint: disable=no-member
-        return None
+    # State
+    status = Column(Enum(*STATES, name='status'), default=PENDING)
+    started_at = Column(DateTime, default=None)
+    ended_at = Column(DateTime, default=None)
+    retry_count = Column(Integer, default=0)
 
     @property
     def actor(self):
@@ -338,12 +358,12 @@ class TaskBase(ModelMixin):
     # endregion
 
     @classmethod
-    def for_node(cls, instance, runs_on, **kwargs):
-        return cls(node=instance, _runs_on=runs_on, **kwargs)
+    def for_node(cls, instance, **kwargs):
+        return cls(node=instance, **kwargs)
 
     @classmethod
-    def for_relationship(cls, instance, runs_on, **kwargs):
-        return cls(relationship=instance, _runs_on=runs_on, **kwargs)
+    def for_relationship(cls, instance, **kwargs):
+        return cls(relationship=instance, **kwargs)
 
     @staticmethod
     def abort(message=None):

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/73c1d029/aria/modeling/service_instance.py
----------------------------------------------------------------------
diff --git a/aria/modeling/service_instance.py b/aria/modeling/service_instance.py
index f120734..6130442 100644
--- a/aria/modeling/service_instance.py
+++ b/aria/modeling/service_instance.py
@@ -28,6 +28,7 @@ from .mixins import InstanceModelMixin
 from ..parser import validation
 from ..parser.consumption import ConsumptionContext
 from ..utils import collections, formatting, console
+from ..orchestrator.execution_plugin.instantiation import configure_operation
 from . import (
     relationship,
     utils,
@@ -180,6 +181,16 @@ class ServiceBase(InstanceModelMixin): # pylint: disable=too-many-public-methods
                 satisfied = False
         return satisfied
 
+    def find_hosts(self):
+        for node in self.nodes.itervalues():
+            node.find_host()
+
+    def configure_operations(self):
+        for node in self.nodes.itervalues():
+            node.configure_operations()
+        for operation in self.workflows.itervalues():
+            operation.configure()
+
     def is_node_a_target(self, target_node):
         for node in self.nodes.itervalues():
             if self._is_node_a_target(node, target_node):
@@ -188,11 +199,11 @@ class ServiceBase(InstanceModelMixin): # pylint: disable=too-many-public-methods
 
     def _is_node_a_target(self, source_node, target_node):
         if source_node.outbound_relationships:
-            for the_relationship in source_node.outbound_relationships:
-                if the_relationship.target_node.name == target_node.name:
+            for relationship_model in source_node.outbound_relationships:
+                if relationship_model.target_node.name == target_node.name:
                     return True
                 else:
-                    node = the_relationship.target_node
+                    node = relationship_model.target_node
                     if node is not None:
                         if self._is_node_a_target(node, target_node):
                             return True
@@ -255,38 +266,25 @@ class ServiceBase(InstanceModelMixin): # pylint: disable=too-many-public-methods
             if not self.is_node_a_target(node):
                 self._dump_graph_node(node)
 
-    def _dump_graph_node(self, node):
+    def _dump_graph_node(self, node, capability=None):
         context = ConsumptionContext.get_thread_local()
         console.puts(context.style.node(node.name))
+        if capability is not None:
+            console.puts('{0} ({1})'.format(context.style.property(capability.name),
+                                            context.style.type(capability.type.name)))
         if node.outbound_relationships:
             with context.style.indent:
-                for the_relationship in node.outbound_relationships:
-                    relationship_name = context.style.property(the_relationship.name)
-                    if the_relationship.type is not None:
-                        relationship_type = context.style.type(the_relationship.type.name)
-                    else:
-                        relationship_type = None
-                    if the_relationship.target_capability is not None:
-                        capability_name = \
-                            context.style.node(the_relationship.target_capability.name)
+                for relationship_model in node.outbound_relationships:
+                    relationship_name = context.style.property(relationship_model.name)
+                    if relationship_model.type is not None:
+                        console.puts('-> {0} ({1})'.format(relationship_name,
+                                                           context.style.type(
+                                                               relationship_model.type.name)))
                     else:
-                        capability_name = None
-                    if capability_name is not None:
-                        if relationship_type is not None:
-                            console.puts('-> {0} ({1}) {2}'.format(relationship_name,
-                                                                   relationship_type,
-                                                                   capability_name))
-                        else:
-                            console.puts('-> {0} {1}'.format(relationship_name, capability_name))
-                    else:
-                        if relationship_type is not None:
-                            console.puts('-> {0} ({1})'.format(relationship_name,
-                                                               relationship_type))
-                        else:
-                            console.puts('-> {0}'.format(relationship_name))
-                    target_node = the_relationship.target_node
+                        console.puts('-> {0}'.format(relationship_name))
                     with console.indent(3):
-                        self._dump_graph_node(target_node)
+                        self._dump_graph_node(relationship_model.target_node,
+                                              relationship_model.target_capability)
 
 
 class NodeBase(InstanceModelMixin): # pylint: disable=too-many-public-methods
@@ -335,8 +333,10 @@ class NodeBase(InstanceModelMixin): # pylint: disable=too-many-public-methods
     :vartype policies: [:class:`Policy`]
     :ivar substitution_mapping: Our contribution to service substitution
     :vartype substitution_mapping: :class:`SubstitutionMapping`
-    :ivar tasks: Tasks on this node
+    :ivar tasks: Tasks for this node
     :vartype tasks: [:class:`Task`]
+    :ivar hosted_tasks: Tasks on this node
+    :vartype hosted_tasks: [:class:`Task`]
     """
 
     __tablename__ = 'node'
@@ -384,7 +384,7 @@ class NodeBase(InstanceModelMixin): # pylint: disable=too-many-public-methods
                                         child_property='target_node')
 
     @declared_attr
-    def host(cls):
+    def host(cls): # pylint: disable=method-hidden
         return relationship.one_to_one_self(cls, 'host_fk')
 
     # region orchestration
@@ -397,17 +397,10 @@ class NodeBase(InstanceModelMixin): # pylint: disable=too-many-public-methods
     __mapper_args__ = {'version_id_col': version} # Enable SQLAlchemy automatic version counting
 
     @property
-    def ip(self):
-        # TODO: totally broken
-        if not self.host_fk:
-            return None
-        host_node = self.host
-        if 'ip' in host_node.runtime_properties:  # pylint: disable=no-member
-            return host_node.runtime_properties['ip']  # pylint: disable=no-member
-        host_node = host_node.node_template  # pylint: disable=no-member
-        host_ip_property = host_node.properties.get('ip')
-        if host_ip_property:
-            return host_ip_property.value
+    def host_address(self):
+        if self.host:
+            if self.host.runtime_properties:
+                return self.host.runtime_properties.get('host_address')
         return None
 
     # endregion
@@ -477,9 +470,10 @@ class NodeBase(InstanceModelMixin): # pylint: disable=too-many-public-methods
             if target_node_capability is not None:
                 # Relate to the first target node that has capacity
                 for node in target_nodes:
-                    target_capability = node.capabilities.get(target_node_capability.name)
-                    if target_capability.relate():
+                    a_target_capability = node.capabilities.get(target_node_capability.name)
+                    if a_target_capability.relate():
                         target_node = node
+                        target_capability = a_target_capability
                         break
             else:
                 # Use first target node
@@ -487,14 +481,15 @@ class NodeBase(InstanceModelMixin): # pylint: disable=too-many-public-methods
 
             if target_node is not None:
                 if requirement_template.relationship_template is not None:
-                    the_relationship = \
+                    relationship_model = \
                         requirement_template.relationship_template.instantiate(self)
                 else:
-                    the_relationship = models.Relationship(target_capability=target_capability)
-                the_relationship.name = requirement_template.name
-                the_relationship.requirement_template = requirement_template
-                the_relationship.target_node = target_node
-                self.outbound_relationships.append(the_relationship)
+                    relationship_model = models.Relationship()
+                relationship_model.name = requirement_template.name
+                relationship_model.requirement_template = requirement_template
+                relationship_model.target_node = target_node
+                relationship_model.target_capability = target_capability
+                self.outbound_relationships.append(relationship_model)
                 return True
             else:
                 context.validation.report('requirement "{0}" of node "{1}" targets node '
@@ -529,6 +524,32 @@ class NodeBase(InstanceModelMixin): # pylint: disable=too-many-public-methods
                 satisfied = False
         return satisfied
 
+    def find_host(self):
+        def _find_host(node):
+            if node.type.role == 'host':
+                return node
+            for the_relationship in node.outbound_relationships:
+                if (the_relationship.target_capability is not None) and \
+                    the_relationship.target_capability.type.role == 'host':
+                    host = _find_host(the_relationship.target_node)
+                    if host is not None:
+                        return host
+            for the_relationship in node.inbound_relationships:
+                if (the_relationship.target_capability is not None) and \
+                    the_relationship.target_capability.type.role == 'feature':
+                    host = _find_host(the_relationship.source_node)
+                    if host is not None:
+                        return host
+            return None
+
+        self.host = _find_host(self)
+
+    def configure_operations(self):
+        for interface in self.interfaces.itervalues():
+            interface.configure_operations()
+        for the_relationship in self.outbound_relationships:
+            the_relationship.configure_operations()
+
     @property
     def as_raw(self):
         return collections.OrderedDict((
@@ -577,6 +598,7 @@ class NodeBase(InstanceModelMixin): # pylint: disable=too-many-public-methods
             utils.dump_dict_values(self.capabilities, 'Capabilities')
             utils.dump_list_values(self.outbound_relationships, 'Relationships')
 
+
 class GroupBase(InstanceModelMixin):
     """
     Usually an instance of a :class:`GroupTemplate`.
@@ -968,7 +990,7 @@ class RelationshipBase(InstanceModelMixin):
     :vartype source_node: :class:`Node`
     :ivar target_node: Target node
     :vartype target_node: :class:`Node`
-    :ivar tasks: Tasks on this node
+    :ivar tasks: Tasks for this relationship
     :vartype tasks: [:class:`Task`]
     """
 
@@ -1062,6 +1084,10 @@ class RelationshipBase(InstanceModelMixin):
 
     # endregion
 
+    def configure_operations(self):
+        for interface in self.interfaces.itervalues():
+            interface.configure_operations()
+
     @property
     def as_raw(self):
         return collections.OrderedDict((
@@ -1294,6 +1320,10 @@ class InterfaceBase(InstanceModelMixin):
 
     # endregion
 
+    def configure_operations(self):
+        for operation in self.operations.itervalues():
+            operation.configure()
+
     @property
     def as_raw(self):
         return collections.OrderedDict((
@@ -1334,10 +1364,12 @@ class OperationBase(InstanceModelMixin):
     :vartype operation_template: :class:`OperationTemplate`
     :ivar description: Human-readable description
     :vartype description: string
-    :ivar plugin_specification: Associated plugin
-    :vartype plugin_specification: :class:`PluginSpecification`
-    :ivar implementation: Implementation string (interpreted by the plugin)
+    :ivar plugin: Associated plugin
+    :vartype plugin: :class:`Plugin`
+    :ivar implementation: Implementation (interpreted by the plugin)
     :vartype implementation: basestring
+    :ivar configuration: Configuration (interpreted by the plugin)
+    :vartype configuration: {basestring, object}
     :ivar dependencies: Dependency strings (interpreted by the plugin)
     :vartype dependencies: [basestring]
     :ivar inputs: Parameters that can be used by this operation
@@ -1369,10 +1401,12 @@ class OperationBase(InstanceModelMixin):
     description = Column(Text)
 
     @declared_attr
-    def plugin_specification(cls):
-        return relationship.one_to_one(cls, 'plugin_specification')
+    def plugin(cls):
+        return relationship.one_to_one(cls, 'plugin')
 
+    runs_on = Column(Text)
     implementation = Column(Text)
+    configuration = Column(modeling_types.StrictDict(key_cls=basestring))
     dependencies = Column(modeling_types.StrictList(item_cls=basestring))
 
     @declared_attr
@@ -1396,9 +1430,9 @@ class OperationBase(InstanceModelMixin):
         return relationship.foreign_key('interface', nullable=True)
 
     @declared_attr
-    def plugin_specification_fk(cls):
-        """For Operation one-to-one to PluginSpecification"""
-        return relationship.foreign_key('plugin_specification', nullable=True)
+    def plugin_fk(cls):
+        """For Operation one-to-one to Plugin"""
+        return relationship.foreign_key('plugin', nullable=True)
 
     @declared_attr
     def operation_template_fk(cls):
@@ -1407,6 +1441,19 @@ class OperationBase(InstanceModelMixin):
 
     # endregion
 
+    def configure(self):
+        if self.implementation is None:
+            return
+        if self.interface is not None:
+            # Operation on an interface
+            if self.plugin is None:
+                configure_operation(self)
+            else:
+                pass
+        elif self.service is not None:
+            # Workflow
+            pass
+
     @property
     def as_raw(self):
         return collections.OrderedDict((
@@ -1432,9 +1479,17 @@ class OperationBase(InstanceModelMixin):
         if self.description:
             console.puts(context.style.meta(self.description))
         with context.style.indent:
+            if self.plugin is not None:
+                console.puts('Plugin: {0}'.format(
+                    context.style.literal(self.plugin.name)))
             if self.implementation is not None:
                 console.puts('Implementation: {0}'.format(
                     context.style.literal(self.implementation)))
+            if self.configuration:
+                with context.style.indent:
+                    for k, v in self.configuration.iteritems():
+                        console.puts('{0}: {1}'.format(context.style.property(k),
+                                                       context.style.literal(v)))
             if self.dependencies:
                 console.puts(
                     'Dependencies: {0}'.format(

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/73c1d029/aria/modeling/service_template.py
----------------------------------------------------------------------
diff --git a/aria/modeling/service_template.py b/aria/modeling/service_template.py
index 7246ff1..3e4554c 100644
--- a/aria/modeling/service_template.py
+++ b/aria/modeling/service_template.py
@@ -1467,8 +1467,10 @@ class OperationTemplateBase(TemplateModelMixin):
     :vartype description: basestring
     :ivar plugin_specification: Associated plugin
     :vartype plugin_specification: :class:`PluginSpecification`
-    :ivar implementation: Implementation string (interpreted by the plugin)
+    :ivar implementation: Implementation (interpreted by the plugin)
     :vartype implementation: basestring
+    :ivar configuration: Configuration (interpreted by the plugin)
+    :vartype configuration: {basestring, object}
     :ivar dependencies: Dependency strings (interpreted by the plugin)
     :vartype dependencies: [basestring]
     :ivar inputs: Parameters that can be used by this operation
@@ -1501,6 +1503,7 @@ class OperationTemplateBase(TemplateModelMixin):
         return relationship.one_to_one(cls, 'plugin_specification')
 
     implementation = Column(Text)
+    configuration = Column(modeling_types.StrictDict(key_cls=basestring))
     dependencies = Column(modeling_types.StrictList(item_cls=basestring))
 
     @declared_attr
@@ -1544,11 +1547,24 @@ class OperationTemplateBase(TemplateModelMixin):
 
     def instantiate(self, container):
         from . import models
+        from ..orchestrator import context
+        plugin = None
+        if self.plugin_specification is not None:
+            try:
+                workflow_context = context.workflow.current.get()
+                plugin = self.plugin_specification.find_plugin(workflow_context.model.plugin.list())
+            except context.exceptions.ContextException:
+                pass
+                # TODO
+                #context = ConsumptionContext.get_thread_local()
+                #context.validation.report('plugin not found for specification: {0}'.format(
+                #    self.plugin_specification.name), level=validation.Issue.EXTERNAL)
         operation = models.Operation(name=self.name,
                                      description=deepcopy_with_locators(self.description),
+                                     plugin=plugin,
                                      implementation=self.implementation,
+                                     configuration=self.configuration,
                                      dependencies=self.dependencies,
-                                     plugin_specification=self.plugin_specification,
                                      executor=self.executor,
                                      max_retries=self.max_retries,
                                      retry_interval=self.retry_interval,
@@ -1568,9 +1584,17 @@ class OperationTemplateBase(TemplateModelMixin):
         if self.description:
             console.puts(context.style.meta(self.description))
         with context.style.indent:
+            if self.plugin_specification is not None:
+                console.puts('Plugin specification: {0}'.format(
+                    context.style.literal(self.plugin_specification.name)))
             if self.implementation is not None:
                 console.puts('Implementation: {0}'.format(
                     context.style.literal(self.implementation)))
+            if self.configuration:
+                with context.style.indent:
+                    for k, v in self.configuration.iteritems():
+                        console.puts('{0}: {1}'.format(context.style.property(k),
+                                                       context.style.literal(v)))
             if self.dependencies:
                 console.puts('Dependencies: {0}'.format(
                     ', '.join((str(context.style.literal(v)) for v in self.dependencies))))

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/73c1d029/aria/orchestrator/execution_plugin/__init__.py
----------------------------------------------------------------------
diff --git a/aria/orchestrator/execution_plugin/__init__.py b/aria/orchestrator/execution_plugin/__init__.py
index 372022f..7bd5f02 100644
--- a/aria/orchestrator/execution_plugin/__init__.py
+++ b/aria/orchestrator/execution_plugin/__init__.py
@@ -15,6 +15,7 @@
 
 from contextlib import contextmanager
 
+
 # Populated during execution of python scripts
 ctx = None
 inputs = None

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/73c1d029/aria/orchestrator/execution_plugin/instantiation.py
----------------------------------------------------------------------
diff --git a/aria/orchestrator/execution_plugin/instantiation.py b/aria/orchestrator/execution_plugin/instantiation.py
new file mode 100644
index 0000000..99d7e6e
--- /dev/null
+++ b/aria/orchestrator/execution_plugin/instantiation.py
@@ -0,0 +1,188 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from ...utils.formatting import full_type_name
+from ...utils.collections import OrderedDict
+from ...parser import validation
+from ...parser.consumption import ConsumptionContext
+
+
+def configure_operation(operation):
+    configuration = operation.configuration or {}
+    inputs = OrderedDict()
+    inputs['script_path'] = operation.implementation
+    inputs['process'] = _get_process(configuration.get('process'))
+
+    host = None
+    interface = operation.interface
+    if interface.node is not None:
+        host = interface.node.host
+    elif interface.relationship is not None:
+        edge = configuration.get('edge', 'source')
+        if edge == 'source':
+            host = interface.relationship.source_node.host
+        elif edge == 'target':
+            host = interface.relationship.target_node.host
+        else:
+            context = ConsumptionContext.get_thread_local()
+            context.validation.report('"edge" configuration must be "source" or "target" for "{0}":'
+                                      ' {1}'.format(operation.implementation, edge),
+                                      level=validation.Issue.BETWEEN_TYPES)
+    # TODO: host address would not be available
+    host_address = host.host_address if host is not None else None
+
+    if host_address is None:
+        _configure_local(operation)
+    else:
+        _configure_remote(operation, inputs, host_address)
+
+    from ...modeling import models
+    for k, v in inputs.iteritems():
+        operation.inputs[k] = models.Parameter.wrap(k, v)
+
+
+def _configure_local(operation):
+    """
+    Local operation.
+    """
+    from . import operations
+    operation.implementation = '{0}.{1}'.format(operations.__name__,
+                                                operations.run_script_locally.__name__)
+
+
+def _configure_remote(operation, inputs, host_address):
+    """
+    Remote SSH operation via Fabric.
+    """
+    default_user = 'admin'
+    default_password = 'admin'
+    configuration = operation.configuration or {}
+    ssh = _get_ssh(configuration.get('ssh'))
+    if 'user' not in ssh:
+        ssh['user'] = default_user
+    if ('password' not in ssh) and ('key' not in ssh) and ('key_filename' not in ssh):
+        ssh['password'] = default_password
+    inputs['use_sudo'] = ssh.get('use_sudo')
+    inputs['hide_output'] = ssh.get('hide_output')
+    inputs['fabric_env'] = {}
+    inputs['fabric_env']['host_string'] = host_address
+    if 'warn_only' in ssh:
+        inputs['fabric_env']['warn_only'] = ssh['warn_only']
+    inputs['fabric_env']['user'] = ssh.get('user')
+    inputs['fabric_env']['password'] = ssh.get('password')
+    inputs['fabric_env']['key'] = ssh.get('key')
+    inputs['fabric_env']['key_filename'] = ssh.get('key_filename')
+
+    if inputs['fabric_env'].get('user') is None:
+        context = ConsumptionContext.get_thread_local()
+        context.validation.report('must configure "ssh.user" for "{0}"'
+                                  .format(operation.implementation),
+                                  level=validation.Issue.BETWEEN_TYPES)
+    if (inputs['fabric_env'].get('password') is None) and \
+        (inputs['fabric_env'].get('key') is None) and \
+        (inputs['fabric_env'].get('key_filename') is None):
+        context = ConsumptionContext.get_thread_local()
+        context.validation.report('must configure "ssh.password", "ssh.key", or "ssh.key_filename" '
+                                  'for "{0}"'
+                                  .format(operation.implementation),
+                                  level=validation.Issue.BETWEEN_TYPES)
+
+    from . import operations
+    operation.implementation = '{0}.{1}'.format(operations.__name__,
+                                                operations.run_script_with_ssh.__name__)
+
+
+def _get_process(value):
+    if value is None:
+        return None
+    _validate_type(value, dict, 'process')
+    for k, v in value.iteritems():
+        if k == 'eval_python':
+            value[k] = _str_to_bool(v, 'process.eval_python')
+        elif k == 'cwd':
+            _validate_type(v, basestring, 'process.cwd')
+        elif k == 'command_prefix':
+            _validate_type(v, basestring, 'process.command_prefix')
+        elif k == 'args':
+            value[k] = _dict_to_list(v, 'process.args')
+        elif k == 'env':
+            _validate_type(v, dict, 'process.env')
+        else:
+            context = ConsumptionContext.get_thread_local()
+            context.validation.report('unsupported configuration: "process.{0}"'.format(k),
+                                      level=validation.Issue.BETWEEN_TYPES)
+    return value
+
+
+def _get_ssh(value):
+    if value is None:
+        return {}
+    _validate_type(value, dict, 'ssh')
+    for k, v in value.iteritems():
+        if k == 'use_sudo':
+            value[k] = _str_to_bool(v, 'ssh.use_sudo')
+        elif k == 'hide_output':
+            value[k] = _dict_to_list(v, 'ssh.hide_output')
+        elif k == 'warn_only':
+            value[k] = _str_to_bool(v, 'ssh.warn_only')
+        elif k == 'user':
+            _validate_type(v, basestring, 'ssh.user')
+        elif k == 'password':
+            _validate_type(v, basestring, 'ssh.password')
+        elif k == 'key':
+            _validate_type(v, basestring, 'ssh.key')
+        elif k == 'key_filename':
+            _validate_type(v, basestring, 'ssh.key_filename')
+        else:
+            context = ConsumptionContext.get_thread_local()
+            context.validation.report('unsupported configuration: "ssh.{0}"'.format(k),
+                                      level=validation.Issue.BETWEEN_TYPES)
+    return value
+
+
+def _validate_type(value, the_type, name):
+    if not isinstance(value, the_type):
+        context = ConsumptionContext.get_thread_local()
+        context.validation.report('"{0}" configuration is not a {1}'
+                                  .format(name, full_type_name(the_type)),
+                                  level=validation.Issue.BETWEEN_TYPES)
+
+def _str_to_bool(value, name):
+    if value is None:
+        return None
+    _validate_type(value, basestring, name)
+    if value == 'true':
+        return True
+    elif value == 'false':
+        return False
+    else:
+        context = ConsumptionContext.get_thread_local()
+        context.validation.report('"{0}" configuration is not "true" or "false": {1}'
+                                  .format(name, repr(value)),
+                                  level=validation.Issue.BETWEEN_TYPES)
+
+
+def _dict_to_list(the_dict, name):
+    _validate_type(the_dict, dict, name)
+    value = []
+    for k in sorted(the_dict):
+        v = the_dict[k]
+        if not isinstance(v, basestring):
+            context = ConsumptionContext.get_thread_local()
+            context.validation.report('"{0}.{1}" configuration is not a string: {2}'
+                                      .format(name, k, repr(v)),
+                                      level=validation.Issue.BETWEEN_TYPES)
+        value.append(v)
+    return value

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/73c1d029/aria/orchestrator/execution_plugin/ssh/operations.py
----------------------------------------------------------------------
diff --git a/aria/orchestrator/execution_plugin/ssh/operations.py b/aria/orchestrator/execution_plugin/ssh/operations.py
index f240beb..7147a30 100644
--- a/aria/orchestrator/execution_plugin/ssh/operations.py
+++ b/aria/orchestrator/execution_plugin/ssh/operations.py
@@ -143,9 +143,9 @@ def _fabric_env(ctx, fabric_env, warn_only):
     env = constants.FABRIC_ENV_DEFAULTS.copy()
     env.update(fabric_env or {})
     env.setdefault('warn_only', warn_only)
-    if 'host_string' not in env:
-        env['host_string'] = ctx.task.runs_on.ip
     # validations
+    if (not env.get('host_string')) and (ctx.task) and (ctx.task.actor) and (ctx.task.actor.host):
+        env['host_string'] = ctx.task.actor.host.host_address
     if not env.get('host_string'):
         ctx.task.abort('`host_string` not supplied and ip cannot be deduced automatically')
     if not (env.get('password') or env.get('key_filename') or env.get('key')):

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/73c1d029/aria/orchestrator/workflows/api/task.py
----------------------------------------------------------------------
diff --git a/aria/orchestrator/workflows/api/task.py b/aria/orchestrator/workflows/api/task.py
index 009b81c..ab9f550 100644
--- a/aria/orchestrator/workflows/api/task.py
+++ b/aria/orchestrator/workflows/api/task.py
@@ -19,7 +19,7 @@ Provides the tasks to be entered into the task graph
 import copy
 
 from ....modeling import models
-from ....utils.collections import OrderedDict
+from ....utils.collections import (OrderedDict, FrozenDict)
 from ....utils.uuid import generate_uuid
 from ... import context
 from .. import exceptions
@@ -56,7 +56,7 @@ class BaseTask(object):
 
 class OperationTask(BaseTask):
     """
-    Represents an operation task in the task_graph
+    Represents an operation task in the task graph.
     """
 
     NAME_FORMAT = '{interface}:{operation}@{type}:{name}'
@@ -65,31 +65,48 @@ class OperationTask(BaseTask):
                  actor,
                  actor_type,
                  interface_name,
-                 operation_name,
-                 runs_on=None,
+                 operation_name, # remove configuration
+                 inputs=None,
                  max_attempts=None,
                  retry_interval=None,
-                 ignore_failure=None,
-                 inputs=None):
+                 ignore_failure=None):
         """
         Do not call this constructor directly. Instead, use :meth:`for_node` or
         :meth:`for_relationship`.
         """
 
-        assert isinstance(actor, (models.Node, models.Relationship))
-        assert actor_type in ('node', 'relationship')
         assert interface_name and operation_name
-        assert runs_on in models.Task.RUNS_ON
         super(OperationTask, self).__init__()
 
         self.actor = actor
+        self.actor_type = actor_type
+
+        operation = None
+        interface = self.actor.interfaces.get(interface_name)
+        if interface is not None:
+            operation = interface.operations.get(operation_name)
+        if operation is None:
+            raise exceptions.OperationNotFoundException(
+                'Could not find operation "{0}" on interface "{1}" for {2} "{3}"'
+                .format(operation_name, interface_name, actor_type, actor.name))
+
+        if operation.implementation is None:
+            raise exceptions.OperationNotFoundException(
+                'Empty operation "{0}" on interface "{1}" for {2} "{3}"'
+                .format(operation_name, interface_name, actor_type, actor.name))
+
+        self.name = OperationTask.NAME_FORMAT.format(type=actor_type,
+                                                     name=actor.name,
+                                                     interface=interface_name,
+                                                     operation=operation_name)
         self.max_attempts = (self.workflow_context._task_max_attempts
                              if max_attempts is None else max_attempts)
         self.retry_interval = (self.workflow_context._task_retry_interval
                                if retry_interval is None else retry_interval)
         self.ignore_failure = (self.workflow_context._task_ignore_failure
                                if ignore_failure is None else ignore_failure)
-        self.runs_on = runs_on
+        self.implementation = operation.implementation
+        self.plugin = operation.plugin
 
         # Wrap inputs
         inputs = copy.deepcopy(inputs) if inputs else {}
@@ -97,54 +114,33 @@ class OperationTask(BaseTask):
             if not isinstance(v, models.Parameter):
                 inputs[k] = models.Parameter.wrap(k, v)
 
-        # TODO: Suggestion: these extra inputs could be stored as a separate entry in the task
-        # model, because they are different from the operation inputs. If we do this, then the two
-        # kinds of inputs should *not* be merged here.
-
-        operation = self._get_operation(interface_name, operation_name)
-        if operation is None:
-            raise exceptions.OperationNotFoundException(
-                'Could not find operation "{0}" on interface "{1}" for {2} "{3}"'
-                .format(operation_name, interface_name, actor_type, actor.name))
-
-        self.plugin = None
-        if operation.plugin_specification:
-            self.plugin = OperationTask._find_plugin(operation.plugin_specification)
-            if self.plugin is None:
-                raise exceptions.PluginNotFoundException(
-                    'Could not find plugin of operation "{0}" on interface "{1}" for {2} "{3}"'
-                    .format(operation_name, interface_name, actor_type, actor.name))
-
-        self.implementation = operation.implementation
-        self.inputs = OperationTask._merge_inputs(operation.inputs, inputs)
-
-        self.name = OperationTask.NAME_FORMAT.format(type=actor_type,
-                                                     name=actor.name,
-                                                     interface=interface_name,
-                                                     operation=operation_name)
+        self.inputs = OrderedDict(operation.inputs)
+        if inputs:
+            self.inputs.update(inputs)
+        self.inputs = FrozenDict(self.inputs)
 
     @classmethod
     def for_node(cls,
                  node,
                  interface_name,
                  operation_name,
+                 inputs=None,
                  max_attempts=None,
                  retry_interval=None,
-                 ignore_failure=None,
-                 inputs=None):
+                 ignore_failure=None):
         """
         Creates an operation on a node.
 
         :param node: The node on which to run the operation
         :param interface_name: The interface name
         :param operation_name: The operation name within the interface
+        :param inputs: Override the operation's inputs
         :param max_attempts: The maximum number of attempts in case the operation fails
                              (if not specified the defaults it taken from the workflow context)
         :param retry_interval: The interval in seconds between attempts when the operation fails
                                (if not specified the defaults it taken from the workflow context)
         :param ignore_failure: Whether to ignore failures
                                (if not specified the defaults it taken from the workflow context)
-        :param inputs: Additional operation inputs
         """
 
         assert isinstance(node, models.Node)
@@ -153,68 +149,45 @@ class OperationTask(BaseTask):
             actor_type='node',
             interface_name=interface_name,
             operation_name=operation_name,
+            inputs=inputs,
             max_attempts=max_attempts,
             retry_interval=retry_interval,
-            ignore_failure=ignore_failure,
-            inputs=inputs,
-            runs_on=models.Task.RUNS_ON_NODE)
+            ignore_failure=ignore_failure)
 
     @classmethod
     def for_relationship(cls,
                          relationship,
                          interface_name,
                          operation_name,
-                         runs_on=models.Task.RUNS_ON_SOURCE,
+                         inputs=None,
                          max_attempts=None,
                          retry_interval=None,
-                         ignore_failure=None,
-                         inputs=None):
+                         ignore_failure=None):
         """
-        Creates an operation on a relationship edge.
+        Creates an operation on a relationship.
 
         :param relationship: The relationship on which to run the operation
         :param interface_name: The interface name
         :param operation_name: The operation name within the interface
-        :param runs_on: where to run the operation ("source" or "target"); defaults to "source"
+        :param inputs: Override the operation's inputs
         :param max_attempts: The maximum number of attempts in case the operation fails
                              (if not specified the defaults it taken from the workflow context)
         :param retry_interval: The interval in seconds between attempts when the operation fails
                                (if not specified the defaults it taken from the workflow context)
         :param ignore_failure: Whether to ignore failures
                                (if not specified the defaults it taken from the workflow context)
-        :param inputs: Additional operation inputs
         """
 
         assert isinstance(relationship, models.Relationship)
-        assert runs_on in models.Task.RUNS_ON
         return cls(
             actor=relationship,
             actor_type='relationship',
             interface_name=interface_name,
             operation_name=operation_name,
-            runs_on=runs_on,
+            inputs=inputs,
             max_attempts=max_attempts,
             retry_interval=retry_interval,
-            ignore_failure=ignore_failure,
-            inputs=inputs)
-
-    def _get_operation(self, interface_name, operation_name):
-        interface = self.actor.interfaces.get(interface_name)
-        if interface is not None:
-            return interface.operations.get(operation_name)
-        return None
-
-    @staticmethod
-    def _find_plugin(plugin_specification):
-        workflow_context = context.workflow.current.get()
-        return plugin_specification.find_plugin(workflow_context.model.plugin.list())
-
-    @staticmethod
-    def _merge_inputs(operation_inputs, override_inputs=None):
-        final_inputs = OrderedDict(operation_inputs)
-        if override_inputs:
-            final_inputs.update(override_inputs)
-        return final_inputs
+            ignore_failure=ignore_failure)
 
 
 class WorkflowTask(BaseTask):

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/73c1d029/aria/orchestrator/workflows/builtin/utils.py
----------------------------------------------------------------------
diff --git a/aria/orchestrator/workflows/builtin/utils.py b/aria/orchestrator/workflows/builtin/utils.py
index 8efa889..36f1a19 100644
--- a/aria/orchestrator/workflows/builtin/utils.py
+++ b/aria/orchestrator/workflows/builtin/utils.py
@@ -31,7 +31,7 @@ def create_node_task(interface_name, operation_name, node):
         return None
 
 
-def create_relationship_tasks(interface_name, operation_name, runs_on, node):
+def create_relationship_tasks(interface_name, operation_name, node):
     """
     Returns a list of operation tasks for each outbound relationship of the node if the operation
     exists there.
@@ -43,8 +43,7 @@ def create_relationship_tasks(interface_name, operation_name, runs_on, node):
             sequence.append(
                 OperationTask.for_relationship(relationship=relationship,
                                                interface_name=interface_name,
-                                               operation_name=operation_name,
-                                               runs_on=runs_on))
+                                               operation_name=operation_name))
         except exceptions.OperationNotFoundException:
             # We will skip relationships which do not have the operation
             pass

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/73c1d029/aria/orchestrator/workflows/builtin/workflows.py
----------------------------------------------------------------------
diff --git a/aria/orchestrator/workflows/builtin/workflows.py b/aria/orchestrator/workflows/builtin/workflows.py
index 6065343..0f649e1 100644
--- a/aria/orchestrator/workflows/builtin/workflows.py
+++ b/aria/orchestrator/workflows/builtin/workflows.py
@@ -18,7 +18,6 @@ TSOCA normative lifecycle workflows.
 """
 
 from ... import workflow
-from ....modeling.models import Task
 from .utils import (create_node_task, create_relationship_tasks)
 
 
@@ -79,12 +78,10 @@ def install_node(graph, node, **kwargs):
     sequence += \
         create_relationship_tasks(
             NORMATIVE_CONFIGURE_INTERFACE, NORMATIVE_PRE_CONFIGURE_SOURCE,
-            Task.RUNS_ON_SOURCE,
             node)
     sequence += \
         create_relationship_tasks(
             NORMATIVE_CONFIGURE_INTERFACE, NORMATIVE_PRE_CONFIGURE_TARGET,
-            Task.RUNS_ON_TARGET,
             node)
     sequence.append(
         create_node_task(
@@ -93,12 +90,10 @@ def install_node(graph, node, **kwargs):
     sequence += \
         create_relationship_tasks(
             NORMATIVE_CONFIGURE_INTERFACE, NORMATIVE_POST_CONFIGURE_SOURCE,
-            Task.RUNS_ON_SOURCE,
             node)
     sequence += \
         create_relationship_tasks(
             NORMATIVE_CONFIGURE_INTERFACE, NORMATIVE_POST_CONFIGURE_TARGET,
-            Task.RUNS_ON_TARGET,
             node)
 
     # Start
@@ -140,17 +135,14 @@ def _create_start_tasks(node):
     sequence += \
         create_relationship_tasks(
             NORMATIVE_CONFIGURE_INTERFACE, NORMATIVE_ADD_SOURCE,
-            Task.RUNS_ON_SOURCE,
             node)
     sequence += \
         create_relationship_tasks(
             NORMATIVE_CONFIGURE_INTERFACE, NORMATIVE_ADD_TARGET,
-            Task.RUNS_ON_TARGET,
             node)
     sequence += \
         create_relationship_tasks(
             NORMATIVE_CONFIGURE_INTERFACE, NORMATIVE_TARGET_CHANGED,
-            Task.RUNS_ON_TARGET,
             node)
     return sequence
 
@@ -160,12 +152,10 @@ def _create_stop_tasks(node):
     sequence += \
         create_relationship_tasks(
             NORMATIVE_CONFIGURE_INTERFACE, NORMATIVE_REMOVE_TARGET,
-            Task.RUNS_ON_TARGET,
             node)
     sequence += \
         create_relationship_tasks(
             NORMATIVE_CONFIGURE_INTERFACE, NORMATIVE_TARGET_CHANGED,
-            Task.RUNS_ON_TARGET,
             node)
     sequence.append(
         create_node_task(

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/73c1d029/aria/orchestrator/workflows/core/task.py
----------------------------------------------------------------------
diff --git a/aria/orchestrator/workflows/core/task.py b/aria/orchestrator/workflows/core/task.py
index f23312d..19ce710 100644
--- a/aria/orchestrator/workflows/core/task.py
+++ b/aria/orchestrator/workflows/core/task.py
@@ -130,8 +130,7 @@ class OperationTask(BaseTask):
             retry_interval=api_task.retry_interval,
             ignore_failure=api_task.ignore_failure,
             plugin=plugin,
-            execution=self._workflow_context.execution,
-            runs_on=api_task.runs_on
+            execution=self._workflow_context.execution
         )
         self._workflow_context.model.task.put(task_model)
 

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/73c1d029/aria/orchestrator/workflows/exceptions.py
----------------------------------------------------------------------
diff --git a/aria/orchestrator/workflows/exceptions.py b/aria/orchestrator/workflows/exceptions.py
index 4fb8dd7..0ca263f 100644
--- a/aria/orchestrator/workflows/exceptions.py
+++ b/aria/orchestrator/workflows/exceptions.py
@@ -70,13 +70,19 @@ class TaskException(exceptions.AriaError):
     """
 
 
-class OperationNotFoundException(TaskException):
+class TaskCreationException(TaskException):
+    """
+    Could not create the task.
+    """
+
+
+class OperationNotFoundException(TaskCreationException):
     """
     Could not find an operation on the node or relationship.
     """
 
 
-class PluginNotFoundException(TaskException):
+class PluginNotFoundException(TaskCreationException):
     """
     Could not find a plugin matching the plugin specification.
     """

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/73c1d029/aria/parser/consumption/modeling.py
----------------------------------------------------------------------
diff --git a/aria/parser/consumption/modeling.py b/aria/parser/consumption/modeling.py
index 4847ba7..6c616b4 100644
--- a/aria/parser/consumption/modeling.py
+++ b/aria/parser/consumption/modeling.py
@@ -103,7 +103,7 @@ class InstantiateServiceInstance(Consumer):
     def consume(self):
         if self.context.modeling.template is None:
             self.context.validation.report('InstantiateServiceInstance consumer: missing service '
-                                           'model')
+                                           'template')
             return
 
         self.context.modeling.template.instantiate(None)
@@ -145,6 +145,24 @@ class ValidateCapabilities(Consumer):
         self.context.modeling.instance.validate_capabilities()
 
 
+class FindHosts(Consumer):
+    """
+    Find hosts for all nodes in the service instance.
+    """
+
+    def consume(self):
+        self.context.modeling.instance.find_hosts()
+
+
+class ConfigureOperations(Consumer):
+    """
+    Configures all operations in the service instance.
+    """
+
+    def consume(self):
+        self.context.modeling.instance.configure_operations()
+
+
 class ServiceInstance(ConsumerChain):
     """
     Generates the service instance by instantiating the service template.
@@ -158,6 +176,8 @@ class ServiceInstance(ConsumerChain):
                                                         SatisfyRequirements,
                                                         CoerceServiceInstanceValues,
                                                         ValidateCapabilities,
+                                                        FindHosts,
+                                                        ConfigureOperations,
                                                         CoerceServiceInstanceValues))
 
     def dump(self):

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/73c1d029/examples/tosca-simple-1.0/use-cases/multi-tier-1/custom_types/elasticsearch.yaml
----------------------------------------------------------------------
diff --git a/examples/tosca-simple-1.0/use-cases/multi-tier-1/custom_types/elasticsearch.yaml b/examples/tosca-simple-1.0/use-cases/multi-tier-1/custom_types/elasticsearch.yaml
index 32623d1..72b210a 100644
--- a/examples/tosca-simple-1.0/use-cases/multi-tier-1/custom_types/elasticsearch.yaml
+++ b/examples/tosca-simple-1.0/use-cases/multi-tier-1/custom_types/elasticsearch.yaml
@@ -4,3 +4,5 @@ node_types:
 
   tosca.nodes.SoftwareComponent.Elasticsearch:
     derived_from: tosca.nodes.SoftwareComponent
+    capabilities:
+      app: tosca.capabilities.Endpoint

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/73c1d029/examples/tosca-simple-1.0/use-cases/multi-tier-1/custom_types/kibana.yaml
----------------------------------------------------------------------
diff --git a/examples/tosca-simple-1.0/use-cases/multi-tier-1/custom_types/kibana.yaml b/examples/tosca-simple-1.0/use-cases/multi-tier-1/custom_types/kibana.yaml
index 7af00d0..4ee8700 100644
--- a/examples/tosca-simple-1.0/use-cases/multi-tier-1/custom_types/kibana.yaml
+++ b/examples/tosca-simple-1.0/use-cases/multi-tier-1/custom_types/kibana.yaml
@@ -8,3 +8,5 @@ node_types:
       - search_endpoint:
           capability: tosca.capabilities.Endpoint
           relationship: tosca.relationships.ConnectsTo
+    capabilities:
+      app: tosca.capabilities.Endpoint

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/73c1d029/examples/tosca-simple-1.0/use-cases/multi-tier-1/custom_types/logstash.yaml
----------------------------------------------------------------------
diff --git a/examples/tosca-simple-1.0/use-cases/multi-tier-1/custom_types/logstash.yaml b/examples/tosca-simple-1.0/use-cases/multi-tier-1/custom_types/logstash.yaml
index a3eebbe..ea74c7e 100644
--- a/examples/tosca-simple-1.0/use-cases/multi-tier-1/custom_types/logstash.yaml
+++ b/examples/tosca-simple-1.0/use-cases/multi-tier-1/custom_types/logstash.yaml
@@ -8,3 +8,5 @@ node_types:
       - search_endpoint:
           capability: tosca.capabilities.Endpoint
           relationship: tosca.relationships.ConnectsTo
+    capabilities:
+      app: tosca.capabilities.Endpoint

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/73c1d029/examples/tosca-simple-1.0/use-cases/webserver-dbms-2/custom_types/paypalpizzastore_nodejs_app.yaml
----------------------------------------------------------------------
diff --git a/examples/tosca-simple-1.0/use-cases/webserver-dbms-2/custom_types/paypalpizzastore_nodejs_app.yaml b/examples/tosca-simple-1.0/use-cases/webserver-dbms-2/custom_types/paypalpizzastore_nodejs_app.yaml
index 4723a3f..02bb399 100644
--- a/examples/tosca-simple-1.0/use-cases/webserver-dbms-2/custom_types/paypalpizzastore_nodejs_app.yaml
+++ b/examples/tosca-simple-1.0/use-cases/webserver-dbms-2/custom_types/paypalpizzastore_nodejs_app.yaml
@@ -9,7 +9,7 @@ node_types:
         type: string
     requirements:
       - database_connection:
-          capability: tosca.capabilities.Container
+          capability: tosca.capabilities.Node
 
   tosca.nodes.WebServer.Nodejs:
     derived_from: tosca.nodes.WebServer

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/73c1d029/examples/tosca-simple-1.0/use-cases/webserver-dbms-2/webserver-dbms-2.yaml
----------------------------------------------------------------------
diff --git a/examples/tosca-simple-1.0/use-cases/webserver-dbms-2/webserver-dbms-2.yaml b/examples/tosca-simple-1.0/use-cases/webserver-dbms-2/webserver-dbms-2.yaml
index 66eab8e..91f0b35 100644
--- a/examples/tosca-simple-1.0/use-cases/webserver-dbms-2/webserver-dbms-2.yaml
+++ b/examples/tosca-simple-1.0/use-cases/webserver-dbms-2/webserver-dbms-2.yaml
@@ -53,7 +53,7 @@ topology_template:
              implementation: scripts/nodejs/configure.sh
              inputs:
                github_url: { get_property: [ SELF, github_url ] }
-               mongodb_ip: { get_attribute: [mongo_server, private_address] }
+               mongodb_ip: { get_attribute: [ mongo_server, private_address ] }
            start: scripts/nodejs/start.sh
 
     nodejs:
@@ -86,7 +86,7 @@ topology_template:
           configure:
             implementation: mongodb/config.sh
             inputs:
-              mongodb_ip: { get_attribute: [mongo_server, private_address] }
+              mongodb_ip: { get_attribute: [ mongo_server, private_address ] }
           start: mongodb/start.sh
 
     mongo_server:
@@ -109,7 +109,7 @@ topology_template:
 
     nodejs_url:
       description: URL for the nodejs server, http://<IP>:3000
-      value: { get_attribute: [app_server, private_address] }
+      value: { get_attribute: [ app_server, private_address ] }
     mongodb_url:
       description: URL for the mongodb server.
       value: { get_attribute: [ mongo_server, private_address ] }

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/73c1d029/extensions/aria_extension_tosca/profiles/aria-1.0/aria-1.0.yaml
----------------------------------------------------------------------
diff --git a/extensions/aria_extension_tosca/profiles/aria-1.0/aria-1.0.yaml b/extensions/aria_extension_tosca/profiles/aria-1.0/aria-1.0.yaml
index 09cef57..43a193c 100644
--- a/extensions/aria_extension_tosca/profiles/aria-1.0/aria-1.0.yaml
+++ b/extensions/aria_extension_tosca/profiles/aria-1.0/aria-1.0.yaml
@@ -17,6 +17,8 @@ policy_types:
 
   aria.Plugin:
     _extensions:
+      shorthand_name: Plugin
+      type_qualified_name: aria:Plugin
       role: plugin
     description: >-
       Policy used to specify plugins used by services. For an operation to be able to use a plugin
@@ -32,6 +34,8 @@ policy_types:
 
   aria.Workflow:
     _extensions:
+      shorthand_name: Workflow
+      type_qualified_name: aria:Workflow
       role: workflow
     description: >-
       Policy used to specify custom workflows. A workflow is usually a workload of interconnected

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/73c1d029/extensions/aria_extension_tosca/profiles/tosca-simple-1.0/capabilities.yaml
----------------------------------------------------------------------
diff --git a/extensions/aria_extension_tosca/profiles/tosca-simple-1.0/capabilities.yaml b/extensions/aria_extension_tosca/profiles/tosca-simple-1.0/capabilities.yaml
index 72f6f0e..0b81a16 100644
--- a/extensions/aria_extension_tosca/profiles/tosca-simple-1.0/capabilities.yaml
+++ b/extensions/aria_extension_tosca/profiles/tosca-simple-1.0/capabilities.yaml
@@ -32,6 +32,7 @@ capability_types:
       specification: tosca-simple-1.0
       specification_section: 5.4.2
       specification_url: 'http://docs.oasis-open.org/tosca/TOSCA-Simple-Profile-YAML/v1.0/cos01/TOSCA-Simple-Profile-YAML-v1.0-cos01.html#DEFN_TYPE_CAPABILITIES_NODE'
+      role: feature
     description: >-
       The Node capability indicates the base capabilities of a TOSCA Node Type.
     derived_from: tosca.capabilities.Root
@@ -43,6 +44,7 @@ capability_types:
       specification: tosca-simple-1.0
       specification_section: 5.4.3
       specification_url: 'http://docs.oasis-open.org/tosca/TOSCA-Simple-Profile-YAML/v1.0/cos01/TOSCA-Simple-Profile-YAML-v1.0-cos01.html#DEFN_TYPE_CAPABILITIES_CONTAINER'
+      role: host
     description: >-
       The Container capability, when included on a Node Type or Template definition, indicates that the node can act as a container
       for (or a host for) one or more other declared Node Types.

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/73c1d029/extensions/aria_extension_tosca/profiles/tosca-simple-1.0/interfaces.yaml
----------------------------------------------------------------------
diff --git a/extensions/aria_extension_tosca/profiles/tosca-simple-1.0/interfaces.yaml b/extensions/aria_extension_tosca/profiles/tosca-simple-1.0/interfaces.yaml
index de1d34f..31c27b5 100644
--- a/extensions/aria_extension_tosca/profiles/tosca-simple-1.0/interfaces.yaml
+++ b/extensions/aria_extension_tosca/profiles/tosca-simple-1.0/interfaces.yaml
@@ -63,24 +63,48 @@ interface_types:
     pre_configure_source:
       description: >-
         Operation to pre-configure the source endpoint.
+      _extensions:
+        default_dependencies:
+          - edge > source
     pre_configure_target:
       description: >-
         Operation to pre-configure the target endpoint.
+      _extensions:
+        default_dependencies:
+          - edge > target
     post_configure_source:
       description: >-
         Operation to post-configure the source endpoint.
+      _extensions:
+        default_dependencies:
+          - edge > source
     post_configure_target:
       description: >-
         Operation to post-configure the target endpoint.
+      _extensions:
+        default_dependencies:
+          - edge > target
     add_target:
       description: >-
         Operation to notify the source node of a target node being added via a relationship.
+      _extensions:
+        default_dependencies:
+          - edge > source
     add_source:
       description: >-
         Operation to notify the target node of a source node which is now available via a relationship.
+      _extensions:
+        default_dependencies:
+          - edge > target
     target_changed:
       description: >-
         Operation to notify source some property or attribute of the target changed
+      _extensions:
+        default_dependencies:
+          - edge > source
     remove_target:
       description: >-
         Operation to remove a target node.
+      _extensions:
+        default_dependencies:
+          - edge > source

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/73c1d029/extensions/aria_extension_tosca/profiles/tosca-simple-1.0/nodes.yaml
----------------------------------------------------------------------
diff --git a/extensions/aria_extension_tosca/profiles/tosca-simple-1.0/nodes.yaml b/extensions/aria_extension_tosca/profiles/tosca-simple-1.0/nodes.yaml
index 414a388..bb33b6f 100644
--- a/extensions/aria_extension_tosca/profiles/tosca-simple-1.0/nodes.yaml
+++ b/extensions/aria_extension_tosca/profiles/tosca-simple-1.0/nodes.yaml
@@ -60,6 +60,7 @@ node_types:
       specification: tosca-simple-1.0
       specification_section: 5.8.2
       specification_url: 'http://docs.oasis-open.org/tosca/TOSCA-Simple-Profile-YAML/v1.0/cos01/TOSCA-Simple-Profile-YAML-v1.0-cos01.html#DEFN_TYPE_NODES_COMPUTE'
+      role: host
     description: >-
       The TOSCA Compute node represents one or more real or virtual processors of software applications or services along with
       other essential local resources. Collectively, the resources the compute node represents can logically be viewed as a (real

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/73c1d029/extensions/aria_extension_tosca/simple_v1_0/assignments.py
----------------------------------------------------------------------
diff --git a/extensions/aria_extension_tosca/simple_v1_0/assignments.py b/extensions/aria_extension_tosca/simple_v1_0/assignments.py
index 2a39ed9..6e36ba8 100644
--- a/extensions/aria_extension_tosca/simple_v1_0/assignments.py
+++ b/extensions/aria_extension_tosca/simple_v1_0/assignments.py
@@ -84,6 +84,25 @@ class OperationAssignment(ExtensiblePresentation):
         :rtype: dict of str, :class:`PropertyAssignment`
         """
 
+    @cachedmethod
+    def _get_extensions(self, context):
+        def update_inherited_extensions(extensions, interface_type):
+            parent = interface_type._get_parent(context)
+            if parent is not None:
+                update_inherited_extensions(extensions, parent)
+            operation_definition = interface_type.operations.get(self._name)
+            if operation_definition is not None:
+                if operation_definition._extensions:
+                    extensions.update(operation_definition._extensions)
+    
+        extensions = {}
+        update_inherited_extensions(extensions, self._container._get_type(context))
+        if self._container._extensions:
+            extensions.update(self._container._extensions)
+        if self._extensions:
+            extensions.update(self._extensions)
+        return extensions
+
 @allow_unknown_fields
 @has_fields
 @dsl_specification('3.5.14-2', 'tosca-simple-1.0')
@@ -247,15 +266,18 @@ class RequirementAssignment(ExtensiblePresentation):
 
     @cachedmethod
     def _get_node(self, context):
-        node_name = self.node
-        if node_name is not None:
-            node = context.presentation.get_from_dict('service_template', 'topology_template',
-                                                      'node_templates', node_name)
-            if node is not None:
-                return node, 'node_template'
-            node = context.presentation.get_from_dict('service_template', 'node_types', node_name)
-            if node is not None:
-                return node, 'node_type'
+        node = self.node
+
+        if node is not None:
+            node_template = context.presentation.get_from_dict('service_template',
+                                                               'topology_template',
+                                                               'node_templates', node)
+            if node_template is not None:
+                return node_template, 'node_template'
+            node_type = get_type_by_full_or_shorthand_name(context, node, 'node_types')
+            if node_type is not None:
+                return node_type, 'node_type'
+
         return None, None
 
     @cachedmethod
@@ -268,11 +290,10 @@ class RequirementAssignment(ExtensiblePresentation):
                 capabilities = node._get_capabilities(context)
                 if capability in capabilities:
                     return capabilities[capability], 'capability_assignment'
-            else:
-                capability_types = context.presentation.get_from_dict('service_template',
-                                                                      'capability_types')
-                if (capability_types is not None) and (capability in capability_types):
-                    return capability_types[capability], 'capability_type'
+            capability_type = get_type_by_full_or_shorthand_name(context, capability,
+                                                                 'capability_types')
+            if capability_type is not None:
+                return capability_type, 'capability_type'
 
         return None, None
 

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/73c1d029/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 d0a39e6..a54c57c 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))
@@ -347,20 +354,44 @@ 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)
-
-        dependencies = implementation.dependencies
-        if dependencies is not None:
-            model.dependencies = dependencies
+    if implementation is not None: 
+        primary = implementation.primary
+        plugin_name, model.implementation = split_prefix(primary)
+        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(
+                    'unknown plugin "%s" specified in operation implementation: %s'
+                    % (plugin_name, primary),
+                    locator=operation._get_child_locator('implementation', 'primary'),
+                    level=Issue.BETWEEN_TYPES)
+    
+        dependencies = []
+    
+        default_dependencies = operation._get_extensions(context).get('default_dependencies')
+        if default_dependencies:
+            dependencies += default_dependencies
+        if implementation.dependencies:
+            # Because these are after the defaults, they will override them
+            dependencies += implementation.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:
@@ -649,21 +680,18 @@ 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):
+    split = IMPLEMENTATION_PREFIX_REGEX.split(string, 2)
+    if len(split) < 2:
+        return None, string
+    return split[0].strip(), split[1].lstrip()
 
-    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))
 
-    implementation = implementation[index+1:].strip()
-    return plugin_specification, implementation
+def set_nested(the_dict, keys, 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)

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/73c1d029/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..7be7239 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/73c1d029/tests/modeling/test_models.py
----------------------------------------------------------------------
diff --git a/tests/modeling/test_models.py b/tests/modeling/test_models.py
index c3b98c1..2e117fa 100644
--- a/tests/modeling/test_models.py
+++ b/tests/modeling/test_models.py
@@ -583,48 +583,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],
@@ -633,23 +634,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='',
             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']['host_address'] = host_address
         if is_host:
             kwargs['host_fk'] = 1
         elif host_fk:

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/73c1d029/tests/orchestrator/context/test_operation.py
----------------------------------------------------------------------
diff --git a/tests/orchestrator/context/test_operation.py b/tests/orchestrator/context/test_operation.py
index 6721b29..89dd511 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/73c1d029/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/73c1d029/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