You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ariatosca.apache.org by em...@apache.org on 2017/08/02 20:58:24 UTC

[2/3] incubator-ariatosca git commit: ARIA-324 Refactor ctx proxy access

ARIA-324 Refactor ctx proxy access

Our previous use of "." to delimit nested dict keys was wrong (keys
could have a ".") and inflexible. The new implementation uses subsequent
args to move into the dict. The same format can now be used to access
object attributes.

This commit also changes how to support setting values: we must now use
"=" as the penultimate argument with the new value following.

Also fixed: callables will now "grab" the number of args they need
instead of all remaining args, making it possible to do further
inspection on the returned value from the callable. To allow for this,
kwargs are now expected as the first arg rather than the last.

Relatedly, this commit instruments all parameter fields from all models
and fixes related bugs in the instrumentation implementation.

Furthmore, this commit fixes a bad null check in the ctx client, and
also allows it to retrieve Unicode data.


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

Branch: refs/heads/ARIA-324-refactor-ctx-access
Commit: 12516271e9aa497ecd6e61d1087e08677066d8dd
Parents: 51e4ed0
Author: Tal Liron <ta...@gmail.com>
Authored: Thu Jul 27 17:58:17 2017 -0500
Committer: Tal Liron <ta...@gmail.com>
Committed: Wed Aug 2 15:58:09 2017 -0500

----------------------------------------------------------------------
 aria/orchestrator/context/common.py             |  19 ++-
 aria/orchestrator/context/operation.py          |  46 +++--
 .../execution_plugin/ctx_proxy/client.py        |   7 +-
 .../execution_plugin/ctx_proxy/server.py        | 169 ++++++++++---------
 aria/orchestrator/workflows/api/task.py         |  13 +-
 aria/storage/collection_instrumentation.py      |  88 +++++-----
 aria/storage/core.py                            |   6 +-
 examples/hello-world/scripts/configure.sh       |   6 +-
 examples/hello-world/scripts/start.sh           |  13 +-
 examples/hello-world/scripts/stop.sh            |   4 +-
 tests/end2end/testenv.py                        |  24 ++-
 .../execution_plugin/test_ctx_proxy_server.py   | 111 +++---------
 .../orchestrator/execution_plugin/test_local.py |  34 ++--
 tests/resources/scripts/test_ssh.sh             |  30 ++--
 14 files changed, 274 insertions(+), 296 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/12516271/aria/orchestrator/context/common.py
----------------------------------------------------------------------
diff --git a/aria/orchestrator/context/common.py b/aria/orchestrator/context/common.py
index f400142..3c5f618 100644
--- a/aria/orchestrator/context/common.py
+++ b/aria/orchestrator/context/common.py
@@ -38,10 +38,27 @@ class BaseContext(object):
     """
 
     INSTRUMENTATION_FIELDS = (
+        modeling.models.Service.inputs,
+        modeling.models.ServiceTemplate.inputs,
+        modeling.models.Policy.properties,
+        modeling.models.PolicyTemplate.properties,
         modeling.models.Node.attributes,
         modeling.models.Node.properties,
         modeling.models.NodeTemplate.attributes,
-        modeling.models.NodeTemplate.properties
+        modeling.models.NodeTemplate.properties,
+        modeling.models.Group.properties,
+        modeling.models.GroupTemplate.properties,
+        modeling.models.Capability.properties,
+        # TODO ARIA-279: modeling.models.Capability.attributes,
+        modeling.models.CapabilityTemplate.properties,
+        # TODO ARIA-279: modeling.models.CapabilityTemplate.attributes
+        modeling.models.Relationship.properties,
+        modeling.models.Artifact.properties,
+        modeling.models.ArtifactTemplate.properties,
+        modeling.models.Interface.inputs,
+        modeling.models.InterfaceTemplate.inputs,
+        modeling.models.Operation.inputs,
+        modeling.models.OperationTemplate.inputs
     )
 
     class PrefixedLogger(object):

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/12516271/aria/orchestrator/context/operation.py
----------------------------------------------------------------------
diff --git a/aria/orchestrator/context/operation.py b/aria/orchestrator/context/operation.py
index 7d5f40c..8613ec3 100644
--- a/aria/orchestrator/context/operation.py
+++ b/aria/orchestrator/context/operation.py
@@ -48,8 +48,7 @@ class BaseOperationContext(common.BaseContext):
     @property
     def task(self):
         """
-        The task in the model storage
-        :return: Task model
+        The task in the model storage.
         """
         # SQLAlchemy prevents from accessing an object which was created on a different thread.
         # So we retrieve the object from the storage if the current thread isn't the same as the
@@ -62,7 +61,7 @@ class BaseOperationContext(common.BaseContext):
     @property
     def plugin_workdir(self):
         """
-        A work directory that is unique to the plugin and the deployment id
+        A work directory that is unique to the plugin and the service ID.
         """
         if self.task.plugin is None:
             return None
@@ -120,20 +119,18 @@ class NodeOperationContext(BaseOperationContext):
     """
 
     @property
-    def node_template(self):
+    def node(self):
         """
-        the node of the current operation
-        :return:
+        The node of the current operation.
         """
-        return self.node.node_template
+        return self.model.node.get(self._actor_id)
 
     @property
-    def node(self):
+    def node_template(self):
         """
-        The node instance of the current operation
-        :return:
+        The node template of the current operation.
         """
-        return self.model.node.get(self._actor_id)
+        return self.node.node_template
 
 
 class RelationshipOperationContext(BaseOperationContext):
