You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@ariatosca.apache.org by ra...@apache.org on 2017/05/22 12:17:28 UTC

[01/19] incubator-ariatosca git commit: ARIA-156 Better handle exceptions in the process executor [Forced Update!]

Repository: incubator-ariatosca
Updated Branches:
  refs/heads/ARIA-208-Missing-back-refrences-for-models d1628c395 -> 5798bbfc7 (forced update)


ARIA-156 Better handle exceptions in the process executor

Previously, if an exception was raised during the starting of a task,
the task's process was permanently blocked on receiving a message.

The reason was that the exception caused the 'listener thread' to
not send a response to the task's process, as the exception was not
handled inside the 'with' block of the listener thread.

The first change I introduced was to wrap the yielding of the message and
the response inside a try-except-finally block, so the exception will be
handled within the 'with' scope, and to ensure a response is sent to the
task's process.

The second change is to move the sending of the 'task started' message in
the task's process to a place where encountering an exception will be
handled via sending a 'task failed' message back to the listener thread.


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

Branch: refs/heads/ARIA-208-Missing-back-refrences-for-models
Commit: 1cb3086f38bc35a6a5e588aa5e340cb4fa5cacf5
Parents: 8553977
Author: Avia Efrat <av...@gigaspaces.com>
Authored: Sun May 7 11:42:58 2017 +0300
Committer: Avia Efrat <av...@gigaspaces.com>
Committed: Sun May 7 14:44:01 2017 +0300

----------------------------------------------------------------------
 aria/orchestrator/workflows/executor/process.py | 11 ++++++++---
 1 file changed, 8 insertions(+), 3 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/1cb3086f/aria/orchestrator/workflows/executor/process.py
----------------------------------------------------------------------
diff --git a/aria/orchestrator/workflows/executor/process.py b/aria/orchestrator/workflows/executor/process.py
index 2378e0a..8481406 100644
--- a/aria/orchestrator/workflows/executor/process.py
+++ b/aria/orchestrator/workflows/executor/process.py
@@ -213,8 +213,13 @@ class ProcessExecutor(base.BaseExecutor):
         with contextlib.closing(self._server_socket.accept()[0]) as connection:
             message = _recv_message(connection)
             response = {}
-            yield message, response
-            _send_message(connection, response)
+            try:
+                yield message, response
+            except BaseException as e:
+                response['exception'] = exceptions.wrap_if_needed(e)
+                raise
+            finally:
+                _send_message(connection, response)
 
     def _handle_task_started_request(self, task_id, **kwargs):
         self._task_started(self._tasks[task_id])
@@ -378,7 +383,6 @@ def _main():
     task_id = arguments['task_id']
     port = arguments['port']
     messenger = _Messenger(task_id=task_id, port=port)
-    messenger.started()
 
     implementation = arguments['implementation']
     operation_inputs = arguments['operation_inputs']
@@ -390,6 +394,7 @@ def _main():
 
     with instrumentation.track_changes() as instrument:
         try:
+            messenger.started()
             ctx = context_dict['context_cls'].deserialize_from_dict(**context_dict['context'])
             _patch_session(ctx=ctx, messenger=messenger, instrument=instrument)
             task_func = imports.load_attribute(implementation)


[14/19] incubator-ariatosca git commit: ARIA-139 Support attributes

Posted by ra...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/extensions/aria_extension_tosca/simple_v1_0/definitions.py
----------------------------------------------------------------------
diff --git a/extensions/aria_extension_tosca/simple_v1_0/definitions.py b/extensions/aria_extension_tosca/simple_v1_0/definitions.py
index b60a797..8564249 100644
--- a/extensions/aria_extension_tosca/simple_v1_0/definitions.py
+++ b/extensions/aria_extension_tosca/simple_v1_0/definitions.py
@@ -15,7 +15,7 @@
 
 from aria.utils.collections import FrozenDict
 from aria.utils.caching import cachedmethod
-from aria.parser import dsl_specification
+from aria.parser import implements_specification
 from aria.parser.presentation import (has_fields, short_form_field, allow_unknown_fields,
                                       primitive_field, primitive_list_field, object_field,
                                       object_list_field, object_dict_field,
@@ -35,7 +35,7 @@ from .modeling.interfaces import (get_and_override_input_definitions_from_type,
                                   get_and_override_operation_definitions_from_type)
 
 @has_fields
-@dsl_specification('3.5.8', 'tosca-simple-1.0')
+@implements_specification('3.5.8', 'tosca-simple-1.0')
 class PropertyDefinition(ExtensiblePresentation):
     """
     A property definition defines a named, typed value and related data that can be associated with
@@ -86,7 +86,7 @@ class PropertyDefinition(ExtensiblePresentation):
 
     @primitive_field(str, default='supported', allowed=('supported', 'unsupported', 'experimental',
                                                         'deprecated'))
-    @dsl_specification(section='3.5.8.3', spec='tosca-simple-1.0')
+    @implements_specification(section='3.5.8.3', spec='tosca-simple-1.0')
     def status(self):
         """
         The optional status of the property relative to the specification or implementation.
@@ -121,7 +121,7 @@ class PropertyDefinition(ExtensiblePresentation):
         return get_property_constraints(context, self)
 
 @has_fields
-@dsl_specification('3.5.10', 'tosca-simple-1.0')
+@implements_specification('3.5.10', 'tosca-simple-1.0')
 class AttributeDefinition(ExtensiblePresentation):
     """
     An attribute definition defines a named, typed value that can be associated with an entity
@@ -190,7 +190,7 @@ class AttributeDefinition(ExtensiblePresentation):
         return get_data_type(context, self, 'type')
 
 @has_fields
-@dsl_specification('3.5.12', 'tosca-simple-1.0')
+@implements_specification('3.5.12', 'tosca-simple-1.0')
 class ParameterDefinition(PropertyDefinition):
     """
     A parameter definition is essentially a TOSCA property definition; however, it also allows a
@@ -225,7 +225,7 @@ class ParameterDefinition(PropertyDefinition):
 
 @short_form_field('implementation')
 @has_fields
-@dsl_specification('3.5.13-1', 'tosca-simple-1.0')
+@implements_specification('3.5.13-1', 'tosca-simple-1.0')
 class OperationDefinition(ExtensiblePresentation):
     """
     An operation definition defines a named function or procedure that can be bound to an
@@ -266,7 +266,7 @@ class OperationDefinition(ExtensiblePresentation):
 
 @allow_unknown_fields
 @has_fields
-@dsl_specification('3.5.14-1', 'tosca-simple-1.0')
+@implements_specification('3.5.14-1', 'tosca-simple-1.0')
 class InterfaceDefinition(ExtensiblePresentation):
     """
     An interface definition defines a named interface that can be associated with a Node or
@@ -352,7 +352,7 @@ class RelationshipDefinition(ExtensiblePresentation):
 
 @short_form_field('capability')
 @has_fields
-@dsl_specification('3.6.2', 'tosca-simple-1.0')
+@implements_specification('3.6.2', 'tosca-simple-1.0')
 class RequirementDefinition(ExtensiblePresentation):
     """
     The Requirement definition describes a named requirement (dependencies) of a TOSCA Node Type or
@@ -418,7 +418,7 @@ class RequirementDefinition(ExtensiblePresentation):
 
 @short_form_field('type')
 @has_fields
-@dsl_specification('3.6.1', 'tosca-simple-1.0')
+@implements_specification('3.6.1', 'tosca-simple-1.0')
 class CapabilityDefinition(ExtensiblePresentation):
     """
     A capability definition defines a named, typed set of data that can be associated with Node Type

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/extensions/aria_extension_tosca/simple_v1_0/filters.py
----------------------------------------------------------------------
diff --git a/extensions/aria_extension_tosca/simple_v1_0/filters.py b/extensions/aria_extension_tosca/simple_v1_0/filters.py
index 617ce7a..838b505 100644
--- a/extensions/aria_extension_tosca/simple_v1_0/filters.py
+++ b/extensions/aria_extension_tosca/simple_v1_0/filters.py
@@ -14,7 +14,7 @@
 # limitations under the License.
 
 from aria.utils.caching import cachedmethod
-from aria.parser import dsl_specification
+from aria.parser import implements_specification
 from aria.parser.presentation import (has_fields, object_sequenced_list_field, field_validator)
 
 from .misc import ConstraintClause
@@ -45,7 +45,7 @@ class CapabilityFilter(ExtensiblePresentation):
         return None
 
 @has_fields
-@dsl_specification('3.5.4', 'tosca-simple-1.0')
+@implements_specification('3.5.4', 'tosca-simple-1.0')
 class NodeFilter(ExtensiblePresentation):
     """
     A node filter definition defines criteria for selection of a TOSCA Node Template based upon the
@@ -58,7 +58,7 @@ class NodeFilter(ExtensiblePresentation):
 
     @field_validator(node_filter_properties_validator)
     @object_sequenced_list_field(ConstraintClause)
-    @dsl_specification('3.5.3', 'tosca-simple-1.0')
+    @implements_specification('3.5.3', 'tosca-simple-1.0')
     def properties(self):
         """
         An optional sequenced list of property filters that would be used to select (filter)

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/extensions/aria_extension_tosca/simple_v1_0/functions.py
----------------------------------------------------------------------
diff --git a/extensions/aria_extension_tosca/simple_v1_0/functions.py b/extensions/aria_extension_tosca/simple_v1_0/functions.py
deleted file mode 100644
index 2f77420..0000000
--- a/extensions/aria_extension_tosca/simple_v1_0/functions.py
+++ /dev/null
@@ -1,536 +0,0 @@
-# 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 cStringIO import StringIO
-
-from aria.utils.collections import FrozenList
-from aria.utils.formatting import as_raw, safe_repr
-from aria.parser import dsl_specification
-from aria.parser.exceptions import InvalidValueError
-from aria.parser.validation import Issue
-from aria.modeling.exceptions import CannotEvaluateFunctionException
-from aria.modeling.functions import Function
-
-#
-# Intrinsic
-#
-
-@dsl_specification('4.3.1', 'tosca-simple-1.0')
-class Concat(Function):
-    """
-    The :code:`concat` function is used to concatenate two or more string values within a TOSCA
-    service template.
-    """
-
-    def __init__(self, context, presentation, argument):
-        self.locator = presentation._locator
-
-        if not isinstance(argument, list):
-            raise InvalidValueError(
-                'function "concat" argument must be a list of string expressions: %s'
-                % safe_repr(argument),
-                locator=self.locator)
-
-        string_expressions = []
-        for index, an_argument in enumerate(argument):
-            string_expressions.append(parse_string_expression(context, presentation, 'concat',
-                                                              index, None, an_argument))
-        self.string_expressions = FrozenList(string_expressions)
-
-    @property
-    def as_raw(self):
-        string_expressions = []
-        for string_expression in self.string_expressions:
-            if hasattr(string_expression, 'as_raw'):
-                string_expression = as_raw(string_expression)
-            string_expressions.append(string_expression)
-        return {'concat': string_expressions}
-
-    def _evaluate(self, context, container):
-        value = StringIO()
-        for e in self.string_expressions:
-            if hasattr(e, '_evaluate'):
-                e = e._evaluate(context, container)
-            value.write(str(e))
-        return value.getvalue()
-
-@dsl_specification('4.3.2', 'tosca-simple-1.0')
-class Token(Function):
-    """
-    The :code:`token` function is used within a TOSCA service template on a string to parse out
-    (tokenize) substrings separated by one or more token characters within a larger string.
-    """
-
-    def __init__(self, context, presentation, argument):
-        self.locator = presentation._locator
-
-        if (not isinstance(argument, list)) or (len(argument) != 3):
-            raise InvalidValueError('function "token" argument must be a list of 3 parameters: %s'
-                                    % safe_repr(argument),
-                                    locator=self.locator)
-
-        self.string_with_tokens = parse_string_expression(context, presentation, 'token', 0,
-                                                          'the string to tokenize', argument[0])
-        self.string_of_token_chars = parse_string_expression(context, presentation, 'token', 1,
-                                                             'the token separator characters',
-                                                             argument[1])
-        self.substring_index = parse_int(context, presentation, 'token', 2,
-                                         'the 0-based index of the token to return', argument[2])
-
-    @property
-    def as_raw(self):
-        string_with_tokens = self.string_with_tokens
-        if hasattr(string_with_tokens, 'as_raw'):
-            string_with_tokens = as_raw(string_with_tokens)
-        string_of_token_chars = self.string_with_tokens
-        if hasattr(string_of_token_chars, 'as_raw'):
-            string_of_token_chars = as_raw(string_of_token_chars)
-        return {'token': [string_with_tokens, string_of_token_chars, self.substring_index]}
-
-    def _evaluate(self, context, container):
-        string_with_tokens = self.string_with_tokens
-        if hasattr(string_with_tokens, '_evaluate'):
-            string_with_tokens = string_with_tokens._evaluate(context, container) # pylint: disable=no-member
-
-#
-# Property
-#
-
-@dsl_specification('4.4.1', 'tosca-simple-1.0')
-class GetInput(Function):
-    """
-    The :code:`get_input` function is used to retrieve the values of properties declared within the
-    inputs section of a TOSCA Service Template.
-    """
-
-    def __init__(self, context, presentation, argument):
-        self.locator = presentation._locator
-
-        self.input_property_name = parse_string_expression(context, presentation, 'get_input',
-                                                           None, 'the input property name',
-                                                           argument)
-
-        if isinstance(self.input_property_name, basestring):
-            the_input = context.presentation.get_from_dict('service_template', 'topology_template',
-                                                           'inputs', self.input_property_name)
-            if the_input is None:
-                raise InvalidValueError(
-                    'function "get_input" argument is not a valid input name: %s'
-                    % safe_repr(argument),
-                    locator=self.locator)
-
-    @property
-    def as_raw(self):
-        return {'get_input': as_raw(self.input_property_name)}
-
-    def _evaluate(self, context, container): # pylint: disable=unused-argument
-        if not context.modeling.instance:
-            raise CannotEvaluateFunctionException()
-        the_input = context.modeling.instance.inputs.get(
-            self.input_property_name,
-            context.modeling.template.inputs.get(self.input_property_name))
-        return as_raw(the_input.value) if the_input is not None else None
-
-@dsl_specification('4.4.2', 'tosca-simple-1.0')
-class GetProperty(Function):
-    """
-    The :code:`get_property` function is used to retrieve property values between modelable entities
-    defined in the same service template.
-    """
-
-    def __init__(self, context, presentation, argument):
-        self.locator = presentation._locator
-
-        if (not isinstance(argument, list)) or (len(argument) < 2):
-            raise InvalidValueError(
-                'function "get_property" argument must be a list of at least 2 string expressions: '
-                '%s'
-                % safe_repr(argument),
-                locator=self.locator)
-
-        self.modelable_entity_name = parse_modelable_entity_name(context, presentation,
-                                                                 'get_property', 0, argument[0])
-        # The first of these will be tried as a req-or-cap name:
-        self.nested_property_name_or_index = argument[1:]
-
-    @property
-    def as_raw(self):
-        return {'get_property': [self.modelable_entity_name] + self.nested_property_name_or_index}
-
-    def _evaluate(self, context, container):
-        modelable_entities = get_modelable_entities(context, container, self.locator,
-                                                    self.modelable_entity_name)
-        req_or_cap_name = self.nested_property_name_or_index[0]
-
-        for modelable_entity in modelable_entities:
-            properties = None
-
-            if hasattr(modelable_entity, 'requirement_templates') \
-                and modelable_entity.requirement_templates \
-                and (req_or_cap_name in [v.name for v in modelable_entity.requirement_templates]):
-                for requirement_template in modelable_entity.requirement_templates:
-                    if requirement_template.name == req_or_cap_name:
-                        # First argument refers to a requirement
-                        # TODO: should follow to matched capability in other node...
-                        raise CannotEvaluateFunctionException()
-                        # break
-                nested_property_name_or_index = self.nested_property_name_or_index[1:]
-            elif hasattr(modelable_entity, 'capability_templates') \
-                and modelable_entity.capability_templates \
-                and (req_or_cap_name in modelable_entity.capability_templates):
-                # First argument refers to a capability
-                properties = modelable_entity.capability_templates[req_or_cap_name].properties
-                nested_property_name_or_index = self.nested_property_name_or_index[1:]
-            else:
-                properties = modelable_entity.properties
-                nested_property_name_or_index = self.nested_property_name_or_index
-
-            if properties:
-                found = True
-                value = properties
-                for name in nested_property_name_or_index:
-                    if (isinstance(value, dict) and (name in value)) \
-                        or (isinstance(value, list) and name < len(list)):
-                        value = value[name]
-                        if hasattr(value, '_evaluate'):
-                            value = value._evaluate(context, modelable_entity)
-                    else:
-                        found = False
-                        break
-                if found:
-                    return as_raw(value)
-
-        raise InvalidValueError(
-            'function "get_property" could not find "%s" in modelable entity "%s"' \
-            % ('.'.join(self.nested_property_name_or_index), self.modelable_entity_name),
-            locator=self.locator)
-
-#
-# Attribute
-#
-
-@dsl_specification('4.5.1', 'tosca-simple-1.0')
-class GetAttribute(Function):
-    """
-    The :code:`get_attribute` function is used to retrieve the values of named attributes declared
-    by the referenced node or relationship template name.
-    """
-
-    def __init__(self, context, presentation, argument):
-        self.locator = presentation._locator
-
-        if (not isinstance(argument, list)) or (len(argument) < 2):
-            raise InvalidValueError(
-                'function "get_attribute" argument must be a list of at least 2 string expressions:'
-                ' %s'
-                % safe_repr(argument),
-                locator=self.locator)
-
-        self.modelable_entity_name = parse_modelable_entity_name(context, presentation,
-                                                                 'get_attribute', 0, argument[0])
-        # The first of these will be tried as a req-or-cap name:
-        self.nested_property_name_or_index = argument[1:]
-
-    @property
-    def as_raw(self):
-        return {'get_attribute': [self.modelable_entity_name] + self.nested_property_name_or_index}
-
-    def _evaluate(self, context, container): # pylint: disable=no-self-use,unused-argument
-        raise CannotEvaluateFunctionException()
-
-#
-# Operation
-#
-
-@dsl_specification('4.6.1', 'tosca-simple-1.0')  # pylint: disable=abstract-method
-class GetOperationOutput(Function):
-    """
-    The :code:`get_operation_output` function is used to retrieve the values of variables exposed /
-    exported from an interface operation.
-    """
-
-    def __init__(self, context, presentation, argument):
-        self.locator = presentation._locator
-
-        if (not isinstance(argument, list)) or (len(argument) != 4):
-            raise InvalidValueError(
-                'function "get_operation_output" argument must be a list of 4 parameters: %s'
-                % safe_repr(argument),
-                locator=self.locator)
-
-        self.modelable_entity_name = parse_string_expression(context, presentation,
-                                                             'get_operation_output', 0,
-                                                             'modelable entity name', argument[0])
-        self.interface_name = parse_string_expression(context, presentation, 'get_operation_output',
-                                                      1, 'the interface name', argument[1])
-        self.operation_name = parse_string_expression(context, presentation, 'get_operation_output',
-                                                      2, 'the operation name', argument[2])
-        self.output_variable_name = parse_string_expression(context, presentation,
-                                                            'get_operation_output', 3,
-                                                            'the output name', argument[3])
-
-    @property
-    def as_raw(self):
-        interface_name = self.interface_name
-        if hasattr(interface_name, 'as_raw'):
-            interface_name = as_raw(interface_name)
-        operation_name = self.operation_name
-        if hasattr(operation_name, 'as_raw'):
-            operation_name = as_raw(operation_name)
-        output_variable_name = self.output_variable_name
-        if hasattr(output_variable_name, 'as_raw'):
-            output_variable_name = as_raw(output_variable_name)
-        return {'get_operation_output': [self.modelable_entity_name, interface_name, operation_name,
-                                         output_variable_name]}
-
-#
-# Navigation
-#
-
-@dsl_specification('4.7.1', 'tosca-simple-1.0')
-class GetNodesOfType(Function):
-    """
-    The :code:`get_nodes_of_type` function can be used to retrieve a list of all known instances of
-    nodes of the declared Node Type.
-    """
-
-    def __init__(self, context, presentation, argument):
-        self.locator = presentation._locator
-
-        self.node_type_name = parse_string_expression(context, presentation, 'get_nodes_of_type',
-                                                      None, 'the node type name', argument)
-
-        if isinstance(self.node_type_name, basestring):
-            node_types = context.presentation.get('service_template', 'node_types')
-            if (node_types is None) or (self.node_type_name not in node_types):
-                raise InvalidValueError(
-                    'function "get_nodes_of_type" argument is not a valid node type name: %s'
-                    % safe_repr(argument),
-                    locator=self.locator)
-
-    @property
-    def as_raw(self):
-        node_type_name = self.node_type_name
-        if hasattr(node_type_name, 'as_raw'):
-            node_type_name = as_raw(node_type_name)
-        return {'get_nodes_of_type': node_type_name}
-
-    def _evaluate(self, context, container):
-        pass
-
-#
-# Artifact
-#
-
-@dsl_specification('4.8.1', 'tosca-simple-1.0') # pylint: disable=abstract-method
-class GetArtifact(Function):
-    """
-    The :code:`get_artifact` function is used to retrieve artifact location between modelable
-    entities defined in the same service template.
-    """
-
-    def __init__(self, context, presentation, argument):
-        self.locator = presentation._locator
-
-        if (not isinstance(argument, list)) or (len(argument) < 2) or (len(argument) > 4):
-            raise InvalidValueError(
-                'function "get_artifact" argument must be a list of 2 to 4 parameters: %s'
-                % safe_repr(argument),
-                locator=self.locator)
-
-        self.modelable_entity_name = parse_string_expression(context, presentation, 'get_artifact',
-                                                             0, 'modelable entity name',
-                                                             argument[0])
-        self.artifact_name = parse_string_expression(context, presentation, 'get_artifact', 1,
-                                                     'the artifact name', argument[1])
-        self.location = parse_string_expression(context, presentation, 'get_artifact', 2,
-                                                'the location or "LOCAL_FILE"', argument[2])
-        self.remove = parse_bool(context, presentation, 'get_artifact', 3, 'the removal flag',
-                                 argument[3])
-
-    @property
-    def as_raw(self):
-        artifact_name = self.artifact_name
-        if hasattr(artifact_name, 'as_raw'):
-            artifact_name = as_raw(artifact_name)
-        location = self.location
-        if hasattr(location, 'as_raw'):
-            location = as_raw(location)
-        return {'get_artifacts': [self.modelable_entity_name, artifact_name, location, self.remove]}
-
-#
-# Utils
-#
-
-def get_function(context, presentation, value):
-    functions = context.presentation.presenter.functions
-    if isinstance(value, dict) and (len(value) == 1):
-        key = value.keys()[0]
-        if key in functions:
-            try:
-                return True, functions[key](context, presentation, value[key])
-            except InvalidValueError as e:
-                context.validation.report(issue=e.issue)
-                return True, None
-    return False, None
-
-def parse_string_expression(context, presentation, name, index, explanation, value): # pylint: disable=unused-argument
-    is_function, func = get_function(context, presentation, value)
-    if is_function:
-        return func
-    else:
-        value = str(value)
-    return value
-
-def parse_int(context, presentation, name, index, explanation, value): # pylint: disable=unused-argument
-    if not isinstance(value, int):
-        try:
-            value = int(value)
-        except ValueError:
-            raise invalid_value(name, index, 'an integer', explanation, value,
-                                presentation._locator)
-    return value
-
-def parse_bool(context, presentation, name, index, explanation, value): # pylint: disable=unused-argument
-    if not isinstance(value, bool):
-        raise invalid_value(name, index, 'a boolean', explanation, value, presentation._locator)
-    return value
-
-def parse_modelable_entity_name(context, presentation, name, index, value):
-    value = parse_string_expression(context, presentation, name, index, 'the modelable entity name',
-                                    value)
-    if value == 'SELF':
-        the_self, _ = parse_self(presentation)
-        if the_self is None:
-            raise invalid_modelable_entity_name(name, index, value, presentation._locator,
-                                                'a node template or a relationship template')
-    elif value == 'HOST':
-        _, self_variant = parse_self(presentation)
-        if self_variant != 'node_template':
-            raise invalid_modelable_entity_name(name, index, value, presentation._locator,
-                                                'a node template')
-    elif (value == 'SOURCE') or (value == 'TARGET'):
-        _, self_variant = parse_self(presentation)
-        if self_variant != 'relationship_template':
-            raise invalid_modelable_entity_name(name, index, value, presentation._locator,
-                                                'a relationship template')
-    elif isinstance(value, basestring):
-        node_templates = \
-            context.presentation.get('service_template', 'topology_template', 'node_templates') \
-            or {}
-        relationship_templates = \
-            context.presentation.get('service_template', 'topology_template',
-                                     'relationship_templates') \
-            or {}
-        if (value not in node_templates) and (value not in relationship_templates):
-            raise InvalidValueError(
-                'function "%s" parameter %d is not a valid modelable entity name: %s'
-                % (name, index + 1, safe_repr(value)),
-                locator=presentation._locator, level=Issue.BETWEEN_TYPES)
-    return value
-
-def parse_self(presentation):
-    from .templates import NodeTemplate, RelationshipTemplate
-    from .types import NodeType, RelationshipType
-
-    if presentation is None:
-        return None, None
-    elif isinstance(presentation, NodeTemplate) or isinstance(presentation, NodeType):
-        return presentation, 'node_template'
-    elif isinstance(presentation, RelationshipTemplate) \
-        or isinstance(presentation, RelationshipType):
-        return presentation, 'relationship_template'
-    else:
-        return parse_self(presentation._container)
-
-@dsl_specification('4.1', 'tosca-simple-1.0')
-def get_modelable_entities(context, container, locator, modelable_entity_name):
-    """
-    The following keywords MAY be used in some TOSCA function in place of a TOSCA Node or
-    Relationship Template name.
-    """
-
-    if modelable_entity_name == 'SELF':
-        return get_self(context, container)
-    elif modelable_entity_name == 'HOST':
-        return get_host(context, container)
-    elif modelable_entity_name == 'SOURCE':
-        return get_source(context, container)
-    elif modelable_entity_name == 'TARGET':
-        return get_target(context, container)
-    elif isinstance(modelable_entity_name, basestring):
-        node_templates = \
-            context.presentation.get('service_template', 'topology_template', 'node_templates') \
-            or {}
-        if modelable_entity_name in node_templates:
-            return [node_templates[modelable_entity_name]]
-        relationship_templates = \
-            context.presentation.get('service_template', 'topology_template',
-                                     'relationship_templates') \
-            or {}
-        if modelable_entity_name in relationship_templates:
-            return [relationship_templates[modelable_entity_name]]
-
-    raise InvalidValueError('function "get_property" could not find modelable entity "%s"'
-                            % modelable_entity_name,
-                            locator=locator)
-
-def get_self(context, container): # pylint: disable=unused-argument
-    """
-    A TOSCA orchestrator will interpret this keyword as the Node or Relationship Template instance
-    that contains the function at the time the function is evaluated.
-    """
-
-    return [container]
-
-def get_host(context, container): # pylint: disable=unused-argument
-    """
-    A TOSCA orchestrator will interpret this keyword to refer to the all nodes that "host" the node
-    using this reference (i.e., as identified by its HostedOn relationship).
-
-    Specifically, TOSCA orchestrators that encounter this keyword when evaluating the get_attribute
-    or :code:`get_property` functions SHALL search each node along the "HostedOn" relationship chain
-    starting at the immediate node that hosts the node where the function was evaluated (and then
-    that node's host node, and so forth) until a match is found or the "HostedOn" relationship chain
-    ends.
-    """
-
-    return []
-
-def get_source(context, container): # pylint: disable=unused-argument
-    """
-    A TOSCA orchestrator will interpret this keyword as the Node Template instance that is at the
-    source end of the relationship that contains the referencing function.
-    """
-
-    return []
-
-def get_target(context, container): # pylint: disable=unused-argument
-    """
-    A TOSCA orchestrator will interpret this keyword as the Node Template instance that is at the
-    target end of the relationship that contains the referencing function.
-    """
-
-def invalid_modelable_entity_name(name, index, value, locator, contexts):
-    return InvalidValueError('function "%s" parameter %d can be "%s" only in %s'
-                             % (name, index + 1, value, contexts),
-                             locator=locator, level=Issue.FIELD)
-
-def invalid_value(name, index, the_type, explanation, value, locator):
-    return InvalidValueError(
-        'function "%s" %s is not %s%s: %s'
-        % (name, ('parameter %d' % (index + 1)) if index is not None else 'argument',
-           the_type, (', %s' % explanation) if explanation is not None else '', safe_repr(value)),
-        locator=locator, level=Issue.FIELD)

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/extensions/aria_extension_tosca/simple_v1_0/misc.py
----------------------------------------------------------------------
diff --git a/extensions/aria_extension_tosca/simple_v1_0/misc.py b/extensions/aria_extension_tosca/simple_v1_0/misc.py
index 42fc1ad..74eba18 100644
--- a/extensions/aria_extension_tosca/simple_v1_0/misc.py
+++ b/extensions/aria_extension_tosca/simple_v1_0/misc.py
@@ -16,7 +16,7 @@
 from aria.utils.caching import cachedmethod
 from aria.utils.console import puts
 from aria.utils.formatting import as_raw
-from aria.parser import dsl_specification
+from aria.parser import implements_specification
 from aria.parser.presentation import (AsIsPresentation, has_fields, allow_unknown_fields,
                                       short_form_field, primitive_field, primitive_list_field,
                                       primitive_dict_unknown_fields, object_field,
@@ -36,7 +36,7 @@ from .presentation.field_validators import (constraint_clause_field_validator,
 from .presentation.types import (convert_shorthand_to_full_type_name,
                                  get_type_by_full_or_shorthand_name)
 
-@dsl_specification('3.5.1', 'tosca-simple-1.0')
+@implements_specification('3.5.1', 'tosca-simple-1.0')
 class Description(AsIsPresentation):
     """
     See the `TOSCA Simple Profile v1.0 cos01 specification <http://docs.oasis-open.org/tosca
@@ -53,10 +53,10 @@ class Description(AsIsPresentation):
 
 @allow_unknown_fields
 @has_fields
-@dsl_specification('3.9.3.2', 'tosca-simple-1.0')
+@implements_specification('3.9.3.2', 'tosca-simple-1.0')
 class MetaData(ExtensiblePresentation):
     @primitive_field(str)
-    @dsl_specification('3.9.3.3', 'tosca-simple-1.0')
+    @implements_specification('3.9.3.3', 'tosca-simple-1.0')
     def template_name(self):
         """
         This optional metadata keyname can be used to declare the name of service template as a
@@ -64,7 +64,7 @@ class MetaData(ExtensiblePresentation):
         """
 
     @primitive_field(str)
-    @dsl_specification('3.9.3.4', 'tosca-simple-1.0')
+    @implements_specification('3.9.3.4', 'tosca-simple-1.0')
     def template_author(self):
         """
         This optional metadata keyname can be used to declare the author(s) of the service template
@@ -72,7 +72,7 @@ class MetaData(ExtensiblePresentation):
         """
 
     @primitive_field(str)
-    @dsl_specification('3.9.3.5', 'tosca-simple-1.0')
+    @implements_specification('3.9.3.5', 'tosca-simple-1.0')
     def template_version(self):
         """
         This optional metadata keyname can be used to declare a domain specific version of the
@@ -87,7 +87,7 @@ class MetaData(ExtensiblePresentation):
 
 @short_form_field('url')
 @has_fields
-@dsl_specification('3.5.5', 'tosca-simple-1.0')
+@implements_specification('3.5.5', 'tosca-simple-1.0')
 class Repository(ExtensiblePresentation):
     """
     A repository definition defines a named external repository which contains deployment and
@@ -128,7 +128,7 @@ class Repository(ExtensiblePresentation):
 
 @short_form_field('file')
 @has_fields
-@dsl_specification('3.5.7', 'tosca-simple-1.0')
+@implements_specification('3.5.7', 'tosca-simple-1.0')
 class Import(ExtensiblePresentation):
     """
     An import definition is used within a TOSCA Service Template to locate and uniquely name another
@@ -177,7 +177,7 @@ class Import(ExtensiblePresentation):
         """
 
 @has_fields
-@dsl_specification('3.5.2', 'tosca-simple-1.0')
+@implements_specification('3.5.2-1', 'tosca-simple-1.0')
 class ConstraintClause(ExtensiblePresentation):
     """
     A constraint clause defines an operation along with one or more compatible values that can be
@@ -376,7 +376,7 @@ class SubstitutionMappingsCapability(AsIsPresentation):
         validate_subtitution_mappings_capability(context, self)
 
 @has_fields
-@dsl_specification('2.10', 'tosca-simple-1.0')
+@implements_specification('2.10', 'tosca-simple-1.0')
 class SubstitutionMappings(ExtensiblePresentation):
     @field_validator(type_validator('node type', convert_shorthand_to_full_type_name, 'node_types'))
     @primitive_field(str, required=True)

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/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 3bda7e2..99389e4 100644
--- a/extensions/aria_extension_tosca/simple_v1_0/modeling/__init__.py
+++ b/extensions/aria_extension_tosca/simple_v1_0/modeling/__init__.py
@@ -34,6 +34,8 @@ from aria.modeling.models import (Type, ServiceTemplate, NodeTemplate,
                                   SubstitutionTemplateMapping, InterfaceTemplate, OperationTemplate,
                                   ArtifactTemplate, Metadata, Parameter, PluginSpecification)
 
+from .constraints import (Equal, GreaterThan, GreaterOrEqual, LessThan, LessOrEqual, InRange,
+                          ValidValues, Length, MinLength, MaxLength, Pattern)
 from ..data_types import coerce_value
 
 
@@ -166,6 +168,8 @@ def create_node_template_model(context, service_template, node_template):
 
     create_parameter_models_from_values(model.properties,
                                         node_template._get_property_values(context))
+    create_parameter_models_from_values(model.attributes,
+                                        node_template._get_attribute_default_values(context))
     create_interface_template_models(context, service_template, model.interface_templates,
                                      node_template._get_interfaces(context))
 
@@ -181,10 +185,10 @@ def create_node_template_model(context, service_template, node_template):
             model.capability_templates[capability_name] = \
                 create_capability_template_model(context, service_template, capability)
 
-    if model.target_node_template_constraints:
+    if node_template.node_filter:
         model.target_node_template_constraints = []
-        create_node_filter_constraint_lambdas(context, node_template.node_filter,
-                                              model.target_node_template_constraints)
+        create_node_filter_constraints(context, node_template.node_filter,
+                                       model.target_node_template_constraints)
 
     return model
 
@@ -273,10 +277,10 @@ def create_requirement_template_model(context, service_template, requirement):
 
     model = RequirementTemplate(**model)
 
-    if model.target_node_template_constraints:
+    if requirement.node_filter:
         model.target_node_template_constraints = []
-        create_node_filter_constraint_lambdas(context, requirement.node_filter,
-                                              model.target_node_template_constraints)
+        create_node_filter_constraints(context, requirement.node_filter,
+                                       model.target_node_template_constraints)
 
     relationship = requirement.relationship
     if relationship is not None:
@@ -348,7 +352,7 @@ def create_interface_template_model(context, service_template, interface):
     inputs = interface.inputs
     if inputs:
         for input_name, the_input in inputs.iteritems():
-            model.inputs[input_name] = Parameter(name=input_name,
+            model.inputs[input_name] = Parameter(name=input_name, # pylint: disable=unexpected-keyword-arg
                                                  type_name=the_input.value.type,
                                                  value=the_input.value.value,
                                                  description=the_input.value.description)
@@ -395,7 +399,7 @@ def create_operation_template_model(context, service_template, operation):
     inputs = operation.inputs
     if inputs:
         for input_name, the_input in inputs.iteritems():
-            model.inputs[input_name] = Parameter(name=input_name,
+            model.inputs[input_name] = Parameter(name=input_name, # pylint: disable=unexpected-keyword-arg
                                                  type_name=the_input.value.type,
                                                  value=the_input.value.value,
                                                  description=the_input.value.description)
@@ -491,7 +495,7 @@ def create_workflow_operation_template_model(context, service_template, policy):
         elif prop_name == 'dependencies':
             model.dependencies = prop.value
         else:
-            model.inputs[prop_name] = Parameter(name=prop_name,
+            model.inputs[prop_name] = Parameter(name=prop_name, # pylint: disable=unexpected-keyword-arg
                                                 type_name=prop.type,
                                                 value=prop.value,
                                                 description=prop.description)
@@ -536,7 +540,7 @@ def create_types(context, root, types):
 def create_parameter_models_from_values(properties, source_properties):
     if source_properties:
         for property_name, prop in source_properties.iteritems():
-            properties[property_name] = Parameter(name=property_name,
+            properties[property_name] = Parameter(name=property_name, # pylint: disable=unexpected-keyword-arg
                                                   type_name=prop.type,
                                                   value=prop.value,
                                                   description=prop.description)
@@ -545,7 +549,7 @@ def create_parameter_models_from_values(properties, source_properties):
 def create_parameter_models_from_assignments(properties, source_properties):
     if source_properties:
         for property_name, prop in source_properties.iteritems():
-            properties[property_name] = Parameter(name=property_name,
+            properties[property_name] = Parameter(name=property_name, # pylint: disable=unexpected-keyword-arg
                                                   type_name=prop.value.type,
                                                   value=prop.value.value,
                                                   description=prop.value.description)
@@ -559,17 +563,13 @@ def create_interface_template_models(context, service_template, interfaces, sour
                 interfaces[interface_name] = interface
 
 
-def create_node_filter_constraint_lambdas(context, node_filter, target_node_template_constraints):
-    if node_filter is None:
-        return
-
+def create_node_filter_constraints(context, node_filter, target_node_template_constraints):
     properties = node_filter.properties
     if properties is not None:
         for property_name, constraint_clause in properties:
-            func = create_constraint_clause_lambda(context, node_filter, constraint_clause,
-                                                   property_name, None)
-            if func is not None:
-                target_node_template_constraints.append(func)
+            constraint = create_constraint(context, node_filter, constraint_clause, property_name,
+                                           None)
+            target_node_template_constraints.append(constraint)
 
     capabilities = node_filter.capabilities
     if capabilities is not None:
@@ -577,129 +577,64 @@ def create_node_filter_constraint_lambdas(context, node_filter, target_node_temp
             properties = capability.properties
             if properties is not None:
                 for property_name, constraint_clause in properties:
-                    func = create_constraint_clause_lambda(context, node_filter, constraint_clause,
-                                                           property_name, capability_name)
-                    if func is not None:
-                        target_node_template_constraints.append(func)
+                    constraint = create_constraint(context, node_filter, constraint_clause,
+                                                   property_name, capability_name)
+                    target_node_template_constraints.append(constraint)
 
 
-def create_constraint_clause_lambda(context, node_filter, constraint_clause, property_name, # pylint: disable=too-many-return-statements
-                                    capability_name):
+def create_constraint(context, node_filter, constraint_clause, property_name, capability_name): # pylint: disable=too-many-return-statements
     constraint_key = constraint_clause._raw.keys()[0]
-    the_type = constraint_clause._get_type(context)
 
-    def coerce_constraint(constraint, container):
-        constraint = coerce_value(context, node_filter, the_type, None, None, constraint,
-                                  constraint_key) if the_type is not None else constraint
-        if hasattr(constraint, '_evaluate'):
-            constraint = constraint._evaluate(context, container)
-        return constraint
-
-    def get_value(node_type):
-        if capability_name is not None:
-            capability = node_type.capability_templates.get(capability_name)
-            prop = capability.properties.get(property_name) if capability is not None else None
-            return prop.value if prop is not None else None
-        value = node_type.properties.get(property_name)
-        return value.value if value is not None else None
+    the_type = constraint_clause._get_type(context)
 
-    if constraint_key == 'equal':
-        def equal(node_type, container):
-            constraint = coerce_constraint(constraint_clause.equal, container)
-            value = get_value(node_type)
-            return value == constraint
+    def coerce_constraint(constraint):
+        if the_type is not None:
+            return coerce_value(context, node_filter, the_type, None, None, constraint,
+                                constraint_key)
+        else:
+            return constraint
 
-        return equal
+    def coerce_constraints(constraints):
+        if the_type is not None:
+            return tuple(coerce_constraint(constraint) for constraint in constraints)
+        else:
+            return constraints
 
+    if constraint_key == 'equal':
+        return Equal(property_name, capability_name,
+                     coerce_constraint(constraint_clause.equal))
     elif constraint_key == 'greater_than':
-        def greater_than(node_type, container):
-            constraint = coerce_constraint(constraint_clause.greater_than, container)
-            value = get_value(node_type)
-            return value > constraint
-
-        return greater_than
-
+        return GreaterThan(property_name, capability_name,
+                           coerce_constraint(constraint_clause.greater_than))
     elif constraint_key == 'greater_or_equal':
-        def greater_or_equal(node_type, container):
-            constraint = coerce_constraint(constraint_clause.greater_or_equal, container)
-            value = get_value(node_type)
-            return value >= constraint
-
-        return greater_or_equal
-
+        return GreaterOrEqual(property_name, capability_name,
+                              coerce_constraint(constraint_clause.greater_or_equal))
     elif constraint_key == 'less_than':
-        def less_than(node_type, container):
-            constraint = coerce_constraint(constraint_clause.less_than, container)
-            value = get_value(node_type)
-            return value < constraint
-
-        return less_than
-
+        return LessThan(property_name, capability_name,
+                        coerce_constraint(constraint_clause.less_than))
     elif constraint_key == 'less_or_equal':
-        def less_or_equal(node_type, container):
-            constraint = coerce_constraint(constraint_clause.less_or_equal, container)
-            value = get_value(node_type)
-            return value <= constraint
-
-        return less_or_equal
-
+        return LessOrEqual(property_name, capability_name,
+                           coerce_constraint(constraint_clause.less_or_equal))
     elif constraint_key == 'in_range':
-        def in_range(node_type, container):
-            lower, upper = constraint_clause.in_range
-            lower, upper = coerce_constraint(lower, container), coerce_constraint(upper, container)
-            value = get_value(node_type)
-            if value < lower:
-                return False
-            if (upper != 'UNBOUNDED') and (value > upper):
-                return False
-            return True
-
-        return in_range
-
+        return InRange(property_name, capability_name,
+                       coerce_constraints(constraint_clause.in_range))
     elif constraint_key == 'valid_values':
-        def valid_values(node_type, container):
-            constraint = tuple(coerce_constraint(v, container)
-                               for v in constraint_clause.valid_values)
-            value = get_value(node_type)
-            return value in constraint
-
-        return valid_values
-
+        return ValidValues(property_name, capability_name,
+                           coerce_constraints(constraint_clause.valid_values))
     elif constraint_key == 'length':
-        def length(node_type, container): # pylint: disable=unused-argument
-            constraint = constraint_clause.length
-            value = get_value(node_type)
-            return len(value) == constraint
-
-        return length
-
+        return Length(property_name, capability_name,
+                      coerce_constraint(constraint_clause.length))
     elif constraint_key == 'min_length':
-        def min_length(node_type, container): # pylint: disable=unused-argument
-            constraint = constraint_clause.min_length
-            value = get_value(node_type)
-            return len(value) >= constraint
-
-        return min_length
-
+        return MinLength(property_name, capability_name,
+                         coerce_constraint(constraint_clause.min_length))
     elif constraint_key == 'max_length':
-        def max_length(node_type, container): # pylint: disable=unused-argument
-            constraint = constraint_clause.max_length
-            value = get_value(node_type)
-            return len(value) >= constraint
-
-        return max_length
-
+        return MaxLength(property_name, capability_name,
+                         coerce_constraint(constraint_clause.max_length))
     elif constraint_key == 'pattern':
-        def pattern(node_type, container): # pylint: disable=unused-argument
-            constraint = constraint_clause.pattern
-            # Note: the TOSCA 1.0 spec does not specify the regular expression grammar, so we will
-            # just use Python's
-            value = node_type.properties.get(property_name)
-            return re.match(constraint, str(value)) is not None
-
-        return pattern
-
-    return None
+        return Pattern(property_name, capability_name,
+                       coerce_constraint(constraint_clause.pattern))
+    else:
+        raise ValueError('malformed node_filter: {0}'.format(constraint_key))
 
 
 def split_prefix(string):

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/extensions/aria_extension_tosca/simple_v1_0/modeling/constraints.py
----------------------------------------------------------------------
diff --git a/extensions/aria_extension_tosca/simple_v1_0/modeling/constraints.py b/extensions/aria_extension_tosca/simple_v1_0/modeling/constraints.py
new file mode 100644
index 0000000..7c99eab
--- /dev/null
+++ b/extensions/aria_extension_tosca/simple_v1_0/modeling/constraints.py
@@ -0,0 +1,144 @@
+# 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.
+
+import re
+
+from aria.modeling.contraints import NodeTemplateConstraint
+from aria.modeling.utils import NodeTemplateContainerHolder
+from aria.modeling.functions import evaluate
+from aria.parser import implements_specification
+
+
+@implements_specification('3.5.2-2', 'tosca-simple-1.0')
+class EvaluatingNodeTemplateConstraint(NodeTemplateConstraint):
+    """
+    A version of :class:`NodeTemplateConstraint` with boilerplate initialization for TOSCA
+    constraints.
+    """
+
+    def __init__(self, property_name, capability_name, constraint, as_list=False):
+        self.property_name = property_name
+        self.capability_name = capability_name
+        self.constraint = constraint
+        self.as_list = as_list
+
+    def matches(self, source_node_template, target_node_template):
+        # TOSCA node template constraints can refer to either capability properties or node
+        # template properties
+        if self.capability_name is not None:
+            # Capability property
+            capability = target_node_template.capability_templates.get(self.capability_name)
+            value = capability.properties.get(self.property_name) \
+                if capability is not None else None # Parameter
+        else:
+            # Node template property
+            value = target_node_template.properties.get(self.property_name) # Parameter
+
+        value = value.value if value is not None else None
+
+        container_holder = NodeTemplateContainerHolder(source_node_template)
+
+        if self.as_list:
+            constraints = []
+            for constraint in self.constraint:
+                evaluation = evaluate(constraint, container_holder)
+                if evaluation is not None:
+                    constraints.append(evaluation.value)
+                else:
+                    constraints.append(constraint)
+            constraint = constraints
+        else:
+            evaluation = evaluate(self.constraint, container_holder)
+            if evaluation is not None:
+                constraint = evaluation.value
+            else:
+                constraint = self.constraint
+
+        return self.matches_evaluated(value, constraint)
+
+    def matches_evaluated(self, value, constraint):
+        raise NotImplementedError
+
+
+class Equal(EvaluatingNodeTemplateConstraint):
+    def matches_evaluated(self, value, constraint):
+        return value == constraint
+
+
+class GreaterThan(EvaluatingNodeTemplateConstraint):
+    def matches_evaluated(self, value, constraint):
+        return value > constraint
+
+
+class GreaterOrEqual(EvaluatingNodeTemplateConstraint):
+    def matches_evaluated(self, value, constraint):
+        return value >= constraint
+
+
+class LessThan(EvaluatingNodeTemplateConstraint):
+    def matches_evaluated(self, value, constraint):
+        return value < constraint
+
+
+class LessOrEqual(EvaluatingNodeTemplateConstraint):
+    def matches_evaluated(self, value, constraint):
+        return value <= constraint
+
+
+class InRange(EvaluatingNodeTemplateConstraint):
+    def __init__(self, property_name, capability_name, constraint):
+        super(InRange, self).__init__(property_name, capability_name, constraint, as_list=True)
+
+    def matches_evaluated(self, value, constraints):
+        lower, upper = constraints
+        if value < lower:
+            return False
+        if (upper != 'UNBOUNDED') and (value > upper):
+            return False
+        return True
+
+
+class ValidValues(EvaluatingNodeTemplateConstraint):
+    def __init__(self, property_name, capability_name, constraint):
+        super(ValidValues, self).__init__(property_name, capability_name, constraint, as_list=True)
+
+    def matches_evaluated(self, value, constraints):
+        return value in constraints
+
+
+class Length(EvaluatingNodeTemplateConstraint):
+    def matches_evaluated(self, value, constraint):
+        return len(value) == constraint
+
+
+class MinLength(EvaluatingNodeTemplateConstraint):
+    def matches_evaluated(self, value, constraint):
+        return len(value) >= constraint
+
+
+class MaxLength(EvaluatingNodeTemplateConstraint):
+    def matches_evaluated(self, value, constraint):
+        return len(value) <= constraint
+
+
+class Pattern(EvaluatingNodeTemplateConstraint):
+    def matches_evaluated(self, value, constraint):
+        # From TOSCA 1.0 3.5.2.1:
+        #
+        # "Note: Future drafts of this specification will detail the use of regular expressions and
+        # reference an appropriate standardized grammar."
+        #
+        # So we will just use Python's.
+        return re.match(constraint, unicode(value)) is not None

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/extensions/aria_extension_tosca/simple_v1_0/modeling/data_types.py
----------------------------------------------------------------------
diff --git a/extensions/aria_extension_tosca/simple_v1_0/modeling/data_types.py b/extensions/aria_extension_tosca/simple_v1_0/modeling/data_types.py
index 99dcfea..3952785 100644
--- a/extensions/aria_extension_tosca/simple_v1_0/modeling/data_types.py
+++ b/extensions/aria_extension_tosca/simple_v1_0/modeling/data_types.py
@@ -16,13 +16,14 @@
 import re
 
 from aria.utils.collections import OrderedDict
-from aria.utils.formatting import full_type_name, safe_repr
+from aria.utils.formatting import safe_repr
+from aria.utils.type import full_type_name
 from aria.utils.imports import import_fullname
-from aria.parser import dsl_specification
+from aria.parser import implements_specification
 from aria.parser.presentation import (get_locator, validate_primitive)
 from aria.parser.validation import Issue
 
-from ..functions import get_function
+from .functions import get_function
 from ..presentation.types import get_type_by_full_or_shorthand_name
 
 #
@@ -295,8 +296,12 @@ def apply_constraint_to_value(context, presentation, constraint_clause, value):
     elif constraint_key == 'pattern':
         constraint = constraint_clause.pattern
         try:
-            # Note: the TOSCA 1.0 spec does not specify the regular expression grammar, so we will
-            # just use Python's
+            # From TOSCA 1.0 3.5.2.1:
+            #
+            # "Note: Future drafts of this specification will detail the use of regular expressions
+            # and reference an appropriate standardized grammar."
+            #
+            # So we will just use Python's.
             if re.match(constraint, str(value)) is None:
                 report('does not match regular expression', constraint)
                 return False
@@ -327,20 +332,20 @@ def get_data_type_value(context, presentation, field_name, type_name):
 
 PRIMITIVE_DATA_TYPES = {
     # YAML 1.2:
-    'tag:yaml.org,2002:str': str,
+    'tag:yaml.org,2002:str': unicode,
     'tag:yaml.org,2002:integer': int,
     'tag:yaml.org,2002:float': float,
     'tag:yaml.org,2002:bool': bool,
     'tag:yaml.org,2002:null': None.__class__,
 
     # TOSCA aliases:
-    'string': str,
+    'string': unicode,
     'integer': int,
     'float': float,
     'boolean': bool,
     'null': None.__class__}
 
-@dsl_specification('3.2.1', 'tosca-simple-1.0')
+@implements_specification('3.2.1-3', 'tosca-simple-1.0')
 def get_primitive_data_type(type_name):
     """
     Many of the types we use in this profile are built-in types from the YAML 1.2 specification
@@ -371,6 +376,8 @@ def coerce_value(context, presentation, the_type, entry_schema, constraints, val
     If the extension is present, we will delegate to that hook.
     """
 
+    # TODO: should support models as well as presentations
+
     is_function, func = get_function(context, presentation, value)
     if is_function:
         return func

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/extensions/aria_extension_tosca/simple_v1_0/modeling/functions.py
----------------------------------------------------------------------
diff --git a/extensions/aria_extension_tosca/simple_v1_0/modeling/functions.py b/extensions/aria_extension_tosca/simple_v1_0/modeling/functions.py
new file mode 100644
index 0000000..7089ed9
--- /dev/null
+++ b/extensions/aria_extension_tosca/simple_v1_0/modeling/functions.py
@@ -0,0 +1,677 @@
+# 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 cStringIO import StringIO
+import re
+
+from aria.utils.collections import FrozenList
+from aria.utils.formatting import (as_raw, safe_repr)
+from aria.utils.type import full_type_name
+from aria.parser import implements_specification
+from aria.parser.exceptions import InvalidValueError
+from aria.parser.validation import Issue
+from aria.modeling.exceptions import CannotEvaluateFunctionException
+from aria.modeling.models import (Node, NodeTemplate, Relationship, RelationshipTemplate)
+from aria.modeling.functions import (Function, Evaluation)
+
+
+#
+# Intrinsic
+#
+
+@implements_specification('4.3.1', 'tosca-simple-1.0')
+class Concat(Function):
+    """
+    The :code:`concat` function is used to concatenate two or more string values within a TOSCA
+    service template.
+    """
+
+    def __init__(self, context, presentation, argument):
+        self.locator = presentation._locator
+
+        if not isinstance(argument, list):
+            raise InvalidValueError(
+                'function "concat" argument must be a list of string expressions: {0}'
+                .format(safe_repr(argument)),
+                locator=self.locator)
+
+        string_expressions = []
+        for index, an_argument in enumerate(argument):
+            string_expressions.append(parse_string_expression(context, presentation, 'concat',
+                                                              index, None, an_argument))
+        self.string_expressions = FrozenList(string_expressions)
+
+    @property
+    def as_raw(self):
+        string_expressions = []
+        for string_expression in self.string_expressions:
+            if hasattr(string_expression, 'as_raw'):
+                string_expression = as_raw(string_expression)
+            string_expressions.append(string_expression)
+        return {'concat': string_expressions}
+
+    def __evaluate__(self, container_holder):
+        final = True
+        value = StringIO()
+        for e in self.string_expressions:
+            e, final = evaluate(e, final, container_holder)
+            if e is not None:
+                value.write(unicode(e))
+        value = value.getvalue()
+        return Evaluation(value, final)
+
+
+@implements_specification('4.3.2', 'tosca-simple-1.0')
+class Token(Function):
+    """
+    The :code:`token` function is used within a TOSCA service template on a string to parse out
+    (tokenize) substrings separated by one or more token characters within a larger string.
+    """
+
+    def __init__(self, context, presentation, argument):
+        self.locator = presentation._locator
+
+        if (not isinstance(argument, list)) or (len(argument) != 3):
+            raise InvalidValueError('function "token" argument must be a list of 3 parameters: {0}'
+                                    .format(safe_repr(argument)),
+                                    locator=self.locator)
+
+        self.string_with_tokens = parse_string_expression(context, presentation, 'token', 0,
+                                                          'the string to tokenize', argument[0])
+        self.string_of_token_chars = parse_string_expression(context, presentation, 'token', 1,
+                                                             'the token separator characters',
+                                                             argument[1])
+        self.substring_index = parse_int(context, presentation, 'token', 2,
+                                         'the 0-based index of the token to return', argument[2])
+
+    @property
+    def as_raw(self):
+        string_with_tokens = self.string_with_tokens
+        if hasattr(string_with_tokens, 'as_raw'):
+            string_with_tokens = as_raw(string_with_tokens)
+        string_of_token_chars = self.string_of_token_chars
+        if hasattr(string_of_token_chars, 'as_raw'):
+            string_of_token_chars = as_raw(string_of_token_chars)
+        return {'token': [string_with_tokens, string_of_token_chars, self.substring_index]}
+
+    def __evaluate__(self, container_holder):
+        final = True
+        string_with_tokens, final = evaluate(self.string_with_tokens, final, container_holder)
+        string_of_token_chars, final = evaluate(self.string_of_token_chars, final, container_holder)
+
+        if string_of_token_chars:
+            regex = '[' + ''.join(re.escape(c) for c in string_of_token_chars) + ']'
+            split = re.split(regex, string_with_tokens)
+            if self.substring_index < len(split):
+                return Evaluation(split[self.substring_index], final)
+
+        raise CannotEvaluateFunctionException()
+
+
+#
+# Property
+#
+
+@implements_specification('4.4.1', 'tosca-simple-1.0')
+class GetInput(Function):
+    """
+    The :code:`get_input` function is used to retrieve the values of properties declared within the
+    inputs section of a TOSCA Service Template.
+    """
+
+    def __init__(self, context, presentation, argument):
+        self.locator = presentation._locator
+
+        self.input_property_name = parse_string_expression(context, presentation, 'get_input',
+                                                           None, 'the input property name',
+                                                           argument)
+
+        if isinstance(self.input_property_name, basestring):
+            the_input = context.presentation.get_from_dict('service_template', 'topology_template',
+                                                           'inputs', self.input_property_name)
+            if the_input is None:
+                raise InvalidValueError(
+                    'function "get_input" argument is not a valid input name: {0}'
+                    .format(safe_repr(argument)),
+                    locator=self.locator)
+
+    @property
+    def as_raw(self):
+        return {'get_input': as_raw(self.input_property_name)}
+
+    def __evaluate__(self, container_holder):
+        service = container_holder.service
+        if service is None:
+            raise CannotEvaluateFunctionException()
+
+        value = service.inputs.get(self.input_property_name)
+        if value is not None:
+            value = value.value
+            return Evaluation(value, False) # We never return final evaluations!
+
+        raise InvalidValueError(
+            'function "get_input" argument is not a valid input name: {0}'
+            .format(safe_repr(self.input_property_name)),
+            locator=self.locator)
+
+
+@implements_specification('4.4.2', 'tosca-simple-1.0')
+class GetProperty(Function):
+    """
+    The :code:`get_property` function is used to retrieve property values between modelable entities
+    defined in the same service template.
+    """
+
+    def __init__(self, context, presentation, argument):
+        self.locator = presentation._locator
+
+        if (not isinstance(argument, list)) or (len(argument) < 2):
+            raise InvalidValueError(
+                'function "get_property" argument must be a list of at least 2 string expressions: '
+                '{0}'.format(safe_repr(argument)),
+                locator=self.locator)
+
+        self.modelable_entity_name = parse_modelable_entity_name(context, presentation,
+                                                                 'get_property', 0, argument[0])
+        # The first of these will be tried as a req-or-cap name:
+        self.nested_property_name_or_index = argument[1:]
+
+    @property
+    def as_raw(self):
+        return {'get_property': [self.modelable_entity_name] + self.nested_property_name_or_index}
+
+    def __evaluate__(self, container_holder):
+        modelable_entities = get_modelable_entities(container_holder, 'get_property', self.locator,
+                                                    self.modelable_entity_name)
+        req_or_cap_name = self.nested_property_name_or_index[0]
+
+        for modelable_entity in modelable_entities:
+            properties = None
+
+            if hasattr(modelable_entity, 'requirement_templates') \
+                and modelable_entity.requirement_templates \
+                and (req_or_cap_name in [v.name for v in modelable_entity.requirement_templates]):
+                for requirement_template in modelable_entity.requirement_templates:
+                    if requirement_template.name == req_or_cap_name:
+                        # First argument refers to a requirement
+                        # TODO: should follow to matched capability in other node...
+                        raise CannotEvaluateFunctionException()
+                        # break
+                nested_property_name_or_index = self.nested_property_name_or_index[1:]
+            elif hasattr(modelable_entity, 'capability_templates') \
+                and modelable_entity.capability_templates \
+                and (req_or_cap_name in modelable_entity.capability_templates):
+                # First argument refers to a capability
+                properties = modelable_entity.capability_templates[req_or_cap_name].properties
+                nested_property_name_or_index = self.nested_property_name_or_index[1:]
+            else:
+                properties = modelable_entity.properties
+                nested_property_name_or_index = self.nested_property_name_or_index
+
+            evaluation = get_modelable_entity_parameter(modelable_entity, properties,
+                                                        nested_property_name_or_index)
+            if evaluation is not None:
+                return evaluation
+
+        raise InvalidValueError(
+            'function "get_property" could not find "{0}" in modelable entity "{1}"'
+            .format('.'.join(self.nested_property_name_or_index), self.modelable_entity_name),
+            locator=self.locator)
+
+
+#
+# Attribute
+#
+
+@implements_specification('4.5.1', 'tosca-simple-1.0')
+class GetAttribute(Function):
+    """
+    The :code:`get_attribute` function is used to retrieve the values of named attributes declared
+    by the referenced node or relationship template name.
+    """
+
+    def __init__(self, context, presentation, argument):
+        self.locator = presentation._locator
+
+        if (not isinstance(argument, list)) or (len(argument) < 2):
+            raise InvalidValueError(
+                'function "get_attribute" argument must be a list of at least 2 string expressions:'
+                ' {0}'.format(safe_repr(argument)),
+                locator=self.locator)
+
+        self.modelable_entity_name = parse_modelable_entity_name(context, presentation,
+                                                                 'get_attribute', 0, argument[0])
+        # The first of these will be tried as a req-or-cap name:
+        self.nested_attribute_name_or_index = argument[1:]
+
+    @property
+    def as_raw(self):
+        return {'get_attribute': [self.modelable_entity_name] + self.nested_attribute_name_or_index}
+
+    def __evaluate__(self, container_holder):
+        modelable_entities = get_modelable_entities(container_holder, 'get_attribute', self.locator,
+                                                    self.modelable_entity_name)
+        for modelable_entity in modelable_entities:
+            attributes = modelable_entity.attributes
+            nested_attribute_name_or_index = self.nested_attribute_name_or_index
+            evaluation = get_modelable_entity_parameter(modelable_entity, attributes,
+                                                        nested_attribute_name_or_index)
+            if evaluation is not None:
+                evaluation.final = False # We never return final evaluations!
+                return evaluation
+
+        raise InvalidValueError(
+            'function "get_attribute" could not find "{0}" in modelable entity "{1}"'
+            .format('.'.join(self.nested_attribute_name_or_index), self.modelable_entity_name),
+            locator=self.locator)
+
+
+#
+# Operation
+#
+
+@implements_specification('4.6.1', 'tosca-simple-1.0') # pylint: disable=abstract-method
+class GetOperationOutput(Function):
+    """
+    The :code:`get_operation_output` function is used to retrieve the values of variables exposed /
+    exported from an interface operation.
+    """
+
+    def __init__(self, context, presentation, argument):
+        self.locator = presentation._locator
+
+        if (not isinstance(argument, list)) or (len(argument) != 4):
+            raise InvalidValueError(
+                'function "get_operation_output" argument must be a list of 4 parameters: {0}'
+                .format(safe_repr(argument)),
+                locator=self.locator)
+
+        self.modelable_entity_name = parse_string_expression(context, presentation,
+                                                             'get_operation_output', 0,
+                                                             'modelable entity name', argument[0])
+        self.interface_name = parse_string_expression(context, presentation, 'get_operation_output',
+                                                      1, 'the interface name', argument[1])
+        self.operation_name = parse_string_expression(context, presentation, 'get_operation_output',
+                                                      2, 'the operation name', argument[2])
+        self.output_variable_name = parse_string_expression(context, presentation,
+                                                            'get_operation_output', 3,
+                                                            'the output name', argument[3])
+
+    @property
+    def as_raw(self):
+        interface_name = self.interface_name
+        if hasattr(interface_name, 'as_raw'):
+            interface_name = as_raw(interface_name)
+        operation_name = self.operation_name
+        if hasattr(operation_name, 'as_raw'):
+            operation_name = as_raw(operation_name)
+        output_variable_name = self.output_variable_name
+        if hasattr(output_variable_name, 'as_raw'):
+            output_variable_name = as_raw(output_variable_name)
+        return {'get_operation_output': [self.modelable_entity_name, interface_name, operation_name,
+                                         output_variable_name]}
+
+
+#
+# Navigation
+#
+
+@implements_specification('4.7.1', 'tosca-simple-1.0')
+class GetNodesOfType(Function):
+    """
+    The :code:`get_nodes_of_type` function can be used to retrieve a list of all known instances of
+    nodes of the declared Node Type.
+    """
+
+    def __init__(self, context, presentation, argument):
+        self.locator = presentation._locator
+
+        self.node_type_name = parse_string_expression(context, presentation, 'get_nodes_of_type',
+                                                      None, 'the node type name', argument)
+
+        if isinstance(self.node_type_name, basestring):
+            node_types = context.presentation.get('service_template', 'node_types')
+            if (node_types is None) or (self.node_type_name not in node_types):
+                raise InvalidValueError(
+                    'function "get_nodes_of_type" argument is not a valid node type name: {0}'
+                    .format(safe_repr(argument)),
+                    locator=self.locator)
+
+    @property
+    def as_raw(self):
+        node_type_name = self.node_type_name
+        if hasattr(node_type_name, 'as_raw'):
+            node_type_name = as_raw(node_type_name)
+        return {'get_nodes_of_type': node_type_name}
+
+    def __evaluate__(self, container):
+        pass
+
+
+#
+# Artifact
+#
+
+@implements_specification('4.8.1', 'tosca-simple-1.0') # pylint: disable=abstract-method
+class GetArtifact(Function):
+    """
+    The :code:`get_artifact` function is used to retrieve artifact location between modelable
+    entities defined in the same service template.
+    """
+
+    def __init__(self, context, presentation, argument):
+        self.locator = presentation._locator
+
+        if (not isinstance(argument, list)) or (len(argument) < 2) or (len(argument) > 4):
+            raise InvalidValueError(
+                'function "get_artifact" argument must be a list of 2 to 4 parameters: {0}'
+                .format(safe_repr(argument)),
+                locator=self.locator)
+
+        self.modelable_entity_name = parse_string_expression(context, presentation, 'get_artifact',
+                                                             0, 'modelable entity name',
+                                                             argument[0])
+        self.artifact_name = parse_string_expression(context, presentation, 'get_artifact', 1,
+                                                     'the artifact name', argument[1])
+        self.location = parse_string_expression(context, presentation, 'get_artifact', 2,
+                                                'the location or "LOCAL_FILE"', argument[2])
+        self.remove = parse_bool(context, presentation, 'get_artifact', 3, 'the removal flag',
+                                 argument[3])
+
+    @property
+    def as_raw(self):
+        artifact_name = self.artifact_name
+        if hasattr(artifact_name, 'as_raw'):
+            artifact_name = as_raw(artifact_name)
+        location = self.location
+        if hasattr(location, 'as_raw'):
+            location = as_raw(location)
+        return {'get_artifacts': [self.modelable_entity_name, artifact_name, location, self.remove]}
+
+
+#
+# Utils
+#
+
+def get_function(context, presentation, value):
+    functions = context.presentation.presenter.functions
+    if isinstance(value, dict) and (len(value) == 1):
+        key = value.keys()[0]
+        if key in functions:
+            try:
+                return True, functions[key](context, presentation, value[key])
+            except InvalidValueError as e:
+                context.validation.report(issue=e.issue)
+                return True, None
+    return False, None
+
+
+def parse_string_expression(context, presentation, name, index, explanation, value): # pylint: disable=unused-argument
+    is_function, func = get_function(context, presentation, value)
+    if is_function:
+        return func
+    else:
+        value = str(value)
+    return value
+
+
+def parse_int(context, presentation, name, index, explanation, value): # pylint: disable=unused-argument
+    if not isinstance(value, int):
+        try:
+            value = int(value)
+        except ValueError:
+            raise invalid_value(name, index, 'an integer', explanation, value,
+                                presentation._locator)
+    return value
+
+
+def parse_bool(context, presentation, name, index, explanation, value): # pylint: disable=unused-argument
+    if not isinstance(value, bool):
+        raise invalid_value(name, index, 'a boolean', explanation, value, presentation._locator)
+    return value
+
+
+def parse_modelable_entity_name(context, presentation, name, index, value):
+    value = parse_string_expression(context, presentation, name, index, 'the modelable entity name',
+                                    value)
+    if value == 'SELF':
+        the_self, _ = parse_self(presentation)
+        if the_self is None:
+            raise invalid_modelable_entity_name(name, index, value, presentation._locator,
+                                                'a node template or a relationship template')
+    elif value == 'HOST':
+        _, self_variant = parse_self(presentation)
+        if self_variant != 'node_template':
+            raise invalid_modelable_entity_name(name, index, value, presentation._locator,
+                                                'a node template')
+    elif (value == 'SOURCE') or (value == 'TARGET'):
+        _, self_variant = parse_self(presentation)
+        if self_variant != 'relationship_template':
+            raise invalid_modelable_entity_name(name, index, value, presentation._locator,
+                                                'a relationship template')
+    elif isinstance(value, basestring):
+        node_templates = \
+            context.presentation.get('service_template', 'topology_template', 'node_templates') \
+            or {}
+        relationship_templates = \
+            context.presentation.get('service_template', 'topology_template',
+                                     'relationship_templates') \
+            or {}
+        if (value not in node_templates) and (value not in relationship_templates):
+            raise InvalidValueError(
+                'function "{0}" parameter {1:d} is not a valid modelable entity name: {2}'
+                .format(name, index + 1, safe_repr(value)),
+                locator=presentation._locator, level=Issue.BETWEEN_TYPES)
+    return value
+
+
+def parse_self(presentation):
+    from ..types import (NodeType, RelationshipType)
+    from ..templates import (
+        NodeTemplate as NodeTemplatePresentation,
+        RelationshipTemplate as RelationshipTemplatePresentation
+    )
+
+    if presentation is None:
+        return None, None
+    elif isinstance(presentation, NodeTemplatePresentation) or isinstance(presentation, NodeType):
+        return presentation, 'node_template'
+    elif isinstance(presentation, RelationshipTemplatePresentation) \
+        or isinstance(presentation, RelationshipType):
+        return presentation, 'relationship_template'
+    else:
+        return parse_self(presentation._container)
+
+
+def evaluate(value, final, container_holder):
+    """
+    Calls ``__evaluate__`` and passes on ``final`` state.
+    """
+
+    if hasattr(value, '__evaluate__'):
+        value = value.__evaluate__(container_holder)
+        if not value.final:
+            final = False
+        return value.value, final
+    else:
+        return value, final
+
+
+@implements_specification('4.1', 'tosca-simple-1.0')
+def get_modelable_entities(container_holder, name, locator, modelable_entity_name):
+    """
+    The following keywords MAY be used in some TOSCA function in place of a TOSCA Node or
+    Relationship Template name.
+    """
+
+    if modelable_entity_name == 'SELF':
+        return get_self(container_holder, name, locator)
+    elif modelable_entity_name == 'HOST':
+        return get_hosts(container_holder, name, locator)
+    elif modelable_entity_name == 'SOURCE':
+        return get_source(container_holder, name, locator)
+    elif modelable_entity_name == 'TARGET':
+        return get_target(container_holder, name, locator)
+    elif isinstance(modelable_entity_name, basestring):
+        modelable_entities = []
+
+        service = container_holder.service
+        if service is not None:
+            for node in service.nodes.itervalues():
+                if node.node_template.name == modelable_entity_name:
+                    modelable_entities.append(node)
+        else:
+            service_template = container_holder.service_template
+            if service_template is not None:
+                for node_template in service_template.node_templates.itervalues():
+                    if node_template.name == modelable_entity_name:
+                        modelable_entities.append(node_template)
+
+        if not modelable_entities:
+            raise CannotEvaluateFunctionException()
+
+        return modelable_entities
+
+    raise InvalidValueError('function "{0}" could not find modelable entity "{1}"'
+                            .format(name, modelable_entity_name),
+                            locator=locator)
+
+
+def get_self(container_holder, name, locator):
+    """
+    A TOSCA orchestrator will interpret this keyword as the Node or Relationship Template instance
+    that contains the function at the time the function is evaluated.
+    """
+
+    container = container_holder.container
+    if (not isinstance(container, Node)) and \
+        (not isinstance(container, NodeTemplate)) and \
+        (not isinstance(container, Relationship)) and \
+        (not isinstance(container, RelationshipTemplate)):
+        raise InvalidValueError('function "{0}" refers to "SELF" but it is not contained in '
+                                'a node or a relationship: {1}'.format(name,
+                                                                       full_type_name(container)),
+                                locator=locator)
+
+    return [container]
+
+
+def get_hosts(container_holder, name, locator):
+    """
+    A TOSCA orchestrator will interpret this keyword to refer to the all nodes that "host" the node
+    using this reference (i.e., as identified by its HostedOn relationship).
+
+    Specifically, TOSCA orchestrators that encounter this keyword when evaluating the get_attribute
+    or :code:`get_property` functions SHALL search each node along the "HostedOn" relationship chain
+    starting at the immediate node that hosts the node where the function was evaluated (and then
+    that node's host node, and so forth) until a match is found or the "HostedOn" relationship chain
+    ends.
+    """
+
+    container = container_holder.container
+    if (not isinstance(container, Node)) and (not isinstance(container, NodeTemplate)):
+        raise InvalidValueError('function "{0}" refers to "HOST" but it is not contained in '
+                                'a node: {1}'.format(name, full_type_name(container)),
+                                locator=locator)
+
+    if not isinstance(container, Node):
+        # NodeTemplate does not have "host"; we'll wait until instantiation
+        raise CannotEvaluateFunctionException()
+
+    host = container.host
+    if host is None:
+        # We might have a host later
+        raise CannotEvaluateFunctionException()
+
+    return [host]
+
+
+def get_source(container_holder, name, locator):
+    """
+    A TOSCA orchestrator will interpret this keyword as the Node Template instance that is at the
+    source end of the relationship that contains the referencing function.
+    """
+
+    container = container_holder.container
+    if (not isinstance(container, Relationship)) and \
+        (not isinstance(container, RelationshipTemplate)):
+        raise InvalidValueError('function "{0}" refers to "SOURCE" but it is not contained in '
+                                'a relationship: {1}'.format(name, full_type_name(container)),
+                                locator=locator)
+
+    if not isinstance(container, RelationshipTemplate):
+        # RelationshipTemplate does not have "source_node"; we'll wait until instantiation
+        raise CannotEvaluateFunctionException()
+
+    return [container.source_node]
+
+
+def get_target(container_holder, name, locator):
+    """
+    A TOSCA orchestrator will interpret this keyword as the Node Template instance that is at the
+    target end of the relationship that contains the referencing function.
+    """
+
+    container = container_holder.container
+    if (not isinstance(container, Relationship)) and \
+        (not isinstance(container, RelationshipTemplate)):
+        raise InvalidValueError('function "{0}" refers to "TARGET" but it is not contained in '
+                                'a relationship: {1}'.format(name, full_type_name(container)),
+                                locator=locator)
+
+    if not isinstance(container, RelationshipTemplate):
+        # RelationshipTemplate does not have "target_node"; we'll wait until instantiation
+        raise CannotEvaluateFunctionException()
+
+    return [container.target_node]
+
+
+def get_modelable_entity_parameter(modelable_entity, parameters, nested_parameter_name_or_index):
+    if not parameters:
+        return False, True, None
+
+    found = True
+    final = True
+    value = parameters
+
+    for name_or_index in nested_parameter_name_or_index:
+        if (isinstance(value, dict) and (name_or_index in value)) \
+            or ((isinstance(value, list) and (name_or_index < len(value)))):
+            value = value[name_or_index] # Parameter
+            # We are not using Parameter.value, but rather Parameter._value, because we want to make
+            # sure to get "final" (it is swallowed by Parameter.value)
+            value, final = evaluate(value._value, final, value)
+        else:
+            found = False
+            break
+
+    return Evaluation(value, final) if found else None
+
+
+def invalid_modelable_entity_name(name, index, value, locator, contexts):
+    return InvalidValueError('function "{0}" parameter {1:d} can be "{2}" only in {3}'
+                             .format(name, index + 1, value, contexts),
+                             locator=locator, level=Issue.FIELD)
+
+
+def invalid_value(name, index, the_type, explanation, value, locator):
+    return InvalidValueError(
+        'function "{0}" {1} is not {2}{3}: {4}'
+        .format(name,
+                'parameter {0:d}'.format(index + 1) if index is not None else 'argument',
+                the_type,
+                ', {0}'.format(explanation) if explanation is not None else '',
+                safe_repr(value)),
+        locator=locator, level=Issue.FIELD)



[06/19] incubator-ariatosca git commit: ARIA-165 Make node name suffix UUIDs become more readable

Posted by ra...@apache.org.
ARIA-165 Make node name suffix UUIDs become more readable


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

Branch: refs/heads/ARIA-208-Missing-back-refrences-for-models
Commit: 1febf80dee57b837d2ed937bcdaa080bdc3bd822
Parents: b11fbc9
Author: max-orlov <ma...@gigaspaces.com>
Authored: Mon May 8 15:25:37 2017 +0300
Committer: max-orlov <ma...@gigaspaces.com>
Committed: Tue May 9 11:24:07 2017 +0300

----------------------------------------------------------------------
 aria/modeling/service_template.py | 8 ++++++--
 aria/parser/modeling/context.py   | 5 -----
 2 files changed, 6 insertions(+), 7 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/1febf80d/aria/modeling/service_template.py
----------------------------------------------------------------------
diff --git a/aria/modeling/service_template.py b/aria/modeling/service_template.py
index 7fab4fc..f721b64 100644
--- a/aria/modeling/service_template.py
+++ b/aria/modeling/service_template.py
@@ -549,9 +549,13 @@ class NodeTemplateBase(TemplateModelMixin):
             ('requirement_templates', formatting.as_raw_list(self.requirement_templates))))
 
     def instantiate(self, container):
-        context = ConsumptionContext.get_thread_local()
         from . import models
-        name = context.modeling.generate_node_id(self.name)
+        if self.nodes:
+            highest_name_suffix = max(int(n.name.rsplit('_', 1)[-1]) for n in self.nodes)
+            suffix = highest_name_suffix + 1
+        else:
+            suffix = 1
+        name = '{name}_{index}'.format(name=self.name, index=suffix)
         node = models.Node(name=name,
                            type=self.type,
                            description=deepcopy_with_locators(self.description),

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/1febf80d/aria/parser/modeling/context.py
----------------------------------------------------------------------
diff --git a/aria/parser/modeling/context.py b/aria/parser/modeling/context.py
index dff5991..4a53641 100644
--- a/aria/parser/modeling/context.py
+++ b/aria/parser/modeling/context.py
@@ -67,11 +67,6 @@ class ModelingContext(object):
         if self.instance is not None:
             model_storage.service.put(self.instance)
 
-    def generate_node_id(self, template_name):
-        return self.node_id_format.format(
-            template=template_name,
-            id=self.generate_id())
-
     def generate_id(self):
         if self.id_type == IdType.LOCAL_SERIAL:
             return self._serial_id_counter.next()


[09/19] incubator-ariatosca git commit: ARIA-140 Version utils

Posted by ra...@apache.org.
ARIA-140 Version utils

Provided mainly to support version comparisons for plugins in order to
select the highest version of available plugins that would match a
plugin specification. In the future may be useful for other version
comparisons.


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

Branch: refs/heads/ARIA-208-Missing-back-refrences-for-models
Commit: eae44d0b0d051fd50cadf94d734bdf7a3b8d3171
Parents: d0411d3
Author: Tal Liron <ta...@gmail.com>
Authored: Fri Apr 14 13:39:02 2017 -0500
Committer: Tal Liron <ta...@gmail.com>
Committed: Tue May 9 15:37:46 2017 -0500

----------------------------------------------------------------------
 aria/modeling/service_template.py |  17 ++--
 aria/utils/versions.py            | 162 +++++++++++++++++++++++++++++++++
 tests/utils/test_versions.py      |  85 +++++++++++++++++
 3 files changed, 257 insertions(+), 7 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/eae44d0b/aria/modeling/service_template.py
----------------------------------------------------------------------
diff --git a/aria/modeling/service_template.py b/aria/modeling/service_template.py
index f721b64..e3320fa 100644
--- a/aria/modeling/service_template.py
+++ b/aria/modeling/service_template.py
@@ -33,7 +33,8 @@ from sqlalchemy.ext.associationproxy import association_proxy
 from ..parser import validation
 from ..parser.consumption import ConsumptionContext
 from ..parser.reading import deepcopy_with_locators
-from ..utils import collections, formatting, console
+from ..utils import (collections, formatting, console)
+from ..utils.versions import VersionString
 from .mixins import TemplateModelMixin
 from . import (
     relationship,
@@ -2135,13 +2136,15 @@ class PluginSpecificationBase(TemplateModelMixin):
         # moved to.
         plugins = model_storage.plugin.list()
         matching_plugins = []
-        for plugin in plugins:
-            # TODO: we need to use a version comparator
-            if (plugin.name == self.name) and \
-                    ((self.version is None) or (plugin.package_version >= self.version)):
-                matching_plugins.append(plugin)
+        if plugins:
+            for plugin in plugins:
+                if (plugin.name == self.name) and \
+                    ((self.version is None) or \
+                     (VersionString(plugin.package_version) >= self.version)):
+                    matching_plugins.append(plugin)
         self.plugin = None
         if matching_plugins:
             # Return highest version of plugin
-            self.plugin = sorted(matching_plugins, key=lambda plugin: plugin.package_version)[-1]
+            key = lambda plugin: VersionString(plugin.package_version).key
+            self.plugin = sorted(matching_plugins, key=key)[-1]
         return self.plugin is not None

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/eae44d0b/aria/utils/versions.py
----------------------------------------------------------------------
diff --git a/aria/utils/versions.py b/aria/utils/versions.py
new file mode 100644
index 0000000..925f59e
--- /dev/null
+++ b/aria/utils/versions.py
@@ -0,0 +1,162 @@
+# 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.
+
+"""
+General-purpose version string handling
+"""
+
+import re
+
+
+_INF = float('inf')
+
+_NULL = (), _INF
+
+_DIGITS_RE = re.compile(r'^\d+$')
+
+_PREFIXES = {
+    'dev':   0.0001,
+    'alpha': 0.001,
+    'beta':  0.01,
+    'rc':    0.1
+}
+
+
+class VersionString(unicode):
+    """
+    Version string that can be compared, sorted, made unique in a set, and used as a unique dict
+    key.
+
+    The primary part of the string is one or more dot-separated natural numbers. Trailing zeroes
+    are treated as redundant, e.g. "1.0.0" == "1.0" == "1".
+
+    An optional qualifier can be added after a "-". The qualifier can be a natural number or a
+    specially treated prefixed natural number, e.g. "1.1-beta1" > "1.1-alpha2". The case of the
+    prefix is ignored.
+
+    Numeric qualifiers will always be greater than prefixed integer qualifiers, e.g. "1.1-1" >
+    "1.1-beta1".
+
+    Versions without a qualifier will always be greater than their equivalents with a qualifier,
+    e.g. e.g. "1.1" > "1.1-1".
+
+    Any value that does not conform to this format will be treated as a zero version, which would
+    be lesser than any non-zero version.
+
+    For efficient list sorts use the ``key`` property, e.g.:
+    ``sorted(versions, key=lambda x: x.key)``
+    """
+
+    NULL = None # initialized below
+
+    def __init__(self, value=None):
+        if value is not None:
+            super(VersionString, self).__init__(value)
+        self.key = parse_version_string(self)
+
+    def __eq__(self, version):
+        if not isinstance(version, VersionString):
+            version = VersionString(version)
+        return self.key == version.key
+
+    def __lt__(self, version):
+        if not isinstance(version, VersionString):
+            version = VersionString(version)
+        return self.key < version.key
+
+    def __hash__(self):
+        return self.key.__hash__()
+
+
+def parse_version_string(version): # pylint: disable=too-many-branches
+    """
+    Parses a version string.
+
+    :param version: The version string
+    :returns: The primary tuple and qualifier float
+    :rtype: ((int), float)
+    """
+
+    if version is None:
+        return _NULL
+    version = unicode(version)
+
+    # Split to primary and qualifier on '-'
+    split = version.split('-', 1)
+    if len(split) == 2:
+        primary, qualifier = split
+    else:
+        primary = split[0]
+        qualifier = None
+
+    # Parse primary
+    split = primary.split('.')
+    primary = []
+    for element in split:
+        if _DIGITS_RE.match(element) is None:
+            # Invalid version string
+            return _NULL
+        try:
+            element = int(element)
+        except ValueError:
+            # Invalid version string
+            return _NULL
+        primary.append(element)
+
+    # Remove redundant zeros
+    for element in reversed(primary):
+        if element == 0:
+            primary.pop()
+        else:
+            break
+    primary = tuple(primary)
+
+    # Parse qualifier
+    if qualifier is not None:
+        if _DIGITS_RE.match(qualifier) is not None:
+            # Integer qualifier
+            try:
+                qualifier = float(int(qualifier))
+            except ValueError:
+                # Invalid version string
+                return _NULL
+        else:
+            # Prefixed integer qualifier
+            value = None
+            qualifier = qualifier.lower()
+            for prefix, factor in _PREFIXES.iteritems():
+                if qualifier.startswith(prefix):
+                    value = qualifier[len(prefix):]
+                    if _DIGITS_RE.match(value) is None:
+                        # Invalid version string
+                        return _NULL
+                    try:
+                        value = float(int(value)) * factor
+                    except ValueError:
+                        # Invalid version string
+                        return _NULL
+                    break
+            if value is None:
+                # Invalid version string
+                return _NULL
+            qualifier = value
+    else:
+        # Version strings with no qualifiers are higher
+        qualifier = _INF
+
+    return primary, qualifier
+
+
+VersionString.NULL = VersionString()

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/eae44d0b/tests/utils/test_versions.py
----------------------------------------------------------------------
diff --git a/tests/utils/test_versions.py b/tests/utils/test_versions.py
new file mode 100644
index 0000000..222949c
--- /dev/null
+++ b/tests/utils/test_versions.py
@@ -0,0 +1,85 @@
+# 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 aria.utils.versions import (VersionString, parse_version_string)
+
+
+def test_version_string():
+    # No qualifiers
+    assert VersionString('20') == VersionString('20')
+    assert VersionString('20') == VersionString('20.0')
+    assert VersionString('20') == VersionString('20.0.0')
+    assert VersionString('20') < VersionString('20.0.1')
+
+    # With numeric qualifiers
+    assert VersionString('20.0.1-1') < VersionString('20.0.1-2')
+    assert VersionString('20.0.1-0') < VersionString('20.0.1')
+    assert VersionString('20.0.1-1') < VersionString('20.0.1')
+
+    # With prefixed qualifiers
+    assert VersionString('20.0.1-beta1') < VersionString('20.0.1-beta2')
+    assert VersionString('20.0.1-beta1') < VersionString('20.0.1-1')
+    assert VersionString('20.0.1-beta1') < VersionString('20.0.1')
+    assert VersionString('20.0.1-beta2') < VersionString('20.0.1-rc2')
+    assert VersionString('20.0.1-alpha2') < VersionString('20.0.1-beta1')
+    assert VersionString('20.0.1-dev2') < VersionString('20.0.1-alpha1')
+    assert VersionString('20.0.1-DEV2') < VersionString('20.0.1-ALPHA1')
+
+    # Coercive comparisons
+    assert VersionString('20.0.0') == VersionString(10 * 2)
+    assert VersionString('20.0.0') == VersionString(20.0)
+
+    # Non-VersionString comparisons
+    assert VersionString('20.0.0') == 20
+    assert VersionString('20.0.0') < '20.0.1'
+
+    # Nulls
+    assert VersionString() == VersionString()
+    assert VersionString() == VersionString.NULL
+    assert VersionString(None) == VersionString.NULL
+    assert VersionString.NULL == None # pylint: disable=singleton-comparison
+    assert VersionString.NULL == 0
+
+    # Invalid version strings
+    assert VersionString('maxim is maxim') == VersionString.NULL
+    assert VersionString('20.maxim.0') == VersionString.NULL
+    assert VersionString('20.0.0-maxim1') == VersionString.NULL
+    assert VersionString('20.0.1-1.1') == VersionString.NULL
+
+    # Sorts
+    v1 = VersionString('20.0.0')
+    v2 = VersionString('20.0.1-beta1')
+    v3 = VersionString('20.0.1')
+    v4 = VersionString('20.0.2')
+    assert [v1, v2, v3, v4] == sorted([v4, v3, v2, v1], key=lambda v: v.key)
+
+    # Sets
+    v1 = VersionString('20.0.0')
+    v2 = VersionString('20.0')
+    v3 = VersionString('20')
+    assert set([v1]) == set([v1, v2, v3])
+
+    # Dicts
+    the_dict = {v1: 'test'}
+    assert the_dict.get(v2) == 'test'
+
+def test_parse_version_string():
+    # One test of each type from the groups above should be enough
+    assert parse_version_string('20') < parse_version_string('20.0.1')
+    assert parse_version_string('20.0.1-1') < parse_version_string('20.0.1-2')
+    assert parse_version_string('20.0.1-beta1') < parse_version_string('20.0.1-beta2')
+    assert parse_version_string('20.0.0') == parse_version_string(10 * 2)
+    assert parse_version_string(None) == parse_version_string(0)
+    assert parse_version_string(None) == parse_version_string('maxim is maxim')


[17/19] incubator-ariatosca git commit: ARIA-171 service_template.services type inconsistency

Posted by ra...@apache.org.
ARIA-171 service_template.services type inconsistency

As opposed to service_template.node_templates and service.nodes
which have a dict interface, service_template.services
had a list interface. That seemed inconsistent, so the former
interface was changed to a dict interface as well.


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

Branch: refs/heads/ARIA-208-Missing-back-refrences-for-models
Commit: 24d693d762e4a56d6bf16d8c29d76c1f2909098d
Parents: fdd57c4
Author: Avia Efrat <av...@gigaspaces.com>
Authored: Mon May 15 14:26:34 2017 +0300
Committer: Avia Efrat <av...@gigaspaces.com>
Committed: Tue May 16 00:09:49 2017 +0300

----------------------------------------------------------------------
 aria/cli/commands/service_templates.py | 4 ++--
 aria/modeling/service_template.py      | 2 +-
 tests/cli/test_service_templates.py    | 6 ++++--
 tests/end2end/test_hello_world.py      | 2 +-
 tests/end2end/test_nodecellar.py       | 2 +-
 5 files changed, 9 insertions(+), 7 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/24d693d7/aria/cli/commands/service_templates.py
----------------------------------------------------------------------
diff --git a/aria/cli/commands/service_templates.py b/aria/cli/commands/service_templates.py
index 0a24907..d139195 100644
--- a/aria/cli/commands/service_templates.py
+++ b/aria/cli/commands/service_templates.py
@@ -89,8 +89,8 @@ def show(service_template_name, model_storage, mode_full, mode_types, format_jso
 
         if service_template.services:
             logger.info('Existing services:')
-            for service in service_template.services:
-                logger.info('\t{0}'.format(service.name))
+            for service_name in service_template.services:
+                logger.info('\t{0}'.format(service_name))
 
 
 @service_templates.command(name='list',

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/24d693d7/aria/modeling/service_template.py
----------------------------------------------------------------------
diff --git a/aria/modeling/service_template.py b/aria/modeling/service_template.py
index 7eb35bd..1eb95a3 100644
--- a/aria/modeling/service_template.py
+++ b/aria/modeling/service_template.py
@@ -208,7 +208,7 @@ class ServiceTemplateBase(TemplateModelMixin):
 
     @declared_attr
     def services(cls):
-        return relationship.one_to_many(cls, 'service')
+        return relationship.one_to_many(cls, 'service', dict_key='name')
 
     @declared_attr
     def operation_templates(cls):

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/24d693d7/tests/cli/test_service_templates.py
----------------------------------------------------------------------
diff --git a/tests/cli/test_service_templates.py b/tests/cli/test_service_templates.py
index bc3c751..7e86896 100644
--- a/tests/cli/test_service_templates.py
+++ b/tests/cli/test_service_templates.py
@@ -65,7 +65,8 @@ class TestServiceTemplatesShow(TestCliBase):
 
         monkeypatch.setattr(_Environment, 'model_storage', mock_storage)
         st = mock_models.create_service_template()
-        st.services = [mock_models.create_service(st)]
+        s = mock_models.create_service(st)
+        st.services = {s.name: s}
         monkeypatch.setattr(mock_storage.service_template, 'get_by_name',
                             mock.MagicMock(return_value=st))
 
@@ -79,7 +80,8 @@ class TestServiceTemplatesShow(TestCliBase):
 
         monkeypatch.setattr(_Environment, 'model_storage', mock_storage)
         st = mock_models.create_service_template(description='test_description')
-        st.services = [mock_models.create_service(st)]
+        s = mock_models.create_service(st)
+        st.services = {s.name: s}
         monkeypatch.setattr(mock_storage.service_template, 'get_by_name',
                             mock.MagicMock(return_value=st))
 

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/24d693d7/tests/end2end/test_hello_world.py
----------------------------------------------------------------------
diff --git a/tests/end2end/test_hello_world.py b/tests/end2end/test_hello_world.py
index fc5f631..71792dd 100644
--- a/tests/end2end/test_hello_world.py
+++ b/tests/end2end/test_hello_world.py
@@ -52,7 +52,7 @@ def _verify_deployed_service_in_storage(service_name, model_storage):
     service_templates = model_storage.service_template.list()
     assert len(service_templates) == 1
     assert len(service_templates[0].services) == 1
-    service = service_templates[0].services[0]
+    service = service_templates[0].services[service_name]
     assert service.name == service_name
     assert len(service.executions) == 1
     assert len(service.nodes) == 2

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/24d693d7/tests/end2end/test_nodecellar.py
----------------------------------------------------------------------
diff --git a/tests/end2end/test_nodecellar.py b/tests/end2end/test_nodecellar.py
index 25ce7a0..f02441f 100644
--- a/tests/end2end/test_nodecellar.py
+++ b/tests/end2end/test_nodecellar.py
@@ -36,7 +36,7 @@ def _verify_deployed_service_in_storage(service_name, model_storage):
     service_templates = model_storage.service_template.list()
     assert len(service_templates) == 1
     assert len(service_templates[0].services) == 1
-    service = service_templates[0].services[0]
+    service = service_templates[0].services[service_name]
     assert service.name == service_name
     assert len(service.executions) == 0  # dry executions leave no traces
     assert len(service.nodes) == 10


[16/19] incubator-ariatosca git commit: ARIA-148 Enhance CLI show commands

Posted by ra...@apache.org.
ARIA-148 Enhance CLI show commands

* Allow "--full" flag to provide a complete dump
* Allow "--json" and "--yaml" flags for dump in those formats
* Support for node graph and type hierarchies
* Some fixes for YAML dump for our custom types
* Also closes ARIA-186: "aria services show" command


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

Branch: refs/heads/ARIA-208-Missing-back-refrences-for-models
Commit: fdd57c47acfe63dc25c6f20bcb3785225b3f774d
Parents: 60ea3eb
Author: Tal Liron <ta...@gmail.com>
Authored: Thu Apr 20 17:54:47 2017 -0500
Committer: Tal Liron <ta...@gmail.com>
Committed: Thu May 11 12:42:40 2017 -0500

----------------------------------------------------------------------
 aria/cli/commands/service_templates.py |  72 ++++++----
 aria/cli/commands/services.py          |  51 ++++++-
 aria/cli/core/aria.py                  | 203 +++++++++++++++++++---------
 aria/cli/helptexts.py                  |  12 +-
 aria/cli/table.py                      |   3 +-
 aria/modeling/service_instance.py      |   4 +-
 aria/modeling/service_template.py      |   2 +-
 aria/modeling/types.py                 |   5 +
 aria/utils/collections.py              |   2 +-
 9 files changed, 260 insertions(+), 94 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/fdd57c47/aria/cli/commands/service_templates.py
----------------------------------------------------------------------
diff --git a/aria/cli/commands/service_templates.py b/aria/cli/commands/service_templates.py
index e459871..0a24907 100644
--- a/aria/cli/commands/service_templates.py
+++ b/aria/cli/commands/service_templates.py
@@ -23,11 +23,13 @@ from .. import utils
 from ..core import aria
 from ...core import Core
 from ...storage import exceptions as storage_exceptions
+from ...parser import consumption
+from ...utils import (formatting, collections, console)
 
 
 DESCRIPTION_FIELD_LENGTH_LIMIT = 20
 SERVICE_TEMPLATE_COLUMNS = \
-    ['id', 'name', 'description', 'main_file_name', 'created_at', 'updated_at']
+    ('id', 'name', 'description', 'main_file_name', 'created_at', 'updated_at')
 
 
 @aria.group(name='service-templates')
@@ -43,32 +45,52 @@ def service_templates():
 @aria.argument('service-template-name')
 @aria.options.verbose()
 @aria.pass_model_storage
+@aria.options.service_template_mode_full
+@aria.options.mode_types
+@aria.options.format_json
+@aria.options.format_yaml
 @aria.pass_logger
-def show(service_template_name, model_storage, logger):
-    """Show information for a specific service templates
+def show(service_template_name, model_storage, mode_full, mode_types, format_json, format_yaml,
+         logger):
+    """Show information for a specific service template
 
     `SERVICE_TEMPLATE_NAME` is the name of the service template to show information on.
     """
-    logger.info('Showing service template {0}...'.format(service_template_name))
     service_template = model_storage.service_template.get_by_name(service_template_name)
-    service_template_dict = service_template.to_dict()
-    service_template_dict['#services'] = len(service_template.services)
 
-    column_formatters = \
-        dict(description=table.trim_formatter_generator(DESCRIPTION_FIELD_LENGTH_LIMIT))
-    columns = SERVICE_TEMPLATE_COLUMNS + ['#services']
-    table.print_data(columns, service_template_dict, 'Service-template:',
-                     column_formatters=column_formatters, col_max_width=50)
-
-    if service_template_dict['description'] is not None:
-        logger.info('Description:')
-        logger.info('{0}{1}'.format(service_template_dict['description'].encode('UTF-8') or '',
-                                    os.linesep))
-
-    if service_template.services:
-        logger.info('Existing services:')
-        for service in service_template.services:
-            logger.info('\t{0}'.format(service.name))
+    if format_json or format_yaml:
+        mode_full = True
+
+    if mode_full:
+        consumption.ConsumptionContext()
+        if format_json:
+            console.puts(formatting.json_dumps(collections.prune(service_template.as_raw)))
+        elif format_yaml:
+            console.puts(formatting.yaml_dumps(collections.prune(service_template.as_raw)))
+        else:
+            service_template.dump()
+    elif mode_types:
+        consumption.ConsumptionContext()
+        service_template.dump_types()
+    else:
+        logger.info('Showing service template {0}...'.format(service_template_name))
+        service_template_dict = service_template.to_dict()
+        service_template_dict['#services'] = len(service_template.services)
+        columns = SERVICE_TEMPLATE_COLUMNS + ('#services',)
+        column_formatters = \
+            dict(description=table.trim_formatter_generator(DESCRIPTION_FIELD_LENGTH_LIMIT))
+        table.print_data(columns, service_template_dict, 'Service-template:',
+                         column_formatters=column_formatters, col_max_width=50)
+
+        if service_template_dict['description'] is not None:
+            logger.info('Description:')
+            logger.info('{0}{1}'.format(service_template_dict['description'].encode('UTF-8') or '',
+                                        os.linesep))
+
+        if service_template.services:
+            logger.info('Existing services:')
+            for service in service_template.services:
+                logger.info('\t{0}'.format(service.name))
 
 
 @service_templates.command(name='list',
@@ -135,6 +157,7 @@ def store(service_template_path, service_template_name, service_template_filenam
 @aria.pass_logger
 def delete(service_template_name, model_storage, resource_storage, plugin_manager, logger):
     """Delete a service template
+
     `SERVICE_TEMPLATE_NAME` is the name of the service template to delete.
     """
     logger.info('Deleting service template {0}...'.format(service_template_name))
@@ -172,7 +195,7 @@ def validate(service_template, service_template_filename,
              model_storage, resource_storage, plugin_manager, logger):
     """Validate a service template
 
-    `SERVICE_TEMPLATE` is the path or url of the service template or archive to validate.
+    `SERVICE_TEMPLATE` is the path or URL of the service template or archive to validate.
     """
     logger.info('Validating service template: {0}'.format(service_template))
     service_template_path = service_template_utils.get(service_template, service_template_filename)
@@ -188,11 +211,10 @@ def validate(service_template, service_template_filename,
 @aria.options.verbose()
 @aria.pass_logger
 def create_archive(service_template_path, destination, logger):
-    """Create a csar archive on the local file system
+    """Create a CSAR archive
 
     `service_template_path` is the path of the service template to create the archive from
-
-    `destination` is the path of the output CSAR archive file
+    `destination` is the path of the output CSAR archive
     """
     logger.info('Creating a CSAR archive')
     if not destination.endswith(csar.CSAR_FILE_EXTENSION):

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/fdd57c47/aria/cli/commands/services.py
----------------------------------------------------------------------
diff --git a/aria/cli/commands/services.py b/aria/cli/commands/services.py
index 50b530a..24de7c5 100644
--- a/aria/cli/commands/services.py
+++ b/aria/cli/commands/services.py
@@ -25,9 +25,12 @@ from ..core import aria
 from ...core import Core
 from ...modeling import exceptions as modeling_exceptions
 from ...storage import exceptions as storage_exceptions
+from ...parser import consumption
+from ...utils import (formatting, collections, console)
 
 
-SERVICE_COLUMNS = ['id', 'name', 'service_template_name', 'created_at', 'updated_at']
+DESCRIPTION_FIELD_LENGTH_LIMIT = 20
+SERVICE_COLUMNS = ('id', 'name', 'description', 'service_template_name', 'created_at', 'updated_at')
 
 
 @aria.group(name='services')
@@ -38,6 +41,52 @@ def services():
     pass
 
 
+@services.command(name='show',
+                  short_help='Display service information')
+@aria.argument('service-name')
+@aria.options.verbose()
+@aria.options.service_mode_full
+@aria.options.mode_graph
+@aria.options.format_json
+@aria.options.format_yaml
+@aria.pass_model_storage
+@aria.pass_logger
+def show(service_name, model_storage, mode_full, mode_graph, format_json, format_yaml, logger):
+    """Show information for a specific service template
+
+    `SERVICE_NAME` is the name of the service to display information on.
+    """
+    service = model_storage.service.get_by_name(service_name)
+
+    if format_json or format_yaml:
+        mode_full = True
+
+    if mode_full:
+        consumption.ConsumptionContext()
+        if format_json:
+            console.puts(formatting.json_dumps(collections.prune(service.as_raw)))
+        elif format_yaml:
+            console.puts(formatting.yaml_dumps(collections.prune(service.as_raw)))
+        else:
+            service.dump()
+    elif mode_graph:
+        consumption.ConsumptionContext()
+        service.dump_graph()
+    else:
+        logger.info('Showing service {0}...'.format(service_name))
+        service_dict = service.to_dict()
+        columns = SERVICE_COLUMNS
+        column_formatters = \
+            dict(description=table.trim_formatter_generator(DESCRIPTION_FIELD_LENGTH_LIMIT))
+        table.print_data(columns, service_dict, 'Service:',
+                         column_formatters=column_formatters, col_max_width=50)
+
+        if service_dict['description'] is not None:
+            logger.info('Description:')
+            logger.info('{0}{1}'.format(service_dict['description'].encode('UTF-8') or '',
+                                        os.linesep))
+
+
 @services.command(name='list', short_help='List services')
 @aria.options.service_template_name()
 @aria.options.sort_by()

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/fdd57c47/aria/cli/core/aria.py
----------------------------------------------------------------------
diff --git a/aria/cli/core/aria.py b/aria/cli/core/aria.py
index ed6afa1..56fe2f7 100644
--- a/aria/cli/core/aria.py
+++ b/aria/cli/core/aria.py
@@ -19,6 +19,7 @@ import sys
 import difflib
 import StringIO
 import traceback
+import inspect
 from functools import wraps
 
 import click
@@ -40,40 +41,57 @@ CLICK_CONTEXT_SETTINGS = dict(
 
 
 class MutuallyExclusiveOption(click.Option):
-    """Makes options mutually exclusive. The option must pass a `cls` argument
-    with this class name and a `mutually_exclusive` argument with a list of
-    argument names it is mutually exclusive with.
-
-    NOTE: All mutually exclusive options must use this. It's not enough to
-    use it in just one of the options.
-    """
-
     def __init__(self, *args, **kwargs):
-        self.mutually_exclusive = set(kwargs.pop('mutually_exclusive', []))
-        self.mutuality_error_message = \
-            kwargs.pop('mutuality_error_message',
-                       helptexts.DEFAULT_MUTUALITY_MESSAGE)
-        self.mutuality_string = ', '.join(self.mutually_exclusive)
+        self.mutually_exclusive = set(kwargs.pop('mutually_exclusive', tuple()))
+        self.mutuality_description = kwargs.pop('mutuality_description',
+                                                ', '.join(self.mutually_exclusive))
+        self.mutuality_error = kwargs.pop('mutuality_error',
+                                          helptexts.DEFAULT_MUTUALITY_ERROR_MESSAGE)
         if self.mutually_exclusive:
             help = kwargs.get('help', '')
-            kwargs['help'] = (
-                '{0}. This argument is mutually exclusive with '
-                'arguments: [{1}] ({2})'.format(
-                    help,
-                    self.mutuality_string,
-                    self.mutuality_error_message))
+            kwargs['help'] = '{0}. {1}'.format(help, self._message)
         super(MutuallyExclusiveOption, self).__init__(*args, **kwargs)
 
     def handle_parse_result(self, ctx, opts, args):
-        if self.mutually_exclusive.intersection(opts) and self.name in opts:
-            raise click.UsageError(
-                'Illegal usage: `{0}` is mutually exclusive with '
-                'arguments: [{1}] ({2}).'.format(
-                    self.name,
-                    self.mutuality_string,
-                    self.mutuality_error_message))
-        return super(MutuallyExclusiveOption, self).handle_parse_result(
-            ctx, opts, args)
+        if (self.name in opts) and self.mutually_exclusive.intersection(opts):
+            raise click.UsageError('Illegal usage: {0}'.format(self._message))
+        return super(MutuallyExclusiveOption, self).handle_parse_result(ctx, opts, args)
+
+    @property
+    def _message(self):
+        return '{0} be used together with {1} ({2}).'.format(
+            '{0} cannot'.format(', '.join(self.opts)) if hasattr(self, 'opts') else 'Cannot',
+            self.mutuality_description,
+            self.mutuality_error)
+
+
+def mutually_exclusive_option(*param_decls, **attrs):
+    """
+    Decorator for mutually exclusive options.
+
+    This decorator works similarly to `click.option`, but supports an extra ``mutually_exclusive``
+    argument, which is a list of argument names with which the option is mutually exclusive.
+
+    You can optionally also supply ``mutuality_description`` and ``mutuality_error`` to override the
+    default messages.
+
+    NOTE: All mutually exclusive options must use this. It's not enough to use it in just one of the
+    options.
+    """
+
+    # NOTE: This code is copied and slightly modified from click.decorators.option and
+    # click.decorators._param_memo. Unfortunately, using click's ``cls`` parameter support does not
+    # work as is with extra decorator arguments.
+
+    def decorator(func):
+        if 'help' in attrs:
+            attrs['help'] = inspect.cleandoc(attrs['help'])
+        param = MutuallyExclusiveOption(param_decls, **attrs)
+        if not hasattr(func, '__click_params__'):
+            func.__click_params__ = []
+        func.__click_params__.append(param)
+        return func
+    return decorator
 
 
 def _format_version_data(version,
@@ -105,13 +123,12 @@ def show_version(ctx, param, value):
 
 
 def inputs_callback(ctx, param, value):
-    """Allow to pass any inputs we provide to a command as
-    processed inputs instead of having to call `inputs_to_dict`
-    inside the command.
+    """
+    Allow to pass any inputs we provide to a command as processed inputs instead of having to call
+    ``inputs_to_dict`` inside the command.
 
-    `@aria.options.inputs` already calls this callback so that
-    every time you use the option it returns the inputs as a
-    dictionary.
+    ``@aria.options.inputs`` already calls this callback so that every time you use the option it
+    returns the inputs as a dictionary.
     """
     if not value:
         return {}
@@ -127,7 +144,6 @@ def set_verbosity_level(ctx, param, value):
 
 
 def set_cli_except_hook():
-
     def recommend(possible_solutions):
         logger.info('Possible solutions:')
         for solution in possible_solutions:
@@ -155,7 +171,8 @@ def set_cli_except_hook():
 
 
 def pass_logger(func):
-    """Simply passes the logger to a command.
+    """
+    Simply passes the logger to a command.
     """
     # Wraps here makes sure the original docstring propagates to click
     @wraps(func)
@@ -166,7 +183,8 @@ def pass_logger(func):
 
 
 def pass_plugin_manager(func):
-    """Simply passes the plugin manager to a command.
+    """
+    Simply passes the plugin manager to a command.
     """
     # Wraps here makes sure the original docstring propagates to click
     @wraps(func)
@@ -177,7 +195,8 @@ def pass_plugin_manager(func):
 
 
 def pass_model_storage(func):
-    """Simply passes the model storage to a command.
+    """
+    Simply passes the model storage to a command.
     """
     # Wraps here makes sure the original docstring propagates to click
     @wraps(func)
@@ -188,7 +207,8 @@ def pass_model_storage(func):
 
 
 def pass_resource_storage(func):
-    """Simply passes the resource storage to a command.
+    """
+    Simply passes the resource storage to a command.
     """
     # Wraps here makes sure the original docstring propagates to click
     @wraps(func)
@@ -199,11 +219,11 @@ def pass_resource_storage(func):
 
 
 def pass_context(func):
-    """Make click context ARIA specific
+    """
+    Make click context ARIA specific.
 
-    This exists purely for aesthetic reasons, otherwise
-    Some decorators are called `@click.something` instead of
-    `@aria.something`
+    This exists purely for aesthetic reasons, otherwise some decorators are called
+    ``@click.something`` instead of ``@aria.something``.
     """
     return click.pass_context(func)
 
@@ -227,7 +247,8 @@ class AliasedGroup(click.Group):
         ctx.fail('Too many matches: {0}'.format(', '.join(sorted(matches))))
 
     def resolve_command(self, ctx, args):
-        """Override clicks ``resolve_command`` method
+        """
+        Override clicks ``resolve_command`` method
         and appends *Did you mean ...* suggestions
         to the raised exception message.
         """
@@ -249,9 +270,9 @@ class AliasedGroup(click.Group):
 
 
 def group(name):
-    """Allow to create a group with a default click context
-    and a cls for click's `didyoueamn` without having to repeat
-    it for every group.
+    """
+    Allow to create a group with a default click context and a cls for click's ``didyoueamn``
+    without having to repeat it for every group.
     """
     return click.group(
         name=name,
@@ -260,34 +281,34 @@ def group(name):
 
 
 def command(*args, **kwargs):
-    """Make Click commands ARIA specific
+    """
+    Make Click commands ARIA specific.
 
-    This exists purely for aesthetical reasons, otherwise
-    Some decorators are called `@click.something` instead of
-    `@aria.something`
+    This exists purely for aesthetic reasons, otherwise some decorators are called
+    ``@click.something`` instead of ``@aria.something``.
     """
     return click.command(*args, **kwargs)
 
 
 def argument(*args, **kwargs):
-    """Make Click arguments ARIA specific
+    """
+    Make Click arguments ARIA specific.
 
-    This exists purely for aesthetic reasons, otherwise
-    Some decorators are called `@click.something` instead of
-    `@aria.something`
+    This exists purely for aesthetic reasons, otherwise some decorators are called
+    ``@click.something`` instead of ``@aria.something``
     """
     return click.argument(*args, **kwargs)
 
 
 class Options(object):
     def __init__(self):
-        """The options api is nicer when you use each option by calling
-        `@aria.options.some_option` instead of `@aria.some_option`.
+        """
+        The options API is nicer when you use each option by calling ``@aria.options.some_option``
+        instead of ``@aria.some_option``.
 
-        Note that some options are attributes and some are static methods.
-        The reason for that is that we want to be explicit regarding how
-        a developer sees an option. It it can receive arguments, it's a
-        method - if not, it's an attribute.
+        Note that some options are attributes and some are static methods. The reason for that is
+        that we want to be explicit regarding how a developer sees an option. If it can receive
+        arguments, it's a method - if not, it's an attribute.
         """
         self.version = click.option(
             '--version',
@@ -325,6 +346,66 @@ class Options(object):
             default=defaults.SERVICE_TEMPLATE_FILENAME,
             help=helptexts.SERVICE_TEMPLATE_FILENAME)
 
+        self.service_template_mode_full = mutually_exclusive_option(
+            '-f',
+            '--full',
+            'mode_full',
+            mutually_exclusive=('mode_types',),
+            is_flag=True,
+            help=helptexts.SHOW_FULL,
+            mutuality_description='-t, --types',
+            mutuality_error=helptexts.MODE_MUTUALITY_ERROR_MESSAGE)
+
+        self.service_mode_full = mutually_exclusive_option(
+            '-f',
+            '--full',
+            'mode_full',
+            mutually_exclusive=('mode_graph',),
+            is_flag=True,
+            help=helptexts.SHOW_FULL,
+            mutuality_description='-g, --graph',
+            mutuality_error=helptexts.MODE_MUTUALITY_ERROR_MESSAGE)
+
+        self.mode_types = mutually_exclusive_option(
+            '-t',
+            '--types',
+            'mode_types',
+            mutually_exclusive=('mode_full',),
+            is_flag=True,
+            help=helptexts.SHOW_TYPES,
+            mutuality_description='-f, --full',
+            mutuality_error=helptexts.MODE_MUTUALITY_ERROR_MESSAGE)
+
+        self.mode_graph = mutually_exclusive_option(
+            '-g',
+            '--graph',
+            'mode_graph',
+            mutually_exclusive=('mode_full',),
+            is_flag=True,
+            help=helptexts.SHOW_GRAPH,
+            mutuality_description='-f, --full',
+            mutuality_error=helptexts.MODE_MUTUALITY_ERROR_MESSAGE)
+
+        self.format_json = mutually_exclusive_option(
+            '-j',
+            '--json',
+            'format_json',
+            mutually_exclusive=('format_yaml',),
+            is_flag=True,
+            help=helptexts.SHOW_JSON,
+            mutuality_description='-y, --yaml',
+            mutuality_error=helptexts.FORMAT_MUTUALITY_ERROR_MESSAGE)
+
+        self.format_yaml = mutually_exclusive_option(
+            '-y',
+            '--yaml',
+            'format_yaml',
+            mutually_exclusive=('format_json',),
+            is_flag=True,
+            help=helptexts.SHOW_YAML,
+            mutuality_description='-j, --json',
+            mutuality_error=helptexts.FORMAT_MUTUALITY_ERROR_MESSAGE)
+
     @staticmethod
     def verbose(expose_value=False):
         return click.option(

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/fdd57c47/aria/cli/helptexts.py
----------------------------------------------------------------------
diff --git a/aria/cli/helptexts.py b/aria/cli/helptexts.py
index 8641822..74934db 100644
--- a/aria/cli/helptexts.py
+++ b/aria/cli/helptexts.py
@@ -14,7 +14,7 @@
 # limitations under the License.
 
 
-DEFAULT_MUTUALITY_MESSAGE = 'Cannot be used simultaneously'
+DEFAULT_MUTUALITY_ERROR_MESSAGE = 'mutually exclusive'
 VERBOSE = \
     "Show verbose output. You can supply this up to three times (i.e. -vvv)"
 
@@ -29,7 +29,7 @@ EXECUTION_ID = "The unique identifier for the execution"
 SERVICE_TEMPLATE_PATH = "The path to the application's service template file"
 SERVICE_TEMPLATE_FILENAME = (
     "The name of the archive's main service template file. "
-    "This is only relevant if uploading a (non-csar) archive")
+    "This is only relevant if uploading a (non-CSAR) archive")
 INPUTS_PARAMS_USAGE = (
     '(Can be provided as wildcard based paths '
     '(*.yaml, /my_inputs/, etc..) to YAML files, a JSON string or as '
@@ -48,3 +48,11 @@ SORT_BY = "Key for sorting the list"
 DESCENDING = "Sort list in descending order [default: False]"
 JSON_OUTPUT = "Output logs in a consumable JSON format"
 MARK_PATTERN = "Mark a regex pattern in the logs"
+
+SHOW_FULL = "Show full information"
+SHOW_JSON = "Show in JSON format (implies --full)"
+SHOW_YAML = "Show in YAML format (implies --full)"
+SHOW_TYPES = "Show only the type hierarchies"
+SHOW_GRAPH = "Show only the node graph"
+MODE_MUTUALITY_ERROR_MESSAGE = 'only one mode is possible'
+FORMAT_MUTUALITY_ERROR_MESSAGE = 'only one format is possible'

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/fdd57c47/aria/cli/table.py
----------------------------------------------------------------------
diff --git a/aria/cli/table.py b/aria/cli/table.py
index 408f81e..d984c87 100644
--- a/aria/cli/table.py
+++ b/aria/cli/table.py
@@ -85,9 +85,10 @@ def _generate(cols, data, column_formatters=None, defaults=None):
 
             return val
         else:
-            return defaults[column]
+            return defaults.get(column)
 
     column_formatters = column_formatters or dict()
+    defaults = defaults or dict()
     pretty_table = PrettyTable(list(cols))
 
     for datum in data:

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/fdd57c47/aria/modeling/service_instance.py
----------------------------------------------------------------------
diff --git a/aria/modeling/service_instance.py b/aria/modeling/service_instance.py
index 1efe1e1..41a388d 100644
--- a/aria/modeling/service_instance.py
+++ b/aria/modeling/service_instance.py
@@ -1019,7 +1019,7 @@ class SubstitutionBase(InstanceModelMixin):
     @property
     def as_raw(self):
         return collections.OrderedDict((
-            ('node_type_name', self.node_type_name),
+            ('node_type_name', self.node_type.name),
             ('mappings', formatting.as_raw_dict(self.mappings))))
 
     def validate(self):
@@ -1127,7 +1127,7 @@ class SubstitutionMappingBase(InstanceModelMixin):
     @property
     def as_raw(self):
         return collections.OrderedDict((
-            ('name', self.name)))
+            ('name', self.name),))
 
     def coerce_values(self, report_issues):
         pass

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/fdd57c47/aria/modeling/service_template.py
----------------------------------------------------------------------
diff --git a/aria/modeling/service_template.py b/aria/modeling/service_template.py
index 7a192a7..7eb35bd 100644
--- a/aria/modeling/service_template.py
+++ b/aria/modeling/service_template.py
@@ -1063,7 +1063,7 @@ class SubstitutionTemplateMappingBase(TemplateModelMixin):
     @property
     def as_raw(self):
         return collections.OrderedDict((
-            ('name', self.name)))
+            ('name', self.name),))
 
     def coerce_values(self, report_issues):
         pass

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/fdd57c47/aria/modeling/types.py
----------------------------------------------------------------------
diff --git a/aria/modeling/types.py b/aria/modeling/types.py
index 06f171c..7460f47 100644
--- a/aria/modeling/types.py
+++ b/aria/modeling/types.py
@@ -22,6 +22,7 @@ from sqlalchemy import (
     event
 )
 from sqlalchemy.ext import mutable
+from ruamel import yaml
 
 from . import exceptions
 
@@ -206,6 +207,8 @@ class _StrictDict(object):
                 (_StrictDictMixin, _MutableDict),
                 {'_key_cls': key_cls, '_value_cls': value_cls}
             )
+            yaml.representer.RoundTripRepresenter.add_representer(
+                listener_cls, yaml.representer.RoundTripRepresenter.represent_list)
             self._strict_map[strict_dict_map_key] = _StrictValue(type_cls=strict_dict_cls,
                                                                  listener_cls=listener_cls)
 
@@ -242,6 +245,8 @@ class _StrictList(object):
                 (_StrictListMixin, _MutableList),
                 {'_item_cls': item_cls}
             )
+            yaml.representer.RoundTripRepresenter.add_representer(
+                listener_cls, yaml.representer.RoundTripRepresenter.represent_list)
             self._strict_map[item_cls] = _StrictValue(type_cls=strict_list_cls,
                                                       listener_cls=listener_cls)
 

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/fdd57c47/aria/utils/collections.py
----------------------------------------------------------------------
diff --git a/aria/utils/collections.py b/aria/utils/collections.py
index 03feabd..1e732aa 100644
--- a/aria/utils/collections.py
+++ b/aria/utils/collections.py
@@ -249,7 +249,7 @@ def prune(value, is_removable_function=is_removable):
             else:
                 prune(v, is_removable_function)
     elif isinstance(value, dict):
-        for k, v in value.iteritems():
+        for k, v in value.items():
             if is_removable_function(value, k, v):
                 del value[k]
             else:


[18/19] incubator-ariatosca git commit: ARIA-259 Updated README install section for ArchLinux

Posted by ra...@apache.org.
ARIA-259 Updated README install section for ArchLinux


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

Branch: refs/heads/ARIA-208-Missing-back-refrences-for-models
Commit: 78d601987e2f51d83da9e0d401f5e063ad66b6ec
Parents: 24d693d
Author: Alexandru Zbarcea <al...@apache.org>
Authored: Thu May 18 23:23:13 2017 +0300
Committer: Ran Ziv <ra...@gigaspaces.com>
Committed: Fri May 19 00:51:54 2017 +0300

----------------------------------------------------------------------
 README.md | 12 ++++++++++--
 1 file changed, 10 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/78d60198/README.md
----------------------------------------------------------------------
diff --git a/README.md b/README.md
index 06c997e..e534645 100644
--- a/README.md
+++ b/README.md
@@ -46,15 +46,23 @@ Quick Start
 You need Python 2.6 or 2.7. Python 3+ is not currently supported.
 
 To install, we recommend using [pip](https://pip.pypa.io/) and a
-[virtualenv](https://virtualenv.pypa.io/en/stable/). In Debian-based systems:
+[virtualenv](https://virtualenv.pypa.io/en/stable/).
+
+In Debian-based systems:
 
 	sudo apt install python-setuptools
 	sudo -H easy_install pip
 	sudo -H pip install virtualenv
+	virtualenv env
+
+Or in Archlinux-based systems:
+
+	pacman -S python2 python-setuptools python-pip
+	pip install virtualenv
+	virtualenv env -p $(type -p python2)
 
 To install the latest development snapshot of ARIA:
 
-	virtualenv env
 	. env/bin/activate
 	pip install git+http://git-wip-us.apache.org/repos/asf/incubator-ariatosca.git
 


[11/19] incubator-ariatosca git commit: ARIA-157 Failing CLI service-templates store tests on Windows

Posted by ra...@apache.org.
ARIA-157 Failing CLI service-templates store tests on Windows

Three tests from `aria service-templates store` failed on Windows, but
not on Linux.

The reason for this failures was differing implementation of
os.path.dirname across the platforms.

Python implements os.path.dirname in the ntpath module. There, somewhere
down the line of calls, (a part of) the argument of dirname is tested
for membership in a string (using `in`). In these three tests, the
argument of dirname is of type MagicMock, and an error is raised since
only a string can be tested for membership in a string.

The solution was to mock the dirname calls.


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

Branch: refs/heads/ARIA-208-Missing-back-refrences-for-models
Commit: 6864d42e1748b5cd8ecc596fc1b4adc04b0cc9ba
Parents: 0af9e63
Author: Avia Efrat <av...@gigaspaces.com>
Authored: Wed May 10 17:39:49 2017 +0300
Committer: Avia Efrat <av...@gigaspaces.com>
Committed: Wed May 10 18:13:02 2017 +0300

----------------------------------------------------------------------
 tests/cli/test_service_templates.py | 3 +++
 1 file changed, 3 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/6864d42e/tests/cli/test_service_templates.py
----------------------------------------------------------------------
diff --git a/tests/cli/test_service_templates.py b/tests/cli/test_service_templates.py
index 22a8fc8..bc3c751 100644
--- a/tests/cli/test_service_templates.py
+++ b/tests/cli/test_service_templates.py
@@ -128,6 +128,7 @@ class TestServiceTemplatesStore(TestCliBase):
 
         monkeypatch.setattr(Core, 'create_service_template', mock_object)
         monkeypatch.setattr(service_template_utils, 'get', mock_object)
+        monkeypatch.setattr(os.path, 'dirname', mock_object)
         self.invoke('service_templates store stubpath {name}'.format(
             name=mock_models.SERVICE_TEMPLATE_NAME))
         assert 'Service template {name} stored'.format(
@@ -152,6 +153,7 @@ class TestServiceTemplatesStore(TestCliBase):
                             'create_service_template',
                             raise_exception(storage_exceptions.NotFoundError,
                                             msg='UNIQUE constraint failed'))
+        monkeypatch.setattr(os.path, 'dirname', mock_object)
 
         assert_exception_raised(
             self.invoke('service_templates store stubpath test_st'),
@@ -164,6 +166,7 @@ class TestServiceTemplatesStore(TestCliBase):
         monkeypatch.setattr(Core,
                             'create_service_template',
                             raise_exception(storage_exceptions.NotFoundError))
+        monkeypatch.setattr(os.path, 'dirname', mock_object)
 
         assert_exception_raised(
             self.invoke('service_templates store stubpath test_st'),


[05/19] incubator-ariatosca git commit: ARIA-230 Dry execution doesn't log empty operations

Posted by ra...@apache.org.
ARIA-230 Dry execution doesn't log empty operations


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

Branch: refs/heads/ARIA-208-Missing-back-refrences-for-models
Commit: b11fbc946ae6d26ad6c70a283cc6d6501c320273
Parents: 0ec2370
Author: max-orlov <ma...@gigaspaces.com>
Authored: Mon May 8 17:29:29 2017 +0300
Committer: max-orlov <ma...@gigaspaces.com>
Committed: Tue May 9 01:02:46 2017 +0300

----------------------------------------------------------------------
 aria/orchestrator/workflows/executor/dry.py | 26 ++++++++++++------------
 1 file changed, 13 insertions(+), 13 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/b11fbc94/aria/orchestrator/workflows/executor/dry.py
----------------------------------------------------------------------
diff --git a/aria/orchestrator/workflows/executor/dry.py b/aria/orchestrator/workflows/executor/dry.py
index f6fb7a6..63ec392 100644
--- a/aria/orchestrator/workflows/executor/dry.py
+++ b/aria/orchestrator/workflows/executor/dry.py
@@ -32,20 +32,20 @@ class DryExecutor(BaseExecutor):
             task.started_at = datetime.utcnow()
             task.status = task.STARTED
 
+        dry_msg = '<dry> {name} {task.interface_name}.{task.operation_name} {suffix}'
+        logger = task.context.logger.info if task.implementation else task.context.logger.debug
+
+        if hasattr(task.actor, 'source_node'):
+            name = '{source_node.name}->{target_node.name}'.format(
+                source_node=task.actor.source_node, target_node=task.actor.target_node)
+        else:
+            name = task.actor.name
+
         if task.implementation:
-            if hasattr(task.actor, 'source_node'):
-                name = '{source_node.name}->{target_node.name}'.format(
-                    source_node=task.actor.source_node, target_node=task.actor.target_node)
-            else:
-                name = task.actor.name
-
-            task.context.logger.info(
-                '<dry> {name} {task.interface_name}.{task.operation_name} started...'
-                .format(name=name, task=task))
-
-            task.context.logger.info(
-                '<dry> {name} {task.interface_name}.{task.operation_name} successful'
-                .format(name=name, task=task))
+            logger(dry_msg.format(name=name, task=task, suffix='started...'))
+            logger(dry_msg.format(name=name, task=task, suffix='successful'))
+        else:
+            logger(dry_msg.format(name=name, task=task, suffix='has no implementation'))
 
         # updating the task manually instead of calling self._task_succeeded(task),
         # to avoid any side effects raising that event might cause


[04/19] incubator-ariatosca git commit: ARIA-214 Dry execution changes the state of non implemented operations

Posted by ra...@apache.org.
ARIA-214 Dry execution changes the state of non implemented operations


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

Branch: refs/heads/ARIA-208-Missing-back-refrences-for-models
Commit: 0ec237071ebdeb28cd2feabbc1b51854543d398d
Parents: 3e1ed14
Author: max-orlov <ma...@gigaspaces.com>
Authored: Sun May 7 16:12:56 2017 +0300
Committer: max-orlov <ma...@gigaspaces.com>
Committed: Sun May 7 22:29:53 2017 +0300

----------------------------------------------------------------------
 aria/orchestrator/workflows/core/task.py        |  3 ---
 aria/orchestrator/workflows/core/translation.py |  6 +----
 aria/orchestrator/workflows/executor/base.py    | 19 ++++++++------
 aria/orchestrator/workflows/executor/celery.py  |  2 +-
 aria/orchestrator/workflows/executor/dry.py     | 26 ++++++++++----------
 aria/orchestrator/workflows/executor/process.py |  2 +-
 aria/orchestrator/workflows/executor/thread.py  |  2 +-
 tests/orchestrator/workflows/core/test_task.py  |  7 ++----
 .../workflows/executor/test_process_executor.py | 18 +-------------
 9 files changed, 31 insertions(+), 54 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/0ec23707/aria/orchestrator/workflows/core/task.py
----------------------------------------------------------------------
diff --git a/aria/orchestrator/workflows/core/task.py b/aria/orchestrator/workflows/core/task.py
index 78159c4..b3dfb3c 100644
--- a/aria/orchestrator/workflows/core/task.py
+++ b/aria/orchestrator/workflows/core/task.py
@@ -163,9 +163,6 @@ class OperationTask(BaseTask):
         self._task_id = task_model.id
         self._update_fields = None
 
-    def execute(self):
-        super(OperationTask, self).execute()
-
     @contextmanager
     def _update(self):
         """

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/0ec23707/aria/orchestrator/workflows/core/translation.py
----------------------------------------------------------------------
diff --git a/aria/orchestrator/workflows/core/translation.py b/aria/orchestrator/workflows/core/translation.py
index 0bbce90..fec108b 100644
--- a/aria/orchestrator/workflows/core/translation.py
+++ b/aria/orchestrator/workflows/core/translation.py
@@ -48,11 +48,7 @@ def build_execution_graph(
             execution_graph, dependencies, default=[start_task])
 
         if isinstance(api_task, api.task.OperationTask):
-            if api_task.implementation:
-                operation_task = core_task.OperationTask(api_task, executor=default_executor)
-            else:
-                operation_task = core_task.OperationTask(api_task,
-                                                         executor=base.EmptyOperationExecutor())
+            operation_task = core_task.OperationTask(api_task, executor=default_executor)
             _add_task_and_dependencies(execution_graph, operation_task, operation_dependencies)
         elif isinstance(api_task, api.task.WorkflowTask):
             # Build the graph recursively while adding start and end markers

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/0ec23707/aria/orchestrator/workflows/executor/base.py
----------------------------------------------------------------------
diff --git a/aria/orchestrator/workflows/executor/base.py b/aria/orchestrator/workflows/executor/base.py
index a225837..c543278 100644
--- a/aria/orchestrator/workflows/executor/base.py
+++ b/aria/orchestrator/workflows/executor/base.py
@@ -25,13 +25,22 @@ class BaseExecutor(logger.LoggerMixin):
     """
     Base class for executors for running tasks
     """
+    def _execute(self, task):
+        raise NotImplementedError
 
     def execute(self, task):
         """
         Execute a task
         :param task: task to execute
         """
-        raise NotImplementedError
+        if task.implementation:
+            self._execute(task)
+        else:
+            # In this case the task is missing an implementation. This task still gets to an
+            # executor, but since there is nothing to run, we by default simply skip the execution
+            # itself.
+            self._task_started(task)
+            self._task_succeeded(task)
 
     def close(self):
         """
@@ -52,12 +61,6 @@ class BaseExecutor(logger.LoggerMixin):
         events.on_success_task_signal.send(task)
 
 
-class StubTaskExecutor(BaseExecutor):
+class StubTaskExecutor(BaseExecutor):                                                               # pylint: disable=abstract-method
     def execute(self, task):
         task.status = task.SUCCESS
-
-
-class EmptyOperationExecutor(BaseExecutor):
-    def execute(self, task):
-        events.start_task_signal.send(task)
-        events.on_success_task_signal.send(task)

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/0ec23707/aria/orchestrator/workflows/executor/celery.py
----------------------------------------------------------------------
diff --git a/aria/orchestrator/workflows/executor/celery.py b/aria/orchestrator/workflows/executor/celery.py
index 7bd9b7c..bbddc25 100644
--- a/aria/orchestrator/workflows/executor/celery.py
+++ b/aria/orchestrator/workflows/executor/celery.py
@@ -42,7 +42,7 @@ class CeleryExecutor(BaseExecutor):
         self._receiver_thread.start()
         self._started_queue.get(timeout=30)
 
-    def execute(self, task):
+    def _execute(self, task):
         self._tasks[task.id] = task
         inputs = dict(inp.unwrap() for inp in task.inputs.values())
         inputs['ctx'] = task.context

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/0ec23707/aria/orchestrator/workflows/executor/dry.py
----------------------------------------------------------------------
diff --git a/aria/orchestrator/workflows/executor/dry.py b/aria/orchestrator/workflows/executor/dry.py
index eb70a41..f6fb7a6 100644
--- a/aria/orchestrator/workflows/executor/dry.py
+++ b/aria/orchestrator/workflows/executor/dry.py
@@ -21,11 +21,10 @@ from datetime import datetime
 from .base import BaseExecutor
 
 
-class DryExecutor(BaseExecutor):
+class DryExecutor(BaseExecutor):                                                                    # pylint: disable=abstract-method
     """
     Executor which dry runs tasks - prints task information without causing any side effects
     """
-
     def execute(self, task):
         # updating the task manually instead of calling self._task_started(task),
         # to avoid any side effects raising that event might cause
@@ -33,19 +32,20 @@ class DryExecutor(BaseExecutor):
             task.started_at = datetime.utcnow()
             task.status = task.STARTED
 
-        if hasattr(task.actor, 'source_node'):
-            name = '{source_node.name}->{target_node.name}'.format(
-                source_node=task.actor.source_node, target_node=task.actor.target_node)
-        else:
-            name = task.actor.name
+        if task.implementation:
+            if hasattr(task.actor, 'source_node'):
+                name = '{source_node.name}->{target_node.name}'.format(
+                    source_node=task.actor.source_node, target_node=task.actor.target_node)
+            else:
+                name = task.actor.name
 
-        task.context.logger.info(
-            '<dry> {name} {task.interface_name}.{task.operation_name} started...'
-            .format(name=name, task=task))
+            task.context.logger.info(
+                '<dry> {name} {task.interface_name}.{task.operation_name} started...'
+                .format(name=name, task=task))
 
-        task.context.logger.info(
-            '<dry> {name} {task.interface_name}.{task.operation_name} successful'
-            .format(name=name, task=task))
+            task.context.logger.info(
+                '<dry> {name} {task.interface_name}.{task.operation_name} successful'
+                .format(name=name, task=task))
 
         # updating the task manually instead of calling self._task_succeeded(task),
         # to avoid any side effects raising that event might cause

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/0ec23707/aria/orchestrator/workflows/executor/process.py
----------------------------------------------------------------------
diff --git a/aria/orchestrator/workflows/executor/process.py b/aria/orchestrator/workflows/executor/process.py
index f3daf04..e464f7d 100644
--- a/aria/orchestrator/workflows/executor/process.py
+++ b/aria/orchestrator/workflows/executor/process.py
@@ -116,7 +116,7 @@ class ProcessExecutor(base.BaseExecutor):
         self._server_socket.close()
         self._listener_thread.join(timeout=60)
 
-    def execute(self, task):
+    def _execute(self, task):
         self._check_closed()
         self._tasks[task.id] = task
 

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/0ec23707/aria/orchestrator/workflows/executor/thread.py
----------------------------------------------------------------------
diff --git a/aria/orchestrator/workflows/executor/thread.py b/aria/orchestrator/workflows/executor/thread.py
index 836b2bf..f53362a 100644
--- a/aria/orchestrator/workflows/executor/thread.py
+++ b/aria/orchestrator/workflows/executor/thread.py
@@ -46,7 +46,7 @@ class ThreadExecutor(BaseExecutor):
             thread.start()
             self._pool.append(thread)
 
-    def execute(self, task):
+    def _execute(self, task):
         self._queue.put(task)
 
     def close(self):

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/0ec23707/tests/orchestrator/workflows/core/test_task.py
----------------------------------------------------------------------
diff --git a/tests/orchestrator/workflows/core/test_task.py b/tests/orchestrator/workflows/core/test_task.py
index 748ee20..50ca7f5 100644
--- a/tests/orchestrator/workflows/core/test_task.py
+++ b/tests/orchestrator/workflows/core/test_task.py
@@ -24,7 +24,6 @@ from aria.orchestrator.workflows import (
     api,
     core,
     exceptions,
-    executor
 )
 
 from tests import mock, storage
@@ -71,8 +70,7 @@ class TestOperationTask(object):
                 node,
                 interface_name=NODE_INTERFACE_NAME,
                 operation_name=NODE_OPERATION_NAME)
-            core_task = core.task.OperationTask(api_task=api_task,
-                                                executor=executor.base.EmptyOperationExecutor())
+            core_task = core.task.OperationTask(api_task=api_task, executor=None)
         return api_task, core_task
 
     def _create_relationship_operation_task(self, ctx, relationship):
@@ -81,8 +79,7 @@ class TestOperationTask(object):
                 relationship,
                 interface_name=RELATIONSHIP_INTERFACE_NAME,
                 operation_name=RELATIONSHIP_OPERATION_NAME)
-            core_task = core.task.OperationTask(api_task=api_task,
-                                                executor=executor.base.EmptyOperationExecutor())
+            core_task = core.task.OperationTask(api_task=api_task, executor=None)
         return api_task, core_task
 
     def test_node_operation_task_creation(self, ctx):

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/0ec23707/tests/orchestrator/workflows/executor/test_process_executor.py
----------------------------------------------------------------------
diff --git a/tests/orchestrator/workflows/executor/test_process_executor.py b/tests/orchestrator/workflows/executor/test_process_executor.py
index b353518..5f240b2 100644
--- a/tests/orchestrator/workflows/executor/test_process_executor.py
+++ b/tests/orchestrator/workflows/executor/test_process_executor.py
@@ -13,7 +13,6 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import logging
 import os
 import Queue
 
@@ -66,7 +65,7 @@ class TestProcessExecutor(object):
     def test_closed(self, executor):
         executor.close()
         with pytest.raises(RuntimeError) as exc_info:
-            executor.execute(task=None)
+            executor.execute(task=MockTask(implementation='some.implementation'))
         assert 'closed' in exc_info.value.message
 
 
@@ -82,18 +81,3 @@ def mock_plugin(plugin_manager, tmpdir):
     source = os.path.join(tests.resources.DIR, 'plugins', 'mock-plugin1')
     plugin_path = create_plugin(source=source, destination_dir=str(tmpdir))
     return plugin_manager.install(source=plugin_path)
-
-
-class MockContext(object):
-
-    def __init__(self, *args, **kwargs):
-        self.logger = logging.getLogger('mock_logger')
-        self.task = type('SubprocessMockTask', (object, ), {'plugin': None})
-        self.serialization_dict = {'context_cls': self.__class__, 'context': {}}
-
-    def __getattr__(self, item):
-        return None
-
-    @classmethod
-    def deserialize_from_dict(cls, **kwargs):
-        return cls()


[12/19] incubator-ariatosca git commit: ARIA-213 Sporadic tests failures over locked database issue

Posted by ra...@apache.org.
ARIA-213 Sporadic tests failures over locked database issue

Move from 2 different sessions - one for the log, and the other for general model operations,
to one single session, while utilizing the keep tracking of changes mechanism for both logs and node/task states.


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

Branch: refs/heads/ARIA-208-Missing-back-refrences-for-models
Commit: 2ee06b8a6abe79f429458c7dbc5f9e1c31aec589
Parents: 6864d42
Author: max-orlov <ma...@gigaspaces.com>
Authored: Tue May 9 17:24:31 2017 +0300
Committer: max-orlov <ma...@gigaspaces.com>
Committed: Thu May 11 17:24:15 2017 +0300

----------------------------------------------------------------------
 aria/logger.py                                  | 25 +----
 aria/orchestrator/context/common.py             | 13 +--
 .../workflows/core/events_handler.py            |  4 +-
 aria/orchestrator/workflows/executor/process.py | 51 ++++++----
 aria/storage/instrumentation.py                 | 97 +++++++++++++++++---
 .../orchestrator/workflows/executor/__init__.py | 21 ++++-
 .../workflows/executor/test_executor.py         | 36 ++++++--
 .../workflows/executor/test_process_executor.py | 13 ++-
 tests/storage/test_instrumentation.py           | 37 +++++++-
 9 files changed, 219 insertions(+), 78 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/2ee06b8a/aria/logger.py
----------------------------------------------------------------------
diff --git a/aria/logger.py b/aria/logger.py
index 97d3878..bd7ed4e 100644
--- a/aria/logger.py
+++ b/aria/logger.py
@@ -114,17 +114,11 @@ def create_console_log_handler(level=logging.DEBUG, formatter=None):
     return console
 
 
-def create_sqla_log_handler(session, engine, log_cls, execution_id, level=logging.DEBUG):
+def create_sqla_log_handler(model, log_cls, execution_id, level=logging.DEBUG):
 
     # This is needed since the engine and session are entirely new we need to reflect the db
     # schema of the logging model into the engine and session.
-    log_cls.__table__.create(bind=engine, checkfirst=True)
-
-    return _SQLAlchemyHandler(session=session,
-                              engine=engine,
-                              log_cls=log_cls,
-                              execution_id=execution_id,
-                              level=level)
+    return _SQLAlchemyHandler(model=model, log_cls=log_cls, execution_id=execution_id, level=level)
 
 
 class _DefaultConsoleFormat(logging.Formatter):
@@ -168,10 +162,9 @@ def create_file_log_handler(
 
 class _SQLAlchemyHandler(logging.Handler):
 
-    def __init__(self, session, engine, log_cls, execution_id, **kwargs):
+    def __init__(self, model, log_cls, execution_id, **kwargs):
         logging.Handler.__init__(self, **kwargs)
-        self._session = session
-        self._engine = engine
+        self._model = model
         self._cls = log_cls
         self._execution_id = execution_id
 
@@ -188,15 +181,7 @@ class _SQLAlchemyHandler(logging.Handler):
             # Not mandatory.
             traceback=getattr(record, 'traceback', None)
         )
-        self._session.add(log)
-
-        try:
-            self._session.commit()
-        except BaseException:
-            self._session.rollback()
-            raise
-        finally:
-            self._session.close()
+        self._model.log.put(log)
 
 
 _default_file_formatter = logging.Formatter(

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/2ee06b8a/aria/orchestrator/context/common.py
----------------------------------------------------------------------
diff --git a/aria/orchestrator/context/common.py b/aria/orchestrator/context/common.py
index 64ef9a4..0854a27 100644
--- a/aria/orchestrator/context/common.py
+++ b/aria/orchestrator/context/common.py
@@ -79,13 +79,9 @@ class BaseContext(object):
             self.logger.addHandler(self._get_sqla_handler())
 
     def _get_sqla_handler(self):
-        api_kwargs = {}
-        if self._model._initiator:
-            api_kwargs.update(self._model._initiator(**self._model._initiator_kwargs))
-        api_kwargs.update(**self._model._api_kwargs)
-        return aria_logger.create_sqla_log_handler(log_cls=modeling.models.Log,
-                                                   execution_id=self._execution_id,
-                                                   **api_kwargs)
+        return aria_logger.create_sqla_log_handler(model=self._model,
+                                                   log_cls=modeling.models.Log,
+                                                   execution_id=self._execution_id)
 
     def __repr__(self):
         return (
@@ -196,7 +192,6 @@ class BaseContext(object):
 
     def _render_resource(self, resource_content, variables):
         variables = variables or {}
-        if 'ctx' not in variables:
-            variables['ctx'] = self
+        variables.setdefault('ctx', self)
         resource_template = jinja2.Template(resource_content)
         return resource_template.render(variables)

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/2ee06b8a/aria/orchestrator/workflows/core/events_handler.py
----------------------------------------------------------------------
diff --git a/aria/orchestrator/workflows/core/events_handler.py b/aria/orchestrator/workflows/core/events_handler.py
index f3e4e7e..669fb43 100644
--- a/aria/orchestrator/workflows/core/events_handler.py
+++ b/aria/orchestrator/workflows/core/events_handler.py
@@ -40,7 +40,7 @@ def _task_started(task, *args, **kwargs):
     with task._update():
         task.started_at = datetime.utcnow()
         task.status = task.STARTED
-        _update_node_state_if_necessary(task, is_transitional=True)
+    _update_node_state_if_necessary(task, is_transitional=True)
 
 
 @events.on_failure_task_signal.connect
@@ -74,7 +74,7 @@ def _task_succeeded(task, *args, **kwargs):
         task.ended_at = datetime.utcnow()
         task.status = task.SUCCESS
 
-        _update_node_state_if_necessary(task)
+    _update_node_state_if_necessary(task)
 
 
 @events.start_workflow_signal.connect

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/2ee06b8a/aria/orchestrator/workflows/executor/process.py
----------------------------------------------------------------------
diff --git a/aria/orchestrator/workflows/executor/process.py b/aria/orchestrator/workflows/executor/process.py
index e464f7d..824c4e1 100644
--- a/aria/orchestrator/workflows/executor/process.py
+++ b/aria/orchestrator/workflows/executor/process.py
@@ -229,6 +229,7 @@ class ProcessExecutor(base.BaseExecutor):
     def _apply_tracked_changes(task, request):
         instrumentation.apply_tracked_changes(
             tracked_changes=request['tracked_changes'],
+            new_instances=request['new_instances'],
             model=task.context.model)
 
 
@@ -277,22 +278,28 @@ class _Messenger(object):
         """Task started message"""
         self._send_message(type='started')
 
-    def succeeded(self, tracked_changes):
+    def succeeded(self, tracked_changes, new_instances):
         """Task succeeded message"""
-        self._send_message(type='succeeded', tracked_changes=tracked_changes)
+        self._send_message(
+            type='succeeded', tracked_changes=tracked_changes, new_instances=new_instances)
 
-    def failed(self, tracked_changes, exception):
+    def failed(self, tracked_changes, new_instances, exception):
         """Task failed message"""
-        self._send_message(type='failed', tracked_changes=tracked_changes, exception=exception)
+        self._send_message(type='failed',
+                           tracked_changes=tracked_changes,
+                           new_instances=new_instances,
+                           exception=exception)
 
-    def apply_tracked_changes(self, tracked_changes):
-        self._send_message(type='apply_tracked_changes', tracked_changes=tracked_changes)
+    def apply_tracked_changes(self, tracked_changes, new_instances):
+        self._send_message(type='apply_tracked_changes',
+                           tracked_changes=tracked_changes,
+                           new_instances=new_instances)
 
     def closed(self):
         """Executor closed message"""
         self._send_message(type='closed')
 
-    def _send_message(self, type, tracked_changes=None, exception=None):
+    def _send_message(self, type, tracked_changes=None, new_instances=None, exception=None):
         sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
         sock.connect(('localhost', self.port))
         try:
@@ -301,7 +308,8 @@ class _Messenger(object):
                 'task_id': self.task_id,
                 'exception': exceptions.wrap_if_needed(exception),
                 'traceback': exceptions.get_exception_as_string(*sys.exc_info()),
-                'tracked_changes': tracked_changes
+                'tracked_changes': tracked_changes or {},
+                'new_instances': new_instances or {}
             })
             response = _recv_message(sock)
             response_exception = response.get('exception')
@@ -311,7 +319,7 @@ class _Messenger(object):
             sock.close()
 
 
-def _patch_session(ctx, messenger, instrument):
+def _patch_ctx(ctx, messenger, instrument):
     # model will be None only in tests that test the executor component directly
     if not ctx.model:
         return
@@ -326,12 +334,13 @@ def _patch_session(ctx, messenger, instrument):
         original_refresh(target)
 
     def patched_commit():
-        messenger.apply_tracked_changes(instrument.tracked_changes)
+        messenger.apply_tracked_changes(instrument.tracked_changes, instrument.new_instances)
+        instrument.expunge_session()
         instrument.clear()
 
     def patched_rollback():
         # Rollback is performed on parent process when commit fails
-        pass
+        instrument.expunge_session()
 
     # when autoflush is set to true (the default), refreshing an object will trigger
     # an auto flush by sqlalchemy, this autoflush will attempt to commit changes made so
@@ -363,21 +372,29 @@ def _main():
     # This is required for the instrumentation work properly.
     # See docstring of `remove_mutable_association_listener` for further details
     modeling_types.remove_mutable_association_listener()
+    try:
+        ctx = context_dict['context_cls'].deserialize_from_dict(**context_dict['context'])
+    except BaseException as e:
+        messenger.failed(exception=e, tracked_changes=None, new_instances=None)
+        return
 
-    with instrumentation.track_changes() as instrument:
+    with instrumentation.track_changes(ctx.model) as instrument:
         try:
             messenger.started()
-            ctx = context_dict['context_cls'].deserialize_from_dict(**context_dict['context'])
-            _patch_session(ctx=ctx, messenger=messenger, instrument=instrument)
+            _patch_ctx(ctx=ctx, messenger=messenger, instrument=instrument)
             task_func = imports.load_attribute(implementation)
             aria.install_aria_extensions()
             for decorate in process_executor.decorate():
                 task_func = decorate(task_func)
             task_func(ctx=ctx, **operation_inputs)
-            messenger.succeeded(tracked_changes=instrument.tracked_changes)
+            messenger.succeeded(tracked_changes=instrument.tracked_changes,
+                                new_instances=instrument.new_instances)
         except BaseException as e:
-            messenger.failed(exception=e, tracked_changes=instrument.tracked_changes)
-
+            messenger.failed(exception=e,
+                             tracked_changes=instrument.tracked_changes,
+                             new_instances=instrument.new_instances)
+        finally:
+            instrument.expunge_session()
 
 if __name__ == '__main__':
     _main()

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/2ee06b8a/aria/storage/instrumentation.py
----------------------------------------------------------------------
diff --git a/aria/storage/instrumentation.py b/aria/storage/instrumentation.py
index cf2a365..390f933 100644
--- a/aria/storage/instrumentation.py
+++ b/aria/storage/instrumentation.py
@@ -13,9 +13,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import os
 import copy
 import json
+import os
 
 import sqlalchemy.event
 
@@ -26,11 +26,19 @@ from ..storage.exceptions import StorageError
 _VERSION_ID_COL = 'version'
 _STUB = object()
 _INSTRUMENTED = {
-    _models.Node.runtime_properties: dict
+    'modified': {
+        _models.Node.runtime_properties: dict,
+        _models.Node.state: str,
+        _models.Task.status: str,
+    },
+    'new': (_models.Log, )
+
 }
 
+_NEW_INSTANCE = 'NEW_INSTANCE'
+
 
-def track_changes(instrumented=None):
+def track_changes(model=None, instrumented=None):
     """Track changes in the specified model columns
 
     This call will register event listeners using sqlalchemy's event mechanism. The listeners
@@ -50,32 +58,78 @@ def track_changes(instrumented=None):
     will then call ``apply_tracked_changes()`` that resides in this module as well.
     At that point, the changes will actually be written back to the database.
 
+    :param model: the model storage. it should hold a mapi for each model. the session of each mapi
+    is needed to setup events
     :param instrumented: A dict from model columns to their python native type
     :return: The instrumentation context
     """
-    return _Instrumentation(instrumented or _INSTRUMENTED)
+    return _Instrumentation(model, instrumented or _INSTRUMENTED)
 
 
 class _Instrumentation(object):
 
-    def __init__(self, instrumented):
+    def __init__(self, model, instrumented):
         self.tracked_changes = {}
+        self.new_instances = {}
         self.listeners = []
+        self._instances_to_expunge = []
+        self._model = model
         self._track_changes(instrumented)
 
+    @property
+    def _new_instance_id(self):
+        return '{prefix}_{index}'.format(prefix=_NEW_INSTANCE,
+                                         index=len(self._instances_to_expunge))
+
+    def expunge_session(self):
+        for new_instance in self._instances_to_expunge:
+            self._get_session_from_model(new_instance.__tablename__).expunge(new_instance)
+
+    def _get_session_from_model(self, tablename):
+        mapi = getattr(self._model, tablename, None)
+        if mapi:
+            return mapi._session
+        raise StorageError("Could not retrieve session for {0}".format(tablename))
+
     def _track_changes(self, instrumented):
-        instrumented_classes = {}
-        for instrumented_attribute, attribute_type in instrumented.items():
+        instrumented_attribute_classes = {}
+        # Track any newly-set attributes.
+        for instrumented_attribute, attribute_type in instrumented.get('modified', {}).items():
             self._register_set_attribute_listener(
                 instrumented_attribute=instrumented_attribute,
                 attribute_type=attribute_type)
             instrumented_class = instrumented_attribute.parent.entity
-            instrumented_class_attributes = instrumented_classes.setdefault(instrumented_class, {})
+            instrumented_class_attributes = instrumented_attribute_classes.setdefault(
+                instrumented_class, {})
             instrumented_class_attributes[instrumented_attribute.key] = attribute_type
-        for instrumented_class, instrumented_attributes in instrumented_classes.items():
-            self._register_instance_listeners(
-                instrumented_class=instrumented_class,
-                instrumented_attributes=instrumented_attributes)
+
+        # Track any global instance update such as 'refresh' or 'load'
+        for instrumented_class, instrumented_attributes in instrumented_attribute_classes.items():
+            self._register_instance_listeners(instrumented_class=instrumented_class,
+                                              instrumented_attributes=instrumented_attributes)
+
+        # Track any newly created instances.
+        for instrumented_class in instrumented.get('new', {}):
+            self._register_new_instance_listener(instrumented_class)
+
+    def _register_new_instance_listener(self, instrumented_class):
+        if self._model is None:
+            raise StorageError("In order to keep track of new instances, a ctx is needed")
+
+        def listener(_, instance):
+            if not isinstance(instance, instrumented_class):
+                return
+            self._instances_to_expunge.append(instance)
+            tracked_instances = self.new_instances.setdefault(instance.__modelname__, {})
+            tracked_attributes = tracked_instances.setdefault(self._new_instance_id, {})
+            instance_as_dict = instance.to_dict()
+            instance_as_dict.update((k, getattr(instance, k))
+                                    for k in getattr(instance, '__private_fields__', []))
+            tracked_attributes.update(instance_as_dict)
+        session = self._get_session_from_model(instrumented_class.__tablename__)
+        listener_args = (session, 'after_attach', listener)
+        sqlalchemy.event.listen(*listener_args)
+        self.listeners.append(listener_args)
 
     def _register_set_attribute_listener(self, instrumented_attribute, attribute_type):
         def listener(target, value, *_):
@@ -125,6 +179,9 @@ class _Instrumentation(object):
         else:
             self.tracked_changes.clear()
 
+        self.new_instances.clear()
+        self._instances_to_expunge = []
+
     def restore(self):
         """Remove all listeners registered by this instrumentation"""
         for listener_args in self.listeners:
@@ -160,7 +217,7 @@ class _Value(object):
         return {'initial': self.initial, 'current': self.current}.copy()
 
 
-def apply_tracked_changes(tracked_changes, model):
+def apply_tracked_changes(tracked_changes, new_instances, model):
     """Write tracked changes back to the database using provided model storage
 
     :param tracked_changes: The ``tracked_changes`` attribute of the instrumentation context
@@ -169,6 +226,7 @@ def apply_tracked_changes(tracked_changes, model):
     """
     successfully_updated_changes = dict()
     try:
+        # handle instance updates
         for mapi_name, tracked_instances in tracked_changes.items():
             successfully_updated_changes[mapi_name] = dict()
             mapi = getattr(model, mapi_name)
@@ -177,18 +235,27 @@ def apply_tracked_changes(tracked_changes, model):
                 instance = None
                 for attribute_name, value in tracked_attributes.items():
                     if value.initial != value.current:
-                        if not instance:
-                            instance = mapi.get(instance_id)
+                        instance = instance or mapi.get(instance_id)
                         setattr(instance, attribute_name, value.current)
                 if instance:
                     _validate_version_id(instance, mapi)
                     mapi.update(instance)
                     successfully_updated_changes[mapi_name][instance_id] = [
                         v.dict for v in tracked_attributes.values()]
+
+        # Handle new instances
+        for mapi_name, new_instance in new_instances.items():
+            successfully_updated_changes[mapi_name] = dict()
+            mapi = getattr(model, mapi_name)
+            for new_instance_kwargs in new_instance.values():
+                instance = mapi.model_cls(**new_instance_kwargs)
+                mapi.put(instance)
+                successfully_updated_changes[mapi_name][instance.id] = new_instance_kwargs
     except BaseException:
         for key, value in successfully_updated_changes.items():
             if not value:
                 del successfully_updated_changes[key]
+        # TODO: if the successful has _STUB, the logging fails because it can't serialize the object
         model.logger.error(
             'Registering all the changes to the storage has failed. {0}'
             'The successful updates were: {0} '

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/2ee06b8a/tests/orchestrator/workflows/executor/__init__.py
----------------------------------------------------------------------
diff --git a/tests/orchestrator/workflows/executor/__init__.py b/tests/orchestrator/workflows/executor/__init__.py
index cedcc5f..8ad8edb 100644
--- a/tests/orchestrator/workflows/executor/__init__.py
+++ b/tests/orchestrator/workflows/executor/__init__.py
@@ -17,6 +17,7 @@ import logging
 from collections import namedtuple
 from contextlib import contextmanager
 
+import aria
 from aria.modeling import models
 
 
@@ -24,7 +25,7 @@ class MockTask(object):
 
     INFINITE_RETRIES = models.Task.INFINITE_RETRIES
 
-    def __init__(self, implementation, inputs=None, plugin=None):
+    def __init__(self, implementation, inputs=None, plugin=None, storage=None):
         self.implementation = self.name = implementation
         self.plugin_fk = plugin.id if plugin else None
         self.plugin = plugin or None
@@ -33,7 +34,7 @@ class MockTask(object):
         self.exception = None
         self.id = str(uuid.uuid4())
         self.logger = logging.getLogger()
-        self.context = MockContext()
+        self.context = MockContext(storage)
         self.attempts_count = 1
         self.max_attempts = 1
         self.ignore_failure = False
@@ -52,14 +53,24 @@ class MockTask(object):
 
 class MockContext(object):
 
-    def __init__(self):
+    def __init__(self, storage=None):
         self.logger = logging.getLogger('mock_logger')
         self.task = type('SubprocessMockTask', (object, ), {'plugin': None})
-        self.serialization_dict = {'context_cls': self.__class__, 'context': {}}
+        self.model = storage
+
+    @property
+    def serialization_dict(self):
+        if self.model:
+            return {'context': self.model.serialization_dict, 'context_cls': self.__class__}
+        else:
+            return {'context_cls': self.__class__, 'context': {}}
 
     def __getattr__(self, item):
         return None
 
     @classmethod
     def deserialize_from_dict(cls, **kwargs):
-        return cls()
+        if kwargs:
+            return cls(storage=aria.application_model_storage(**kwargs))
+        else:
+            return cls()

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/2ee06b8a/tests/orchestrator/workflows/executor/test_executor.py
----------------------------------------------------------------------
diff --git a/tests/orchestrator/workflows/executor/test_executor.py b/tests/orchestrator/workflows/executor/test_executor.py
index d4482ae..29cb0e8 100644
--- a/tests/orchestrator/workflows/executor/test_executor.py
+++ b/tests/orchestrator/workflows/executor/test_executor.py
@@ -25,6 +25,7 @@ except ImportError:
     _celery = None
     app = None
 
+import aria
 from aria.modeling import models
 from aria.orchestrator import events
 from aria.orchestrator.workflows.executor import (
@@ -41,12 +42,13 @@ def _get_implementation(func):
     return '{module}.{func.__name__}'.format(module=__name__, func=func)
 
 
-def test_execute(executor):
+def execute_and_assert(executor, storage=None):
     expected_value = 'value'
-    successful_task = MockTask(_get_implementation(mock_successful_task))
-    failing_task = MockTask(_get_implementation(mock_failing_task))
+    successful_task = MockTask(_get_implementation(mock_successful_task), storage=storage)
+    failing_task = MockTask(_get_implementation(mock_failing_task), storage=storage)
     task_with_inputs = MockTask(_get_implementation(mock_task_with_input),
-                                inputs={'input': models.Parameter.wrap('input', 'value')})
+                                inputs={'input': models.Parameter.wrap('input', 'value')},
+                                storage=storage)
 
     for task in [successful_task, failing_task, task_with_inputs]:
         executor.execute(task)
@@ -62,6 +64,14 @@ def test_execute(executor):
     assertion()
 
 
+def test_thread_execute(thread_executor):
+    execute_and_assert(thread_executor)
+
+
+def test_process_execute(process_executor, storage):
+    execute_and_assert(process_executor, storage)
+
+
 def mock_successful_task(**_):
     pass
 
@@ -83,21 +93,35 @@ class MockException(Exception):
     pass
 
 
+@pytest.fixture
+def storage(tmpdir):
+    return aria.application_model_storage(
+        aria.storage.sql_mapi.SQLAlchemyModelAPI,
+        initiator_kwargs=dict(base_dir=str(tmpdir))
+    )
+
+
 @pytest.fixture(params=[
     (thread.ThreadExecutor, {'pool_size': 1}),
     (thread.ThreadExecutor, {'pool_size': 2}),
     # subprocess needs to load a tests module so we explicitly add the root directory as if
     # the project has been installed in editable mode
-    (process.ProcessExecutor, {'python_path': [tests.ROOT_DIR]}),
     # (celery.CeleryExecutor, {'app': app})
 ])
-def executor(request):
+def thread_executor(request):
     executor_cls, executor_kwargs = request.param
     result = executor_cls(**executor_kwargs)
     yield result
     result.close()
 
 
+@pytest.fixture
+def process_executor():
+    result = process.ProcessExecutor(python_path=tests.ROOT_DIR)
+    yield result
+    result.close()
+
+
 @pytest.fixture(autouse=True)
 def register_signals():
     def start_handler(task, *args, **kwargs):

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/2ee06b8a/tests/orchestrator/workflows/executor/test_process_executor.py
----------------------------------------------------------------------
diff --git a/tests/orchestrator/workflows/executor/test_process_executor.py b/tests/orchestrator/workflows/executor/test_process_executor.py
index 5f240b2..e6333e8 100644
--- a/tests/orchestrator/workflows/executor/test_process_executor.py
+++ b/tests/orchestrator/workflows/executor/test_process_executor.py
@@ -18,6 +18,7 @@ import Queue
 
 import pytest
 
+import aria
 from aria.orchestrator import events
 from aria.utils.plugin import create as create_plugin
 from aria.orchestrator.workflows.executor import process
@@ -34,8 +35,8 @@ from . import MockTask
 
 class TestProcessExecutor(object):
 
-    def test_plugin_execution(self, executor, mock_plugin):
-        task = MockTask('mock_plugin1.operation', plugin=mock_plugin)
+    def test_plugin_execution(self, executor, mock_plugin, storage):
+        task = MockTask('mock_plugin1.operation', plugin=mock_plugin, storage=storage)
 
         queue = Queue.Queue()
 
@@ -81,3 +82,11 @@ def mock_plugin(plugin_manager, tmpdir):
     source = os.path.join(tests.resources.DIR, 'plugins', 'mock-plugin1')
     plugin_path = create_plugin(source=source, destination_dir=str(tmpdir))
     return plugin_manager.install(source=plugin_path)
+
+
+@pytest.fixture
+def storage(tmpdir):
+    return aria.application_model_storage(
+        aria.storage.sql_mapi.SQLAlchemyModelAPI,
+        initiator_kwargs=dict(base_dir=str(tmpdir))
+    )

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/2ee06b8a/tests/storage/test_instrumentation.py
----------------------------------------------------------------------
diff --git a/tests/storage/test_instrumentation.py b/tests/storage/test_instrumentation.py
index 673103e..bdbb17e 100644
--- a/tests/storage/test_instrumentation.py
+++ b/tests/storage/test_instrumentation.py
@@ -227,6 +227,7 @@ class TestInstrumentation(object):
 
         instrumentation.apply_tracked_changes(
             tracked_changes=instrument.tracked_changes,
+            new_instances={},
             model=storage)
 
         instance1_1, instance1_2, instance2_1, instance2_2 = get_instances()
@@ -273,8 +274,40 @@ class TestInstrumentation(object):
         instrument.clear()
         assert instrument.tracked_changes == {}
 
-    def _track_changes(self, instrumented):
-        instrument = instrumentation.track_changes(instrumented)
+    def test_new_instances(self, storage):
+        model_kwargs = dict(
+            name='name',
+            dict1={'initial': 'value'},
+            dict2={'initial': 'value'},
+            list1=['initial'],
+            list2=['initial'],
+            int1=0,
+            int2=0,
+            string2='string')
+        model_instance_1 = MockModel1(**model_kwargs)
+        model_instance_2 = MockModel2(**model_kwargs)
+
+        instrument = self._track_changes(model=storage, instrumented_new=(MockModel1,))
+        assert not instrument.tracked_changes
+
+        storage.mock_model_1.put(model_instance_1)
+        storage.mock_model_2.put(model_instance_2)
+        # Assert all models made it to storage
+        assert len(storage.mock_model_1.list()) == len(storage.mock_model_2.list()) == 1
+
+        # Assert only one model was tracked
+        assert len(instrument.new_instances) == 1
+
+        mock_model_1 = instrument.new_instances[MockModel1.__tablename__].values()[0]
+        storage_model1_instance = storage.mock_model_1.get(model_instance_1.id)
+
+        for key in model_kwargs:
+            assert mock_model_1[key] == model_kwargs[key] == getattr(storage_model1_instance, key)
+
+    def _track_changes(self, instrumented_modified=None, model=None, instrumented_new=None):
+        instrument = instrumentation.track_changes(
+            model=model,
+            instrumented={'modified': instrumented_modified or {}, 'new': instrumented_new or {}})
         instruments_holder.append(instrument)
         return instrument
 


[10/19] incubator-ariatosca git commit: ARIA-62 Apply pylint on extensions

Posted by ra...@apache.org.
ARIA-62 Apply pylint on extensions


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

Branch: refs/heads/ARIA-208-Missing-back-refrences-for-models
Commit: 0af9e638b7c75eea6eac39374b7176f9dc2c645a
Parents: eae44d0
Author: Ran Ziv <ra...@gigaspaces.com>
Authored: Wed May 10 12:08:01 2017 +0300
Committer: Ran Ziv <ra...@gigaspaces.com>
Committed: Wed May 10 12:08:01 2017 +0300

----------------------------------------------------------------------
 aria/.pylintrc                                            |  2 +-
 .../aria_extension_tosca/simple_nfv_v1_0/presenter.py     |  2 +-
 .../aria_extension_tosca/simple_v1_0/assignments.py       |  2 +-
 extensions/aria_extension_tosca/simple_v1_0/functions.py  |  8 ++++----
 .../aria_extension_tosca/simple_v1_0/modeling/__init__.py | 10 ++++++----
 extensions/aria_extension_tosca/simple_v1_0/presenter.py  |  2 +-
 tests/.pylintrc                                           |  2 +-
 tox.ini                                                   |  2 +-
 8 files changed, 16 insertions(+), 14 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/0af9e638/aria/.pylintrc
----------------------------------------------------------------------
diff --git a/aria/.pylintrc b/aria/.pylintrc
index 7da8c56..4d77556 100644
--- a/aria/.pylintrc
+++ b/aria/.pylintrc
@@ -360,7 +360,7 @@ max-locals=20
 max-returns=10
 
 # Maximum number of branch for function / method body
-max-branches=12
+max-branches=15
 
 # Maximum number of statements in function / method body
 max-statements=50

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/0af9e638/extensions/aria_extension_tosca/simple_nfv_v1_0/presenter.py
----------------------------------------------------------------------
diff --git a/extensions/aria_extension_tosca/simple_nfv_v1_0/presenter.py b/extensions/aria_extension_tosca/simple_nfv_v1_0/presenter.py
index 0ce918e..cd07f42 100644
--- a/extensions/aria_extension_tosca/simple_nfv_v1_0/presenter.py
+++ b/extensions/aria_extension_tosca/simple_nfv_v1_0/presenter.py
@@ -19,7 +19,7 @@ from aria.utils.caching import cachedmethod
 from ..simple_v1_0 import ToscaSimplePresenter1_0
 
 
-class ToscaSimpleNfvPresenter1_0(ToscaSimplePresenter1_0): # pylint: disable=invalid-name
+class ToscaSimpleNfvPresenter1_0(ToscaSimplePresenter1_0): # pylint: disable=invalid-name,abstract-method
     """
     ARIA presenter for the `TOSCA Simple Profile for NFV v1.0 csd03 <http://docs.oasis-open.org
     /tosca/tosca-nfv/v1.0/csd03/tosca-nfv-v1.0-csd03.html>`__.

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/0af9e638/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 6e36ba8..9a2179a 100644
--- a/extensions/aria_extension_tosca/simple_v1_0/assignments.py
+++ b/extensions/aria_extension_tosca/simple_v1_0/assignments.py
@@ -94,7 +94,7 @@ class OperationAssignment(ExtensiblePresentation):
             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:

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/0af9e638/extensions/aria_extension_tosca/simple_v1_0/functions.py
----------------------------------------------------------------------
diff --git a/extensions/aria_extension_tosca/simple_v1_0/functions.py b/extensions/aria_extension_tosca/simple_v1_0/functions.py
index 405aa8f..2f77420 100644
--- a/extensions/aria_extension_tosca/simple_v1_0/functions.py
+++ b/extensions/aria_extension_tosca/simple_v1_0/functions.py
@@ -176,7 +176,7 @@ class GetProperty(Function):
 
         for modelable_entity in modelable_entities:
             properties = None
-            
+
             if hasattr(modelable_entity, 'requirement_templates') \
                 and modelable_entity.requirement_templates \
                 and (req_or_cap_name in [v.name for v in modelable_entity.requirement_templates]):
@@ -185,7 +185,7 @@ class GetProperty(Function):
                         # First argument refers to a requirement
                         # TODO: should follow to matched capability in other node...
                         raise CannotEvaluateFunctionException()
-                        break
+                        # break
                 nested_property_name_or_index = self.nested_property_name_or_index[1:]
             elif hasattr(modelable_entity, 'capability_templates') \
                 and modelable_entity.capability_templates \
@@ -254,7 +254,7 @@ class GetAttribute(Function):
 # Operation
 #
 
-@dsl_specification('4.6.1', 'tosca-simple-1.0')
+@dsl_specification('4.6.1', 'tosca-simple-1.0')  # pylint: disable=abstract-method
 class GetOperationOutput(Function):
     """
     The :code:`get_operation_output` function is used to retrieve the values of variables exposed /
@@ -334,7 +334,7 @@ class GetNodesOfType(Function):
 # Artifact
 #
 
-@dsl_specification('4.8.1', 'tosca-simple-1.0')
+@dsl_specification('4.8.1', 'tosca-simple-1.0') # pylint: disable=abstract-method
 class GetArtifact(Function):
     """
     The :code:`get_artifact` function is used to retrieve artifact location between modelable

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/0af9e638/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 9576260..3bda7e2 100644
--- a/extensions/aria_extension_tosca/simple_v1_0/modeling/__init__.py
+++ b/extensions/aria_extension_tosca/simple_v1_0/modeling/__init__.py
@@ -16,9 +16,11 @@
 """
 Creates ARIA service template models based on the TOSCA presentation.
 
-Relies on many helper methods in the presentation classes. 
+Relies on many helper methods in the presentation classes.
 """
 
+#pylint: disable=unsubscriptable-object
+
 import os
 import re
 from types import FunctionType
@@ -155,7 +157,7 @@ def create_node_template_model(context, service_template, node_template):
     node_type = service_template.node_types.get_descendant(node_type._name)
     model = NodeTemplate(name=node_template._name,
                          type=node_type)
-    
+
     model.default_instances = 1
     model.min_instances = 0
 
@@ -367,7 +369,7 @@ def create_operation_template_model(context, service_template, operation):
         model.description = operation.description.value
 
     implementation = operation.implementation
-    if implementation is not None: 
+    if implementation is not None:
         primary = implementation.primary
         parse_implementation_string(context, service_template, operation, model, primary)
         relationship_edge = operation._get_extensions(context).get('relationship_edge')
@@ -376,7 +378,7 @@ def create_operation_template_model(context, service_template, operation):
                 model.relationship_edge = False
             elif relationship_edge == 'target':
                 model.relationship_edge = True
-            
+
         dependencies = implementation.dependencies
         if dependencies:
             for dependency in dependencies:

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/0af9e638/extensions/aria_extension_tosca/simple_v1_0/presenter.py
----------------------------------------------------------------------
diff --git a/extensions/aria_extension_tosca/simple_v1_0/presenter.py b/extensions/aria_extension_tosca/simple_v1_0/presenter.py
index 96cc763..c88decd 100644
--- a/extensions/aria_extension_tosca/simple_v1_0/presenter.py
+++ b/extensions/aria_extension_tosca/simple_v1_0/presenter.py
@@ -22,7 +22,7 @@ from .functions import (Concat, Token, GetInput, GetProperty, GetAttribute, GetO
 from .modeling import create_service_template_model
 from .templates import ServiceTemplate
 
-class ToscaSimplePresenter1_0(Presenter): # pylint: disable=invalid-name
+class ToscaSimplePresenter1_0(Presenter): # pylint: disable=invalid-name,abstract-method
     """
     ARIA presenter for the `TOSCA Simple Profile v1.0 cos01 <http://docs.oasis-open.org/tosca
     /TOSCA-Simple-Profile-YAML/v1.0/cos01/TOSCA-Simple-Profile-YAML-v1.0-cos01.html>`__.

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/0af9e638/tests/.pylintrc
----------------------------------------------------------------------
diff --git a/tests/.pylintrc b/tests/.pylintrc
index 9795bfc..0352dd3 100644
--- a/tests/.pylintrc
+++ b/tests/.pylintrc
@@ -360,7 +360,7 @@ max-locals=30
 max-returns=10
 
 # Maximum number of branch for function / method body
-max-branches=12
+max-branches=15
 
 # Maximum number of statements in function / method body
 max-statements=50

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/0af9e638/tox.ini
----------------------------------------------------------------------
diff --git a/tox.ini b/tox.ini
index f4e9871..6ab97cb 100644
--- a/tox.ini
+++ b/tox.ini
@@ -49,7 +49,7 @@ commands=pytest tests/end2end --cov-report term-missing --cov aria
 commands=pytest tests --ignore=tests/end2end --cov-report term-missing --cov aria
 
 [testenv:pylint_code]
-commands=pylint --rcfile=aria/.pylintrc --disable=fixme,missing-docstring aria
+commands=pylint --rcfile=aria/.pylintrc --disable=fixme,missing-docstring aria extensions/aria_extension_tosca/
 
 [testenv:pylint_tests]
 commands=pylint --rcfile=tests/.pylintrc --disable=fixme,missing-docstring tests


[13/19] incubator-ariatosca git commit: ARIA-139 Support attributes

Posted by ra...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/extensions/aria_extension_tosca/simple_v1_0/modeling/properties.py
----------------------------------------------------------------------
diff --git a/extensions/aria_extension_tosca/simple_v1_0/modeling/properties.py b/extensions/aria_extension_tosca/simple_v1_0/modeling/properties.py
index f61cb99..9c3ea42 100644
--- a/extensions/aria_extension_tosca/simple_v1_0/modeling/properties.py
+++ b/extensions/aria_extension_tosca/simple_v1_0/modeling/properties.py
@@ -58,7 +58,8 @@ def get_inherited_property_definitions(context, presentation, field_name, for_pr
 # NodeTemplate, RelationshipTemplate, GroupTemplate, PolicyTemplate
 #
 
-def get_assigned_and_defined_property_values(context, presentation):
+def get_assigned_and_defined_property_values(context, presentation, field_name='property',
+                                             field_name_plural='properties'):
     """
     Returns the assigned property values while making sure they are defined in our type.
 
@@ -70,8 +71,9 @@ def get_assigned_and_defined_property_values(context, presentation):
     values = OrderedDict()
 
     the_type = presentation._get_type(context)
-    assignments = presentation.properties
-    definitions = the_type._get_properties(context) if the_type is not None else None
+    assignments = getattr(presentation, field_name_plural)
+    get_fn_name = '_get_{0}'.format(field_name_plural)
+    definitions = getattr(the_type, get_fn_name)(context) if the_type is not None else None
 
     # Fill in our assignments, but make sure they are defined
     if assignments:
@@ -80,14 +82,14 @@ def get_assigned_and_defined_property_values(context, presentation):
                 definition = definitions[name]
                 values[name] = coerce_property_value(context, value, definition, value.value)
             else:
-                context.validation.report('assignment to undefined property "%s" in "%s"'
-                                          % (name, presentation._fullname),
+                context.validation.report('assignment to undefined {0} "{1}" in "{2}"'
+                                          .format(field_name, name, presentation._fullname),
                                           locator=value._locator, level=Issue.BETWEEN_TYPES)
 
     # Fill in defaults from the definitions
     if definitions:
         for name, definition in definitions.iteritems():
-            if (values.get(name) is None) and (definition.default is not None):
+            if values.get(name) is None:
                 values[name] = coerce_property_value(context, presentation, definition,
                                                      definition.default)
 
@@ -181,7 +183,8 @@ def merge_property_definitions(context, presentation, property_definitions,
 def coerce_property_value(context, presentation, definition, value, aspect=None):
     the_type = definition._get_type(context) if definition is not None else None
     entry_schema = definition.entry_schema if definition is not None else None
-    constraints = definition._get_constraints(context) if definition is not None else None
+    constraints = definition._get_constraints(context) \
+        if ((definition is not None) and hasattr(definition, '_get_constraints')) else None
     value = coerce_value(context, presentation, the_type, entry_schema, constraints, value, aspect)
     if (the_type is not None) and hasattr(the_type, '_name'):
         type_name = the_type._name

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/extensions/aria_extension_tosca/simple_v1_0/presentation/field_validators.py
----------------------------------------------------------------------
diff --git a/extensions/aria_extension_tosca/simple_v1_0/presentation/field_validators.py b/extensions/aria_extension_tosca/simple_v1_0/presentation/field_validators.py
index 6ff4384..d7b03ae 100644
--- a/extensions/aria_extension_tosca/simple_v1_0/presentation/field_validators.py
+++ b/extensions/aria_extension_tosca/simple_v1_0/presentation/field_validators.py
@@ -16,7 +16,7 @@
 import re
 
 from aria.utils.formatting import safe_repr
-from aria.parser import dsl_specification
+from aria.parser import implements_specification
 from aria.parser.presentation import (report_issue_for_unknown_type, derived_from_validator)
 from aria.parser.validation import Issue
 
@@ -28,8 +28,7 @@ from .types import get_type_by_full_or_shorthand_name, convert_shorthand_to_full
 # NodeTemplate, RelationshipTemplate
 #
 
-@dsl_specification('3.7.3.3', 'tosca-simple-1.0')
-@dsl_specification('3.7.4.3', 'tosca-simple-1.0')
+@implements_specification('3.7.3.3', 'tosca-simple-1.0')
 def copy_validator(template_type_name, templates_dict_name):
     """
     Makes sure that the field refers to an existing template defined in the root presenter.
@@ -304,8 +303,12 @@ def constraint_clause_pattern_validator(field, presentation, context):
     value = getattr(presentation, field.name)
     if value is not None:
         try:
-            # Note: the TOSCA 1.0 spec does not specify the regular expression grammar, so we will
-            # just use Python's
+            # From TOSCA 1.0 3.5.2.1:
+            #
+            # "Note: Future drafts of this specification will detail the use of regular expressions
+            # and reference an appropriate standardized grammar."
+            #
+            # So we will just use Python's.
             re.compile(value)
         except re.error as e:
             context.validation.report(

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/extensions/aria_extension_tosca/simple_v1_0/presenter.py
----------------------------------------------------------------------
diff --git a/extensions/aria_extension_tosca/simple_v1_0/presenter.py b/extensions/aria_extension_tosca/simple_v1_0/presenter.py
index c88decd..f64078f 100644
--- a/extensions/aria_extension_tosca/simple_v1_0/presenter.py
+++ b/extensions/aria_extension_tosca/simple_v1_0/presenter.py
@@ -17,9 +17,9 @@ from aria.utils.collections import FrozenList, EMPTY_READ_ONLY_LIST
 from aria.utils.caching import cachedmethod
 from aria.parser.presentation import Presenter
 
-from .functions import (Concat, Token, GetInput, GetProperty, GetAttribute, GetOperationOutput,
-                        GetNodesOfType, GetArtifact)
 from .modeling import create_service_template_model
+from .modeling.functions import (Concat, Token, GetInput, GetProperty, GetAttribute,
+                                 GetOperationOutput, GetNodesOfType, GetArtifact)
 from .templates import ServiceTemplate
 
 class ToscaSimplePresenter1_0(Presenter): # pylint: disable=invalid-name,abstract-method

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/extensions/aria_extension_tosca/simple_v1_0/templates.py
----------------------------------------------------------------------
diff --git a/extensions/aria_extension_tosca/simple_v1_0/templates.py b/extensions/aria_extension_tosca/simple_v1_0/templates.py
index 6860b72..ce6b5d9 100644
--- a/extensions/aria_extension_tosca/simple_v1_0/templates.py
+++ b/extensions/aria_extension_tosca/simple_v1_0/templates.py
@@ -15,7 +15,7 @@
 
 from aria.utils.collections import FrozenDict, FrozenList
 from aria.utils.caching import cachedmethod
-from aria.parser import dsl_specification
+from aria.parser import implements_specification
 from aria.parser.presentation import (has_fields, primitive_field, primitive_list_field,
                                       object_field, object_list_field, object_dict_field,
                                       object_sequenced_list_field, field_validator,
@@ -26,7 +26,7 @@ from .assignments import (PropertyAssignment, AttributeAssignment, RequirementAs
 from .definitions import ParameterDefinition
 from .filters import NodeFilter
 from .misc import (Description, MetaData, Repository, Import, SubstitutionMappings)
-from .modeling.properties import get_assigned_and_defined_property_values, get_parameter_values
+from .modeling.properties import (get_assigned_and_defined_property_values, get_parameter_values)
 from .modeling.interfaces import get_template_interfaces
 from .modeling.requirements import get_template_requirements
 from .modeling.capabilities import get_template_capabilities
@@ -41,7 +41,7 @@ from .types import (ArtifactType, DataType, CapabilityType, InterfaceType, Relat
                     NodeType, GroupType, PolicyType)
 
 @has_fields
-@dsl_specification('3.7.3', 'tosca-simple-1.0')
+@implements_specification('3.7.3', 'tosca-simple-1.0')
 class NodeTemplate(ExtensiblePresentation):
     """
     A Node Template specifies the occurrence of a manageable software component as part of an
@@ -160,6 +160,11 @@ class NodeTemplate(ExtensiblePresentation):
         return FrozenDict(get_assigned_and_defined_property_values(context, self))
 
     @cachedmethod
+    def _get_attribute_default_values(self, context):
+        return FrozenDict(get_assigned_and_defined_property_values(context, self,
+                                                                   'attribute', 'attributes'))
+
+    @cachedmethod
     def _get_requirements(self, context):
         return FrozenList(get_template_requirements(context, self))
 
@@ -198,7 +203,7 @@ class NodeTemplate(ExtensiblePresentation):
             'copy'))
 
 @has_fields
-@dsl_specification('3.7.4', 'tosca-simple-1.0')
+@implements_specification('3.7.4', 'tosca-simple-1.0')
 class RelationshipTemplate(ExtensiblePresentation):
     """
     A Relationship Template specifies the occurrence of a manageable relationship between node
@@ -297,7 +302,7 @@ class RelationshipTemplate(ExtensiblePresentation):
             'copy'))
 
 @has_fields
-@dsl_specification('3.7.5', 'tosca-simple-1.0')
+@implements_specification('3.7.5', 'tosca-simple-1.0')
 class GroupTemplate(ExtensiblePresentation):
     """
     A group definition defines a logical grouping of node templates, typically for management
@@ -370,7 +375,7 @@ class GroupTemplate(ExtensiblePresentation):
         self._get_interfaces(context)
 
 @has_fields
-@dsl_specification('3.7.6', 'tosca-simple-1.0')
+@implements_specification('3.7.6', 'tosca-simple-1.0')
 class PolicyTemplate(ExtensiblePresentation):
     """
     A policy definition defines a policy that can be associated with a TOSCA topology or top-level
@@ -434,7 +439,7 @@ class PolicyTemplate(ExtensiblePresentation):
         self._get_property_values(context)
 
 @has_fields
-@dsl_specification('3.8', 'tosca-simple-1.0')
+@implements_specification('3.8', 'tosca-simple-1.0')
 class TopologyTemplate(ExtensiblePresentation):
     """
     This section defines the topology template of a cloud application. The main ingredients of the
@@ -544,7 +549,7 @@ class TopologyTemplate(ExtensiblePresentation):
             'substitution_mappings'))
 
 @has_fields
-@dsl_specification('3.9', 'tosca-simple-1.0')
+@implements_specification('3.9', 'tosca-simple-1.0')
 class ServiceTemplate(ExtensiblePresentation):
     """
     See the `TOSCA Simple Profile v1.0 cos01 specification <http://docs.oasis-open.org/tosca
@@ -553,7 +558,7 @@ class ServiceTemplate(ExtensiblePresentation):
     """
 
     @primitive_field(str)
-    @dsl_specification('3.9.3.1', 'tosca-simple-1.0')
+    @implements_specification('3.9.3.1', 'tosca-simple-1.0')
     def tosca_definitions_version(self):
         """
         Defines the version of the TOSCA Simple Profile specification the template (grammar)
@@ -580,7 +585,7 @@ class ServiceTemplate(ExtensiblePresentation):
         """
 
     @object_field(Description)
-    @dsl_specification('3.9.3.6', 'tosca-simple-1.0')
+    @implements_specification('3.9.3.6', 'tosca-simple-1.0')
     def description(self):
         """
         Declares a description for this Service Template and its contents.
@@ -589,7 +594,7 @@ class ServiceTemplate(ExtensiblePresentation):
         """
 
     @primitive_field()
-    @dsl_specification('3.9.3.7', 'tosca-simple-1.0')
+    @implements_specification('3.9.3.7', 'tosca-simple-1.0')
     def dsl_definitions(self):
         """
         Declares optional DSL-specific definitions and conventions. For example, in YAML, this
@@ -602,7 +607,7 @@ class ServiceTemplate(ExtensiblePresentation):
         """
 
     @object_dict_field(Repository)
-    @dsl_specification('3.9.3.8', 'tosca-simple-1.0')
+    @implements_specification('3.9.3.8', 'tosca-simple-1.0')
     def repositories(self):
         """
         Declares the list of external repositories which contain artifacts that are referenced in
@@ -613,7 +618,7 @@ class ServiceTemplate(ExtensiblePresentation):
         """
 
     @object_list_field(Import)
-    @dsl_specification('3.9.3.9', 'tosca-simple-1.0')
+    @implements_specification('3.9.3.9', 'tosca-simple-1.0')
     def imports(self):
         """
         Declares import statements external TOSCA Definitions documents. For example, these may be
@@ -623,7 +628,7 @@ class ServiceTemplate(ExtensiblePresentation):
         """
 
     @object_dict_field(ArtifactType)
-    @dsl_specification('3.9.3.10', 'tosca-simple-1.0')
+    @implements_specification('3.9.3.10', 'tosca-simple-1.0')
     def artifact_types(self):
         """
         This section contains an optional list of artifact type definitions for use in the service
@@ -633,7 +638,7 @@ class ServiceTemplate(ExtensiblePresentation):
         """
 
     @object_dict_field(DataType)
-    @dsl_specification('3.9.3.11', 'tosca-simple-1.0')
+    @implements_specification('3.9.3.11', 'tosca-simple-1.0')
     def data_types(self):
         """
         Declares a list of optional TOSCA Data Type definitions.
@@ -642,7 +647,7 @@ class ServiceTemplate(ExtensiblePresentation):
         """
 
     @object_dict_field(CapabilityType)
-    @dsl_specification('3.9.3.12', 'tosca-simple-1.0')
+    @implements_specification('3.9.3.12', 'tosca-simple-1.0')
     def capability_types(self):
         """
         This section contains an optional list of capability type definitions for use in the service
@@ -652,7 +657,7 @@ class ServiceTemplate(ExtensiblePresentation):
         """
 
     @object_dict_field(InterfaceType)
-    @dsl_specification('3.9.3.13', 'tosca-simple-1.0')
+    @implements_specification('3.9.3.13', 'tosca-simple-1.0')
     def interface_types(self):
         """
         This section contains an optional list of interface type definitions for use in the service
@@ -662,7 +667,7 @@ class ServiceTemplate(ExtensiblePresentation):
         """
 
     @object_dict_field(RelationshipType)
-    @dsl_specification('3.9.3.14', 'tosca-simple-1.0')
+    @implements_specification('3.9.3.14', 'tosca-simple-1.0')
     def relationship_types(self):
         """
         This section contains a set of relationship type definitions for use in the service
@@ -672,7 +677,7 @@ class ServiceTemplate(ExtensiblePresentation):
         """
 
     @object_dict_field(NodeType)
-    @dsl_specification('3.9.3.15', 'tosca-simple-1.0')
+    @implements_specification('3.9.3.15', 'tosca-simple-1.0')
     def node_types(self):
         """
         This section contains a set of node type definitions for use in the service template.
@@ -681,7 +686,7 @@ class ServiceTemplate(ExtensiblePresentation):
         """
 
     @object_dict_field(GroupType)
-    @dsl_specification('3.9.3.16', 'tosca-simple-1.0')
+    @implements_specification('3.9.3.16', 'tosca-simple-1.0')
     def group_types(self):
         """
         This section contains a list of group type definitions for use in the service template.
@@ -690,7 +695,7 @@ class ServiceTemplate(ExtensiblePresentation):
         """
 
     @object_dict_field(PolicyType)
-    @dsl_specification('3.9.3.17', 'tosca-simple-1.0')
+    @implements_specification('3.9.3.17', 'tosca-simple-1.0')
     def policy_types(self):
         """
         This section contains a list of policy type definitions for use in the service template.

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/extensions/aria_extension_tosca/simple_v1_0/types.py
----------------------------------------------------------------------
diff --git a/extensions/aria_extension_tosca/simple_v1_0/types.py b/extensions/aria_extension_tosca/simple_v1_0/types.py
index 2112f7f..bc80eb9 100644
--- a/extensions/aria_extension_tosca/simple_v1_0/types.py
+++ b/extensions/aria_extension_tosca/simple_v1_0/types.py
@@ -15,7 +15,7 @@
 
 from aria.utils.collections import FrozenDict, FrozenList
 from aria.utils.caching import cachedmethod
-from aria.parser import dsl_specification
+from aria.parser import implements_specification
 from aria.parser.presentation import (has_fields, allow_unknown_fields, primitive_field,
                                       primitive_list_field, object_field, object_dict_field,
                                       object_list_field, object_sequenced_list_field,
@@ -46,7 +46,7 @@ from .presentation.field_validators import (data_type_derived_from_validator,
 from .presentation.types import convert_shorthand_to_full_type_name
 
 @has_fields
-@dsl_specification('3.6.3', 'tosca-simple-1.0')
+@implements_specification('3.6.3', 'tosca-simple-1.0')
 class ArtifactType(ExtensiblePresentation):
     """
     An Artifact Type is a reusable entity that defines the type of one or more files that are used
@@ -131,7 +131,7 @@ class ArtifactType(ExtensiblePresentation):
             'properties'))
 
 @has_fields
-@dsl_specification('3.6.5', 'tosca-simple-1.0')
+@implements_specification('3.6.5', 'tosca-simple-1.0')
 class DataType(ExtensiblePresentation):
     """
     A Data Type definition defines the schema for new named datatypes in TOSCA.
@@ -225,7 +225,7 @@ class DataType(ExtensiblePresentation):
             'properties'))
 
 @has_fields
-@dsl_specification('3.6.6', 'tosca-simple-1.0')
+@implements_specification('3.6.6', 'tosca-simple-1.0')
 class CapabilityType(ExtensiblePresentation):
     """
     A Capability Type is a reusable entity that describes a kind of capability that a Node Type can
@@ -328,7 +328,7 @@ class CapabilityType(ExtensiblePresentation):
 
 @allow_unknown_fields
 @has_fields
-@dsl_specification('3.6.4', 'tosca-simple-1.0')
+@implements_specification('3.6.4', 'tosca-simple-1.0')
 class InterfaceType(ExtensiblePresentation):
     """
     An Interface Type is a reusable entity that describes a set of operations that can be used to
@@ -406,7 +406,7 @@ class InterfaceType(ExtensiblePresentation):
             'operations'))
 
 @has_fields
-@dsl_specification('3.6.9', 'tosca-simple-1.0')
+@implements_specification('3.6.9', 'tosca-simple-1.0')
 class RelationshipType(ExtensiblePresentation):
     """
     A Relationship Type is a reusable entity that defines the type of one or more relationships
@@ -520,7 +520,7 @@ class RelationshipType(ExtensiblePresentation):
             'interfaces'))
 
 @has_fields
-@dsl_specification('3.6.8', 'tosca-simple-1.0')
+@implements_specification('3.6.8', 'tosca-simple-1.0')
 class NodeType(ExtensiblePresentation):
     """
     A Node Type is a reusable entity that defines the type of one or more Node Templates. As such, a
@@ -668,7 +668,7 @@ class NodeType(ExtensiblePresentation):
             'capabilities'))
 
 @has_fields
-@dsl_specification('3.6.10', 'tosca-simple-1.0')
+@implements_specification('3.6.10', 'tosca-simple-1.0')
 class GroupType(ExtensiblePresentation):
     """
     A Group Type defines logical grouping types for nodes, typically for different management
@@ -781,7 +781,7 @@ class GroupType(ExtensiblePresentation):
             'interfaces'))
 
 @has_fields
-@dsl_specification('3.6.11', 'tosca-simple-1.0')
+@implements_specification('3.6.11', 'tosca-simple-1.0')
 class PolicyType(ExtensiblePresentation):
     """
     A Policy Type defines a type of requirement that affects or governs an application or service's

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/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 349a166..8e80640 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
@@ -33,6 +33,7 @@ imports:
   - types/mongodb.yaml
   - types/nginx.yaml
   - aria-1.0
+
 dsl_definitions:
 
   default_openstack_credential: &DEFAULT_OPENSTACK_CREDENTIAL
@@ -94,8 +95,11 @@ topology_template:
           properties:
             unpack_credential:
               user: gigaspaces
-              token: { get_property: [ SELF, app_endpoint, protocol ] }
+              token: { get_attribute: [ SELF, tosca_id ] }
+              #token: { get_property: [ SELF, app_endpoint, protocol ] }
               #token: { get_property: [ HOST, flavor_name ] }
+              #token: { token: [ { get_property: [ HOST, flavor_name ] }, '.', 1 ] }
+              #token: { token: [ 'zero.one|two-three', '.|-', 3 ] }
       interfaces:
         Maintenance:
           enable: juju > charm.maintenance_on


[15/19] incubator-ariatosca git commit: ARIA-139 Support attributes

Posted by ra...@apache.org.
ARIA-139 Support attributes

* Fully implement attribute support in parser
* New intrinsic function evaluation mechanism
* Implemented more intrinsic functions, including get_attribute
* Fix to one-on-one relationship back population
* Fixes to TOSCA use case examples
* Indirectly related: re-enabled node_filter mechanism and reworked
filter constraints in order to make them serializable
* utils/type is much more robust now and consolidates all conversions
and names
* Moved dsl_specification to new utils/specification (because utils/type
uses it)


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

Branch: refs/heads/ARIA-208-Missing-back-refrences-for-models
Commit: 60ea3ebb21e762d36115db26563a93dd3cb72003
Parents: 2ee06b8
Author: Tal Liron <ta...@gmail.com>
Authored: Wed Apr 19 20:07:33 2017 -0500
Committer: Tal Liron <ta...@gmail.com>
Committed: Thu May 11 11:31:02 2017 -0500

----------------------------------------------------------------------
 aria/core.py                                    |   6 +-
 aria/modeling/contraints.py                     |  28 +
 aria/modeling/functions.py                      | 104 ++-
 aria/modeling/mixins.py                         |   2 +-
 aria/modeling/relationship.py                   |  19 +-
 aria/modeling/service_common.py                 | 164 ++++-
 aria/modeling/service_instance.py               |  78 ++-
 aria/modeling/service_template.py               | 126 ++--
 aria/modeling/utils.py                          |  50 +-
 .../execution_plugin/instantiation.py           |   2 +-
 aria/parser/__init__.py                         |   4 +-
 aria/parser/consumption/__init__.py             |  11 +-
 aria/parser/consumption/modeling.py             |   4 +-
 aria/parser/presentation/fields.py              |   3 +-
 aria/parser/presentation/presentation.py        |   3 +-
 aria/parser/presentation/utils.py               |   3 +-
 aria/parser/specification.py                    |  39 +-
 aria/parser/validation/issue.py                 |   2 +-
 aria/utils/caching.py                           |  13 +-
 aria/utils/formatting.py                        |  16 +-
 aria/utils/specification.py                     |  53 ++
 aria/utils/type.py                              | 157 ++++-
 .../block-storage-1/block-storage-1.yaml        |   2 +-
 .../block-storage-2/block-storage-2.yaml        |   2 +-
 .../block-storage-3/block-storage-3.yaml        |   2 +-
 .../block-storage-4/block-storage-4.yaml        |   2 +-
 .../block-storage-5/block-storage-5.yaml        |   6 +-
 .../block-storage-6/block-storage-6.yaml        |   4 +-
 .../use-cases/multi-tier-1/multi-tier-1.yaml    |  14 +-
 .../simple_v1_0/__init__.py                     |  12 +-
 .../simple_v1_0/assignments.py                  |  16 +-
 .../simple_v1_0/data_types.py                   |  20 +-
 .../simple_v1_0/definitions.py                  |  18 +-
 .../aria_extension_tosca/simple_v1_0/filters.py |   6 +-
 .../simple_v1_0/functions.py                    | 536 ---------------
 .../aria_extension_tosca/simple_v1_0/misc.py    |  20 +-
 .../simple_v1_0/modeling/__init__.py            | 185 ++---
 .../simple_v1_0/modeling/constraints.py         | 144 ++++
 .../simple_v1_0/modeling/data_types.py          |  23 +-
 .../simple_v1_0/modeling/functions.py           | 677 +++++++++++++++++++
 .../simple_v1_0/modeling/properties.py          |  17 +-
 .../presentation/field_validators.py            |  13 +-
 .../simple_v1_0/presenter.py                    |   4 +-
 .../simple_v1_0/templates.py                    |  47 +-
 .../aria_extension_tosca/simple_v1_0/types.py   |  18 +-
 .../node-cellar/node-cellar.yaml                |   6 +-
 46 files changed, 1644 insertions(+), 1037 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/aria/core.py
----------------------------------------------------------------------
diff --git a/aria/core.py b/aria/core.py
index af1984a..cc943ef 100644
--- a/aria/core.py
+++ b/aria/core.py
@@ -77,10 +77,14 @@ class Core(object):
             consumption.ConsumerChain(
                 context,
                 (
+                    consumption.CoerceServiceInstanceValues,
+                    consumption.ValidateServiceInstance,
                     consumption.SatisfyRequirements,
+                    consumption.CoerceServiceInstanceValues,
                     consumption.ValidateCapabilities,
                     consumption.FindHosts,
-                    consumption.ConfigureOperations
+                    consumption.ConfigureOperations,
+                    consumption.CoerceServiceInstanceValues
                 )).consume()
             if context.validation.dump_issues():
                 raise exceptions.InstantiationError('Failed to instantiate service template')

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/aria/modeling/contraints.py
----------------------------------------------------------------------
diff --git a/aria/modeling/contraints.py b/aria/modeling/contraints.py
new file mode 100644
index 0000000..107b010
--- /dev/null
+++ b/aria/modeling/contraints.py
@@ -0,0 +1,28 @@
+# 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.
+
+
+class NodeTemplateConstraint(object):
+    """
+    Used to constrain requirements for node templates.
+
+    Must be serializable.
+    """
+
+    def matches(self, source_node_template, target_node_template):
+        """
+        Returns true is the target matches the constraint for the source.
+        """
+        raise NotImplementedError

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/aria/modeling/functions.py
----------------------------------------------------------------------
diff --git a/aria/modeling/functions.py b/aria/modeling/functions.py
index 02f4454..06fd19f 100644
--- a/aria/modeling/functions.py
+++ b/aria/modeling/functions.py
@@ -13,20 +13,118 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from ..parser.consumption import ConsumptionContext
+from ..parser.exceptions import InvalidValueError
+from ..utils.collections import OrderedDict
+from . import exceptions
+
+
 class Function(object):
     """
-    An intrinsic function.
+    Base class for intrinsic functions. Serves as a placeholder for a value that should eventually
+    be derived by "evaluating" (calling) the function.
 
-    Serves as a placeholder for a value that should eventually be derived by calling the function.
+    Note that this base class is provided as a convenience and you do not have to inherit it: any
+    object with an ``__evaluate__`` method would be treated similarly.
     """
 
     @property
     def as_raw(self):
         raise NotImplementedError
 
-    def _evaluate(self, context, container):
+    def __evaluate__(self, container_holder):
+        """
+        Evaluates the function if possible. If impossible, raises
+        :class:`CannotEvaluateFunctionException` (do not just return None).
+
+        :rtype: Evaluation (or any object with ``value`` and ``final`` properties)
+        """
+
         raise NotImplementedError
 
     def __deepcopy__(self, memo):
         # Circumvent cloning in order to maintain our state
         return self
+
+
+class Evaluation(object):
+    """
+    An evaluated :class:`Function` return value.
+    """
+
+    def __init__(self, value, final=False):
+        self.value = value
+        self.final = final
+
+
+def evaluate(value, container_holder, report_issues=False): # pylint: disable=too-many-branches
+    """
+    Recursively attempts to call ``__evaluate__``. If an evaluation occurred will return an
+    :class:`Evaluation`, otherwise it will be None. If any evaluation is non-final, then the entire
+    evaluation will also be non-final.
+
+    The ``container_holder`` argument should have three properties: ``container`` should return
+    the model that contains the value, ``service`` should return the containing
+    :class:`aria.modeling.models.Service` model or None, and ``service_template`` should return the
+    containing :class:`aria.modeling.models.ServiceTemplate` model or None.
+    """
+
+    evaluated = False
+    final = True
+
+    if hasattr(value, '__evaluate__'):
+        try:
+            evaluation = value.__evaluate__(container_holder)
+
+            # Verify evaluation structure
+            if (evaluation is None) \
+                or (not hasattr(evaluation, 'value')) \
+                or (not hasattr(evaluation, 'final')):
+                raise InvalidValueError('bad __evaluate__ implementation')
+
+            evaluated = True
+            value = evaluation.value
+            final = evaluation.final
+
+            # The evaluated value might itself be evaluable
+            evaluation = evaluate(value, container_holder, report_issues)
+            if evaluation is not None:
+                value = evaluation.value
+                if not evaluation.final:
+                    final = False
+        except exceptions.CannotEvaluateFunctionException:
+            pass
+        except InvalidValueError as e:
+            if report_issues:
+                context = ConsumptionContext.get_thread_local()
+                context.validation.report(e.issue)
+
+    elif isinstance(value, list):
+        evaluated_list = []
+        for v in value:
+            evaluation = evaluate(v, container_holder, report_issues)
+            if evaluation is not None:
+                evaluated_list.append(evaluation.value)
+                evaluated = True
+                if not evaluation.final:
+                    final = False
+            else:
+                evaluated_list.append(v)
+        if evaluated:
+            value = evaluated_list
+
+    elif isinstance(value, dict):
+        evaluated_dict = OrderedDict()
+        for k, v in value.iteritems():
+            evaluation = evaluate(v, container_holder, report_issues)
+            if evaluation is not None:
+                evaluated_dict[k] = evaluation.value
+                evaluated = True
+                if not evaluation.final:
+                    final = False
+            else:
+                evaluated_dict[k] = v
+        if evaluated:
+            value = evaluated_dict
+
+    return Evaluation(value, final) if evaluated else None

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/aria/modeling/mixins.py
----------------------------------------------------------------------
diff --git a/aria/modeling/mixins.py b/aria/modeling/mixins.py
index e6db5a3..38c812d 100644
--- a/aria/modeling/mixins.py
+++ b/aria/modeling/mixins.py
@@ -124,7 +124,7 @@ class InstanceModelMixin(ModelMixin):
     def validate(self):
         pass
 
-    def coerce_values(self, container, report_issues):
+    def coerce_values(self, report_issues):
         pass
 
     def dump(self):

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/aria/modeling/relationship.py
----------------------------------------------------------------------
diff --git a/aria/modeling/relationship.py b/aria/modeling/relationship.py
index 291d08c..40be5b2 100644
--- a/aria/modeling/relationship.py
+++ b/aria/modeling/relationship.py
@@ -146,13 +146,18 @@ def one_to_one(model_class,
                        false to disable
     :type back_populates: basestring|bool
     """
-    if back_populates is None:
-        back_populates = model_class.__tablename__
+    backref_kwargs = None
+    if back_populates is not NO_BACK_POP:
+        if back_populates is None:
+            back_populates = model_class.__tablename__
+        backref_kwargs = {'name': back_populates, 'uselist': False}
+        back_populates = None
 
     return _relationship(model_class,
                          other_table,
                          fk=fk,
                          back_populates=back_populates,
+                         backref_kwargs=backref_kwargs,
                          other_fk=other_fk)
 
 
@@ -190,6 +195,7 @@ def one_to_many(model_class,
     rel_kwargs.setdefault('cascade', 'all')
     if back_populates is None:
         back_populates = model_class.__tablename__
+
     return _relationship(
         model_class,
         child_table,
@@ -330,10 +336,11 @@ def _relationship(model_class,
 
     if backref_kwargs:
         assert back_populates is None
-        return relationship(lambda: _get_class_for_table(model_class, other_table_name),
-                            backref=backref(**backref_kwargs),
-                            **relationship_kwargs
-                           )
+        return relationship(
+            lambda: _get_class_for_table(model_class, other_table_name),
+            backref=backref(**backref_kwargs),
+            **relationship_kwargs
+        )
     else:
         if back_populates is not NO_BACK_POP:
             relationship_kwargs['back_populates'] = back_populates

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/aria/modeling/service_common.py
----------------------------------------------------------------------
diff --git a/aria/modeling/service_common.py b/aria/modeling/service_common.py
index 1188f34..e9c96a4 100644
--- a/aria/modeling/service_common.py
+++ b/aria/modeling/service_common.py
@@ -23,19 +23,20 @@ from sqlalchemy import (
 from sqlalchemy.ext.declarative import declared_attr
 
 from ..parser.consumption import ConsumptionContext
-from ..utils import collections, formatting, console
-from .mixins import InstanceModelMixin, TemplateModelMixin
+from ..utils import (collections, formatting, console, caching)
+from ..utils.type import (canonical_type_name, full_type_name)
+from .mixins import (InstanceModelMixin, TemplateModelMixin)
 from . import (
     relationship,
-    utils
+    functions
 )
 
 
-class ParameterBase(TemplateModelMixin):
+class ParameterBase(TemplateModelMixin, caching.HasCachedMethods):
     """
-    Represents a typed value.
+    Represents a typed value. The value can contain nested intrinsic functions.
 
-    This model is used by both service template and service instance elements.
+    This model can be used as the ``container_holder`` argument for :func:`functions.evaluate`.
 
     :ivar name: Name
     :vartype name: basestring
@@ -50,8 +51,120 @@ class ParameterBase(TemplateModelMixin):
 
     name = Column(Text)
     type_name = Column(Text)
-    value = Column(PickleType)
     description = Column(Text)
+    _value = Column(PickleType)
+
+    @property
+    def value(self):
+        value = self._value
+        if value is not None:
+            evaluation = functions.evaluate(value, self)
+            if evaluation is not None:
+                value = evaluation.value
+        return value
+
+    @value.setter
+    def value(self, value):
+        self._value = value
+
+    @property
+    @caching.cachedmethod
+    def owner(self):
+        """
+        The sole owner of this parameter, which is another model that relates to it.
+
+        *All* parameters should have an owner model. In case this property method fails to find
+        it, it will raise a ValueError, which should signify an abnormal, orphaned parameter.
+        """
+
+        # Find first non-null relationship
+        for the_relationship in self.__mapper__.relationships:
+            v = getattr(self, the_relationship.key)
+            if v:
+                return v[0] # because we are many-to-many, the back reference will be a list
+
+        raise ValueError('orphaned parameter: does not have an owner: {0}'.format(self.name))
+
+
+    @property
+    @caching.cachedmethod
+    def container(self): # pylint: disable=too-many-return-statements,too-many-branches
+        """
+        The logical container for this parameter, which would be another model: service, node,
+        group, or policy (or their templates).
+
+        The logical container is equivalent to the ``SELF`` keyword used by intrinsic functions in
+        TOSCA.
+
+        *All* parameters should have a container model. In case this property method fails to find
+        it, it will raise a ValueError, which should signify an abnormal, orphaned parameter.
+        """
+
+        from . import models
+
+        container = self.owner
+
+        # Extract interface from operation
+        if isinstance(container, models.Operation):
+            container = container.interface
+        elif isinstance(container, models.OperationTemplate):
+            container = container.interface_template
+
+        # Extract from other models
+        if isinstance(container, models.Interface):
+            container = container.node or container.group or container.relationship
+        elif isinstance(container, models.InterfaceTemplate):
+            container = container.node_template or container.group_template \
+                or container.relationship_template
+        elif isinstance(container, models.Capability) or isinstance(container, models.Artifact):
+            container = container.node
+        elif isinstance(container, models.CapabilityTemplate) \
+            or isinstance(container, models.ArtifactTemplate):
+            container = container.node_template
+        elif isinstance(container, models.Task):
+            container = container.actor
+
+        # Extract node from relationship
+        if isinstance(container, models.Relationship):
+            container = container.source_node
+        elif isinstance(container, models.RelationshipTemplate):
+            container = container.requirement_template.node_template
+
+        if container is not None:
+            return container
+
+        raise ValueError('orphaned parameter: does not have a container: {0}'.format(self.name))
+
+    @property
+    @caching.cachedmethod
+    def service(self):
+        """
+        The :class:`Service` containing this parameter, or None if not contained in a service.
+        """
+
+        from . import models
+        container = self.container
+        if isinstance(container, models.Service):
+            return container
+        elif hasattr(container, 'service'):
+            return container.service
+        return None
+
+    @property
+    @caching.cachedmethod
+    def service_template(self):
+        """
+        The :class:`ServiceTemplate` containing this parameter, or None if not contained in a
+        service template.
+        """
+
+        from . import models
+        container = self.container
+        if isinstance(container, models.ServiceTemplate):
+            return container
+        elif hasattr(container, 'service_template'):
+            return container.service_template
+        return None
 
     @property
     def as_raw(self):
@@ -63,27 +176,30 @@ class ParameterBase(TemplateModelMixin):
 
     def instantiate(self, container):
         from . import models
-        return models.Parameter(name=self.name,
+        return models.Parameter(name=self.name, # pylint: disable=unexpected-keyword-arg
                                 type_name=self.type_name,
-                                value=self.value,
+                                _value=self._value,
                                 description=self.description)
 
-    def coerce_values(self, container, report_issues):
-        if self.value is not None:
-            self.value = utils.coerce_value(container, self.value,
-                                            report_issues)
+    def coerce_values(self, report_issues):
+        value = self._value
+        if value is not None:
+            evaluation = functions.evaluate(value, self, report_issues)
+            if (evaluation is not None) and evaluation.final:
+                # A final evaluation can safely replace the existing value
+                self._value = evaluation.value
 
     def dump(self):
         context = ConsumptionContext.get_thread_local()
         if self.type_name is not None:
             console.puts('{0}: {1} ({2})'.format(
                 context.style.property(self.name),
-                context.style.literal(self.value),
+                context.style.literal(formatting.as_raw(self.value)),
                 context.style.type(self.type_name)))
         else:
             console.puts('{0}: {1}'.format(
                 context.style.property(self.name),
-                context.style.literal(self.value)))
+                context.style.literal(formatting.as_raw(self.value))))
         if self.description:
             console.puts(context.style.meta(self.description))
 
@@ -101,11 +217,15 @@ class ParameterBase(TemplateModelMixin):
         :param description: Description (optional)
         :type description: basestring
         """
-        return cls(name=name,
-                   type_name=formatting.full_type_name(value)
-                   if value is not None else None,
-                   value=value,
-                   description=description)
+
+        from . import models
+        type_name = canonical_type_name(value)
+        if type_name is None:
+            type_name = full_type_name(value)
+        return models.Parameter(name=name, # pylint: disable=unexpected-keyword-arg
+                                type_name=type_name,
+                                value=value,
+                                description=description)
 
 
 class TypeBase(InstanceModelMixin):
@@ -188,7 +308,7 @@ class TypeBase(InstanceModelMixin):
         self._append_raw_children(types)
         return types
 
-    def coerce_values(self, container, report_issues):
+    def coerce_values(self, report_issues):
         pass
 
     def dump(self):
@@ -237,7 +357,7 @@ class MetadataBase(TemplateModelMixin):
             ('name', self.name),
             ('value', self.value)))
 
-    def coerce_values(self, container, report_issues):
+    def coerce_values(self, report_issues):
         pass
 
     def instantiate(self, container):

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/aria/modeling/service_instance.py
----------------------------------------------------------------------
diff --git a/aria/modeling/service_instance.py b/aria/modeling/service_instance.py
index ad8e7ed..1efe1e1 100644
--- a/aria/modeling/service_instance.py
+++ b/aria/modeling/service_instance.py
@@ -251,16 +251,16 @@ class ServiceBase(InstanceModelMixin):
         utils.validate_dict_values(self.outputs)
         utils.validate_dict_values(self.workflows)
 
-    def coerce_values(self, container, report_issues):
-        utils.coerce_dict_values(container, self.meta_data, report_issues)
-        utils.coerce_dict_values(container, self.nodes, report_issues)
-        utils.coerce_dict_values(container, self.groups, report_issues)
-        utils.coerce_dict_values(container, self.policies, report_issues)
+    def coerce_values(self, report_issues):
+        utils.coerce_dict_values(self.meta_data, report_issues)
+        utils.coerce_dict_values(self.nodes, report_issues)
+        utils.coerce_dict_values(self.groups, report_issues)
+        utils.coerce_dict_values(self.policies, report_issues)
         if self.substitution is not None:
-            self.substitution.coerce_values(container, report_issues)
-        utils.coerce_dict_values(container, self.inputs, report_issues)
-        utils.coerce_dict_values(container, self.outputs, report_issues)
-        utils.coerce_dict_values(container, self.workflows, report_issues)
+            self.substitution.coerce_values(report_issues)
+        utils.coerce_dict_values(self.inputs, report_issues)
+        utils.coerce_dict_values(self.outputs, report_issues)
+        utils.coerce_dict_values(self.workflows, report_issues)
 
     def dump(self):
         context = ConsumptionContext.get_thread_local()
@@ -513,6 +513,10 @@ class NodeBase(InstanceModelMixin):
     def properties(cls):
         return relationship.many_to_many(cls, 'parameter', prefix='properties', dict_key='name')
 
+    @declared_attr
+    def attributes(cls):
+        return relationship.many_to_many(cls, 'parameter', prefix='attributes', dict_key='name')
+
     # endregion
 
     description = Column(Text)
@@ -646,6 +650,7 @@ class NodeBase(InstanceModelMixin):
             ('name', self.name),
             ('type_name', self.type.name),
             ('properties', formatting.as_raw_dict(self.properties)),
+            ('attributes', formatting.as_raw_dict(self.properties)),
             ('interfaces', formatting.as_raw_list(self.interfaces)),
             ('artifacts', formatting.as_raw_list(self.artifacts)),
             ('capabilities', formatting.as_raw_list(self.capabilities)),
@@ -664,17 +669,19 @@ class NodeBase(InstanceModelMixin):
         # TODO: validate that node template is of type?
 
         utils.validate_dict_values(self.properties)
+        utils.validate_dict_values(self.attributes)
         utils.validate_dict_values(self.interfaces)
         utils.validate_dict_values(self.artifacts)
         utils.validate_dict_values(self.capabilities)
         utils.validate_list_values(self.outbound_relationships)
 
-    def coerce_values(self, container, report_issues):
-        utils.coerce_dict_values(self, self.properties, report_issues)
-        utils.coerce_dict_values(self, self.interfaces, report_issues)
-        utils.coerce_dict_values(self, self.artifacts, report_issues)
-        utils.coerce_dict_values(self, self.capabilities, report_issues)
-        utils.coerce_list_values(self, self.outbound_relationships, report_issues)
+    def coerce_values(self, report_issues):
+        utils.coerce_dict_values(self.properties, report_issues)
+        utils.coerce_dict_values(self.attributes, report_issues)
+        utils.coerce_dict_values(self.interfaces, report_issues)
+        utils.coerce_dict_values(self.artifacts, report_issues)
+        utils.coerce_dict_values(self.capabilities, report_issues)
+        utils.coerce_list_values(self.outbound_relationships, report_issues)
 
     def dump(self):
         context = ConsumptionContext.get_thread_local()
@@ -683,6 +690,7 @@ class NodeBase(InstanceModelMixin):
             console.puts('Type: {0}'.format(context.style.type(self.type.name)))
             console.puts('Template: {0}'.format(context.style.node(self.node_template.name)))
             utils.dump_dict_values(self.properties, 'Properties')
+            utils.dump_dict_values(self.attributes, 'Attributes')
             utils.dump_interfaces(self.interfaces)
             utils.dump_dict_values(self.artifacts, 'Artifacts')
             utils.dump_dict_values(self.capabilities, 'Capabilities')
@@ -797,9 +805,9 @@ class GroupBase(InstanceModelMixin):
         utils.validate_dict_values(self.properties)
         utils.validate_dict_values(self.interfaces)
 
-    def coerce_values(self, container, report_issues):
-        utils.coerce_dict_values(container, self.properties, report_issues)
-        utils.coerce_dict_values(container, self.interfaces, report_issues)
+    def coerce_values(self, report_issues):
+        utils.coerce_dict_values(self.properties, report_issues)
+        utils.coerce_dict_values(self.interfaces, report_issues)
 
     def dump(self):
         context = ConsumptionContext.get_thread_local()
@@ -916,8 +924,8 @@ class PolicyBase(InstanceModelMixin):
     def validate(self):
         utils.validate_dict_values(self.properties)
 
-    def coerce_values(self, container, report_issues):
-        utils.coerce_dict_values(container, self.properties, report_issues)
+    def coerce_values(self, report_issues):
+        utils.coerce_dict_values(self.properties, report_issues)
 
     def dump(self):
         context = ConsumptionContext.get_thread_local()
@@ -1017,8 +1025,8 @@ class SubstitutionBase(InstanceModelMixin):
     def validate(self):
         utils.validate_dict_values(self.mappings)
 
-    def coerce_values(self, container, report_issues):
-        utils.coerce_dict_values(container, self.mappings, report_issues)
+    def coerce_values(self, report_issues):
+        utils.coerce_dict_values(self.mappings, report_issues)
 
     def dump(self):
         context = ConsumptionContext.get_thread_local()
@@ -1121,7 +1129,7 @@ class SubstitutionMappingBase(InstanceModelMixin):
         return collections.OrderedDict((
             ('name', self.name)))
 
-    def coerce_values(self, container, report_issues):
+    def coerce_values(self, report_issues):
         pass
 
     def validate(self):
@@ -1311,9 +1319,9 @@ class RelationshipBase(InstanceModelMixin):
         utils.validate_dict_values(self.properties)
         utils.validate_dict_values(self.interfaces)
 
-    def coerce_values(self, container, report_issues):
-        utils.coerce_dict_values(container, self.properties, report_issues)
-        utils.coerce_dict_values(container, self.interfaces, report_issues)
+    def coerce_values(self, report_issues):
+        utils.coerce_dict_values(self.properties, report_issues)
+        utils.coerce_dict_values(self.interfaces, report_issues)
 
     def dump(self):
         context = ConsumptionContext.get_thread_local()
@@ -1451,8 +1459,8 @@ class CapabilityBase(InstanceModelMixin):
     def validate(self):
         utils.validate_dict_values(self.properties)
 
-    def coerce_values(self, container, report_issues):
-        utils.coerce_dict_values(container, self.properties, report_issues)
+    def coerce_values(self, report_issues):
+        utils.coerce_dict_values(self.properties, report_issues)
 
     def dump(self):
         context = ConsumptionContext.get_thread_local()
@@ -1598,9 +1606,9 @@ class InterfaceBase(InstanceModelMixin):
         utils.validate_dict_values(self.inputs)
         utils.validate_dict_values(self.operations)
 
-    def coerce_values(self, container, report_issues):
-        utils.coerce_dict_values(container, self.inputs, report_issues)
-        utils.coerce_dict_values(container, self.operations, report_issues)
+    def coerce_values(self, report_issues):
+        utils.coerce_dict_values(self.inputs, report_issues)
+        utils.coerce_dict_values(self.operations, report_issues)
 
     def dump(self):
         context = ConsumptionContext.get_thread_local()
@@ -1765,8 +1773,8 @@ class OperationBase(InstanceModelMixin):
         # TODO must be associated with interface or service
         utils.validate_dict_values(self.inputs)
 
-    def coerce_values(self, container, report_issues):
-        utils.coerce_dict_values(container, self.inputs, report_issues)
+    def coerce_values(self, report_issues):
+        utils.coerce_dict_values(self.inputs, report_issues)
 
     def dump(self):
         context = ConsumptionContext.get_thread_local()
@@ -1905,8 +1913,8 @@ class ArtifactBase(InstanceModelMixin):
     def validate(self):
         utils.validate_dict_values(self.properties)
 
-    def coerce_values(self, container, report_issues):
-        utils.coerce_dict_values(container, self.properties, report_issues)
+    def coerce_values(self, report_issues):
+        utils.coerce_dict_values(self.properties, report_issues)
 
     def dump(self):
         context = ConsumptionContext.get_thread_local()

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/aria/modeling/service_template.py
----------------------------------------------------------------------
diff --git a/aria/modeling/service_template.py b/aria/modeling/service_template.py
index e3320fa..7a192a7 100644
--- a/aria/modeling/service_template.py
+++ b/aria/modeling/service_template.py
@@ -17,7 +17,6 @@
 
 from __future__ import absolute_import  # so we can import standard 'types'
 
-from types import FunctionType
 from datetime import datetime
 
 from sqlalchemy import (
@@ -25,7 +24,8 @@ from sqlalchemy import (
     Text,
     Integer,
     Boolean,
-    DateTime
+    DateTime,
+    PickleType
 )
 from sqlalchemy.ext.declarative import declared_attr
 from sqlalchemy.ext.associationproxy import association_proxy
@@ -347,16 +347,16 @@ class ServiceTemplateBase(TemplateModelMixin):
         if self.artifact_types is not None:
             self.artifact_types.validate()
 
-    def coerce_values(self, container, report_issues):
-        utils.coerce_dict_values(container, self.meta_data, report_issues)
-        utils.coerce_dict_values(container, self.node_templates, report_issues)
-        utils.coerce_dict_values(container, self.group_templates, report_issues)
-        utils.coerce_dict_values(container, self.policy_templates, report_issues)
+    def coerce_values(self, report_issues):
+        utils.coerce_dict_values(self.meta_data, report_issues)
+        utils.coerce_dict_values(self.node_templates, report_issues)
+        utils.coerce_dict_values(self.group_templates, report_issues)
+        utils.coerce_dict_values(self.policy_templates, report_issues)
         if self.substitution_template is not None:
-            self.substitution_template.coerce_values(container, report_issues)
-        utils.coerce_dict_values(container, self.inputs, report_issues)
-        utils.coerce_dict_values(container, self.outputs, report_issues)
-        utils.coerce_dict_values(container, self.workflow_templates, report_issues)
+            self.substitution_template.coerce_values(report_issues)
+        utils.coerce_dict_values(self.inputs, report_issues)
+        utils.coerce_dict_values(self.outputs, report_issues)
+        utils.coerce_dict_values(self.workflow_templates, report_issues)
 
     def dump(self):
         context = ConsumptionContext.get_thread_local()
@@ -427,7 +427,7 @@ class NodeTemplateBase(TemplateModelMixin):
     :ivar requirement_templates: Potential relationships with other nodes
     :vartype requirement_templates: [:class:`RequirementTemplate`]
     :ivar target_node_template_constraints: Constraints for filtering relationship targets
-    :vartype target_node_template_constraints: [:class:`FunctionType`]
+    :vartype target_node_template_constraints: [:class:`NodeTemplateConstraint`]
     :ivar service_template: Containing service template
     :vartype service_template: :class:`ServiceTemplate`
     :ivar group_templates: We are a member of these groups
@@ -504,6 +504,10 @@ class NodeTemplateBase(TemplateModelMixin):
         return relationship.many_to_many(cls, 'parameter', prefix='properties', dict_key='name')
 
     @declared_attr
+    def attributes(cls):
+        return relationship.many_to_many(cls, 'parameter', prefix='attributes', dict_key='name')
+
+    @declared_attr
     def interface_templates(cls):
         return relationship.one_to_many(cls, 'interface_template', dict_key='name')
 
@@ -525,12 +529,12 @@ class NodeTemplateBase(TemplateModelMixin):
     default_instances = Column(Integer, default=1)
     min_instances = Column(Integer, default=0)
     max_instances = Column(Integer, default=None)
-    target_node_template_constraints = Column(modeling_types.StrictList(FunctionType))
+    target_node_template_constraints = Column(PickleType)
 
-    def is_target_node_valid(self, target_node_template):
+    def is_target_node_template_valid(self, target_node_template):
         if self.target_node_template_constraints:
-            for node_type_constraint in self.target_node_template_constraints:
-                if not node_type_constraint(target_node_template, self):
+            for node_template_constraint in self.target_node_template_constraints:
+                if not node_template_constraint.matches(self, target_node_template):
                     return False
         return True
 
@@ -544,6 +548,7 @@ class NodeTemplateBase(TemplateModelMixin):
             ('min_instances', self.min_instances),
             ('max_instances', self.max_instances),
             ('properties', formatting.as_raw_dict(self.properties)),
+            ('attributes', formatting.as_raw_dict(self.properties)),
             ('interface_templates', formatting.as_raw_list(self.interface_templates)),
             ('artifact_templates', formatting.as_raw_list(self.artifact_templates)),
             ('capability_templates', formatting.as_raw_list(self.capability_templates)),
@@ -564,24 +569,34 @@ class NodeTemplateBase(TemplateModelMixin):
                            runtime_properties={},
                            node_template=self)
         utils.instantiate_dict(node, node.properties, self.properties)
+        utils.instantiate_dict(node, node.attributes, self.attributes)
         utils.instantiate_dict(node, node.interfaces, self.interface_templates)
         utils.instantiate_dict(node, node.artifacts, self.artifact_templates)
         utils.instantiate_dict(node, node.capabilities, self.capability_templates)
+
+        # Default attributes
+        if 'tosca_name' in node.attributes:
+            node.attributes['tosca_name'].value = self.name
+        if 'tosca_id' in node.attributes:
+            node.attributes['tosca_id'].value = name
+
         return node
 
     def validate(self):
         utils.validate_dict_values(self.properties)
+        utils.validate_dict_values(self.attributes)
         utils.validate_dict_values(self.interface_templates)
         utils.validate_dict_values(self.artifact_templates)
         utils.validate_dict_values(self.capability_templates)
         utils.validate_list_values(self.requirement_templates)
 
-    def coerce_values(self, container, report_issues):
-        utils.coerce_dict_values(self, self.properties, report_issues)
-        utils.coerce_dict_values(self, self.interface_templates, report_issues)
-        utils.coerce_dict_values(self, self.artifact_templates, report_issues)
-        utils.coerce_dict_values(self, self.capability_templates, report_issues)
-        utils.coerce_list_values(self, self.requirement_templates, report_issues)
+    def coerce_values(self, report_issues):
+        utils.coerce_dict_values(self.properties, report_issues)
+        utils.coerce_dict_values(self.attributes, report_issues)
+        utils.coerce_dict_values(self.interface_templates, report_issues)
+        utils.coerce_dict_values(self.artifact_templates, report_issues)
+        utils.coerce_dict_values(self.capability_templates, report_issues)
+        utils.coerce_list_values(self.requirement_templates, report_issues)
 
     def dump(self):
         context = ConsumptionContext.get_thread_local()
@@ -597,6 +612,7 @@ class NodeTemplateBase(TemplateModelMixin):
                 if self.max_instances is not None
                 else ' or more'))
             utils.dump_dict_values(self.properties, 'Properties')
+            utils.dump_dict_values(self.attributes, 'Attributes')
             utils.dump_interfaces(self.interface_templates)
             utils.dump_dict_values(self.artifact_templates, 'Artifact templates')
             utils.dump_dict_values(self.capability_templates, 'Capability templates')
@@ -720,9 +736,9 @@ class GroupTemplateBase(TemplateModelMixin):
         utils.validate_dict_values(self.properties)
         utils.validate_dict_values(self.interface_templates)
 
-    def coerce_values(self, container, report_issues):
-        utils.coerce_dict_values(self, self.properties, report_issues)
-        utils.coerce_dict_values(self, self.interface_templates, report_issues)
+    def coerce_values(self, report_issues):
+        utils.coerce_dict_values(self.properties, report_issues)
+        utils.coerce_dict_values(self.interface_templates, report_issues)
 
     def dump(self):
         context = ConsumptionContext.get_thread_local()
@@ -851,8 +867,8 @@ class PolicyTemplateBase(TemplateModelMixin):
     def validate(self):
         utils.validate_dict_values(self.properties)
 
-    def coerce_values(self, container, report_issues):
-        utils.coerce_dict_values(self, self.properties, report_issues)
+    def coerce_values(self, report_issues):
+        utils.coerce_dict_values(self.properties, report_issues)
 
     def dump(self):
         context = ConsumptionContext.get_thread_local()
@@ -945,8 +961,8 @@ class SubstitutionTemplateBase(TemplateModelMixin):
     def validate(self):
         utils.validate_dict_values(self.mappings)
 
-    def coerce_values(self, container, report_issues):
-        utils.coerce_dict_values(self, self.mappings, report_issues)
+    def coerce_values(self, report_issues):
+        utils.coerce_dict_values(self.mappings, report_issues)
 
     def dump(self):
         context = ConsumptionContext.get_thread_local()
@@ -1049,7 +1065,7 @@ class SubstitutionTemplateMappingBase(TemplateModelMixin):
         return collections.OrderedDict((
             ('name', self.name)))
 
-    def coerce_values(self, container, report_issues):
+    def coerce_values(self, report_issues):
         pass
 
     def instantiate(self, container):
@@ -1113,7 +1129,7 @@ class RequirementTemplateBase(TemplateModelMixin):
     :ivar target_capability_name: Name of capability in target node (optional)
     :vartype target_capability_name: basestring
     :ivar target_node_template_constraints: Constraints for filtering relationship targets
-    :vartype target_node_template_constraints: [:class:`FunctionType`]
+    :vartype target_node_template_constraints: [:class:`NodeTemplateConstraint`]
     :ivar relationship_template: Template for relationships (optional)
     :vartype relationship_template: :class:`RelationshipTemplate`
     :ivar node_template: Containing node template
@@ -1183,9 +1199,7 @@ class RequirementTemplateBase(TemplateModelMixin):
 
     @declared_attr
     def relationship_template(cls):
-        return relationship.one_to_one(cls,
-                                       'relationship_template',
-                                       back_populates=relationship.NO_BACK_POP)
+        return relationship.one_to_one(cls, 'relationship_template')
 
     # endregion
 
@@ -1215,18 +1229,18 @@ class RequirementTemplateBase(TemplateModelMixin):
     # endregion
 
     target_capability_name = Column(Text)
-    target_node_template_constraints = Column(modeling_types.StrictList(FunctionType))
+    target_node_template_constraints = Column(PickleType)
 
     def find_target(self, source_node_template):
         context = ConsumptionContext.get_thread_local()
 
         # We might already have a specific node template, so we'll just verify it
         if self.target_node_template is not None:
-            if not source_node_template.is_target_node_valid(self.target_node_template):
+            if not source_node_template.is_target_node_template_valid(self.target_node_template):
                 context.validation.report('requirement "{0}" of node template "{1}" is for node '
                                           'template "{2}" but it does not match constraints'.format(
                                               self.name,
-                                              self.target_node_template_name,
+                                              self.target_node_template.name,
                                               source_node_template.name),
                                           level=validation.Issue.BETWEEN_TYPES)
             if (self.target_capability_type is not None) \
@@ -1247,7 +1261,7 @@ class RequirementTemplateBase(TemplateModelMixin):
                 if self.target_node_type.get_descendant(target_node_template.type.name) is None:
                     continue
 
-                if not source_node_template.is_target_node_valid(target_node_template):
+                if not source_node_template.is_target_node_template_valid(target_node_template):
                     continue
 
                 target_node_capability = self.find_target_capability(source_node_template,
@@ -1284,9 +1298,9 @@ class RequirementTemplateBase(TemplateModelMixin):
         if self.relationship_template:
             self.relationship_template.validate()
 
-    def coerce_values(self, container, report_issues):
+    def coerce_values(self, report_issues):
         if self.relationship_template is not None:
-            self.relationship_template.coerce_values(container, report_issues)
+            self.relationship_template.coerce_values(report_issues)
 
     def dump(self):
         context = ConsumptionContext.get_thread_local()
@@ -1417,9 +1431,9 @@ class RelationshipTemplateBase(TemplateModelMixin):
         utils.validate_dict_values(self.properties)
         utils.validate_dict_values(self.interface_templates)
 
-    def coerce_values(self, container, report_issues):
-        utils.coerce_dict_values(self, self.properties, report_issues)
-        utils.coerce_dict_values(self, self.interface_templates, report_issues)
+    def coerce_values(self, report_issues):
+        utils.coerce_dict_values(self.properties, report_issues)
+        utils.coerce_dict_values(self.interface_templates, report_issues)
 
     def dump(self):
         context = ConsumptionContext.get_thread_local()
@@ -1543,8 +1557,8 @@ class CapabilityTemplateBase(TemplateModelMixin):
 
         # Apply requirement constraints
         if requirement.target_node_template_constraints:
-            for node_type_constraint in requirement.target_node_template_constraints:
-                if not node_type_constraint(target_node_template, source_node_template):
+            for node_template_constraint in requirement.target_node_template_constraints:
+                if not node_template_constraint.matches(source_node_template, target_node_template):
                     return False
 
         return True
@@ -1574,8 +1588,8 @@ class CapabilityTemplateBase(TemplateModelMixin):
     def validate(self):
         utils.validate_dict_values(self.properties)
 
-    def coerce_values(self, container, report_issues):
-        utils.coerce_dict_values(self, self.properties, report_issues)
+    def coerce_values(self, report_issues):
+        utils.coerce_dict_values(self.properties, report_issues)
 
     def dump(self):
         context = ConsumptionContext.get_thread_local()
@@ -1728,9 +1742,9 @@ class InterfaceTemplateBase(TemplateModelMixin):
         utils.validate_dict_values(self.inputs)
         utils.validate_dict_values(self.operation_templates)
 
-    def coerce_values(self, container, report_issues):
-        utils.coerce_dict_values(container, self.inputs, report_issues)
-        utils.coerce_dict_values(container, self.operation_templates, report_issues)
+    def coerce_values(self, report_issues):
+        utils.coerce_dict_values(self.inputs, report_issues)
+        utils.coerce_dict_values(self.operation_templates, report_issues)
 
     def dump(self):
         context = ConsumptionContext.get_thread_local()
@@ -1882,7 +1896,7 @@ class OperationTemplateBase(TemplateModelMixin):
                 plugin = None
                 implementation = None
         else:
-            # using the execution plugin
+            # Using the execution plugin
             plugin = None
             implementation = self.implementation
 
@@ -1903,8 +1917,8 @@ class OperationTemplateBase(TemplateModelMixin):
     def validate(self):
         utils.validate_dict_values(self.inputs)
 
-    def coerce_values(self, container, report_issues):
-        utils.coerce_dict_values(container, self.inputs, report_issues)
+    def coerce_values(self, report_issues):
+        utils.coerce_dict_values(self.inputs, report_issues)
 
     def dump(self):
         context = ConsumptionContext.get_thread_local()
@@ -2051,8 +2065,8 @@ class ArtifactTemplateBase(TemplateModelMixin):
     def validate(self):
         utils.validate_dict_values(self.properties)
 
-    def coerce_values(self, container, report_issues):
-        utils.coerce_dict_values(container, self.properties, report_issues)
+    def coerce_values(self, report_issues):
+        utils.coerce_dict_values(self.properties, report_issues)
 
     def dump(self):
         context = ConsumptionContext.get_thread_local()
@@ -2128,7 +2142,7 @@ class PluginSpecificationBase(TemplateModelMixin):
             ('version', self.version),
             ('enabled', self.enabled)))
 
-    def coerce_values(self, container, report_issues):
+    def coerce_values(self, report_issues):
         pass
 
     def resolve(self, model_storage):

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/aria/modeling/utils.py
----------------------------------------------------------------------
diff --git a/aria/modeling/utils.py b/aria/modeling/utils.py
index 91d7b9c..0404fe4 100644
--- a/aria/modeling/utils.py
+++ b/aria/modeling/utils.py
@@ -19,9 +19,6 @@ from StringIO import StringIO
 
 from . import exceptions
 from ..parser.consumption import ConsumptionContext
-from ..parser.exceptions import InvalidValueError
-from ..parser.presentation import Value
-from ..utils.collections import OrderedDict
 from ..utils.console import puts
 from ..utils.type import validate_value_type
 
@@ -39,6 +36,21 @@ class ModelJSONEncoder(JSONEncoder):
             return JSONEncoder.default(self, o)
 
 
+class NodeTemplateContainerHolder(object):
+    """
+    Wrapper that allows using a :class:`aria.modeling.models.NodeTemplate` model directly as the
+    ``container_holder`` argument for :func:`aria.modeling.functions.evaluate`.
+    """
+
+    def __init__(self, node_template):
+        self.container = node_template
+        self.service = None
+
+    @property
+    def service_template(self):
+        return self.container.service_template
+
+
 def create_inputs(inputs, template_inputs):
     """
     :param inputs: key-value dict
@@ -50,7 +62,7 @@ def create_inputs(inputs, template_inputs):
     from . import models
     input_models = []
     for input_name, input_val in merged_inputs.iteritems():
-        parameter = models.Parameter(
+        parameter = models.Parameter( # pylint: disable=unexpected-keyword-arg
             name=input_name,
             type_name=template_inputs[input_name].type_name,
             description=template_inputs[input_name].description,
@@ -109,39 +121,17 @@ def _merge_and_validate_inputs(inputs, template_inputs):
     return merged_inputs
 
 
-def coerce_value(container, value, report_issues=False):
-    if isinstance(value, Value):
-        value = value.value
-
-    if isinstance(value, list):
-        return [coerce_value(container, v, report_issues) for v in value]
-    elif isinstance(value, dict):
-        return OrderedDict((k, coerce_value(container, v, report_issues))
-                           for k, v in value.iteritems())
-    elif hasattr(value, '_evaluate'):
-        context = ConsumptionContext.get_thread_local()
-        try:
-            value = value._evaluate(context, container)
-            value = coerce_value(container, value, report_issues)
-        except exceptions.CannotEvaluateFunctionException:
-            pass
-        except InvalidValueError as e:
-            if report_issues:
-                context.validation.report(e.issue)
-    return value
-
-
-def coerce_dict_values(container, the_dict, report_issues=False):
+def coerce_dict_values(the_dict, report_issues=False):
     if not the_dict:
         return
-    coerce_list_values(container, the_dict.itervalues(), report_issues)
+    coerce_list_values(the_dict.itervalues(), report_issues)
 
 
-def coerce_list_values(container, the_list, report_issues=False):
+def coerce_list_values(the_list, report_issues=False):
     if not the_list:
         return
     for value in the_list:
-        value.coerce_values(container, report_issues)
+        value.coerce_values(report_issues)
 
 
 def validate_dict_values(the_dict):

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/aria/orchestrator/execution_plugin/instantiation.py
----------------------------------------------------------------------
diff --git a/aria/orchestrator/execution_plugin/instantiation.py b/aria/orchestrator/execution_plugin/instantiation.py
index ce114f0..c09434e 100644
--- a/aria/orchestrator/execution_plugin/instantiation.py
+++ b/aria/orchestrator/execution_plugin/instantiation.py
@@ -15,7 +15,7 @@
 
 # TODO: this module will eventually be moved to a new "aria.instantiation" package
 
-from ...utils.formatting import full_type_name
+from ...utils.type import full_type_name
 from ...utils.collections import OrderedDict
 from ...parser import validation
 from ...parser.consumption import ConsumptionContext

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/aria/parser/__init__.py
----------------------------------------------------------------------
diff --git a/aria/parser/__init__.py b/aria/parser/__init__.py
index 9ab8785..64df88a 100644
--- a/aria/parser/__init__.py
+++ b/aria/parser/__init__.py
@@ -13,7 +13,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-from .specification import dsl_specification, iter_specifications
+from .specification import implements_specification, iter_specifications
 
 
 MODULES = (
@@ -26,5 +26,5 @@ MODULES = (
 
 __all__ = (
     'MODULES',
-    'dsl_specification',
+    'implements_specification',
     'iter_specifications')

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/aria/parser/consumption/__init__.py
----------------------------------------------------------------------
diff --git a/aria/parser/consumption/__init__.py b/aria/parser/consumption/__init__.py
index 8f6d2b6..76e73be 100644
--- a/aria/parser/consumption/__init__.py
+++ b/aria/parser/consumption/__init__.py
@@ -28,9 +28,11 @@ from .modeling import (
     Types,
     ServiceInstance,
     FindHosts,
+    ValidateServiceInstance,
     ConfigureOperations,
     SatisfyRequirements,
-    ValidateCapabilities
+    ValidateCapabilities,
+    CoerceServiceInstanceValues
 )
 from .inputs import Inputs
 
@@ -45,7 +47,10 @@ __all__ = (
     'ServiceTemplate',
     'Types',
     'ServiceInstance',
-    'Inputs',
+    'FindHosts',
+    'ValidateServiceInstance',
+    'ConfigureOperations',
     'SatisfyRequirements',
-    'ValidateCapabilities'
+    'ValidateCapabilities',
+    'CoerceServiceInstanceValues'
 )

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/aria/parser/consumption/modeling.py
----------------------------------------------------------------------
diff --git a/aria/parser/consumption/modeling.py b/aria/parser/consumption/modeling.py
index 771fd7f..44027b9 100644
--- a/aria/parser/consumption/modeling.py
+++ b/aria/parser/consumption/modeling.py
@@ -42,7 +42,7 @@ class CoerceServiceTemplateValues(Consumer):
     """
 
     def consume(self):
-        self.context.modeling.template.coerce_values(None, True)
+        self.context.modeling.template.coerce_values(True)
 
 
 class ValidateServiceTemplate(Consumer):
@@ -116,7 +116,7 @@ class CoerceServiceInstanceValues(Consumer):
     """
 
     def consume(self):
-        self.context.modeling.instance.coerce_values(None, True)
+        self.context.modeling.instance.coerce_values(True)
 
 
 class ValidateServiceInstance(Consumer):

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/aria/parser/presentation/fields.py
----------------------------------------------------------------------
diff --git a/aria/parser/presentation/fields.py b/aria/parser/presentation/fields.py
index da3cb05..7f85723 100644
--- a/aria/parser/presentation/fields.py
+++ b/aria/parser/presentation/fields.py
@@ -21,7 +21,8 @@ from ...exceptions import AriaException
 from ...utils.collections import FrozenDict, FrozenList, deepcopy_with_locators, merge, OrderedDict
 from ...utils.caching import cachedmethod
 from ...utils.console import puts
-from ...utils.formatting import as_raw, safe_repr, full_type_name
+from ...utils.formatting import as_raw, safe_repr
+from ...utils.type import full_type_name
 from ...utils.exceptions import print_exception
 from ..exceptions import InvalidValueError
 

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/aria/parser/presentation/presentation.py
----------------------------------------------------------------------
diff --git a/aria/parser/presentation/presentation.py b/aria/parser/presentation/presentation.py
index 0f098b5..644d880 100644
--- a/aria/parser/presentation/presentation.py
+++ b/aria/parser/presentation/presentation.py
@@ -15,7 +15,8 @@
 
 from ...utils.caching import HasCachedMethods
 from ...utils.collections import deepcopy_with_locators
-from ...utils.formatting import full_type_name, safe_repr
+from ...utils.formatting import safe_repr
+from ...utils.type import full_type_name
 from ...utils.console import puts
 from ..validation import Issue
 from .null import none_to_null

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/aria/parser/presentation/utils.py
----------------------------------------------------------------------
diff --git a/aria/parser/presentation/utils.py b/aria/parser/presentation/utils.py
index 74adbd1..fbe971b 100644
--- a/aria/parser/presentation/utils.py
+++ b/aria/parser/presentation/utils.py
@@ -15,7 +15,8 @@
 
 from types import FunctionType
 
-from ...utils.formatting import full_type_name, safe_repr
+from ...utils.formatting import safe_repr
+from ...utils.type import full_type_name
 from ..validation import Issue
 from .null import NULL
 

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/aria/parser/specification.py
----------------------------------------------------------------------
diff --git a/aria/parser/specification.py b/aria/parser/specification.py
index 1df11ce..714bed1 100644
--- a/aria/parser/specification.py
+++ b/aria/parser/specification.py
@@ -17,40 +17,7 @@ import re
 
 from ..extension import parser
 from ..utils.collections import OrderedDict
-from ..utils.formatting import full_type_name
-
-_DSL_SPECIFICATIONS = {}
-
-
-def dsl_specification(section, spec):
-    """
-    Decorator for TOSCA specification.
-
-    Used for documentation and standards compliance.
-    """
-    def decorator(obj):
-        specification = _DSL_SPECIFICATIONS.get(spec)
-
-        if specification is None:
-            specification = {}
-            _DSL_SPECIFICATIONS[spec] = specification
-
-        if section in specification:
-            raise Exception('you cannot specify the same @dsl_specification twice, consider'
-                            ' adding \'-1\', \'-2\', etc.: %s, %s' % (spec, section))
-
-        specification[section] = OrderedDict((
-            ('code', full_type_name(obj)),
-            ('doc', obj.__doc__)))
-
-        try:
-            setattr(obj, '_dsl_specifications', {section: section, spec: spec})
-        except BaseException:
-            pass
-
-        return obj
-
-    return decorator
+from ..utils.specification import (DSL_SPECIFICATIONS, implements_specification) # pylint: disable=unused-import
 
 
 def iter_specifications():
@@ -63,12 +30,10 @@ def iter_specifications():
             details['code'] = sections[k]['code']
             yield k, _fix_details(sections[k], spec)
 
-    for spec, sections in _DSL_SPECIFICATIONS.iteritems():
+    for spec, sections in DSL_SPECIFICATIONS.iteritems():
         yield spec, iter_sections(spec, sections)
 
 
-# Utils
-
 def _section_key(value):
     try:
         parts = value.split('-', 1)

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/aria/parser/validation/issue.py
----------------------------------------------------------------------
diff --git a/aria/parser/validation/issue.py b/aria/parser/validation/issue.py
index f001efc..db8065d 100644
--- a/aria/parser/validation/issue.py
+++ b/aria/parser/validation/issue.py
@@ -16,7 +16,7 @@
 from __future__ import absolute_import  # so we can import standard 'collections'
 
 from ...utils.collections import OrderedDict
-from ...utils.formatting import full_type_name
+from ...utils.type import full_type_name
 
 
 class Issue(object):

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/aria/utils/caching.py
----------------------------------------------------------------------
diff --git a/aria/utils/caching.py b/aria/utils/caching.py
index 613ade6..c9e475a 100644
--- a/aria/utils/caching.py
+++ b/aria/utils/caching.py
@@ -65,22 +65,24 @@ class cachedmethod(object):  # pylint: disable=invalid-name
             return self.func(*args, **kwargs)
 
         instance = args[0]
-        cache = instance.get_method_cache()
+        if not hasattr(instance, '_method_cache'):
+            instance._method_cache = {}
+        method_cache = instance._method_cache
 
         key = (self.func, args[1:], frozenset(kwargs.items()))
 
         try:
             with self.lock:
-                return_value = cache[key]
+                return_value = method_cache[key]
                 self.hits += 1
         except KeyError:
             return_value = self.func(*args, **kwargs)
             with self.lock:
-                cache[key] = return_value
+                method_cache[key] = return_value
                 self.misses += 1
             # Another thread may override our cache entry here, so we need to read
             # it again to make sure all threads use the same return value
-            return_value = cache.get(key, return_value)
+            return_value = method_cache.get(key, return_value)
 
         return return_value
 
@@ -93,9 +95,6 @@ class HasCachedMethods(object):
     def __init__(self, method_cache=None):
         self._method_cache = method_cache or {}
 
-    def get_method_cache(self):
-        return self._method_cache
-
     @property
     def _method_cache_info(self):
         """

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/aria/utils/formatting.py
----------------------------------------------------------------------
diff --git a/aria/utils/formatting.py b/aria/utils/formatting.py
index b5e141d..f96a4ce 100644
--- a/aria/utils/formatting.py
+++ b/aria/utils/formatting.py
@@ -71,18 +71,6 @@ class YamlAsRawDumper(yaml.dumper.RoundTripDumper):  # pylint: disable=too-many-
         return super(YamlAsRawDumper, self).represent_data(data)
 
 
-def full_type_name(value):
-    """
-    The full class name of a type or object.
-    """
-
-    if not isinstance(value, type):
-        value = value.__class__
-    module = str(value.__module__)
-    name = str(value.__name__)
-    return name if module == '__builtin__' else '%s.%s' % (module, name)
-
-
 def decode_list(data):
     decoded_list = []
     for item in data:
@@ -163,8 +151,8 @@ def as_raw(value):
             value = value()
     elif isinstance(value, list):
         value = list(value)
-        for i, _ in enumerate(value):
-            value[i] = as_raw(value[i])
+        for i, v in enumerate(value):
+            value[i] = as_raw(v)
     elif isinstance(value, dict):
         value = dict(value)
         for k, v in value.iteritems():

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/aria/utils/specification.py
----------------------------------------------------------------------
diff --git a/aria/utils/specification.py b/aria/utils/specification.py
new file mode 100644
index 0000000..e74c103
--- /dev/null
+++ b/aria/utils/specification.py
@@ -0,0 +1,53 @@
+# 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 .collections import OrderedDict
+
+
+DSL_SPECIFICATIONS = {}
+
+
+def implements_specification(section, spec):
+    """
+    Decorator for specification implementations.
+
+    Used for documentation and standards compliance.
+    """
+
+    from .type import full_type_name
+
+    def decorator(obj):
+        specification = DSL_SPECIFICATIONS.get(spec)
+
+        if specification is None:
+            specification = {}
+            DSL_SPECIFICATIONS[spec] = specification
+
+        if section in specification:
+            raise Exception('you cannot specify the same @implements_specification twice, consider'
+                            ' adding \'-1\', \'-2\', etc.: {0}, {1}'.format(spec, section))
+
+        specification[section] = OrderedDict((
+            ('code', full_type_name(obj)),
+            ('doc', obj.__doc__)))
+
+        try:
+            setattr(obj, '_dsl_specifications', {section: section, spec: spec})
+        except BaseException:
+            pass
+
+        return obj
+
+    return decorator

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/aria/utils/type.py
----------------------------------------------------------------------
diff --git a/aria/utils/type.py b/aria/utils/type.py
index dad5427..f08159a 100644
--- a/aria/utils/type.py
+++ b/aria/utils/type.py
@@ -13,49 +13,142 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+import datetime
+
+from .specification import implements_specification
+
+
+BASE_TYPES_TO_CANONICAL_NAMES = {
+    # TOSCA aliases:
+    None.__class__: 'null',
+    basestring: 'string',
+    int: 'integer',
+    float: 'float',
+    bool: 'boolean',
+    list: 'list',
+    tuple: 'list',
+    dict: 'map',
+    datetime.datetime: 'timestamp'
+}
+
+NAMES_TO_CANONICAL_TYPES = {
+    # Python:
+    'none': None.__class__,
+    'basestring': unicode,
+    'str': unicode,
+    'unicode': unicode,
+    'int': int,
+    'float': float, # also a TOSCA alias
+    'bool': bool,
+    'list': list, # also a TOSCA alias
+    'tuple': list,
+    'dict': dict,
+    'datetime': datetime.datetime,
+
+    # YAML 1.2:
+    'tag:yaml.org,2002:null': None.__class__,
+    'tag:yaml.org,2002:str': unicode,
+    'tag:yaml.org,2002:integer': int,
+    'tag:yaml.org,2002:float': float,
+    'tag:yaml.org,2002:bool': bool,
+
+    # TOSCA aliases:
+    'null': None.__class__,
+    'string': unicode,
+    'integer': int,
+    'boolean': bool,
+
+    # TOSCA custom types:
+    'map': dict,
+    'timestamp': datetime.datetime
+}
+
+
+def full_type_name(value):
+    """
+    The full class name of a type or instance.
+    """
+
+    if not isinstance(value, type):
+        value = value.__class__
+    module = str(value.__module__)
+    name = str(value.__name__)
+    return name if module == '__builtin__' else '{0}.{1}'.format(module, name)
+
+
+@implements_specification('3.2.1-1', 'tosca-simple-1.0')
+def canonical_type_name(value):
+    """
+    Returns the canonical TOSCA type name of a primitive value, or None if unknown.
+
+    For a list of TOSCA type names, see the `TOSCA Simple Profile v1.0
+    cos01 specification <http://docs.oasis-open.org/tosca/TOSCA-Simple-Profile-YAML/v1.0/cos01
+    /TOSCA-Simple-Profile-YAML-v1.0-cos01.html#_Toc373867862>`__
+    """
+
+    for the_type, name in BASE_TYPES_TO_CANONICAL_NAMES.iteritems():
+        if isinstance(value, the_type):
+            return name
+    return None
+
+
+@implements_specification('3.2.1-2', 'tosca-simple-1.0')
+def canonical_type(type_name):
+    """
+    Return the canonical type for any Python, YAML, or TOSCA type name or alias, or None if
+    unsupported.
+
+    :param type_name: Type name (case insensitive)
+    """
+
+    return NAMES_TO_CANONICAL_TYPES.get(type_name.lower())
+
 
 def validate_value_type(value, type_name):
     """
-    Validate a value is of a specific type.
+    Validate that a value is of a specific type. Supports Python, YAML, and TOSCA type names and
+    aliases.
+
     A ValueError will be raised on type mismatch.
-    Supports both python and yaml type names.
-    """
-
-    #TODO add timestamp type?
-    name_to_type = {
-        'list': list,
-        'dict': dict,
-        'tuple': tuple,
-        'str': str,
-        'unicode': str,
-        'string': str,
-        'int': int,
-        'integer': int,
-        'bool': bool,
-        'boolean': bool,
-        'float': float
-    }
-
-    type_ = name_to_type.get(type_name.lower())
-    if type_ is None:
-        raise RuntimeError('No supported type_name was provided')
-
-    if not isinstance(value, type_):
+
+    :param type_name: Type name (case insensitive)
+    """
+
+    the_type = canonical_type(type_name)
+    if the_type is None:
+        raise RuntimeError('Unsupported type name: {0}'.format(type_name))
+
+    # The following Python types do not inherit from the canonical type, but are considered valid
+    if (the_type is unicode) and isinstance(value, str):
+        return
+    if (the_type is list) and isinstance(value, tuple):
+        return
+
+    if not isinstance(value, the_type):
         raise ValueError('Value {0} is not of type {1}'.format(value, type_name))
 
 
-def convert_value_to_type(str_value, type_name):
+def convert_value_to_type(str_value, python_type_name):
+    """
+    Converts a value to a specific Python primitive type.
+
+    A ValueError will be raised for unsupported types or conversion failure.
+
+    :param python_type_name: Python primitive type name (case insensitive)
+    """
+
+    python_type_name = python_type_name.lower()
     try:
-        if type_name.lower() in ['str', 'unicode']:
+        if python_type_name in ('str', 'unicode'):
             return str_value.decode('utf-8')
-        elif type_name.lower() == 'int':
+        elif python_type_name == 'int':
             return int(str_value)
-        elif type_name.lower() == 'bool':
+        elif python_type_name == 'bool':
             return bool(str_value)
-        elif type_name.lower() == 'float':
+        elif python_type_name == 'float':
             return float(str_value)
         else:
-            raise ValueError('No supported type_name was provided')
+            raise ValueError('Unsupported Python type name: {0}'.format(python_type_name))
     except ValueError:
-        raise ValueError('Trying to convert {0} to {1} failed'.format(str_value,
-                                                                      type_name))
+        raise ValueError('Failed to to convert {0} to {1}'.format(str_value,
+                                                                  python_type_name))

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/examples/tosca-simple-1.0/use-cases/block-storage-1/block-storage-1.yaml
----------------------------------------------------------------------
diff --git a/examples/tosca-simple-1.0/use-cases/block-storage-1/block-storage-1.yaml b/examples/tosca-simple-1.0/use-cases/block-storage-1/block-storage-1.yaml
index ff6dc92..b912fb2 100644
--- a/examples/tosca-simple-1.0/use-cases/block-storage-1/block-storage-1.yaml
+++ b/examples/tosca-simple-1.0/use-cases/block-storage-1/block-storage-1.yaml
@@ -65,4 +65,4 @@ topology_template:
       value: { get_attribute: [ my_server, private_address ] }
     volume_id:
       description: The volume id of the block storage instance.
-      value: { get_attribute: [ my_storage, volume_id ] }
+      value: { get_property: [ my_storage, volume_id ] } # ARIA NOTE: wrong in spec

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/examples/tosca-simple-1.0/use-cases/block-storage-2/block-storage-2.yaml
----------------------------------------------------------------------
diff --git a/examples/tosca-simple-1.0/use-cases/block-storage-2/block-storage-2.yaml b/examples/tosca-simple-1.0/use-cases/block-storage-2/block-storage-2.yaml
index 09c30a7..ac475cf 100644
--- a/examples/tosca-simple-1.0/use-cases/block-storage-2/block-storage-2.yaml
+++ b/examples/tosca-simple-1.0/use-cases/block-storage-2/block-storage-2.yaml
@@ -72,4 +72,4 @@ topology_template:
 
     volume_id:
       description: The volume id of the block storage instance.
-      value: { get_attribute: [ my_storage, volume_id ] }
+      value: { get_property: [ my_storage, volume_id ] } # ARIA NOTE: wrong in spec

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/examples/tosca-simple-1.0/use-cases/block-storage-3/block-storage-3.yaml
----------------------------------------------------------------------
diff --git a/examples/tosca-simple-1.0/use-cases/block-storage-3/block-storage-3.yaml b/examples/tosca-simple-1.0/use-cases/block-storage-3/block-storage-3.yaml
index 3018fe9..c3f183e 100644
--- a/examples/tosca-simple-1.0/use-cases/block-storage-3/block-storage-3.yaml
+++ b/examples/tosca-simple-1.0/use-cases/block-storage-3/block-storage-3.yaml
@@ -65,4 +65,4 @@ topology_template:
       value: { get_attribute: [ my_server, private_address ] }
     volume_id:
       description: The volume id of the block storage instance.
-      value: { get_attribute: [ my_storage, volume_id ] }
+      value: { get_property: [ my_storage, volume_id ] } # ARIA NOTE: wrong in spec

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/examples/tosca-simple-1.0/use-cases/block-storage-4/block-storage-4.yaml
----------------------------------------------------------------------
diff --git a/examples/tosca-simple-1.0/use-cases/block-storage-4/block-storage-4.yaml b/examples/tosca-simple-1.0/use-cases/block-storage-4/block-storage-4.yaml
index 0693ddd..e2bdb9f 100644
--- a/examples/tosca-simple-1.0/use-cases/block-storage-4/block-storage-4.yaml
+++ b/examples/tosca-simple-1.0/use-cases/block-storage-4/block-storage-4.yaml
@@ -93,4 +93,4 @@ topology_template:
       value: { get_attribute: [ my_web_app_tier_2, private_address ] }
     volume_id:
       description: The volume id of the block storage instance.
-      value: { get_attribute: [ my_storage, volume_id ] }
+      value: { get_property: [ my_storage, volume_id ] } # ARIA NOTE: wrong in spec

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/examples/tosca-simple-1.0/use-cases/block-storage-5/block-storage-5.yaml
----------------------------------------------------------------------
diff --git a/examples/tosca-simple-1.0/use-cases/block-storage-5/block-storage-5.yaml b/examples/tosca-simple-1.0/use-cases/block-storage-5/block-storage-5.yaml
index 5f5cf71..a0c2229 100644
--- a/examples/tosca-simple-1.0/use-cases/block-storage-5/block-storage-5.yaml
+++ b/examples/tosca-simple-1.0/use-cases/block-storage-5/block-storage-5.yaml
@@ -100,10 +100,10 @@ topology_template:
   outputs:
     private_ip_1:
       description: The private IP address of the application's first tier.
-      value: { get_attribute: [my_web_app_tier_1, private_address] }
+      value: { get_attribute: [ my_web_app_tier_1, private_address ] }
     private_ip_2:
       description: The private IP address of the application's second tier.
-      value: { get_attribute: [my_web_app_tier_2, private_address] }
+      value: { get_attribute: [ my_web_app_tier_2, private_address ] }
     volume_id:
       description: The volume id of the block storage instance.
-      value: { get_attribute: [my_storage, volume_id] }
+      value: { get_property: [ my_storage, volume_id ] } # ARIA NOTE: wrong in spec

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/examples/tosca-simple-1.0/use-cases/block-storage-6/block-storage-6.yaml
----------------------------------------------------------------------
diff --git a/examples/tosca-simple-1.0/use-cases/block-storage-6/block-storage-6.yaml b/examples/tosca-simple-1.0/use-cases/block-storage-6/block-storage-6.yaml
index 808245b..534884a 100644
--- a/examples/tosca-simple-1.0/use-cases/block-storage-6/block-storage-6.yaml
+++ b/examples/tosca-simple-1.0/use-cases/block-storage-6/block-storage-6.yaml
@@ -96,7 +96,7 @@ topology_template:
       value: { get_attribute: [ my_server2, private_address ] }
     volume_id_1:
       description: The volume id of the first block storage instance.
-      value: { get_attribute: [my_storage, volume_id] }
+      value: { get_property: [ my_storage, volume_id ] } # ARIA NOTE: wrong in spec
     volume_id_2:
       description: The volume id of the second block storage instance.
-      value: { get_attribute: [ my_storage2, volume_id ] }
+      value: { get_property: [ my_storage2, volume_id ] } # ARIA NOTE: wrong in spec

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/examples/tosca-simple-1.0/use-cases/multi-tier-1/multi-tier-1.yaml
----------------------------------------------------------------------
diff --git a/examples/tosca-simple-1.0/use-cases/multi-tier-1/multi-tier-1.yaml b/examples/tosca-simple-1.0/use-cases/multi-tier-1/multi-tier-1.yaml
index 3485e49..50401ec 100644
--- a/examples/tosca-simple-1.0/use-cases/multi-tier-1/multi-tier-1.yaml
+++ b/examples/tosca-simple-1.0/use-cases/multi-tier-1/multi-tier-1.yaml
@@ -59,7 +59,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:
@@ -90,7 +90,7 @@ topology_template:
           configure:
             implementation: scripts/mongodb/config.sh
             inputs:
-              mongodb_ip: { get_attribute: [mongo_server, ip_address] }
+              mongodb_ip: { get_attribute: [ mongo_server, private_address ] } # ARIA NOTE: wrong in spec
           start: scripts/mongodb/start.sh
 
     elasticsearch:
@@ -115,7 +115,7 @@ topology_template:
                   pre_configure_source:
                     implementation: python/logstash/configure_elasticsearch.py
                     inputs:
-                      elasticsearch_ip: { get_attribute: [elasticsearch_server, ip_address] }
+                      elasticsearch_ip: { get_attribute: [ elasticsearch_server, private_address ] } # ARIA NOTE: wrong in spec
       interfaces:
         Standard: # ARIA NOTE: wrong in spec
           create: scripts/lostash/create.sh
@@ -133,8 +133,8 @@ topology_template:
           configure:
             implementation: scripts/kibana/config.sh
             inputs:
-              elasticsearch_ip: { get_attribute: [ elasticsearch_server, ip_address ] }
-              kibana_ip: { get_attribute: [ kibana_server, ip_address ] }
+              elasticsearch_ip: { get_attribute: [ elasticsearch_server, private_address ] } # ARIA NOTE: wrong in spec
+              kibana_ip: { get_attribute: [ kibana_server, private_address ] } # ARIA NOTE: wrong in spec
           start: scripts/kibana/start.sh
 
     app_collectd:
@@ -155,7 +155,7 @@ topology_template:
           configure:
             implementation: python/collectd/config.py
             inputs:
-              logstash_ip: { get_attribute: [ logstash_server, ip_address ] }
+              logstash_ip: { get_attribute: [ logstash_server, private_address ] } # ARIA NOTE: wrong in spec
           start: scripts/collectd/start.sh
 
     app_rsyslog:
@@ -176,7 +176,7 @@ topology_template:
           configure:
             implementation: scripts/rsyslog/config.sh
             inputs:
-              logstash_ip: { get_attribute: [ logstash_server, ip_address ] }
+              logstash_ip: { get_attribute: [ logstash_server, private_address ] } # ARIA NOTE: wrong in spec
           start: scripts/rsyslog/start.sh
 
     app_server:

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/extensions/aria_extension_tosca/simple_v1_0/__init__.py
----------------------------------------------------------------------
diff --git a/extensions/aria_extension_tosca/simple_v1_0/__init__.py b/extensions/aria_extension_tosca/simple_v1_0/__init__.py
index 29df362..7dcc60a 100644
--- a/extensions/aria_extension_tosca/simple_v1_0/__init__.py
+++ b/extensions/aria_extension_tosca/simple_v1_0/__init__.py
@@ -30,8 +30,6 @@ from .types import (ArtifactType, DataType, CapabilityType, InterfaceType, Relat
                     NodeType, GroupType, PolicyType)
 from .data_types import (Timestamp, Version, Range, List, Map, ScalarSize, ScalarTime,
                          ScalarFrequency)
-from .functions import (Concat, Token, GetInput, GetProperty, GetAttribute, GetOperationOutput,
-                        GetNodesOfType, GetArtifact)
 
 MODULES = (
     'modeling',
@@ -89,12 +87,4 @@ __all__ = (
     'Map',
     'ScalarSize',
     'ScalarTime',
-    'ScalarFrequency',
-    'Concat',
-    'Token',
-    'GetInput',
-    'GetProperty',
-    'GetAttribute',
-    'GetOperationOutput',
-    'GetNodesOfType',
-    'GetArtifact')
+    'ScalarFrequency')

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/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 9a2179a..d929ce0 100644
--- a/extensions/aria_extension_tosca/simple_v1_0/assignments.py
+++ b/extensions/aria_extension_tosca/simple_v1_0/assignments.py
@@ -15,7 +15,7 @@
 
 from aria.utils.collections import FrozenDict
 from aria.utils.caching import cachedmethod
-from aria.parser import dsl_specification
+from aria.parser import implements_specification
 from aria.parser.presentation import (AsIsPresentation, has_fields, allow_unknown_fields,
                                       short_form_field, primitive_field, object_field,
                                       object_dict_field, object_dict_unknown_fields,
@@ -32,7 +32,7 @@ from .presentation.field_validators import (node_template_or_type_validator,
 from .presentation.types import (convert_shorthand_to_full_type_name,
                                  get_type_by_full_or_shorthand_name)
 
-@dsl_specification('3.5.9', 'tosca-simple-1.0')
+@implements_specification('3.5.9', 'tosca-simple-1.0')
 class PropertyAssignment(AsIsPresentation):
     """
     This section defines the grammar for assigning values to named properties within TOSCA Node and
@@ -45,7 +45,7 @@ class PropertyAssignment(AsIsPresentation):
 
 @short_form_field('implementation')
 @has_fields
-@dsl_specification('3.5.13-2', 'tosca-simple-1.0')
+@implements_specification('3.5.13-2', 'tosca-simple-1.0')
 class OperationAssignment(ExtensiblePresentation):
     """
     An operation definition defines a named function or procedure that can be bound to an
@@ -105,7 +105,7 @@ class OperationAssignment(ExtensiblePresentation):
 
 @allow_unknown_fields
 @has_fields
-@dsl_specification('3.5.14-2', 'tosca-simple-1.0')
+@implements_specification('3.5.14-2', 'tosca-simple-1.0')
 class InterfaceAssignment(ExtensiblePresentation):
     """
     An interface definition defines a named interface that can be associated with a Node or
@@ -200,7 +200,7 @@ class RelationshipAssignment(ExtensiblePresentation):
 
 @short_form_field('node')
 @has_fields
-@dsl_specification('3.7.2', 'tosca-simple-1.0')
+@implements_specification('3.7.2', 'tosca-simple-1.0')
 class RequirementAssignment(ExtensiblePresentation):
     """
     A Requirement assignment allows template authors to provide either concrete names of TOSCA
@@ -297,7 +297,7 @@ class RequirementAssignment(ExtensiblePresentation):
 
         return None, None
 
-@dsl_specification('3.5.11', 'tosca-simple-1.0')
+@implements_specification('3.5.11', 'tosca-simple-1.0')
 class AttributeAssignment(AsIsPresentation):
     """
     This section defines the grammar for assigning values to named attributes within TOSCA Node and
@@ -309,7 +309,7 @@ class AttributeAssignment(AsIsPresentation):
     """
 
 @has_fields
-@dsl_specification('3.7.1', 'tosca-simple-1.0')
+@implements_specification('3.7.1', 'tosca-simple-1.0')
 class CapabilityAssignment(ExtensiblePresentation):
     """
     A capability assignment allows node template authors to assign values to properties and
@@ -351,7 +351,7 @@ class CapabilityAssignment(ExtensiblePresentation):
             if capability_definition is not None else None
 
 @has_fields
-@dsl_specification('3.5.6', 'tosca-simple-1.0')
+@implements_specification('3.5.6', 'tosca-simple-1.0')
 class ArtifactAssignment(ExtensiblePresentation):
     """
     An artifact definition defines a named, typed file that can be associated with Node Type or Node

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/extensions/aria_extension_tosca/simple_v1_0/data_types.py
----------------------------------------------------------------------
diff --git a/extensions/aria_extension_tosca/simple_v1_0/data_types.py b/extensions/aria_extension_tosca/simple_v1_0/data_types.py
index a06834c..c385f78 100644
--- a/extensions/aria_extension_tosca/simple_v1_0/data_types.py
+++ b/extensions/aria_extension_tosca/simple_v1_0/data_types.py
@@ -20,7 +20,7 @@ try:
 except ImportError:
     from total_ordering import total_ordering
 
-from aria.parser import dsl_specification
+from aria.parser import implements_specification
 from aria.utils.collections import StrictDict, OrderedDict
 from aria.utils.formatting import safe_repr
 
@@ -51,7 +51,7 @@ class Timezone(tzinfo):
 UTC = Timezone()
 
 @total_ordering
-@dsl_specification('timestamp', 'yaml-1.1')
+@implements_specification('timestamp', 'yaml-1.1')
 class Timestamp(object):
     '''
     TOSCA timestamps follow the YAML specification, which in turn is a variant of ISO8601.
@@ -146,7 +146,7 @@ class Timestamp(object):
         return '{0:g}'.format(the_datetime.microsecond / 1000000.0).lstrip('0')
 
 @total_ordering
-@dsl_specification('3.2.2', 'tosca-simple-1.0')
+@implements_specification('3.2.2', 'tosca-simple-1.0')
 class Version(object):
     """
     TOSCA supports the concept of "reuse" of type definitions, as well as template definitions which
@@ -229,7 +229,7 @@ class Version(object):
                             return True
         return False
 
-@dsl_specification('3.2.3', 'tosca-simple-1.0')
+@implements_specification('3.2.3', 'tosca-simple-1.0')
 class Range(object):
     """
     The range type can be used to define numeric ranges with a lower and upper boundary. For
@@ -276,7 +276,7 @@ class Range(object):
     def as_raw(self):
         return list(self.value)
 
-@dsl_specification('3.2.4', 'tosca-simple-1.0')
+@implements_specification('3.2.4', 'tosca-simple-1.0')
 class List(list):
     """
     The list type allows for specifying multiple values for a parameter of property. For example, if
@@ -309,7 +309,7 @@ class List(list):
     def as_raw(self):
         return list(self)
 
-@dsl_specification('3.2.5', 'tosca-simple-1.0')
+@implements_specification('3.2.5', 'tosca-simple-1.0')
 class Map(StrictDict):
     """
     The map type allows for specifying multiple values for a parameter of property as a map. In
@@ -349,7 +349,7 @@ class Map(StrictDict):
         return OrderedDict(self)
 
 @total_ordering
-@dsl_specification('3.2.6', 'tosca-simple-1.0')
+@implements_specification('3.2.6', 'tosca-simple-1.0')
 class Scalar(object):
     """
     The scalar-unit type can be used to define scalar values along with a unit from the list of
@@ -416,7 +416,7 @@ class Scalar(object):
             value = self.TYPE(scalar) # pylint: disable=no-member
         return self.value < value
 
-@dsl_specification('3.2.6.4', 'tosca-simple-1.0')
+@implements_specification('3.2.6.4', 'tosca-simple-1.0')
 class ScalarSize(Scalar):
     """
     Integer scalar for counting bytes.
@@ -444,7 +444,7 @@ class ScalarSize(Scalar):
     TYPE = int
     UNIT = 'bytes'
 
-@dsl_specification('3.2.6.5', 'tosca-simple-1.0')
+@implements_specification('3.2.6.5', 'tosca-simple-1.0')
 class ScalarTime(Scalar):
     """
     Floating point scalar for counting seconds.
@@ -469,7 +469,7 @@ class ScalarTime(Scalar):
     TYPE = float
     UNIT = 'seconds'
 
-@dsl_specification('3.2.6.6', 'tosca-simple-1.0')
+@implements_specification('3.2.6.6', 'tosca-simple-1.0')
 class ScalarFrequency(Scalar):
     """
     Floating point scalar for counting cycles per second (Hz).



[19/19] incubator-ariatosca git commit: ARIA-208 Fix models relationships

Posted by ra...@apache.org.
ARIA-208 Fix models relationships


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

Branch: refs/heads/ARIA-208-Missing-back-refrences-for-models
Commit: 5798bbfc7d1a95965a4c44d2ebad5dad245c824a
Parents: 78d6019
Author: Ran Ziv <ra...@gigaspaces.com>
Authored: Sun May 7 14:58:46 2017 +0300
Committer: Ran Ziv <ra...@gigaspaces.com>
Committed: Mon May 22 15:17:21 2017 +0300

----------------------------------------------------------------------
 aria/modeling/service_template.py | 49 ++++++++++++++++------------------
 1 file changed, 23 insertions(+), 26 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/5798bbfc/aria/modeling/service_template.py
----------------------------------------------------------------------
diff --git a/aria/modeling/service_template.py b/aria/modeling/service_template.py
index 1eb95a3..12195a1 100644
--- a/aria/modeling/service_template.py
+++ b/aria/modeling/service_template.py
@@ -211,10 +211,6 @@ class ServiceTemplateBase(TemplateModelMixin):
         return relationship.one_to_many(cls, 'service', dict_key='name')
 
     @declared_attr
-    def operation_templates(cls):
-        return relationship.one_to_many(cls, 'operation_template')
-
-    @declared_attr
     def node_templates(cls):
         return relationship.one_to_many(cls, 'node_template', dict_key='name')
 
@@ -483,6 +479,22 @@ class NodeTemplateBase(TemplateModelMixin):
     def nodes(cls):
         return relationship.one_to_many(cls, 'node')
 
+    @declared_attr
+    def interface_templates(cls):
+        return relationship.one_to_many(cls, 'interface_template', dict_key='name')
+
+    @declared_attr
+    def artifact_templates(cls):
+        return relationship.one_to_many(cls, 'artifact_template', dict_key='name')
+
+    @declared_attr
+    def capability_templates(cls):
+        return relationship.one_to_many(cls, 'capability_template', dict_key='name')
+
+    @declared_attr
+    def requirement_templates(cls):
+        return relationship.one_to_many(cls, 'requirement_template', child_fk='node_template_fk')
+
     # endregion
 
     # region many_to_one relationships
@@ -507,22 +519,6 @@ class NodeTemplateBase(TemplateModelMixin):
     def attributes(cls):
         return relationship.many_to_many(cls, 'parameter', prefix='attributes', dict_key='name')
 
-    @declared_attr
-    def interface_templates(cls):
-        return relationship.one_to_many(cls, 'interface_template', dict_key='name')
-
-    @declared_attr
-    def artifact_templates(cls):
-        return relationship.one_to_many(cls, 'artifact_template', dict_key='name')
-
-    @declared_attr
-    def capability_templates(cls):
-        return relationship.one_to_many(cls, 'capability_template', dict_key='name')
-
-    @declared_attr
-    def requirement_templates(cls):
-        return relationship.one_to_many(cls, 'requirement_template', child_fk='node_template_fk')
-
     # endregion
 
     description = Column(Text)
@@ -1209,11 +1205,6 @@ class RequirementTemplateBase(TemplateModelMixin):
     def relationships(cls):
         return relationship.one_to_many(cls, 'relationship')
 
-    @declared_attr
-    def target_node_type(cls):
-        return relationship.many_to_one(
-            cls, 'type', fk='target_node_type_fk', back_populates=relationship.NO_BACK_POP)
-
     # endregion
 
     # region many_to_one relationships
@@ -1222,6 +1213,11 @@ class RequirementTemplateBase(TemplateModelMixin):
     def node_template(cls):
         return relationship.many_to_one(cls, 'node_template', fk='node_template_fk')
 
+    @declared_attr
+    def target_node_type(cls):
+        return relationship.many_to_one(
+            cls, 'type', fk='target_node_type_fk', back_populates=relationship.NO_BACK_POP)
+
     # endregion
 
     # region many_to_many relationships
@@ -1845,7 +1841,8 @@ class OperationTemplateBase(TemplateModelMixin):
 
     @declared_attr
     def service_template(cls):
-        return relationship.many_to_one(cls, 'service_template')
+        return relationship.many_to_one(cls, 'service_template',
+                                        back_populates='workflow_templates')
 
     @declared_attr
     def interface_template(cls):


[03/19] incubator-ariatosca git commit: ARIA-215 Refactor plugin-related code into PluginManager

Posted by ra...@apache.org.
ARIA-215 Refactor plugin-related code into PluginManager

Refactored plugin-related code from ProcessExecutor into PluginManager.
Additionally, renamed plugin_prefix to plugin_dir in PluginManager.


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

Branch: refs/heads/ARIA-208-Missing-back-refrences-for-models
Commit: 3e1ed14c00ea2c83fafdca8ec0e37817bea1d5e8
Parents: 45c158e
Author: Ran Ziv <ra...@gigaspaces.com>
Authored: Sun May 7 16:36:39 2017 +0300
Committer: Ran Ziv <ra...@gigaspaces.com>
Committed: Sun May 7 17:27:36 2017 +0300

----------------------------------------------------------------------
 aria/orchestrator/plugin.py                     | 36 +++++++++++-
 aria/orchestrator/workflows/executor/process.py | 60 ++++++--------------
 aria/utils/process.py                           | 47 +++++++++++++++
 tests/utils/test_plugin.py                      |  6 +-
 4 files changed, 100 insertions(+), 49 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/3e1ed14c/aria/orchestrator/plugin.py
----------------------------------------------------------------------
diff --git a/aria/orchestrator/plugin.py b/aria/orchestrator/plugin.py
index f99666c..8fbcf5a 100644
--- a/aria/orchestrator/plugin.py
+++ b/aria/orchestrator/plugin.py
@@ -23,6 +23,9 @@ from datetime import datetime
 import wagon
 
 from . import exceptions
+from ..utils import process as process_utils
+
+_IS_WIN = os.name == 'nt'
 
 
 class PluginManager(object):
@@ -62,11 +65,40 @@ class PluginManager(object):
             raise exceptions.PluginAlreadyExistsError(
                 'Plugin {0}, version {1} already exists'.format(plugin.package_name,
                                                                 plugin.package_version))
-        self._install_wagon(source=source, prefix=self.get_plugin_prefix(plugin))
+        self._install_wagon(source=source, prefix=self.get_plugin_dir(plugin))
         self._model.plugin.put(plugin)
         return plugin
 
-    def get_plugin_prefix(self, plugin):
+    def load_plugin(self, plugin, env=None):
+        """
+        Load the plugin into an environment.
+        Loading the plugin means the plugin's code and binaries paths will be appended to the
+        environment's PATH and PYTHONPATH, thereby allowing usage of the plugin.
+        :param plugin: The plugin to load
+        :param env: The environment to load the plugin into; If `None`, os.environ will be used.
+        """
+        env = env or os.environ
+        plugin_dir = self.get_plugin_dir(plugin)
+
+        # Update PATH environment variable to include plugin's bin dir
+        bin_dir = 'Scripts' if _IS_WIN else 'bin'
+        process_utils.append_to_path(os.path.join(plugin_dir, bin_dir), env=env)
+
+        # Update PYTHONPATH environment variable to include plugin's site-packages
+        # directories
+        if _IS_WIN:
+            pythonpath_dirs = [os.path.join(plugin_dir, 'Lib', 'site-packages')]
+        else:
+            # In some linux environments, there will be both a lib and a lib64 directory
+            # with the latter, containing compiled packages.
+            pythonpath_dirs = [os.path.join(
+                plugin_dir, 'lib{0}'.format(b),
+                'python{0}.{1}'.format(sys.version_info[0], sys.version_info[1]),
+                'site-packages') for b in ('', '64')]
+
+        process_utils.append_to_pythonpath(*pythonpath_dirs, env=env)
+
+    def get_plugin_dir(self, plugin):
         return os.path.join(
             self._plugins_dir,
             '{0}-{1}'.format(plugin.package_name, plugin.package_version))

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/3e1ed14c/aria/orchestrator/workflows/executor/process.py
----------------------------------------------------------------------
diff --git a/aria/orchestrator/workflows/executor/process.py b/aria/orchestrator/workflows/executor/process.py
index 8481406..f3daf04 100644
--- a/aria/orchestrator/workflows/executor/process.py
+++ b/aria/orchestrator/workflows/executor/process.py
@@ -47,13 +47,12 @@ from aria.storage import instrumentation
 from aria.extension import process_executor
 from aria.utils import (
     imports,
-    exceptions
+    exceptions,
+    process as process_utils
 )
 from aria.modeling import types as modeling_types
 
 
-_IS_WIN = os.name == 'nt'
-
 _INT_FMT = 'I'
 _INT_SIZE = struct.calcsize(_INT_FMT)
 UPDATE_TRACKED_CHANGES_FAILED_STR = \
@@ -127,13 +126,7 @@ class ProcessExecutor(base.BaseExecutor):
         with open(arguments_json_path, 'wb') as f:
             f.write(pickle.dumps(self._create_arguments_dict(task)))
 
-        env = os.environ.copy()
-        # See _update_env for plugin_prefix usage
-        if task.plugin_fk and self._plugin_manager:
-            plugin_prefix = self._plugin_manager.get_plugin_prefix(task.plugin)
-        else:
-            plugin_prefix = None
-        self._update_env(env=env, plugin_prefix=plugin_prefix)
+        env = self._construct_subprocess_env(task=task)
         # Asynchronously start the operation in a subprocess
         subprocess.Popen(
             '{0} {1} {2}'.format(sys.executable, __file__, arguments_json_path),
@@ -156,40 +149,19 @@ class ProcessExecutor(base.BaseExecutor):
             'context': task.context.serialization_dict,
         }
 
-    def _update_env(self, env, plugin_prefix):
-        pythonpath_dirs = []
-        # If this is a plugin operation, plugin prefix will point to where
-        # This plugin is installed.
-        # We update the environment variables that the subprocess will be started with based on it
-        if plugin_prefix:
-
-            # Update PATH environment variable to include plugin's bin dir
-            bin_dir = 'Scripts' if _IS_WIN else 'bin'
-            env['PATH'] = '{0}{1}{2}'.format(
-                os.path.join(plugin_prefix, bin_dir),
-                os.pathsep,
-                env.get('PATH', ''))
-
-            # Update PYTHONPATH environment variable to include plugin's site-packages
-            # directories
-            if _IS_WIN:
-                pythonpath_dirs = [os.path.join(plugin_prefix, 'Lib', 'site-packages')]
-            else:
-                # In some linux environments, there will be both a lib and a lib64 directory
-                # with the latter, containing compiled packages.
-                pythonpath_dirs = [os.path.join(
-                    plugin_prefix, 'lib{0}'.format(b),
-                    'python{0}.{1}'.format(sys.version_info[0], sys.version_info[1]),
-                    'site-packages') for b in ('', '64')]
-
-        # Add used supplied directories to injected PYTHONPATH
-        pythonpath_dirs.extend(self._python_path)
-
-        if pythonpath_dirs:
-            env['PYTHONPATH'] = '{0}{1}{2}'.format(
-                os.pathsep.join(pythonpath_dirs),
-                os.pathsep,
-                env.get('PYTHONPATH', ''))
+    def _construct_subprocess_env(self, task):
+        env = os.environ.copy()
+
+        if task.plugin_fk and self._plugin_manager:
+            # If this is a plugin operation,
+            # load the plugin on the subprocess env we're constructing
+            self._plugin_manager.load_plugin(task.plugin, env=env)
+
+        # Add user supplied directories to injected PYTHONPATH
+        if self._python_path:
+            process_utils.append_to_pythonpath(*self._python_path, env=env)
+
+        return env
 
     def _listener(self):
         # Notify __init__ method this thread has actually started

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/3e1ed14c/aria/utils/process.py
----------------------------------------------------------------------
diff --git a/aria/utils/process.py b/aria/utils/process.py
new file mode 100644
index 0000000..9aeae67
--- /dev/null
+++ b/aria/utils/process.py
@@ -0,0 +1,47 @@
+# 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.
+
+import os
+
+
+def append_to_path(*args, **kwargs):
+    """
+    Appends one or more paths to the system path of an environment.
+    The environment will be that of the current process unless another is passed using the
+    'env' keyword argument.
+    :param args: paths to append
+    :param kwargs: 'env' may be used to pass a custom environment to use
+    """
+    _append_to_path('PATH', *args, **kwargs)
+
+
+def append_to_pythonpath(*args, **kwargs):
+    """
+    Appends one or more paths to the python path of an environment.
+    The environment will be that of the current process unless another is passed using the
+    'env' keyword argument.
+    :param args: paths to append
+    :param kwargs: 'env' may be used to pass a custom environment to use
+    """
+    _append_to_path('PYTHONPATH', *args, **kwargs)
+
+
+def _append_to_path(path, *args, **kwargs):
+    env = kwargs.get('env') or os.environ
+    env[path] = '{0}{1}{2}'.format(
+        os.pathsep.join(args),
+        os.pathsep,
+        env.get(path, '')
+    )

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/3e1ed14c/tests/utils/test_plugin.py
----------------------------------------------------------------------
diff --git a/tests/utils/test_plugin.py b/tests/utils/test_plugin.py
index 3350247..c91d0c9 100644
--- a/tests/utils/test_plugin.py
+++ b/tests/utils/test_plugin.py
@@ -38,9 +38,9 @@ class TestPluginManager(object):
         assert plugin.package_name == PACKAGE_NAME
         assert plugin.package_version == PACKAGE_VERSION
         assert plugin == model.plugin.get(plugin.id)
-        plugin_prefix = os.path.join(plugins_dir, '{0}-{1}'.format(PACKAGE_NAME, PACKAGE_VERSION))
-        assert os.path.isdir(plugin_prefix)
-        assert plugin_prefix == plugin_manager.get_plugin_prefix(plugin)
+        plugin_dir = os.path.join(plugins_dir, '{0}-{1}'.format(PACKAGE_NAME, PACKAGE_VERSION))
+        assert os.path.isdir(plugin_dir)
+        assert plugin_dir == plugin_manager.get_plugin_dir(plugin)
 
     def test_install_already_exits(self, plugin_manager, mock_plugin):
         plugin_manager.install(mock_plugin)


[07/19] incubator-ariatosca git commit: ARIA-160 Tests fail spordically over parameter binding

Posted by ra...@apache.org.
ARIA-160 Tests fail spordically over parameter binding


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

Branch: refs/heads/ARIA-208-Missing-back-refrences-for-models
Commit: 16fcca45f60f81261757c9fee8f61782c479e30e
Parents: 1febf80
Author: max-orlov <ma...@gigaspaces.com>
Authored: Mon May 8 14:46:58 2017 +0300
Committer: max-orlov <ma...@gigaspaces.com>
Committed: Tue May 9 14:07:24 2017 +0300

----------------------------------------------------------------------
 aria/orchestrator/workflows/core/engine.py   |   3 +-
 tests/helpers.py                             |  23 +++--
 tests/orchestrator/context/test_operation.py | 107 ++++++++++++----------
 tests/orchestrator/context/test_toolbelt.py  |  40 ++++----
 tests/storage/__init__.py                    |   3 +-
 5 files changed, 92 insertions(+), 84 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/16fcca45/aria/orchestrator/workflows/core/engine.py
----------------------------------------------------------------------
diff --git a/aria/orchestrator/workflows/core/engine.py b/aria/orchestrator/workflows/core/engine.py
index fd0dd6d..561265c 100644
--- a/aria/orchestrator/workflows/core/engine.py
+++ b/aria/orchestrator/workflows/core/engine.py
@@ -42,10 +42,9 @@ class Engine(logger.LoggerMixin):
         super(Engine, self).__init__(**kwargs)
         self._workflow_context = workflow_context
         self._execution_graph = networkx.DiGraph()
-        self._executor = executor
         translation.build_execution_graph(task_graph=tasks_graph,
                                           execution_graph=self._execution_graph,
-                                          default_executor=self._executor)
+                                          default_executor=executor)
 
     def execute(self):
         """

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/16fcca45/tests/helpers.py
----------------------------------------------------------------------
diff --git a/tests/helpers.py b/tests/helpers.py
index 423e63f..3c3efc9 100644
--- a/tests/helpers.py
+++ b/tests/helpers.py
@@ -14,7 +14,6 @@
 # limitations under the License.
 
 import os
-import tempfile
 import json
 
 from . import ROOT_DIR
@@ -34,16 +33,19 @@ def get_service_template_uri(*args):
 
 
 class FilesystemDataHolder(object):
-    _tmpfile = tempfile.NamedTemporaryFile('w')
+
+    def __init__(self, path, reset=False):
+        self._path = path
+        if reset or not os.path.exists(self._path) or open(self._path).read() == '':
+            self._dump({})
 
     def _load(self):
-        return json.load(open(self._tmpfile.name))
+        with open(self._path) as f:
+            return json.load(f)
 
     def _dump(self, value):
-        return json.dump(value, open(self._tmpfile.name, 'w'))
-
-    def __init__(self):
-        self.clear()
+        with open(self._path, 'w') as f:
+            return json.dump(value, f)
 
     def __setitem__(self, key, value):
         dict_ = self._load()
@@ -56,9 +58,6 @@ class FilesystemDataHolder(object):
     def __iter__(self):
         return iter(self._load())
 
-    def clear(self):
-        self._dump({})
-
     def get(self, item, default=None):
         return self._load().get(item, default)
 
@@ -67,3 +66,7 @@ class FilesystemDataHolder(object):
         return_value = dict_.setdefault(key, value)
         self._dump(dict_)
         return return_value
+
+    @property
+    def path(self):
+        return self._path

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/16fcca45/tests/orchestrator/context/test_operation.py
----------------------------------------------------------------------
diff --git a/tests/orchestrator/context/test_operation.py b/tests/orchestrator/context/test_operation.py
index 3180d89..cdeb5fa 100644
--- a/tests/orchestrator/context/test_operation.py
+++ b/tests/orchestrator/context/test_operation.py
@@ -38,7 +38,6 @@ from . import (
     execute,
 )
 
-global_test_holder = helpers.FilesystemDataHolder()
 
 @pytest.fixture
 def ctx(tmpdir):
@@ -68,11 +67,18 @@ def thread_executor():
         ex.close()
 
 
-def test_node_operation_task_execution(ctx, thread_executor):
+@pytest.fixture
+def dataholder(tmpdir):
+    dataholder_path = str(tmpdir.join('dataholder'))
+    holder = helpers.FilesystemDataHolder(dataholder_path)
+    return holder
+
+
+def test_node_operation_task_execution(ctx, thread_executor, dataholder):
     interface_name = 'Standard'
     operation_name = 'create'
 
-    inputs = {'putput': True}
+    inputs = {'putput': True, 'holder_path': dataholder.path}
     node = ctx.model.node.get_by_name(mock.models.DEPENDENCY_NODE_NAME)
     interface = mock.models.create_interface(
         node.service,
@@ -97,11 +103,11 @@ def test_node_operation_task_execution(ctx, thread_executor):
 
     execute(workflow_func=basic_workflow, workflow_context=ctx, executor=thread_executor)
 
-    assert global_test_holder['ctx_name'] == context.operation.NodeOperationContext.__name__
+    assert dataholder['ctx_name'] == context.operation.NodeOperationContext.__name__
 
     # Task bases assertions
-    assert global_test_holder['actor_name'] == node.name
-    assert global_test_holder['task_name'] == api.task.OperationTask.NAME_FORMAT.format(
+    assert dataholder['actor_name'] == node.name
+    assert dataholder['task_name'] == api.task.OperationTask.NAME_FORMAT.format(
         type='node',
         name=node.name,
         interface=interface_name,
@@ -109,19 +115,19 @@ def test_node_operation_task_execution(ctx, thread_executor):
     )
     operations = interface.operations
     assert len(operations) == 1
-    assert global_test_holder['implementation'] == operations.values()[0].implementation             # pylint: disable=no-member
-    assert global_test_holder['inputs']['putput'] is True
+    assert dataholder['implementation'] == operations.values()[0].implementation             # pylint: disable=no-member
+    assert dataholder['inputs']['putput'] is True
 
     # Context based attributes (sugaring)
-    assert global_test_holder['template_name'] == node.node_template.name
-    assert global_test_holder['node_name'] == node.name
+    assert dataholder['template_name'] == node.node_template.name
+    assert dataholder['node_name'] == node.name
 
 
-def test_relationship_operation_task_execution(ctx, thread_executor):
+def test_relationship_operation_task_execution(ctx, thread_executor, dataholder):
     interface_name = 'Configure'
     operation_name = 'post_configure'
 
-    inputs = {'putput': True}
+    inputs = {'putput': True, 'holder_path': dataholder.path}
     relationship = ctx.model.relationship.list()[0]
     interface = mock.models.create_interface(
         relationship.source_node.service,
@@ -148,14 +154,14 @@ def test_relationship_operation_task_execution(ctx, thread_executor):
 
     execute(workflow_func=basic_workflow, workflow_context=ctx, executor=thread_executor)
 
-    assert global_test_holder['ctx_name'] == context.operation.RelationshipOperationContext.__name__
+    assert dataholder['ctx_name'] == context.operation.RelationshipOperationContext.__name__
 
     # Task bases assertions
-    assert global_test_holder['actor_name'] == relationship.name
-    assert interface_name in global_test_holder['task_name']
+    assert dataholder['actor_name'] == relationship.name
+    assert interface_name in dataholder['task_name']
     operations = interface.operations
-    assert global_test_holder['implementation'] == operations.values()[0].implementation           # pylint: disable=no-member
-    assert global_test_holder['inputs']['putput'] is True
+    assert dataholder['implementation'] == operations.values()[0].implementation           # pylint: disable=no-member
+    assert dataholder['inputs']['putput'] is True
 
     # Context based attributes (sugaring)
     dependency_node_template = ctx.model.node_template.get_by_name(
@@ -165,14 +171,14 @@ def test_relationship_operation_task_execution(ctx, thread_executor):
         mock.models.DEPENDENT_NODE_TEMPLATE_NAME)
     dependent_node = ctx.model.node.get_by_name(mock.models.DEPENDENT_NODE_NAME)
 
-    assert global_test_holder['target_node_template_name'] == dependency_node_template.name
-    assert global_test_holder['target_node_name'] == dependency_node.name
-    assert global_test_holder['relationship_name'] == relationship.name
-    assert global_test_holder['source_node_template_name'] == dependent_node_template.name
-    assert global_test_holder['source_node_name'] == dependent_node.name
+    assert dataholder['target_node_template_name'] == dependency_node_template.name
+    assert dataholder['target_node_name'] == dependency_node.name
+    assert dataholder['relationship_name'] == relationship.name
+    assert dataholder['source_node_template_name'] == dependent_node_template.name
+    assert dataholder['source_node_name'] == dependent_node.name
 
 
-def test_invalid_task_operation_id(ctx, thread_executor):
+def test_invalid_task_operation_id(ctx, thread_executor, dataholder):
     """
     Checks that the right id is used. The task created with id == 1, thus running the task on
     node with id == 2. will check that indeed the node uses the correct id.
@@ -191,7 +197,8 @@ def test_invalid_task_operation_id(ctx, thread_executor):
         node.service,
         interface_name=interface_name,
         operation_name=operation_name,
-        operation_kwargs=dict(implementation=op_path(get_node_id, module_path=__name__))
+        operation_kwargs=dict(implementation=op_path(get_node_id, module_path=__name__),
+                              inputs={'holder_path': dataholder.path})
     )
     node.interfaces[interface.name] = interface
     ctx.model.node.update(node)
@@ -202,12 +209,13 @@ def test_invalid_task_operation_id(ctx, thread_executor):
             api.task.OperationTask(
                 node,
                 interface_name=interface_name,
-                operation_name=operation_name)
+                operation_name=operation_name,
+            )
         )
 
     execute(workflow_func=basic_workflow, workflow_context=ctx, executor=thread_executor)
 
-    op_node_id = global_test_holder[api.task.OperationTask.NAME_FORMAT.format(
+    op_node_id = dataholder[api.task.OperationTask.NAME_FORMAT.format(
         type='node',
         name=node.name,
         interface=interface_name,
@@ -376,42 +384,41 @@ def logged_operation(ctx, **_):
 
 
 @operation
-def basic_node_operation(ctx, **_):
-    operation_common(ctx)
-    global_test_holder['template_name'] = ctx.node_template.name
-    global_test_holder['node_name'] = ctx.node.name
+def basic_node_operation(ctx, holder_path, **_):
+    holder = helpers.FilesystemDataHolder(holder_path)
+
+    operation_common(ctx, holder)
+    holder['template_name'] = ctx.node_template.name
+    holder['node_name'] = ctx.node.name
 
 
 @operation
-def basic_relationship_operation(ctx, **_):
-    operation_common(ctx)
-    global_test_holder['target_node_template_name'] = ctx.target_node_template.name
-    global_test_holder['target_node_name'] = ctx.target_node.name
-    global_test_holder['relationship_name'] = ctx.relationship.name
-    global_test_holder['source_node_template_name'] = ctx.source_node_template.name
-    global_test_holder['source_node_name'] = ctx.source_node.name
+def basic_relationship_operation(ctx, holder_path, **_):
+    holder = helpers.FilesystemDataHolder(holder_path)
+
+    operation_common(ctx, holder)
+    holder['target_node_template_name'] = ctx.target_node_template.name
+    holder['target_node_name'] = ctx.target_node.name
+    holder['relationship_name'] = ctx.relationship.name
+    holder['source_node_template_name'] = ctx.source_node_template.name
+    holder['source_node_name'] = ctx.source_node.name
 
 
-def operation_common(ctx):
-    global_test_holder['ctx_name'] = ctx.__class__.__name__
+def operation_common(ctx, holder):
+    holder['ctx_name'] = ctx.__class__.__name__
 
-    global_test_holder['actor_name'] = ctx.task.actor.name
-    global_test_holder['task_name'] = ctx.task.name
-    global_test_holder['implementation'] = ctx.task.implementation
-    global_test_holder['inputs'] = dict(i.unwrap() for i in ctx.task.inputs.values())
+    holder['actor_name'] = ctx.task.actor.name
+    holder['task_name'] = ctx.task.name
+    holder['implementation'] = ctx.task.implementation
+    holder['inputs'] = dict(i.unwrap() for i in ctx.task.inputs.values())
 
 
 @operation
-def get_node_id(ctx, **_):
-    global_test_holder[ctx.name] = ctx.node.id
+def get_node_id(ctx, holder_path, **_):
+    helpers.FilesystemDataHolder(holder_path)[ctx.name] = ctx.node.id
 
 
 @operation
 def _test_plugin_workdir(ctx, filename, content):
     with open(os.path.join(ctx.plugin_workdir, filename), 'w') as f:
         f.write(content)
-
-
-@pytest.fixture(autouse=True)
-def cleanup():
-    global_test_holder.clear()

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/16fcca45/tests/orchestrator/context/test_toolbelt.py
----------------------------------------------------------------------
diff --git a/tests/orchestrator/context/test_toolbelt.py b/tests/orchestrator/context/test_toolbelt.py
index 822ac5a..d199954 100644
--- a/tests/orchestrator/context/test_toolbelt.py
+++ b/tests/orchestrator/context/test_toolbelt.py
@@ -30,12 +30,10 @@ from . import (
     execute,
 )
 
-global_test_holder = helpers.FilesystemDataHolder()
-
 
 @pytest.fixture
 def workflow_context(tmpdir):
-    context = mock.context.simple(str(tmpdir), inmemory=True)
+    context = mock.context.simple(str(tmpdir))
     yield context
     storage.release_sqlite_storage(context.model)
 
@@ -49,6 +47,13 @@ def executor():
         result.close()
 
 
+@pytest.fixture
+def dataholder(tmpdir):
+    dataholder_path = str(tmpdir.join('dataholder'))
+    holder = helpers.FilesystemDataHolder(dataholder_path)
+    return holder
+
+
 def _get_elements(workflow_context):
     dependency_node_template = workflow_context.model.node_template.get_by_name(
         mock.models.DEPENDENCY_NODE_TEMPLATE_NAME)
@@ -75,17 +80,17 @@ def _get_elements(workflow_context):
         relationship
 
 
-def test_host_ip(workflow_context, executor):
+def test_host_ip(workflow_context, executor, dataholder):
+
     interface_name = 'Standard'
     operation_name = 'create'
     _, dependency_node, _, _, _ = _get_elements(workflow_context)
-    inputs = {'putput': True}
+    inputs = {'putput': True, 'holder_path': dataholder.path}
     interface = mock.models.create_interface(
         dependency_node.service,
         interface_name=interface_name,
         operation_name=operation_name,
-        operation_kwargs=dict(implementation=op_path(host_ip, module_path=__name__),
-                              inputs=inputs)
+        operation_kwargs=dict(implementation=op_path(host_ip, module_path=__name__), inputs=inputs)
     )
     dependency_node.interfaces[interface.name] = interface
     dependency_node.runtime_properties['ip'] = '1.1.1.1'
@@ -105,14 +110,14 @@ def test_host_ip(workflow_context, executor):
 
     execute(workflow_func=basic_workflow, workflow_context=workflow_context, executor=executor)
 
-    assert global_test_holder.get('host_ip') == dependency_node.runtime_properties.get('ip')
+    assert dataholder.get('host_ip') == dependency_node.runtime_properties.get('ip')
 
 
-def test_relationship_tool_belt(workflow_context, executor):
+def test_relationship_tool_belt(workflow_context, executor, dataholder):
     interface_name = 'Configure'
     operation_name = 'post_configure'
     _, _, _, _, relationship = _get_elements(workflow_context)
-    inputs = {'putput': True}
+    inputs = {'putput': True, 'holder_path': dataholder.path}
     interface = mock.models.create_interface(
         relationship.source_node.service,
         interface_name=interface_name,
@@ -136,7 +141,7 @@ def test_relationship_tool_belt(workflow_context, executor):
 
     execute(workflow_func=basic_workflow, workflow_context=workflow_context, executor=executor)
 
-    assert global_test_holder.get(api.task.OperationTask.NAME_FORMAT.format(
+    assert dataholder.get(api.task.OperationTask.NAME_FORMAT.format(
         type='relationship',
         name=relationship.name,
         interface=interface_name,
@@ -149,15 +154,10 @@ def test_wrong_model_toolbelt():
 
 
 @operation(toolbelt=True)
-def host_ip(toolbelt, **_):
-    global_test_holder['host_ip'] = toolbelt.host_ip
+def host_ip(toolbelt, holder_path, **_):
+    helpers.FilesystemDataHolder(holder_path)['host_ip'] = toolbelt.host_ip
 
 
 @operation(toolbelt=True)
-def relationship_operation(ctx, toolbelt, **_):
-    global_test_holder[ctx.name] = toolbelt._op_context.source_node.name
-
-
-@pytest.fixture(autouse=True)
-def cleanup():
-    global_test_holder.clear()
+def relationship_operation(ctx, toolbelt, holder_path, **_):
+    helpers.FilesystemDataHolder(holder_path)[ctx.name] = toolbelt._op_context.source_node.name

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/16fcca45/tests/storage/__init__.py
----------------------------------------------------------------------
diff --git a/tests/storage/__init__.py b/tests/storage/__init__.py
index 66424db..8ca1480 100644
--- a/tests/storage/__init__.py
+++ b/tests/storage/__init__.py
@@ -51,6 +51,5 @@ def init_inmemory_model_storage():
 
     engine = create_engine(uri, **engine_kwargs)
     session_factory = orm.sessionmaker(bind=engine)
-    session = orm.scoped_session(session_factory=session_factory)
 
-    return dict(engine=engine, session=session)
+    return dict(engine=engine, session=session_factory())


[02/19] incubator-ariatosca git commit: ARIA-160 Operation toolbelt unit tests fail spordically

Posted by ra...@apache.org.
ARIA-160 Operation toolbelt unit tests fail spordically

Inroduced a filesystem based data holder


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

Branch: refs/heads/ARIA-208-Missing-back-refrences-for-models
Commit: 45c158eff0d7ea97be0937cba9009522733948ae
Parents: 1cb3086
Author: max-orlov <ma...@gigaspaces.com>
Authored: Wed May 3 12:42:27 2017 +0300
Committer: max-orlov <ma...@gigaspaces.com>
Committed: Sun May 7 14:59:29 2017 +0300

----------------------------------------------------------------------
 aria/orchestrator/workflows/core/task.py     |  5 +-
 tests/helpers.py                             | 38 ++++++++++
 tests/orchestrator/context/test_operation.py | 87 +++++++++++++----------
 tests/orchestrator/context/test_toolbelt.py  | 18 +++--
 4 files changed, 100 insertions(+), 48 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/45c158ef/aria/orchestrator/workflows/core/task.py
----------------------------------------------------------------------
diff --git a/aria/orchestrator/workflows/core/task.py b/aria/orchestrator/workflows/core/task.py
index 0e081c2..78159c4 100644
--- a/aria/orchestrator/workflows/core/task.py
+++ b/aria/orchestrator/workflows/core/task.py
@@ -175,10 +175,9 @@ class OperationTask(BaseTask):
         self._update_fields = {}
         try:
             yield
-            task = self.model_task
             for key, value in self._update_fields.items():
-                setattr(task, key, value)
-            self.model_task = task
+                setattr(self.model_task, key, value)
+            self.model_task = self.model_task
         finally:
             self._update_fields = None
 

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/45c158ef/tests/helpers.py
----------------------------------------------------------------------
diff --git a/tests/helpers.py b/tests/helpers.py
index 472d696..423e63f 100644
--- a/tests/helpers.py
+++ b/tests/helpers.py
@@ -14,6 +14,8 @@
 # limitations under the License.
 
 import os
+import tempfile
+import json
 
 from . import ROOT_DIR
 from .resources import DIR as RESOURCES_DIR
@@ -29,3 +31,39 @@ def get_resource_uri(*args):
 
 def get_service_template_uri(*args):
     return os.path.join(RESOURCES_DIR, 'service-templates', *args)
+
+
+class FilesystemDataHolder(object):
+    _tmpfile = tempfile.NamedTemporaryFile('w')
+
+    def _load(self):
+        return json.load(open(self._tmpfile.name))
+
+    def _dump(self, value):
+        return json.dump(value, open(self._tmpfile.name, 'w'))
+
+    def __init__(self):
+        self.clear()
+
+    def __setitem__(self, key, value):
+        dict_ = self._load()
+        dict_[key] = value
+        self._dump(dict_)
+
+    def __getitem__(self, item):
+        return self._load()[item]
+
+    def __iter__(self):
+        return iter(self._load())
+
+    def clear(self):
+        self._dump({})
+
+    def get(self, item, default=None):
+        return self._load().get(item, default)
+
+    def setdefault(self, key, value):
+        dict_ = self._load()
+        return_value = dict_.setdefault(key, value)
+        self._dump(dict_)
+        return return_value

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/45c158ef/tests/orchestrator/context/test_operation.py
----------------------------------------------------------------------
diff --git a/tests/orchestrator/context/test_operation.py b/tests/orchestrator/context/test_operation.py
index 971e0db..3180d89 100644
--- a/tests/orchestrator/context/test_operation.py
+++ b/tests/orchestrator/context/test_operation.py
@@ -28,14 +28,17 @@ from aria.orchestrator import context
 from aria.orchestrator.workflows import api
 
 import tests
-from tests import mock, storage
+from tests import (
+    mock,
+    storage,
+    helpers
+)
 from . import (
     op_path,
     execute,
 )
 
-global_test_holder = {}
-
+global_test_holder = helpers.FilesystemDataHolder()
 
 @pytest.fixture
 def ctx(tmpdir):
@@ -75,7 +78,7 @@ def test_node_operation_task_execution(ctx, thread_executor):
         node.service,
         interface_name,
         operation_name,
-        operation_kwargs=dict(implementation=op_path(basic_operation, module_path=__name__),
+        operation_kwargs=dict(implementation=op_path(basic_node_operation, module_path=__name__),
                               inputs=inputs)
     )
     node.interfaces[interface.name] = interface
@@ -94,18 +97,11 @@ def test_node_operation_task_execution(ctx, thread_executor):
 
     execute(workflow_func=basic_workflow, workflow_context=ctx, executor=thread_executor)
 
-    operation_context = global_test_holder[api.task.OperationTask.NAME_FORMAT.format(
-        type='node',
-        name=node.name,
-        interface=interface_name,
-        operation=operation_name
-    )]
-
-    assert isinstance(operation_context, context.operation.NodeOperationContext)
+    assert global_test_holder['ctx_name'] == context.operation.NodeOperationContext.__name__
 
     # Task bases assertions
-    assert operation_context.task.actor == node
-    assert operation_context.task.name == api.task.OperationTask.NAME_FORMAT.format(
+    assert global_test_holder['actor_name'] == node.name
+    assert global_test_holder['task_name'] == api.task.OperationTask.NAME_FORMAT.format(
         type='node',
         name=node.name,
         interface=interface_name,
@@ -113,12 +109,12 @@ def test_node_operation_task_execution(ctx, thread_executor):
     )
     operations = interface.operations
     assert len(operations) == 1
-    assert operation_context.task.implementation == operations.values()[0].implementation           # pylint: disable=no-member
-    assert operation_context.task.inputs['putput'].value is True
+    assert global_test_holder['implementation'] == operations.values()[0].implementation             # pylint: disable=no-member
+    assert global_test_holder['inputs']['putput'] is True
 
     # Context based attributes (sugaring)
-    assert operation_context.node_template == node.node_template
-    assert operation_context.node == node
+    assert global_test_holder['template_name'] == node.node_template.name
+    assert global_test_holder['node_name'] == node.name
 
 
 def test_relationship_operation_task_execution(ctx, thread_executor):
@@ -131,7 +127,8 @@ def test_relationship_operation_task_execution(ctx, thread_executor):
         relationship.source_node.service,
         interface_name,
         operation_name,
-        operation_kwargs=dict(implementation=op_path(basic_operation, module_path=__name__),
+        operation_kwargs=dict(implementation=op_path(basic_relationship_operation,
+                                                     module_path=__name__),
                               inputs=inputs),
     )
 
@@ -151,21 +148,14 @@ def test_relationship_operation_task_execution(ctx, thread_executor):
 
     execute(workflow_func=basic_workflow, workflow_context=ctx, executor=thread_executor)
 
-    operation_context = global_test_holder[api.task.OperationTask.NAME_FORMAT.format(
-        type='relationship',
-        name=relationship.name,
-        interface=interface_name,
-        operation=operation_name
-    )]
-
-    assert isinstance(operation_context, context.operation.RelationshipOperationContext)
+    assert global_test_holder['ctx_name'] == context.operation.RelationshipOperationContext.__name__
 
     # Task bases assertions
-    assert operation_context.task.actor == relationship
-    assert interface_name in operation_context.task.name
+    assert global_test_holder['actor_name'] == relationship.name
+    assert interface_name in global_test_holder['task_name']
     operations = interface.operations
-    assert operation_context.task.implementation == operations.values()[0].implementation           # pylint: disable=no-member
-    assert operation_context.task.inputs['putput'].value is True
+    assert global_test_holder['implementation'] == operations.values()[0].implementation           # pylint: disable=no-member
+    assert global_test_holder['inputs']['putput'] is True
 
     # Context based attributes (sugaring)
     dependency_node_template = ctx.model.node_template.get_by_name(
@@ -175,11 +165,11 @@ def test_relationship_operation_task_execution(ctx, thread_executor):
         mock.models.DEPENDENT_NODE_TEMPLATE_NAME)
     dependent_node = ctx.model.node.get_by_name(mock.models.DEPENDENT_NODE_NAME)
 
-    assert operation_context.target_node_template == dependency_node_template
-    assert operation_context.target_node == dependency_node
-    assert operation_context.relationship == relationship
-    assert operation_context.source_node_template == dependent_node_template
-    assert operation_context.source_node == dependent_node
+    assert global_test_holder['target_node_template_name'] == dependency_node_template.name
+    assert global_test_holder['target_node_name'] == dependency_node.name
+    assert global_test_holder['relationship_name'] == relationship.name
+    assert global_test_holder['source_node_template_name'] == dependent_node_template.name
+    assert global_test_holder['source_node_name'] == dependent_node.name
 
 
 def test_invalid_task_operation_id(ctx, thread_executor):
@@ -386,8 +376,29 @@ def logged_operation(ctx, **_):
 
 
 @operation
-def basic_operation(ctx, **_):
-    global_test_holder[ctx.name] = ctx
+def basic_node_operation(ctx, **_):
+    operation_common(ctx)
+    global_test_holder['template_name'] = ctx.node_template.name
+    global_test_holder['node_name'] = ctx.node.name
+
+
+@operation
+def basic_relationship_operation(ctx, **_):
+    operation_common(ctx)
+    global_test_holder['target_node_template_name'] = ctx.target_node_template.name
+    global_test_holder['target_node_name'] = ctx.target_node.name
+    global_test_holder['relationship_name'] = ctx.relationship.name
+    global_test_holder['source_node_template_name'] = ctx.source_node_template.name
+    global_test_holder['source_node_name'] = ctx.source_node.name
+
+
+def operation_common(ctx):
+    global_test_holder['ctx_name'] = ctx.__class__.__name__
+
+    global_test_holder['actor_name'] = ctx.task.actor.name
+    global_test_holder['task_name'] = ctx.task.name
+    global_test_holder['implementation'] = ctx.task.implementation
+    global_test_holder['inputs'] = dict(i.unwrap() for i in ctx.task.inputs.values())
 
 
 @operation

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/45c158ef/tests/orchestrator/context/test_toolbelt.py
----------------------------------------------------------------------
diff --git a/tests/orchestrator/context/test_toolbelt.py b/tests/orchestrator/context/test_toolbelt.py
index ecc3ac2..822ac5a 100644
--- a/tests/orchestrator/context/test_toolbelt.py
+++ b/tests/orchestrator/context/test_toolbelt.py
@@ -19,15 +19,18 @@ from aria import workflow, operation
 from aria.orchestrator import context
 from aria.orchestrator.workflows import api
 from aria.orchestrator.workflows.executor import thread
-from aria.orchestrator.context.toolbelt import RelationshipToolBelt
 
-from tests import mock, storage
+from tests import (
+    mock,
+    storage,
+    helpers
+)
 from . import (
     op_path,
     execute,
 )
 
-global_test_holder = {}
+global_test_holder = helpers.FilesystemDataHolder()
 
 
 @pytest.fixture
@@ -85,6 +88,8 @@ def test_host_ip(workflow_context, executor):
                               inputs=inputs)
     )
     dependency_node.interfaces[interface.name] = interface
+    dependency_node.runtime_properties['ip'] = '1.1.1.1'
+
     workflow_context.model.node.update(dependency_node)
 
     @workflow
@@ -131,12 +136,11 @@ def test_relationship_tool_belt(workflow_context, executor):
 
     execute(workflow_func=basic_workflow, workflow_context=workflow_context, executor=executor)
 
-    assert isinstance(global_test_holder.get(api.task.OperationTask.NAME_FORMAT.format(
+    assert global_test_holder.get(api.task.OperationTask.NAME_FORMAT.format(
         type='relationship',
         name=relationship.name,
         interface=interface_name,
-        operation=operation_name
-    )), RelationshipToolBelt)
+        operation=operation_name)) == relationship.source_node.name
 
 
 def test_wrong_model_toolbelt():
@@ -151,7 +155,7 @@ def host_ip(toolbelt, **_):
 
 @operation(toolbelt=True)
 def relationship_operation(ctx, toolbelt, **_):
-    global_test_holder[ctx.name] = toolbelt
+    global_test_holder[ctx.name] = toolbelt._op_context.source_node.name
 
 
 @pytest.fixture(autouse=True)


[08/19] incubator-ariatosca git commit: ARIA-210 Handle relative paths in CLI service-templates

Posted by ra...@apache.org.
ARIA-210 Handle relative paths in CLI service-templates

This was a rather simple change, mainly involving adding absolute path
references.

The problems were only in `service-templates store` and in
`service-templates create-archive`.
`service-templates validate` was not affected.


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

Branch: refs/heads/ARIA-208-Missing-back-refrences-for-models
Commit: d0411d3de37bb31073fda605cd9b73431b685d92
Parents: 16fcca4
Author: Avia Efrat <av...@gigaspaces.com>
Authored: Mon May 8 17:45:23 2017 +0300
Committer: Avia Efrat <av...@gigaspaces.com>
Committed: Tue May 9 17:30:22 2017 +0300

----------------------------------------------------------------------
 aria/cli/commands/service_templates.py |  4 +++-
 aria/cli/csar.py                       | 18 ++++++++++--------
 aria/cli/service_template_utils.py     |  2 +-
 tests/cli/test_service_templates.py    | 22 ++++++++++++++++++++++
 4 files changed, 36 insertions(+), 10 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/d0411d3d/aria/cli/commands/service_templates.py
----------------------------------------------------------------------
diff --git a/aria/cli/commands/service_templates.py b/aria/cli/commands/service_templates.py
index 2537012..e459871 100644
--- a/aria/cli/commands/service_templates.py
+++ b/aria/cli/commands/service_templates.py
@@ -195,7 +195,9 @@ def create_archive(service_template_path, destination, logger):
     `destination` is the path of the output CSAR archive file
     """
     logger.info('Creating a CSAR archive')
-    csar.write(os.path.dirname(service_template_path), service_template_path, destination, logger)
+    if not destination.endswith(csar.CSAR_FILE_EXTENSION):
+        destination += csar.CSAR_FILE_EXTENSION
+    csar.write(service_template_path, destination, logger)
     logger.info('CSAR archive created at {0}'.format(destination))
 
 

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/d0411d3d/aria/cli/csar.py
----------------------------------------------------------------------
diff --git a/aria/cli/csar.py b/aria/cli/csar.py
index 5bc35ac..8f44557 100644
--- a/aria/cli/csar.py
+++ b/aria/cli/csar.py
@@ -22,7 +22,7 @@ import zipfile
 import requests
 from ruamel import yaml
 
-
+CSAR_FILE_EXTENSION = '.csar'
 META_FILE = 'TOSCA-Metadata/TOSCA.meta'
 META_FILE_VERSION_KEY = 'TOSCA-Meta-File-Version'
 META_FILE_VERSION_VALUE = '1.0'
@@ -38,17 +38,19 @@ BASE_METADATA = {
 }
 
 
-def write(source, entry, destination, logger):
-    source = os.path.expanduser(source)
-    destination = os.path.expanduser(destination)
-    entry_definitions = os.path.join(source, entry)
+def write(service_template_path, destination, logger):
+
+    service_template_path = os.path.abspath(os.path.expanduser(service_template_path))
+    source = os.path.dirname(service_template_path)
+    entry = os.path.basename(service_template_path)
+
     meta_file = os.path.join(source, META_FILE)
     if not os.path.isdir(source):
         raise ValueError('{0} is not a directory. Please specify the service template '
                          'directory.'.format(source))
-    if not os.path.isfile(entry_definitions):
+    if not os.path.isfile(service_template_path):
         raise ValueError('{0} does not exists. Please specify a valid entry point.'
-                         .format(entry_definitions))
+                         .format(service_template_path))
     if os.path.exists(destination):
         raise ValueError('{0} already exists. Please provide a path to where the CSAR should be '
                          'created.'.format(destination))
@@ -175,4 +177,4 @@ def read(source, destination=None, logger=None):
 
 
 def is_csar_archive(source):
-    return source.endswith('.csar')
+    return source.endswith(CSAR_FILE_EXTENSION)

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/d0411d3d/aria/cli/service_template_utils.py
----------------------------------------------------------------------
diff --git a/aria/cli/service_template_utils.py b/aria/cli/service_template_utils.py
index 382cce1..c953c02 100644
--- a/aria/cli/service_template_utils.py
+++ b/aria/cli/service_template_utils.py
@@ -53,7 +53,7 @@ def get(source, service_template_filename):
             return _get_service_template_file_from_archive(source, service_template_filename)
         else:
             # Maybe check if yaml.
-            return source
+            return os.path.abspath(source)
     elif len(source.split('/')) == 2:
         url = _map_to_github_url(source)
         downloaded_file = utils.download_file(url)

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/d0411d3d/tests/cli/test_service_templates.py
----------------------------------------------------------------------
diff --git a/tests/cli/test_service_templates.py b/tests/cli/test_service_templates.py
index dd9eedd..22a8fc8 100644
--- a/tests/cli/test_service_templates.py
+++ b/tests/cli/test_service_templates.py
@@ -12,6 +12,8 @@
 # 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.
+import os
+import zipfile
 
 import pytest
 import mock
@@ -131,6 +133,18 @@ class TestServiceTemplatesStore(TestCliBase):
         assert 'Service template {name} stored'.format(
             name=mock_models.SERVICE_TEMPLATE_NAME) in self.logger_output_string
 
+    def test_store_relative_path_single_yaml_file(self, monkeypatch, mock_object):
+        monkeypatch.setattr(Core, 'create_service_template', mock_object)
+        monkeypatch.setattr(os.path, 'isfile', lambda x: True)
+        monkeypatch.setattr(service_template_utils, '_is_archive', lambda x: False)
+
+        self.invoke('service_templates store service_template.yaml {name}'.format(
+            name=mock_models.SERVICE_TEMPLATE_NAME))
+
+        mock_object.assert_called_with(os.path.join(os.getcwd(), 'service_template.yaml'),
+                                       mock.ANY,
+                                       mock.ANY)
+
     def test_store_raises_exception_resulting_from_name_uniqueness(self, monkeypatch, mock_object):
 
         monkeypatch.setattr(service_template_utils, 'get', mock_object)
@@ -244,3 +258,11 @@ class TestServiceTemplatesCreateArchive(TestCliBase):
         monkeypatch.setattr(csar, 'write', mock_object)
         self.invoke('service_templates create_archive stubpath stubdest')
         assert 'CSAR archive created at stubdest' in self.logger_output_string
+
+    def test_create_archive_from_relative_path(self, monkeypatch, mock_object):
+
+        monkeypatch.setattr(os.path, 'isfile', mock_object)
+        monkeypatch.setattr(zipfile, 'ZipFile', mock.MagicMock)
+
+        self.invoke('service_templates create_archive archive stubdest')
+        mock_object.assert_called_with(os.path.join(os.getcwd(), 'archive'))