Repository: incubator-ariatosca
Updated Branches:
  refs/heads/ARIA-92-plugin-in-implementation-string e10d788c9 -> a6bfe803d


Support for operation 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/a6bfe803
Tree: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/tree/a6bfe803
Diff: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/diff/a6bfe803

Branch: refs/heads/ARIA-92-plugin-in-implementation-string
Commit: a6bfe803da64370319c8f86d99945dc8fe2dd2af
Parents: e10d788
Author: Tal Liron <[email protected]>
Authored: Wed Mar 29 20:23:24 2017 -0500
Committer: Tal Liron <[email protected]>
Committed: Wed Mar 29 20:23:24 2017 -0500

----------------------------------------------------------------------
 aria/modeling/orchestration.py                  |  27 ++---
 aria/modeling/service_instance.py               |  22 ++--
 aria/modeling/service_template.py               |  18 ++--
 aria/orchestrator/execution_plugin/__init__.py  |  44 ++++++++
 aria/orchestrator/workflows/api/task.py         | 102 +++++++++---------
 aria/orchestrator/workflows/core/task.py        |   3 +-
 .../profiles/tosca-simple-1.0/interfaces.yaml   |  24 +++--
 .../simple_v1_0/modeling/__init__.py            | 104 +++++++++----------
 .../node-cellar/node-cellar.yaml                |  14 ++-
 9 files changed, 203 insertions(+), 155 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a6bfe803/aria/modeling/orchestration.py
----------------------------------------------------------------------
diff --git a/aria/modeling/orchestration.py b/aria/modeling/orchestration.py
index 2d58671..e7118f9 100644
--- a/aria/modeling/orchestration.py
+++ b/aria/modeling/orchestration.py
@@ -208,7 +208,7 @@ class TaskBase(ModelMixin):
     __private_fields__ = ['node_fk',
                           'relationship_fk',
                           'plugin_fk',
-                          'execution_fk',
+                          'execution_fk'
                           'node_name',
                           'relationship_name',
                           'execution_name']
@@ -231,11 +231,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
@@ -270,17 +265,7 @@ class TaskBase(ModelMixin):
 
     # 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
+    configuration = Column(modeling_types.StrictDict(key_cls=basestring))
 
     @property
     def actor(self):
@@ -338,12 +323,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/a6bfe803/aria/modeling/service_instance.py
----------------------------------------------------------------------
diff --git a/aria/modeling/service_instance.py 
b/aria/modeling/service_instance.py
index d189213..8852887 100644
--- a/aria/modeling/service_instance.py
+++ b/aria/modeling/service_instance.py
@@ -326,8 +326,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'
@@ -541,7 +543,6 @@ class NodeBase(InstanceModelMixin): # pylint: 
disable=too-many-public-methods
             return None
 
         self.host = _find_host(self)
-        print self, self.host
 
     @property
     def as_raw(self):