@@ -142,41 +139,36 @@ class RelationshipOperationContext(BaseOperationContext):
     """
 
     @property
-    def source_node_template(self):
+    def relationship(self):
         """
-        The source node
-        :return:
+        The relationship instance of the current operation.
         """
-        return self.source_node.node_template
+        return self.model.relationship.get(self._actor_id)
 
     @property
     def source_node(self):
         """
-        The source node instance
-        :return:
+        The relationship source node.
         """
         return self.relationship.source_node
 
     @property
-    def target_node_template(self):
+    def source_node_template(self):
         """
-        The target node
-        :return:
+        The relationship source node template.
         """
-        return self.target_node.node_template
+        return self.source_node.node_template
 
     @property
     def target_node(self):
         """
-        The target node instance
-        :return:
+        The relationship target node.
         """
         return self.relationship.target_node
 
     @property
-    def relationship(self):
+    def target_node_template(self):
         """
-        The relationship instance of the current operation
-        :return:
+        The relationship target node template.
         """
-        return self.model.relationship.get(self._actor_id)
+        return self.target_node.node_template

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/12516271/aria/orchestrator/execution_plugin/ctx_proxy/client.py
----------------------------------------------------------------------
diff --git a/aria/orchestrator/execution_plugin/ctx_proxy/client.py b/aria/orchestrator/execution_plugin/ctx_proxy/client.py
index 1310c21..84d66f1 100644
--- a/aria/orchestrator/execution_plugin/ctx_proxy/client.py
+++ b/aria/orchestrator/execution_plugin/ctx_proxy/client.py
@@ -102,9 +102,12 @@ def main(args=None):
     if args.json_output:
         response = json.dumps(response)
     else:
-        if not response:
+        if response is None:
             response = ''
-        response = str(response)
+        try:
+            response = str(response)
+        except UnicodeEncodeError:
+            response = unicode(response).encode('utf8')
     sys.stdout.write(response)
 
 if __name__ == '__main__':

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/12516271/aria/orchestrator/execution_plugin/ctx_proxy/server.py
----------------------------------------------------------------------
diff --git a/aria/orchestrator/execution_plugin/ctx_proxy/server.py b/aria/orchestrator/execution_plugin/ctx_proxy/server.py
index ca910e0..7410fd4 100644
--- a/aria/orchestrator/execution_plugin/ctx_proxy/server.py
+++ b/aria/orchestrator/execution_plugin/ctx_proxy/server.py
@@ -17,14 +17,14 @@
 ``ctx`` proxy server implementation.
 """
 
-import collections
 import json
-import re
 import socket
 import threading
+import functools
 import traceback
 import Queue
 import StringIO
+import inspect
 import wsgiref.simple_server
 
 import bottle
@@ -150,43 +150,101 @@ class CtxProxy(object):
         self.close()
 
 
-def _process_ctx_request(ctx, args):
+def _process_ctx_request(ctx, args): # pylint: disable=too-many-branches,too-many-statements
     current = ctx
-    num_args = len(args)
     index = 0
+
+    try:
+        # TODO: should there be a way to escape "=" in case it is needed as real argument?
+        equals_index = args.index('=')
+        if equals_index == 0:
+            raise RuntimeError('The "=" argument cannot be first')
+        if equals_index != len(args) - 2:
+            raise RuntimeError('The "=" argument must be penultimate')
+        modifying = True
+        modifying_key = args[-3]
+        modifying_value = args[-1]
+        args = args[:-3]
+    except ValueError:
+        modifying = False
+        modifying_key = None
+        modifying_value = None
+
+    num_args = len(args)
+
     while index < num_args:
         arg = args[index]
+
+        # Object attribute
         attr = _desugar_attr(current, arg)
-        if attr:
+        if attr is not None:
             current = getattr(current, attr)
-        elif isinstance(current, collections.MutableMapping):
-            key = arg
-            path_dict = _PathDictAccess(current)
-            if index + 1 == num_args:
-                # read dict prop by path
-                value = path_dict.get(key)
-                current = value
-            elif index + 2 == num_args:
-                # set dict prop by path
-                value = args[index + 1]
-                path_dict.set(key, value)
-                current = None
-            else:
-                raise RuntimeError('Illegal argument while accessing dict')
-            break
+
+        # List entry
+        elif isinstance(current, list) and _is_int(arg):
+            current = current[int(arg)]
+
+        # Dict (or dict-like object) value
+        elif hasattr(current, '__getitem__'):
+            if modifying and (not arg in current):
+                current[arg] = {}
+            current = current[arg]
+
+        # Call
         elif callable(current):
-            kwargs = {}
-            remaining_args = args[index:]
-            if isinstance(remaining_args[-1], collections.MutableMapping):
-                kwargs = remaining_args[-1]
-                remaining_args = remaining_args[:-1]
-            current = current(*remaining_args, **kwargs)
-            break
+            if isinstance(current, functools.partial):
+                argspec = inspect.getargspec(current.func)
+                # Don't count initial args fulfilled by the partial
+                spec_args = argspec.args[len(current.args):]
+                # Don't count keyword args fulfilled by the partial
+                spec_args = tuple(a for a in spec_args if a not in current.keywords)
+            else:
+                argspec = inspect.getargspec(current)
+                spec_args = argspec.args
+
+            callable_kwargs = {}
+            if isinstance(arg, dict):
+                # If the first arg is a dict, treat it as our kwargs
+                # TODO: what if the first argument really needs to be a dict?
+                callable_kwargs = arg
+                index += 1
+
+            if argspec.varargs is not None:
+                # Gobble the rest of the args
+                callable_args = args[index:]
+            else:
+                # Take only what we need
+                spec_args = tuple(a for a in spec_args if a not in callable_kwargs)
+                spec_args_count = len(spec_args)
+                if inspect.ismethod(current):
+                    # Don't count "self" argument
+                    spec_args_count -= 1
+                callable_args = args[index:index + spec_args_count]
+                # Note: we might actually have fewer args than the args_count, but that could be OK
+                # if the user expects subsequent args to have default values
+
+            args_count = len(callable_args)
+            if args_count > 1:
+                index += args_count - 1
+
+            current = current(*callable_args, **callable_kwargs)
+
         else:
