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

[01/13] incubator-ariatosca git commit: ARIA-142-defaults-missing-for-execution-plugin [Forced Update!]

Repository: incubator-ariatosca
Updated Branches:
  refs/heads/ARIA-139-attributes 309cedb16 -> b7e1836d8 (forced update)


ARIA-142-defaults-missing-for-execution-plugin


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

Branch: refs/heads/ARIA-139-attributes
Commit: 1321d27204429195c80c86fa944613d43e61aa0e
Parents: 8e5a1ec
Author: max-orlov <ma...@gigaspaces.com>
Authored: Wed Apr 19 21:31:08 2017 +0300
Committer: max-orlov <ma...@gigaspaces.com>
Committed: Thu Apr 20 13:56:26 2017 +0300

----------------------------------------------------------------------
 aria/orchestrator/execution_plugin/instantiation.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/1321d272/aria/orchestrator/execution_plugin/instantiation.py
----------------------------------------------------------------------
diff --git a/aria/orchestrator/execution_plugin/instantiation.py b/aria/orchestrator/execution_plugin/instantiation.py
index 7627a38..ce114f0 100644
--- a/aria/orchestrator/execution_plugin/instantiation.py
+++ b/aria/orchestrator/execution_plugin/instantiation.py
@@ -73,8 +73,8 @@ def _configure_remote(operation, configuration, arguments):
     if ('password' not in ssh) and ('key' not in ssh) and ('key_filename' not in ssh):
         ssh['password'] = default_password
 
-    arguments['use_sudo'] = ssh.get('use_sudo')
-    arguments['hide_output'] = ssh.get('hide_output')
+    arguments['use_sudo'] = ssh.get('use_sudo', False)
+    arguments['hide_output'] = ssh.get('hide_output', [])
     arguments['fabric_env'] = {}
     if 'warn_only' in ssh:
         arguments['fabric_env']['warn_only'] = ssh['warn_only']


[08/13] incubator-ariatosca git commit: ARIA-146 Support colorful execution logging

Posted by em...@apache.org.
ARIA-146 Support colorful execution logging


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

Branch: refs/heads/ARIA-139-attributes
Commit: 5bc28b6641c2e749b0eebca05281790d21bf3c00
Parents: 8dc7b00
Author: max-orlov <ma...@gigaspaces.com>
Authored: Thu Apr 20 18:51:46 2017 +0300
Committer: max-orlov <ma...@gigaspaces.com>
Committed: Thu Apr 27 15:34:09 2017 +0300

----------------------------------------------------------------------
 aria/cli/color.py                      |  95 ++++++++++++
 aria/cli/commands/executions.py        |   6 +-
 aria/cli/commands/logs.py              |   6 +-
 aria/cli/config/config.py              |  25 ++-
 aria/cli/config/config_template.yaml   |  32 +++-
 aria/cli/core/aria.py                  |   9 ++
 aria/cli/execution_logging.py          | 232 +++++++++++++++++++++++-----
 aria/cli/helptexts.py                  |   1 +
 aria/cli/logger.py                     |   1 -
 aria/orchestrator/context/operation.py |   8 +-
 10 files changed, 366 insertions(+), 49 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/5bc28b66/aria/cli/color.py