@@ -982,7 +983,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`]
     """
 
@@ -1350,8 +1351,10 @@ class OperationBase(InstanceModelMixin):
     :vartype description: string
     :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
@@ -1386,8 +1389,9 @@ class OperationBase(InstanceModelMixin):
     def plugin_specification(cls):
         return relationship.one_to_one(cls, 'plugin_specification')
 
-    plugin_argument = Column(Text)
+    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
@@ -1450,12 +1454,14 @@ class OperationBase(InstanceModelMixin):
             if self.plugin_specification is not None:
                 console.puts('Plugin specification: {0}'.format(
                     context.style.literal(self.plugin_specification.name)))
-            if self.plugin_argument is not None:
-                console.puts('Plugin argument: {0}'.format(
-                    context.style.literal(self.plugin_argument)))
             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/a6bfe803/aria/modeling/service_template.py
----------------------------------------------------------------------
diff --git a/aria/modeling/service_template.py 
b/aria/modeling/service_template.py
index 4e29e87..2eb88c9 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
@@ -1500,8 +1502,8 @@ class OperationTemplateBase(TemplateModelMixin):
     def plugin_specification(cls):
         return relationship.one_to_one(cls, 'plugin_specification')
 
-    plugin_argument = Column(Text)
     implementation = Column(Text)
+    configuration = Column(modeling_types.StrictDict(key_cls=basestring))
     dependencies = Column(modeling_types.StrictList(item_cls=basestring))
 
     @declared_attr
@@ -1547,10 +1549,10 @@ class OperationTemplateBase(TemplateModelMixin):
         from . import models
         operation = models.Operation(name=self.name,
                                      
description=deepcopy_with_locators(self.description),
+                                     
plugin_specification=self.plugin_specification,
                                      implementation=self.implementation,
+                                     configuration=self.configuration,
                                      dependencies=self.dependencies,
-                                     
plugin_specification=self.plugin_specification,
-                                     plugin_argument=self.plugin_argument,
                                      executor=self.executor,
                                      max_retries=self.max_retries,
                                      retry_interval=self.retry_interval,
@@ -1573,12 +1575,14 @@ class OperationTemplateBase(TemplateModelMixin):
             if self.plugin_specification is not None:
                 console.puts('Plugin specification: {0}'.format(
                     context.style.literal(self.plugin_specification.name)))
-            if self.plugin_argument is not None:
-                console.puts('Plugin argument: {0}'.format(
-                    context.style.literal(self.plugin_argument)))
             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/a6bfe803/aria/orchestrator/execution_plugin/__init__.py
----------------------------------------------------------------------
diff --git a/aria/orchestrator/execution_plugin/__init__.py 
b/aria/orchestrator/execution_plugin/__init__.py
index 372022f..5561ddf 100644
--- a/aria/orchestrator/execution_plugin/__init__.py
+++ b/aria/orchestrator/execution_plugin/__init__.py
@@ -14,6 +14,8 @@
 # limitations under the License.
 
 from contextlib import contextmanager
+from ...modeling import models
+
 
 # Populated during execution of python scripts
 ctx = None
@@ -31,3 +33,45 @@ def python_script_scope(operation_ctx, operation_inputs):
     finally:
         ctx = None
         inputs = None
+
+
+def init_operation(operation_task):
+    from . import operations
+
+    inputs = {}
+    inputs['script_path'] = operation_task.implementation
+
+    host = None
+    if operation_task.actor_type == 'node':
+        host = operation_task.actor.host
+    elif operation_task.actor_type == 'relationship':
+        edge = operation_task.configuration.get('edge', 'source')
+        if edge == 'source':
+            host = operation_task.actor.source_node.host
+        elif edge == 'target':
+            host = operation_task.actor.target_node.host
+        else:
+            raise ValueError('"edge" configuration must be "source" or 
"target": {0}'.format(edge))
+
+    if host is None:
+        # Local operation
+        operation_task.implementation = '{0}.{1}'.format(operations.__name__,
+                                                         
operations.run_script_locally.__name__)
+    else:
+        # Remote SSH operation via Fabric
+        inputs['use_sudo'] = 
_to_bool(operation_task.configuration.get('use_sudo'))
+        inputs['hide_output'] = operation_task.configuration.get('hide_output')
+        inputs['fabric_env'] = operation_task.configuration.get('fabric_env')
+        # How to set up fabric env?
+        # How to set up host address?
+        operation_task.implementation = '{0}.{1}'.format(operations.__name__,
+                                                         
operations.run_script_with_ssh.__name__)
+
+    for k, v in inputs.iteritems():
+        operation_task.inputs[k] = models.Parameter.wrap(k, v)
+
+
+def _to_bool(value):
+    if value is None:
+        return None
+    return unicode(value).lower() == 'true'

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a6bfe803/aria/orchestrator/workflows/api/task.py
----------------------------------------------------------------------
diff --git a/aria/orchestrator/workflows/api/task.py 
b/aria/orchestrator/workflows/api/task.py
index af50120..902f1be 100644
--- a/aria/orchestrator/workflows/api/task.py
+++ b/aria/orchestrator/workflows/api/task.py
@@ -19,8 +19,9 @@ 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, merge)
 from ....utils.uuid import generate_uuid
+from ... import execution_plugin
 from ... import context
 from .. import exceptions
 
@@ -56,7 +57,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}'
@@ -66,29 +67,45 @@ class OperationTask(BaseTask):
                  actor_type,
                  interface_name,
                  operation_name,
-                 runs_on=None,
+                 inputs=None,
+                 configuration=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 is None) or (runs_on in models.Task.RUNS_ON)
         super(OperationTask, self).__init__()
 
         self.actor = actor
+        self.actor_type = actor_type
+
+        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.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.implementation = operation.implementation
+        self.configuration = {}
+
+        if operation.configuration:
+            merge(self.configuration, operation.configuration)
+        if configuration:
+            merge(self.configuration, operation.configuration)
 
         # Wrap inputs
         inputs = copy.deepcopy(inputs) if inputs else {}
@@ -96,112 +113,97 @@ 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.inputs = OperationTask._merge_inputs(operation.inputs, inputs)
 
         self.plugin = None
         if operation.plugin_specification:
             self.plugin = 
OperationTask._find_plugin(operation.plugin_specification)
             if self.plugin is None:
+                raise exceptions.OperationNotFoundException() ###
                 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))
-
-        # TODO: change runs_on to plugin_argument
-        if operation.plugin_argument is not None:
-            self.runs_on = operation.plugin_argument
+                    'Could not find plugin "{0}" of operation "{1}" on 
interface "{2}" for '
+                    '{3} "{4}"'
+                    .format(operation.plugin_specification.name, 
operation_name, interface_name,
+                            actor_type, actor.name))
         else:
-            # TODO: the execution plugin should choose the defaults
-            if actor_type == 'node':
-                self.runs_on = models.Task.RUNS_ON_NODE
-            else:
-                self.runs_on = models.Task.RUNS_ON_SOURCE
-
-        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)
+            # Default plugin (execution)
+            execution_plugin.init_operation(self)
 
     @classmethod
     def for_node(cls,
                  node,
                  interface_name,
                  operation_name,
+                 inputs=None,
+                 configuration=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 configuration: Override the operation's configuration
         :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)
         return cls(
             actor=node,
             actor_type='node',
             interface_name=interface_name,
             operation_name=operation_name,
+            inputs=inputs,
+            configuration=configuration,
             max_attempts=max_attempts,
             retry_interval=retry_interval,
-            ignore_failure=ignore_failure,
-            inputs=inputs)
+            ignore_failure=ignore_failure)
 
     @classmethod
     def for_relationship(cls,
                          relationship,
                          interface_name,
                          operation_name,
-                         runs_on=None,
+                         inputs=None,
+                         configuration=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 configuration: Override the operation's configuration
         :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)
         return cls(
             actor=relationship,
             actor_type='relationship',
             interface_name=interface_name,
             operation_name=operation_name,
-            runs_on=runs_on,
+            inputs=inputs,
+            configuration=configuration,
             max_attempts=max_attempts,
             retry_interval=retry_interval,
-            ignore_failure=ignore_failure,
-            inputs=inputs)
+            ignore_failure=ignore_failure)
 
     def _get_operation(self, interface_name, operation_name):
         interface = self.actor.interfaces.get(interface_name)

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a6bfe803/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/a6bfe803/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 9eab339..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
@@ -64,39 +64,47 @@ interface_types:
       description: >-
         Operation to pre-configure the source endpoint.
       _extensions:
-        default_prefix: :source
+        default_dependencies:
+          - edge > source
     pre_configure_target:
       description: >-
         Operation to pre-configure the target endpoint.
       _extensions:
-        default_prefix: :target
+        default_dependencies:
+          - edge > target
     post_configure_source:
       description: >-
         Operation to post-configure the source endpoint.
       _extensions:
-        default_prefix: :source
+        default_dependencies:
+          - edge > source
     post_configure_target:
       description: >-
         Operation to post-configure the target endpoint.
       _extensions:
-        default_prefix: :target
+        default_dependencies:
+          - edge > target
     add_target:
       description: >-
         Operation to notify the source node of a target node being added via a 
relationship.
       _extensions:
-        default_prefix: :source
+        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_prefix: :target
+        default_dependencies:
+          - edge > target
     target_changed:
       description: >-
         Operation to notify source some property or attribute of the target 
changed
       _extensions:
-        default_prefix: :source
+        default_dependencies:
+          - edge > source
     remove_target:
       description: >-
         Operation to remove a target node.
       _extensions:
-        default_prefix: :source
+        default_dependencies:
+          - edge > source

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a6bfe803/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 e00bbc3..45c26eb 100644
--- a/extensions/aria_extension_tosca/simple_v1_0/modeling/__init__.py
+++ b/extensions/aria_extension_tosca/simple_v1_0/modeling/__init__.py
@@ -24,6 +24,7 @@ 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,
@@ -33,10 +34,9 @@ from aria.modeling.models import (Type, ServiceTemplate, 
NodeTemplate,
 from ..data_types import coerce_value
 
 
-# These match the first un-escaped ">" and ":"
+# These match the first un-escaped ">"
 # See: http://stackoverflow.com/a/11819111/849021
 IMPLEMENTATION_PREFIX_REGEX = re.compile(r'(?<!\\)(?:\\\\)*>')
-PLUGIN_ARGUMENT_REGEX = re.compile(r'(?<!\\)(?:\\\\)*:')
 
 
 def create_service_template_model(context): # pylint: 
disable=too-many-locals,too-many-branches
@@ -354,25 +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, plugin_argument, model.implementation = \
-            parse_implementation_string(context,
-                                        service_template,
-                                        operation)
-
-        if plugin_argument is not None:
-            model.plugin_argument = plugin_argument
-
-        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:
@@ -661,47 +680,18 @@ def create_constraint_clause_lambda(context, node_filter, 
constraint_clause, pro
     return None
 
 
-def parse_implementation_string(context, service_template, operation):
-    primary = operation.implementation.primary
-    if not primary:
-        return None, 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()
 
-    prefix_split = IMPLEMENTATION_PREFIX_REGEX.split(primary, 2)
-    if len(prefix_split) < 2:
-        extensions = operation._get_extensions(context)
-        default_prefix = extensions.get('default_prefix')
-        if default_prefix is None:
-            # No prefix
-            return None, None, primary
-        else:
-            # Use default prefix
-            prefix = default_prefix
-            implementation = primary
-    else:
-        # Get prefix from string
-        prefix, implementation = prefix_split
-        implementation = implementation.lstrip()
-
-    argument_split = PLUGIN_ARGUMENT_REGEX.split(prefix, 2)
-    if len(argument_split) < 2:
-        # No plugin argument    
-        name = prefix.strip()
-        argument = None
+
+def set_nested(the_dict, keys, value):
+    key = keys.pop(0)
+    if len(keys) == 0:
+        the_dict[key] = value
     else:
-        # Plugin argument
-        name, argument = argument_split
-        name = name.strip()
-        argument = argument.strip()
-    
-    if name:
-        plugin_specification = service_template.plugin_specifications.get(name)
-        if plugin_specification is None:
-            context.validation.report(
-                'unknown plugin "%s" specified in operation implementation: %s'
-                % (name, primary),
-                locator=operation._get_child_locator('implementation', 
'primary'),
-                level=Issue.BETWEEN_TYPES)
-    else:            
-        plugin_specification = None
-
-    return plugin_specification, argument, implementation
+        if key not in the_dict:
+            the_dict[key] = StrictDict(key_class=basestring, 
value_class=basestring)
+        set_nested(the_dict[key], keys, value)

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a6bfe803/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 b271bf2..3cffc0b 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
@@ -158,7 +158,13 @@ topology_template:
             relationship:
               interfaces:
                 Configure:
-                  target_changed: :target > mongodb/host_changed.sh
+                  target_changed:
+                    implementation:
+                      primary: mongodb/host_changed.sh
+                      dependencies:
+                        - runs_on > target
+                        - ssh.user > admin
+                        - ssh.password > 12345
 
     nginx:
       type: nginx.Nginx
@@ -188,7 +194,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

Reply via email to