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

[1/6] incubator-ariatosca git commit: ARIA-64 Remove PyYAML dependency [Forced Update!]

Repository: incubator-ariatosca
Updated Branches:
  refs/heads/ARIA-54-prepare-aria-packaging e988f3455 -> 950fd5060 (forced update)


ARIA-64 Remove PyYAML dependency


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

Branch: refs/heads/ARIA-54-prepare-aria-packaging
Commit: e4d003680acc1dc4bc442a044a5feb0688475b46
Parents: b6d3c43
Author: Tal Liron <ta...@gmail.com>
Authored: Fri Jun 2 12:39:00 2017 -0500
Committer: Ran Ziv <ra...@gigaspaces.com>
Committed: Tue Jun 6 11:50:09 2017 +0300

----------------------------------------------------------------------
 aria/cli/config/config.py | 2 +-
 aria/cli/inputs.py        | 2 +-
 requirements.in           | 1 -
 3 files changed, 2 insertions(+), 3 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/e4d00368/aria/cli/config/config.py
----------------------------------------------------------------------
diff --git a/aria/cli/config/config.py b/aria/cli/config/config.py
index 8c4214c..d584fad 100644
--- a/aria/cli/config/config.py
+++ b/aria/cli/config/config.py
@@ -15,8 +15,8 @@
 
 
 import os
-import yaml
 import pkg_resources
+from ruamel import yaml
 
 from jinja2.environment import Template
 

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/e4d00368/aria/cli/inputs.py
----------------------------------------------------------------------
diff --git a/aria/cli/inputs.py b/aria/cli/inputs.py
index 0ff48dc..4d46ebb 100644
--- a/aria/cli/inputs.py
+++ b/aria/cli/inputs.py
@@ -15,7 +15,7 @@
 
 import os
 import glob
-import yaml
+from ruamel import yaml
 
 from .env import logger
 from .exceptions import AriaCliError

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/e4d00368/requirements.in
----------------------------------------------------------------------
diff --git a/requirements.in b/requirements.in
index ab06d93..54e8714 100644
--- a/requirements.in
+++ b/requirements.in
@@ -13,7 +13,6 @@
 # In order to create the requirements.txt file, execute
 # pip-compile --output-file requirements.txt requirements.in (pip-tools package is needed).
 
-PyYAML<3.13
 requests>=2.3.0, <2.14.0
 networkx>=1.9, <1.10 # version 1.10 dropped support of python 2.6
 retrying>=1.3.0, <1.4.0


[6/6] incubator-ariatosca git commit: ARIA-54 Prepare for ARIA packaging

Posted by ra...@apache.org.
ARIA-54 Prepare for ARIA packaging

Preparations for ARIA packaging:
 - Added CHANGELOG file
 - Added CONTRIBUTING file
 - Added DISCLAIMER file
 - Converted README from md to rst for PyPI compatiability
 - Removed outdated TODO file
 - Added long_description, download_url to setup.py metadata
 - Modified setup.py url metadata to point at ASF domain
 - Added more badges to README


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

Branch: refs/heads/ARIA-54-prepare-aria-packaging
Commit: 950fd506027dd214e209d6ba2a4650fa322adbd1
Parents: 22f6e9e
Author: Ran Ziv <ra...@gigaspaces.com>
Authored: Mon Jun 5 13:24:49 2017 +0300
Committer: Ran Ziv <ra...@gigaspaces.com>
Committed: Mon Jun 12 19:27:58 2017 +0300

----------------------------------------------------------------------
 CHANGELOG.rst |   4 ++
 CONTRIBUTING  |   3 +
 DISCLAIMER    |  10 ++++
 MANIFEST.in   |   9 ++-
 README.md     | 120 -------------------------------------
 README.rst    | 171 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 TODO.md       |   8 ---
 setup.py      |   8 ++-
 8 files changed, 202 insertions(+), 131 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/950fd506/CHANGELOG.rst
----------------------------------------------------------------------
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
new file mode 100644
index 0000000..6abb1af
--- /dev/null
+++ b/CHANGELOG.rst
@@ -0,0 +1,4 @@
+0.1.0
+-----
+
+ * Initial release.
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/950fd506/CONTRIBUTING
----------------------------------------------------------------------
diff --git a/CONTRIBUTING b/CONTRIBUTING
new file mode 100644
index 0000000..4124003
--- /dev/null
+++ b/CONTRIBUTING
@@ -0,0 +1,3 @@
+Contribution guide is available on our Confluence:
+
+https://cwiki.apache.org/confluence/display/ARIATOSCA/Contributing+to+ARIA
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/950fd506/DISCLAIMER
----------------------------------------------------------------------
diff --git a/DISCLAIMER b/DISCLAIMER
new file mode 100644
index 0000000..358d8e1
--- /dev/null
+++ b/DISCLAIMER
@@ -0,0 +1,10 @@
+Apache AriaTosca is an effort undergoing incubation at the Apache Software
+Foundation (ASF), sponsored by the Apache Incubator.
+
+Incubation is required of all newly accepted projects until a further review
+indicates that the infrastructure, communications, and decision making process
+have stabilized in a manner consistent with other successful ASF projects.
+
+While incubation status is not necessarily a reflection of the completeness
+or stability of the code, it does indicate that the project has yet to be
+fully endorsed by the ASF.
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/950fd506/MANIFEST.in
----------------------------------------------------------------------
diff --git a/MANIFEST.in b/MANIFEST.in
index 6c79a3a..dd16e28 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,4 +1,9 @@
+include CHANGELOG.rst
+include CONTRIBUTING
+include LICENSE
+include NOTICE
+include README.rst
 include requirements.txt
 include VERSION