----------------------------------------------------------------------
diff --git a/aria/cli/color.py b/aria/cli/color.py
new file mode 100644
index 0000000..5e0355a
--- /dev/null
+++ b/aria/cli/color.py
@@ -0,0 +1,95 @@
+# 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 StringIO import StringIO
+import re
+
+import colorama
+
+colorama.init()
+
+
+class StringStylizer(object):
+    def __init__(self, str_, color_spec=None):
+        self._str = str_
+        self._color_spec = color_spec
+
+    def __repr__(self):
+        if self._color_spec:
+            return '{schema}{str}{reset}'.format(
+                schema=self._color_spec, str=str(self._str), reset=Colors.Style.RESET_ALL)
+        return self._str
+
+    def __add__(self, other):
+        return str(self) + other
+
+    def __radd__(self, other):
+        return other + str(self)
+
+    def color(self, color_spec):
+        self._color_spec = color_spec
+
+    def replace(self, old, new, **kwargs):
+        self._str = self._str.replace(str(old), str(new), **kwargs)
+
+    def format(self, *args, **kwargs):
+        self._str = self._str.format(*args, **kwargs)
+
+    def highlight(self, pattern, schema):
+        if pattern is None:
+            return
+        for match in set(re.findall(re.compile(pattern), self._str)):
+            self.replace(match, schema + match + Colors.Style.RESET_ALL + self._color_spec)
+
+
+def _get_colors(color_type):
+    for name in dir(color_type):
+        if not name.startswith('_'):
+            yield (name.lower(), getattr(color_type, name))
+
+
+class Colors(object):
+    Fore = colorama.Fore
+    Back = colorama.Back
+    Style = colorama.Style
+
+    _colors = {
+        'fore': dict(_get_colors(Fore)),
+        'back': dict(_get_colors(Back)),
+        'style': dict(_get_colors(Style))
+    }
+
+
+class ColorSpec(object):
+    def __init__(self, fore=None, back=None, style=None):
+        """
+        It is possible to provide fore, back and style arguments. each could be either
+        the color is lower case letter, or the actual color from colorama.
+
+        """
+        self._kwargs = dict(fore=fore, back=back, style=style)
+        self._str = StringIO()
+        for type_, colors in Colors._colors.items():
+            value = self._kwargs.get(type_, None)
+            # the former case is if the value is a string, the latter is in case of an object.
+            self._str.write(colors.get(value) or value)
+
+    def __str__(self):
+        return self._str.getvalue()
+
+    def __add__(self, other):
+        return str(self) + str(other)
+
+    def __radd__(self, other):
+        return str(other) + str(self)

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/5bc28b66/aria/cli/commands/executions.py
----------------------------------------------------------------------
diff --git a/aria/cli/commands/executions.py b/aria/cli/commands/executions.py
index cb86376..6176ea2 100644
--- a/aria/cli/commands/executions.py
+++ b/aria/cli/commands/executions.py
@@ -109,6 +109,7 @@ def list(service_name,
 @aria.options.dry_execution
 @aria.options.task_max_attempts()
 @aria.options.task_retry_interval()
+@aria.options.mark_pattern()
 @aria.options.verbose()
 @aria.pass_model_storage
 @aria.pass_resource_storage
@@ -120,6 +121,7 @@ def start(workflow_name,
           dry,
           task_max_attempts,
           task_retry_interval,
+          mark_pattern,
           model_storage,
           resource_storage,
           plugin_manager,
@@ -146,7 +148,7 @@ def start(workflow_name,
     log_iterator = cli_logger.ModelLogIterator(model_storage, workflow_runner.execution_id)
     try:
         while execution_thread.is_alive():
-            execution_logging.log_list(log_iterator)
+            execution_logging.log_list(log_iterator, mark_pattern=mark_pattern)
             execution_thread.join(1)
 
     except KeyboardInterrupt:
@@ -154,7 +156,7 @@ def start(workflow_name,
 
     # It might be the case where some logs were written and the execution was terminated, thus we
     # need to drain the remaining logs.
-    execution_logging.log_list(log_iterator)
+    execution_logging.log_list(log_iterator, mark_pattern=mark_pattern)
 
     # raise any errors from the execution thread (note these are not workflow execution errors)
     execution_thread.raise_error_if_exists()

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/5bc28b66/aria/cli/commands/logs.py
----------------------------------------------------------------------
diff --git a/aria/cli/commands/logs.py b/aria/cli/commands/logs.py
index 79aff07..2f7f361 100644
--- a/aria/cli/commands/logs.py
+++ b/aria/cli/commands/logs.py
@@ -18,6 +18,7 @@ from ..core import aria
 
 
 @aria.group(name='logs')
+@aria.options.verbose()
 def logs():
     """Show logs from workflow executions
     """
@@ -28,15 +29,16 @@ def logs():
               short_help='List execution logs')
 @aria.argument('execution-id')
 @aria.options.verbose()
+@aria.options.mark_pattern()
 @aria.pass_model_storage
 @aria.pass_logger
-def list(execution_id, model_storage, logger):
+def list(execution_id, mark_pattern, model_storage, logger):
     """Display logs for an execution
     """
     logger.info('Listing logs for execution id {0}'.format(execution_id))
     log_iterator = ModelLogIterator(model_storage, execution_id)
 
-    any_logs = execution_logging.log_list(log_iterator)
+    any_logs = execution_logging.log_list(log_iterator, mark_pattern=mark_pattern)
 
     if not any_logs:
         logger.info('\tNo logs')

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/5bc28b66/aria/cli/config/config.py
----------------------------------------------------------------------
diff --git a/aria/cli/config/config.py b/aria/cli/config/config.py
index 99f46ca..8c4214c 100644
--- a/aria/cli/config/config.py
+++ b/aria/cli/config/config.py
@@ -52,10 +52,6 @@ class CliConfig(object):
         return cls(config_path)
 
     @property
-    def colors(self):
-        return self._config.get('colors', False)
-
-    @property
     def logging(self):
         return self.Logging(self._config.get('logging'))
 
@@ -71,3 +67,24 @@ class CliConfig(object):
         @property
         def loggers(self):
             return self._logging.get('loggers', {})
+
+        @property
+        def execution(self):
+            return self.Execution(self._logging.get('execution'))
+
+        class Execution(object):
+
+            def __init__(self, execution_logging):
+                self._execution_logging = execution_logging
+
+            @property
+            def colors_enabled(self):
+                return self.colors.get('enabled', False)
+
+            @property
+            def colors(self):
+                return self._execution_logging.get('colors', {})
+
+            @property
+            def formats(self):
+                return self._execution_logging.get('formats', {})

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/5bc28b66/aria/cli/config/config_template.yaml
----------------------------------------------------------------------
diff --git a/aria/cli/config/config_template.yaml b/aria/cli/config/config_template.yaml
index 13f2cf9..94fcac3 100644
--- a/aria/cli/config/config_template.yaml
+++ b/aria/cli/config/config_template.yaml
@@ -1,4 +1,3 @@
-colors: {{ enable_colors }}
 
 logging:
 
@@ -10,3 +9,34 @@ logging:
 
     # main logger of the cli. provides basic descriptions for executed operations.
     aria.cli.main: info
+
+  execution:
+    formats:
+      # According to verbosity level 0 - no verbose. 3 - high verbose
+      0: '{message}'
+      1: '{timestamp:%H:%M:%S} | {level[0]} | {message}'
+      2: '{timestamp:%H:%M:%S} | {level[0]} | {implementation} | {message}'
+      3: '{timestamp:%H:%M:%S} | {level[0]} | {implementation} | {inputs} | {message}'
+
+    colors:
+      enabled: true
+
+      level:
+        default: {'fore': 'lightmagenta_ex'}
+        error: {'fore': 'red', 'style': 'bright'}
+      timestamp:
+        default: {'fore': 'lightmagenta_ex'}
+        error: {'fore': 'red', 'style': 'bright'}
+      message:
+        default: {'fore': 'lightblue_ex'}
+        error: {'fore': 'red', 'style': 'bright'}
+      implementation:
+        default: {'fore': 'lightblack_ex'}
+        error: {'fore': 'red', 'style': 'bright'}
+      inputs:
+        default: {'fore': 'blue'}
+        error: {'fore': 'red', 'style': 'bright'}
+      traceback:
+        default: {'fore': 'red'}
+
+      marker: 'lightyellow_ex'

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/5bc28b66/aria/cli/core/aria.py
----------------------------------------------------------------------
diff --git a/aria/cli/core/aria.py b/aria/cli/core/aria.py
index ed7c490..ed6afa1 100644
--- a/aria/cli/core/aria.py
+++ b/aria/cli/core/aria.py
@@ -425,5 +425,14 @@ class Options(object):
             required=required,
             help=helptexts.SERVICE_ID)
 
+    @staticmethod
+    def mark_pattern():
+        return click.option(
+            '-m',
+            '--mark-pattern',
+            help=helptexts.MARK_PATTERN,
+            type=str,
+            required=False
+        )
 
 options = Options()

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/5bc28b66/aria/cli/execution_logging.py
----------------------------------------------------------------------
diff --git a/aria/cli/execution_logging.py b/aria/cli/execution_logging.py
index 8baf6d7..b23165f 100644
--- a/aria/cli/execution_logging.py
+++ b/aria/cli/execution_logging.py
@@ -12,67 +12,227 @@
 # 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 re
 from StringIO import StringIO
+from functools import partial
 
-from . import logger
+from . import (
+    logger,
+    color
+)
 from .env import env
 
-DEFAULT_FORMATTING = {
-    logger.NO_VERBOSE: {'message': '{item.msg}'},
-    logger.LOW_VERBOSE: {
-        'message': '{timestamp} | {item.level[0]} | {item.msg}',
-        'timestamp': '%H:%M:%S'
+
+FIELD_TYPE = 'field_type'
+LEVEL = 'level'
+TIMESTAMP = 'timestamp'
+MESSAGE = 'message'
+IMPLEMENTATION = 'implementation'
+INPUTS = 'inputs'
+TRACEBACK = 'traceback'
+MARKER = 'marker'
+
+FINAL_STATES = 'final_states'
+SUCCESS_STATE = 'succeeded'
+CANCEL_STATE = 'canceled'
+FAIL_STATE = 'failed'
+
+_EXECUTION_PATTERN = "\'.*\' workflow execution {0}".format
+# In order to be able to format a string into this regex pattern, we need to provide support
+# in adding this string into double curly brackets. This is an issue with python format, so we add
+# this via format itself.
+_FIELD_TYPE_PATTERN = partial('.*({starting}{0}{closing}).*'.format, starting='{', closing='.*?}')
+
+_PATTERNS = {
+    FINAL_STATES: {
+        SUCCESS_STATE: re.compile(_EXECUTION_PATTERN(SUCCESS_STATE)),
+        CANCEL_STATE: re.compile(_EXECUTION_PATTERN(CANCEL_STATE)),
+        FAIL_STATE: re.compile(_EXECUTION_PATTERN(FAIL_STATE)),
     },
-    logger.MEDIUM_VERBOSE: {
-        'message': '{timestamp} | {item.level[0]} | {implementation} | {item.msg} ',
-        'timestamp': '%H:%M:%S'
+    FIELD_TYPE: {
+        IMPLEMENTATION: re.compile(_FIELD_TYPE_PATTERN(IMPLEMENTATION)),
+        LEVEL: re.compile(_FIELD_TYPE_PATTERN(LEVEL)),
+        MESSAGE: re.compile(_FIELD_TYPE_PATTERN(MESSAGE)),
+        INPUTS: re.compile(_FIELD_TYPE_PATTERN(INPUTS)),
+        TIMESTAMP: re.compile(_FIELD_TYPE_PATTERN(TIMESTAMP))
+    }
+}
+
+_FINAL_STATES = {
+    SUCCESS_STATE: color.Colors.Fore.GREEN,
+    CANCEL_STATE: color.Colors.Fore.YELLOW,
+    FAIL_STATE: color.Colors.Fore.RED
+}
+
+_DEFAULT_COLORS = {
+    LEVEL: {
+        'default': {'fore': 'lightmagenta_ex'},
+        'error': {'fore': 'red', 'style': 'bright'},
+    },
+    TIMESTAMP: {
+        'default': {'fore': 'lightmagenta_ex'},
+        'error': {'fore': 'red', 'style': 'bright'},
     },
-    logger.HIGH_VERBOSE: {
-        'message': '{timestamp} | {item.level[0]} | {implementation}({inputs}) | {item.msg} ',
-        'timestamp': '%H:%M:%S'
+    MESSAGE: {
+        'default': {'fore': 'lightblue_ex'},
+        'error': {'fore': 'red', 'style': 'bright'},
     },
+    IMPLEMENTATION:{
+        'default': {'fore': 'lightblack_ex'},
+        'error': {'fore': 'red', 'style': 'bright'},
+    },
+    INPUTS: {
+        'default': {'fore': 'blue'},
+        'error': {'fore': 'red', 'style': 'bright'},
+    },
+    TRACEBACK: {'default': {'fore': 'red'}},
+
+    MARKER: 'lightyellow_ex'
 }
 
+_DEFAULT_FORMATS = {
+    logger.NO_VERBOSE: '{message}',
+    logger.LOW_VERBOSE: '{timestamp:%H:%M:%S} | {level[0]} | {message}',
+    logger.MEDIUM_VERBOSE: '{timestamp:%H:%M:%S} | {level[0]} | {implementation} | {message}',
+    logger.HIGH_VERBOSE:
+        '{timestamp:%H:%M:%S} | {level[0]} | {implementation} | {inputs} | {message}'
+}
 
-def _str(item, formats=None):
-    # If no formats are passed we revert to the default formats (per level)
-    formats = formats or {}
-    formatting = formats.get(env.logging.verbosity_level,
-                             DEFAULT_FORMATTING[env.logging.verbosity_level])
-    msg = StringIO()
 
-    formatting_kwargs = dict(item=item)
+def stylize_log(item, mark_pattern):
 
+    # implementation
     if item.task:
-        formatting_kwargs['implementation'] = item.task.implementation
-        formatting_kwargs['inputs'] = dict(i.unwrap() for i in item.task.inputs.values())
-    else:
-        formatting_kwargs['implementation'] = item.execution.workflow_name
-        formatting_kwargs['inputs'] = dict(i.unwrap() for i in item.execution.inputs.values())
-
-    if 'timestamp' in formatting:
-        formatting_kwargs['timestamp'] = item.created_at.strftime(formatting['timestamp'])
+        # operation task
+        implementation = item.task.implementation
+        inputs = dict(i.unwrap() for i in item.task.inputs.values())
     else:
-        formatting_kwargs['timestamp'] = item.created_at
+        # execution task
+        implementation = item.execution.workflow_name
+        inputs = dict(i.unwrap() for i in item.execution.inputs.values())
 
-    msg.write(formatting['message'].format(**formatting_kwargs))
+    stylized_str = color.StringStylizer(_get_format())
+    _populate_level(stylized_str, item)
+    _populate_timestamp(stylized_str, item)
+    _populate_message(stylized_str, item, mark_pattern)
+    _populate_inputs(stylized_str, inputs, item, mark_pattern)
+    _populate_implementation(stylized_str, implementation, item, mark_pattern)
 
+    msg = StringIO()
+    msg.write(str(stylized_str))
     # Add the exception and the error msg.
     if item.traceback and env.logging.verbosity_level >= logger.MEDIUM_VERBOSE:
-        for line in item.traceback.splitlines(True):
-            msg.write('\t' + '|' + line)
+        msg.write(os.linesep)
+        msg.writelines(_color_traceback('\t' + '|' + line, item, mark_pattern)
+                       for line in item.traceback.splitlines(True))
 
     return msg.getvalue()
 
 
-def log(item, *args, **kwargs):
-    return getattr(env.logging.logger, item.level.lower())(_str(item), *args, **kwargs)
+def log(item, mark_pattern=None, *args, **kwargs):
+    leveled_log = getattr(env.logging.logger, item.level.lower())
+    return leveled_log(stylize_log(item, mark_pattern), *args, **kwargs)
 
 
-def log_list(iterator):
+def log_list(iterator, mark_pattern=None):
     any_logs = False
     for item in iterator:
-        log(item)
+        log(item, mark_pattern)
         any_logs = True
     return any_logs
+
+
+def _get_format():
+    return (env.config.logging.execution.formats.get(env.logging.verbosity_level) or
+            _DEFAULT_FORMATS.get(env.logging.verbosity_level))
+
+
+def _get_styles(field_type):
+    return env.config.logging.execution.colors[field_type]
+
+
+def _is_color_enabled():
+    # If styling is enabled and the current log_item isn't final string
+    return env.config.logging.execution.colors_enabled
+
+
+def _get_marker_schema():
+    return color.ColorSpec(back=_get_styles(MARKER))
+
+
+def _populate_implementation(str_, implementation, log_item, mark_pattern=None):
+    _stylize(str_, implementation, log_item, IMPLEMENTATION, mark_pattern)
+
+
+def _populate_inputs(str_, inputs, log_item, mark_pattern=None):
+    _stylize(str_, inputs, log_item, INPUTS, mark_pattern)
+
+
+def _populate_timestamp(str_, log_item):
+    _stylize(str_, log_item.created_at, log_item, TIMESTAMP)
+
+
+def _populate_message(str_, log_item, mark_pattern=None):
+    _stylize(str_, log_item.msg, log_item, MESSAGE, mark_pattern)
+
+
+def _populate_level(str_, log_item):
+    _stylize(str_, log_item.level[0], log_item, LEVEL)
+
+
+def _stylize(stylized_str, msg, log_item, msg_type, mark_pattern=None):
+    match = re.match(_PATTERNS[FIELD_TYPE][msg_type], stylized_str._str)
+    if not match:
+        return
+    matched_substr = match.group(1)
+
+    substring = color.StringStylizer(matched_substr)
+
+    # handle format
+    substring.format(**{msg_type: msg})
+
+    if _is_color_enabled():
+        # handle color
+        substring.color(_resolve_schema(msg_type, log_item))
+        if not _is_end_execution_log(log_item):
+            # handle highlighting
+            substring.highlight(mark_pattern, _get_marker_schema())
+
+    stylized_str.replace(matched_substr, substring)
+
+
+def _color_traceback(traceback, log_item, mark_pattern):
+    if _is_color_enabled():
+        stylized_string = color.StringStylizer(traceback, _resolve_schema(TRACEBACK, log_item))
+        stylized_string.highlight(mark_pattern, _get_marker_schema())
+        return stylized_string
+    return traceback
+
+
+def _is_end_execution_log(log_item):
+    return not log_item.task and bool(_end_execution_schema(log_item))
+
+
+def _end_execution_schema(log_item):
+    for state, pattern in _PATTERNS[FINAL_STATES].items():
+        if re.match(pattern, log_item.msg):
+            return _FINAL_STATES[state]
+
+
+def _resolve_schema(msg_type, log_item):
+    if _is_end_execution_log(log_item):
+        return _end_execution_schema(log_item)
+    else:
+        return color.ColorSpec(
+            **(
+                # retrieve the schema from the user config according to the level
+                _get_styles(msg_type).get(log_item.level.lower()) or
+                # retrieve the default schema from the user config
+                _get_styles(msg_type).get('default') or
+                # retrieve the schema from the aria default config according to the level
+                _DEFAULT_COLORS[msg_type].get(log_item.level.lower()) or
+                # retrieve the default schema from the aria default config
+                _DEFAULT_COLORS[msg_type].get('default')
+            )
+        )

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/5bc28b66/aria/cli/helptexts.py
----------------------------------------------------------------------
diff --git a/aria/cli/helptexts.py b/aria/cli/helptexts.py
index 1a3f6c0..8641822 100644
--- a/aria/cli/helptexts.py
+++ b/aria/cli/helptexts.py
@@ -47,3 +47,4 @@ IGNORE_AVAILABLE_NODES = "Delete the service even if it has available nodes"
 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"

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/5bc28b66/aria/cli/logger.py
----------------------------------------------------------------------
diff --git a/aria/cli/logger.py b/aria/cli/logger.py
index c240f02..5de3701 100644
--- a/aria/cli/logger.py
+++ b/aria/cli/logger.py
@@ -19,7 +19,6 @@ import copy
 import logging
 from logutils import dictconfig
 
-
 HIGH_VERBOSE = 3
 MEDIUM_VERBOSE = 2
 LOW_VERBOSE = 1

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/5bc28b66/aria/orchestrator/context/operation.py
----------------------------------------------------------------------
diff --git a/aria/orchestrator/context/operation.py b/aria/orchestrator/context/operation.py
index c383958..68a02aa 100644
--- a/aria/orchestrator/context/operation.py
+++ b/aria/orchestrator/context/operation.py
@@ -30,11 +30,12 @@ class BaseOperationContext(BaseContext):
     """
 
     def __init__(self, task_id, actor_id, **kwargs):
-        super(BaseOperationContext, self).__init__(**kwargs)
         self._task_id = task_id
         self._actor_id = actor_id
         self._thread_local = threading.local()
-        self._register_logger(task_id=self.task.id)
+        logger_level = kwargs.pop('logger_level', None)
+        super(BaseOperationContext, self).__init__(**kwargs)
+        self._register_logger(task_id=self.task.id, level=logger_level)
 
     def __repr__(self):
         details = 'implementation={task.implementation}; ' \
@@ -80,7 +81,8 @@ class BaseOperationContext(BaseContext):
             'workdir': self._workdir,
             'model_storage': self.model.serialization_dict if self.model else None,
             'resource_storage': self.resource.serialization_dict if self.resource else None,
-            'execution_id': self._execution_id
+            'execution_id': self._execution_id,
+            'logger_level': self.logger.level
         }
         return {
             'context_cls': context_cls,


[06/13] incubator-ariatosca git commit: ARIA-153 Write end-to-end tests for ARIA

Posted by em...@apache.org.
ARIA-153 Write end-to-end tests for ARIA

Created infrastructure for end-to-end tests,
plus a test for the hello-world example.


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

Branch: refs/heads/ARIA-139-attributes
Commit: d91696bdf5d208ff57496df23026341bc6cfa351
Parents: 5cd7aec
Author: Ran Ziv <ra...@gigaspaces.com>
Authored: Wed Apr 26 20:21:19 2017 +0300
Committer: Ran Ziv <ra...@gigaspaces.com>
Committed: Thu Apr 27 14:28:10 2017 +0300

----------------------------------------------------------------------
 .travis.yml                       |  2 +
 tests/end2end/__init__.py         |  0
 tests/end2end/test_hello_world.py | 61 +++++++++++++++++++++
 tests/end2end/testenv.py          | 96 ++++++++++++++++++++++++++++++++++
 tests/helpers.py                  | 31 +++++++++++
 tests/parser/service_templates.py |  9 ++--
 tests/parser/utils.py             | 16 ------
 tests/requirements.txt            |  1 +
 tox.ini                           | 14 +++--
 9 files changed, 207 insertions(+), 23 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/d91696bd/.travis.yml
----------------------------------------------------------------------
diff --git a/.travis.yml b/.travis.yml
index 5413ff2..b11ed62 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -19,6 +19,8 @@ env:
 - TOX_ENV=pylint_tests
 - TOX_ENV=py27
 - TOX_ENV=py26
+- TOX_ENV=py27e2e
+- TOX_ENV=py26e2e
 install:
   - pip install --upgrade pip
   - pip install --upgrade setuptools

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/d91696bd/tests/end2end/__init__.py
----------------------------------------------------------------------
diff --git a/tests/end2end/__init__.py b/tests/end2end/__init__.py
new file mode 100644
index 0000000..e69de29

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/d91696bd/tests/end2end/test_hello_world.py
----------------------------------------------------------------------
diff --git a/tests/end2end/test_hello_world.py b/tests/end2end/test_hello_world.py
new file mode 100644
index 0000000..09e5d06
--- /dev/null
+++ b/tests/end2end/test_hello_world.py
@@ -0,0 +1,61 @@
+# 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 requests
+
+from .testenv import testenv  # pylint: disable=unused-import
+from .. import helpers
+
+
+def test_hello_world(testenv):
+    hello_world_template_uri = helpers.get_example_uri('hello-world', 'helloworld.yaml')
+    service_name = testenv.install_service(hello_world_template_uri)
+
+    try:
+        _verify_deployed_service_in_storage(service_name, testenv.model_storage)
+        _verify_webserver_up('http://localhost:9090')
+    finally:
+        # Even if some assertions failed, attempt to execute uninstall so the
+        # webserver process doesn't stay up once the test is finished
+        # TODO: remove force_service_delete=True
+        testenv.uninstall_service(force_service_delete=True)
+
+    _verify_webserver_down('http://localhost:9090')
+    testenv.verify_clean_storage()
+
+
+def _verify_webserver_up(http_endpoint):
+    server_response = requests.get(http_endpoint, timeout=10)
+    assert server_response.status_code == 200
+
+
+def _verify_webserver_down(http_endpoint):
+    try:
+        requests.get(http_endpoint, timeout=10)
+        assert False
+    except requests.exceptions.ConnectionError:
+        pass
+
+
+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]
+    assert service.name == service_name
+    assert len(service.executions) == 1
+    assert len(service.nodes) == 2
+    # TODO: validate node states
+    assert len(service.executions[0].logs) > 0

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/d91696bd/tests/end2end/testenv.py
----------------------------------------------------------------------
diff --git a/tests/end2end/testenv.py b/tests/end2end/testenv.py
new file mode 100644
index 0000000..3950b20
--- /dev/null
+++ b/tests/end2end/testenv.py
@@ -0,0 +1,96 @@
+# 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 sys
+
+import pytest
+import sh
+
+
+@pytest.fixture
+def testenv(tmpdir, request, monkeypatch):
+    test_name = request.node.name
+    workdir = str(tmpdir)
+
+    # setting the workdir environment variable for the CLI to work with
+    monkeypatch.setenv('ARIA_WORKDIR', workdir)
+    return TestEnvironment(workdir, test_name)
+
+
+class TestEnvironment(object):
+
+    def __init__(self, workdir, test_name):
+        self.workdir = workdir
+        self.test_name = test_name
+
+        self.cli = self._get_cli()
+        env = self._get_aria_env()
+        self.model_storage = env.model_storage
+        self.resource_storage = env.resource_storage
+        self.plugin_manager = env.plugin_manager
+
+    def install_service(self, service_template_path, dry=False, service_template_name=None,
+                        service_name=None):
+        service_template_name = service_template_name or self.test_name
+        service_name = service_name or self.test_name
+
+        self.cli.service_templates.store(service_template_path, service_template_name)
+        self.cli.services.create(service_name, service_template_name=service_template_name)
+        self.execute_workflow(service_name, 'install', dry=dry)
+        return service_name
+
+    def uninstall_service(self, service_name=None, service_template_name=None, dry=False,
+                          force_service_delete=False):
+        service_name = service_name or self.test_name
+        self.execute_workflow(service_name, 'uninstall', dry=dry)
+        self.cli.services.delete(service_name, force=force_service_delete)
+        self.cli.service_templates.delete(service_template_name or self.test_name)
+
+    def execute_workflow(self, service_name, workflow_name, dry=False):
+        self.cli.executions.start(workflow_name, service_name=service_name, dry=dry)
+
+    def verify_clean_storage(self):
+        assert len(self.model_storage.service_template.list()) == 0
+        assert len(self.model_storage.service.list()) == 0
+        assert len(self.model_storage.execution.list()) == 0
+        assert len(self.model_storage.node_template.list()) == 0
+        assert len(self.model_storage.node.list()) == 0
+        assert len(self.model_storage.log.list()) == 0
+
+    def _get_cli(self):
+        cli = sh.aria.bake(_out=sys.stdout.write, _err=sys.stderr.write)
+
+        # the `sh` library supports underscore-dash auto-replacement for commands and option flags
+        # yet not for subcommands (e.g. `aria service-templates`); The following class fixes this.
+        class PatchedCli(object):
+            def __getattr__(self, attr):
+                if '_' in attr:
+                    return cli.bake(attr.replace('_', '-'))
+                return getattr(cli, attr)
+
+            def __call__(self, *args, **kwargs):
+                # this is to support the `aria` command itself (e.g. `aria --version` calls)
+                return cli(*args, **kwargs)
+
+        return PatchedCli()
+
+    def _get_aria_env(self):
+        # a somewhat hackish but most simple way of acquiring environment context such as
+        # the model storage, resource storage etc.
+        # note that the `ARIA_WORKDIR` environment variable must be exported before the import
+        # below is used, as the import itself will initialize the `.aria` directory.
+        from aria.cli import env as cli_env
+        reload(cli_env)  # reloading the module in-between tests
+        return cli_env.env

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/d91696bd/tests/helpers.py
----------------------------------------------------------------------
diff --git a/tests/helpers.py b/tests/helpers.py
new file mode 100644
index 0000000..472d696
--- /dev/null
+++ b/tests/helpers.py
@@ -0,0 +1,31 @@
+# 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
+
+from . import ROOT_DIR
+from .resources import DIR as RESOURCES_DIR
+
+
+def get_example_uri(*args):
+    return os.path.join(ROOT_DIR, 'examples', *args)
+
+
+def get_resource_uri(*args):
+    return os.path.join(RESOURCES_DIR, *args)
+
+
+def get_service_template_uri(*args):
+    return os.path.join(RESOURCES_DIR, 'service-templates', *args)

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/d91696bd/tests/parser/service_templates.py
----------------------------------------------------------------------
diff --git a/tests/parser/service_templates.py b/tests/parser/service_templates.py
index a8fde14..56f75ab 100644
--- a/tests/parser/service_templates.py
+++ b/tests/parser/service_templates.py
@@ -17,7 +17,8 @@ import os
 
 from aria.utils.caching import cachedmethod
 
-from .utils import (get_example_uri, get_test_uri, create_context, create_consumer)
+from .utils import (create_context, create_consumer)
+from ..helpers import (get_example_uri, get_service_template_uri)
 
 
 def consume_use_case(use_case_name, consumer_class_name='instance', cache=True):
@@ -37,10 +38,10 @@ def consume_use_case(use_case_name, consumer_class_name='instance', cache=True):
 
 def consume_node_cellar(consumer_class_name='instance', cache=True):
     cachedmethod.ENABLED = cache
-    uri = get_test_uri('tosca-simple-1.0', 'node-cellar', 'node-cellar.yaml')
+    uri = get_service_template_uri('tosca-simple-1.0', 'node-cellar', 'node-cellar.yaml')
     context = create_context(uri)
-    context.args.append('--inputs=' + get_test_uri('tosca-simple-1.0', 'node-cellar',
-                                                   'inputs.yaml'))
+    context.args.append('--inputs=' + get_service_template_uri('tosca-simple-1.0', 'node-cellar',
+                                                               'inputs.yaml'))
     consumer, dumper = create_consumer(context, consumer_class_name)
     consumer.consume()
     context.validation.dump_issues()

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/d91696bd/tests/parser/utils.py
----------------------------------------------------------------------
diff --git a/tests/parser/utils.py b/tests/parser/utils.py
index 8460de8..f0e890f 100644
--- a/tests/parser/utils.py
+++ b/tests/parser/utils.py
@@ -13,8 +13,6 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import os
-
 from aria.parser.loading import UriLocation
 from aria.parser.consumption import (
     ConsumptionContext,
@@ -28,20 +26,6 @@ from aria.parser.consumption import (
 )
 from aria.utils.imports import import_fullname
 
-from tests import ROOT_DIR
-from tests.resources import DIR
-
-
-SERVICE_TEMPLATES_DIR = os.path.join(DIR, 'service-templates')
-
-
-def get_example_uri(*args):
-    return os.path.join(ROOT_DIR, 'examples', *args)
-
-
-def get_test_uri(*args):
-    return os.path.join(SERVICE_TEMPLATES_DIR, *args)
-
 
 def create_context(uri,
                    loader_source='aria.parser.loading.DefaultLoaderSource',

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/d91696bd/tests/requirements.txt
----------------------------------------------------------------------
diff --git a/tests/requirements.txt b/tests/requirements.txt
index 2f0245a..71a227a 100644
--- a/tests/requirements.txt
+++ b/tests/requirements.txt
@@ -12,6 +12,7 @@
 
 testtools
 fasteners==0.13.0
+sh==1.12.13
 mock==1.0.1
 pylint==1.6.4
 pytest==3.0.2

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/d91696bd/tox.ini
----------------------------------------------------------------------
diff --git a/tox.ini b/tox.ini
index 6ad048f..4d86c6e 100644
--- a/tox.ini
+++ b/tox.ini
@@ -11,7 +11,7 @@
 # limitations under the License.
 
 [tox]
-envlist=py27,py26,pywin,pylint_code,pylint_tests
+envlist=py27,py26,py27e2e,py26e2e,pywin,pylint_code,pylint_tests
 
 [testenv]
 passenv =
@@ -27,15 +27,23 @@ deps =
 basepython =
   py26: python2.6
   py27: python2.7
+  py26e2e: python2.6
+  py27e2e: python2.7
   pywin: {env:PYTHON:}\python.exe
   pylint_code: python2.7
   pylint_tests: python2.7
 
 [testenv:py27]
-commands=pytest tests --cov-report term-missing --cov aria
+commands=pytest tests --ignore=tests/end2end --cov-report term-missing --cov aria
 
 [testenv:py26]
-commands=pytest tests --cov-report term-missing --cov aria
+commands=pytest tests --ignore=tests/end2end --cov-report term-missing --cov aria
+
+[testenv:py27e2e]
+commands=pytest tests/end2end --cov-report term-missing --cov aria
+
+[testenv:py26e2e]
+commands=pytest tests/end2end --cov-report term-missing --cov aria
 
 [testenv:pywin]
 commands=pytest tests --cov-report term-missing --cov aria


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

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


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

Branch: refs/heads/ARIA-139-attributes
Commit: b7e1836d894944ca1ae32fc63bb1b25e2fcaad6a
Parents: 0878526
Author: Tal Liron <ta...@gmail.com>
Authored: Wed Apr 19 20:07:33 2017 -0500
Committer: Tal Liron <ta...@gmail.com>
Committed: Mon May 1 17:03:52 2017 -0500

----------------------------------------------------------------------
 aria/core.py                                    |   6 +-
 aria/modeling/functions.py                      | 113 ++-
 aria/modeling/mixins.py                         |   2 +-
 aria/modeling/relationship.py                   |  19 +-
 aria/modeling/service_common.py                 | 220 +++++-
 aria/modeling/service_instance.py               |  78 ++-
 aria/modeling/service_template.py               | 126 ++--
 aria/modeling/utils.py                          |  33 +-
 aria/parser/consumption/__init__.py             |  11 +-
 aria/parser/consumption/modeling.py             |   4 +-
 aria/utils/formatting.py                        |   4 +-
 .../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/functions.py                    | 536 ---------------
 .../simple_v1_0/modeling/__init__.py            | 171 ++---
 .../simple_v1_0/modeling/data_types.py          |  10 +-
 .../simple_v1_0/modeling/functions.py           | 687 +++++++++++++++++++
 .../simple_v1_0/modeling/properties.py          |  17 +-
 .../simple_v1_0/presenter.py                    |   4 +-
 .../simple_v1_0/templates.py                    |   7 +-
 .../node-cellar/node-cellar.yaml                |   6 +-
 27 files changed, 1248 insertions(+), 850 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/b7e1836d/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/b7e1836d/aria/modeling/functions.py
----------------------------------------------------------------------
diff --git a/aria/modeling/functions.py b/aria/modeling/functions.py
index 02f4454..7c773ed 100644
--- a/aria/modeling/functions.py
+++ b/aria/modeling/functions.py
@@ -13,20 +13,127 @@
 # 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):
+    """
+    A wrapper for an evaluated :class:`Function` value.
+    """
+
+    def __init__(self, value, final=False):
+        self.value = value
+        self.final = final
+
+
+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
+
+
+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.
+    """
+
+    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/b7e1836d/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/b7e1836d/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/b7e1836d/aria/modeling/service_common.py
----------------------------------------------------------------------
diff --git a/aria/modeling/service_common.py b/aria/modeling/service_common.py
index 1188f34..25ea905 100644
--- a/aria/modeling/service_common.py
+++ b/aria/modeling/service_common.py
@@ -15,6 +15,8 @@
 
 # pylint: disable=no-self-argument, no-member, abstract-method
 
+import datetime
+
 from sqlalchemy import (
     Column,
     Text,
@@ -22,12 +24,13 @@ from sqlalchemy import (
 )
 from sqlalchemy.ext.declarative import declared_attr
 
+from ..parser import dsl_specification
 from ..parser.consumption import ConsumptionContext
 from ..utils import collections, formatting, console
 from .mixins import InstanceModelMixin, TemplateModelMixin
 from . import (
     relationship,
-    utils
+    functions
 )
 
 
@@ -50,8 +53,171 @@ 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
+    def container(self): # pylint: disable=too-many-return-statements,too-many-branches
+        """
+        The container for this parameter, which would be another model.
+
+        *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.
+        """
+
+        def get_interface_container(interface):
+            # Node
+            if interface.node:
+                return interface.node
+            elif interface.relationship:
+                return interface.relationship.source_node
+            # Group
+            elif interface.group:
+                return interface.group
+            raise ValueError('interface parameter does not have a container: {0}'
+                             .format(self.name))
+
+        def get_interface_template_container(interface_template):
+            # NodeTemplate
+            if interface_template.node_template:
+                return interface_template.node_template
+            elif interface_template.relationship_template:
+                relationship_template = interface_template.relationship_template
+                requirement_template = relationship_template.requirement_template
+                return requirement_template.node_template
+            # GroupTemplate
+            elif interface_template.group_template:
+                return interface_template.group_template
+            raise ValueError('interface_template parameter does not have a container: {0}'
+                             .format(self.name))
+
+        # Node
+        if self.properties_nodes:
+            return self.properties_nodes[0]
+        elif self.attributes_nodes:
+            return self.attributes_nodes[0]
+        elif self.properties_capabilities:
+            capability = self.properties_capabilities[0]
+            return capability.node
+        elif self.properties_artifacts:
+            artifact = self.properties_artifacts[0]
+            return artifact.node
+        elif self.properties_relationships:
+            relationship = self.properties_relationships[0] # pylint: disable=redefined-outer-name
+            return relationship.source_node
+        # Group
+        elif self.properties_groups:
+            return self.properties_groups[0]
+        # Policy
+        elif self.properties_policies:
+            return self.properties_policies[0]
+        # Service
+        elif self.inputs_services:
+            return self.inputs_services[0]
+        elif self.outputs_services:
+            return self.outputs_services[0]
+        # Execution
+        elif self.inputs_executions:
+            return self.inputs_executions[0]
+        # Node or Group
+        elif self.inputs_interfaces:
+            interface = self.inputs_interfaces[0]
+            return get_interface_container(interface)
+        elif self.inputs_operations:
+            operation = self.inputs_operations[0]
+            # Node or Group
+            if operation.interface:
+                return get_interface_container(operation.interface)
+            # Service
+            elif operation.service:
+                return operation.service
+            raise ValueError('operation parameter does not have a container: {0}'
+                             .format(self.name))
+
+        # NodeTemplate
+        elif self.properties_node_templates:
+            return self.properties_node_templates[0]
+        elif self.attributes_node_templates:
+            return self.attributes_node_templates[0]
+        elif self.properties_capability_templates:
+            capability_template = self.properties_capability_templates[0]
+            return capability_template.node_template
+        elif self.properties_artifact_templates:
+            artifact_template = self.properties_artifact_templates[0]
+            return artifact_template.node_template
+        elif self.properties_relationship_templates:
+            relationship_template = self.properties_relationship_templates[0]
+            requirement_template = relationship_template.requirement_template
+            return requirement_template.node_template
+        # GroupTemplate
+        elif self.properties_group_templates:
+            return self.properties_group_templates[0]
+        # PolicyTemplate
+        elif self.properties_policy_templates:
+            return self.properties_policy_templates[0]
+        # ServiceTemplate
+        elif self.inputs_service_templates:
+            return self.inputs_service_templates[0]
+        elif self.outputs_service_templates:
+            return self.outputs_service_templates[0]
+        # NodeTemplate or GroupTemplate
+        elif self.inputs_interface_templates:
+            interface_template = self.inputs_interface_templates[0]
+            return get_interface_template_container(interface_template)
+        elif self.inputs_operation_templates:
+            operation_template = self.inputs_operation_templates[0]
+            # NodeTemplate or GroupTemplate
+            if operation_template.interface_template:
+                return get_interface_template_container(operation_template.interface_template)
+            # ServiceTemplate
+            elif operation_template.service_template:
+                return operation_template.service_template
+            raise ValueError('operation_template parameter does not have a container: {0}'
+                             .format(self.name))
+
+        raise ValueError('parameter does not have a container: {0}'.format(self.name))
+
+    @property
+    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
+    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):
@@ -65,47 +231,69 @@ class ParameterBase(TemplateModelMixin):
         from . import models
         return models.Parameter(name=self.name,
                                 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))
 
     def unwrap(self):
         return self.name, self.value
 
+    @dsl_specification('3.2.1-2', 'tosca-simple-1.0')
     @classmethod
     def wrap(cls, name, value, description=None):
         """
         Wraps an arbitrary value as a parameter. The type will be guessed via introspection.
 
+        For primitive types, we will prefer their TOSCA aliases. 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>`__
+
         :param name: Parameter name
         :type name: basestring
         :param value: Parameter value
         :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
+        if value is None:
+            type_name = 'null'
+        elif isinstance(value, basestring):
+            type_name = 'string'
+        elif isinstance(value, int):
+            type_name = 'integer'
+        elif isinstance(value, float):
+            type_name = 'float'
+        elif isinstance(value, bool):
+            type_name = 'boolean'
+        elif isinstance(value, datetime.datetime):
+            type_name = 'timestamp'
+        else:
+            type_name = formatting.full_type_name(value)
+        return models.Parameter(name=name,
+                                type_name=type_name,
+                                value=value,
+                                description=description)
 
 
 class TypeBase(InstanceModelMixin):
@@ -188,7 +376,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 +425,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/b7e1836d/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/b7e1836d/aria/modeling/service_template.py
----------------------------------------------------------------------
diff --git a/aria/modeling/service_template.py b/aria/modeling/service_template.py
index 7fab4fc..73025e2 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
@@ -346,16 +346,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()
@@ -426,7 +426,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
@@ -503,6 +503,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')
 
@@ -524,12 +528,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
 
@@ -543,6 +547,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)),
@@ -559,24 +564,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()
@@ -592,6 +607,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')
@@ -715,9 +731,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()
@@ -846,8 +862,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()
@@ -940,8 +956,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()
@@ -1044,7 +1060,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):
@@ -1108,7 +1124,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
@@ -1178,9 +1194,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
 
@@ -1210,18 +1224,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) \
@@ -1242,7 +1256,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,
@@ -1279,9 +1293,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()
@@ -1412,9 +1426,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()
@@ -1538,8 +1552,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
@@ -1569,8 +1583,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()
@@ -1723,9 +1737,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()
@@ -1877,7 +1891,7 @@ class OperationTemplateBase(TemplateModelMixin):
                 plugin = None
                 implementation = None
         else:
-            # using the execution plugin
+            # Using the execution plugin
             plugin = None
             implementation = self.implementation
 
@@ -1898,8 +1912,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()
@@ -2046,8 +2060,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()
@@ -2123,7 +2137,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/b7e1836d/aria/modeling/utils.py
----------------------------------------------------------------------
diff --git a/aria/modeling/utils.py b/aria/modeling/utils.py
index 91d7b9c..c786965 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
 
@@ -109,39 +106,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/b7e1836d/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/b7e1836d/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/b7e1836d/aria/utils/formatting.py
----------------------------------------------------------------------
diff --git a/aria/utils/formatting.py b/aria/utils/formatting.py
index b5e141d..75652a7 100644
--- a/aria/utils/formatting.py
+++ b/aria/utils/formatting.py
@@ -163,8 +163,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/b7e1836d/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/b7e1836d/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/b7e1836d/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/b7e1836d/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/b7e1836d/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/b7e1836d/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/b7e1836d/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/b7e1836d/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..d701a1d 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')
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/b7e1836d/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 405aa8f..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')
-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')
-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)



[07/13] incubator-ariatosca git commit: ARIA-154 Create end-to-end test for the Nodecellar example

Posted by em...@apache.org.
ARIA-154 Create end-to-end test for the Nodecellar example


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

Branch: refs/heads/ARIA-139-attributes
Commit: 8dc7b0060bedf25c5b135b9b014c3a1f87d6aaf2
Parents: d91696b
Author: Ran Ziv <ra...@gigaspaces.com>
Authored: Thu Apr 27 15:11:15 2017 +0300
Committer: Ran Ziv <ra...@gigaspaces.com>
Committed: Thu Apr 27 15:11:15 2017 +0300

----------------------------------------------------------------------
 tests/end2end/test_nodecellar.py                | 42 ++++++++++++++++++++
 .../tosca-simple-1.0/node-cellar/workflows.py   |  5 +--
 2 files changed, 44 insertions(+), 3 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/8dc7b006/tests/end2end/test_nodecellar.py
----------------------------------------------------------------------
diff --git a/tests/end2end/test_nodecellar.py b/tests/end2end/test_nodecellar.py
new file mode 100644
index 0000000..25ce7a0
--- /dev/null
+++ b/tests/end2end/test_nodecellar.py
@@ -0,0 +1,42 @@
+# 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 .testenv import testenv  # pylint: disable=unused-import
+from .. import helpers
+
+
+def test_nodecellar(testenv):
+    nodecellar_template_uri = helpers.get_service_template_uri(
+        'tosca-simple-1.0', 'node-cellar', 'node-cellar.yaml')
+
+    service_name = testenv.install_service(nodecellar_template_uri, dry=True)
+    _verify_deployed_service_in_storage(service_name, testenv.model_storage)
+
+    # testing dry execution of custom workflows
+    testenv.execute_workflow(service_name, 'maintenance_on', dry=True)
+    testenv.execute_workflow(service_name, 'maintenance_off', dry=True)
+
+    testenv.uninstall_service(dry=True)
+    testenv.verify_clean_storage()
+
+
+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]
+    assert service.name == service_name
+    assert len(service.executions) == 0  # dry executions leave no traces
+    assert len(service.nodes) == 10

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/8dc7b006/tests/resources/service-templates/tosca-simple-1.0/node-cellar/workflows.py
----------------------------------------------------------------------
diff --git a/tests/resources/service-templates/tosca-simple-1.0/node-cellar/workflows.py b/tests/resources/service-templates/tosca-simple-1.0/node-cellar/workflows.py
index 94ee824..abe1ee2 100644
--- a/tests/resources/service-templates/tosca-simple-1.0/node-cellar/workflows.py
+++ b/tests/resources/service-templates/tosca-simple-1.0/node-cellar/workflows.py
@@ -1,6 +1,5 @@
-
 from aria import workflow
-from aria.orchestrator.workflows.api.task import OperationTask
+from aria.orchestrator.workflows.builtin import utils
 from aria.orchestrator.workflows.exceptions import TaskException
 
 
@@ -17,7 +16,7 @@ def maintenance(ctx, graph, enabled):
 
     for node in ctx.model.node.iter():
         try:
-            graph.add_tasks(OperationTask.for_node(node=node,
+            graph.add_tasks(utils.create_node_task(node=node,
                                                    interface_name=INTERFACE_NAME,
                                                    operation_name=ENABLE_OPERATION_NAME if enabled
                                                    else DISABLE_OPERATION_NAME))


[09/13] incubator-ariatosca git commit: ARIA-158 Restore support for relationship ordering

Posted by em...@apache.org.
ARIA-158 Restore support for relationship ordering


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

Branch: refs/heads/ARIA-139-attributes
Commit: 1f3e7ff42fefcd5dc8191e3b0eeac0460db059b0
Parents: 5bc28b6
Author: max-orlov <ma...@gigaspaces.com>
Authored: Thu Apr 27 17:09:14 2017 +0300
Committer: max-orlov <ma...@gigaspaces.com>
Committed: Thu Apr 27 17:20:48 2017 +0300

----------------------------------------------------------------------
 aria/modeling/relationship.py     |  8 +++++---
 aria/modeling/service_instance.py | 27 ++++++++++++++++++++-------
 tests/modeling/test_mixins.py     |  7 +++++--
 3 files changed, 30 insertions(+), 12 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/1f3e7ff4/aria/modeling/relationship.py
----------------------------------------------------------------------
diff --git a/aria/modeling/relationship.py b/aria/modeling/relationship.py
index e6830b8..291d08c 100644
--- a/aria/modeling/relationship.py
+++ b/aria/modeling/relationship.py
@@ -14,7 +14,6 @@
 # limitations under the License.
 
 # pylint: disable=invalid-name, redefined-outer-name
-
 from sqlalchemy.orm import relationship, backref
 from sqlalchemy.orm.collections import attribute_mapped_collection
 from sqlalchemy import (
@@ -161,7 +160,8 @@ def one_to_many(model_class,
                 child_table,
                 child_fk=None,
                 dict_key=None,
-                back_populates=None):
+                back_populates=None,
+                rel_kwargs=None):
     """
     Declare a one-to-many relationship property. The property value would be a list or dict of
     instances of the child table's model.
@@ -186,6 +186,8 @@ def one_to_many(model_class,
                            false to disable
     :type back_populates: basestring|bool
     """
+    rel_kwargs = rel_kwargs or {}
+    rel_kwargs.setdefault('cascade', 'all')
     if back_populates is None:
         back_populates = model_class.__tablename__
     return _relationship(
@@ -194,7 +196,7 @@ def one_to_many(model_class,
         back_populates=back_populates,
         other_fk=child_fk,
         dict_key=dict_key,
-        relationship_kwargs=dict(cascade='all'))
+        relationship_kwargs=rel_kwargs)
 
 
 def many_to_one(model_class,

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/1f3e7ff4/aria/modeling/service_instance.py
----------------------------------------------------------------------
diff --git a/aria/modeling/service_instance.py b/aria/modeling/service_instance.py
index 6d8f3fe..d5391ef 100644
--- a/aria/modeling/service_instance.py
+++ b/aria/modeling/service_instance.py
@@ -25,6 +25,7 @@ from sqlalchemy import (
 from sqlalchemy import DateTime
 from sqlalchemy.ext.associationproxy import association_proxy
 from sqlalchemy.ext.declarative import declared_attr
+from sqlalchemy.ext.orderinglist import ordering_list
 
 from .mixins import InstanceModelMixin
 from ..orchestrator import execution_plugin
@@ -485,12 +486,22 @@ class NodeBase(InstanceModelMixin):
     @declared_attr
     def outbound_relationships(cls):
         return relationship.one_to_many(
-            cls, 'relationship', child_fk='source_node_fk', back_populates='source_node')
+            cls, 'relationship', child_fk='source_node_fk', back_populates='source_node',
+            rel_kwargs=dict(
+                order_by='Relationship.source_position',
+                collection_class=ordering_list('source_position', count_from=0)
+            )
+        )
 
     @declared_attr
     def inbound_relationships(cls):
         return relationship.one_to_many(
-            cls, 'relationship', child_fk='target_node_fk', back_populates='target_node')
+            cls, 'relationship', child_fk='target_node_fk', back_populates='target_node',
+            rel_kwargs=dict(
+                order_by='Relationship.target_position',
+                collection_class=ordering_list('target_position', count_from=0)
+            )
+        )
 
     # endregion
 
@@ -1166,9 +1177,9 @@ class RelationshipBase(InstanceModelMixin):
     :vartype properties: {basestring: :class:`Parameter`}
     :ivar interfaces: Bundles of operations
     :vartype interfaces: {basestring: :class:`Interfaces`}
-    :ivar source_position: ??
+    :ivar source_position: The position of the relationship in the outbound relationships.
     :vartype source_position: int
-    :ivar target_position: ??
+    :ivar target_position: The position of the relationship in the inbound relationships.
     :vartype target_position: int
     :ivar source_node: Source node
     :vartype source_node: :class:`Node`
@@ -1185,7 +1196,9 @@ class RelationshipBase(InstanceModelMixin):
                           'target_node_fk',
                           'target_capability_fk',
                           'requirement_template_fk',
-                          'relationship_template_fk']
+                          'relationship_template_fk',
+                          'target_position',
+                          'source_position']
 
     # region foreign keys
 
@@ -1289,8 +1302,8 @@ class RelationshipBase(InstanceModelMixin):
 
     # endregion
 
-    source_position = Column(Integer) # ???
-    target_position = Column(Integer) # ???
+    source_position = Column(Integer)
+    target_position = Column(Integer)
 
     def configure_operations(self):
         for interface in self.interfaces.itervalues():

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/1f3e7ff4/tests/modeling/test_mixins.py
----------------------------------------------------------------------
diff --git a/tests/modeling/test_mixins.py b/tests/modeling/test_mixins.py
index 651f53f..cd7cadf 100644
--- a/tests/modeling/test_mixins.py
+++ b/tests/modeling/test_mixins.py
@@ -155,7 +155,10 @@ def test_relationship_model_ordering(context):
         """
         assert direction in ('inbound', 'outbound')
 
-        relationships = getattr(node, direction + '_relationships')
+        def get_relationships():
+            return getattr(node, direction + '_relationships')
+
+        relationships = get_relationships()
         assert len(relationships) == 2
 
         reversed_relationship = list(reversed(relationships))
@@ -163,7 +166,7 @@ def test_relationship_model_ordering(context):
 
         relationships[:] = reversed_relationship
         context.model.node.update(node)
-        assert relationships == reversed_relationship
+        assert get_relationships() == reversed_relationship
 
     flip_and_assert(source_node, 'outbound')
     flip_and_assert(target_node, 'inbound')


[03/13] incubator-ariatosca git commit: ARIA-138-Make-logging-more-informative

Posted by em...@apache.org.
ARIA-138-Make-logging-more-informative


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

Branch: refs/heads/ARIA-139-attributes
Commit: 29bc84be8cd0f05e1a741d39b6a1b8d3825fe108
Parents: f73c121
Author: max-orlov <ma...@gigaspaces.com>
Authored: Wed Apr 19 17:14:15 2017 +0300
Committer: max-orlov <ma...@gigaspaces.com>
Committed: Thu Apr 20 15:30:03 2017 +0300

----------------------------------------------------------------------
 aria/cli/commands/executions.py                 | 16 +++-
 aria/cli/commands/logs.py                       | 21 ++----
 aria/cli/execution_logging.py                   | 78 ++++++++++++++++++++
 aria/cli/logger.py                              | 18 +++++
 aria/logger.py                                  |  8 +-
 aria/modeling/orchestration.py                  | 11 ++-
 aria/orchestrator/context/common.py             | 47 ++++++------
 aria/orchestrator/context/operation.py          | 29 +-------
 aria/orchestrator/context/workflow.py           |  4 +-
 aria/orchestrator/workflow_runner.py            |  6 +-
 aria/orchestrator/workflows/events_logging.py   | 35 ++++++---
 aria/orchestrator/workflows/executor/base.py    |  4 +-
 aria/orchestrator/workflows/executor/dry.py     | 18 +++--
 aria/orchestrator/workflows/executor/process.py | 15 ++--
 aria/orchestrator/workflows/executor/thread.py  |  8 +-
 aria/storage/core.py                            |  6 +-
 tests/.pylintrc                                 |  2 +-
 .../orchestrator/workflows/executor/__init__.py | 51 +++++++++++++
 .../workflows/executor/test_executor.py         | 64 +++-------------
 .../workflows/executor/test_process_executor.py | 37 +---------
 20 files changed, 280 insertions(+), 198 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/29bc84be/aria/cli/commands/executions.py
----------------------------------------------------------------------
diff --git a/aria/cli/commands/executions.py b/aria/cli/commands/executions.py
index e100f0d..6a1f02a 100644
--- a/aria/cli/commands/executions.py
+++ b/aria/cli/commands/executions.py
@@ -18,6 +18,8 @@ import os
 from .. import helptexts
 from .. import table
 from .. import utils
+from .. import logger as cli_logger
+from .. import execution_logging
 from ..core import aria
 from ...modeling.models import Execution
 from ...orchestrator.workflow_runner import WorkflowRunner
@@ -141,12 +143,19 @@ def start(workflow_name,
 
     logger.info('Starting {0}execution. Press Ctrl+C cancel'.format('dry ' if dry else ''))
     execution_thread.start()
+
+    log_iterator = cli_logger.ModelLogIterator(model_storage, workflow_runner.execution_id)
     try:
         while execution_thread.is_alive():
-            # using join without a timeout blocks and ignores KeyboardInterrupt
+            execution_logging.log_list(log_iterator)
             execution_thread.join(1)
+
     except KeyboardInterrupt:
-        _cancel_execution(workflow_runner, execution_thread, logger)
+        _cancel_execution(workflow_runner, execution_thread, logger, log_iterator)
+
+    # It might be the case where some logs were written and the execution was terminated, thus we
+    # need to drain the remaining logs.
+    execution_logging.log_list(log_iterator)
 
     # raise any errors from the execution thread (note these are not workflow execution errors)
     execution_thread.raise_error_if_exists()
@@ -161,11 +170,12 @@ def start(workflow_name,
         model_storage.execution.delete(execution)
 
 
-def _cancel_execution(workflow_runner, execution_thread, logger):
+def _cancel_execution(workflow_runner, execution_thread, logger, log_iterator):
     logger.info('Cancelling execution. Press Ctrl+C again to force-cancel')
     try:
         workflow_runner.cancel()
         while execution_thread.is_alive():
+            execution_logging.log_list(log_iterator)
             execution_thread.join(1)
     except KeyboardInterrupt:
         logger.info('Force-cancelling execution')

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/29bc84be/aria/cli/commands/logs.py
----------------------------------------------------------------------
diff --git a/aria/cli/commands/logs.py b/aria/cli/commands/logs.py
index 6c83347..79aff07 100644
--- a/aria/cli/commands/logs.py
+++ b/aria/cli/commands/logs.py
@@ -12,13 +12,12 @@
 # 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 .. import utils
+from .. import execution_logging
+from ..logger import ModelLogIterator
 from ..core import aria
 
 
 @aria.group(name='logs')
-@aria.options.verbose()
 def logs():
     """Show logs from workflow executions
     """
@@ -31,19 +30,15 @@ def logs():
 @aria.options.verbose()
 @aria.pass_model_storage
 @aria.pass_logger
-def list(execution_id,
-         model_storage,
-         logger):
+def list(execution_id, model_storage, logger):
     """Display logs for an execution
     """
     logger.info('Listing logs for execution id {0}'.format(execution_id))
-    logs_list = model_storage.log.list(filters=dict(execution_fk=execution_id),
-                                       sort=utils.storage_sort_param('created_at', False))
-    # TODO: print logs nicely
-    if logs_list:
-        for log in logs_list:
-            logger.info(log)
-    else:
+    log_iterator = ModelLogIterator(model_storage, execution_id)
+
+    any_logs = execution_logging.log_list(log_iterator)
+
+    if not any_logs:
         logger.info('\tNo logs')
 
 

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/29bc84be/aria/cli/execution_logging.py
----------------------------------------------------------------------
diff --git a/aria/cli/execution_logging.py b/aria/cli/execution_logging.py
new file mode 100644
index 0000000..8baf6d7
--- /dev/null
+++ b/aria/cli/execution_logging.py
@@ -0,0 +1,78 @@
+# 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 StringIO import StringIO
+
+from . import logger
+from .env import env
+
+DEFAULT_FORMATTING = {
+    logger.NO_VERBOSE: {'message': '{item.msg}'},
+    logger.LOW_VERBOSE: {
+        'message': '{timestamp} | {item.level[0]} | {item.msg}',
+        'timestamp': '%H:%M:%S'
+    },
+    logger.MEDIUM_VERBOSE: {
+        'message': '{timestamp} | {item.level[0]} | {implementation} | {item.msg} ',
+        'timestamp': '%H:%M:%S'
+    },
+    logger.HIGH_VERBOSE: {
+        'message': '{timestamp} | {item.level[0]} | {implementation}({inputs}) | {item.msg} ',
+        'timestamp': '%H:%M:%S'
+    },
+}
+
+
+def _str(item, formats=None):
+    # If no formats are passed we revert to the default formats (per level)
+    formats = formats or {}
+    formatting = formats.get(env.logging.verbosity_level,
+                             DEFAULT_FORMATTING[env.logging.verbosity_level])
+    msg = StringIO()
+
+    formatting_kwargs = dict(item=item)
+
+    if item.task:
+        formatting_kwargs['implementation'] = item.task.implementation
+        formatting_kwargs['inputs'] = dict(i.unwrap() for i in item.task.inputs.values())
+    else:
+        formatting_kwargs['implementation'] = item.execution.workflow_name
+        formatting_kwargs['inputs'] = dict(i.unwrap() for i in item.execution.inputs.values())
+
+    if 'timestamp' in formatting:
+        formatting_kwargs['timestamp'] = item.created_at.strftime(formatting['timestamp'])
+    else:
+        formatting_kwargs['timestamp'] = item.created_at
+
+    msg.write(formatting['message'].format(**formatting_kwargs))
+
+    # Add the exception and the error msg.
+    if item.traceback and env.logging.verbosity_level >= logger.MEDIUM_VERBOSE:
+        for line in item.traceback.splitlines(True):
+            msg.write('\t' + '|' + line)
+
+    return msg.getvalue()
+
+
+def log(item, *args, **kwargs):
+    return getattr(env.logging.logger, item.level.lower())(_str(item), *args, **kwargs)
+
+
+def log_list(iterator):
+    any_logs = False
+    for item in iterator:
+        log(item)
+        any_logs = True
+    return any_logs

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/29bc84be/aria/cli/logger.py
----------------------------------------------------------------------
diff --git a/aria/cli/logger.py b/aria/cli/logger.py
index 1ffa918..c240f02 100644
--- a/aria/cli/logger.py
+++ b/aria/cli/logger.py
@@ -112,3 +112,21 @@ class Logging(object):
             log.setLevel(level)
 
         dictconfig.dictConfig(logger_dict)
+
+
+class ModelLogIterator(object):
+
+    def __init__(self, model_storage, execution_id, filters=None, sort=None):
+        self._last_visited_id = 0
+        self._model_storage = model_storage
+        self._execution_id = execution_id
+        self._additional_filters = filters or {}
+        self._sort = sort or {}
+
+    def __iter__(self):
+        filters = dict(execution_fk=self._execution_id, id=dict(gt=self._last_visited_id))
+        filters.update(self._additional_filters)
+
+        for log in self._model_storage.log.iter(filters=filters, sort=self._sort):
+            self._last_visited_id = log.id
+            yield log

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/29bc84be/aria/logger.py
----------------------------------------------------------------------
diff --git a/aria/logger.py b/aria/logger.py
index dd54264..8e15f5b 100644
--- a/aria/logger.py
+++ b/aria/logger.py
@@ -51,6 +51,10 @@ class LoggerMixin(object):
     def __init__(self, *args, **kwargs):
         self.logger_name = self.logger_name or self.__class__.__name__
         self.logger = logging.getLogger('{0}.{1}'.format(_base_logger.name, self.logger_name))
+        # Set the logger handler of any object derived from LoggerMixing to NullHandler.
+        # This is since the absence of a handler shows up while using the CLI in the form of:
+        # `No handlers could be found for logger "..."`.
+        self.logger.addHandler(NullHandler())
         self.logger.setLevel(self.logger_level)
         super(LoggerMixin, self).__init__(*args, **kwargs)
 
@@ -177,10 +181,12 @@ class _SQLAlchemyHandler(logging.Handler):
         log = self._cls(
             execution_fk=self._execution_id,
             task_fk=record.task_id,
-            actor=record.prefix,
             level=record.levelname,
             msg=str(record.msg),
             created_at=created_at,
+
+            # Not mandatory.
+            traceback=getattr(record, 'traceback', None)
         )
         self._session.add(log)
 

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/29bc84be/aria/modeling/orchestration.py
----------------------------------------------------------------------
diff --git a/aria/modeling/orchestration.py b/aria/modeling/orchestration.py
index 01ab2e8..b9a75e9 100644
--- a/aria/modeling/orchestration.py
+++ b/aria/modeling/orchestration.py
@@ -413,7 +413,9 @@ class LogBase(ModelMixin):
     level = Column(String)
     msg = Column(String)
     created_at = Column(DateTime, index=True)
-    actor = Column(String)
+
+    # In case of failed execution
+    traceback = Column(Text)
 
     # region foreign keys
 
@@ -427,6 +429,9 @@ class LogBase(ModelMixin):
 
     # endregion
 
+    def __str__(self):
+        return self.msg
+
     def __repr__(self):
-        return "<{self.created_at}: [{self.level}] @{self.actor}> {msg}".format(
-            self=self, msg=self.msg[:50])
+        name = (self.task.actor if self.task else self.execution).name
+        return '{name}: {self.msg}'.format(name=name, self=self)

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/29bc84be/aria/orchestrator/context/common.py
----------------------------------------------------------------------
diff --git a/aria/orchestrator/context/common.py b/aria/orchestrator/context/common.py
index 15843db..64ef9a4 100644
--- a/aria/orchestrator/context/common.py
+++ b/aria/orchestrator/context/common.py
@@ -38,43 +38,44 @@ class BaseContext(object):
     """
 
     class PrefixedLogger(object):
-        def __init__(self, logger, prefix='', task_id=None):
-            self._logger = logger
-            self._prefix = prefix
+        def __init__(self, base_logger, task_id=None):
+            self._logger = base_logger
             self._task_id = task_id
 
-        def __getattr__(self, item):
-            if item.upper() in logging._levelNames:
-                return partial(getattr(self._logger, item),
-                               extra={'prefix': self._prefix, 'task_id': self._task_id})
+        def __getattr__(self, attribute):
+            if attribute.upper() in logging._levelNames:
+                return partial(self._logger_with_task_id, _level=attribute)
             else:
-                return getattr(self._logger, item)
-
-    def __init__(
-            self,
-            name,
-            service_id,
-            execution_id,
-            model_storage,
-            resource_storage,
-            workdir=None,
-            **kwargs):
+                return getattr(self._logger, attribute)
+
+        def _logger_with_task_id(self, *args, **kwargs):
+            level = kwargs.pop('_level')
+            kwargs.setdefault('extra', {})['task_id'] = self._task_id
+            return getattr(self._logger, level)(*args, **kwargs)
+
+    def __init__(self,
+                 name,
+                 service_id,
+                 model_storage,
+                 resource_storage,
+                 execution_id,
+                 workdir=None,
+                 **kwargs):
         super(BaseContext, self).__init__(**kwargs)
         self._name = name
         self._id = generate_uuid(variant='uuid')
         self._model = model_storage
         self._resource = resource_storage
         self._service_id = service_id
-        self._execution_id = execution_id
         self._workdir = workdir
+        self._execution_id = execution_id
         self.logger = None
 
     def _register_logger(self, level=None, task_id=None):
         self.logger = self.PrefixedLogger(
-            logging.getLogger(aria_logger.TASK_LOGGER_NAME), self.logging_id, task_id=task_id)
+            logging.getLogger(aria_logger.TASK_LOGGER_NAME), task_id=task_id)
         self.logger.setLevel(level or logging.DEBUG)
         if not self.logger.handlers:
-            self.logger.addHandler(aria_logger.create_console_log_handler())
             self.logger.addHandler(self._get_sqla_handler())
 
     def _get_sqla_handler(self):
@@ -104,10 +105,6 @@ class BaseContext(object):
                 self.logger.removeHandler(handler)
 
     @property
-    def logging_id(self):
-        raise NotImplementedError
-
-    @property
     def model(self):
         """
         Access to the model storage

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/29bc84be/aria/orchestrator/context/operation.py
----------------------------------------------------------------------
diff --git a/aria/orchestrator/context/operation.py b/aria/orchestrator/context/operation.py
index c7d8246..c383958 100644
--- a/aria/orchestrator/context/operation.py
+++ b/aria/orchestrator/context/operation.py
@@ -29,20 +29,8 @@ class BaseOperationContext(BaseContext):
     Context object used during operation creation and execution
     """
 
-    def __init__(self,
-                 name,
-                 model_storage,
-                 resource_storage,
-                 service_id,
-                 task_id,
-                 actor_id,
-                 **kwargs):
-        super(BaseOperationContext, self).__init__(
-            name=name,
-            model_storage=model_storage,
-            resource_storage=resource_storage,
-            service_id=service_id,
-            **kwargs)
+    def __init__(self, task_id, actor_id, **kwargs):
+        super(BaseOperationContext, self).__init__(**kwargs)
         self._task_id = task_id
         self._actor_id = actor_id
         self._thread_local = threading.local()
@@ -55,10 +43,6 @@ class BaseOperationContext(BaseContext):
         return '{name}({0})'.format(details, name=self.name)
 
     @property
-    def logging_id(self):
-        raise NotImplementedError
-
-    @property
     def task(self):
         """
         The task in the model storage
@@ -119,10 +103,6 @@ class NodeOperationContext(BaseOperationContext):
     """
 
     @property
-    def logging_id(self):
-        return self.node.name or self.node.id
-
-    @property
     def node_template(self):
         """
         the node of the current operation
@@ -145,11 +125,6 @@ class RelationshipOperationContext(BaseOperationContext):
     """
 
     @property
-    def logging_id(self):
-        return '{0}->{1}'.format(self.source_node.name or self.source_node.id,
-                                 self.target_node.name or self.target_node.id)
-
-    @property
     def source_node_template(self):
         """
         The source node

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/29bc84be/aria/orchestrator/context/workflow.py
----------------------------------------------------------------------
diff --git a/aria/orchestrator/context/workflow.py b/aria/orchestrator/context/workflow.py
index 667d22f..920b237 100644
--- a/aria/orchestrator/context/workflow.py
+++ b/aria/orchestrator/context/workflow.py
@@ -50,8 +50,8 @@ class WorkflowContext(BaseContext):
                 name=self.__class__.__name__, self=self))
 
     @property
-    def logging_id(self):
-        return '{0}[{1}]'.format(self._workflow_name, self._execution_id)
+    def workflow_name(self):
+        return self._workflow_name
 
     @property
     def execution(self):

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/29bc84be/aria/orchestrator/workflow_runner.py
----------------------------------------------------------------------
diff --git a/aria/orchestrator/workflow_runner.py b/aria/orchestrator/workflow_runner.py
index 1ea60a1..8f25cce 100644
--- a/aria/orchestrator/workflow_runner.py
+++ b/aria/orchestrator/workflow_runner.py
@@ -90,8 +90,12 @@ class WorkflowRunner(object):
             tasks_graph=self._tasks_graph)
 
     @property
+    def execution_id(self):
+        return self._execution_id
+
+    @property
     def execution(self):
-        return self._model_storage.execution.get(self._execution_id)
+        return self._model_storage.execution.get(self.execution_id)
 
     @property
     def service(self):

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/29bc84be/aria/orchestrator/workflows/events_logging.py
----------------------------------------------------------------------
diff --git a/aria/orchestrator/workflows/events_logging.py b/aria/orchestrator/workflows/events_logging.py
index e831bfe..7d15c81 100644
--- a/aria/orchestrator/workflows/events_logging.py
+++ b/aria/orchestrator/workflows/events_logging.py
@@ -22,45 +22,56 @@ Implementation of logger handlers for workflow and operation events.
 """
 
 from .. import events
+from ... import modeling
+
+
+def _get_task_name(task):
+    if isinstance(task.actor, modeling.model_bases.service_instance.RelationshipBase):
+        return '{source_node.name}->{target_node.name}'.format(
+            source_node=task.actor.source_node, target_node=task.actor.target_node)
+    else:
+        return task.actor.name
 
 
 @events.start_task_signal.connect
 def _start_task_handler(task, **kwargs):
-    task.context.logger.debug('Event: Starting task: {task.name}'.format(task=task))
+    task.context.logger.info('{name} {task.interface_name}.{task.operation_name} started...'
+                             .format(name=_get_task_name(task), task=task))
 
 
 @events.on_success_task_signal.connect
 def _success_task_handler(task, **kwargs):
-    task.context.logger.debug('Event: Task success: {task.name}'.format(task=task))
+    task.context.logger.info('{name} {task.interface_name}.{task.operation_name} successful'
+                             .format(name=_get_task_name(task), task=task))
 
 
 @events.on_failure_task_signal.connect
-def _failure_operation_handler(task, exception, **kwargs):
-    error = '{0}: {1}'.format(type(exception).__name__, exception)
-    task.context.logger.error('Event: Task failure: {task.name} [{error}]'.format(
-        task=task, error=error))
-
+def _failure_operation_handler(task, traceback, **kwargs):
+    task.context.logger.error(
+        '{name} {task.interface_name}.{task.operation_name} failed'
+        .format(name=_get_task_name(task), task=task), extra=dict(traceback=traceback)
+    )
 
 @events.start_workflow_signal.connect
 def _start_workflow_handler(context, **kwargs):
-    context.logger.debug('Event: Starting workflow: {context.name}'.format(context=context))
+    context.logger.info("Starting '{ctx.workflow_name}' workflow execution".format(ctx=context))
 
 
 @events.on_failure_workflow_signal.connect
 def _failure_workflow_handler(context, **kwargs):
-    context.logger.debug('Event: Workflow failure: {context.name}'.format(context=context))
+    context.logger.info("'{ctx.workflow_name}' workflow execution failed".format(ctx=context))
 
 
 @events.on_success_workflow_signal.connect
 def _success_workflow_handler(context, **kwargs):
-    context.logger.debug('Event: Workflow success: {context.name}'.format(context=context))
+    context.logger.info("'{ctx.workflow_name}' workflow execution succeeded".format(ctx=context))
 
 
 @events.on_cancelled_workflow_signal.connect
 def _cancel_workflow_handler(context, **kwargs):
-    context.logger.debug('Event: Workflow cancelled: {context.name}'.format(context=context))
+    context.logger.info("'{ctx.workflow_name}' workflow execution canceled".format(ctx=context))
 
 
 @events.on_cancelling_workflow_signal.connect
 def _cancelling_workflow_handler(context, **kwargs):
-    context.logger.debug('Event: Workflow cancelling: {context.name}'.format(context=context))
+    context.logger.info("Cancelling '{ctx.workflow_name}' workflow execution".format(ctx=context))

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/29bc84be/aria/orchestrator/workflows/executor/base.py
----------------------------------------------------------------------
diff --git a/aria/orchestrator/workflows/executor/base.py b/aria/orchestrator/workflows/executor/base.py
index 4ae046d..39becef 100644
--- a/aria/orchestrator/workflows/executor/base.py
+++ b/aria/orchestrator/workflows/executor/base.py
@@ -44,8 +44,8 @@ class BaseExecutor(logger.LoggerMixin):
         events.start_task_signal.send(task)
 
     @staticmethod
-    def _task_failed(task, exception):
-        events.on_failure_task_signal.send(task, exception=exception)
+    def _task_failed(task, exception, traceback=None):
+        events.on_failure_task_signal.send(task, exception=exception, traceback=traceback)
 
     @staticmethod
     def _task_succeeded(task):

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/29bc84be/aria/orchestrator/workflows/executor/dry.py
----------------------------------------------------------------------
diff --git a/aria/orchestrator/workflows/executor/dry.py b/aria/orchestrator/workflows/executor/dry.py
index d894b25..e1261bb 100644
--- a/aria/orchestrator/workflows/executor/dry.py
+++ b/aria/orchestrator/workflows/executor/dry.py
@@ -34,15 +34,19 @@ class DryExecutor(BaseExecutor):
             task.started_at = datetime.utcnow()
             task.status = task.STARTED
 
-        actor_type = type(task.actor).__name__.lower()
-        implementation = '{0} > '.format(task.plugin) if task.plugin else ''
-        implementation += task.implementation
-        inputs = dict(inp.unwrap() for inp in task.inputs.values())
+        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(
-            'Executing {actor_type} {task.actor.name} operation {task.interface_name} '
-            '{task.operation_name}: {implementation} (Inputs: {inputs})'
-            .format(actor_type=actor_type, task=task, implementation=implementation, inputs=inputs))
+            '<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))
 
         # 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/29bc84be/aria/orchestrator/workflows/executor/process.py
----------------------------------------------------------------------
diff --git a/aria/orchestrator/workflows/executor/process.py b/aria/orchestrator/workflows/executor/process.py
index 851d78e..2378e0a 100644
--- a/aria/orchestrator/workflows/executor/process.py
+++ b/aria/orchestrator/workflows/executor/process.py
@@ -42,13 +42,16 @@ import pickle
 import jsonpickle
 
 import aria
-from aria.extension import process_executor
-from aria.utils import imports
-from aria.utils import exceptions
 from aria.orchestrator.workflows.executor import base
 from aria.storage import instrumentation
+from aria.extension import process_executor
+from aria.utils import (
+    imports,
+    exceptions
+)
 from aria.modeling import types as modeling_types
 
+
 _IS_WIN = os.name == 'nt'
 
 _INT_FMT = 'I'
@@ -233,9 +236,10 @@ class ProcessExecutor(base.BaseExecutor):
         except BaseException as e:
             e.message += 'Task failed due to {0}.'.format(request['exception']) + \
                          UPDATE_TRACKED_CHANGES_FAILED_STR
-            self._task_failed(task, exception=e)
+            self._task_failed(
+                task, exception=e, traceback=exceptions.get_exception_as_string(*sys.exc_info()))
         else:
-            self._task_failed(task, exception=request['exception'])
+            self._task_failed(task, exception=request['exception'], traceback=request['traceback'])
 
     def _handle_apply_tracked_changes_request(self, task_id, request, response):
         task = self._tasks[task_id]
@@ -319,6 +323,7 @@ class _Messenger(object):
                 'type': type,
                 'task_id': self.task_id,
                 'exception': exceptions.wrap_if_needed(exception),
+                'traceback': exceptions.get_exception_as_string(*sys.exc_info()),
                 'tracked_changes': tracked_changes
             })
             response = _recv_message(sock)

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/29bc84be/aria/orchestrator/workflows/executor/thread.py
----------------------------------------------------------------------
diff --git a/aria/orchestrator/workflows/executor/thread.py b/aria/orchestrator/workflows/executor/thread.py
index f422592..836b2bf 100644
--- a/aria/orchestrator/workflows/executor/thread.py
+++ b/aria/orchestrator/workflows/executor/thread.py
@@ -20,7 +20,9 @@ Thread based executor
 import Queue
 import threading
 
-from aria.utils import imports
+import sys
+
+from aria.utils import imports, exceptions
 
 from .base import BaseExecutor
 
@@ -63,7 +65,9 @@ class ThreadExecutor(BaseExecutor):
                     task_func(ctx=task.context, **inputs)
                     self._task_succeeded(task)
                 except BaseException as e:
-                    self._task_failed(task, exception=e)
+                    self._task_failed(task,
+                                      exception=e,
+                                      traceback=exceptions.get_exception_as_string(*sys.exc_info()))
             # Daemon threads
             except BaseException as e:
                 pass

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/29bc84be/aria/storage/core.py
----------------------------------------------------------------------
diff --git a/aria/storage/core.py b/aria/storage/core.py
index 8caca66..8302fc9 100644
--- a/aria/storage/core.py
+++ b/aria/storage/core.py
@@ -38,7 +38,7 @@ API:
     * StorageDriver - class, abstract model implementation.
 """
 
-from aria.logger import LoggerMixin, NullHandler
+from aria.logger import LoggerMixin
 from . import sql_mapi
 
 __all__ = (
@@ -71,10 +71,6 @@ class Storage(LoggerMixin):
         :param kwargs:
         """
         super(Storage, self).__init__(**kwargs)
-        # Set the logger handler of any storage object to NullHandler.
-        # This is since the absence of a handler shows up while using the CLI in the form of:
-        # `No handlers could be found for logger "aria.ResourceStorage"`.
-        self.logger.addHandler(NullHandler())
         self.api = api_cls
         self.registered = {}
         self._initiator = initiator

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/29bc84be/tests/.pylintrc
----------------------------------------------------------------------
diff --git a/tests/.pylintrc b/tests/.pylintrc
index eead6e8..9795bfc 100644
--- a/tests/.pylintrc
+++ b/tests/.pylintrc
@@ -369,7 +369,7 @@ max-statements=50
 max-parents=7
 
 # Maximum number of attributes for a class (see R0902).
-max-attributes=15
+max-attributes=25
 
 # Minimum number of public methods for a class (see R0903).
 min-public-methods=0

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/29bc84be/tests/orchestrator/workflows/executor/__init__.py
----------------------------------------------------------------------
diff --git a/tests/orchestrator/workflows/executor/__init__.py b/tests/orchestrator/workflows/executor/__init__.py
index ae1e83e..c05831a 100644
--- a/tests/orchestrator/workflows/executor/__init__.py
+++ b/tests/orchestrator/workflows/executor/__init__.py
@@ -12,3 +12,54 @@
 # 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 uuid
+import logging
+from collections import namedtuple
+from contextlib import contextmanager
+
+from aria.modeling import models
+
+
+class MockTask(object):
+
+    INFINITE_RETRIES = models.Task.INFINITE_RETRIES
+
+    def __init__(self, implementation, inputs=None, plugin=None):
+        self.implementation = self.name = implementation
+        self.plugin_fk = plugin.id if plugin else None
+        self.plugin = plugin or None
+        self.inputs = inputs or {}
+        self.states = []
+        self.exception = None
+        self.id = str(uuid.uuid4())
+        self.logger = logging.getLogger()
+        self.context = MockContext()
+        self.retry_count = 0
+        self.max_attempts = 1
+        self.ignore_failure = False
+        self.interface_name = 'interface_name'
+        self.operation_name = 'operation_name'
+        self.actor = namedtuple('actor', 'name')(name='actor_name')
+        self.model_task = None
+
+        for state in models.Task.STATES:
+            setattr(self, state.upper(), state)
+
+    @contextmanager
+    def _update(self):
+        yield self
+
+
+class MockContext(object):
+
+    def __init__(self):
+        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()

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/29bc84be/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 a7619de..d4482ae 100644
--- a/tests/orchestrator/workflows/executor/test_executor.py
+++ b/tests/orchestrator/workflows/executor/test_executor.py
@@ -13,9 +13,6 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import logging
-import uuid
-from contextlib import contextmanager
 
 import pytest
 import retrying
@@ -37,14 +34,19 @@ from aria.orchestrator.workflows.executor import (
 )
 
 import tests
+from . import MockTask
+
+
+def _get_implementation(func):
+    return '{module}.{func.__name__}'.format(module=__name__, func=func)
 
 
 def test_execute(executor):
     expected_value = 'value'
-    successful_task = MockTask(mock_successful_task)
-    failing_task = MockTask(mock_failing_task)
-    task_with_inputs = MockTask(mock_task_with_input, inputs={'input': models.Parameter.wrap(
-        'input', 'value')})
+    successful_task = MockTask(_get_implementation(mock_successful_task))
+    failing_task = MockTask(_get_implementation(mock_failing_task))
+    task_with_inputs = MockTask(_get_implementation(mock_task_with_input),
+                                inputs={'input': models.Parameter.wrap('input', 'value')})
 
     for task in [successful_task, failing_task, task_with_inputs]:
         executor.execute(task)
@@ -81,54 +83,6 @@ class MockException(Exception):
     pass
 
 
-class MockContext(object):
-
-    def __init__(self, *args, **kwargs):
-        self.logger = logging.getLogger()
-        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()
-
-
-class MockTask(object):
-
-    INFINITE_RETRIES = models.Task.INFINITE_RETRIES
-
-    def __init__(self, func, inputs=None):
-        self.states = []
-        self.exception = None
-        self.id = str(uuid.uuid4())
-        name = func.__name__
-        implementation = '{module}.{name}'.format(
-            module=__name__,
-            name=name)
-        self.implementation = implementation
-        self.logger = logging.getLogger()
-        self.name = name
-        self.inputs = inputs or {}
-        self.context = MockContext()
-        self.retry_count = 0
-        self.max_attempts = 1
-        self.plugin_fk = None
-        self.ignore_failure = False
-        self.interface_name = 'interface_name'
-        self.operation_name = 'operation_name'
-        self.model_task = None
-
-        for state in models.Task.STATES:
-            setattr(self, state.upper(), state)
-
-    @contextmanager
-    def _update(self):
-        yield self
-
-
 @pytest.fixture(params=[
     (thread.ThreadExecutor, {'pool_size': 1}),
     (thread.ThreadExecutor, {'pool_size': 2}),

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/29bc84be/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 839b9f1..b353518 100644
--- a/tests/orchestrator/workflows/executor/test_process_executor.py
+++ b/tests/orchestrator/workflows/executor/test_process_executor.py
@@ -15,13 +15,10 @@
 
 import logging
 import os
-import uuid
 import Queue
-from contextlib import contextmanager
 
 import pytest
 
-from aria.modeling import models as aria_models
 from aria.orchestrator import events
 from aria.utils.plugin import create as create_plugin
 from aria.orchestrator.workflows.executor import process
@@ -33,17 +30,17 @@ from tests.fixtures import (  # pylint: disable=unused-import
     plugin_manager,
     fs_model as model
 )
+from . import MockTask
 
 
 class TestProcessExecutor(object):
 
     def test_plugin_execution(self, executor, mock_plugin):
-        task = MockTask(plugin=mock_plugin,
-                        implementation='mock_plugin1.operation')
+        task = MockTask('mock_plugin1.operation', plugin=mock_plugin)
 
         queue = Queue.Queue()
 
-        def handler(_, exception=None):
+        def handler(_, exception=None, **kwargs):
             queue.put(exception)
 
         events.on_success_task_signal.connect(handler)
@@ -100,31 +97,3 @@ class MockContext(object):
     @classmethod
     def deserialize_from_dict(cls, **kwargs):
         return cls()
-
-
-class MockTask(object):
-
-    INFINITE_RETRIES = aria_models.Task.INFINITE_RETRIES
-
-    def __init__(self, plugin, implementation):
-        self.id = str(uuid.uuid4())
-        self.implementation = implementation
-        self.logger = logging.getLogger()
-        self.name = implementation
-        self.inputs = {}
-        self.context = MockContext()
-        self.retry_count = 0
-        self.max_attempts = 1
-        self.plugin_fk = plugin.id
-        self.plugin = plugin
-        self.ignore_failure = False
-        self.interface_name = 'interface_name'
-        self.operation_name = 'operation_name'
-        self.model_task = None
-
-        for state in aria_models.Task.STATES:
-            setattr(self, state.upper(), state)
-
-    @contextmanager
-    def _update(self):
-        yield self



[11/13] incubator-ariatosca git commit: ARIA-161 Disable end2end tests on Windows

Posted by em...@apache.org.
ARIA-161 Disable end2end tests on Windows


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

Branch: refs/heads/ARIA-139-attributes
Commit: 08785262e21b14c3c124c6d6a8217b8faac1c5f7
Parents: d0c5e6f
Author: Ran Ziv <ra...@gigaspaces.com>
Authored: Sun Apr 30 15:40:57 2017 +0300
Committer: Ran Ziv <ra...@gigaspaces.com>
Committed: Sun Apr 30 16:05:21 2017 +0300

----------------------------------------------------------------------
 tox.ini | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/08785262/tox.ini
----------------------------------------------------------------------
diff --git a/tox.ini b/tox.ini
index 4d86c6e..f4e9871 100644
--- a/tox.ini
+++ b/tox.ini
@@ -46,7 +46,7 @@ commands=pytest tests/end2end --cov-report term-missing --cov aria
 commands=pytest tests/end2end --cov-report term-missing --cov aria
 
 [testenv:pywin]
-commands=pytest tests --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


[02/13] incubator-ariatosca git commit: ARIA-145 deploying CLI config file at installation

Posted by em...@apache.org.
ARIA-145 deploying CLI config file at installation


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

Branch: refs/heads/ARIA-139-attributes
Commit: f73c121a3d74c5fd08ec71de194b0646f5949e5e
Parents: 1321d27
Author: Ran Ziv <ra...@gigaspaces.com>
Authored: Thu Apr 20 15:00:30 2017 +0300
Committer: Ran Ziv <ra...@gigaspaces.com>
Committed: Thu Apr 20 15:00:30 2017 +0300

----------------------------------------------------------------------
 setup.py | 3 +++
 1 file changed, 3 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/f73c121a/setup.py
----------------------------------------------------------------------
diff --git a/setup.py b/setup.py
index b64453a..c0ad75b 100644
--- a/setup.py
+++ b/setup.py
@@ -127,6 +127,9 @@ setup(
                            include=['{0}*'.format(name) for name in _EXTENSION_NAMES]),
     package_dir=dict((name, '{0}/{1}'.format(_EXTENSION_DIR, name)) for name in _EXTENSION_NAMES),
     package_data={
+        'aria': [
+            'cli/config/config_template.yaml'
+        ],
         'aria_extension_tosca': [
             'profiles/tosca-simple-1.0/**',
             'profiles/tosca-simple-nfv-1.0/**',


[05/13] incubator-ariatosca git commit: Improve execution cancelling

Posted by em...@apache.org.
Improve execution cancelling

Unhandled execution status transitions resulting from cancelling an
execution via the CLI, that we indentified and tried to address:

1. TERMINATED -> CANCELLING
You cancel the execution, but by the time we try to set the status to
CANCELLING, the execution thread had already finished, and therefore,
in SUCCEEDED status.

2. FAILED -> CANCELLING
You cancel the execution, but by the time we try to set the status to
CANCELLING, the execution thread had already encountered an error,
and therefore, in FAILED state.

3. TERMINATED -> CANCELLED
Similar to #1, but with CANCELLED instead of CANCELLING.

4. FAILED -> CANCELLED
Similar to #1, but with CANCELLED instead of CANCELLING.

In all of the above cases (#1-#4), we skip updating the execution
status, and log that the execution already succeeded/failed before we
were able to cancel it.

5. CANCELLING -> STARTED
You cancel the execution while it is still in pending state. Meanwhile,
while the execution status was already set to CANCELLING, we try to set
the execution status

6. CANCELLED -> STARTED
Similar to #5, but after the status is set to CANCELLING, it also gets
set to CANCELLED before attempting to set it to STARTED.

In cases #5-#6, we skip updtating the execution status, and nothing
is logged.


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

Branch: refs/heads/ARIA-139-attributes
Commit: 5cd7aec550a4720fb63fa8b36c27ff52921d0b14
Parents: 7a4a1dd
Author: Avia Efrat <av...@gigaspaces.com>
Authored: Thu Apr 20 13:23:32 2017 +0300
Committer: Avia Efrat <av...@gigaspaces.com>
Committed: Wed Apr 26 15:37:06 2017 +0300

----------------------------------------------------------------------
 aria/cli/commands/executions.py                 | 12 +++-----
 aria/modeling/orchestration.py                  |  9 +++---
 .../workflows/core/events_handler.py            | 32 +++++++++++++++-----
 aria/orchestrator/workflows/events_logging.py   |  1 +
 tests/modeling/test_models.py                   | 22 +++++++-------
 tests/orchestrator/test_workflow_runner.py      |  2 +-
 .../orchestrator/workflows/core/test_engine.py  |  2 +-
 7 files changed, 47 insertions(+), 33 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/5cd7aec5/aria/cli/commands/executions.py
----------------------------------------------------------------------
diff --git a/aria/cli/commands/executions.py b/aria/cli/commands/executions.py
index 6a1f02a..cb86376 100644
--- a/aria/cli/commands/executions.py
+++ b/aria/cli/commands/executions.py
@@ -139,7 +139,6 @@ def start(workflow_name,
     execution_thread_name = '{0}_{1}'.format(service_name, workflow_name)
     execution_thread = threading.ExceptionThread(target=workflow_runner.execute,
                                                  name=execution_thread_name)
-    execution_thread.daemon = True  # allows force-cancel to exit immediately
 
     logger.info('Starting {0}execution. Press Ctrl+C cancel'.format('dry ' if dry else ''))
     execution_thread.start()
@@ -172,11 +171,10 @@ def start(workflow_name,
 
 def _cancel_execution(workflow_runner, execution_thread, logger, log_iterator):
     logger.info('Cancelling execution. Press Ctrl+C again to force-cancel')
-    try:
-        workflow_runner.cancel()
-        while execution_thread.is_alive():
+    workflow_runner.cancel()
+    while execution_thread.is_alive():
+        try:
             execution_logging.log_list(log_iterator)
             execution_thread.join(1)
-    except KeyboardInterrupt:
-        logger.info('Force-cancelling execution')
-        # TODO handle execution (update status etc.) and exit process
+        except KeyboardInterrupt:
+            pass

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/5cd7aec5/aria/modeling/orchestration.py
----------------------------------------------------------------------
diff --git a/aria/modeling/orchestration.py b/aria/modeling/orchestration.py
index b9a75e9..f163903 100644
--- a/aria/modeling/orchestration.py
+++ b/aria/modeling/orchestration.py
@@ -56,21 +56,20 @@ class ExecutionBase(ModelMixin):
     __private_fields__ = ['service_fk',
                           'service_template']
 
-    TERMINATED = 'terminated'
+    SUCCEEDED = 'succeeded'
     FAILED = 'failed'
     CANCELLED = 'cancelled'
     PENDING = 'pending'
     STARTED = 'started'
     CANCELLING = 'cancelling'
-    FORCE_CANCELLING = 'force_cancelling'
 
-    STATES = (TERMINATED, FAILED, CANCELLED, PENDING, STARTED, CANCELLING, FORCE_CANCELLING)
-    END_STATES = (TERMINATED, FAILED, CANCELLED)
+    STATES = (SUCCEEDED, FAILED, CANCELLED, PENDING, STARTED, CANCELLING)
+    END_STATES = (SUCCEEDED, FAILED, CANCELLED)
 
     VALID_TRANSITIONS = {
         PENDING: (STARTED, CANCELLED),
         STARTED: END_STATES + (CANCELLING,),
-        CANCELLING: END_STATES + (FORCE_CANCELLING,)
+        CANCELLING: END_STATES
     }
 
     @orm.validates('status')

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/5cd7aec5/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 7f61bfa..b43082b 100644
--- a/aria/orchestrator/workflows/core/events_handler.py
+++ b/aria/orchestrator/workflows/core/events_handler.py
@@ -81,6 +81,9 @@ def _task_succeeded(task, *args, **kwargs):
 @events.start_workflow_signal.connect
 def _workflow_started(workflow_context, *args, **kwargs):
     execution = workflow_context.execution
+    # the execution may already be in the process of cancelling
+    if execution.status in (execution.CANCELLING, execution.CANCELLED):
+        return
     execution.status = execution.STARTED
     execution.started_at = datetime.utcnow()
     workflow_context.execution = execution
@@ -98,7 +101,7 @@ def _workflow_failed(workflow_context, exception, *args, **kwargs):
 @events.on_success_workflow_signal.connect
 def _workflow_succeeded(workflow_context, *args, **kwargs):
     execution = workflow_context.execution
-    execution.status = execution.TERMINATED
+    execution.status = execution.SUCCEEDED
     execution.ended_at = datetime.utcnow()
     workflow_context.execution = execution
 
@@ -106,13 +109,16 @@ def _workflow_succeeded(workflow_context, *args, **kwargs):
 @events.on_cancelled_workflow_signal.connect
 def _workflow_cancelled(workflow_context, *args, **kwargs):
     execution = workflow_context.execution
-    # _workflow_cancelling function may have called this function
-    # already
+    # _workflow_cancelling function may have called this function already
     if execution.status == execution.CANCELLED:
         return
-    execution.status = execution.CANCELLED
-    execution.ended_at = datetime.utcnow()
-    workflow_context.execution = execution
+    # the execution may have already been finished
+    elif execution.status in (execution.SUCCEEDED, execution.FAILED):
+        _log_tried_to_cancel_execution_but_it_already_ended(workflow_context, execution.status)
+    else:
+        execution.status = execution.CANCELLED
+        execution.ended_at = datetime.utcnow()
+        workflow_context.execution = execution
 
 
 @events.on_cancelling_workflow_signal.connect
@@ -120,8 +126,12 @@ def _workflow_cancelling(workflow_context, *args, **kwargs):
     execution = workflow_context.execution
     if execution.status == execution.PENDING:
         return _workflow_cancelled(workflow_context=workflow_context)
-    execution.status = execution.CANCELLING
-    workflow_context.execution = execution
+    # the execution may have already been finished
+    elif execution.status in (execution.SUCCEEDED, execution.FAILED):
+        _log_tried_to_cancel_execution_but_it_already_ended(workflow_context, execution.status)
+    else:
+        execution.status = execution.CANCELLING
+        workflow_context.execution = execution
 
 
 def _update_node_state_if_necessary(task, is_transitional=False):
@@ -135,3 +145,9 @@ def _update_node_state_if_necessary(task, is_transitional=False):
         if state:
             node.state = state
             task.context.model.node.update(node)
+
+
+def _log_tried_to_cancel_execution_but_it_already_ended(workflow_context, status):
+    workflow_context.logger.info(
+        "'{workflow_name}' workflow execution {status} before the cancel request"
+        "was fully processed".format(workflow_name=workflow_context.workflow_name, status=status))

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/5cd7aec5/aria/orchestrator/workflows/events_logging.py
----------------------------------------------------------------------
diff --git a/aria/orchestrator/workflows/events_logging.py b/aria/orchestrator/workflows/events_logging.py
index 7d15c81..3ffe18b 100644
--- a/aria/orchestrator/workflows/events_logging.py
+++ b/aria/orchestrator/workflows/events_logging.py
@@ -52,6 +52,7 @@ def _failure_operation_handler(task, traceback, **kwargs):
         .format(name=_get_task_name(task), task=task), extra=dict(traceback=traceback)
     )
 
+
 @events.start_workflow_signal.connect
 def _start_workflow_handler(context, **kwargs):
     context.logger.info("Starting '{ctx.workflow_name}' workflow execution".format(ctx=context))

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/5cd7aec5/tests/modeling/test_models.py
----------------------------------------------------------------------
diff --git a/tests/modeling/test_models.py b/tests/modeling/test_models.py
index d64cdba..2da2154 100644
--- a/tests/modeling/test_models.py
+++ b/tests/modeling/test_models.py
@@ -310,40 +310,40 @@ class TestExecution(object):
                                 Execution.CANCELLED,
                                 Execution.PENDING],
             Execution.STARTED: [Execution.FAILED,
-                                Execution.TERMINATED,
+                                Execution.SUCCEEDED,
                                 Execution.CANCELLED,
                                 Execution.CANCELLING,
                                 Execution.STARTED],
             Execution.CANCELLING: [Execution.FAILED,
-                                   Execution.TERMINATED,
+                                   Execution.SUCCEEDED,
                                    Execution.CANCELLED,
                                    Execution.CANCELLING],
             Execution.FAILED: [Execution.FAILED],
-            Execution.TERMINATED: [Execution.TERMINATED],
+            Execution.SUCCEEDED: [Execution.SUCCEEDED],
             Execution.CANCELLED: [Execution.CANCELLED]
         }
 
         invalid_transitions = {
             Execution.PENDING: [Execution.FAILED,
-                                Execution.TERMINATED,
+                                Execution.SUCCEEDED,
                                 Execution.CANCELLING],
             Execution.STARTED: [Execution.PENDING],
             Execution.CANCELLING: [Execution.PENDING,
                                    Execution.STARTED],
             Execution.FAILED: [Execution.PENDING,
                                Execution.STARTED,
-                               Execution.TERMINATED,
+                               Execution.SUCCEEDED,
                                Execution.CANCELLED,
                                Execution.CANCELLING],
-            Execution.TERMINATED: [Execution.PENDING,
-                                   Execution.STARTED,
-                                   Execution.FAILED,
-                                   Execution.CANCELLED,
-                                   Execution.CANCELLING],
+            Execution.SUCCEEDED: [Execution.PENDING,
+                                  Execution.STARTED,
+                                  Execution.FAILED,
+                                  Execution.CANCELLED,
+                                  Execution.CANCELLING],
             Execution.CANCELLED: [Execution.PENDING,
                                   Execution.STARTED,
                                   Execution.FAILED,
-                                  Execution.TERMINATED,
+                                  Execution.SUCCEEDED,
                                   Execution.CANCELLING],
         }
 

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/5cd7aec5/tests/orchestrator/test_workflow_runner.py
----------------------------------------------------------------------
diff --git a/tests/orchestrator/test_workflow_runner.py b/tests/orchestrator/test_workflow_runner.py
index 54e940f..7374e50 100644
--- a/tests/orchestrator/test_workflow_runner.py
+++ b/tests/orchestrator/test_workflow_runner.py
@@ -86,7 +86,7 @@ def test_existing_active_executions(request, service, model):
 def test_existing_executions_but_no_active_ones(request, service, model):
     existing_terminated_execution = models.Execution(
         service=service,
-        status=models.Execution.TERMINATED,
+        status=models.Execution.SUCCEEDED,
         workflow_name='uninstall')
     model.execution.put(existing_terminated_execution)
     # no active executions exist, so no error should be raised

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/5cd7aec5/tests/orchestrator/workflows/core/test_engine.py
----------------------------------------------------------------------
diff --git a/tests/orchestrator/workflows/core/test_engine.py b/tests/orchestrator/workflows/core/test_engine.py
index 1a88f13..af9af17 100644
--- a/tests/orchestrator/workflows/core/test_engine.py
+++ b/tests/orchestrator/workflows/core/test_engine.py
@@ -157,7 +157,7 @@ class TestEngine(BaseTest):
         execution = workflow_context.execution
         assert execution.started_at <= execution.ended_at <= datetime.utcnow()
         assert execution.error is None
-        assert execution.status == models.Execution.TERMINATED
+        assert execution.status == models.Execution.SUCCEEDED
 
     def test_single_task_successful_execution(self, workflow_context, executor):
         @workflow


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

Posted by em...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/b7e1836d/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..add0766 100644
--- a/extensions/aria_extension_tosca/simple_v1_0/modeling/__init__.py
+++ b/extensions/aria_extension_tosca/simple_v1_0/modeling/__init__.py
@@ -32,6 +32,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
 
 
@@ -164,6 +166,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))
 
@@ -179,9 +183,9 @@ 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,
+        create_node_filter_constraints(context, node_template.node_filter,
                                               model.target_node_template_constraints)
 
     return model
@@ -271,9 +275,9 @@ 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,
+        create_node_filter_constraints(context, requirement.node_filter,
                                               model.target_node_template_constraints)
 
     relationship = requirement.relationship
@@ -557,17 +561,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:
@@ -575,129 +575,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/b7e1836d/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..01f222f 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
@@ -22,7 +22,7 @@ from aria.parser import dsl_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
 
 #
@@ -327,20 +327,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')
+@dsl_specification('3.2.1-1', '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 +371,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/b7e1836d/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..ba6cafd
--- /dev/null
+++ b/extensions/aria_extension_tosca/simple_v1_0/modeling/functions.py
@@ -0,0 +1,687 @@
+# 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, full_type_name
+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.models import (Parameter, Node, NodeTemplate, Relationship, RelationshipTemplate)
+from aria.modeling.functions import (Function, Evaluation)
+
+
+#
+# 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: {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)
+
+
+@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: {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
+#
+
+@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: {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):
+        if not isinstance(container_holder, Parameter):
+            raise CannotEvaluateFunctionException()
+
+        service = container_holder.service
+        if service is None:
+            raise CannotEvaluateFunctionException()
+            
+        value = service.inputs.get(self.input_property_name)
+        if value is None:
+            raise InvalidValueError(
+                'function "get_input" argument is not a valid input name: {0}'
+                .format(safe_repr(self.input_property_name)),
+                locator=self.locator)
+
+        value = value.value
+        value, _ = evaluate(value, False, container_holder)
+        #value = as_raw(value)
+        return Evaluation(value, False) # We never return final evaluations!
+
+
+@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: '
+                '{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
+#
+
+@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:'
+                ' {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
+#
+
+@dsl_specification('4.6.1', 'tosca-simple-1.0')
+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
+#
+
+@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: {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
+#
+
+@dsl_specification('4.8.1', 'tosca-simple-1.0')
+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):
+    if hasattr(value, '__evaluate__'):
+        value = value.__evaluate__(container_holder)
+        if not value.final:
+            final = False
+        return value.value, final
+    else:
+        return value, final
+
+
+@dsl_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
+        
+        #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 "{0}" could not find modelable entity "{0}"'
+                            .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 in nested_parameter_name_or_index:
+        if (isinstance(value, dict) and (name in value)) \
+            or (isinstance(value, list) and name < len(list)):
+            value = value[name].value
+            value, final = evaluate(value, final, modelable_entity)
+        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)

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/b7e1836d/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/b7e1836d/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..231a7d1 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

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/b7e1836d/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..c0f9f23 100644
--- a/extensions/aria_extension_tosca/simple_v1_0/templates.py
+++ b/extensions/aria_extension_tosca/simple_v1_0/templates.py
@@ -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
@@ -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))
 

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/b7e1836d/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


[04/13] incubator-ariatosca git commit: ARIA-150 Fixed simple hello-world example

Posted by em...@apache.org.
ARIA-150 Fixed simple hello-world example

The simple hello-world example had a missing host node,
which caused an error at service creation phase.
The example now uses custom types which do not require
the extra host node.


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

Branch: refs/heads/ARIA-139-attributes
Commit: 7a4a1ddabadef814affb56778c7614256ca8fe00
Parents: 29bc84b
Author: Ran Ziv <ra...@gigaspaces.com>
Authored: Sun Apr 23 15:39:57 2017 +0300
Committer: Ran Ziv <ra...@gigaspaces.com>
Committed: Sun Apr 23 15:39:57 2017 +0300

----------------------------------------------------------------------
 examples/hello-world/helloworld.yaml | 11 ++++++++---
 1 file changed, 8 insertions(+), 3 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/7a4a1dda/examples/hello-world/helloworld.yaml
----------------------------------------------------------------------
diff --git a/examples/hello-world/helloworld.yaml b/examples/hello-world/helloworld.yaml
index b6efaca..77cef30 100644
--- a/examples/hello-world/helloworld.yaml
+++ b/examples/hello-world/helloworld.yaml
@@ -1,18 +1,23 @@
-tosca_definitions_version: tosca_simple_profile_for_nfv_1_0
+tosca_definitions_version: tosca_simple_yaml_1_0
 
 node_types:
+  web_server:
+    derived_from: tosca.nodes.Root
+    capabilities:
+      host:
+        type: tosca.capabilities.Container
+
   web_app:
     derived_from: tosca.nodes.WebApplication
     properties:
       port:
         type: integer
-        default: 8080
 
 topology_template:
 
   node_templates:
     web_server:
-      type: tosca.nodes.WebServer
+      type: web_server
 
     web_app:
       type: web_app


[10/13] incubator-ariatosca git commit: ARIA-155 Clean models from unused fields

Posted by em...@apache.org.
ARIA-155 Clean models from unused fields


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

Branch: refs/heads/ARIA-139-attributes
Commit: d0c5e6fa1732a1b90992a7a75721b408f8a1b575
Parents: 1f3e7ff
Author: Ran Ziv <ra...@gigaspaces.com>
Authored: Sun Apr 30 15:15:37 2017 +0300
Committer: Ran Ziv <ra...@gigaspaces.com>
Committed: Sun Apr 30 15:39:21 2017 +0300

----------------------------------------------------------------------
 aria/modeling/orchestration.py                  |   7 +-
 aria/modeling/service_instance.py               |  28 ++---
 aria/modeling/service_template.py               |  16 +--
 .../workflows/core/events_handler.py            |   4 +-
 aria/orchestrator/workflows/core/task.py        |  14 +--
 tests/mock/models.py                            |   3 -
 tests/modeling/test_mixins.py                   |   3 -
 tests/modeling/test_models.py                   | 103 ++++++++-----------
 tests/orchestrator/workflows/core/test_task.py  |   8 +-
 .../orchestrator/workflows/executor/__init__.py |   2 +-
 10 files changed, 77 insertions(+), 111 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/d0c5e6fa/aria/modeling/orchestration.py
----------------------------------------------------------------------
diff --git a/aria/modeling/orchestration.py b/aria/modeling/orchestration.py
index f163903..ab9d34d 100644
--- a/aria/modeling/orchestration.py
+++ b/aria/modeling/orchestration.py
@@ -92,7 +92,6 @@ class ExecutionBase(ModelMixin):
     started_at = Column(DateTime, nullable=True, index=True)
     ended_at = Column(DateTime, nullable=True, index=True)
     error = Column(Text, nullable=True)
-    is_system_workflow = Column(Boolean, nullable=False, default=False)
     status = Column(Enum(*STATES, name='execution_status'), default=PENDING)
     workflow_name = Column(Text)
 
@@ -252,8 +251,8 @@ class TaskBase(ModelMixin):
     :vartype started_at: datetime
     :ivar ended_at: Timestamp for when task ended
     :vartype ended_at: datetime
-    :ivar retry_count: How many retries occurred
-    :vartype retry_count: int
+    :ivar attempts_count: How many attempts occurred
+    :vartype attempts_count: int
     """
 
     __tablename__ = 'task'
@@ -314,7 +313,7 @@ class TaskBase(ModelMixin):
     due_at = Column(DateTime, nullable=False, index=True, default=datetime.utcnow())
     started_at = Column(DateTime, default=None)
     ended_at = Column(DateTime, default=None)
-    retry_count = Column(Integer, default=0)
+    attempts_count = Column(Integer, default=1)
 
     def has_ended(self):
         return self.status in (self.SUCCESS, self.FAILED)

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/d0c5e6fa/aria/modeling/service_instance.py
----------------------------------------------------------------------
diff --git a/aria/modeling/service_instance.py b/aria/modeling/service_instance.py
index d5391ef..ad8e7ed 100644
--- a/aria/modeling/service_instance.py
+++ b/aria/modeling/service_instance.py
@@ -74,10 +74,6 @@ class ServiceBase(InstanceModelMixin):
     :vartype created_at: :class:`datetime.datetime`
     :ivar updated_at: Update timestamp
     :vartype updated_at: :class:`datetime.datetime`
-    :ivar permalink: ??
-    :vartype permalink: basestring
-    :ivar scaling_groups: ??
-    :vartype scaling_groups: {}
     :ivar modifications: Modifications of this service
     :vartype modifications: [:class:`ServiceModification`]
     :ivar updates: Updates of this service
@@ -187,13 +183,6 @@ class ServiceBase(InstanceModelMixin):
     created_at = Column(DateTime, nullable=False, index=True)
     updated_at = Column(DateTime)
 
-    # region orchestration
-
-    permalink = Column(Text)
-    scaling_groups = Column(modeling_types.Dict)
-
-    # endregion
-
     def satisfy_requirements(self):
         satisfied = True
         for node in self.nodes.itervalues():
@@ -346,8 +335,6 @@ class NodeBase(InstanceModelMixin):
     :vartype host: :class:`Node`
     :ivar runtime_properties: TODO: should be replaced with attributes
     :vartype runtime_properties: {}
-    :ivar scaling_groups: ??
-    :vartype scaling_groups: []
     :ivar state: The state of the node, according to to the TOSCA-defined node states
     :vartype state: string
     :ivar version: Used by `aria.storage.instrumentation`
@@ -530,7 +517,6 @@ class NodeBase(InstanceModelMixin):
 
     description = Column(Text)
     runtime_properties = Column(modeling_types.Dict)
-    scaling_groups = Column(modeling_types.List)
     state = Column(Enum(*STATES, name='node_state'), nullable=False, default=INITIAL)
     version = Column(Integer, default=1)
 
@@ -1653,10 +1639,10 @@ class OperationBase(InstanceModelMixin):
     :vartype dependencies: [basestring]
     :ivar inputs: Parameters that can be used by this operation
     :vartype inputs: {basestring: :class:`Parameter`}
-    :ivar executor: Executor name
+    :ivar executor: Name of executor to run the operation with
     :vartype executor: basestring
-    :ivar max_retries: Maximum number of retries allowed in case of failure
-    :vartype max_retries: int
+    :ivar max_attempts: Maximum number of attempts allowed in case of failure
+    :vartype max_attempts: int
     :ivar retry_interval: Interval between retries (in seconds)
     :vartype retry_interval: int
     :ivar interface: Containing interface
@@ -1742,7 +1728,7 @@ class OperationBase(InstanceModelMixin):
     configuration = Column(modeling_types.StrictDict(key_cls=basestring))
     dependencies = Column(modeling_types.StrictList(item_cls=basestring))
     executor = Column(Text)
-    max_retries = Column(Integer)
+    max_attempts = Column(Integer)
     retry_interval = Column(Integer)
 
     def configure(self):
@@ -1771,7 +1757,7 @@ class OperationBase(InstanceModelMixin):
             ('implementation', self.implementation),
             ('dependencies', self.dependencies),
             ('executor', self.executor),
-            ('max_retries', self.max_retries),
+            ('max_attempts', self.max_attempts),
             ('retry_interval', self.retry_interval),
             ('inputs', formatting.as_raw_dict(self.inputs))))
 
@@ -1805,8 +1791,8 @@ class OperationBase(InstanceModelMixin):
                         ', '.join((str(context.style.literal(v)) for v in self.dependencies))))
             if self.executor is not None:
                 console.puts('Executor: {0}'.format(context.style.literal(self.executor)))
-            if self.max_retries is not None:
-                console.puts('Max retries: {0}'.format(context.style.literal(self.max_retries)))
+            if self.max_attempts is not None:
+                console.puts('Max attempts: {0}'.format(context.style.literal(self.max_attempts)))
             if self.retry_interval is not None:
                 console.puts('Retry interval: {0}'.format(
                     context.style.literal(self.retry_interval)))

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/d0c5e6fa/aria/modeling/service_template.py
----------------------------------------------------------------------
diff --git a/aria/modeling/service_template.py b/aria/modeling/service_template.py
index f1c2bcb..7fab4fc 100644
--- a/aria/modeling/service_template.py
+++ b/aria/modeling/service_template.py
@@ -1762,10 +1762,10 @@ class OperationTemplateBase(TemplateModelMixin):
     :vartype dependencies: [basestring]
     :ivar inputs: Parameters that can be used by this operation
     :vartype inputs: {basestring: :class:`Parameter`}
-    :ivar executor: Executor name
+    :ivar executor: Name of executor to run the operation with
     :vartype executor: basestring
-    :ivar max_retries: Maximum number of retries allowed in case of failure
-    :vartype max_retries: int
+    :ivar max_attempts: Maximum number of attempts allowed in case of failure
+    :vartype max_attempts: int
     :ivar retry_interval: Interval between retries (in seconds)
     :vartype retry_interval: int
     :ivar interface_template: Containing interface template
@@ -1848,7 +1848,7 @@ class OperationTemplateBase(TemplateModelMixin):
     configuration = Column(modeling_types.StrictDict(key_cls=basestring))
     dependencies = Column(modeling_types.StrictList(item_cls=basestring))
     executor = Column(Text)
-    max_retries = Column(Integer)
+    max_attempts = Column(Integer)
     retry_interval = Column(Integer)
 
     @property
@@ -1859,7 +1859,7 @@ class OperationTemplateBase(TemplateModelMixin):
             ('implementation', self.implementation),
             ('dependencies', self.dependencies),
             ('executor', self.executor),
-            ('max_retries', self.max_retries),
+            ('max_attempts', self.max_attempts),
             ('retry_interval', self.retry_interval),
             ('inputs', formatting.as_raw_dict(self.inputs))))
 
@@ -1889,7 +1889,7 @@ class OperationTemplateBase(TemplateModelMixin):
                                      configuration=self.configuration,
                                      dependencies=self.dependencies,
                                      executor=self.executor,
-                                     max_retries=self.max_retries,
+                                     max_attempts=self.max_attempts,
                                      retry_interval=self.retry_interval,
                                      operation_template=self)
         utils.instantiate_dict(container, operation.inputs, self.inputs)
@@ -1923,8 +1923,8 @@ class OperationTemplateBase(TemplateModelMixin):
                     ', '.join((str(context.style.literal(v)) for v in self.dependencies))))
             if self.executor is not None:
                 console.puts('Executor: {0}'.format(context.style.literal(self.executor)))
-            if self.max_retries is not None:
-                console.puts('Max retries: {0}'.format(context.style.literal(self.max_retries)))
+            if self.max_attempts is not None:
+                console.puts('Max attempts: {0}'.format(context.style.literal(self.max_attempts)))
             if self.retry_interval is not None:
                 console.puts('Retry interval: {0}'.format(
                     context.style.literal(self.retry_interval)))

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/d0c5e6fa/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 b43082b..88c24bd 100644
--- a/aria/orchestrator/workflows/core/events_handler.py
+++ b/aria/orchestrator/workflows/core/events_handler.py
@@ -49,7 +49,7 @@ def _task_failed(task, exception, *args, **kwargs):
     with task._update():
         should_retry = all([
             not isinstance(exception, exceptions.TaskAbortException),
-            task.retry_count < task.max_attempts - 1 or task.max_attempts == task.INFINITE_RETRIES,
+            task.attempts_count < task.max_attempts or task.max_attempts == task.INFINITE_RETRIES,
             # ignore_failure check here means the task will not be retries and it will be marked
             # as failed. The engine will also look at ignore_failure so it won't fail the
             # workflow.
@@ -62,7 +62,7 @@ def _task_failed(task, exception, *args, **kwargs):
             if retry_interval is None:
                 retry_interval = task.retry_interval
             task.status = task.RETRYING
-            task.retry_count += 1
+            task.attempts_count += 1
             task.due_at = datetime.utcnow() + timedelta(seconds=retry_interval)
         else:
             task.ended_at = datetime.utcnow()

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/d0c5e6fa/aria/orchestrator/workflows/core/task.py
----------------------------------------------------------------------
diff --git a/aria/orchestrator/workflows/core/task.py b/aria/orchestrator/workflows/core/task.py
index 2b26152..8adeb7e 100644
--- a/aria/orchestrator/workflows/core/task.py
+++ b/aria/orchestrator/workflows/core/task.py
@@ -229,17 +229,17 @@ class OperationTask(BaseTask):
         self._update_fields['ended_at'] = value
 
     @property
-    def retry_count(self):
+    def attempts_count(self):
         """
-        Returns the retry count for the task
-        :return: retry count
+        Returns the attempts count for the task
+        :return: attempts count
         """
-        return self.model_task.retry_count
+        return self.model_task.attempts_count
 
-    @retry_count.setter
+    @attempts_count.setter
     @_locked
-    def retry_count(self, value):
-        self._update_fields['retry_count'] = value
+    def attempts_count(self, value):
+        self._update_fields['attempts_count'] = value
 
     @property
     def due_at(self):

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/d0c5e6fa/tests/mock/models.py
----------------------------------------------------------------------
diff --git a/tests/mock/models.py b/tests/mock/models.py
index cdedea9..f066551 100644
--- a/tests/mock/models.py
+++ b/tests/mock/models.py
@@ -81,8 +81,6 @@ def create_service(service_template, name=SERVICE_NAME, inputs=None):
         description='',
         created_at=now,
         updated_at=now,
-        permalink='',
-        scaling_groups={},
     )
 
 
@@ -196,7 +194,6 @@ def create_node(dependency_node_template, service, name=NODE_NAME, state=models.
         version=None,
         node_template=dependency_node_template,
         state=state,
-        scaling_groups=[],
         service=service,
         interfaces=get_standard_interface(service),
     )

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/d0c5e6fa/tests/modeling/test_mixins.py
----------------------------------------------------------------------
diff --git a/tests/modeling/test_mixins.py b/tests/modeling/test_mixins.py
index cd7cadf..a18a04e 100644
--- a/tests/modeling/test_mixins.py
+++ b/tests/modeling/test_mixins.py
@@ -97,8 +97,6 @@ def test_model_to_dict(context):
     expected_keys = [
         'description',
         'created_at',
-        'permalink',
-        'scaling_groups',
         'updated_at'
     ]
 
@@ -128,7 +126,6 @@ def test_relationship_model_ordering(context):
         version=None,
         node_template=new_node_template,
         state=modeling.models.Node.INITIAL,
-        scaling_groups=[]
     )
 
     source_node.outbound_relationships.append(modeling.models.Relationship(

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/d0c5e6fa/tests/modeling/test_models.py
----------------------------------------------------------------------
diff --git a/tests/modeling/test_models.py b/tests/modeling/test_models.py
index 2da2154..61034bd 100644
--- a/tests/modeling/test_models.py
+++ b/tests/modeling/test_models.py
@@ -41,7 +41,7 @@ from aria.modeling.models import (
 )
 
 from tests import mock
-from ..storage import release_sqlite_storage, init_inmemory_model_storage
+from tests.storage import release_sqlite_storage, init_inmemory_model_storage
 
 
 @contextmanager
@@ -206,31 +206,25 @@ class TestServiceTemplate(object):
 class TestService(object):
 
     @pytest.mark.parametrize(
-        'is_valid, name, created_at, description, inputs, permalink, '
-        'outputs, scaling_groups, updated_at',
+        'is_valid, name, created_at, description, inputs, '
+        'outputs, updated_at',
         [
-            (False, m_cls, now, 'desc', {}, 'perlnk', {}, {}, now),
-            (False, 'name', m_cls, 'desc', {}, 'perlnk', {}, {}, now),
-            (False, 'name', now, m_cls, {}, 'perlnk', {}, {}, now),
-            (False, 'name', now, 'desc', {}, m_cls, {}, {}, now),
-            (False, 'name', now, 'desc', {}, 'perlnk', m_cls, {}, now),
-            (False, 'name', now, 'desc', {}, 'perlnk', {}, m_cls, now),
-            (False, 'name', now, 'desc', {}, 'perlnk', {}, {}, m_cls),
-
-            (True, 'name', now, 'desc', {}, 'perlnk', {}, {}, now),
-            (True, None, now, 'desc', {}, 'perlnk', {}, {}, now),
-            (True, 'name', now, 'desc', {}, 'perlnk', {}, {}, now),
-            (True, 'name', now, None, {}, 'perlnk', {}, {}, now),
-            (True, 'name', now, 'desc', {}, 'perlnk', {}, {}, now),
-            (True, 'name', now, 'desc', {}, None, {}, {}, now),
-            (True, 'name', now, 'desc', {}, 'perlnk', {}, {}, now),
-            (True, 'name', now, 'desc', {}, 'perlnk', {}, None, now),
-            (True, 'name', now, 'desc', {}, 'perlnk', {}, {}, None),
-            (True, 'name', now, 'desc', {}, 'perlnk', {}, {}, now),
+            (False, m_cls, now, 'desc', {}, {}, now),
+            (False, 'name', m_cls, 'desc', {}, {}, now),
+            (False, 'name', now, m_cls, {}, {}, now),
+            (False, 'name', now, 'desc', m_cls, {}, now),
+            (False, 'name', now, 'desc', {}, m_cls, now),
+            (False, 'name', now, 'desc', {}, {}, m_cls),
+
+            (True, 'name', now, 'desc', {}, {}, now),
+            (True, None, now, 'desc', {}, {}, now),
+            (True, 'name', now, None, {}, {}, now),
+            (True, 'name', now, 'desc', {}, {}, None),
+            (True, 'name', now, 'desc', {}, {}, now),
         ]
     )
     def test_service_model_creation(self, service_storage, is_valid, name, created_at, description,
-                                    inputs, permalink, outputs, scaling_groups, updated_at):
+                                    inputs, outputs, updated_at):
         service = _test_model(
             is_valid=is_valid,
             storage=service_storage,
@@ -241,9 +235,7 @@ class TestService(object):
                 created_at=created_at,
                 description=description,
                 inputs=inputs,
-                permalink=permalink,
                 outputs=outputs,
-                scaling_groups=scaling_groups,
                 updated_at=updated_at
             ))
         if is_valid:
@@ -254,27 +246,25 @@ class TestService(object):
 class TestExecution(object):
 
     @pytest.mark.parametrize(
-        'is_valid, created_at, started_at, ended_at, error, is_system_workflow, inputs, '
+        'is_valid, created_at, started_at, ended_at, error, inputs, '
         'status, workflow_name',
         [
-            (False, m_cls, now, now, 'error', False, {}, Execution.STARTED, 'wf_name'),
-            (False, now, m_cls, now, 'error', False, {}, Execution.STARTED, 'wf_name'),
-            (False, now, now, m_cls, 'error', False, {}, Execution.STARTED, 'wf_name'),
-            (False, now, now, now, m_cls, False, {}, Execution.STARTED, 'wf_name'),
-            (False, now, now, now, 'error', False, m_cls, Execution.STARTED, 'wf_name'),
-            (False, now, now, now, 'error', False, {}, m_cls, 'wf_name'),
-            (False, now, now, now, 'error', False, {}, Execution.STARTED, m_cls),
-
-            (True, now, now, now, 'error', False, {}, Execution.STARTED, 'wf_name'),
-            (True, now, None, now, 'error', False, {}, Execution.STARTED, 'wf_name'),
-            (True, now, now, None, 'error', False, {}, Execution.STARTED, 'wf_name'),
-            (True, now, now, now, None, False, {}, Execution.STARTED, 'wf_name'),
-            (True, now, now, now, 'error', False, {}, Execution.STARTED, 'wf_name'),
+            (False, m_cls, now, now, 'error', {}, Execution.STARTED, 'wf_name'),
+            (False, now, m_cls, now, 'error', {}, Execution.STARTED, 'wf_name'),
+            (False, now, now, m_cls, 'error', {}, Execution.STARTED, 'wf_name'),
+            (False, now, now, now, m_cls, {}, Execution.STARTED, 'wf_name'),
+            (False, now, now, now, 'error', m_cls, Execution.STARTED, 'wf_name'),
+            (False, now, now, now, 'error', {}, m_cls, 'wf_name'),
+            (False, now, now, now, 'error', {}, Execution.STARTED, m_cls),
+
+            (True, now, now, now, 'error', {}, Execution.STARTED, 'wf_name'),
+            (True, now, None, now, 'error', {}, Execution.STARTED, 'wf_name'),
+            (True, now, now, None, 'error', {}, Execution.STARTED, 'wf_name'),
+            (True, now, now, now, None, {}, Execution.STARTED, 'wf_name'),
         ]
     )
     def test_execution_model_creation(self, service_storage, is_valid, created_at, started_at,
-                                      ended_at, error, is_system_workflow, inputs, status,
-                                      workflow_name):
+                                      ended_at, error, inputs, status, workflow_name):
         execution = _test_model(
             is_valid=is_valid,
             storage=service_storage,
@@ -285,7 +275,6 @@ class TestExecution(object):
                 started_at=started_at,
                 ended_at=ended_at,
                 error=error,
-                is_system_workflow=is_system_workflow,
                 inputs=inputs,
                 status=status,
                 workflow_name=workflow_name,
@@ -549,23 +538,22 @@ class TestNodeTemplate(object):
 
 class TestNode(object):
     @pytest.mark.parametrize(
-        'is_valid, name, runtime_properties, scaling_groups, state, version',
+        'is_valid, name, runtime_properties, state, version',
         [
-            (False, m_cls, {}, [], 'state', 1),
-            (False, 'name', m_cls, [], 'state', 1),
-            (False, 'name', {}, m_cls, 'state', 1),
-            (False, 'name', {}, [], m_cls, 1),
-            (False, m_cls, {}, [], 'state', m_cls),
-
-            (True, 'name', {}, [], 'initial', 1),
-            (True, None, {}, [], 'initial', 1),
-            (True, 'name', None, [], 'initial', 1),
-            (True, 'name', {}, None, 'initial', 1),
-            (True, 'name', {}, [], 'initial', None),
+            (False, m_cls, {}, 'state', 1),
+            (False, 'name', m_cls, 'state', 1),
+            (False, 'name', {}, 'state', 1),
+            (False, 'name', {}, m_cls, 1),
+            (False, m_cls, {}, 'state', m_cls),
+
+            (True, 'name', {}, 'initial', 1),
+            (True, None, {}, 'initial', 1),
+            (True, 'name', None, 'initial', 1),
+            (True, 'name', {}, 'initial', None),
         ]
     )
     def test_node_model_creation(self, node_template_storage, is_valid, name, runtime_properties,
-                                 scaling_groups, state, version):
+                                 state, version):
         node = _test_model(
             is_valid=is_valid,
             storage=node_template_storage,
@@ -575,7 +563,6 @@ class TestNode(object):
                 type=node_template_storage.type.list()[0],
                 name=name,
                 runtime_properties=runtime_properties,
-                scaling_groups=scaling_groups,
                 state=state,
                 version=version,
                 service=node_template_storage.service.list()[0]
@@ -771,7 +758,7 @@ class TestPlugin(object):
 class TestTask(object):
 
     @pytest.mark.parametrize(
-        'is_valid, status, due_at, started_at, ended_at, max_attempts, retry_count, '
+        'is_valid, status, due_at, started_at, ended_at, max_attempts, attempts_count, '
         'retry_interval, ignore_failure, name, operation_mapping, inputs, plugin_id',
         [
             (False, m_cls, now, now, now, 1, 1, 1, True, 'name', 'map', {}, '1'),
@@ -800,7 +787,7 @@ class TestTask(object):
         ]
     )
     def test_task_model_creation(self, execution_storage, is_valid, status, due_at, started_at,
-                                 ended_at, max_attempts, retry_count, retry_interval,
+                                 ended_at, max_attempts, attempts_count, retry_interval,
                                  ignore_failure, name, operation_mapping, inputs, plugin_id):
         task = _test_model(
             is_valid=is_valid,
@@ -813,7 +800,7 @@ class TestTask(object):
                 started_at=started_at,
                 ended_at=ended_at,
                 max_attempts=max_attempts,
-                retry_count=retry_count,
+                attempts_count=attempts_count,
                 retry_interval=retry_interval,
                 ignore_failure=ignore_failure,
                 name=name,

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/d0c5e6fa/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 8dda209..0765350 100644
--- a/tests/orchestrator/workflows/core/test_task.py
+++ b/tests/orchestrator/workflows/core/test_task.py
@@ -127,7 +127,7 @@ class TestOperationTask(object):
         with pytest.raises(exceptions.TaskException):
             core_task.ended_at = now
         with pytest.raises(exceptions.TaskException):
-            core_task.retry_count = 2
+            core_task.attempts_count = 2
         with pytest.raises(exceptions.TaskException):
             core_task.due_at = now
 
@@ -141,16 +141,16 @@ class TestOperationTask(object):
             core_task.status = core_task.STARTED
             core_task.started_at = future_time
             core_task.ended_at = future_time
-            core_task.retry_count = 2
+            core_task.attempts_count = 2
             core_task.due_at = future_time
             assert core_task.status != core_task.STARTED
             assert core_task.started_at != future_time
             assert core_task.ended_at != future_time
-            assert core_task.retry_count != 2
+            assert core_task.attempts_count != 2
             assert core_task.due_at != future_time
 
         assert core_task.status == core_task.STARTED
         assert core_task.started_at == future_time
         assert core_task.ended_at == future_time
-        assert core_task.retry_count == 2
+        assert core_task.attempts_count == 2
         assert core_task.due_at == future_time

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/d0c5e6fa/tests/orchestrator/workflows/executor/__init__.py
----------------------------------------------------------------------
diff --git a/tests/orchestrator/workflows/executor/__init__.py b/tests/orchestrator/workflows/executor/__init__.py
index c05831a..cedcc5f 100644
--- a/tests/orchestrator/workflows/executor/__init__.py
+++ b/tests/orchestrator/workflows/executor/__init__.py
@@ -34,7 +34,7 @@ class MockTask(object):
         self.id = str(uuid.uuid4())
         self.logger = logging.getLogger()
         self.context = MockContext()
-        self.retry_count = 0
+        self.attempts_count = 1
         self.max_attempts = 1
         self.ignore_failure = False
         self.interface_name = 'interface_name'