-            raise RuntimeError('{0} cannot be processed in {1}'.format(arg, args))
+            raise RuntimeError('`{0}` cannot be processed in {1}'.format(arg, args))
+
         index += 1
+
     if callable(current):
         current = current()
+
+    if modifying:
+        if hasattr(current, '__setitem__'):
+            # Modify dict value
+            current[modifying_key] = modifying_value
+        else:
+            # Modify object attribute
+            setattr(current, modifying_key, modifying_value)
+
     return current
 
 
@@ -201,55 +259,12 @@ def _desugar_attr(obj, attr):
     return None
 
 
-class _PathDictAccess(object):
-    pattern = re.compile(r"(.+)\[(\d+)\]")
-
-    def __init__(self, obj):
-        self.obj = obj
-
-    def set(self, prop_path, value):
-        obj, prop_name = self._get_parent_obj_prop_name_by_path(prop_path)
-        obj[prop_name] = value
-
-    def get(self, prop_path):
-        value = self._get_object_by_path(prop_path)
-        return value
-
-    def _get_object_by_path(self, prop_path, fail_on_missing=True):
-        # when setting a nested object, make sure to also set all the
-        # intermediate path objects
-        current = self.obj
-        for prop_segment in prop_path.split('.'):
-            match = self.pattern.match(prop_segment)
-            if match:
-                index = int(match.group(2))
-                property_name = match.group(1)
-                if property_name not in current:
-                    self._raise_illegal(prop_path)
-                if not isinstance(current[property_name], list):
-                    self._raise_illegal(prop_path)
-                current = current[property_name][index]
-            else:
-                if prop_segment not in current:
-                    if fail_on_missing:
-                        self._raise_illegal(prop_path)
-                    else:
-                        current[prop_segment] = {}
-                current = current[prop_segment]
-        return current
-
-    def _get_parent_obj_prop_name_by_path(self, prop_path):
-        split = prop_path.split('.')
-        if len(split) == 1:
-            return self.obj, prop_path
-        parent_path = '.'.join(split[:-1])
-        parent_obj = self._get_object_by_path(parent_path, fail_on_missing=False)
-        prop_name = split[-1]
-        return parent_obj, prop_name
-
-    @staticmethod
-    def _raise_illegal(prop_path):
-        raise RuntimeError('illegal path: {0}'.format(prop_path))
+def _is_int(arg):
+    try:
+        int(arg)
+    except ValueError:
+        return False
+    return True
 
 
 def _get_unused_port():

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/12516271/aria/orchestrator/workflows/api/task.py
----------------------------------------------------------------------
diff --git a/aria/orchestrator/workflows/api/task.py b/aria/orchestrator/workflows/api/task.py
index 4c518fc..ec96d27 100644
--- a/aria/orchestrator/workflows/api/task.py
+++ b/aria/orchestrator/workflows/api/task.py
@@ -140,13 +140,18 @@ class OperationTask(BaseTask):
         self.arguments = modeling_utils.merge_parameter_values(arguments,
                                                                operation.arguments,
                                                                model_cls=models.Argument)
-        if getattr(self.actor, 'outbound_relationships', None) is not None:
+
+        actor = self.actor
+        if hasattr(actor, '_wrapped'):
+            # Unwrap instrumented model
+            actor = actor._wrapped
+        if isinstance(actor, models.Node):
             self._context_cls = context.operation.NodeOperationContext
-        elif getattr(self.actor, 'source_node', None) is not None:
+        elif isinstance(actor, models.Relationship):
             self._context_cls = context.operation.RelationshipOperationContext
         else:
-            raise exceptions.TaskCreationException('Could not locate valid context for '
-                                                   '{actor.__class__}'.format(actor=self.actor))
+            raise exceptions.TaskCreationException('Could not create valid context for '
+                                                   '{actor.__class__}'.format(actor=actor))
 
     def __repr__(self):
         return self.name

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/12516271/aria/storage/collection_instrumentation.py
----------------------------------------------------------------------
diff --git a/aria/storage/collection_instrumentation.py b/aria/storage/collection_instrumentation.py
index c90cb18..e04865e 100644
--- a/aria/storage/collection_instrumentation.py
+++ b/aria/storage/collection_instrumentation.py
@@ -146,14 +146,14 @@ class _InstrumentedDict(_InstrumentedCollection, dict):
     def _load(self, dict_=None, **kwargs):
         dict.__init__(
             self,
-            tuple((key, self._raw_value(value)) for key, value in (dict_ or {}).items()),
+            tuple((key, self._raw_value(value)) for key, value in (dict_ or {}).iteritems()),
             **kwargs)
 
     def update(self, dict_=None, **kwargs):
         dict_ = dict_ or {}
-        for key, value in dict_.items():
+        for key, value in dict_.iteritems():
             self[key] = value
-        for key, value in kwargs.items():
+        for key, value in kwargs.iteritems():
             self[key] = value
 
     def __getitem__(self, key):
@@ -202,9 +202,31 @@ class _InstrumentedList(_InstrumentedCollection, list):
 
 class _WrappedBase(object):
 
-    def __init__(self, wrapped, instrumentation):
+    def __init__(self, wrapped, instrumentation, instrumentation_kwargs=None):
+        """
+        :param wrapped: model to be instrumented
+        :param instrumentation: instrumentation dict
+        :param instrumentation_kwargs: arguments for instrumentation class
+        """
         self._wrapped = wrapped
         self._instrumentation = instrumentation
+        self._instrumentation_kwargs = instrumentation_kwargs or {}
+
+    def _wrap(self, value):
+        if value.__class__ in set(class_.class_ for class_ in self._instrumentation):
+            return _create_instrumented_model(
+                value, instrumentation=self._instrumentation, **self._instrumentation_kwargs)
+        # Check that the value is a SQLAlchemy model (it should have metadata) or a collection
+        elif hasattr(value, 'metadata') or isinstance(value, (dict, list)):
+            return _create_wrapped_model(
+                value, instrumentation=self._instrumentation, **self._instrumentation_kwargs)
+        return value
+
+    def __getattr__(self, item):
+        if hasattr(self, '_wrapped'):
+            return self._wrap(getattr(self._wrapped, item))
+        else:
+            super(_WrappedBase, self).__getattribute__(item)
 
 
 class _InstrumentedModel(_WrappedBase):
@@ -213,32 +235,32 @@ class _InstrumentedModel(_WrappedBase):
         """
         The original model.
 
-        :param wrapped: model to be instrumented
         :param mapi: MAPI for the wrapped model
+        :param wrapped: model to be instrumented
+        :param instrumentation: instrumentation dict
+        :param instrumentation_kwargs: arguments for instrumentation class
         """
-        super(_InstrumentedModel, self).__init__(*args, **kwargs)
+        super(_InstrumentedModel, self).__init__(instrumentation_kwargs=dict(mapi=mapi),
+                                                 *args, **kwargs)
         self._mapi = mapi
         self._apply_instrumentation()
 
-    def __getattr__(self, item):
-        return_value = getattr(self._wrapped, item)
-        if isinstance(return_value, self._wrapped.__class__):
-            return _create_instrumented_model(return_value, self._mapi, self._instrumentation)
-        if isinstance(return_value, (list, dict)):
-            return _create_wrapped_model(return_value, self._mapi, self._instrumentation)
-        return return_value
-
     def _apply_instrumentation(self):
         for field in self._instrumentation:
+            if field.parent.class_ != type(self._wrapped):
+                # Only apply to fields of our class
+                continue
+
             field_name = field.key
             field_cls = field.mapper.class_
+
             field = getattr(self._wrapped, field_name)
 
-            # Preserve the original value. e.g. original attributes would be located under
-            # _attributes
+            # Preserve the original field, e.g. original "attributes" would be located under
+            # "_attributes"
             setattr(self, '_{0}'.format(field_name), field)
 
-            # set instrumented value
+            # Set instrumented value
             if isinstance(field, dict):
                 instrumentation_cls = _InstrumentedDict
             elif isinstance(field, list):
@@ -247,7 +269,7 @@ class _InstrumentedModel(_WrappedBase):
                 # TODO: raise proper error
                 raise exceptions.StorageError(
                     "ARIA supports instrumentation for dict and list. Field {field} of the "
-                    "class {model} is of {type} type.".format(
+                    "class `{model}` is of type `{type}`.".format(
                         field=field,
                         model=self._wrapped,
                         type=type(field)))
@@ -262,35 +284,13 @@ class _InstrumentedModel(_WrappedBase):
 
 class _WrappedModel(_WrappedBase):
 
-    def __init__(self, instrumentation_kwargs, *args, **kwargs):
-        """
-        :param instrumented_cls: class to be instrumented
-        :param instrumentation_cls: instrumentation cls
-        :param wrapped: currently wrapped instance
-        :param kwargs: passed to the instrumented class
-        """
-        super(_WrappedModel, self).__init__(*args, **kwargs)
-        self._kwargs = instrumentation_kwargs
-
-    def _wrap(self, value):
-        if value.__class__ in (class_.class_ for class_ in self._instrumentation):
-            return _create_instrumented_model(
-                value, instrumentation=self._instrumentation, **self._kwargs)
-        elif hasattr(value, 'metadata') or isinstance(value, (dict, list)):
-            # Basically checks that the value is indeed an sqlmodel (it should have metadata)
-            return _create_wrapped_model(
-                value, instrumentation=self._instrumentation, **self._kwargs)
-        return value
-
-    def __getattr__(self, item):
-        if hasattr(self, '_wrapped'):
-            return self._wrap(getattr(self._wrapped, item))
-        else:
-            super(_WrappedModel, self).__getattribute__(item)
-
     def __getitem__(self, item):
         return self._wrap(self._wrapped[item])
 
+    def __iter__(self):
+        for item in self._wrapped.__iter__():
+            yield self._wrap(item)
+
 
 def _create_instrumented_model(original_model, mapi, instrumentation):
     return type('Instrumented{0}'.format(original_model.__class__.__name__),

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/12516271/aria/storage/core.py
----------------------------------------------------------------------
diff --git a/aria/storage/core.py b/aria/storage/core.py
index 2a5745e..7e9b201 100644
--- a/aria/storage/core.py
+++ b/aria/storage/core.py
@@ -130,7 +130,7 @@ class ModelStorage(Storage):
         """
         model_name = model_cls.__modelname__
         if model_name in self.registered:
-            self.logger.debug('{name} in already storage {self!r}'.format(name=model_name,
+            self.logger.debug('{name} already in storage {self!r}'.format(name=model_name,
                                                                           self=self))
             return
         self.registered[model_name] = self.api(name=model_name,
@@ -151,10 +151,10 @@ class ModelStorage(Storage):
         original_instrumentation = {}
 
         try:
-            for mapi in self.registered.values():
+            for mapi in self.registered.itervalues():
                 original_instrumentation[mapi] = copy.copy(mapi._instrumentation)
                 mapi._instrumentation.extend(instrumentation)
             yield self
         finally:
-            for mapi in self.registered.values():
+            for mapi in self.registered.itervalues():
                 mapi._instrumentation[:] = original_instrumentation[mapi]

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/12516271/examples/hello-world/scripts/configure.sh
----------------------------------------------------------------------
diff --git a/examples/hello-world/scripts/configure.sh b/examples/hello-world/scripts/configure.sh
index 9ac26d5..cc26b52 100755
--- a/examples/hello-world/scripts/configure.sh
+++ b/examples/hello-world/scripts/configure.sh
@@ -19,10 +19,10 @@ set -e
 TEMP_DIR="/tmp"
 PYTHON_FILE_SERVER_ROOT=${TEMP_DIR}/python-simple-http-webserver
 if [ -d ${PYTHON_FILE_SERVER_ROOT} ]; then
-	echo "Removing file server root folder ${PYTHON_FILE_SERVER_ROOT}"
+	echo "Removing old web server root folder: ${PYTHON_FILE_SERVER_ROOT}."
 	rm -rf ${PYTHON_FILE_SERVER_ROOT}
 fi
-ctx logger info "Creating HTTP server root directory at ${PYTHON_FILE_SERVER_ROOT}"
+ctx logger info "Creating web server root folder: ${PYTHON_FILE_SERVER_ROOT}."
 
 mkdir -p ${PYTHON_FILE_SERVER_ROOT}
 
@@ -31,7 +31,7 @@ cd ${PYTHON_FILE_SERVER_ROOT}
 index_path="index.html"
 image_path="images/aria-logo.png"
 
-ctx logger info "Downloading blueprint resources..."
+ctx logger info "Downloading service template resources..."
 ctx download-resource-and-render ${PYTHON_FILE_SERVER_ROOT}/index.html ${index_path}
 ctx download-resource ${PYTHON_FILE_SERVER_ROOT}/aria-logo.png ${image_path}
 

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/12516271/examples/hello-world/scripts/start.sh
----------------------------------------------------------------------
diff --git a/examples/hello-world/scripts/start.sh b/examples/hello-world/scripts/start.sh
index 010af2c..6be8f91 100755
--- a/examples/hello-world/scripts/start.sh
+++ b/examples/hello-world/scripts/start.sh
@@ -20,16 +20,15 @@ TEMP_DIR="/tmp"
 PYTHON_FILE_SERVER_ROOT=${TEMP_DIR}/python-simple-http-webserver
 PID_FILE="server.pid"
 
-ctx logger info "Starting HTTP server from ${PYTHON_FILE_SERVER_ROOT}"
+ctx logger info "Starting web server at: ${PYTHON_FILE_SERVER_ROOT}."
 
 port=$(ctx node properties port)
 
 cd ${PYTHON_FILE_SERVER_ROOT}
-ctx logger info "Starting SimpleHTTPServer"
 nohup python -m SimpleHTTPServer ${port} > /dev/null 2>&1 &
 echo $! > ${PID_FILE}
 
-ctx logger info "Waiting for server to launch on port ${port}"
+ctx logger info "Waiting for web server to launch on port ${port}..."
 url="http://localhost:${port}"
 
 server_is_up() {
@@ -42,7 +41,7 @@ server_is_up() {
 			return 0
 		fi
 	else
-		ctx logger error "Both curl, wget were not found in path"
+		ctx logger error "Both curl and wget were not found in path."
 		exit 1
 	fi
 	return 1
@@ -52,15 +51,15 @@ STARTED=false
 for i in $(seq 1 15)
 do
 	if server_is_up; then
-		ctx logger info "Server is up."
+		ctx logger info "Web server is up."
 		STARTED=true
     	break
 	else
-		ctx logger info "Server not up. waiting 1 second."
+		ctx logger info "Web server not up. waiting 1 second."
 		sleep 1
 	fi
 done
 if [ ${STARTED} = false ]; then
-	ctx logger error "Failed starting web server in 15 seconds."
+	ctx logger error "Web server did not start within 15 seconds."
 	exit 1
 fi

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/12516271/examples/hello-world/scripts/stop.sh
----------------------------------------------------------------------
diff --git a/examples/hello-world/scripts/stop.sh b/examples/hello-world/scripts/stop.sh
index e393dbf..5fc347e 100755
--- a/examples/hello-world/scripts/stop.sh
+++ b/examples/hello-world/scripts/stop.sh
@@ -22,8 +22,8 @@ PID_FILE="server.pid"
 
 PID=`cat ${PYTHON_FILE_SERVER_ROOT}/${PID_FILE}`
 
-ctx logger info "Shutting down file server. pid = ${PID}"
+ctx logger info "Shutting down web server, pid = ${PID}."
 kill -9 ${PID} || exit $?
 
-ctx logger info "Deleting file server root directory (${PYTHON_FILE_SERVER_ROOT})"
+ctx logger info "Removing web server root folder: ${PYTHON_FILE_SERVER_ROOT}."
 rm -rf ${PYTHON_FILE_SERVER_ROOT}

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/12516271/tests/end2end/testenv.py
----------------------------------------------------------------------
diff --git a/tests/end2end/testenv.py b/tests/end2end/testenv.py
index 85714e5..43ec274 100644
--- a/tests/end2end/testenv.py
+++ b/tests/end2end/testenv.py
@@ -24,7 +24,7 @@ def testenv(tmpdir, request, monkeypatch):
     test_name = request.node.name
     workdir = str(tmpdir)
 
-    # setting the workdir environment variable for the CLI to work with
+    # Setting the workdir environment variable for the CLI
     monkeypatch.setenv('ARIA_WORKDIR', workdir)
     return TestEnvironment(workdir, test_name)
 
@@ -70,27 +70,33 @@ class TestEnvironment(object):
         assert len(self.model_storage.log.list()) == 0
 
     def _get_cli(self):
-        cli = sh.aria.bake('-vvv', _out=sys.stdout.write, _err=sys.stderr.write)
+        cli = sh.aria.bake('-vvv', _out=sys.stdout, _err=sys.stderr)
 
-        # the `sh` library supports underscore-dash auto-replacement for commands and option flags
-        # yet not for subcommands (e.g. `aria service-templates`); The following class fixes this.
         class PatchedCli(object):
+            """
+            The ``sh`` library supports underscore-dash auto-replacement for commands and option
+            flags yet not for subcommands (e.g. ``aria service-templates``). This class fixes this.
+            """
             def __getattr__(self, attr):
                 if '_' in attr:
                     return cli.bake(attr.replace('_', '-'))
                 return getattr(cli, attr)
 
             def __call__(self, *args, **kwargs):
-                # this is to support the `aria` command itself (e.g. `aria --version` calls)
+                """
+                This is to support the ``aria`` command itself (e.g. ``aria --version`` calls).
+                """
                 return cli(*args, **kwargs)
 
         return PatchedCli()
 
     def _get_aria_env(self):
-        # a somewhat hackish but most simple way of acquiring environment context such as
-        # the model storage, resource storage etc.
-        # note that the `ARIA_WORKDIR` environment variable must be exported before the import
-        # below is used, as the import itself will initialize the `.aria` directory.
+        """
+        A somewhat hacky but most simple way of acquiring environment context such as the model
+        storage, resource storage, etc. Note that the ``ARIA_WORKDIR`` environment variable must be
+        exported before the import below is used, as the import itself will initialize the ``.aria``
+        directory.
+        """
         from aria.cli import env as cli_env
         reload(cli_env)  # reloading the module in-between tests
         return cli_env.env

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/12516271/tests/orchestrator/execution_plugin/test_ctx_proxy_server.py
----------------------------------------------------------------------
diff --git a/tests/orchestrator/execution_plugin/test_ctx_proxy_server.py b/tests/orchestrator/execution_plugin/test_ctx_proxy_server.py
index 7ab1bdb..c06bd30 100644
--- a/tests/orchestrator/execution_plugin/test_ctx_proxy_server.py
+++ b/tests/orchestrator/execution_plugin/test_ctx_proxy_server.py
@@ -39,26 +39,26 @@ class TestCtxProxy(object):
         assert response == 'value1'
 
     def test_dict_prop_access_get_key_nested(self, server):
-        response = self.request(server, 'node', 'properties', 'prop2.nested_prop1')
+        response = self.request(server, 'node', 'properties', 'prop2', 'nested_prop1')
         assert response == 'nested_value1'
 
     def test_dict_prop_access_get_with_list_index(self, server):
-        response = self.request(server, 'node', 'properties', 'prop3[2].value')
+        response = self.request(server, 'node', 'properties', 'prop3', 2, 'value')
         assert response == 'value_2'
 
     def test_dict_prop_access_set(self, server, ctx):
-        self.request(server, 'node', 'properties', 'prop4.key', 'new_value')
-        self.request(server, 'node', 'properties', 'prop3[2].value', 'new_value_2')
-        self.request(server, 'node', 'properties', 'prop4.some.new.path',
+        self.request(server, 'node', 'properties', 'prop4', 'key', '=', 'new_value')
+        self.request(server, 'node', 'properties', 'prop3', 2, 'value', '=', 'new_value_2')
+        self.request(server, 'node', 'properties', 'prop4', 'some', 'new', 'path', '=',
                      'some_new_value')
         assert ctx.node.properties['prop4']['key'] == 'new_value'
         assert ctx.node.properties['prop3'][2]['value'] == 'new_value_2'
         assert ctx.node.properties['prop4']['some']['new']['path'] == 'some_new_value'
 
     def test_illegal_dict_access(self, server):
-        self.request(server, 'node', 'properties', 'prop4.key', 'new_value')
+        self.request(server, 'node', 'properties', 'prop4', 'key', '=', 'new_value')
         with pytest.raises(RuntimeError):
-            self.request(server, 'node', 'properties', 'prop4.key', 'new_value', 'what')
+            self.request(server, 'node', 'properties', 'prop4', 'key', '=', 'new_value', 'what')
 
     def test_method_invocation(self, server):
         args = ['arg1', 'arg2', 'arg3']
@@ -77,7 +77,7 @@ class TestCtxProxy(object):
         kwargs = dict(
             arg4=arg4,
             arg5=arg5)
-        response = self.request(server, 'stub_args', arg1, arg2, kwargs)
+        response = self.request(server, 'stub_args', kwargs, arg1, arg2)
         assert response == dict(
             arg1=arg1,
             arg2=arg2,
@@ -87,6 +87,21 @@ class TestCtxProxy(object):
             kwargs=dict(
                 arg5=arg5))
 
+        kwargs = dict(
+            arg2=arg2,
+            arg4=arg4,
+            arg5=arg5)
+        response = self.request(server, 'stub_args', kwargs, arg1)
+        assert response == dict(
+            arg1=arg1,
+            arg2=arg2,
+            arg3='arg3',
+            arg4=arg4,
+            args=[],
+            kwargs=dict(
+                arg5=arg5))
+
+
     def test_empty_return_value(self, server):
         response = self.request(server, 'stub_none')
         assert response is None
@@ -236,10 +251,10 @@ class TestArgumentParsing(object):
         self.assert_valid_output({'key': 1},
                                  "{'key': 1}",
                                  '{"key": 1}')
-        self.assert_valid_output(False, '', 'false')
+        self.assert_valid_output(False, 'False', 'false')
         self.assert_valid_output(True, 'True', 'true')
-        self.assert_valid_output([], '', '[]')
-        self.assert_valid_output({}, '', '{}')
+        self.assert_valid_output([], '[]', '[]')
+        self.assert_valid_output({}, '{}', '{}')
 
     def assert_valid_output(self, response, ex_typed_output, ex_json_output):
         self.mock_response = response
@@ -285,77 +300,3 @@ class TestCtxEntryPoint(object):
                              stderr=subprocess.PIPE)
         p.communicate()
         assert not p.wait()
-
-
-class TestPathDictAccess(object):
-    def test_simple_set(self):
-        obj = {}
-        path_dict = ctx_proxy.server._PathDictAccess(obj)
-        path_dict.set('foo', 42)
-        assert obj == {'foo': 42}
-
-    def test_nested_set(self):
-        obj = {'foo': {}}
-        path_dict = ctx_proxy.server._PathDictAccess(obj)
-        path_dict.set('foo.bar', 42)
-        assert obj == {'foo': {'bar': 42}}
-
-    def test_set_index(self):
-        obj = {'foo': [None, {'bar': 0}]}
-        path_dict = ctx_proxy.server._PathDictAccess(obj)
-        path_dict.set('foo[1].bar', 42)
-        assert obj == {'foo': [None, {'bar': 42}]}
-
-    def test_set_nonexistent_parent(self):
-        obj = {}
-        path_dict = ctx_proxy.server._PathDictAccess(obj)
-        path_dict.set('foo.bar', 42)
-        assert obj == {'foo': {'bar': 42}}
-
-    def test_set_nonexistent_parent_nested(self):
-        obj = {}
-        path_dict = ctx_proxy.server._PathDictAccess(obj)
-        path_dict.set('foo.bar.baz', 42)
-        assert obj == {'foo': {'bar': {'baz': 42}}}
-
-    def test_simple_get(self):
-        obj = {'foo': 42}
-        path_dict = ctx_proxy.server._PathDictAccess(obj)
-        result = path_dict.get('foo')
-        assert result == 42
-
-    def test_nested_get(self):
-        obj = {'foo': {'bar': 42}}
-        path_dict = ctx_proxy.server._PathDictAccess(obj)
-        result = path_dict.get('foo.bar')
-        assert result == 42
-
-    def test_nested_get_shadows_dotted_name(self):
-        obj = {'foo': {'bar': 42}, 'foo.bar': 58}
-        path_dict = ctx_proxy.server._PathDictAccess(obj)
-        result = path_dict.get('foo.bar')
-        assert result == 42
-
-    def test_index_get(self):
-        obj = {'foo': [0, 1]}
-        path_dict = ctx_proxy.server._PathDictAccess(obj)
-        result = path_dict.get('foo[1]')
-        assert result == 1
-
-    def test_get_nonexistent(self):
-        obj = {}
-        path_dict = ctx_proxy.server._PathDictAccess(obj)
-        with pytest.raises(RuntimeError):
-            path_dict.get('foo')
-
-    def test_get_by_index_not_list(self):
-        obj = {'foo': {0: 'not-list'}}
-        path_dict = ctx_proxy.server._PathDictAccess(obj)
-        with pytest.raises(RuntimeError):
-            path_dict.get('foo[0]')
-
-    def test_get_by_index_nonexistent_parent(self):
-        obj = {}
-        path_dict = ctx_proxy.server._PathDictAccess(obj)
-        with pytest.raises(RuntimeError):
-            path_dict.get('foo[1]')

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/12516271/tests/orchestrator/execution_plugin/test_local.py
----------------------------------------------------------------------
diff --git a/tests/orchestrator/execution_plugin/test_local.py b/tests/orchestrator/execution_plugin/test_local.py
index e64e998..f091194 100644
--- a/tests/orchestrator/execution_plugin/test_local.py
+++ b/tests/orchestrator/execution_plugin/test_local.py
@@ -43,10 +43,10 @@ class TestLocalRunScript(object):
         script_path = self._create_script(
             tmpdir,
             linux_script='''#! /bin/bash -e
-            ctx node attributes map.key value
+            ctx node attributes map key = value
             ''',
             windows_script='''
-            ctx node attributes map.key value
+            ctx node attributes map key = value
         ''')
         props = self._run(
             executor, workflow_context,
@@ -57,12 +57,12 @@ class TestLocalRunScript(object):
         script_path = self._create_script(
             tmpdir,
             linux_script='''#! /bin/bash -e
-            ctx node attributes map.key1 $key1
-            ctx node attributes map.key2 $key2
+            ctx node attributes map key1 = $key1
+            ctx node attributes map key2 = $key2
             ''',
             windows_script='''
-            ctx node attributes map.key1 %key1%
-            ctx node attributes map.key2 %key2%
+            ctx node attributes map key1 = %key1%
+            ctx node attributes map key2 = %key2%
         ''')
         props = self._run(
             executor, workflow_context,
@@ -81,10 +81,10 @@ class TestLocalRunScript(object):
         script_path = self._create_script(
             tmpdir,
             linux_script='''#! /bin/bash -e
-            ctx node attributes map.cwd $PWD
+            ctx node attributes map cwd = $PWD
             ''',
             windows_script='''
-            ctx node attributes map.cwd %CD%
+            ctx node attributes map cwd = %CD%
             ''')
         tmpdir = str(tmpdir)
         props = self._run(
@@ -97,7 +97,7 @@ class TestLocalRunScript(object):
         assert p_map['cwd'] == tmpdir
 
     def test_process_command_prefix(self, executor, workflow_context, tmpdir):
-        use_ctx = 'ctx node attributes map.key value'
+        use_ctx = 'ctx node attributes map key = value'
         python_script = ['import subprocess',
                          'subprocess.Popen("{0}".split(' ')).communicate()[0]'.format(use_ctx)]
         python_script = '\n'.join(python_script)
@@ -121,12 +121,12 @@ class TestLocalRunScript(object):
         script_path = self._create_script(
             tmpdir,
             linux_script='''#! /bin/bash -e
-            ctx node attributes map.arg1 "$1"
-            ctx node attributes map.arg2 $2
+            ctx node attributes map arg1 = "$1"
+            ctx node attributes map arg2 = $2
             ''',
             windows_script='''
-            ctx node attributes map.arg1 %1
-            ctx node attributes map.arg2 %2
+            ctx node attributes map arg1 = %1
+            ctx node attributes map arg2 = %2
             ''')
         props = self._run(
             executor, workflow_context,
@@ -209,10 +209,10 @@ if __name__ == '__main__':
         script_path = self._create_script(
             tmpdir,
             linux_script='''#! /bin/bash -e
-            ctx node attributes key "${input_as_env_var}"
+            ctx node attributes key = "${input_as_env_var}"
             ''',
             windows_script='''
-            ctx node attributes key "%input_as_env_var%"
+            ctx node attributes key = "%input_as_env_var%"
         ''')
         props = self._run(
             executor, workflow_context,
@@ -228,10 +228,10 @@ if __name__ == '__main__':
         script_path = self._create_script(
             tmpdir,
             linux_script='''#! /bin/bash -e
-            ctx node attributes key "${input_as_env_var}"
+            ctx node attributes key = "${input_as_env_var}"
             ''',
             windows_script='''
-            ctx node attributes key "%input_as_env_var%"
+            ctx node attributes key = "%input_as_env_var%"
         ''')
 
         props = self._run(

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/12516271/tests/resources/scripts/test_ssh.sh
----------------------------------------------------------------------
diff --git a/tests/resources/scripts/test_ssh.sh b/tests/resources/scripts/test_ssh.sh
index 4261d7e..ca82aaa 100644
--- a/tests/resources/scripts/test_ssh.sh
+++ b/tests/resources/scripts/test_ssh.sh
@@ -19,7 +19,7 @@ set -u
 set -e
 
 test_run_script_basic() {
-    ctx node attributes test_value $test_value
+    ctx node attributes test_value = $test_value
 }
 
 test_run_script_as_sudo() {
@@ -27,7 +27,7 @@ test_run_script_as_sudo() {
 }
 
 test_run_script_default_base_dir() {
-    ctx node attributes work_dir $PWD
+    ctx node attributes work_dir = $PWD
 }
 
 test_run_script_with_hide() {
@@ -35,44 +35,44 @@ test_run_script_with_hide() {
 }
 
 test_run_script_process_config() {
-    ctx node attributes env_value $test_value_env
-    ctx node attributes bash_version $BASH_VERSION
-    ctx node attributes arg1_value $1
-    ctx node attributes arg2_value $2
-    ctx node attributes cwd $PWD
-    ctx node attributes ctx_path $(which ctx)
+    ctx node attributes env_value = $test_value_env
+    ctx node attributes bash_version = $BASH_VERSION
+    ctx node attributes arg1_value = $1
+    ctx node attributes arg2_value = $2
+    ctx node attributes cwd = $PWD
+    ctx node attributes ctx_path = $(which ctx)
 }
 
 test_run_script_command_prefix() {
-    ctx node attributes dollar_dash $-
+    ctx node attributes dollar_dash = $-
 }
 
 test_run_script_reuse_existing_ctx_1() {
-    ctx node attributes test_value1 $test_value1
+    ctx node attributes test_value1 = $test_value1
 }
 
 test_run_script_reuse_existing_ctx_2() {
-    ctx node attributes test_value2 $test_value2
+    ctx node attributes test_value2 = $test_value2
 }
 
 test_run_script_download_resource_plain() {
     local destination=$(mktemp)
     ctx download-resource ${destination} test_resource
-    ctx node attributes test_value "$(cat ${destination})"
+    ctx node attributes test_value = "$(cat ${destination})"
 }
 
 test_run_script_download_resource_and_render() {
     local destination=$(mktemp)
     ctx download-resource-and-render ${destination} test_resource
-    ctx node attributes test_value "$(cat ${destination})"
+    ctx node attributes test_value = "$(cat ${destination})"
 }
 
 test_run_script_inputs_as_env_variables_no_override() {
-    ctx node attributes test_value "$custom_env_var"
+    ctx node attributes test_value = "$custom_env_var"
 }
 
 test_run_script_inputs_as_env_variables_process_env_override() {
-    ctx node attributes test_value "$custom_env_var"
+    ctx node attributes test_value = "$custom_env_var"
 }
 
 test_run_script_error_in_script() {