-include LICENSE
-recursive-include examples *
+recursive-include docs *
+recursive-include examples *
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/950fd506/README.md
----------------------------------------------------------------------
diff --git a/README.md b/README.md
deleted file mode 100644
index 6aee414..0000000
--- a/README.md
+++ /dev/null
@@ -1,120 +0,0 @@
-ARIA
-====
-
-[![Build Status](https://img.shields.io/travis/apache/incubator-ariatosca/master.svg)](https://travis-ci.org/apache/incubator-ariatosca)
-[![Appveyor Build Status](https://img.shields.io/appveyor/ci/ApacheSoftwareFoundation/incubator-ariatosca/master.svg)](https://ci.appveyor.com/project/ApacheSoftwareFoundation/incubator-ariatosca/history)
-[![License](https://img.shields.io/github/license/apache/incubator-ariatosca.svg)](http://www.apache.org/licenses/LICENSE-2.0)
-[![PyPI release](https://img.shields.io/pypi/v/ariatosca.svg)](https://pypi.python.org/pypi/ariatosca)
-![Python Versions](https://img.shields.io/pypi/pyversions/ariatosca.svg)
-![Wheel](https://img.shields.io/pypi/wheel/ariatosca.svg)
-![Contributors](https://img.shields.io/github/contributors/apache/incubator-ariatosca.svg)
-[![Open Pull Requests](https://img.shields.io/github/issues-pr/apache/incubator-ariatosca.svg)](https://github.com/apache/incubator-ariatosca/pulls)
-[![Closed Pull Requests](https://img.shields.io/github/issues-pr-closed-raw/apache/incubator-ariatosca.svg)](https://github.com/apache/incubator-ariatosca/pulls?q=is%3Apr+is%3Aclosed)
-
-
-What is ARIA?
-----------------
-
-[ARIA](http://ariatosca.incubator.apache.org/) is a an open-source, [TOSCA](https://www.oasis-open.org/committees/tosca/)-based, lightweight library and CLI for orchestration and for consumption by projects building TOSCA-based solutions for resources and services orchestration.
-
-ARIA can be utilized by any organization that wants to implement TOSCA-based orchestration in its solutions, whether a multi-cloud enterprise application, or an NFV or SDN solution for multiple virtual infrastructure managers.
-
-With ARIA, you can utilize TOSCA's cloud portability out-of-the-box, to develop, test and run your applications, from template to deployment.
-
-ARIA is an incubation project under the [Apache Software Foundation](https://www.apache.org/).
-
-
-Installation
-----------------
-
-ARIA is [available on PyPI](https://pypi.python.org/pypi/ariatosca).    
-
-To install ARIA directly from PyPI (using a `wheel`), use:
-
-    pip install aria
-
-
-To install ARIA from source, download the source tarball from [PyPI](https://pypi.python.org/pypi/ariatosca),
-extract it, and then when inside the extracted directory, use:
-
-    pip install .
-
-The source package comes along with relevant examples, documentation,
-`requirements.txt` (for installing specifically the frozen dependencies' versions with which ARIA was tested) and more.
-
-<br>
-Note that for the `pip install` commands mentioned above, you must use a privileged user, or use virtualenv.
-<br><br><br>
-
-ARIA itself is in a `wheel` format compatible with all platforms. 
-Some dependencies, however, might require compilation (based on a given platform), and therefore possibly some system dependencies are required as well.
-
-On Ubuntu or other Debian-based systems:
-
-	sudo apt install python-setuptools python-dev build-essential libssl-dev libffi-dev
-
-On Archlinux:
-
-	sudo pacman -S python-setuptools
-
-
-ARIA requires Python 2.6/2.7. Python 3+ is currently not supported.
-
-
-Getting Started
----------------
-
-This section will describe how to run a simple "Hello World" example.
-
-First, provide ARIA with the ARIA "hello world" service-template and name it (e.g. `my-service-template`):
-
-	aria service-templates store examples/hello-world/helloworld.yaml my-service-template
-	
-Now create a service based on this service-template and name it (e.g. `my-service`):
-	
-	aria services create my-service -t my-service-template
-	
-Finally, start an `install` workflow execution on `my-service` like so:
-
-	aria executions start install -s my-service
-
-<br>
-You should now have a simple web-server running on your local machine.
-You can try visiting http://localhost:9090 to view your deployed application.
-
-To uninstall and clean your environment, follow these steps:
-
-    aria executions start uninstall -s my-service
-    aria services delete my-service
-    aria service-templates delete my-service-template
-
-
-Contribution
-------------
-
-You are welcome and encouraged to participate and contribute to the ARIA project.
-
-Please see our guide to [Contributing to ARIA](https://cwiki.apache.org/confluence/display/ARIATOSCA/Contributing+to+ARIA).
-
-Feel free to also provide feedback on the mailing lists (see [Resources](#user-content-resources) section).
-
-
-Resources
----------
-
-* [ARIA homepage](http://ariatosca.incubator.apache.org/)
-* [ARIA wiki](https://cwiki.apache.org/confluence/display/AriaTosca)
-* [Issue tracker](https://issues.apache.org/jira/browse/ARIA)
-
-* Dev mailing list: dev@ariatosca.incubator.apache.org
-* User mailing list: user@ariatosca.incubator.apache.org
-
-Subscribe by sending a mail to `<group>-subscribe@ariatosca.incubator.apache.org` (e.g. `dev-subscribe@ariatosca.incubator.apache.org`).
-See information on how to subscribe to mailing list [here](https://www.apache.org/foundation/mailinglists.html).
-
-For past correspondence, see the [dev mailing list archive](http://mail-archives.apache.org/mod_mbox/incubator-ariatosca-dev/).
-
-
-License
--------
-ARIA is licensed under the [Apache License 2.0](https://github.com/apache/incubator-ariatosca/blob/master/LICENSE).

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/950fd506/README.rst
----------------------------------------------------------------------
diff --git a/README.rst b/README.rst
new file mode 100644
index 0000000..cad30b4
--- /dev/null
+++ b/README.rst
@@ -0,0 +1,171 @@
+ARIA
+====
+
+| |Build Status|
+| |Appveyor Build Status|
+| |License|
+| |PyPI release|
+| |Python Versions|
+| |Wheel|
+| |Contributors|
+| |Open Pull Requests|
+| |Closed Pull Requests|
+
+What is ARIA?
+-------------
+
+`ARIA <http://ariatosca.incubator.apache.org/>`__ is a an open-source,
+`TOSCA <https://www.oasis-open.org/committees/tosca/>`__-based,
+lightweight library and CLI for orchestration and for consumption by
+projects building TOSCA-based solutions for resources and services
+orchestration.
+
+ARIA can be utilized by any organization that wants to implement
+TOSCA-based orchestration in its solutions, whether a multi-cloud
+enterprise application, or an NFV or SDN solution for multiple virtual
+infrastructure managers.
+
+With ARIA, you can utilize TOSCA's cloud portability out-of-the-box, to
+develop, test and run your applications, from template to deployment.
+
+ARIA is an incubation project under the `Apache Software
+Foundation <https://www.apache.org/>`__.
+
+Installation
+------------
+
+ARIA is `available on PyPI <https://pypi.python.org/pypi/ariatosca>`__.
+
+To install ARIA directly from PyPI (using a ``wheel``), use:
+
+::
+
+    pip install aria
+
+| To install ARIA from source, download the source tarball from
+`PyPI <https://pypi.python.org/pypi/ariatosca>`__,
+| extract it, and then when inside the extracted directory, use:
+
+::
+
+    pip install .
+
+| The source package comes along with relevant examples, documentation,
+| ``requirements.txt`` (for installing specifically the frozen
+dependencies' versions with which ARIA was tested) and more.
+
+|
+| Note that for the ``pip install`` commands mentioned above, you must
+use a privileged user, or use virtualenv.
+|
+
+| ARIA itself is in a ``wheel`` format compatible with all platforms.
+| Some dependencies, however, might require compilation (based on a
+given platform), and therefore possibly some system dependencies are
+required as well.
+
+On Ubuntu or other Debian-based systems:
+
+::
+
+    sudo apt install python-setuptools python-dev build-essential libssl-dev libffi-dev
+
+On Archlinux:
+
+::
+
+    sudo pacman -S python-setuptools
+
+ARIA requires Python 2.6/2.7. Python 3+ is currently not supported.
+
+Getting Started
+---------------
+
+This section will describe how to run a simple "Hello World" example.
+
+First, provide ARIA with the ARIA "hello world" service-template and
+name it (e.g. ``my-service-template``):
+
+::
+
+    aria service-templates store examples/hello-world/helloworld.yaml my-service-template
+
+Now create a service based on this service-template and name it (e.g.
+``my-service``):
+
+::
+
+    aria services create my-service -t my-service-template
+
+Finally, start an ``install`` workflow execution on ``my-service`` like
+so:
+
+::
+
+    aria executions start install -s my-service
+
+|
+| You should now have a simple web-server running on your local machine.
+| You can try visiting http://localhost:9090 to view your deployed
+application.
+
+To uninstall and clean your environment, follow these steps:
+
+::
+
+    aria executions start uninstall -s my-service
+    aria services delete my-service
+    aria service-templates delete my-service-template
+
+Contribution
+------------
+
+You are welcome and encouraged to participate and contribute to the ARIA
+project.
+
+Please see our guide to `Contributing to
+ARIA <https://cwiki.apache.org/confluence/display/ARIATOSCA/Contributing+to+ARIA>`__.
+
+Feel free to also provide feedback on the mailing lists (see
+`Resources <#user-content-resources>`__ section).
+
+Resources
+---------
+
+-  `ARIA homepage <http://ariatosca.incubator.apache.org/>`__
+-  `ARIA wiki <https://cwiki.apache.org/confluence/display/AriaTosca>`__
+-  `Issue tracker <https://issues.apache.org/jira/browse/ARIA>`__
+
+-  Dev mailing list: dev@ariatosca.incubator.apache.org
+-  User mailing list: user@ariatosca.incubator.apache.org
+
+| Subscribe by sending a mail to
+``<group>-subscribe@ariatosca.incubator.apache.org`` (e.g.
+``dev-subscribe@ariatosca.incubator.apache.org``).
+| See information on how to subscribe to mailing list
+`here <https://www.apache.org/foundation/mailinglists.html>`__.
+
+For past correspondence, see the `dev mailing list
+archive <http://mail-archives.apache.org/mod_mbox/incubator-ariatosca-dev/>`__.
+
+License
+-------
+
+ARIA is licensed under the `Apache License
+2.0 <https://github.com/apache/incubator-ariatosca/blob/master/LICENSE>`__.
+
+.. |Build Status| image:: https://img.shields.io/travis/apache/incubator-ariatosca/master.svg
+   :target: https://travis-ci.org/apache/incubator-ariatosca
+.. |Appveyor Build Status| image:: https://img.shields.io/appveyor/ci/ApacheSoftwareFoundation/incubator-ariatosca/master.svg
+   :target: https://ci.appveyor.com/project/ApacheSoftwareFoundation/incubator-ariatosca/history
+.. |License| image:: https://img.shields.io/github/license/apache/incubator-ariatosca.svg
+   :target: http://www.apache.org/licenses/LICENSE-2.0
+.. |PyPI release| image:: https://img.shields.io/pypi/v/ariatosca.svg
+   :target: https://pypi.python.org/pypi/ariatosca
+.. |Python Versions| image:: https://img.shields.io/pypi/pyversions/ariatosca.svg
+.. |Wheel| image:: https://img.shields.io/pypi/wheel/ariatosca.svg
+.. |Contributors| image:: https://img.shields.io/github/contributors/apache/incubator-ariatosca.svg
+.. |Open Pull Requests| image:: https://img.shields.io/github/issues-pr/apache/incubator-ariatosca.svg
+   :target: https://github.com/apache/incubator-ariatosca/pulls
+.. |Closed Pull Requests| image:: https://img.shields.io/github/issues-pr-closed-raw/apache/incubator-ariatosca.svg
+   :target: https://github.com/apache/incubator-ariatosca/pulls?q=is%3Apr+is%3Aclosed

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/950fd506/TODO.md
----------------------------------------------------------------------
diff --git a/TODO.md b/TODO.md
deleted file mode 100644
index ab32cf7..0000000
--- a/TODO.md
+++ /dev/null
@@ -1,8 +0,0 @@
-- aria/parser/extension_tools.py:66:    #  todo: maybe add replace action and check in add that we don't replace...
-- aria/parser/framework/elements/policies.py:128:    #  TODO: policies should be implemented according to TOSCA as generic types
-- aria/parser/framework/elements/node_templates.py:42:    #  TODO: Capabilities should be implemented according to TOSCA as generic types
-- aria/parser/framework/functions.py:25:    #  TODO: ugly huck for now..., sort the imports when you have time
-- aria/parser/framework/parser.py:258:    #  TODO: need to clean up
-- tests/parser/test_parser_api.py:430:    #  TODO: assert node-type's default and description values once
-- tests/parser/test_parser_api.py:450:    #  TODO: assert type's default and description values once 'type' is
-- tests/parser/test_parser_api.py:472:    #  TODO: assert type's default and description values once 'type' is

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/950fd506/setup.py
----------------------------------------------------------------------
diff --git a/setup.py b/setup.py
index d43ce91..8d5f463 100644
--- a/setup.py
+++ b/setup.py
@@ -39,6 +39,9 @@ root_dir = os.path.dirname(__file__)
 with open(os.path.join(root_dir, 'VERSION')) as version_file:
     __version__ = version_file.read().strip()
 
+with open(os.path.join(root_dir, 'README.rst')) as readme:
+    long_description = readme.read()
+
 install_requires = []
 extras_require = {}
 
@@ -106,10 +109,13 @@ setup(
     name=_PACKAGE_NAME,
     version=__version__,
     description='ARIA',
+    long_description=long_description,
     license='Apache License 2.0',
     author='aria',
     author_email='dev@ariatosca.incubator.apache.org',
-    url='http://ariatosca.org',
+    url='http://ariatosca.incubator.apache.org/',
+    download_url=(
+        'https://dist.apache.org/repos/dist/release/incubator/ariatosca/' + __version__),
     classifiers=[
         'Development Status :: 4 - Beta',
         'Environment :: Console',


[3/6] incubator-ariatosca git commit: ARIA-213 sporadic tests failures over locked database issue

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

Increased the timeout for acquiring database lock (for sqlite based db).


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

Branch: refs/heads/ARIA-54-prepare-aria-packaging
Commit: cd830731bff21e836b5e661623b269aa40f92f52
Parents: 180e0a1
Author: max-orlov <ma...@gigaspaces.com>
Authored: Tue Jun 6 17:05:21 2017 +0300
Committer: max-orlov <ma...@gigaspaces.com>
Committed: Wed Jun 7 17:21:01 2017 +0300

----------------------------------------------------------------------
 aria/storage/sql_mapi.py | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/cd830731/aria/storage/sql_mapi.py
----------------------------------------------------------------------
diff --git a/aria/storage/sql_mapi.py b/aria/storage/sql_mapi.py
index 4d7e233..bb6223a 100644
--- a/aria/storage/sql_mapi.py
+++ b/aria/storage/sql_mapi.py
@@ -405,7 +405,8 @@ def init_storage(base_dir, filename='db.sqlite'):
 
         path=os.path.join(base_dir, filename))
 
-    engine = create_engine(uri)
+    engine = create_engine(uri, connect_args=dict(timeout=15))
+
     session_factory = orm.sessionmaker(bind=engine)
     session = orm.scoped_session(session_factory=session_factory)
 


[2/6] incubator-ariatosca git commit: ARIA-262 Inconsistent node attributes behavior

Posted by ra...@apache.org.
ARIA-262 Inconsistent node attributes behavior

Inroduced a more comprehensive way to instrument relationship attributes.

Old behavior instrumented attributes only if they were accessed directly from the
parent model. Traversing the storage made the access to an attribute inconsistent.

The new solution enables encapsulating the attributes disregarding the way they
were retrieved.


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

Branch: refs/heads/ARIA-54-prepare-aria-packaging
Commit: 180e0a1cf1ad6da0ddd611b90a58e71acbea52e7
Parents: e4d0036
Author: max-orlov <ma...@gigaspaces.com>
Authored: Wed May 31 21:07:49 2017 +0300
Committer: max-orlov <ma...@gigaspaces.com>
Committed: Tue Jun 6 15:20:12 2017 +0300

----------------------------------------------------------------------
 .../context/collection_instrumentation.py       | 242 ---------------
 aria/orchestrator/context/operation.py          |  21 +-
 aria/orchestrator/context/toolbelt.py           |   6 +-
 aria/orchestrator/decorators.py                 |   6 +-
 .../execution_plugin/ctx_proxy/server.py        |  17 +-
 aria/storage/api.py                             |  10 +
 aria/storage/collection_instrumentation.py      | 306 +++++++++++++++++++
 aria/storage/core.py                            |  15 +
 aria/storage/sql_mapi.py                        |  14 +-
 aria/utils/imports.py                           |   2 +-
 .../context/test_collection_instrumentation.py  | 150 ++++++---
 .../execution_plugin/test_ctx_proxy_server.py   |   2 +-
 tests/orchestrator/execution_plugin/test_ssh.py |  10 +-
 tests/orchestrator/workflows/core/test_task.py  |   2 +-
 .../executor/test_process_executor_extension.py |   3 +-
 15 files changed, 490 insertions(+), 316 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/180e0a1c/aria/orchestrator/context/collection_instrumentation.py
----------------------------------------------------------------------
diff --git a/aria/orchestrator/context/collection_instrumentation.py b/aria/orchestrator/context/collection_instrumentation.py
deleted file mode 100644
index 8f80d4a..0000000
--- a/aria/orchestrator/context/collection_instrumentation.py
+++ /dev/null
@@ -1,242 +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 functools import partial
-
-from aria.modeling import models
-
-
-class _InstrumentedCollection(object):
-
-    def __init__(self,
-                 model,
-                 parent,
-                 field_name,
-                 seq=None,
-                 is_top_level=True,
-                 **kwargs):
-        self._model = model
-        self._parent = parent
-        self._field_name = field_name
-        self._is_top_level = is_top_level
-        self._load(seq, **kwargs)
-
-    @property
-    def _raw(self):
-        raise NotImplementedError
-
-    def _load(self, seq, **kwargs):
-        """
-        Instantiates the object from existing seq.
-
-        :param seq: the original sequence to load from
-        :return:
-        """
-        raise NotImplementedError
-
-    def _set(self, key, value):
-        """
-        set the changes for the current object (not in the db)
-
-        :param key:
-        :param value:
-        :return:
-        """
-        raise NotImplementedError
-
-    def _del(self, collection, key):
-        raise NotImplementedError
-
-    def _instrument(self, key, value):
-        """
-        Instruments any collection to track changes (and ease of access)
-        :param key:
-        :param value:
-        :return:
-        """
-        if isinstance(value, _InstrumentedCollection):
-            return value
-        elif isinstance(value, dict):
-            instrumentation_cls = _InstrumentedDict
-        elif isinstance(value, list):
-            instrumentation_cls = _InstrumentedList
-        else:
-            return value
-
-        return instrumentation_cls(self._model, self, key, value, False)
-
-    @staticmethod
-    def _raw_value(value):
-        """
-        Get the raw value.
-        :param value:
-        :return:
-        """
-        if isinstance(value, models.Attribute):
-            return value.value
-        return value
-
-    @staticmethod
-    def _encapsulate_value(key, value):
-        """
-        Create a new item cls if needed.
-        :param key:
-        :param value:
-        :return:
-        """
-        if isinstance(value, models.Attribute):
-            return value
-        # If it is not wrapped
-        return models.Attribute.wrap(key, value)
-
-    def __setitem__(self, key, value):
-        """
-        Update the values in both the local and the db locations.
-        :param key:
-        :param value:
-        :return:
-        """
-        self._set(key, value)
-        if self._is_top_level:
-            # We are at the top level
-            field = getattr(self._parent, self._field_name)
-            mapi = getattr(self._model, models.Attribute.__modelname__)
-            value = self._set_field(field,
-                                    key,
-                                    value if key in field else self._encapsulate_value(key, value))
-            mapi.update(value)
-        else:
-            # We are not at the top level
-            self._set_field(self._parent, self._field_name, self)
-
-    def _set_field(self, collection, key, value):
-        """
-        enables updating the current change in the ancestors
-        :param collection: the collection to change
-        :param key: the key for the specific field
-        :param value: the new value
-        :return:
-        """
-        if isinstance(value, _InstrumentedCollection):
-            value = value._raw
-        if key in collection and isinstance(collection[key], models.Attribute):
-            if isinstance(collection[key], _InstrumentedCollection):
-                self._del(collection, key)
-            collection[key].value = value
-        else:
-            collection[key] = value
-        return collection[key]
-
-    def __deepcopy__(self, *args, **kwargs):
-        return self._raw
-
-
-class _InstrumentedDict(_InstrumentedCollection, dict):
-
-    def _load(self, dict_=None, **kwargs):
-        dict.__init__(
-            self,
-            tuple((key, self._raw_value(value)) for key, value in (dict_ or {}).items()),
-            **kwargs)
-
-    def update(self, dict_=None, **kwargs):
-        dict_ = dict_ or {}
-        for key, value in dict_.items():
-            self[key] = value
-        for key, value in kwargs.items():
-            self[key] = value
-
-    def __getitem__(self, key):
-        return self._instrument(key, dict.__getitem__(self, key))
-
-    def _set(self, key, value):
-        dict.__setitem__(self, key, self._raw_value(value))
-
-    @property
-    def _raw(self):
-        return dict(self)
-
-    def _del(self, collection, key):
-        del collection[key]
-
-
-class _InstrumentedList(_InstrumentedCollection, list):
-
-    def _load(self, list_=None, **kwargs):
-        list.__init__(self, list(item for item in list_ or []))
-
-    def append(self, value):
-        self.insert(len(self), value)
-
-    def insert(self, index, value):
-        list.insert(self, index, self._raw_value(value))
-        if self._is_top_level:
-            field = getattr(self._parent, self._field_name)
-            field.insert(index, self._encapsulate_value(index, value))
-        else:
-            self._parent[self._field_name] = self
-
-    def __getitem__(self, key):
-        return self._instrument(key, list.__getitem__(self, key))
-
-    def _set(self, key, value):
-        list.__setitem__(self, key, value)
-
-    def _del(self, collection, key):
-        del collection[key]
-
-    @property
-    def _raw(self):
-        return list(self)
-
-
-class _InstrumentedModel(object):
-
-    def __init__(self, field_name, original_model, model_storage):
-        super(_InstrumentedModel, self).__init__()
-        self._field_name = field_name
-        self._model_storage = model_storage
-        self._original_model = original_model
-        self._apply_instrumentation()
-
-    def __getattr__(self, item):
-        return getattr(self._original_model, item)
-
-    def _apply_instrumentation(self):
-
-        field = getattr(self._original_model, self._field_name)
-
-        # Preserve the original value. e.g. original attributes would be located under
-        # _attributes
-        setattr(self, '_{0}'.format(self._field_name), field)
-
-        # set instrumented value
-        setattr(self, self._field_name, _InstrumentedDict(self._model_storage,
-                                                          self._original_model,
-                                                          self._field_name,
-                                                          field))
-
-
-def instrument_collection(field_name, func=None):
-    if func is None:
-        return partial(instrument_collection, field_name)
-
-    def _wrapper(*args, **kwargs):
-        original_model = func(*args, **kwargs)
-        return type('Instrumented{0}'.format(original_model.__class__.__name__),
-                    (_InstrumentedModel, ),
-                    {})(field_name, original_model, args[0].model)
-
-    return _wrapper

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/180e0a1c/aria/orchestrator/context/operation.py
----------------------------------------------------------------------
diff --git a/aria/orchestrator/context/operation.py b/aria/orchestrator/context/operation.py
index f0ba337..af7220d 100644
--- a/aria/orchestrator/context/operation.py
+++ b/aria/orchestrator/context/operation.py
@@ -21,10 +21,7 @@ import threading
 
 import aria
 from aria.utils import file
-from . import (
-    common,
-    collection_instrumentation
-)
+from . import common
 
 
 class BaseOperationContext(common.BaseContext):
@@ -32,6 +29,13 @@ class BaseOperationContext(common.BaseContext):
     Context object used during operation creation and execution
     """
 
+    INSTRUMENTATION_FIELDS = (
+        aria.modeling.models.Node.attributes,
+        aria.modeling.models.Node.properties,
+        aria.modeling.models.NodeTemplate.attributes,
+        aria.modeling.models.NodeTemplate.properties
+    )
+
     def __init__(self, task_id, actor_id, **kwargs):
         self._task_id = task_id
         self._actor_id = actor_id
@@ -76,7 +80,6 @@ class BaseOperationContext(common.BaseContext):
 
     @property
     def serialization_dict(self):
-        context_cls = self.__class__
         context_dict = {
             'name': self.name,
             'service_id': self._service_id,
@@ -89,7 +92,7 @@ class BaseOperationContext(common.BaseContext):
             'logger_level': self.logger.level
         }
         return {
-            'context_cls': context_cls,
+            'context_cls': self.__class__,
             'context': context_dict
         }
 
@@ -117,7 +120,6 @@ class NodeOperationContext(BaseOperationContext):
     """
 
     @property
-    @collection_instrumentation.instrument_collection('attributes')
     def node_template(self):
         """
         the node of the current operation
@@ -126,7 +128,6 @@ class NodeOperationContext(BaseOperationContext):
         return self.node.node_template
 
     @property
-    @collection_instrumentation.instrument_collection('attributes')
     def node(self):
         """
         The node instance of the current operation
@@ -141,7 +142,6 @@ class RelationshipOperationContext(BaseOperationContext):
     """
 
     @property
-    @collection_instrumentation.instrument_collection('attributes')
     def source_node_template(self):
         """
         The source node
@@ -150,7 +150,6 @@ class RelationshipOperationContext(BaseOperationContext):
         return self.source_node.node_template
 
     @property
-    @collection_instrumentation.instrument_collection('attributes')
     def source_node(self):
         """
         The source node instance
@@ -159,7 +158,6 @@ class RelationshipOperationContext(BaseOperationContext):
         return self.relationship.source_node
 
     @property
-    @collection_instrumentation.instrument_collection('attributes')
     def target_node_template(self):
         """
         The target node
@@ -168,7 +166,6 @@ class RelationshipOperationContext(BaseOperationContext):
         return self.target_node.node_template
 
     @property
-    @collection_instrumentation.instrument_collection('attributes')
     def target_node(self):
         """
         The target node instance

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/180e0a1c/aria/orchestrator/context/toolbelt.py
----------------------------------------------------------------------
diff --git a/aria/orchestrator/context/toolbelt.py b/aria/orchestrator/context/toolbelt.py
index 5788ee7..b5a54a9 100644
--- a/aria/orchestrator/context/toolbelt.py
+++ b/aria/orchestrator/context/toolbelt.py
@@ -33,11 +33,7 @@ class NodeToolBelt(object):
         :return:
         """
         assert isinstance(self._op_context, operation.NodeOperationContext)
-        host = self._op_context.node.host
-        ip = host.attributes.get('ip')
-        if ip:
-            return ip.value
-
+        return self._op_context.node.host.attributes.get('ip')
 
 
 class RelationshipToolBelt(object):

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/180e0a1c/aria/orchestrator/decorators.py
----------------------------------------------------------------------
diff --git a/aria/orchestrator/decorators.py b/aria/orchestrator/decorators.py
index 4051a54..80f6962 100644
--- a/aria/orchestrator/decorators.py
+++ b/aria/orchestrator/decorators.py
@@ -68,11 +68,13 @@ def operation(func=None, toolbelt=False, suffix_template='', logging_handlers=No
 
     @wraps(func)
     def _wrapper(**func_kwargs):
+        ctx = func_kwargs['ctx']
         if toolbelt:
-            operation_toolbelt = context.toolbelt(func_kwargs['ctx'])
+            operation_toolbelt = context.toolbelt(ctx)
             func_kwargs.setdefault('toolbelt', operation_toolbelt)
         validate_function_arguments(func, func_kwargs)
-        return func(**func_kwargs)
+        with ctx.model.instrument(*ctx.INSTRUMENTATION_FIELDS):
+            return func(**func_kwargs)
     return _wrapper
 
 

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/180e0a1c/aria/orchestrator/execution_plugin/ctx_proxy/server.py
----------------------------------------------------------------------
diff --git a/aria/orchestrator/execution_plugin/ctx_proxy/server.py b/aria/orchestrator/execution_plugin/ctx_proxy/server.py
index 102ff9a..50d4c3a 100644
--- a/aria/orchestrator/execution_plugin/ctx_proxy/server.py
+++ b/aria/orchestrator/execution_plugin/ctx_proxy/server.py
@@ -117,14 +117,15 @@ class CtxProxy(object):
 
     def _process(self, request):
         try:
-            typed_request = json.loads(request)
-            args = typed_request['args']
-            payload = _process_ctx_request(self.ctx, args)
-            result_type = 'result'
-            if isinstance(payload, exceptions.ScriptException):
-                payload = dict(message=str(payload))
-                result_type = 'stop_operation'
-            result = {'type': result_type, 'payload': payload}
+            with self.ctx.model.instrument(*self.ctx.INSTRUMENTATION_FIELDS):
+                typed_request = json.loads(request)
+                args = typed_request['args']
+                payload = _process_ctx_request(self.ctx, args)
+                result_type = 'result'
+                if isinstance(payload, exceptions.ScriptException):
+                    payload = dict(message=str(payload))
+                    result_type = 'stop_operation'
+                result = {'type': result_type, 'payload': payload}
         except Exception as e:
             traceback_out = StringIO.StringIO()
             traceback.print_exc(file=traceback_out)

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/180e0a1c/aria/storage/api.py
----------------------------------------------------------------------
diff --git a/aria/storage/api.py b/aria/storage/api.py
index ed8a2ff..3304721 100644
--- a/aria/storage/api.py
+++ b/aria/storage/api.py
@@ -15,6 +15,7 @@
 """
 General storage API
 """
+import threading
 
 
 class StorageAPI(object):
@@ -45,6 +46,15 @@ class ModelAPI(StorageAPI):
         super(ModelAPI, self).__init__(**kwargs)
         self._model_cls = model_cls
         self._name = name or model_cls.__modelname__
+        self._thread_local = threading.local()
+        self._thread_local._instrumentation = []
+
+    @property
+    def _instrumentation(self):
+        if not hasattr(self._thread_local, '_instrumentation'):
+            self._thread_local._instrumentation = []
+        return self._thread_local._instrumentation
+
 
     @property
     def name(self):

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/180e0a1c/aria/storage/collection_instrumentation.py
----------------------------------------------------------------------
diff --git a/aria/storage/collection_instrumentation.py b/aria/storage/collection_instrumentation.py
new file mode 100644
index 0000000..27d8322
--- /dev/null
+++ b/aria/storage/collection_instrumentation.py
@@ -0,0 +1,306 @@
+# 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 . import exceptions
+
+
+class _InstrumentedCollection(object):
+
+    def __init__(self,
+                 mapi,
+                 parent,
+                 field_name,
+                 field_cls,
+                 seq=None,
+                 is_top_level=True,
+                 **kwargs):
+        self._mapi = mapi
+        self._parent = parent
+        self._field_name = field_name
+        self._is_top_level = is_top_level
+        self._field_cls = field_cls
+        self._load(seq, **kwargs)
+
+    @property
+    def _raw(self):
+        raise NotImplementedError
+
+    def _load(self, seq, **kwargs):
+        """
+        Instantiates the object from existing seq.
+
+        :param seq: the original sequence to load from
+        :return:
+        """
+        raise NotImplementedError
+
+    def _set(self, key, value):
+        """
+        set the changes for the current object (not in the db)
+
+        :param key:
+        :param value:
+        :return:
+        """
+        raise NotImplementedError
+
+    def _del(self, collection, key):
+        raise NotImplementedError
+
+    def _instrument(self, key, value):
+        """
+        Instruments any collection to track changes (and ease of access)
+        :param key:
+        :param value:
+        :return:
+        """
+        if isinstance(value, _InstrumentedCollection):
+            return value
+        elif isinstance(value, dict):
+            instrumentation_cls = _InstrumentedDict
+        elif isinstance(value, list):
+            instrumentation_cls = _InstrumentedList
+        else:
+            return value
+
+        return instrumentation_cls(self._mapi, self, key, self._field_cls, value, False)
+
+    def _raw_value(self, value):
+        """
+        Get the raw value.
+        :param value:
+        :return:
+        """
+        if isinstance(value, self._field_cls):
+            return value.value
+        return value
+
+    def _encapsulate_value(self, key, value):
+        """
+        Create a new item cls if needed.
+        :param key:
+        :param value:
+        :return:
+        """
+        if isinstance(value, self._field_cls):
+            return value
+        # If it is not wrapped
+        return self._field_cls.wrap(key, value)
+
+    def __setitem__(self, key, value):
+        """
+        Update the values in both the local and the db locations.
+        :param key:
+        :param value:
+        :return:
+        """
+        self._set(key, value)
+        if self._is_top_level:
+            # We are at the top level
+            field = getattr(self._parent, self._field_name)
+            self._set_field(
+                field, key, value if key in field else self._encapsulate_value(key, value))
+            self._mapi.update(self._parent)
+        else:
+            # We are not at the top level
+            self._set_field(self._parent, self._field_name, self)
+
+    def _set_field(self, collection, key, value):
+        """
+        enables updating the current change in the ancestors
+        :param collection: the collection to change
+        :param key: the key for the specific field
+        :param value: the new value
+        :return:
+        """
+        if isinstance(value, _InstrumentedCollection):
+            value = value._raw
+        if key in collection and isinstance(collection[key], self._field_cls):
+            if isinstance(collection[key], _InstrumentedCollection):
+                self._del(collection, key)
+            collection[key].value = value
+        else:
+            collection[key] = value
+        return collection[key]
+
+    def __deepcopy__(self, *args, **kwargs):
+        return self._raw
+
+
+class _InstrumentedDict(_InstrumentedCollection, dict):
+
+    def _load(self, dict_=None, **kwargs):
+        dict.__init__(
+            self,
+            tuple((key, self._raw_value(value)) for key, value in (dict_ or {}).items()),
+            **kwargs)
+
+    def update(self, dict_=None, **kwargs):
+        dict_ = dict_ or {}
+        for key, value in dict_.items():
+            self[key] = value
+        for key, value in kwargs.items():
+            self[key] = value
+
+    def __getitem__(self, key):
+        return self._instrument(key, dict.__getitem__(self, key))
+
+    def _set(self, key, value):
+        dict.__setitem__(self, key, self._raw_value(value))
+
+    @property
+    def _raw(self):
+        return dict(self)
+
+    def _del(self, collection, key):
+        del collection[key]
+
+
+class _InstrumentedList(_InstrumentedCollection, list):
+
+    def _load(self, list_=None, **kwargs):
+        list.__init__(self, list(item for item in list_ or []))
+
+    def append(self, value):
+        self.insert(len(self), value)
+
+    def insert(self, index, value):
+        list.insert(self, index, self._raw_value(value))
+        if self._is_top_level:
+            field = getattr(self._parent, self._field_name)
+            field.insert(index, self._encapsulate_value(index, value))
+        else:
+            self._parent[self._field_name] = self
+
+    def __getitem__(self, key):
+        return self._instrument(key, list.__getitem__(self, key))
+
+    def _set(self, key, value):
+        list.__setitem__(self, key, value)
+
+    def _del(self, collection, key):
+        del collection[key]
+
+    @property
+    def _raw(self):
+        return list(self)
+
+
+class _InstrumentedModel(object):
+
+    def __init__(self, original_model, mapi, instrumentation):
+        """
+        The original model
+        :param original_model: the model to be instrumented
+        :param mapi: the mapi for that model
+        """
+        super(_InstrumentedModel, self).__init__()
+        self._original_model = original_model
+        self._mapi = mapi
+        self._instrumentation = instrumentation
+        self._apply_instrumentation()
+
+    def __getattr__(self, item):
+        return_value = getattr(self._original_model, item)
+        if isinstance(return_value, self._original_model.__class__):
+            return _create_instrumented_model(return_value, self._mapi, self._instrumentation)
+        if isinstance(return_value, (list, dict)):
+            return _create_wrapped_model(return_value, self._mapi, self._instrumentation)
+        return return_value
+
+    def _apply_instrumentation(self):
+        for field in self._instrumentation:
+            field_name = field.key
+            field_cls = field.mapper.class_
+            field = getattr(self._original_model, field_name)
+
+            # Preserve the original value. e.g. original attributes would be located under
+            # _attributes
+            setattr(self, '_{0}'.format(field_name), field)
+
+            # set instrumented value
+            if isinstance(field, dict):
+                instrumentation_cls = _InstrumentedDict
+            elif isinstance(field, list):
+                instrumentation_cls = _InstrumentedList
+            else:
+                # TODO: raise proper error
+                raise exceptions.StorageError(
+                    "ARIA supports instrumentation for dict and list. Field {field} of the "
+                    "class {model} is of {type} type.".format(
+                        field=field,
+                        model=self._original_model,
+                        type=type(field)))
+
+            instrumented_class = instrumentation_cls(seq=field,
+                                                     parent=self._original_model,
+                                                     mapi=self._mapi,
+                                                     field_name=field_name,
+                                                     field_cls=field_cls)
+            setattr(self, field_name, instrumented_class)
+
+
+class _WrappedModel(object):
+
+    def __init__(self, wrapped, instrumentation, **kwargs):
+        """
+
+        :param instrumented_cls: The class to be instrumented
+        :param instrumentation_cls: the instrumentation cls
+        :param wrapped: the currently wrapped instance
+        :param kwargs: and kwargs to the passed to the instrumented class.
+        """
+        self._kwargs = kwargs
+        self._instrumentation = instrumentation
+        self._wrapped = wrapped
+
+    def _wrap(self, value):
+        if value.__class__ in (class_.class_ for class_ in self._instrumentation):
+            return _create_instrumented_model(
+                value, instrumentation=self._instrumentation, **self._kwargs)
+        elif hasattr(value, 'metadata') or isinstance(value, (dict, list)):
+            # Basically checks that the value is indeed an sqlmodel (it should have metadata)
+            return _create_wrapped_model(
+                value, instrumentation=self._instrumentation, **self._kwargs)
+        return value
+
+    def __getattr__(self, item):
+        if hasattr(self, '_wrapped'):
+            return self._wrap(getattr(self._wrapped, item))
+        else:
+            super(_WrappedModel, self).__getattribute__(item)
+
+    def __getitem__(self, item):
+        return self._wrap(self._wrapped[item])
+
+
+def _create_instrumented_model(original_model, mapi, instrumentation, **kwargs):
+    return type('Instrumented{0}'.format(original_model.__class__.__name__),
+                (_InstrumentedModel,),
+                {})(original_model, mapi, instrumentation, **kwargs)
+
+
+def _create_wrapped_model(original_model, mapi, instrumentation, **kwargs):
+    return type('Wrapped{0}'.format(original_model.__class__.__name__),
+                (_WrappedModel, ),
+                {})(original_model, instrumentation, mapi=mapi, **kwargs)
+
+
+def instrument(instrumentation, original_model, mapi):
+    for instrumented_field in instrumentation:
+        if isinstance(original_model, instrumented_field.class_):
+            return _create_instrumented_model(original_model, mapi, instrumentation)
+
+    return _create_wrapped_model(original_model, mapi, instrumentation)

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/180e0a1c/aria/storage/core.py
----------------------------------------------------------------------
diff --git a/aria/storage/core.py b/aria/storage/core.py
index 8302fc9..06c29e8 100644
--- a/aria/storage/core.py
+++ b/aria/storage/core.py
@@ -37,6 +37,8 @@ API:
     * drivers - module, a pool of ARIA standard drivers.
     * StorageDriver - class, abstract model implementation.
 """
+import copy
+from contextlib import contextmanager
 
 from aria.logger import LoggerMixin
 from . import sql_mapi
@@ -165,3 +167,16 @@ class ModelStorage(Storage):
         """
         for mapi in self.registered.values():
             mapi.drop()
+
+    @contextmanager
+    def instrument(self, *instrumentation):
+        original_instrumentation = {}
+
+        try:
+            for mapi in self.registered.values():
+                original_instrumentation[mapi] = copy.copy(mapi._instrumentation)
+                mapi._instrumentation.extend(instrumentation)
+            yield self
+        finally:
+            for mapi in self.registered.values():
+                mapi._instrumentation[:] = original_instrumentation[mapi]

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/180e0a1c/aria/storage/sql_mapi.py
----------------------------------------------------------------------
diff --git a/aria/storage/sql_mapi.py b/aria/storage/sql_mapi.py
index 730d007..4d7e233 100644
--- a/aria/storage/sql_mapi.py
+++ b/aria/storage/sql_mapi.py
@@ -29,6 +29,7 @@ from aria.utils.collections import OrderedDict
 from . import (
     api,
     exceptions,
+    collection_instrumentation
 )
 
 _predicates = {'ge': '__ge__',
@@ -63,7 +64,7 @@ class SQLAlchemyModelAPI(api.ModelAPI):
                 'Requested `{0}` with ID `{1}` was not found'
                 .format(self.model_cls.__name__, entry_id)
             )
-        return result
+        return self._instrument(result)
 
     def get_by_name(self, entry_name, include=None, **kwargs):
         assert hasattr(self.model_cls, 'name')
@@ -93,7 +94,7 @@ class SQLAlchemyModelAPI(api.ModelAPI):
 
         return ListResult(
             dict(total=total, size=size, offset=offset),
-            results
+            [self._instrument(result) for result in results]
         )
 
     def iter(self,
@@ -103,7 +104,8 @@ class SQLAlchemyModelAPI(api.ModelAPI):
              **kwargs):
         """Return a (possibly empty) list of `model_class` results
         """
-        return iter(self._get_query(include, filters, sort))
+        for result in self._get_query(include, filters, sort):
+            yield self._instrument(result)
 
     def put(self, entry, **kwargs):
         """Create a `model_class` instance from a serializable `model` object
@@ -378,6 +380,12 @@ class SQLAlchemyModelAPI(api.ModelAPI):
         for rel in instance.__mapper__.relationships:
             getattr(instance, rel.key)
 
+    def _instrument(self, model):
+        if self._instrumentation:
+            return collection_instrumentation.instrument(self._instrumentation, model, self)
+        else:
+            return model
+
 
 def init_storage(base_dir, filename='db.sqlite'):
     """

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/180e0a1c/aria/utils/imports.py
----------------------------------------------------------------------
diff --git a/aria/utils/imports.py b/aria/utils/imports.py
index 64a48cf..35aa0fc 100644
--- a/aria/utils/imports.py
+++ b/aria/utils/imports.py
@@ -17,8 +17,8 @@
 Utility methods for dynamically loading python code
 """
 
-import importlib
 import pkgutil
+import importlib
 
 
 def import_fullname(name, paths=None):

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/180e0a1c/tests/orchestrator/context/test_collection_instrumentation.py
----------------------------------------------------------------------
diff --git a/tests/orchestrator/context/test_collection_instrumentation.py b/tests/orchestrator/context/test_collection_instrumentation.py
index 1e6214a..ae3e8ac 100644
--- a/tests/orchestrator/context/test_collection_instrumentation.py
+++ b/tests/orchestrator/context/test_collection_instrumentation.py
@@ -15,8 +15,14 @@
 
 import pytest
 
-from aria.modeling.models import Attribute
-from aria.orchestrator.context import collection_instrumentation
+from aria.modeling import models
+from aria.storage import collection_instrumentation
+from aria.orchestrator.context import operation
+
+from tests import (
+    mock,
+    storage
+)
 
 
 class MockActor(object):
@@ -25,12 +31,16 @@ class MockActor(object):
         self.list_ = []
 
 
-class MockModel(object):
+class MockMAPI(object):
 
     def __init__(self):
-        self.attribute = type('MockModel', (object, ), {'model_cls': Attribute,
-                                                        'put': lambda *args, **kwargs: None,
-                                                        'update': lambda *args, **kwargs: None})()
+        pass
+
+    def put(self, *args, **kwargs):
+        pass
+
+    def update(self, *args, **kwargs):
+        pass
 
 
 class CollectionInstrumentation(object):
@@ -41,15 +51,15 @@ class CollectionInstrumentation(object):
 
     @pytest.fixture
     def model(self):
-        return MockModel()
+        return MockMAPI()
 
     @pytest.fixture
     def dict_(self, actor, model):
-        return collection_instrumentation._InstrumentedDict(model, actor, 'dict_')
+        return collection_instrumentation._InstrumentedDict(model, actor, 'dict_', models.Attribute)
 
     @pytest.fixture
     def list_(self, actor, model):
-        return collection_instrumentation._InstrumentedList(model, actor, 'list_')
+        return collection_instrumentation._InstrumentedList(model, actor, 'list_', models.Attribute)
 
 
 class TestDict(CollectionInstrumentation):
@@ -57,16 +67,16 @@ class TestDict(CollectionInstrumentation):
     def test_keys(self, actor, dict_):
         dict_.update(
             {
-                'key1': Attribute.wrap('key1', 'value1'),
-                'key2': Attribute.wrap('key2', 'value2')
+                'key1': models.Attribute.wrap('key1', 'value1'),
+                'key2': models.Attribute.wrap('key2', 'value2')
             }
         )
         assert sorted(dict_.keys()) == sorted(['key1', 'key2']) == sorted(actor.dict_.keys())
 
     def test_values(self, actor, dict_):
         dict_.update({
-            'key1': Attribute.wrap('key1', 'value1'),
-            'key2': Attribute.wrap('key1', 'value2')
+            'key1': models.Attribute.wrap('key1', 'value1'),
+            'key2': models.Attribute.wrap('key1', 'value2')
         })
         assert (sorted(dict_.values()) ==
                 sorted(['value1', 'value2']) ==
@@ -74,34 +84,34 @@ class TestDict(CollectionInstrumentation):
 
     def test_items(self, dict_):
         dict_.update({
-            'key1': Attribute.wrap('key1', 'value1'),
-            'key2': Attribute.wrap('key1', 'value2')
+            'key1': models.Attribute.wrap('key1', 'value1'),
+            'key2': models.Attribute.wrap('key1', 'value2')
         })
         assert sorted(dict_.items()) == sorted([('key1', 'value1'), ('key2', 'value2')])
 
     def test_iter(self, actor, dict_):
         dict_.update({
-            'key1': Attribute.wrap('key1', 'value1'),
-            'key2': Attribute.wrap('key1', 'value2')
+            'key1': models.Attribute.wrap('key1', 'value1'),
+            'key2': models.Attribute.wrap('key1', 'value2')
         })
         assert sorted(list(dict_)) == sorted(['key1', 'key2']) == sorted(actor.dict_.keys())
 
     def test_bool(self, dict_):
         assert not dict_
         dict_.update({
-            'key1': Attribute.wrap('key1', 'value1'),
-            'key2': Attribute.wrap('key1', 'value2')
+            'key1': models.Attribute.wrap('key1', 'value1'),
+            'key2': models.Attribute.wrap('key1', 'value2')
         })
         assert dict_
 
     def test_set_item(self, actor, dict_):
-        dict_['key1'] = Attribute.wrap('key1', 'value1')
+        dict_['key1'] = models.Attribute.wrap('key1', 'value1')
         assert dict_['key1'] == 'value1' == actor.dict_['key1'].value
-        assert isinstance(actor.dict_['key1'], Attribute)
+        assert isinstance(actor.dict_['key1'], models.Attribute)
 
     def test_nested(self, actor, dict_):
         dict_['key'] = {}
-        assert isinstance(actor.dict_['key'], Attribute)
+        assert isinstance(actor.dict_['key'], models.Attribute)
         assert dict_['key'] == actor.dict_['key'].value == {}
 
         dict_['key']['inner_key'] = 'value'
@@ -112,7 +122,7 @@ class TestDict(CollectionInstrumentation):
         assert dict_['key'].keys() == ['inner_key']
         assert dict_['key'].values() == ['value']
         assert dict_['key'].items() == [('inner_key', 'value')]
-        assert isinstance(actor.dict_['key'], Attribute)
+        assert isinstance(actor.dict_['key'], models.Attribute)
         assert isinstance(dict_['key'], collection_instrumentation._InstrumentedDict)
 
         dict_['key'].update({'updated_key': 'updated_value'})
@@ -123,7 +133,7 @@ class TestDict(CollectionInstrumentation):
         assert sorted(dict_['key'].values()) == sorted(['value', 'updated_value'])
         assert sorted(dict_['key'].items()) == sorted([('inner_key', 'value'),
                                                        ('updated_key', 'updated_value')])
-        assert isinstance(actor.dict_['key'], Attribute)
+        assert isinstance(actor.dict_['key'], models.Attribute)
         assert isinstance(dict_['key'], collection_instrumentation._InstrumentedDict)
 
         dict_.update({'key': 'override_value'})
@@ -131,12 +141,12 @@ class TestDict(CollectionInstrumentation):
         assert 'key' in dict_
         assert dict_['key'] == 'override_value'
         assert len(actor.dict_) == 1
-        assert isinstance(actor.dict_['key'], Attribute)
+        assert isinstance(actor.dict_['key'], models.Attribute)
         assert actor.dict_['key'].value == 'override_value'
 
     def test_get_item(self, actor, dict_):
-        dict_['key1'] = Attribute.wrap('key1', 'value1')
-        assert isinstance(actor.dict_['key1'], Attribute)
+        dict_['key1'] = models.Attribute.wrap('key1', 'value1')
+        assert isinstance(actor.dict_['key1'], models.Attribute)
 
     def test_update(self, actor, dict_):
         dict_['key1'] = 'value1'
@@ -145,7 +155,7 @@ class TestDict(CollectionInstrumentation):
         dict_.update(new_dict)
         assert len(dict_) == 2
         assert dict_['key2'] == 'value2'
-        assert isinstance(actor.dict_['key2'], Attribute)
+        assert isinstance(actor.dict_['key2'], models.Attribute)
 
         new_dict = {}
         new_dict.update(dict_)
@@ -172,20 +182,20 @@ class TestDict(CollectionInstrumentation):
 class TestList(CollectionInstrumentation):
 
     def test_append(self, actor, list_):
-        list_.append(Attribute.wrap('name', 'value1'))
+        list_.append(models.Attribute.wrap('name', 'value1'))
         list_.append('value2')
         assert len(actor.list_) == 2
         assert len(list_) == 2
-        assert isinstance(actor.list_[0], Attribute)
+        assert isinstance(actor.list_[0], models.Attribute)
         assert list_[0] == 'value1'
 
-        assert isinstance(actor.list_[1], Attribute)
+        assert isinstance(actor.list_[1], models.Attribute)
         assert list_[1] == 'value2'
 
         list_[0] = 'new_value1'
         list_[1] = 'new_value2'
-        assert isinstance(actor.list_[1], Attribute)
-        assert isinstance(actor.list_[1], Attribute)
+        assert isinstance(actor.list_[1], models.Attribute)
+        assert isinstance(actor.list_[1], models.Attribute)
         assert list_[0] == 'new_value1'
         assert list_[1] == 'new_value2'
 
@@ -214,12 +224,12 @@ class TestList(CollectionInstrumentation):
         list_.append([])
 
         list_[0].append('inner_item')
-        assert isinstance(actor.list_[0], Attribute)
+        assert isinstance(actor.list_[0], models.Attribute)
         assert len(list_) == 1
         assert list_[0][0] == 'inner_item'
 
         list_[0].append('new_item')
-        assert isinstance(actor.list_[0], Attribute)
+        assert isinstance(actor.list_[0], models.Attribute)
         assert len(list_) == 1
         assert list_[0][1] == 'new_item'
 
@@ -231,23 +241,85 @@ class TestDictList(CollectionInstrumentation):
     def test_dict_in_list(self, actor, list_):
         list_.append({})
         assert len(list_) == 1
-        assert isinstance(actor.list_[0], Attribute)
+        assert isinstance(actor.list_[0], models.Attribute)
         assert actor.list_[0].value == {}
 
         list_[0]['key'] = 'value'
         assert list_[0]['key'] == 'value'
         assert len(actor.list_) == 1
-        assert isinstance(actor.list_[0], Attribute)
+        assert isinstance(actor.list_[0], models.Attribute)
         assert actor.list_[0].value['key'] == 'value'
 
     def test_list_in_dict(self, actor, dict_):
         dict_['key'] = []
         assert len(dict_) == 1
-        assert isinstance(actor.dict_['key'], Attribute)
+        assert isinstance(actor.dict_['key'], models.Attribute)
         assert actor.dict_['key'].value == []
 
         dict_['key'].append('value')
         assert dict_['key'][0] == 'value'
         assert len(actor.dict_) == 1
-        assert isinstance(actor.dict_['key'], Attribute)
+        assert isinstance(actor.dict_['key'], models.Attribute)
         assert actor.dict_['key'].value[0] == 'value'
+
+
+class TestModelInstrumentation(object):
+
+    @pytest.fixture
+    def workflow_ctx(self, tmpdir):
+        context = mock.context.simple(str(tmpdir), inmemory=True)
+        yield context
+        storage.release_sqlite_storage(context.model)
+
+    def test_attributes_access(self, workflow_ctx):
+        node = workflow_ctx.model.node.list()[0]
+        task = models.Task(node=node)
+        workflow_ctx.model.task.put(task)
+
+        ctx = operation.NodeOperationContext(
+            task.id, node.id, name='', service_id=workflow_ctx.model.service.list()[0].id,
+            model_storage=workflow_ctx.model, resource_storage=workflow_ctx.resource,
+            execution_id=1)
+
+        def _run_assertions(is_under_ctx):
+            def ctx_assert(expr):
+                if is_under_ctx:
+                    assert expr
+                else:
+                    assert not expr
+
+            ctx_assert(isinstance(ctx.node.attributes,
+                                  collection_instrumentation._InstrumentedDict))
+            assert not isinstance(ctx.node.properties,
+                                  collection_instrumentation._InstrumentedCollection)
+
+            for rel in ctx.node.inbound_relationships:
+                ctx_assert(isinstance(rel, collection_instrumentation._WrappedModel))
+                ctx_assert(isinstance(rel.source_node.attributes,
+                                      collection_instrumentation._InstrumentedDict))
+                ctx_assert(isinstance(rel.target_node.attributes,
+                                      collection_instrumentation._InstrumentedDict))
+
+            for node in ctx.model.node:
+                ctx_assert(isinstance(node.attributes,
+                                      collection_instrumentation._InstrumentedDict))
+                assert not isinstance(node.properties,
+                                      collection_instrumentation._InstrumentedCollection)
+
+            for rel in ctx.model.relationship:
+                ctx_assert(isinstance(rel, collection_instrumentation._WrappedModel))
+
+                ctx_assert(isinstance(rel.source_node.attributes,
+                                      collection_instrumentation._InstrumentedDict))
+                ctx_assert(isinstance(rel.target_node.attributes,
+                                      collection_instrumentation._InstrumentedDict))
+
+                assert not isinstance(rel.source_node.properties,
+                                      collection_instrumentation._InstrumentedCollection)
+                assert not isinstance(rel.target_node.properties,
+                                      collection_instrumentation._InstrumentedCollection)
+
+        with ctx.model.instrument(models.Node.attributes):
+            _run_assertions(True)
+
+        _run_assertions(False)

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/180e0a1c/tests/orchestrator/execution_plugin/test_ctx_proxy_server.py
----------------------------------------------------------------------
diff --git a/tests/orchestrator/execution_plugin/test_ctx_proxy_server.py b/tests/orchestrator/execution_plugin/test_ctx_proxy_server.py
index 1b19fd9..7ab1bdb 100644
--- a/tests/orchestrator/execution_plugin/test_ctx_proxy_server.py
+++ b/tests/orchestrator/execution_plugin/test_ctx_proxy_server.py
@@ -138,7 +138,7 @@ class TestCtxProxy(object):
     @pytest.fixture
     def ctx(self, mocker):
         class MockCtx(object):
-            pass
+            INSTRUMENTATION_FIELDS = ()
         ctx = MockCtx()
         properties = {
             'prop1': 'value1',

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/180e0a1c/tests/orchestrator/execution_plugin/test_ssh.py
----------------------------------------------------------------------
diff --git a/tests/orchestrator/execution_plugin/test_ssh.py b/tests/orchestrator/execution_plugin/test_ssh.py
index 899a007..8b326e7 100644
--- a/tests/orchestrator/execution_plugin/test_ssh.py
+++ b/tests/orchestrator/execution_plugin/test_ssh.py
@@ -422,15 +422,24 @@ class TestFabricEnvHideGroupsAndRunCommands(object):
             raise RuntimeError
 
     class _Ctx(object):
+        INSTRUMENTATION_FIELDS = ()
+
         class Task(object):
             @staticmethod
             def abort(message=None):
                 models.Task.abort(message)
             actor = None
+
         class Actor(object):
             host = None
+
+        class Model(object):
+            @contextlib.contextmanager
+            def instrument(self, *args, **kwargs):
+                yield
         task = Task
         task.actor = Actor
+        model = Model()
         logger = logging.getLogger()
 
     @staticmethod
@@ -439,7 +448,6 @@ class TestFabricEnvHideGroupsAndRunCommands(object):
         yield
     _Ctx.logging_handlers = _mock_self_logging
 
-
     @pytest.fixture(autouse=True)
     def _setup(self, mocker):
         self.default_fabric_env = {

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/180e0a1c/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 a717e19..c0d3616 100644
--- a/tests/orchestrator/workflows/core/test_task.py
+++ b/tests/orchestrator/workflows/core/test_task.py
@@ -100,7 +100,7 @@ class TestOperationTask(object):
         storage_task = ctx.model.task.get_by_name(core_task.name)
         assert storage_task.plugin is storage_plugin
         assert storage_task.execution_name == ctx.execution.name
-        assert storage_task.actor == core_task.context.node._original_model
+        assert storage_task.actor == core_task.context.node
         assert core_task.model_task == storage_task
         assert core_task.name == api_task.name
         assert core_task.function == api_task.function

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/180e0a1c/tests/orchestrator/workflows/executor/test_process_executor_extension.py
----------------------------------------------------------------------
diff --git a/tests/orchestrator/workflows/executor/test_process_executor_extension.py b/tests/orchestrator/workflows/executor/test_process_executor_extension.py
index e4944df..7969457 100644
--- a/tests/orchestrator/workflows/executor/test_process_executor_extension.py
+++ b/tests/orchestrator/workflows/executor/test_process_executor_extension.py
@@ -66,7 +66,8 @@ class MockProcessExecutorExtension(object):
     def decorate(self):
         def decorator(function):
             def wrapper(ctx, **operation_arguments):
-                ctx.node.attributes['out'] = {'wrapper_arguments': operation_arguments}
+                with ctx.model.instrument(ctx.model.node.model_cls.attributes):
+                    ctx.node.attributes['out'] = {'wrapper_arguments': operation_arguments}
                 function(ctx=ctx, **operation_arguments)
             return wrapper
         return decorator


[5/6] incubator-ariatosca git commit: ARIA-166 Update README file

Posted by ra...@apache.org.
ARIA-166 Update README file


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

Branch: refs/heads/ARIA-54-prepare-aria-packaging
Commit: 22f6e9efd5300f33bee51a1c1622c22b1531bbf5
Parents: 5afa2f7
Author: Ran Ziv <ra...@gigaspaces.com>
Authored: Thu Jun 8 18:26:29 2017 +0300
Committer: Ran Ziv <ra...@gigaspaces.com>
Committed: Mon Jun 12 19:08:44 2017 +0300

----------------------------------------------------------------------
 README.md | 231 +++++++++++++++++++--------------------------------------
 1 file changed, 75 insertions(+), 156 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/22f6e9ef/README.md
----------------------------------------------------------------------
diff --git a/README.md b/README.md
index e534645..6aee414 100644
--- a/README.md
+++ b/README.md
@@ -1,201 +1,120 @@
 ARIA
 ====
 
-[![Build Status](https://travis-ci.org/apache/incubator-ariatosca.svg?branch=master)](https://travis-ci.org/apache/incubator-ariatosca)
-[![Appveyor Build Status](https://ci.appveyor.com/api/projects/status/ltv89jk63ahiu306?svg=true)](https://ci.appveyor.com/project/ApacheSoftwareFoundation/incubator-ariatosca/history)
-[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
+[![Build Status](https://img.shields.io/travis/apache/incubator-ariatosca/master.svg)](https://travis-ci.org/apache/incubator-ariatosca)
+[![Appveyor Build Status](https://img.shields.io/appveyor/ci/ApacheSoftwareFoundation/incubator-ariatosca/master.svg)](https://ci.appveyor.com/project/ApacheSoftwareFoundation/incubator-ariatosca/history)
+[![License](https://img.shields.io/github/license/apache/incubator-ariatosca.svg)](http://www.apache.org/licenses/LICENSE-2.0)
+[![PyPI release](https://img.shields.io/pypi/v/ariatosca.svg)](https://pypi.python.org/pypi/ariatosca)
+![Python Versions](https://img.shields.io/pypi/pyversions/ariatosca.svg)
+![Wheel](https://img.shields.io/pypi/wheel/ariatosca.svg)
+![Contributors](https://img.shields.io/github/contributors/apache/incubator-ariatosca.svg)
+[![Open Pull Requests](https://img.shields.io/github/issues-pr/apache/incubator-ariatosca.svg)](https://github.com/apache/incubator-ariatosca/pulls)
+[![Closed Pull Requests](https://img.shields.io/github/issues-pr-closed-raw/apache/incubator-ariatosca.svg)](https://github.com/apache/incubator-ariatosca/pulls?q=is%3Apr+is%3Aclosed)
 
 
-[ARIA](http://ariatosca.org/) is a minimal TOSCA orchestrator, as well as a platform for building
-TOSCA-based products. Its features can be accessed via a well-documented Python API.
+What is ARIA?
+----------------
 
-On its own, ARIA provides built-in tools for blueprint validation and for creating ready-to-run
-service instances. 
+[ARIA](http://ariatosca.incubator.apache.org/) is a an open-source, [TOSCA](https://www.oasis-open.org/committees/tosca/)-based, lightweight library and CLI for orchestration and for consumption by projects building TOSCA-based solutions for resources and services orchestration.
 
-ARIA adheres strictly and meticulously to 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),
-providing state-of-the-art validation at seven different levels:
+ARIA can be utilized by any organization that wants to implement TOSCA-based orchestration in its solutions, whether a multi-cloud enterprise application, or an NFV or SDN solution for multiple virtual infrastructure managers.
 
-<ol start="0">
-<li>Platform errors. E.g. network, hardware, or even an internal bug in ARIA (let us know,
-	please!).</li>
-<li>Syntax and format errors. E.g. non-compliant YAML, XML, JSON.</li>
-<li>Field validation. E.g. assigning a string where an integer is expected, using a list instead of
-	a dict.</li>
-<li>Relationships between fields within a type. This is "grammar" as it applies to rules for
-    setting the values of fields in relation to each other.</li>
-<li>Relationships between types. E.g. referring to an unknown type, causing a type inheritance
-    loop.</li>
-<li>Topology. These errors happen if requirements and capabilities cannot be matched in order to
-	assemble a valid topology.</li>
-<li>External dependencies. These errors happen if requirement/capability matching fails due to
-    external resources missing, e.g. the lack of a valid virtual machine, API credentials, etc.
-    </li> 
-</ol>
+With ARIA, you can utilize TOSCA's cloud portability out-of-the-box, to develop, test and run your applications, from template to deployment.
 
-Validation errors include a plain English message and when relevant the exact location (file, row,
-column) of the data the caused the error.
+ARIA is an incubation project under the [Apache Software Foundation](https://www.apache.org/).
 
-The ARIA API documentation always links to the relevant section of the specification, and likewise
-we provide an annotated version of the specification that links back to the API documentation.
 
+Installation
+----------------
 
-Quick Start
------------
+ARIA is [available on PyPI](https://pypi.python.org/pypi/ariatosca).    
 
-You need Python 2.6 or 2.7. Python 3+ is not currently supported.
+To install ARIA directly from PyPI (using a `wheel`), use:
 
-To install, we recommend using [pip](https://pip.pypa.io/) and a
-[virtualenv](https://virtualenv.pypa.io/en/stable/).
+    pip install aria
 
-In Debian-based systems:
 
-	sudo apt install python-setuptools
-	sudo -H easy_install pip
-	sudo -H pip install virtualenv
-	virtualenv env
+To install ARIA from source, download the source tarball from [PyPI](https://pypi.python.org/pypi/ariatosca),
+extract it, and then when inside the extracted directory, use:
 
-Or in Archlinux-based systems:
+    pip install .
 
-	pacman -S python2 python-setuptools python-pip
-	pip install virtualenv
-	virtualenv env -p $(type -p python2)
+The source package comes along with relevant examples, documentation,
+`requirements.txt` (for installing specifically the frozen dependencies' versions with which ARIA was tested) and more.
 
-To install the latest development snapshot of ARIA:
+<br>
+Note that for the `pip install` commands mentioned above, you must use a privileged user, or use virtualenv.
+<br><br><br>
 
-	. env/bin/activate
-	pip install git+http://git-wip-us.apache.org/repos/asf/incubator-ariatosca.git
+ARIA itself is in a `wheel` format compatible with all platforms. 
+Some dependencies, however, might require compilation (based on a given platform), and therefore possibly some system dependencies are required as well.
 
-To test it, let's create a service instance from a TOSCA blueprint:
+On Ubuntu or other Debian-based systems:
 
-	aria parse blueprints/tosca/node-cellar/node-cellar.yaml
-	
-You can also get it in JSON or YAML formats:
+	sudo apt install python-setuptools python-dev build-essential libssl-dev libffi-dev
 
-	aria parse blueprints/tosca/node-cellar/node-cellar.yaml --json
+On Archlinux:
 
-Or get an overview of the relationship graph:
+	sudo pacman -S python-setuptools
 
-	aria parse blueprints/tosca/node-cellar/node-cellar.yaml --graph
 
-You can provide inputs as JSON, overriding default values provided in the blueprint
+ARIA requires Python 2.6/2.7. Python 3+ is currently not supported.
 
-	aria parse blueprints/tosca/node-cellar/node-cellar.yaml --inputs='{"openstack_credential": {"user": "username"}}'
 
-Instead of providing them explicitly, you can also provide them in a file or URL, in either JSON or
-YAML. If you do so, the value must end in ".json" or ".yaml":
+Getting Started
+---------------
 
-	aria parse blueprints/tosca/node-cellar/node-cellar.yaml --inputs=blueprints/tosca/node-cellar/inputs.yaml
+This section will describe how to run a simple "Hello World" example.
 
+First, provide ARIA with the ARIA "hello world" service-template and name it (e.g. `my-service-template`):
 
-CLI
----
+	aria service-templates store examples/hello-world/helloworld.yaml my-service-template
+	
+Now create a service based on this service-template and name it (e.g. `my-service`):
+	
+	aria services create my-service -t my-service-template
+	
+Finally, start an `install` workflow execution on `my-service` like so:
 
-Though ARIA is fully exposed as an API, it also comes with a CLI tool to allow you to work from the
-shell:
+	aria executions start install -s my-service
 
-	aria parse blueprints/tosca/node-cellar/node-cellar.yaml instance
+<br>
+You should now have a simple web-server running on your local machine.
+You can try visiting http://localhost:9090 to view your deployed application.
 
-The `parse` command supports the following directives to create variations of the default consumer
-chain:
+To uninstall and clean your environment, follow these steps:
 
-* `presentation`: emits a colorized textual representation of the Python presentation classes
-   wrapping the blueprint.
-* `model`: emits a colorized textual representation of the complete service model derived from the
-   validated blueprint. This includes all the node templates, with their requirements satisfied at
-   the level of relating to other node templates.
-* `types`: emits a colorized textual representation of the type hierarchies.
-* `instance`: **this is the default command**; emits a colorized textual representation of a
-   service instance instantiated from the service model. Here the node templates are each used to
-   create one or more nodes, with the appropriate relationships between them. Note that every time
-   you run this consumer, you will get a different set of node IDs. Use `--graph` to see just the
-   node relationship graph.
-   
-For all these commands, you can also use `--json` or `--yaml` flags to emit in those formats.
+    aria executions start uninstall -s my-service
+    aria services delete my-service
+    aria service-templates delete my-service-template
 
-Additionally, The CLI tool lets you specify the complete classname of your own custom consumer to
-chain at the end of the default consumer chain, after `instance`.
 
-Your custom consumer can be an entry point into a powerful TOSCA-based tool or application, such as
-an orchestrator, a graphical modeling tool, etc.
+Contribution
+------------
 
+You are welcome and encouraged to participate and contribute to the ARIA project.
 
-Development
------------
+Please see our guide to [Contributing to ARIA](https://cwiki.apache.org/confluence/display/ARIATOSCA/Contributing+to+ARIA).
 
-Instead of installing with `pip`, it would be easier to work directly with the source files:
+Feel free to also provide feedback on the mailing lists (see [Resources](#user-content-resources) section).
 
-	pip install virtualenv
-	virtualenv env
-	. env/bin/activate
-	git clone http://git-wip-us.apache.org/repos/asf/incubator-ariatosca.git ariatosca
-	cd ariatosca
-	pip install -e .
 
-To run tests:
+Resources
+---------
 
-	pip install tox
-	tox
+* [ARIA homepage](http://ariatosca.incubator.apache.org/)
+* [ARIA wiki](https://cwiki.apache.org/confluence/display/AriaTosca)
+* [Issue tracker](https://issues.apache.org/jira/browse/ARIA)
 
-Here's a quick example of using the API to parse YAML text into a service instance:
+* Dev mailing list: dev@ariatosca.incubator.apache.org
+* User mailing list: user@ariatosca.incubator.apache.org
 
-	from aria import install_aria_extensions
-	from aria.parser.consumption import ConsumptionContext, ConsumerChain, Read, Validate, Model, Instance
-	from aria.parser.loading import LiteralLocation
-	
-	def parse_text(payload, file_search_paths=[]):
-	    context = ConsumptionContext()
-	    context.presentation.location = LiteralLocation(payload)
-	    context.loading.file_search_paths += file_search_paths
-	    ConsumerChain(context, (Read, Validate, Model, Instance)).consume()
-	    if not context.validation.dump_issues():
-	        return context.modeling.instance
-	    return None
-	
-	install_aria_extensions()
-
-	print parse_text("""
-	tosca_definitions_version: tosca_simple_yaml_1_0
-	topology_template:
-	  node_templates:
-	    MyNode:
-	      type: tosca.nodes.Compute 
-	""")
-
-
-Parser API Architecture
------------------------
-
-ARIA's parsing engine comprises individual "consumers" (in the `aria.parser.consumption` package)
-that do things with blueprints. When chained together, each performs a different task, adds its own
-validations, and can provide its own output.
-
-Parsing happens in five phases, represented in five packages:
-
-* `aria.parser.loading`: Loaders are used to read the TOSCA data, usually as text. For example
-  UriTextLoader will load text from URIs (including files).
-* `aria.parser.reading`: Readers convert data from the loaders into agnostic raw data. For
-  example, `YamlReader` converts YAML text into Python dicts, lists, and primitives.
-* `aria.parser.presentation`: Presenters wrap the agnostic raw data in a nice
-  Python facade (a "presentation") that makes it much easier to work with the data, including
-  utilities for validation, querying, etc. Note that presenters are _wrappers_: the agnostic raw
-  data is always maintained intact, and can always be accessed directly or written back to files.
-* `aria.parser.modeling.model`: Here the topology is normalized into a coherent structure of
-  node templates, requirements, and capabilities. Types are inherited and properties are assigned.
-  The service model is a _new_ structure, which is not mapped to the YAML. In fact, it is possible
-  to generate the model programmatically, or from a DSL parser other than TOSCA.
-* `aria.parser.modeling.instance`: The service instance is an instantiated service model. Node
-  templates turn into node instances (with unique IDs), and requirements are satisfied by matching
-  them to capabilities. This is where level 5 validation errors are detected (see above).
-
-The phases do not have to be used in order. Indeed, consumers do not have to be used at all: ARIA
-can be used to _produce_ blueprints. For example, it is possible to fill in the
-`aria.parser.presentation` classes programmatically, in Python, and then write the presentation
-to a YAML file as compliant TOSCA. The same technique can be used to convert from one DSL (consume
-it) to another (write it).
-
-The term "agnostic raw data" (ARD?) appears often in the documentation. It denotes data structures
-comprising _only_ Python dicts, lists, and primitives, such that they can always be converted to and
-from language-agnostic formats such as YAML, JSON, and XML. A considerable effort has been made to
-conserve the agnostic raw data at all times. Thus, though ARIA makes good use of the dynamic power
-of Python, you will _always_ be able to use ARIA with other systems.
+Subscribe by sending a mail to `<group>-subscribe@ariatosca.incubator.apache.org` (e.g. `dev-subscribe@ariatosca.incubator.apache.org`).
+See information on how to subscribe to mailing list [here](https://www.apache.org/foundation/mailinglists.html).
+
+For past correspondence, see the [dev mailing list archive](http://mail-archives.apache.org/mod_mbox/incubator-ariatosca-dev/).
+
+
+License
+-------
+ARIA is licensed under the [Apache License 2.0](https://github.com/apache/incubator-ariatosca/blob/master/LICENSE).


[4/6] incubator-ariatosca git commit: ARIA-199 Add "services outputs" CLI command

Posted by ra...@apache.org.
ARIA-199 Add "services outputs" CLI command

* Also add an output to 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/5afa2f7f
Tree: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/tree/5afa2f7f
Diff: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/diff/5afa2f7f

Branch: refs/heads/ARIA-54-prepare-aria-packaging
Commit: 5afa2f7fe11977593009b6da25733fa8dd61a1e9
Parents: cd83073
Author: Tal Liron <ta...@gmail.com>
Authored: Fri Jun 2 14:20:28 2017 -0500
Committer: Tal Liron <ta...@gmail.com>
Committed: Wed Jun 7 14:42:35 2017 -0500

----------------------------------------------------------------------
 aria/cli/commands/services.py                   | 23 ++++++++---------
 aria/modeling/models.py                         |  4 +--
 examples/hello-world/helloworld.yaml            | 16 ++++++++----
 tests/cli/test_services.py                      | 26 ++++++++++++++++++--
 tests/end2end/test_hello_world.py               |  1 +
 tests/mock/models.py                            | 21 ++++++++++------
 .../node-cellar/node-cellar.yaml                | 12 +++++++++
 7 files changed, 76 insertions(+), 27 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/5afa2f7f/aria/cli/commands/services.py
----------------------------------------------------------------------
diff --git a/aria/cli/commands/services.py b/aria/cli/commands/services.py
index 476387c..ae5895a 100644
--- a/aria/cli/commands/services.py
+++ b/aria/cli/commands/services.py
@@ -192,17 +192,16 @@ def outputs(service_name, model_storage, logger):
     """
     logger.info('Showing outputs for service {0}...'.format(service_name))
     service = model_storage.service.get_by_name(service_name)
-    #TODO fix this section..
-    outputs_def = service.outputs
-    response = model_storage.service.outputs.get(service_name)
-    outputs_ = StringIO()
-    for output_name, output in response.outputs.iteritems():
-        outputs_.write(' - "{0}":{1}'.format(output_name, os.linesep))
-        description = outputs_def[output_name].get('description', '')
-        outputs_.write('     Description: {0}{1}'.format(description,
-                                                         os.linesep))
-        outputs_.write('     Value: {0}{1}'.format(output, os.linesep))
-    logger.info(outputs_.getvalue())
+
+    if service.outputs:
+        outputs_string = StringIO()
+        for output_name, output in service.outputs.iteritems():
+            outputs_string.write(' - "{0}":{1}'.format(output_name, os.linesep))
+            outputs_string.write('     Description: {0}{1}'.format(output.description, os.linesep))
+            outputs_string.write('     Value: {0}{1}'.format(output.value, os.linesep))
+        logger.info(outputs_string.getvalue())
+    else:
+        logger.info('\tNo outputs')
 
 
 @services.command(name='inputs',
@@ -218,10 +217,12 @@ def inputs(service_name, model_storage, logger):
     """
     logger.info('Showing inputs for service {0}...'.format(service_name))
     service = model_storage.service.get_by_name(service_name)
+
     if service.inputs:
         inputs_string = StringIO()
         for input_name, input_ in service.inputs.iteritems():
             inputs_string.write(' - "{0}":{1}'.format(input_name, os.linesep))
+            inputs_string.write('     Description: {0}{1}'.format(input_.description, os.linesep))
             inputs_string.write('     Value: {0}{1}'.format(input_.value, os.linesep))
         logger.info(inputs_string.getvalue())
     else:

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/5afa2f7f/aria/modeling/models.py
----------------------------------------------------------------------
diff --git a/aria/modeling/models.py b/aria/modeling/models.py
index 4102090..f30b86f 100644
--- a/aria/modeling/models.py
+++ b/aria/modeling/models.py
@@ -268,7 +268,7 @@ class Argument(aria_declarative_base, orchestration.ArgumentBase):
 
 
 # See also __all__ at the top of this file
-models_to_register = [
+models_to_register = (
     # Service template models
     ServiceTemplate,
     NodeTemplate,
@@ -317,4 +317,4 @@ models_to_register = [
     Task,
     Log,
     Argument
-]
+)

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/5afa2f7f/examples/hello-world/helloworld.yaml
----------------------------------------------------------------------
diff --git a/examples/hello-world/helloworld.yaml b/examples/hello-world/helloworld.yaml
index be78401..d3369b7 100644
--- a/examples/hello-world/helloworld.yaml
+++ b/examples/hello-world/helloworld.yaml
@@ -1,13 +1,14 @@
 tosca_definitions_version: tosca_simple_yaml_1_0
 
 node_types:
-  web_server:
+
+  WebServer:
     derived_from: tosca.nodes.Root
     capabilities:
       host:
         type: tosca.capabilities.Container
 
-  web_app:
+  WebApp:
     derived_from: tosca.nodes.WebApplication
     properties:
       port:
@@ -17,10 +18,10 @@ topology_template:
 
   node_templates:
     web_server:
-      type: web_server
+      type: WebServer
 
     web_app:
-      type: web_app
+      type: WebApp
       properties:
         port: 9090
       requirements:
@@ -29,4 +30,9 @@ topology_template:
         Standard:
           configure: scripts/configure.sh
           start: scripts/start.sh
-          stop: scripts/stop.sh
\ No newline at end of file
+          stop: scripts/stop.sh
+
+  outputs:
+    port:
+      type: integer
+      value: { get_property: [ web_app, port ] }

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/5afa2f7f/tests/cli/test_services.py
----------------------------------------------------------------------
diff --git a/tests/cli/test_services.py b/tests/cli/test_services.py
index e5717cc..7dc84bc 100644
--- a/tests/cli/test_services.py
+++ b/tests/cli/test_services.py
@@ -174,7 +174,30 @@ class TestServicesDelete(TestCliBase):
 
 
 class TestServicesOutputs(TestCliBase):
-    pass
+
+    def test_header_string(self, monkeypatch, mock_storage):
+        monkeypatch.setattr(_Environment, 'model_storage', mock_storage)
+        self.invoke('services outputs test_s')
+        assert 'Showing outputs for service test_s...' in self.logger_output_string
+
+    def test_outputs_no_outputs(self, monkeypatch, mock_storage):
+        monkeypatch.setattr(_Environment, 'model_storage', mock_storage)
+        self.invoke('services outputs service_with_no_outputs')
+
+        assert 'No outputs' in self.logger_output_string
+        assert 'output1' not in self.logger_output_string
+        assert 'value1' not in self.logger_output_string
+
+    def test_outputs_one_output(self, monkeypatch, mock_storage):
+        monkeypatch.setattr(_Environment, 'model_storage', mock_storage)
+        s = mock_models.create_service_with_dependencies(include_output=True)
+        monkeypatch.setattr(mock_storage.service, 'get_by_name', mock.MagicMock(return_value=s))
+
+        self.invoke('services outputs test_s')
+
+        assert 'output1' in self.logger_output_string
+        assert 'value1' in self.logger_output_string
+        assert 'No outputs' not in self.logger_output_string
 
 
 class TestServicesInputs(TestCliBase):
@@ -193,7 +216,6 @@ class TestServicesInputs(TestCliBase):
         assert 'value1' not in self.logger_output_string
 
     def test_inputs_one_input(self, monkeypatch, mock_storage):
-
         monkeypatch.setattr(_Environment, 'model_storage', mock_storage)
         s = mock_models.create_service_with_dependencies(include_input=True)
         monkeypatch.setattr(mock_storage.service, 'get_by_name', mock.MagicMock(return_value=s))

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/5afa2f7f/tests/end2end/test_hello_world.py
----------------------------------------------------------------------
diff --git a/tests/end2end/test_hello_world.py b/tests/end2end/test_hello_world.py
index 71792dd..b55b9a8 100644
--- a/tests/end2end/test_hello_world.py
+++ b/tests/end2end/test_hello_world.py
@@ -56,5 +56,6 @@ def _verify_deployed_service_in_storage(service_name, model_storage):
     assert service.name == service_name
     assert len(service.executions) == 1
     assert len(service.nodes) == 2
+    assert service.outputs['port'].value == 9090
     assert all(node.state == node.STARTED for node in service.nodes.values())
     assert len(service.executions[0].logs) > 0

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/5afa2f7f/tests/mock/models.py
----------------------------------------------------------------------
diff --git a/tests/mock/models.py b/tests/mock/models.py
index 56a6e3e..7f6bbea 100644
--- a/tests/mock/models.py
+++ b/tests/mock/models.py
@@ -86,6 +86,7 @@ def create_service(service_template, name=SERVICE_NAME, inputs=None):
 
 def create_service_with_dependencies(include_execution=False,
                                      include_input=False,
+                                     include_output=False,
                                      include_node=False):
     service_template = create_service_template()
     service = create_service(service_template=service_template)
@@ -96,6 +97,9 @@ def create_service_with_dependencies(include_execution=False,
     if include_input:
         input = create_input(name='input1', value='value1')
         service.inputs = {'input1': input}
+    if include_output:
+        output = create_output(name='output1', value='value1')
+        service.outputs = {'output1': output}
     if include_node:
         node_template = create_node_template(service_template=service_template)
         node = create_node(node_template, service, state=models.Node.STARTED)
@@ -290,6 +294,10 @@ def create_input(name, value):
     return _create_parameter(name, value, model_cls=models.Input)
 
 
+def create_output(name, value):
+    return _create_parameter(name, value, model_cls=models.Output)
+
+
 def _dictify(item):
     return dict(((item.name, item),))
 
@@ -300,8 +308,8 @@ def get_standard_interface_template(service_template):
     op_templates = dict(
         (op_name, models.OperationTemplate(
             name=op_name, implementation='{0}.{1}'.format(__file__, mock_operation.__name__)))
-        for op_name in [NORMATIVE_CREATE, NORMATIVE_CONFIGURE, NORMATIVE_START,
-                        NORMATIVE_STOP, NORMATIVE_DELETE]
+        for op_name in (NORMATIVE_CREATE, NORMATIVE_CONFIGURE, NORMATIVE_START,
+                        NORMATIVE_STOP, NORMATIVE_DELETE)
     )
     return models.InterfaceTemplate(name=NORMATIVE_STANDARD_INTERFACE,
                                     operation_templates=op_templates,
@@ -314,8 +322,8 @@ def get_standard_interface(service):
     ops = dict(
         (op_name, models.Operation(
             name=op_name, implementation='{0}.{1}'.format(__file__, mock_operation.__name__)))
-        for op_name in [NORMATIVE_CREATE, NORMATIVE_CONFIGURE, NORMATIVE_START,
-                        NORMATIVE_STOP, NORMATIVE_DELETE]
+        for op_name in (NORMATIVE_CREATE, NORMATIVE_CONFIGURE, NORMATIVE_START,
+                        NORMATIVE_STOP, NORMATIVE_DELETE)
     )
     return {
         NORMATIVE_STANDARD_INTERFACE:
@@ -329,7 +337,7 @@ def get_configure_interfaces(service):
     operations = dict(
         (op_name, models.Operation(
             name=op_name, implementation='{0}.{1}'.format(__file__, mock_operation.__name__)))
-        for op_name in [NORMATIVE_PRE_CONFIGURE_SOURCE,
+        for op_name in (NORMATIVE_PRE_CONFIGURE_SOURCE,
                         NORMATIVE_POST_CONFIGURE_SOURCE,
                         NORMATIVE_ADD_SOURCE,
                         NORMATIVE_REMOVE_SOURCE,
@@ -337,8 +345,7 @@ def get_configure_interfaces(service):
                         NORMATIVE_PRE_CONFIGURE_TARGET,
                         NORMATIVE_POST_CONFIGURE_TARGET,
                         NORMATIVE_ADD_TARGET,
-                        NORMATIVE_REMOVE_TARGET
-                       ]
+                        NORMATIVE_REMOVE_TARGET)
     )
     interface = {
         NORMATIVE_CONFIGURE_INTERFACE: models.Interface(

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/5afa2f7f/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 4d53f9b..a34301c 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
@@ -144,6 +144,10 @@ topology_template:
       type: nodejs.Server
       requirements:
         - host: application_host
+      capabilities:
+        data_endpoint:
+          properties:
+            url_path: /app
       node_filter: # cannot be validated
         properties:
           #- flavor_name: { valid_values: [ {concat:[m1,.,small]} ] } # won't work because not validated :/
@@ -302,6 +306,14 @@ topology_template:
     capabilities:
       app_endpoint: [ loadbalancer, client ]
 
+  outputs:
+
+    endpoint:
+      description: >-
+        The application endpoint.
+      type: string
+      value: { get_property: [ nodejs, data_endpoint, url_path ] }
+
 policy_types:
 
   MaintenanceWorkflow: