diff options
author | julien zhang <zhang.jun3g@zte.com.cn> | 2016-04-22 08:43:26 +0000 |
---|---|---|
committer | Gerrit Code Review <gerrit@172.30.200.206> | 2016-04-22 08:43:26 +0000 |
commit | 2fa4785aa218cf655f3405d832d2200d64fd033e (patch) | |
tree | 0061edccecca9a99aa9a50c6c0e2783c07370112 /tosca2heat/tosca-parser | |
parent | 56c2a5d3ba6193c9a98870a264bc5e3aca19a086 (diff) | |
parent | c8201c119ec686e79797721156767685fe848aca (diff) |
Merge "Update tosca lib to version 0.5"
Diffstat (limited to 'tosca2heat/tosca-parser')
259 files changed, 17083 insertions, 0 deletions
diff --git a/tosca2heat/tosca-parser/CONTRIBUTING.rst b/tosca2heat/tosca-parser/CONTRIBUTING.rst new file mode 100644 index 0000000..7366825 --- /dev/null +++ b/tosca2heat/tosca-parser/CONTRIBUTING.rst @@ -0,0 +1,16 @@ +If you would like to contribute to the development of OpenStack, +you must follow the steps in this page: + + http://docs.openstack.org/infra/manual/developers.html + +Once those steps have been completed, changes to OpenStack +should be submitted for review via the Gerrit tool, following +the workflow documented at: + + http://docs.openstack.org/infra/manual/developers.html#development-workflow + +Pull requests submitted through GitHub will be ignored. + +Bugs should be filed on Launchpad, not GitHub: + + https://bugs.launchpad.net/tosca-parser
\ No newline at end of file diff --git a/tosca2heat/tosca-parser/HACKING.rst b/tosca2heat/tosca-parser/HACKING.rst new file mode 100644 index 0000000..60505f1 --- /dev/null +++ b/tosca2heat/tosca-parser/HACKING.rst @@ -0,0 +1,4 @@ +tosca-parser Style Commandments +=============================================== + +Read the OpenStack Style Commandments http://docs.openstack.org/developer/hacking/
\ No newline at end of file diff --git a/tosca2heat/tosca-parser/LICENSE b/tosca2heat/tosca-parser/LICENSE new file mode 100644 index 0000000..67db858 --- /dev/null +++ b/tosca2heat/tosca-parser/LICENSE @@ -0,0 +1,175 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. diff --git a/tosca2heat/tosca-parser/MANIFEST.in b/tosca2heat/tosca-parser/MANIFEST.in new file mode 100644 index 0000000..90f8a7a --- /dev/null +++ b/tosca2heat/tosca-parser/MANIFEST.in @@ -0,0 +1,6 @@ +include AUTHORS +include ChangeLog +exclude .gitignore +exclude .gitreview + +global-exclude *.pyc
\ No newline at end of file diff --git a/tosca2heat/tosca-parser/README.rst b/tosca2heat/tosca-parser/README.rst new file mode 100644 index 0000000..d80d75b --- /dev/null +++ b/tosca2heat/tosca-parser/README.rst @@ -0,0 +1,45 @@ +=============== +TOSCA Parser +=============== + +Overview +-------- + +The TOSCA Parser is an OpenStack project and licensed under Apache 2. It is +developed to parse TOSCA Simple Profile in YAML. It reads the TOSCA templates +and creates an in-memory graph of TOSCA nodes and their relationship. + +Architecture +------------ + +The TOSCA Parser takes TOSCA YAML template or TOSCA Cloud Service Archive (CSAR) +file as an input, with optional input of dictionary of needed parameters with their +values, and produces in-memory objects of different TOSCA elements with their +relationship to each other. It also creates a graph of TOSCA node templates and their +relationship. + +The ToscaTemplate class located in the toscaparser/tosca_template.py is an entry +class of the parser and various functionality of parser can be used by initiating +this class. In order to see an example usage of TOSCA Parser from a separate tool, +refer to the OpenStack heat-translator class TranslateTemplate located in the +translator/osc/v1/translate.py module. The toscaparser/shell.py module of tosca-parser +also provides a good reference on how to invoke TOSCA Parser from Command Line Interface. + +The toscaparser/elements sub-directory contains various modules to handle +various TOSCA type elements like node type, relationship type etc. The +entity_type.py module is a parent of all type elements. The toscaparser +directory contains various python module to handle service template including +topology template, node templates, relationship templates etc. The +entity_template.py is a parent of all template elements. + + +How To Use +---------- +Please refer to `doc/source/usage.rst <https://github.com/openstack/tosca-parser/blob/master/doc/source/usage.rst>`_ + +Project Info +------------ + +* License: Apache License, Version 2.0 +* Source: http://git.openstack.org/cgit/openstack/tosca-parser/ + diff --git a/tosca2heat/tosca-parser/babel.cfg b/tosca2heat/tosca-parser/babel.cfg new file mode 100644 index 0000000..efceab8 --- /dev/null +++ b/tosca2heat/tosca-parser/babel.cfg @@ -0,0 +1 @@ +[python: **.py] diff --git a/tosca2heat/tosca-parser/doc/source/conf.py b/tosca2heat/tosca-parser/doc/source/conf.py new file mode 100644 index 0000000..e461246 --- /dev/null +++ b/tosca2heat/tosca-parser/doc/source/conf.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import sys + +sys.path.insert(0, os.path.abspath('../..')) +# -- General configuration ---------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = [ + 'sphinx.ext.autodoc', + #'sphinx.ext.intersphinx', + 'oslosphinx' +] + +# autodoc generation is a bit aggressive and a nuisance when doing heavy +# text edit cycles. +# execute "export SPHINX_DEBUG=1" in your terminal to disable + +# The suffix of source filenames. +source_suffix = '.rst' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'tosca-parser' +copyright = u'2013, OpenStack Foundation' + +# If true, '()' will be appended to :func: etc. cross-reference text. +add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +add_module_names = True + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# -- Options for HTML output -------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. Major themes that come with +# Sphinx are currently 'default' and 'sphinxdoc'. +# html_theme_path = ["."] +# html_theme = 'default' +# html_static_path = ['static'] + +# Output file base name for HTML help builder. +htmlhelp_basename = '%sdoc' % project + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass +# [howto/manual]). +latex_documents = [ + ('index', + '%s.tex' % project, + u'%s Documentation' % project, + u'OpenStack Foundation', 'manual'), +] + +# Example configuration for intersphinx: refer to the Python standard library. +#intersphinx_mapping = {'http://docs.python.org/': None} diff --git a/tosca2heat/tosca-parser/doc/source/contributing.rst b/tosca2heat/tosca-parser/doc/source/contributing.rst new file mode 100644 index 0000000..8cb3146 --- /dev/null +++ b/tosca2heat/tosca-parser/doc/source/contributing.rst @@ -0,0 +1 @@ +.. include:: ../../CONTRIBUTING.rst
\ No newline at end of file diff --git a/tosca2heat/tosca-parser/doc/source/index.rst b/tosca2heat/tosca-parser/doc/source/index.rst new file mode 100644 index 0000000..c3894f4 --- /dev/null +++ b/tosca2heat/tosca-parser/doc/source/index.rst @@ -0,0 +1,38 @@ +.. tosca-parser documentation master file, created by + sphinx-quickstart on Tue Jul 9 22:26:36 2013. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to tosca-parser's documentation! +=========================================== + +The TOSCA Parser is developed to parse TOSCA Simple Profile in YAML. It reads +the TOSCA templates and creates an in-memory graph of TOSCA nodes and their +relationship. + +The TOSCA Parser can also be used for parsing TOSCA Simple Profile for Network +Functions Virtualization (NFV). The work to provide such a support was started +with the release of TOSCA Parser 0.4.0 PyPI release and it is ongoing. +The TOSCA Simple Profile for NFV can be accessed by using TOSCA version +"tosca_simple_profile_for_nfv_1_0_0" in the template. + +The TOSCA Parser now supports profile definition extensions that can be +accessed via a custom tosca_definitions_version. Extensions can be added by +creating a module in the "toscaparser/extensions" directory. See the "nfv" +module for an example. + +Contents: + +.. toctree:: + :maxdepth: 2 + + installation + usage + contributing + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/tosca2heat/tosca-parser/doc/source/installation.rst b/tosca2heat/tosca-parser/doc/source/installation.rst new file mode 100644 index 0000000..f3805a5 --- /dev/null +++ b/tosca2heat/tosca-parser/doc/source/installation.rst @@ -0,0 +1,9 @@ +============ +Installation +============ + +You can clone the project and use it as below:: + + git clone https://github.com/openstack/tosca-parser + +Tosca-Parser can be installed via PyPI package as well. Refer to https://pypi.python.org/pypi/tosca-parser for available packages.
\ No newline at end of file diff --git a/tosca2heat/tosca-parser/doc/source/usage.rst b/tosca2heat/tosca-parser/doc/source/usage.rst new file mode 100644 index 0000000..2b822a8 --- /dev/null +++ b/tosca2heat/tosca-parser/doc/source/usage.rst @@ -0,0 +1,16 @@ +===== +Usage +===== + +The TOSCA Parser can be used as a library by any client program, for example, +OpenStack heat-translator uses it to translate TOSCA to Heat Orchestration +Template (HOT). + +For an easy reference on how TOSCA Parser can be used programmatically or to +test that the a TOSCA template passes validation, refer to the tosca_parser.py +test program which is located at the root level of the project. Alternatively, +you can install 0.3.0 or higher PyPI release of TOSCA-Parser as available at the +https://pypi.python.org/pypi/tosca-parser and test use the parser via CLI +entry point as:: + tosca-parser --template-file=toscaparser/tests/data/tosca_helloworld.yaml +The value to the --template-file is required to be a relative or an absolute path. diff --git a/tosca2heat/tosca-parser/openstack-common.conf b/tosca2heat/tosca-parser/openstack-common.conf new file mode 100644 index 0000000..178c8c4 --- /dev/null +++ b/tosca2heat/tosca-parser/openstack-common.conf @@ -0,0 +1,6 @@ +[DEFAULT] + +# The list of modules to copy from oslo-incubator.git + +# The base module to hold the copy of openstack.common +base=toscaparser
\ No newline at end of file diff --git a/tosca2heat/tosca-parser/requirements.txt b/tosca2heat/tosca-parser/requirements.txt new file mode 100644 index 0000000..dd27a53 --- /dev/null +++ b/tosca2heat/tosca-parser/requirements.txt @@ -0,0 +1,9 @@ +# The order of packages is significant, because pip processes them in the order +# of appearance. Changing the order has an impact on the overall integration +# process, which may cause wedges in the gate later. +pbr>=1.6 # Apache-2.0 +Babel>=1.3 # BSD +cliff!=1.16.0,!=1.17.0,>=1.15.0 # Apache-2.0 +PyYAML>=3.1.0 # MIT +python-dateutil>=2.4.2 # BSD +six>=1.9.0 # MIT diff --git a/tosca2heat/tosca-parser/setup.cfg b/tosca2heat/tosca-parser/setup.cfg new file mode 100644 index 0000000..747d88c --- /dev/null +++ b/tosca2heat/tosca-parser/setup.cfg @@ -0,0 +1,49 @@ +[metadata] +name = tosca-parser +summary = Parser for TOSCA Simple Profile in YAML. +description-file = + README.rst +author = OpenStack +author-email = openstack-dev@lists.openstack.org +home-page = http://www.openstack.org/ +classifier = + Environment :: OpenStack + Intended Audience :: Information Technology + Intended Audience :: System Administrators + License :: OSI Approved :: Apache Software License + Operating System :: POSIX :: Linux + Programming Language :: Python + Programming Language :: Python :: 2 + Programming Language :: Python :: 2.7 + Programming Language :: Python :: 3 + Programming Language :: Python :: 3.4 + +[files] +packages = + toscaparser + +[entry_points] +console_scripts = + tosca-parser = toscaparser.shell:main + +[build_sphinx] +source-dir = doc/source +build-dir = doc/build +all_files = 1 + +[upload_sphinx] +upload-dir = doc/build/html + +[compile_catalog] +directory = toscaparser/locale +domain = toscaparser + +[update_catalog] +domain = toscaparser +output_dir = toscaparser/locale +input_file = toscaparser/locale/toscaparser.pot + +[extract_messages] +keywords = _ gettext ngettext l_ lazy_gettext +mapping_file = babel.cfg +output_file = toscaparser/locale/toscaparser.pot diff --git a/tosca2heat/tosca-parser/setup.py b/tosca2heat/tosca-parser/setup.py new file mode 100644 index 0000000..782bb21 --- /dev/null +++ b/tosca2heat/tosca-parser/setup.py @@ -0,0 +1,29 @@ +# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. +# +# Licensed 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. + +# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT +import setuptools + +# In python < 2.7.4, a lazy loading of package `pbr` will break +# setuptools if some other modules registered functions in `atexit`. +# solution from: http://bugs.python.org/issue15881#msg170215 +try: + import multiprocessing # noqa +except ImportError: + pass + +setuptools.setup( + setup_requires=['pbr>=1.8'], + pbr=True) diff --git a/tosca2heat/tosca-parser/test-requirements.txt b/tosca2heat/tosca-parser/test-requirements.txt new file mode 100644 index 0000000..2b9cae7 --- /dev/null +++ b/tosca2heat/tosca-parser/test-requirements.txt @@ -0,0 +1,14 @@ +# The order of packages is significant, because pip processes them in the order +# of appearance. Changing the order has an impact on the overall integration +# process, which may cause wedges in the gate later. +hacking<0.11,>=0.10.0 +coverage>=3.6 # Apache-2.0 +discover # BSD +fixtures>=1.3.1 # Apache-2.0/BSD +oslotest>=1.10.0 # Apache-2.0 +oslosphinx!=3.4.0,>=2.5.0 # Apache-2.0 +python-subunit>=0.0.18 # Apache-2.0/BSD +sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2 # BSD +testrepository>=0.0.18 # Apache-2.0/BSD +testscenarios>=0.4 # Apache-2.0/BSD +testtools>=1.4.0 # MIT diff --git a/tosca2heat/tosca-parser/tosca_parser.py b/tosca2heat/tosca-parser/tosca_parser.py new file mode 100644 index 0000000..0d2de75 --- /dev/null +++ b/tosca2heat/tosca-parser/tosca_parser.py @@ -0,0 +1,32 @@ +# Licensed 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 toscaparser import shell as parser_shell + +""" +Test utility to show how TOSCA Parser can be used programmatically + +It can be used as, +#python tosca_parser.py --template-file=<path to the YAML template> +#python tosca_parser.py --template-file=<path to the CSAR zip file> +#python tosca_parser.py --template-file=<URL to the template or CSAR> + +e.g. +#python tosca_parser.py +--template-file=toscaparser/tests/data/tosca_helloworld.yaml +#python tosca_parser.py +--template-file=toscaparser/tests/data/CSAR/csar_hello_world.zip +""" + +if __name__ == '__main__': + parser_shell.main() diff --git a/tosca2heat/tosca-parser/toscaparser/__init__.py b/tosca2heat/tosca-parser/toscaparser/__init__.py new file mode 100644 index 0000000..d6fa60c --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/__init__.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- + +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import pbr.version + + +__version__ = pbr.version.VersionInfo( + 'tosca-parser').version_string() diff --git a/tosca2heat/tosca-parser/toscaparser/capabilities.py b/tosca2heat/tosca-parser/toscaparser/capabilities.py new file mode 100644 index 0000000..c23ef72 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/capabilities.py @@ -0,0 +1,45 @@ +# Licensed 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 toscaparser.properties import Property + + +class Capability(object): + '''TOSCA built-in capabilities type.''' + + def __init__(self, name, properties, definition): + self.name = name + self._properties = properties + self.definition = definition + + def get_properties_objects(self): + '''Return a list of property objects.''' + properties = [] + props = self._properties + if props: + for name, value in props.items(): + props_def = self.definition.get_properties_def() + if props_def and name in props_def: + properties.append(Property(name, value, + props_def[name].schema)) + return properties + + def get_properties(self): + '''Return a dictionary of property name-object pairs.''' + return {prop.name: prop + for prop in self.get_properties_objects()} + + def get_property_value(self, name): + '''Return the value of a given property name.''' + props = self.get_properties() + if props and name in props: + return props[name].value diff --git a/tosca2heat/tosca-parser/toscaparser/common/__init__.py b/tosca2heat/tosca-parser/toscaparser/common/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/common/__init__.py diff --git a/tosca2heat/tosca-parser/toscaparser/common/exception.py b/tosca2heat/tosca-parser/toscaparser/common/exception.py new file mode 100644 index 0000000..4f99dda --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/common/exception.py @@ -0,0 +1,208 @@ +# Licensed 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. + +''' +TOSCA exception classes +''' +import logging +import sys +import traceback + +from toscaparser.utils.gettextutils import _ + + +log = logging.getLogger(__name__) + + +class TOSCAException(Exception): + '''Base exception class for TOSCA + + To correctly use this class, inherit from it and define + a 'msg_fmt' property. + + ''' + + _FATAL_EXCEPTION_FORMAT_ERRORS = False + + message = _('An unknown exception occurred.') + + def __init__(self, **kwargs): + try: + self.message = self.msg_fmt % kwargs + except KeyError: + exc_info = sys.exc_info() + log.exception(_('Exception in string format operation: %s') + % exc_info[1]) + + if TOSCAException._FATAL_EXCEPTION_FORMAT_ERRORS: + raise exc_info[0] + + def __str__(self): + return self.message + + @staticmethod + def generate_inv_schema_property_error(self, attr, value, valid_values): + msg = (_('Schema definition of "%(propname)s" has ' + '"%(attr)s" attribute with invalid value ' + '"%(value1)s". The value must be one of ' + '"%(value2)s".') % {"propname": self.name, + "attr": attr, + "value1": value, + "value2": valid_values}) + ExceptionCollector.appendException( + InvalidSchemaError(message=msg)) + + @staticmethod + def set_fatal_format_exception(flag): + if isinstance(flag, bool): + TOSCAException._FATAL_EXCEPTION_FORMAT_ERRORS = flag + + +class MissingRequiredFieldError(TOSCAException): + msg_fmt = _('%(what)s is missing required field "%(required)s".') + + +class UnknownFieldError(TOSCAException): + msg_fmt = _('%(what)s contains unknown field "%(field)s". Refer to the ' + 'definition to verify valid values.') + + +class TypeMismatchError(TOSCAException): + msg_fmt = _('%(what)s must be of type "%(type)s".') + + +class InvalidNodeTypeError(TOSCAException): + msg_fmt = _('Node type "%(what)s" is not a valid type.') + + +class InvalidTypeError(TOSCAException): + msg_fmt = _('Type "%(what)s" is not a valid type.') + + +class InvalidSchemaError(TOSCAException): + msg_fmt = _('%(message)s') + + +class ValidationError(TOSCAException): + msg_fmt = _('%(message)s') + + +class UnknownInputError(TOSCAException): + msg_fmt = _('Unknown input "%(input_name)s".') + + +class InvalidPropertyValueError(TOSCAException): + msg_fmt = _('Value of property "%(what)s" is invalid.') + + +class InvalidTemplateVersion(TOSCAException): + msg_fmt = _('The template version "%(what)s" is invalid. ' + 'Valid versions are "%(valid_versions)s".') + + +class InvalidTOSCAVersionPropertyException(TOSCAException): + msg_fmt = _('Value of TOSCA version property "%(what)s" is invalid.') + + +class URLException(TOSCAException): + msg_fmt = _('%(what)s') + + +class ToscaExtImportError(TOSCAException): + msg_fmt = _('Unable to import extension "%(ext_name)s". ' + 'Check to see that it exists and has no ' + 'language definition errors.') + + +class ToscaExtAttributeError(TOSCAException): + msg_fmt = _('Missing attribute in extension "%(ext_name)s". ' + 'Check to see that it has required attributes ' + '"%(attrs)s" defined.') + + +class InvalidGroupTargetException(TOSCAException): + msg_fmt = _('"%(message)s"') + + +class ExceptionCollector(object): + + exceptions = [] + collecting = False + + @staticmethod + def clear(): + del ExceptionCollector.exceptions[:] + + @staticmethod + def start(): + ExceptionCollector.clear() + ExceptionCollector.collecting = True + + @staticmethod + def stop(): + ExceptionCollector.collecting = False + + @staticmethod + def contains(exception): + for ex in ExceptionCollector.exceptions: + if str(ex) == str(exception): + return True + return False + + @staticmethod + def appendException(exception): + if ExceptionCollector.collecting: + if not ExceptionCollector.contains(exception): + exception.trace = traceback.extract_stack()[:-1] + ExceptionCollector.exceptions.append(exception) + else: + raise exception + + @staticmethod + def exceptionsCaught(): + return len(ExceptionCollector.exceptions) > 0 + + @staticmethod + def getTraceString(traceList): + traceString = '' + for entry in traceList: + f, l, m, c = entry[0], entry[1], entry[2], entry[3] + traceString += (_('\t\tFile %(file)s, line %(line)s, in ' + '%(method)s\n\t\t\t%(call)s\n') + % {'file': f, 'line': l, 'method': m, 'call': c}) + return traceString + + @staticmethod + def getExceptionReportEntry(exception, full=True): + entry = exception.__class__.__name__ + ': ' + str(exception) + if full: + entry += '\n' + ExceptionCollector.getTraceString(exception.trace) + return entry + + @staticmethod + def getExceptions(): + return ExceptionCollector.exceptions + + @staticmethod + def getExceptionsReport(full=True): + report = [] + for exception in ExceptionCollector.exceptions: + report.append( + ExceptionCollector.getExceptionReportEntry(exception, full)) + return report + + @staticmethod + def assertExceptionMessage(exception, message): + err_msg = exception.__name__ + ': ' + message + report = ExceptionCollector.getExceptionsReport(False) + assert err_msg in report, (_('Could not find "%(msg)s" in "%(rep)s".') + % {'rep': report.__str__(), 'msg': err_msg}) diff --git a/tosca2heat/tosca-parser/toscaparser/dataentity.py b/tosca2heat/tosca-parser/toscaparser/dataentity.py new file mode 100644 index 0000000..6e7d59e --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/dataentity.py @@ -0,0 +1,169 @@ +# Licensed 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 toscaparser.common.exception import ExceptionCollector +from toscaparser.common.exception import MissingRequiredFieldError +from toscaparser.common.exception import TypeMismatchError +from toscaparser.common.exception import UnknownFieldError +from toscaparser.elements.constraints import Schema +from toscaparser.elements.datatype import DataType +from toscaparser.elements.scalarunit import ScalarUnit_Frequency +from toscaparser.elements.scalarunit import ScalarUnit_Size +from toscaparser.elements.scalarunit import ScalarUnit_Time + +from toscaparser.utils.gettextutils import _ +from toscaparser.utils import validateutils + + +class DataEntity(object): + '''A complex data value entity.''' + + def __init__(self, datatypename, value_dict, custom_def=None): + self.custom_def = custom_def + self.datatype = DataType(datatypename, custom_def) + self.schema = self.datatype.get_all_properties() + self.value = value_dict + + def validate(self): + '''Validate the value by the definition of the datatype.''' + + # A datatype can not have both 'type' and 'properties' definitions. + # If the datatype has 'type' definition + if self.datatype.value_type: + self.value = DataEntity.validate_datatype(self.datatype.value_type, + self.value, + None, + self.custom_def) + schema = Schema(None, self.datatype.defs) + for constraint in schema.constraints: + constraint.validate(self.value) + # If the datatype has 'properties' definition + else: + if not isinstance(self.value, dict): + ExceptionCollector.appendException( + TypeMismatchError(what=self.value, + type=self.datatype.type)) + allowed_props = [] + required_props = [] + default_props = {} + if self.schema: + allowed_props = self.schema.keys() + for name, prop_def in self.schema.items(): + if prop_def.required: + required_props.append(name) + if prop_def.default: + default_props[name] = prop_def.default + + # check allowed field + for value_key in list(self.value.keys()): + if value_key not in allowed_props: + ExceptionCollector.appendException( + UnknownFieldError(what=(_('Data value of type "%s"') + % self.datatype.type), + field=value_key)) + + # check default field + for def_key, def_value in list(default_props.items()): + if def_key not in list(self.value.keys()): + self.value[def_key] = def_value + + # check missing field + missingprop = [] + for req_key in required_props: + if req_key not in list(self.value.keys()): + missingprop.append(req_key) + if missingprop: + ExceptionCollector.appendException( + MissingRequiredFieldError( + what=(_('Data value of type "%s"') + % self.datatype.type), required=missingprop)) + + # check every field + for name, value in list(self.value.items()): + prop_schema = Schema(name, self._find_schema(name)) + # check if field value meets type defined + DataEntity.validate_datatype(prop_schema.type, value, + prop_schema.entry_schema, + self.custom_def) + # check if field value meets constraints defined + if prop_schema.constraints: + for constraint in prop_schema.constraints: + if isinstance(value, list): + for val in value: + constraint.validate(val) + else: + constraint.validate(value) + + return self.value + + def _find_schema(self, name): + if self.schema and name in self.schema.keys(): + return self.schema[name].schema + + @staticmethod + def validate_datatype(type, value, entry_schema=None, custom_def=None): + '''Validate value with given type. + + If type is list or map, validate its entry by entry_schema(if defined) + If type is a user-defined complex datatype, custom_def is required. + ''' + if type == Schema.STRING: + return validateutils.validate_string(value) + elif type == Schema.INTEGER: + return validateutils.validate_integer(value) + elif type == Schema.FLOAT: + return validateutils.validate_float(value) + elif type == Schema.NUMBER: + return validateutils.validate_number(value) + elif type == Schema.BOOLEAN: + return validateutils.validate_boolean(value) + elif type == Schema.RANGE: + return validateutils.validate_range(value) + elif type == Schema.TIMESTAMP: + validateutils.validate_timestamp(value) + return value + elif type == Schema.LIST: + validateutils.validate_list(value) + if entry_schema: + DataEntity.validate_entry(value, entry_schema, custom_def) + return value + elif type == Schema.SCALAR_UNIT_SIZE: + return ScalarUnit_Size(value).validate_scalar_unit() + elif type == Schema.SCALAR_UNIT_FREQUENCY: + return ScalarUnit_Frequency(value).validate_scalar_unit() + elif type == Schema.SCALAR_UNIT_TIME: + return ScalarUnit_Time(value).validate_scalar_unit() + elif type == Schema.VERSION: + return validateutils.TOSCAVersionProperty(value).get_version() + elif type == Schema.MAP: + validateutils.validate_map(value) + if entry_schema: + DataEntity.validate_entry(value, entry_schema, custom_def) + return value + else: + data = DataEntity(type, value, custom_def) + return data.validate() + + @staticmethod + def validate_entry(value, entry_schema, custom_def=None): + '''Validate entries for map and list.''' + schema = Schema(None, entry_schema) + valuelist = value + if isinstance(value, dict): + valuelist = list(value.values()) + for v in valuelist: + DataEntity.validate_datatype(schema.type, v, schema.entry_schema, + custom_def) + if schema.constraints: + for constraint in schema.constraints: + constraint.validate(v) + return value diff --git a/tosca2heat/tosca-parser/toscaparser/elements/TOSCA_definition_1_0.yaml b/tosca2heat/tosca-parser/toscaparser/elements/TOSCA_definition_1_0.yaml new file mode 100644 index 0000000..f8d781c --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/elements/TOSCA_definition_1_0.yaml @@ -0,0 +1,926 @@ +# Licensed 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. + +########################################################################## +# The content of this file reflects TOSCA Simple Profile in YAML version +# 1.0.0. It describes the definition for TOSCA types including Node Type, +# Relationship Type, Capability Type and Interfaces. +########################################################################## +tosca_definitions_version: tosca_simple_yaml_1_0 + +########################################################################## +# Node Type. +# A Node Type is a reusable entity that defines the type of one or more +# Node Templates. +########################################################################## +tosca.nodes.Root: + description: > + The TOSCA root node all other TOSCA base node types derive from. + attributes: + tosca_id: + type: string + tosca_name: + type: string + state: + type: string + capabilities: + feature: + type: tosca.capabilities.Node + requirements: + - dependency: + capability: tosca.capabilities.Node + node: tosca.nodes.Root + relationship: tosca.relationships.DependsOn + occurrences: [ 0, UNBOUNDED ] + interfaces: + Standard: + type: tosca.interfaces.node.lifecycle.Standard + +tosca.nodes.Compute: + derived_from: tosca.nodes.Root + attributes: + private_address: + type: string + public_address: + type: string + networks: + type: map + entry_schema: + type: tosca.datatypes.network.NetworkInfo + ports: + type: map + entry_schema: + type: tosca.datatypes.network.PortInfo + capabilities: + host: + type: tosca.capabilities.Container + binding: + type: tosca.capabilities.network.Bindable + os: + type: tosca.capabilities.OperatingSystem + scalable: + type: tosca.capabilities.Scalable + requirements: + - local_storage: + capability: tosca.capabilities.Attachment + node: tosca.nodes.BlockStorage + relationship: tosca.relationships.AttachesTo + occurrences: [0, UNBOUNDED] + +tosca.nodes.SoftwareComponent: + derived_from: tosca.nodes.Root + properties: + # domain-specific software component version + component_version: + type: version + required: false + description: > + Software component version. + admin_credential: + type: tosca.datatypes.Credential + required: false + requirements: + - host: + capability: tosca.capabilities.Container + node: tosca.nodes.Compute + relationship: tosca.relationships.HostedOn + +tosca.nodes.DBMS: + derived_from: tosca.nodes.SoftwareComponent + properties: + port: + required: false + type: integer + description: > + The port the DBMS service will listen to for data and requests. + root_password: + required: false + type: string + description: > + The root password for the DBMS service. + capabilities: + host: + type: tosca.capabilities.Container + valid_source_types: [tosca.nodes.Database] + +tosca.nodes.Database: + derived_from: tosca.nodes.Root + properties: + user: + required: false + type: string + description: > + User account name for DB administration + port: + required: false + type: integer + description: > + The port the database service will use to listen for incoming data and + requests. + name: + required: false + type: string + description: > + The name of the database. + password: + required: false + type: string + description: > + The password for the DB user account + requirements: + - host: + capability: tosca.capabilities.Container + node: tosca.nodes.DBMS + relationship: tosca.relationships.HostedOn + capabilities: + database_endpoint: + type: tosca.capabilities.Endpoint.Database + +tosca.nodes.WebServer: + derived_from: tosca.nodes.SoftwareComponent + capabilities: + data_endpoint: + type: tosca.capabilities.Endpoint + admin_endpoint: + type: tosca.capabilities.Endpoint.Admin + host: + type: tosca.capabilities.Container + valid_source_types: [tosca.nodes.WebApplication] + +tosca.nodes.WebApplication: + derived_from: tosca.nodes.Root + properties: + context_root: + type: string + required: false + requirements: + - host: + capability: tosca.capabilities.Container + node: tosca.nodes.WebServer + relationship: tosca.relationships.HostedOn + capabilities: + app_endpoint: + type: tosca.capabilities.Endpoint + +tosca.nodes.BlockStorage: + derived_from: tosca.nodes.Root + properties: + size: + type: scalar-unit.size + constraints: + - greater_or_equal: 1 MB + volume_id: + type: string + required: false + snapshot_id: + type: string + required: false + attributes: + volume_id: + type: string + capabilities: + attachment: + type: tosca.capabilities.Attachment + +tosca.nodes.network.Network: + derived_from: tosca.nodes.Root + description: > + The TOSCA Network node represents a simple, logical network service. + properties: + ip_version: + type: integer + required: false + default: 4 + constraints: + - valid_values: [ 4, 6 ] + description: > + The IP version of the requested network. Valid values are 4 for ipv4 + or 6 for ipv6. + cidr: + type: string + required: false + description: > + The cidr block of the requested network. + start_ip: + type: string + required: false + description: > + The IP address to be used as the start of a pool of addresses within + the full IP range derived from the cidr block. + end_ip: + type: string + required: false + description: > + The IP address to be used as the end of a pool of addresses within + the full IP range derived from the cidr block. + gateway_ip: + type: string + required: false + description: > + The gateway IP address. + network_name: + type: string + required: false + description: > + An identifier that represents an existing Network instance in the + underlying cloud infrastructure or can be used as the name of the + newly created network. If network_name is provided and no other + properties are provided (with exception of network_id), then an + existing network instance will be used. If network_name is provided + alongside with more properties then a new network with this name will + be created. + network_id: + type: string + required: false + description: > + An identifier that represents an existing Network instance in the + underlying cloud infrastructure. This property is mutually exclusive + with all other properties except network_name. This can be used alone + or together with network_name to identify an existing network. + segmentation_id: + type: string + required: false + description: > + A segmentation identifier in the underlying cloud infrastructure. + E.g. VLAN ID, GRE tunnel ID, etc.. + network_type: + type: string + required: false + description: > + It specifies the nature of the physical network in the underlying + cloud infrastructure. Examples are flat, vlan, gre or vxlan. + For flat and vlan types, physical_network should be provided too. + physical_network: + type: string + required: false + description: > + It identifies the physical network on top of which the network is + implemented, e.g. physnet1. This property is required if network_type + is flat or vlan. + dhcp_enabled: + type: boolean + required: false + default: true + description: > + Indicates should DHCP service be enabled on the network or not. + capabilities: + link: + type: tosca.capabilities.network.Linkable + +tosca.nodes.network.Port: + derived_from: tosca.nodes.Root + description: > + The TOSCA Port node represents a logical entity that associates between + Compute and Network normative types. The Port node type effectively + represents a single virtual NIC on the Compute node instance. + properties: + ip_address: + type: string + required: false + description: > + Allow the user to set a static IP. + order: + type: integer + required: false + default: 0 + constraints: + - greater_or_equal: 0 + description: > + The order of the NIC on the compute instance (e.g. eth2). + is_default: + type: boolean + required: false + default: false + description: > + If is_default=true this port will be used for the default gateway + route. Only one port that is associated to single compute node can + set as is_default=true. + ip_range_start: + type: string + required: false + description: > + Defines the starting IP of a range to be allocated for the compute + instances that are associated with this Port. + ip_range_end: + type: string + required: false + description: > + Defines the ending IP of a range to be allocated for the compute + instances that are associated with this Port. + attributes: + ip_address: + type: string + requirements: + - binding: + description: > + Binding requirement expresses the relationship between Port and + Compute nodes. Effectively it indicates that the Port will be + attached to specific Compute node instance + capability: tosca.capabilities.network.Bindable + relationship: tosca.relationships.network.BindsTo + node: tosca.nodes.Compute + - link: + description: > + Link requirement expresses the relationship between Port and Network + nodes. It indicates which network this port will connect to. + capability: tosca.capabilities.network.Linkable + relationship: tosca.relationships.network.LinksTo + node: tosca.nodes.network.Network + +tosca.nodes.ObjectStorage: + derived_from: tosca.nodes.Root + description: > + The TOSCA ObjectStorage node represents storage that provides the ability + to store data as objects (or BLOBs of data) without consideration for the + underlying filesystem or devices + properties: + name: + type: string + required: true + description: > + The logical name of the object store (or container). + size: + type: scalar-unit.size + required: false + constraints: + - greater_or_equal: 0 GB + description: > + The requested initial storage size. + maxsize: + type: scalar-unit.size + required: false + constraints: + - greater_or_equal: 0 GB + description: > + The requested maximum storage size. + capabilities: + storage_endpoint: + type: tosca.capabilities.Endpoint + +tosca.nodes.LoadBalancer: + derived_from: tosca.nodes.Root + properties: + algorithm: + type: string + required: false + status: experimental + capabilities: + client: + type: tosca.capabilities.Endpoint.Public + occurrences: [0, UNBOUNDED] + description: the Floating (IP) client’s on the public network can connect to + requirements: + - application: + capability: tosca.capabilities.Endpoint + relationship: tosca.relationships.RoutesTo + occurrences: [0, UNBOUNDED] + description: Connection to one or more load balanced applications + +tosca.nodes.Container.Application: + derived_from: tosca.nodes.Root + requirements: + - host: + capability: tosca.capabilities.Container + node: tosca.nodes.Container + relationship: tosca.relationships.HostedOn + +tosca.nodes.Container.Runtime: + derived_from: tosca.nodes.SoftwareComponent + capabilities: + host: + type: tosca.capabilities.Container + scalable: + type: tosca.capabilities.Scalable + +tosca.nodes.Container.Application.Docker: + derived_from: tosca.nodes.Container.Application + requirements: + - host: + capability: tosca.capabilities.Container.Docker + +########################################################################## +# Relationship Type. +# A Relationship Type is a reusable entity that defines the type of one +# or more relationships between Node Types or Node Templates. +########################################################################## +tosca.relationships.Root: + description: > + The TOSCA root Relationship Type all other TOSCA base Relationship Types + derive from. + attributes: + tosca_id: + type: string + tosca_name: + type: string + interfaces: + Configure: + type: tosca.interfaces.relationship.Configure + +tosca.relationships.DependsOn: + derived_from: tosca.relationships.Root + +tosca.relationships.HostedOn: + derived_from: tosca.relationships.Root + valid_target_types: [ tosca.capabilities.Container ] + +tosca.relationships.ConnectsTo: + derived_from: tosca.relationships.Root + valid_target_types: [ tosca.capabilities.Endpoint ] + credential: + type: tosca.datatypes.Credential + required: false + +tosca.relationships.AttachesTo: + derived_from: tosca.relationships.Root + valid_target_types: [ tosca.capabilities.Attachment ] + properties: + location: + required: true + type: string + constraints: + - min_length: 1 + device: + required: false + type: string + +tosca.relationships.RoutesTo: + derived_from: tosca.relationships.ConnectsTo + valid_target_types: [ tosca.capabilities.Endpoint ] + +tosca.relationships.network.LinksTo: + derived_from: tosca.relationships.DependsOn + valid_target_types: [ tosca.capabilities.network.Linkable ] + +tosca.relationships.network.BindsTo: + derived_from: tosca.relationships.DependsOn + valid_target_types: [ tosca.capabilities.network.Bindable ] + +########################################################################## +# Capability Type. +# A Capability Type is a reusable entity that describes a kind of +# capability that a Node Type can declare to expose. +########################################################################## +tosca.capabilities.Root: + description: > + The TOSCA root Capability Type all other TOSCA base Capability Types + derive from. + +tosca.capabilities.Node: + derived_from: tosca.capabilities.Root + +tosca.capabilities.Container: + derived_from: tosca.capabilities.Root + properties: + num_cpus: + required: false + type: integer + constraints: + - greater_or_equal: 1 + cpu_frequency: + required: false + type: scalar-unit.frequency + constraints: + - greater_or_equal: 0.1 GHz + disk_size: + required: false + type: scalar-unit.size + constraints: + - greater_or_equal: 0 MB + mem_size: + required: false + type: scalar-unit.size + constraints: + - greater_or_equal: 0 MB + +tosca.capabilities.Endpoint: + derived_from: tosca.capabilities.Root + properties: + protocol: + type: string + required: true + default: tcp + port: + type: tosca.datatypes.network.PortDef + required: false + secure: + type: boolean + required: false + default: false + url_path: + type: string + required: false + port_name: + type: string + required: false + network_name: + type: string + required: false + initiator: + type: string + required: false + default: source + constraints: + - valid_values: [source, target, peer] + ports: + type: map + required: false + constraints: + - min_length: 1 + entry_schema: + type: tosca.datatypes.network.PortSpec + attributes: + ip_address: + type: string + +tosca.capabilities.Endpoint.Admin: + derived_from: tosca.capabilities.Endpoint + properties: + secure: + type: boolean + default: true + constraints: + - equal: true + +tosca.capabilities.Endpoint.Public: + derived_from: tosca.capabilities.Endpoint + properties: + # Change the default network_name to use the first public network found + network_name: + type: string + default: PUBLIC + constraints: + - equal: PUBLIC + floating: + description: > + Indicates that the public address should be allocated from a pool of + floating IPs that are associated with the network. + type: boolean + default: false + status: experimental + dns_name: + description: The optional name to register with DNS + type: string + required: false + status: experimental + +tosca.capabilities.Scalable: + derived_from: tosca.capabilities.Root + properties: + min_instances: + type: integer + required: true + default: 1 + description: > + This property is used to indicate the minimum number of instances + that should be created for the associated TOSCA Node Template by + a TOSCA orchestrator. + max_instances: + type: integer + required: true + default: 1 + description: > + This property is used to indicate the maximum number of instances + that should be created for the associated TOSCA Node Template by + a TOSCA orchestrator. + default_instances: + type: integer + required: false + description: > + An optional property that indicates the requested default number + of instances that should be the starting number of instances a + TOSCA orchestrator should attempt to allocate. + The value for this property MUST be in the range between the values + set for min_instances and max_instances properties. + +tosca.capabilities.Endpoint.Database: + derived_from: tosca.capabilities.Endpoint + +tosca.capabilities.Attachment: + derived_from: tosca.capabilities.Root + +tosca.capabilities.network.Linkable: + derived_from: tosca.capabilities.Root + description: > + A node type that includes the Linkable capability indicates that it can + be pointed by tosca.relationships.network.LinksTo relationship type, which + represents an association relationship between Port and Network node types. + +tosca.capabilities.network.Bindable: + derived_from: tosca.capabilities.Root + description: > + A node type that includes the Bindable capability indicates that it can + be pointed by tosca.relationships.network.BindsTo relationship type, which + represents a network association relationship between Port and Compute node + types. + +tosca.capabilities.OperatingSystem: + derived_from: tosca.capabilities.Root + properties: + architecture: + required: false + type: string + description: > + The host Operating System (OS) architecture. + type: + required: false + type: string + description: > + The host Operating System (OS) type. + distribution: + required: false + type: string + description: > + The host Operating System (OS) distribution. Examples of valid values + for an “type” of “Linux” would include: + debian, fedora, rhel and ubuntu. + version: + required: false + type: version + description: > + The host Operating System version. + +tosca.capabilities.Container.Docker: + derived_from: tosca.capabilities.Container + properties: + version: + type: list + required: false + entry_schema: + type: version + description: > + The Docker version capability. + publish_all: + type: boolean + default: false + required: false + description: > + Indicates that all ports (ranges) listed in the dockerfile + using the EXPOSE keyword be published. + publish_ports: + type: list + entry_schema: + type: PortSpec + required: false + description: > + List of ports mappings from source (Docker container) + to target (host) ports to publish. + expose_ports: + type: list + entry_schema: + type: PortSpec + required: false + description: > + List of ports mappings from source (Docker container) to expose + to other Docker containers (not accessible outside host). + volumes: + type: list + entry_schema: + type: string + required: false + description: > + The dockerfile VOLUME command which is used to enable access + from the Docker container to a directory on the host machine. + host_id: + type: string + required: false + description: > + The optional identifier of an existing host resource + that should be used to run this container on. + volume_id: + type: string + required: false + description: > + The optional identifier of an existing storage volume (resource) + that should be used to create the container's mount point(s) on. + +########################################################################## + # Interfaces Type. + # The Interfaces element describes a list of one or more interface + # definitions for a modelable entity (e.g., a Node or Relationship Type) + # as defined within the TOSCA Simple Profile specification. +########################################################################## +tosca.interfaces.node.lifecycle.Standard: + create: + description: Standard lifecycle create operation. + configure: + description: Standard lifecycle configure operation. + start: + description: Standard lifecycle start operation. + stop: + description: Standard lifecycle stop operation. + delete: + description: Standard lifecycle delete operation. + +tosca.interfaces.relationship.Configure: + pre_configure_source: + description: Operation to pre-configure the source endpoint. + pre_configure_target: + description: Operation to pre-configure the target endpoint. + post_configure_source: + description: Operation to post-configure the source endpoint. + post_configure_target: + description: Operation to post-configure the target endpoint. + add_target: + description: Operation to add a target node. + remove_target: + description: Operation to remove a target node. + add_source: > + description: Operation to notify the target node of a source node which + is now available via a relationship. + description: + target_changed: > + description: Operation to notify source some property or attribute of the + target changed + +########################################################################## + # Data Type. + # A Datatype is a complex data type declaration which contains other + # complex or simple data types. +########################################################################## +tosca.datatypes.Root: + description: > + The TOSCA root Data Type all other TOSCA base Data Types derive from + +tosca.datatypes.network.NetworkInfo: + derived_from: tosca.datatypes.Root + properties: + network_name: + type: string + network_id: + type: string + addresses: + type: list + entry_schema: + type: string + +tosca.datatypes.network.PortInfo: + derived_from: tosca.datatypes.Root + properties: + port_name: + type: string + port_id: + type: string + network_id: + type: string + mac_address: + type: string + addresses: + type: list + entry_schema: + type: string + +tosca.datatypes.network.PortDef: + derived_from: tosca.datatypes.Root + type: integer + constraints: + - in_range: [ 1, 65535 ] + +tosca.datatypes.network.PortSpec: + derived_from: tosca.datatypes.Root + properties: + protocol: + type: string + required: true + default: tcp + constraints: + - valid_values: [ udp, tcp, igmp ] + target: + type: PortDef + required: false + target_range: + type: range + required: false + constraints: + - in_range: [ 1, 65535 ] + source: + type: PortDef + required: false + source_range: + type: range + required: false + constraints: + - in_range: [ 1, 65535 ] + +tosca.datatypes.Credential: + derived_from: tosca.datatypes.Root + properties: + protocol: + type: string + token_type: + type: string + token: + type: string + keys: + type: map + entry_schema: + type: string + user: + type: string + required: false + +########################################################################## + # Artifact Type. + # An Artifact Type is a reusable entity that defines the type of one or more + # files which Node Types or Node Templates can have dependent relationships + # and used during operations such as during installation or deployment. +########################################################################## +tosca.artifacts.Root: + description: > + The TOSCA Artifact Type all other TOSCA Artifact Types derive from + properties: + version: version + +tosca.artifacts.File: + derived_from: tosca.artifacts.Root + +tosca.artifacts.Deployment: + derived_from: tosca.artifacts.Root + description: TOSCA base type for deployment artifacts + +tosca.artifacts.Deployment.Image: + derived_from: tosca.artifacts.Deployment + +tosca.artifacts.Deployment.Image.VM: + derived_from: tosca.artifacts.Deployment.Image + +tosca.artifacts.Implementation: + derived_from: tosca.artifacts.Root + description: TOSCA base type for implementation artifacts + +tosca.artifacts.Implementation.Bash: + derived_from: tosca.artifacts.Implementation + description: Script artifact for the Unix Bash shell + mime_type: application/x-sh + file_ext: [ sh ] + +tosca.artifacts.Implementation.Python: + derived_from: tosca.artifacts.Implementation + description: Artifact for the interpreted Python language + mime_type: application/x-python + file_ext: [ py ] + +tosca.artifacts.Deployment.Image.Container.Docker: + derived_from: tosca.artifacts.Deployment.Image + description: Docker container image + +tosca.artifacts.Deployment.Image.VM.ISO: + derived_from: tosca.artifacts.Deployment.Image + description: Virtual Machine (VM) image in ISO disk format + mime_type: application/octet-stream + file_ext: [ iso ] + +tosca.artifacts.Deployment.Image.VM.QCOW2: + derived_from: tosca.artifacts.Deployment.Image + description: Virtual Machine (VM) image in QCOW v2 standard disk format + mime_type: application/octet-stream + file_ext: [ qcow2 ] + +########################################################################## + # Policy Type. + # TOSCA Policy Types represent logical grouping of TOSCA nodes that have + # an implied relationship and need to be orchestrated or managed together + # to achieve some result. +########################################################################## +tosca.policies.Root: + description: The TOSCA Policy Type all other TOSCA Policy Types derive from. + +tosca.policies.Placement: + derived_from: tosca.policies.Root + description: The TOSCA Policy Type definition that is used to govern + placement of TOSCA nodes or groups of nodes. + +tosca.policies.Scaling: + derived_from: tosca.policies.Root + description: The TOSCA Policy Type definition that is used to govern + scaling of TOSCA nodes or groups of nodes. + +tosca.policies.Update: + derived_from: tosca.policies.Root + description: The TOSCA Policy Type definition that is used to govern + update of TOSCA nodes or groups of nodes. + +tosca.policies.Performance: + derived_from: tosca.policies.Root + description: The TOSCA Policy Type definition that is used to declare + performance requirements for TOSCA nodes or groups of nodes. + +########################################################################## + # Group Type. + # Group Type represents logical grouping of TOSCA nodes that have an + # implied membership relationship and may need to be orchestrated or + # managed together to achieve some result. +########################################################################## +tosca.groups.Root: + description: The TOSCA Group Type all other TOSCA Group Types derive from + interfaces: + Standard: + type: tosca.interfaces.node.lifecycle.Standard diff --git a/tosca2heat/tosca-parser/toscaparser/elements/__init__.py b/tosca2heat/tosca-parser/toscaparser/elements/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/elements/__init__.py diff --git a/tosca2heat/tosca-parser/toscaparser/elements/artifacttype.py b/tosca2heat/tosca-parser/toscaparser/elements/artifacttype.py new file mode 100644 index 0000000..3bfd7d0 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/elements/artifacttype.py @@ -0,0 +1,45 @@ +# Licensed 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 toscaparser.elements.statefulentitytype import StatefulEntityType + + +class ArtifactTypeDef(StatefulEntityType): + '''TOSCA built-in artifacts type.''' + + def __init__(self, atype, custom_def=None): + super(ArtifactTypeDef, self).__init__(atype, self.ARTIFACT_PREFIX, + custom_def) + self.type = atype + self.properties = None + if self.PROPERTIES in self.defs: + self.properties = self.defs[self.PROPERTIES] + self.parent_artifacts = self._get_parent_artifacts() + + def _get_parent_artifacts(self): + artifacts = {} + parent_artif = self.parent_type + if parent_artif: + while parent_artif != 'tosca.artifacts.Root': + artifacts[parent_artif] = self.TOSCA_DEF[parent_artif] + parent_artif = artifacts[parent_artif]['derived_from'] + return artifacts + + @property + def parent_type(self): + '''Return an artifact this artifact is derived from.''' + return self.derived_from(self.defs) + + def get_artifact(self, name): + '''Return the definition of an artifact field by name.''' + if name in self.defs: + return self.defs[name] diff --git a/tosca2heat/tosca-parser/toscaparser/elements/attribute_definition.py b/tosca2heat/tosca-parser/toscaparser/elements/attribute_definition.py new file mode 100644 index 0000000..35ba27f --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/elements/attribute_definition.py @@ -0,0 +1,20 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +class AttributeDef(object): + '''TOSCA built-in Attribute type.''' + + def __init__(self, name, value=None, schema=None): + self.name = name + self.value = value + self.schema = schema diff --git a/tosca2heat/tosca-parser/toscaparser/elements/capabilitytype.py b/tosca2heat/tosca-parser/toscaparser/elements/capabilitytype.py new file mode 100644 index 0000000..0413443 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/elements/capabilitytype.py @@ -0,0 +1,75 @@ +# Licensed 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 toscaparser.elements.property_definition import PropertyDef +from toscaparser.elements.statefulentitytype import StatefulEntityType + + +class CapabilityTypeDef(StatefulEntityType): + '''TOSCA built-in capabilities type.''' + + def __init__(self, name, ctype, ntype, custom_def=None): + self.name = name + super(CapabilityTypeDef, self).__init__(ctype, self.CAPABILITY_PREFIX, + custom_def) + self.nodetype = ntype + self.properties = None + if self.PROPERTIES in self.defs: + self.properties = self.defs[self.PROPERTIES] + self.parent_capabilities = self._get_parent_capabilities(custom_def) + + def get_properties_def_objects(self): + '''Return a list of property definition objects.''' + properties = [] + parent_properties = {} + if self.parent_capabilities: + for type, value in self.parent_capabilities.items(): + parent_properties[type] = value.get('properties') + if self.properties: + for prop, schema in self.properties.items(): + properties.append(PropertyDef(prop, None, schema)) + if parent_properties: + for parent, props in parent_properties.items(): + for prop, schema in props.items(): + # add parent property if not overridden by children type + if not self.properties or \ + prop not in self.properties.keys(): + properties.append(PropertyDef(prop, None, schema)) + return properties + + def get_properties_def(self): + '''Return a dictionary of property definition name-object pairs.''' + return {prop.name: prop + for prop in self.get_properties_def_objects()} + + def get_property_def_value(self, name): + '''Return the definition of a given property name.''' + props_def = self.get_properties_def() + if props_def and name in props_def: + return props_def[name].value + + def _get_parent_capabilities(self, custom_def=None): + capabilities = {} + parent_cap = self.parent_type + if parent_cap: + while parent_cap != 'tosca.capabilities.Root': + if parent_cap in self.TOSCA_DEF.keys(): + capabilities[parent_cap] = self.TOSCA_DEF[parent_cap] + elif custom_def and parent_cap in custom_def.keys(): + capabilities[parent_cap] = custom_def[parent_cap] + parent_cap = capabilities[parent_cap]['derived_from'] + return capabilities + + @property + def parent_type(self): + '''Return a capability this capability is derived from.''' + return self.derived_from(self.defs) diff --git a/tosca2heat/tosca-parser/toscaparser/elements/constraints.py b/tosca2heat/tosca-parser/toscaparser/elements/constraints.py new file mode 100644 index 0000000..9883da3 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/elements/constraints.py @@ -0,0 +1,611 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import collections +import datetime +import re + +import toscaparser +from toscaparser.common.exception import ExceptionCollector +from toscaparser.common.exception import InvalidSchemaError +from toscaparser.common.exception import ValidationError +from toscaparser.elements import scalarunit +from toscaparser.utils.gettextutils import _ + + +class Schema(collections.Mapping): + + KEYS = ( + TYPE, REQUIRED, DESCRIPTION, + DEFAULT, CONSTRAINTS, ENTRYSCHEMA + ) = ( + 'type', 'required', 'description', + 'default', 'constraints', 'entry_schema' + ) + + PROPERTY_TYPES = ( + INTEGER, STRING, BOOLEAN, FLOAT, RANGE, + NUMBER, TIMESTAMP, LIST, MAP, + SCALAR_UNIT_SIZE, SCALAR_UNIT_FREQUENCY, SCALAR_UNIT_TIME, + PORTDEF, VERSION + ) = ( + 'integer', 'string', 'boolean', 'float', 'range', + 'number', 'timestamp', 'list', 'map', + 'scalar-unit.size', 'scalar-unit.frequency', 'scalar-unit.time', + 'PortDef', 'version' + ) + + SCALAR_UNIT_SIZE_DEFAULT = 'B' + SCALAR_UNIT_SIZE_DICT = {'B': 1, 'KB': 1000, 'KIB': 1024, 'MB': 1000000, + 'MIB': 1048576, 'GB': 1000000000, + 'GIB': 1073741824, 'TB': 1000000000000, + 'TIB': 1099511627776} + + def __init__(self, name, schema_dict): + self.name = name + if not isinstance(schema_dict, collections.Mapping): + msg = (_('Schema definition of "%(pname)s" must be a dict.') + % dict(pname=name)) + ExceptionCollector.appendException(InvalidSchemaError(message=msg)) + + try: + schema_dict['type'] + except KeyError: + msg = (_('Schema definition of "%(pname)s" must have a "type" ' + 'attribute.') % dict(pname=name)) + ExceptionCollector.appendException(InvalidSchemaError(message=msg)) + + self.schema = schema_dict + self._len = None + self.constraints_list = [] + + @property + def type(self): + return self.schema[self.TYPE] + + @property + def required(self): + return self.schema.get(self.REQUIRED, True) + + @property + def description(self): + return self.schema.get(self.DESCRIPTION, '') + + @property + def default(self): + return self.schema.get(self.DEFAULT) + + @property + def constraints(self): + if not self.constraints_list: + constraint_schemata = self.schema.get(self.CONSTRAINTS) + if constraint_schemata: + self.constraints_list = [Constraint(self.name, + self.type, + cschema) + for cschema in constraint_schemata] + return self.constraints_list + + @property + def entry_schema(self): + return self.schema.get(self.ENTRYSCHEMA) + + def __getitem__(self, key): + return self.schema[key] + + def __iter__(self): + for k in self.KEYS: + try: + self.schema[k] + except KeyError: + pass + else: + yield k + + def __len__(self): + if self._len is None: + self._len = len(list(iter(self))) + return self._len + + +class Constraint(object): + '''Parent class for constraints for a Property or Input.''' + + CONSTRAINTS = (EQUAL, GREATER_THAN, + GREATER_OR_EQUAL, LESS_THAN, LESS_OR_EQUAL, IN_RANGE, + VALID_VALUES, LENGTH, MIN_LENGTH, MAX_LENGTH, PATTERN) = \ + ('equal', 'greater_than', 'greater_or_equal', 'less_than', + 'less_or_equal', 'in_range', 'valid_values', 'length', + 'min_length', 'max_length', 'pattern') + + UNBOUNDED = 'UNBOUNDED' + + def __new__(cls, property_name, property_type, constraint): + if cls is not Constraint: + return super(Constraint, cls).__new__(cls) + + if(not isinstance(constraint, collections.Mapping) or + len(constraint) != 1): + ExceptionCollector.appendException( + InvalidSchemaError(message=_('Invalid constraint schema.'))) + + for type in constraint.keys(): + ConstraintClass = get_constraint_class(type) + if not ConstraintClass: + msg = _('Invalid property "%s".') % type + ExceptionCollector.appendException( + InvalidSchemaError(message=msg)) + + return ConstraintClass(property_name, property_type, constraint) + + def __init__(self, property_name, property_type, constraint): + self.property_name = property_name + self.property_type = property_type + self.constraint_value = constraint[self.constraint_key] + self.constraint_value_msg = self.constraint_value + if self.property_type in scalarunit.ScalarUnit.SCALAR_UNIT_TYPES: + self.constraint_value = self._get_scalarunit_constraint_value() + # check if constraint is valid for property type + if property_type not in self.valid_prop_types: + msg = _('Property "%(ctype)s" is not valid for data type ' + '"%(dtype)s".') % dict( + ctype=self.constraint_key, + dtype=property_type) + ExceptionCollector.appendException(InvalidSchemaError(message=msg)) + + def _get_scalarunit_constraint_value(self): + if self.property_type in scalarunit.ScalarUnit.SCALAR_UNIT_TYPES: + ScalarUnit_Class = (scalarunit. + get_scalarunit_class(self.property_type)) + if isinstance(self.constraint_value, list): + return [ScalarUnit_Class(v).get_num_from_scalar_unit() + for v in self.constraint_value] + else: + return (ScalarUnit_Class(self.constraint_value). + get_num_from_scalar_unit()) + + def _err_msg(self, value): + return _('Property "%s" could not be validated.') % self.property_name + + def validate(self, value): + self.value_msg = value + if self.property_type in scalarunit.ScalarUnit.SCALAR_UNIT_TYPES: + value = scalarunit.get_scalarunit_value(self.property_type, value) + if not self._is_valid(value): + err_msg = self._err_msg(value) + ExceptionCollector.appendException( + ValidationError(message=err_msg)) + + +class Equal(Constraint): + """Constraint class for "equal" + + Constrains a property or parameter to a value equal to ('=') + the value declared. + """ + + constraint_key = Constraint.EQUAL + + valid_prop_types = Schema.PROPERTY_TYPES + + def _is_valid(self, value): + if value == self.constraint_value: + return True + + return False + + def _err_msg(self, value): + return (_('The value "%(pvalue)s" of property "%(pname)s" is not ' + 'equal to "%(cvalue)s".') % + dict(pname=self.property_name, + pvalue=self.value_msg, + cvalue=self.constraint_value_msg)) + + +class GreaterThan(Constraint): + """Constraint class for "greater_than" + + Constrains a property or parameter to a value greater than ('>') + the value declared. + """ + + constraint_key = Constraint.GREATER_THAN + + valid_types = (int, float, datetime.date, + datetime.time, datetime.datetime) + + valid_prop_types = (Schema.INTEGER, Schema.FLOAT, Schema.TIMESTAMP, + Schema.SCALAR_UNIT_SIZE, Schema.SCALAR_UNIT_FREQUENCY, + Schema.SCALAR_UNIT_TIME) + + def __init__(self, property_name, property_type, constraint): + super(GreaterThan, self).__init__(property_name, property_type, + constraint) + if not isinstance(constraint[self.GREATER_THAN], self.valid_types): + ExceptionCollector.appendException( + InvalidSchemaError(message=_('The property "greater_than" ' + 'expects comparable values.'))) + + def _is_valid(self, value): + if value > self.constraint_value: + return True + + return False + + def _err_msg(self, value): + return (_('The value "%(pvalue)s" of property "%(pname)s" must be ' + 'greater than "%(cvalue)s".') % + dict(pname=self.property_name, + pvalue=self.value_msg, + cvalue=self.constraint_value_msg)) + + +class GreaterOrEqual(Constraint): + """Constraint class for "greater_or_equal" + + Constrains a property or parameter to a value greater than or equal + to ('>=') the value declared. + """ + + constraint_key = Constraint.GREATER_OR_EQUAL + + valid_types = (int, float, datetime.date, + datetime.time, datetime.datetime) + + valid_prop_types = (Schema.INTEGER, Schema.FLOAT, Schema.TIMESTAMP, + Schema.SCALAR_UNIT_SIZE, Schema.SCALAR_UNIT_FREQUENCY, + Schema.SCALAR_UNIT_TIME) + + def __init__(self, property_name, property_type, constraint): + super(GreaterOrEqual, self).__init__(property_name, property_type, + constraint) + if not isinstance(self.constraint_value, self.valid_types): + ExceptionCollector.appendException( + InvalidSchemaError(message=_('The property ' + '"greater_or_equal" expects ' + 'comparable values.'))) + + def _is_valid(self, value): + if toscaparser.functions.is_function(value) or \ + value >= self.constraint_value: + return True + return False + + def _err_msg(self, value): + return (_('The value "%(pvalue)s" of property "%(pname)s" must be ' + 'greater than or equal to "%(cvalue)s".') % + dict(pname=self.property_name, + pvalue=self.value_msg, + cvalue=self.constraint_value_msg)) + + +class LessThan(Constraint): + """Constraint class for "less_than" + + Constrains a property or parameter to a value less than ('<') + the value declared. + """ + + constraint_key = Constraint.LESS_THAN + + valid_types = (int, float, datetime.date, + datetime.time, datetime.datetime) + + valid_prop_types = (Schema.INTEGER, Schema.FLOAT, Schema.TIMESTAMP, + Schema.SCALAR_UNIT_SIZE, Schema.SCALAR_UNIT_FREQUENCY, + Schema.SCALAR_UNIT_TIME) + + def __init__(self, property_name, property_type, constraint): + super(LessThan, self).__init__(property_name, property_type, + constraint) + if not isinstance(self.constraint_value, self.valid_types): + ExceptionCollector.appendException( + InvalidSchemaError(message=_('The property "less_than" ' + 'expects comparable values.'))) + + def _is_valid(self, value): + if value < self.constraint_value: + return True + + return False + + def _err_msg(self, value): + return (_('The value "%(pvalue)s" of property "%(pname)s" must be ' + 'less than "%(cvalue)s".') % + dict(pname=self.property_name, + pvalue=self.value_msg, + cvalue=self.constraint_value_msg)) + + +class LessOrEqual(Constraint): + """Constraint class for "less_or_equal" + + Constrains a property or parameter to a value less than or equal + to ('<=') the value declared. + """ + + constraint_key = Constraint.LESS_OR_EQUAL + + valid_types = (int, float, datetime.date, + datetime.time, datetime.datetime) + + valid_prop_types = (Schema.INTEGER, Schema.FLOAT, Schema.TIMESTAMP, + Schema.SCALAR_UNIT_SIZE, Schema.SCALAR_UNIT_FREQUENCY, + Schema.SCALAR_UNIT_TIME) + + def __init__(self, property_name, property_type, constraint): + super(LessOrEqual, self).__init__(property_name, property_type, + constraint) + if not isinstance(self.constraint_value, self.valid_types): + ExceptionCollector.appendException( + InvalidSchemaError(message=_('The property "less_or_equal" ' + 'expects comparable values.'))) + + def _is_valid(self, value): + if value <= self.constraint_value: + return True + + return False + + def _err_msg(self, value): + return (_('The value "%(pvalue)s" of property "%(pname)s" must be ' + 'less than or equal to "%(cvalue)s".') % + dict(pname=self.property_name, + pvalue=self.value_msg, + cvalue=self.constraint_value_msg)) + + +class InRange(Constraint): + """Constraint class for "in_range" + + Constrains a property or parameter to a value in range of (inclusive) + the two values declared. + """ + + constraint_key = Constraint.IN_RANGE + + valid_types = (int, float, datetime.date, + datetime.time, datetime.datetime, str) + + valid_prop_types = (Schema.INTEGER, Schema.FLOAT, Schema.TIMESTAMP, + Schema.SCALAR_UNIT_SIZE, Schema.SCALAR_UNIT_FREQUENCY, + Schema.SCALAR_UNIT_TIME, Schema.RANGE) + + def __init__(self, property_name, property_type, constraint): + super(InRange, self).__init__(property_name, property_type, constraint) + if(not isinstance(self.constraint_value, collections.Sequence) or + (len(constraint[self.IN_RANGE]) != 2)): + ExceptionCollector.appendException( + InvalidSchemaError(message=_('The property "in_range" ' + 'expects a list.'))) + + msg = _('The property "in_range" expects comparable values.') + for value in self.constraint_value: + if not isinstance(value, self.valid_types): + ExceptionCollector.appendException( + InvalidSchemaError(message=msg)) + # The only string we allow for range is the special value + # 'UNBOUNDED' + if(isinstance(value, str) and value != self.UNBOUNDED): + ExceptionCollector.appendException( + InvalidSchemaError(message=msg)) + + self.min = self.constraint_value[0] + self.max = self.constraint_value[1] + + def _is_valid(self, value): + if not isinstance(self.min, str): + if value < self.min: + return False + elif self.min != self.UNBOUNDED: + return False + if not isinstance(self.max, str): + if value > self.max: + return False + elif self.max != self.UNBOUNDED: + return False + return True + + def _err_msg(self, value): + return (_('The value "%(pvalue)s" of property "%(pname)s" is out of ' + 'range "(min:%(vmin)s, max:%(vmax)s)".') % + dict(pname=self.property_name, + pvalue=self.value_msg, + vmin=self.constraint_value_msg[0], + vmax=self.constraint_value_msg[1])) + + +class ValidValues(Constraint): + """Constraint class for "valid_values" + + Constrains a property or parameter to a value that is in the list of + declared values. + """ + constraint_key = Constraint.VALID_VALUES + + valid_prop_types = Schema.PROPERTY_TYPES + + def __init__(self, property_name, property_type, constraint): + super(ValidValues, self).__init__(property_name, property_type, + constraint) + if not isinstance(self.constraint_value, collections.Sequence): + ExceptionCollector.appendException( + InvalidSchemaError(message=_('The property "valid_values" ' + 'expects a list.'))) + + def _is_valid(self, value): + if isinstance(value, list): + return all(v in self.constraint_value for v in value) + return value in self.constraint_value + + def _err_msg(self, value): + allowed = '[%s]' % ', '.join(str(a) for a in self.constraint_value) + return (_('The value "%(pvalue)s" of property "%(pname)s" is not ' + 'valid. Expected a value from "%(cvalue)s".') % + dict(pname=self.property_name, + pvalue=value, + cvalue=allowed)) + + +class Length(Constraint): + """Constraint class for "length" + + Constrains the property or parameter to a value of a given length. + """ + + constraint_key = Constraint.LENGTH + + valid_types = (int, ) + + valid_prop_types = (Schema.STRING, ) + + def __init__(self, property_name, property_type, constraint): + super(Length, self).__init__(property_name, property_type, constraint) + if not isinstance(self.constraint_value, self.valid_types): + ExceptionCollector.appendException( + InvalidSchemaError(message=_('The property "length" expects ' + 'an integer.'))) + + def _is_valid(self, value): + if isinstance(value, str) and len(value) == self.constraint_value: + return True + + return False + + def _err_msg(self, value): + return (_('Length of value "%(pvalue)s" of property "%(pname)s" ' + 'must be equal to "%(cvalue)s".') % + dict(pname=self.property_name, + pvalue=value, + cvalue=self.constraint_value)) + + +class MinLength(Constraint): + """Constraint class for "min_length" + + Constrains the property or parameter to a value to a minimum length. + """ + + constraint_key = Constraint.MIN_LENGTH + + valid_types = (int, ) + + valid_prop_types = (Schema.STRING, Schema.MAP) + + def __init__(self, property_name, property_type, constraint): + super(MinLength, self).__init__(property_name, property_type, + constraint) + if not isinstance(self.constraint_value, self.valid_types): + ExceptionCollector.appendException( + InvalidSchemaError(message=_('The property "min_length" ' + 'expects an integer.'))) + + def _is_valid(self, value): + if ((isinstance(value, str) or isinstance(value, dict)) and + len(value) >= self.constraint_value): + return True + + return False + + def _err_msg(self, value): + return (_('Length of value "%(pvalue)s" of property "%(pname)s" ' + 'must be at least "%(cvalue)s".') % + dict(pname=self.property_name, + pvalue=value, + cvalue=self.constraint_value)) + + +class MaxLength(Constraint): + """Constraint class for "max_length" + + Constrains the property or parameter to a value to a maximum length. + """ + + constraint_key = Constraint.MAX_LENGTH + + valid_types = (int, ) + + valid_prop_types = (Schema.STRING, Schema.MAP) + + def __init__(self, property_name, property_type, constraint): + super(MaxLength, self).__init__(property_name, property_type, + constraint) + if not isinstance(self.constraint_value, self.valid_types): + ExceptionCollector.appendException( + InvalidSchemaError(message=_('The property "max_length" ' + 'expects an integer.'))) + + def _is_valid(self, value): + if ((isinstance(value, str) or isinstance(value, dict)) and + len(value) <= self.constraint_value): + return True + + return False + + def _err_msg(self, value): + return (_('Length of value "%(pvalue)s" of property "%(pname)s" ' + 'must be no greater than "%(cvalue)s".') % + dict(pname=self.property_name, + pvalue=value, + cvalue=self.constraint_value)) + + +class Pattern(Constraint): + """Constraint class for "pattern" + + Constrains the property or parameter to a value that is allowed by + the provided regular expression. + """ + + constraint_key = Constraint.PATTERN + + valid_types = (str, ) + + valid_prop_types = (Schema.STRING, ) + + def __init__(self, property_name, property_type, constraint): + super(Pattern, self).__init__(property_name, property_type, constraint) + if not isinstance(self.constraint_value, self.valid_types): + ExceptionCollector.appendException( + InvalidSchemaError(message=_('The property "pattern" ' + 'expects a string.'))) + self.match = re.compile(self.constraint_value).match + + def _is_valid(self, value): + match = self.match(value) + return match is not None and match.end() == len(value) + + def _err_msg(self, value): + return (_('The value "%(pvalue)s" of property "%(pname)s" does not ' + 'match pattern "%(cvalue)s".') % + dict(pname=self.property_name, + pvalue=value, + cvalue=self.constraint_value)) + + +constraint_mapping = { + Constraint.EQUAL: Equal, + Constraint.GREATER_THAN: GreaterThan, + Constraint.GREATER_OR_EQUAL: GreaterOrEqual, + Constraint.LESS_THAN: LessThan, + Constraint.LESS_OR_EQUAL: LessOrEqual, + Constraint.IN_RANGE: InRange, + Constraint.VALID_VALUES: ValidValues, + Constraint.LENGTH: Length, + Constraint.MIN_LENGTH: MinLength, + Constraint.MAX_LENGTH: MaxLength, + Constraint.PATTERN: Pattern + } + + +def get_constraint_class(type): + return constraint_mapping.get(type) diff --git a/tosca2heat/tosca-parser/toscaparser/elements/datatype.py b/tosca2heat/tosca-parser/toscaparser/elements/datatype.py new file mode 100644 index 0000000..7e05a69 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/elements/datatype.py @@ -0,0 +1,56 @@ +# Licensed 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 toscaparser.elements.statefulentitytype import StatefulEntityType + + +class DataType(StatefulEntityType): + '''TOSCA built-in and user defined complex data type.''' + + def __init__(self, datatypename, custom_def=None): + super(DataType, self).__init__(datatypename, self.DATATYPE_PREFIX, + custom_def) + self.custom_def = custom_def + + @property + def parent_type(self): + '''Return a datatype this datatype is derived from.''' + ptype = self.derived_from(self.defs) + if ptype: + return DataType(ptype, self.custom_def) + return None + + @property + def value_type(self): + '''Return 'type' section in the datatype schema.''' + return self.entity_value(self.defs, 'type') + + def get_all_properties_objects(self): + '''Return all properties objects defined in type and parent type.''' + props_def = self.get_properties_def_objects() + ptype = self.parent_type + while ptype: + props_def.extend(ptype.get_properties_def_objects()) + ptype = ptype.parent_type + return props_def + + def get_all_properties(self): + '''Return a dictionary of all property definition name-object pairs.''' + return {prop.name: prop + for prop in self.get_all_properties_objects()} + + def get_all_property_value(self, name): + '''Return the value of a given property name.''' + props_def = self.get_all_properties() + if props_def and name in props_def.key(): + return props_def[name].value diff --git a/tosca2heat/tosca-parser/toscaparser/elements/entity_type.py b/tosca2heat/tosca-parser/toscaparser/elements/entity_type.py new file mode 100644 index 0000000..5d620a5 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/elements/entity_type.py @@ -0,0 +1,145 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import copy +import logging +import os +from toscaparser.common.exception import ExceptionCollector +from toscaparser.common.exception import ValidationError +from toscaparser.extensions.exttools import ExtTools +import toscaparser.utils.yamlparser + +log = logging.getLogger('tosca') + + +class EntityType(object): + '''Base class for TOSCA elements.''' + + SECTIONS = (DERIVED_FROM, PROPERTIES, ATTRIBUTES, REQUIREMENTS, + INTERFACES, CAPABILITIES, TYPE, ARTIFACTS) = \ + ('derived_from', 'properties', 'attributes', 'requirements', + 'interfaces', 'capabilities', 'type', 'artifacts') + + '''TOSCA definition file.''' + TOSCA_DEF_FILE = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "TOSCA_definition_1_0.yaml") + + loader = toscaparser.utils.yamlparser.load_yaml + + TOSCA_DEF = loader(TOSCA_DEF_FILE) + + RELATIONSHIP_TYPE = (DEPENDSON, HOSTEDON, CONNECTSTO, ATTACHESTO, + LINKSTO, BINDSTO) = \ + ('tosca.relationships.DependsOn', + 'tosca.relationships.HostedOn', + 'tosca.relationships.ConnectsTo', + 'tosca.relationships.AttachesTo', + 'tosca.relationships.network.LinksTo', + 'tosca.relationships.network.BindsTo') + + NODE_PREFIX = 'tosca.nodes.' + RELATIONSHIP_PREFIX = 'tosca.relationships.' + CAPABILITY_PREFIX = 'tosca.capabilities.' + INTERFACE_PREFIX = 'tosca.interfaces.' + ARTIFACT_PREFIX = 'tosca.artifacts.' + POLICY_PREFIX = 'tosca.policies.' + GROUP_PREFIX = 'tosca.groups.' + # currently the data types are defined only for network + # but may have changes in the future. + DATATYPE_PREFIX = 'tosca.datatypes.network.' + TOSCA = 'tosca' + + def derived_from(self, defs): + '''Return a type this type is derived from.''' + return self.entity_value(defs, 'derived_from') + + def is_derived_from(self, type_str): + '''Check if object inherits from the given type. + + Returns true if this object is derived from 'type_str'. + False otherwise. + ''' + if not self.type: + return False + elif self.type == type_str: + return True + elif self.parent_type: + return self.parent_type.is_derived_from(type_str) + else: + return False + + def entity_value(self, defs, key): + if key in defs: + return defs[key] + + def get_value(self, ndtype, defs=None, parent=None): + value = None + if defs is None: + if not hasattr(self, 'defs'): + return None + defs = self.defs + if ndtype in defs: + # copy the value to avoid that next operations add items in the + # item definitions + value = copy.copy(defs[ndtype]) + if parent: + p = self + if p: + while p: + if ndtype in p.defs: + # get the parent value + parent_value = p.defs[ndtype] + if value: + if isinstance(value, dict): + for k, v in parent_value.items(): + if k not in value.keys(): + value[k] = v + if isinstance(value, list): + for p_value in parent_value: + if p_value not in value: + value.append(p_value) + else: + value = copy.copy(parent_value) + p = p.parent_type + return value + + def get_definition(self, ndtype): + value = None + if not hasattr(self, 'defs'): + defs = None + ExceptionCollector.appendException( + ValidationError(message="defs is " + str(defs))) + else: + defs = self.defs + if defs is not None and ndtype in defs: + value = defs[ndtype] + p = self.parent_type + if p: + inherited = p.get_definition(ndtype) + if inherited: + inherited = dict(inherited) + if not value: + value = inherited + else: + inherited.update(value) + value.update(inherited) + return value + + +def update_definitions(version): + exttools = ExtTools() + extension_defs_file = exttools.get_defs_file(version) + + loader = toscaparser.utils.yamlparser.load_yaml + + EntityType.TOSCA_DEF.update(loader(extension_defs_file)) diff --git a/tosca2heat/tosca-parser/toscaparser/elements/grouptype.py b/tosca2heat/tosca-parser/toscaparser/elements/grouptype.py new file mode 100644 index 0000000..ec5571c --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/elements/grouptype.py @@ -0,0 +1,86 @@ +# Licensed 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 toscaparser.common.exception import ExceptionCollector +from toscaparser.common.exception import InvalidTypeError +from toscaparser.common.exception import UnknownFieldError +from toscaparser.elements.statefulentitytype import StatefulEntityType + + +class GroupType(StatefulEntityType): + '''TOSCA built-in group type.''' + + SECTIONS = (DERIVED_FROM, VERSION, METADATA, DESCRIPTION, PROPERTIES, + MEMBERS, INTERFACES) = \ + ("derived_from", "version", "metadata", "description", + "properties", "members", "interfaces") + + def __init__(self, grouptype, custom_def=None): + super(GroupType, self).__init__(grouptype, self.GROUP_PREFIX, + custom_def) + self.custom_def = custom_def + self.grouptype = grouptype + self._validate_fields() + self.group_description = None + if self.DESCRIPTION in self.defs: + self.group_description = self.defs[self.DESCRIPTION] + + self.group_version = None + if self.VERSION in self.defs: + self.group_version = self.defs[self.VERSION] + + self.group_properties = None + if self.PROPERTIES in self.defs: + self.group_properties = self.defs[self.PROPERTIES] + + self.group_members = None + if self.MEMBERS in self.defs: + self.group_members = self.defs[self.MEMBERS] + + if self.METADATA in self.defs: + self.meta_data = self.defs[self.METADATA] + self._validate_metadata(self.meta_data) + + @property + def description(self): + return self.group_description + + @property + def version(self): + return self.group_version + + @property + def interfaces(self): + return self.get_value(self.INTERFACES) + + def _validate_fields(self): + if self.defs: + for name in self.defs.keys(): + if name not in self.SECTIONS: + ExceptionCollector.appendException( + UnknownFieldError(what='Group Type %s' + % self.grouptype, field=name)) + + def _validate_metadata(self, meta_data): + if not meta_data.get('type') in ['map', 'tosca:map']: + ExceptionCollector.appendException( + InvalidTypeError(what='"%s" defined in group for ' + 'metadata' % (meta_data.get('type')))) + for entry_schema, entry_schema_type in meta_data.items(): + if isinstance(entry_schema_type, dict) and not \ + entry_schema_type.get('type') == 'string': + ExceptionCollector.appendException( + InvalidTypeError(what='"%s" defined in group for ' + 'metadata "%s"' + % (entry_schema_type.get('type'), + entry_schema))) diff --git a/tosca2heat/tosca-parser/toscaparser/elements/interfaces.py b/tosca2heat/tosca-parser/toscaparser/elements/interfaces.py new file mode 100644 index 0000000..88fb8ab --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/elements/interfaces.py @@ -0,0 +1,76 @@ +# Licensed 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 toscaparser.common.exception import ExceptionCollector +from toscaparser.common.exception import UnknownFieldError +from toscaparser.elements.statefulentitytype import StatefulEntityType + +SECTIONS = (LIFECYCLE, CONFIGURE, LIFECYCLE_SHORTNAME, + CONFIGURE_SHORTNAME) = \ + ('tosca.interfaces.node.lifecycle.Standard', + 'tosca.interfaces.relationship.Configure', + 'Standard', 'Configure') + +INTERFACEVALUE = (IMPLEMENTATION, INPUTS) = ('implementation', 'inputs') + + +class InterfacesDef(StatefulEntityType): + '''TOSCA built-in interfaces type.''' + + def __init__(self, node_type, interfacetype, + node_template=None, name=None, value=None): + self.ntype = node_type + self.node_template = node_template + self.type = interfacetype + self.name = name + self.value = value + self.implementation = None + self.inputs = None + self.defs = {} + if interfacetype == LIFECYCLE_SHORTNAME: + interfacetype = LIFECYCLE + if interfacetype == CONFIGURE_SHORTNAME: + interfacetype = CONFIGURE + if node_type: + self.defs = self.TOSCA_DEF[interfacetype] + if value: + if isinstance(self.value, dict): + for i, j in self.value.items(): + if i == IMPLEMENTATION: + self.implementation = j + elif i == INPUTS: + self.inputs = j + else: + what = ('"interfaces" of template "%s"' % + self.node_template.name) + ExceptionCollector.appendException( + UnknownFieldError(what=what, field=i)) + else: + self.implementation = value + + @property + def lifecycle_ops(self): + if self.defs: + if self.type == LIFECYCLE: + return self._ops() + + @property + def configure_ops(self): + if self.defs: + if self.type == CONFIGURE: + return self._ops() + + def _ops(self): + ops = [] + for name in list(self.defs.keys()): + ops.append(name) + return ops diff --git a/tosca2heat/tosca-parser/toscaparser/elements/nodetype.py b/tosca2heat/tosca-parser/toscaparser/elements/nodetype.py new file mode 100644 index 0000000..8176def --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/elements/nodetype.py @@ -0,0 +1,226 @@ +# Licensed 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 toscaparser.common.exception import ExceptionCollector +from toscaparser.common.exception import UnknownFieldError +from toscaparser.common.exception import ValidationError +from toscaparser.elements.capabilitytype import CapabilityTypeDef +import toscaparser.elements.interfaces as ifaces +from toscaparser.elements.interfaces import InterfacesDef +from toscaparser.elements.relationshiptype import RelationshipType +from toscaparser.elements.statefulentitytype import StatefulEntityType + + +class NodeType(StatefulEntityType): + '''TOSCA built-in node type.''' + SECTIONS = (DERIVED_FROM, METADATA, PROPERTIES, VERSION, DESCRIPTION, ATTRIBUTES, REQUIREMENTS, CAPABILITIES, INTERFACES, ARTIFACTS) = \ + ('derived_from', 'metadata', 'properties', 'version', + 'description', 'attributes', 'requirements', 'capabilities', + 'interfaces', 'artifacts') + + def __init__(self, ntype, custom_def=None): + super(NodeType, self).__init__(ntype, self.NODE_PREFIX, custom_def) + self.ntype = ntype + self.custom_def = custom_def + self._validate_keys() + + @property + def parent_type(self): + '''Return a node this node is derived from.''' + if not hasattr(self, 'defs'): + return None + pnode = self.derived_from(self.defs) + if pnode: + return NodeType(pnode, self.custom_def) + + @property + def relationship(self): + '''Return a dictionary of relationships to other node types. + + This method returns a dictionary of named relationships that nodes + of the current node type (self) can have to other nodes (of specific + types) in a TOSCA template. + + ''' + relationship = {} + requires = self.get_all_requirements() + if requires: + # NOTE(sdmonov): Check if requires is a dict. + # If it is a dict convert it to a list of dicts. + # This is needed because currently the code below supports only + # lists as requirements definition. The following check will + # make sure if a map (dict) was provided it will be converted to + # a list before proceeding to the parsing. + if isinstance(requires, dict): + requires = [{key: value} for key, value in requires.items()] + + keyword = None + node_type = None + for require in requires: + for key, req in require.items(): + if 'relationship' in req: + relation = req.get('relationship') + if 'type' in relation: + relation = relation.get('type') + node_type = req.get('node') + value = req + if node_type: + keyword = 'node' + else: + # If value is a dict and has a type key + # we need to lookup the node type using + # the capability type + value = req + if isinstance(value, dict): + captype = value['capability'] + value = (self. + _get_node_type_by_cap(key, captype)) + relation = self._get_relation(key, value) + keyword = key + node_type = value + rtype = RelationshipType(relation, keyword, self.custom_def) + relatednode = NodeType(node_type, self.custom_def) + relationship[rtype] = relatednode + return relationship + + def _get_node_type_by_cap(self, key, cap): + '''Find the node type that has the provided capability + + This method will lookup all node types if they have the + provided capability. + ''' + + # Filter the node types + node_types = [node_type for node_type in self.TOSCA_DEF.keys() + if node_type.startswith(self.NODE_PREFIX) and + node_type != 'tosca.nodes.Root'] + + for node_type in node_types: + node_def = self.TOSCA_DEF[node_type] + if isinstance(node_def, dict) and 'capabilities' in node_def: + node_caps = node_def['capabilities'] + for value in node_caps.values(): + if isinstance(value, dict) and \ + 'type' in value and value['type'] == cap: + return node_type + + def _get_relation(self, key, ndtype): + relation = None + ntype = NodeType(ndtype) + caps = ntype.get_capabilities() + if caps and key in caps.keys(): + c = caps[key] + for r in self.RELATIONSHIP_TYPE: + rtypedef = ntype.TOSCA_DEF[r] + for properties in rtypedef.values(): + if c.type in properties: + relation = r + break + if relation: + break + else: + for properties in rtypedef.values(): + if c.parent_type in properties: + relation = r + break + return relation + + def get_capabilities_objects(self): + '''Return a list of capability objects.''' + typecapabilities = [] + caps = self.get_value(self.CAPABILITIES, None, True) + if caps is None: + caps = self.get_value(self.CAPABILITIES, None, True) + if caps: + # 'name' is symbolic name of the capability + # 'value' is a dict { 'type': <capability type name> } + for name, value in caps.items(): + ctype = value.get('type') + cap = CapabilityTypeDef(name, ctype, self.type, + self.custom_def) + typecapabilities.append(cap) + return typecapabilities + + def get_capabilities(self): + '''Return a dictionary of capability name-objects pairs.''' + return {cap.name: cap + for cap in self.get_capabilities_objects()} + + @property + def requirements(self): + return self.get_value(self.REQUIREMENTS, None, True) + + def get_all_requirements(self): + requires = self.requirements + parent_node = self.parent_type + if requires is None: + requires = self.get_value(self.REQUIREMENTS, None, True) + if parent_node is None: + ExceptionCollector.appendException( + ValidationError(message="parent_node is " + + str(parent_node))) + else: + parent_node = parent_node.parent_type + if parent_node: + while parent_node.type != 'tosca.nodes.Root': + req = parent_node.get_value(self.REQUIREMENTS, None, True) + for r in req: + if r not in requires: + requires.append(r) + parent_node = parent_node.parent_type + return requires + + @property + def interfaces(self): + return self.get_value(self.INTERFACES) + + @property + def lifecycle_inputs(self): + '''Return inputs to life cycle operations if found.''' + inputs = [] + interfaces = self.interfaces + if interfaces: + for name, value in interfaces.items(): + if name == ifaces.LIFECYCLE: + for x, y in value.items(): + if x == 'inputs': + for i in y.iterkeys(): + inputs.append(i) + return inputs + + @property + def lifecycle_operations(self): + '''Return available life cycle operations if found.''' + ops = None + interfaces = self.interfaces + if interfaces: + i = InterfacesDef(self.type, ifaces.LIFECYCLE) + ops = i.lifecycle_ops + return ops + + def get_capability(self, name): + caps = self.get_capabilities() + if caps and name in caps.keys(): + return caps[name].value + + def get_capability_type(self, name): + captype = self.get_capability(name) + if captype and name in captype.keys(): + return captype[name].value + + def _validate_keys(self): + if self.defs: + for key in self.defs.keys(): + if key not in self.SECTIONS: + ExceptionCollector.appendException( + UnknownFieldError(what='Nodetype"%s"' % self.ntype, + field=key)) diff --git a/tosca2heat/tosca-parser/toscaparser/elements/policytype.py b/tosca2heat/tosca-parser/toscaparser/elements/policytype.py new file mode 100644 index 0000000..04cbab5 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/elements/policytype.py @@ -0,0 +1,115 @@ +# Licensed 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 toscaparser.common.exception import ExceptionCollector +from toscaparser.common.exception import InvalidTypeError +from toscaparser.common.exception import UnknownFieldError +from toscaparser.elements.statefulentitytype import StatefulEntityType +from toscaparser.utils.validateutils import TOSCAVersionProperty + + +class PolicyType(StatefulEntityType): + + '''TOSCA built-in policies type.''' + SECTIONS = (DERIVED_FROM, METADATA, PROPERTIES, VERSION, DESCRIPTION, TARGETS) = \ + ('derived_from', 'metadata', 'properties', 'version', + 'description', 'targets') + + def __init__(self, ptype, custom_def=None): + super(PolicyType, self).__init__(ptype, self.POLICY_PREFIX, + custom_def) + self.type = ptype + self._validate_keys() + + self.meta_data = None + if self.METADATA in self.defs: + self.meta_data = self.defs[self.METADATA] + self._validate_metadata(self.meta_data) + + self.properties = None + if self.PROPERTIES in self.defs: + self.properties = self.defs[self.PROPERTIES] + self.parent_policies = self._get_parent_policies() + + self.policy_version = None + if self.VERSION in self.defs: + self.policy_version = TOSCAVersionProperty( + self.defs[self.VERSION]).get_version() + + self.policy_description = self.defs[self.DESCRIPTION] \ + if self.DESCRIPTION in self.defs else None + + self.targets_list = None + if self.TARGETS in self.defs: + self.targets_list = self.defs[self.TARGETS] + self._validate_targets(self.targets_list, custom_def) + + def _get_parent_policies(self): + policies = {} + parent_policy = self.parent_type + if parent_policy: + while parent_policy != 'tosca.policies.Root': + policies[parent_policy] = self.TOSCA_DEF[parent_policy] + parent_policy = policies[parent_policy]['derived_from'] + return policies + + @property + def parent_type(self): + '''Return a policy this policy is derived from.''' + return self.derived_from(self.defs) + + def get_policy(self, name): + '''Return the definition of a policy field by name.''' + if name in self.defs: + return self.defs[name] + + @property + def targets(self): + '''Return targets.''' + return self.targets_list + + @property + def description(self): + return self.policy_description + + @property + def version(self): + return self.policy_version + + def _validate_keys(self): + for key in self.defs.keys(): + if key not in self.SECTIONS: + ExceptionCollector.appendException( + UnknownFieldError(what='Policy "%s"' % self.type, + field=key)) + + def _validate_targets(self, targets_list, custom_def): + for nodetype in targets_list: + if nodetype not in custom_def: + ExceptionCollector.appendException( + InvalidTypeError(what='"%s" defined in targets for ' + 'policy "%s"' % (nodetype, self.type))) + + def _validate_metadata(self, meta_data): + if not meta_data.get('type') in ['map', 'tosca:map']: + ExceptionCollector.appendException( + InvalidTypeError(what='"%s" defined in policy for ' + 'metadata' % (meta_data.get('type')))) + + for entry_schema, entry_schema_type in meta_data.items(): + if isinstance(entry_schema_type, dict) and not \ + entry_schema_type.get('type') == 'string': + ExceptionCollector.appendException( + InvalidTypeError(what='"%s" defined in policy for ' + 'metadata "%s"' + % (entry_schema_type.get('type'), + entry_schema))) diff --git a/tosca2heat/tosca-parser/toscaparser/elements/property_definition.py b/tosca2heat/tosca-parser/toscaparser/elements/property_definition.py new file mode 100644 index 0000000..a242ddf --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/elements/property_definition.py @@ -0,0 +1,100 @@ +# Licensed 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 toscaparser.common.exception import ExceptionCollector +from toscaparser.common.exception import InvalidSchemaError +from toscaparser.common.exception import TOSCAException +from toscaparser.utils.gettextutils import _ + + +class PropertyDef(object): + '''TOSCA built-in Property type.''' + + VALID_PROPERTY_KEYNAMES = (PROPERTY_KEYNAME_DEFAULT, + PROPERTY_KEYNAME_REQUIRED, + PROPERTY_KEYNAME_STATUS) = \ + ('default', 'required', 'status') + + PROPERTY_REQUIRED_DEFAULT = True + + VALID_REQUIRED_VALUES = ['true', 'false'] + VALID_STATUS_VALUES = (PROPERTY_STATUS_SUPPORTED, + PROPERTY_STATUS_EXPERIMENTAL) = \ + ('supported', 'experimental') + + PROPERTY_STATUS_DEFAULT = PROPERTY_STATUS_SUPPORTED + + def __init__(self, name, value=None, schema=None): + self.name = name + self.value = value + self.schema = schema + self._status = self.PROPERTY_STATUS_DEFAULT + self._required = self.PROPERTY_REQUIRED_DEFAULT + + # Validate required 'type' property exists + try: + self.schema['type'] + except KeyError: + msg = (_('Schema definition of "%(pname)s" must have a "type" ' + 'attribute.') % dict(pname=self.name)) + ExceptionCollector.appendException( + InvalidSchemaError(message=msg)) + + if self.schema: + self._load_required_attr_from_schema() + self._load_status_attr_from_schema() + + @property + def default(self): + if self.schema: + for prop_key, prop_value in self.schema.items(): + if prop_key == self.PROPERTY_KEYNAME_DEFAULT: + return prop_value + return None + + @property + def required(self): + return self._required + + def _load_required_attr_from_schema(self): + # IF 'required' keyname exists verify it's a boolean, + # if so override default + if self.PROPERTY_KEYNAME_REQUIRED in self.schema: + value = self.schema[self.PROPERTY_KEYNAME_REQUIRED] + if isinstance(value, bool): + self._required = value + else: + valid_values = ', '.join(self.VALID_REQUIRED_VALUES) + attr = self.PROPERTY_KEYNAME_REQUIRED + TOSCAException.generate_inv_schema_property_error(self, + attr, + value, + valid_values) + + @property + def status(self): + return self._status + + def _load_status_attr_from_schema(self): + # IF 'status' keyname exists verify it's a valid value, + # if so override default + if self.PROPERTY_KEYNAME_STATUS in self.schema: + value = self.schema[self.PROPERTY_KEYNAME_STATUS] + if value in self.VALID_STATUS_VALUES: + self._status = value + else: + valid_values = ', '.join(self.VALID_STATUS_VALUES) + attr = self.PROPERTY_KEYNAME_STATUS + TOSCAException.generate_inv_schema_property_error(self, + attr, + value, + valid_values) diff --git a/tosca2heat/tosca-parser/toscaparser/elements/relationshiptype.py b/tosca2heat/tosca-parser/toscaparser/elements/relationshiptype.py new file mode 100644 index 0000000..9462d38 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/elements/relationshiptype.py @@ -0,0 +1,33 @@ +# Licensed 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 toscaparser.elements.statefulentitytype import StatefulEntityType + + +class RelationshipType(StatefulEntityType): + '''TOSCA built-in relationship type.''' + def __init__(self, type, capability_name=None, custom_def=None): + super(RelationshipType, self).__init__(type, self.RELATIONSHIP_PREFIX, + custom_def) + self.capability_name = capability_name + self.custom_def = custom_def + + @property + def parent_type(self): + '''Return a relationship this reletionship is derived from.''' + prel = self.derived_from(self.defs) + if prel: + return RelationshipType(prel) + + @property + def valid_target_types(self): + return self.entity_value(self.defs, 'valid_target_types') diff --git a/tosca2heat/tosca-parser/toscaparser/elements/scalarunit.py b/tosca2heat/tosca-parser/toscaparser/elements/scalarunit.py new file mode 100644 index 0000000..d7f72e6 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/elements/scalarunit.py @@ -0,0 +1,129 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import logging +import re + +from toscaparser.common.exception import ExceptionCollector +from toscaparser.utils.gettextutils import _ +from toscaparser.utils import validateutils + +log = logging.getLogger('tosca') + + +class ScalarUnit(object): + '''Parent class for scalar-unit type.''' + + SCALAR_UNIT_TYPES = ( + SCALAR_UNIT_SIZE, SCALAR_UNIT_FREQUENCY, SCALAR_UNIT_TIME + ) = ( + 'scalar-unit.size', 'scalar-unit.frequency', 'scalar-unit.time' + ) + + def __init__(self, value): + self.value = value + + def _check_unit_in_scalar_standard_units(self, input_unit): + """Check whether the input unit is following specified standard + + If unit is not following specified standard, convert it to standard + unit after displaying a warning message. + """ + if input_unit in self.SCALAR_UNIT_DICT.keys(): + return input_unit + else: + for key in self.SCALAR_UNIT_DICT.keys(): + if key.upper() == input_unit.upper(): + log.warning(_('The unit "%(unit)s" does not follow ' + 'scalar unit standards; using "%(key)s" ' + 'instead.') % {'unit': input_unit, + 'key': key}) + return key + msg = (_('The unit "%(unit)s" is not valid. Valid units are ' + '"%(valid_units)s".') % + {'unit': input_unit, + 'valid_units': sorted(self.SCALAR_UNIT_DICT.keys())}) + ExceptionCollector.appendException(ValueError(msg)) + + def validate_scalar_unit(self): + regex = re.compile('([0-9.]+)\s*(\w+)') + try: + result = regex.match(str(self.value)).groups() + validateutils.str_to_num(result[0]) + scalar_unit = self._check_unit_in_scalar_standard_units(result[1]) + self.value = ' '.join([result[0], scalar_unit]) + return self.value + + except Exception: + ExceptionCollector.appendException( + ValueError(_('"%s" is not a valid scalar-unit.') + % self.value)) + + def get_num_from_scalar_unit(self, unit=None): + if unit: + unit = self._check_unit_in_scalar_standard_units(unit) + else: + unit = self.SCALAR_UNIT_DEFAULT + self.validate_scalar_unit() + + regex = re.compile('([0-9.]+)\s*(\w+)') + result = regex.match(str(self.value)).groups() + converted = (float(validateutils.str_to_num(result[0])) + * self.SCALAR_UNIT_DICT[result[1]] + / self.SCALAR_UNIT_DICT[unit]) + if converted - int(converted) < 0.0000000000001: + converted = int(converted) + return converted + + +class ScalarUnit_Size(ScalarUnit): + + SCALAR_UNIT_DEFAULT = 'B' + SCALAR_UNIT_DICT = {'B': 1, 'kB': 1000, 'KiB': 1024, 'MB': 1000000, + 'MiB': 1048576, 'GB': 1000000000, + 'GiB': 1073741824, 'TB': 1000000000000, + 'TiB': 1099511627776} + + +class ScalarUnit_Time(ScalarUnit): + + SCALAR_UNIT_DEFAULT = 'ms' + SCALAR_UNIT_DICT = {'d': 86400, 'h': 3600, 'm': 60, 's': 1, + 'ms': 0.001, 'us': 0.000001, 'ns': 0.000000001} + + +class ScalarUnit_Frequency(ScalarUnit): + + SCALAR_UNIT_DEFAULT = 'GHz' + SCALAR_UNIT_DICT = {'Hz': 1, 'kHz': 1000, + 'MHz': 1000000, 'GHz': 1000000000} + + +scalarunit_mapping = { + ScalarUnit.SCALAR_UNIT_FREQUENCY: ScalarUnit_Frequency, + ScalarUnit.SCALAR_UNIT_SIZE: ScalarUnit_Size, + ScalarUnit.SCALAR_UNIT_TIME: ScalarUnit_Time, + } + + +def get_scalarunit_class(type): + return scalarunit_mapping.get(type) + + +def get_scalarunit_value(type, value, unit=None): + if type in ScalarUnit.SCALAR_UNIT_TYPES: + ScalarUnit_Class = get_scalarunit_class(type) + return (ScalarUnit_Class(value). + get_num_from_scalar_unit(unit)) + else: + ExceptionCollector.appendException( + TypeError(_('"%s" is not a valid scalar-unit type.') % type)) diff --git a/tosca2heat/tosca-parser/toscaparser/elements/statefulentitytype.py b/tosca2heat/tosca-parser/toscaparser/elements/statefulentitytype.py new file mode 100644 index 0000000..47496f7 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/elements/statefulentitytype.py @@ -0,0 +1,84 @@ +# Licensed 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 toscaparser.common.exception import ExceptionCollector +from toscaparser.common.exception import InvalidTypeError +from toscaparser.elements.attribute_definition import AttributeDef +from toscaparser.elements.entity_type import EntityType +from toscaparser.elements.property_definition import PropertyDef + + +class StatefulEntityType(EntityType): + '''Class representing TOSCA states.''' + + interfaces_node_lifecycle_operations = ['create', + 'configure', 'start', + 'stop', 'delete'] + + interfaces_relationship_configure_operations = ['post_configure_source', + 'post_configure_target', + 'add_target', + 'remove_target'] + + def __init__(self, entitytype, prefix, custom_def=None): + entire_entitytype = entitytype + if not entitytype.startswith(self.TOSCA): + entire_entitytype = prefix + entitytype + if entire_entitytype in list(self.TOSCA_DEF.keys()): + self.defs = self.TOSCA_DEF[entire_entitytype] + entitytype = entire_entitytype + elif custom_def and entitytype in list(custom_def.keys()): + self.defs = custom_def[entitytype] + else: + self.defs = None + ExceptionCollector.appendException( + InvalidTypeError(what=entitytype)) + self.type = entitytype + + def get_properties_def_objects(self): + '''Return a list of property definition objects.''' + properties = [] + props = self.get_definition(self.PROPERTIES) + if props: + for prop, schema in props.items(): + properties.append(PropertyDef(prop, None, schema)) + return properties + + def get_properties_def(self): + '''Return a dictionary of property definition name-object pairs.''' + return {prop.name: prop + for prop in self.get_properties_def_objects()} + + def get_property_def_value(self, name): + '''Return the property definition associated with a given name.''' + props_def = self.get_properties_def() + if props_def and name in props_def.keys(): + return props_def[name].value + + def get_attributes_def_objects(self): + '''Return a list of attribute definition objects.''' + attrs = self.get_value(self.ATTRIBUTES, parent=True) + if attrs: + return [AttributeDef(attr, None, schema) + for attr, schema in attrs.items()] + return [] + + def get_attributes_def(self): + '''Return a dictionary of attribute definition name-object pairs.''' + return {attr.name: attr + for attr in self.get_attributes_def_objects()} + + def get_attribute_def_value(self, name): + '''Return the attribute definition associated with a given name.''' + attrs_def = self.get_attributes_def() + if attrs_def and name in attrs_def.keys(): + return attrs_def[name].value diff --git a/tosca2heat/tosca-parser/toscaparser/elements/tosca_type_validation.py b/tosca2heat/tosca-parser/toscaparser/elements/tosca_type_validation.py new file mode 100644 index 0000000..16764bc --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/elements/tosca_type_validation.py @@ -0,0 +1,58 @@ +# Licensed 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 toscaparser.common.exception import ExceptionCollector +from toscaparser.common.exception import InvalidTemplateVersion +from toscaparser.common.exception import UnknownFieldError +from toscaparser.extensions.exttools import ExtTools + + +class TypeValidation(object): + + ALLOWED_TYPE_SECTIONS = (DEFINITION_VERSION, DESCRIPTION, IMPORTS, + DSL_DEFINITIONS, NODE_TYPES, REPOSITORIES, + DATA_TYPES, ARTIFACT_TYPES, GROUP_TYPES, + RELATIONSHIP_TYPES, CAPABILITY_TYPES, + INTERFACE_TYPES, POLICY_TYPES) = \ + ('tosca_definitions_version', 'description', 'imports', + 'dsl_definitions', 'node_types', 'repositories', + 'data_types', 'artifact_types', 'group_types', + 'relationship_types', 'capability_types', + 'interface_types', 'policy_types') + VALID_TEMPLATE_VERSIONS = ['tosca_simple_yaml_1_0'] + exttools = ExtTools() + VALID_TEMPLATE_VERSIONS.extend(exttools.get_versions()) + + def __init__(self, custom_types, import_def): + self.import_def = import_def + self._validate_type_keys(custom_types) + + def _validate_type_keys(self, custom_type): + version = custom_type[self.DEFINITION_VERSION] \ + if self.DEFINITION_VERSION in custom_type \ + else None + if version: + self._validate_type_version(version) + self.version = version + + for name in custom_type: + if name not in self.ALLOWED_TYPE_SECTIONS: + ExceptionCollector.appendException( + UnknownFieldError(what='Template ' + (self.import_def), + field=name)) + + def _validate_type_version(self, version): + if version not in self.VALID_TEMPLATE_VERSIONS: + ExceptionCollector.appendException( + InvalidTemplateVersion( + what=version + ' in ' + self.import_def, + valid_versions=', '. join(self.VALID_TEMPLATE_VERSIONS))) diff --git a/tosca2heat/tosca-parser/toscaparser/entity_template.py b/tosca2heat/tosca-parser/toscaparser/entity_template.py new file mode 100644 index 0000000..281012b --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/entity_template.py @@ -0,0 +1,321 @@ +# Licensed 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 toscaparser.capabilities import Capability +from toscaparser.common.exception import ExceptionCollector +from toscaparser.common.exception import MissingRequiredFieldError +from toscaparser.common.exception import UnknownFieldError +from toscaparser.common.exception import ValidationError +from toscaparser.elements.grouptype import GroupType +from toscaparser.elements.interfaces import InterfacesDef +from toscaparser.elements.nodetype import NodeType +from toscaparser.elements.policytype import PolicyType +from toscaparser.elements.relationshiptype import RelationshipType +from toscaparser.properties import Property +from toscaparser.utils.gettextutils import _ + + +class EntityTemplate(object): + '''Base class for TOSCA templates.''' + + SECTIONS = (DERIVED_FROM, PROPERTIES, REQUIREMENTS, + INTERFACES, CAPABILITIES, TYPE, DESCRIPTION, DIRECTIVES, + ATTRIBUTES, ARTIFACTS, NODE_FILTER, COPY) = \ + ('derived_from', 'properties', 'requirements', 'interfaces', + 'capabilities', 'type', 'description', 'directives', + 'attributes', 'artifacts', 'node_filter', 'copy') + REQUIREMENTS_SECTION = (NODE, CAPABILITY, RELATIONSHIP, OCCURRENCES, NODE_FILTER) = \ + ('node', 'capability', 'relationship', + 'occurrences', 'node_filter') + # Special key names + SPECIAL_SECTIONS = (METADATA) = ('metadata') + + def __init__(self, name, template, entity_name, custom_def=None): + self.name = name + self.entity_tpl = template + self.custom_def = custom_def + self._validate_field(self.entity_tpl) + if entity_name == 'node_type': + type = self.entity_tpl.get('type') + self.type_definition = NodeType(type, custom_def) \ + if type is not None else None + if entity_name == 'relationship_type': + relationship = template.get('relationship') + type = None + if relationship and isinstance(relationship, dict): + type = relationship.get('type') + elif isinstance(relationship, str): + type = self.entity_tpl['relationship'] + else: + type = self.entity_tpl['type'] + self.type_definition = RelationshipType(type, + None, custom_def) + if entity_name == 'policy_type': + type = self.entity_tpl.get('type') + if not type: + msg = (_('Policy definition of "%(pname)s" must have' + ' a "type" ''attribute.') % dict(pname=name)) + ExceptionCollector.appendException( + ValidationError(msg)) + + self.type_definition = PolicyType(type, custom_def) + if entity_name == 'group_type': + type = self.entity_tpl.get('type') + self.type_definition = GroupType(type, custom_def) \ + if type is not None else None + self._properties = None + self._interfaces = None + self._requirements = None + self._capabilities = None + + @property + def type(self): + if self.type_definition: + return self.type_definition.type + + @property + def requirements(self): + if self._requirements is None: + self._requirements = self.type_definition.get_value( + self.REQUIREMENTS, + self.entity_tpl) or [] + return self._requirements + + def get_properties_objects(self): + '''Return properties objects for this template.''' + if self._properties is None: + self._properties = self._create_properties() + return self._properties + + def get_properties(self): + '''Return a dictionary of property name-object pairs.''' + return {prop.name: prop + for prop in self.get_properties_objects()} + + def get_property_value(self, name): + '''Return the value of a given property name.''' + props = self.get_properties() + if props and name in props.keys(): + return props[name].value + + @property + def interfaces(self): + if self._interfaces is None: + self._interfaces = self._create_interfaces() + return self._interfaces + + def get_capabilities_objects(self): + '''Return capabilities objects for this template.''' + if not self._capabilities: + self._capabilities = self._create_capabilities() + return self._capabilities + + def get_capabilities(self): + '''Return a dictionary of capability name-object pairs.''' + return {cap.name: cap + for cap in self.get_capabilities_objects()} + + def is_derived_from(self, type_str): + '''Check if object inherits from the given type. + + Returns true if this object is derived from 'type_str'. + False otherwise. + ''' + if not self.type: + return False + elif self.type == type_str: + return True + elif self.parent_type: + return self.parent_type.is_derived_from(type_str) + else: + return False + + def _create_capabilities(self): + capability = [] + caps = self.type_definition.get_value(self.CAPABILITIES, + self.entity_tpl, True) + if caps: + for name, props in caps.items(): + capabilities = self.type_definition.get_capabilities() + if name in capabilities.keys(): + c = capabilities[name] + properties = {} + # first use the definition default value + if c.properties: + for property_name in c.properties.keys(): + prop_def = c.properties[property_name] + if 'default' in prop_def: + properties[property_name] = prop_def['default'] + # then update (if available) with the node properties + if 'properties' in props and props['properties']: + properties.update(props['properties']) + + cap = Capability(name, properties, c) + capability.append(cap) + return capability + + def _validate_properties(self, template, entitytype): + properties = entitytype.get_value(self.PROPERTIES, template) + self._common_validate_properties(entitytype, properties) + + def _validate_capabilities(self): + type_capabilities = self.type_definition.get_capabilities() + allowed_caps = \ + type_capabilities.keys() if type_capabilities else [] + capabilities = self.type_definition.get_value(self.CAPABILITIES, + self.entity_tpl) + if capabilities: + self._common_validate_field(capabilities, allowed_caps, + 'capabilities') + self._validate_capabilities_properties(capabilities) + + def _validate_capabilities_properties(self, capabilities): + for cap, props in capabilities.items(): + capabilitydef = self.get_capability(cap).definition + self._common_validate_properties(capabilitydef, + props[self.PROPERTIES]) + + # validating capability properties values + for prop in self.get_capability(cap).get_properties_objects(): + prop.validate() + + # TODO(srinivas_tadepalli): temporary work around to validate + # default_instances until standardized in specification + if cap == "scalable" and prop.name == "default_instances": + prop_dict = props[self.PROPERTIES] + min_instances = prop_dict.get("min_instances") + max_instances = prop_dict.get("max_instances") + default_instances = prop_dict.get("default_instances") + if not (min_instances <= default_instances + <= max_instances): + err_msg = ('"properties" of template "%s": ' + '"default_instances" value is not between ' + '"min_instances" and "max_instances".' % + self.name) + ExceptionCollector.appendException( + ValidationError(message=err_msg)) + + def _common_validate_properties(self, entitytype, properties): + allowed_props = [] + required_props = [] + for p in entitytype.get_properties_def_objects(): + allowed_props.append(p.name) + # If property is 'required' and has no 'default' value then record + if p.required and p.default is None: + required_props.append(p.name) + # validate all required properties have values + if properties: + req_props_no_value_or_default = [] + self._common_validate_field(properties, allowed_props, + 'properties') + # make sure it's not missing any property required by a tosca type + for r in required_props: + if r not in properties.keys(): + req_props_no_value_or_default.append(r) + # Required properties found without value or a default value + if req_props_no_value_or_default: + ExceptionCollector.appendException( + MissingRequiredFieldError( + what='"properties" of template "%s"' % self.name, + required=req_props_no_value_or_default)) + else: + # Required properties in schema, but not in template + if required_props: + ExceptionCollector.appendException( + MissingRequiredFieldError( + what='"properties" of template "%s"' % self.name, + required=required_props)) + + def _validate_field(self, template): + if not isinstance(template, dict): + ExceptionCollector.appendException( + MissingRequiredFieldError( + what='Template "%s"' % self.name, required=self.TYPE)) + try: + relationship = template.get('relationship') + if relationship and not isinstance(relationship, str): + relationship[self.TYPE] + elif isinstance(relationship, str): + template['relationship'] + else: + template[self.TYPE] + except KeyError: + ExceptionCollector.appendException( + MissingRequiredFieldError( + what='Template "%s"' % self.name, required=self.TYPE)) + + def _common_validate_field(self, schema, allowedlist, section): + for name in schema: + if name not in allowedlist: + ExceptionCollector.appendException( + UnknownFieldError( + what=('"%(section)s" of template "%(nodename)s"' + % {'section': section, 'nodename': self.name}), + field=name)) + + def _create_properties(self): + props = [] + properties = self.type_definition.get_value(self.PROPERTIES, + self.entity_tpl) or {} + for name, value in properties.items(): + props_def = self.type_definition.get_properties_def() + if props_def and name in props_def: + prop = Property(name, value, + props_def[name].schema, self.custom_def) + props.append(prop) + for p in self.type_definition.get_properties_def_objects(): + if p.default is not None and p.name not in properties.keys(): + prop = Property(p.name, p.default, p.schema, self.custom_def) + props.append(prop) + return props + + def _create_interfaces(self): + interfaces = [] + type_interfaces = None + if isinstance(self.type_definition, RelationshipType): + if isinstance(self.entity_tpl, dict): + if self.INTERFACES in self.entity_tpl: + type_interfaces = self.entity_tpl[self.INTERFACES] + else: + for rel_def, value in self.entity_tpl.items(): + if rel_def != 'type': + rel_def = self.entity_tpl.get(rel_def) + rel = None + if isinstance(rel_def, dict): + rel = rel_def.get('relationship') + if rel: + if self.INTERFACES in rel: + type_interfaces = rel[self.INTERFACES] + break + else: + type_interfaces = self.type_definition.get_value(self.INTERFACES, + self.entity_tpl) + if type_interfaces: + for interface_type, value in type_interfaces.items(): + for op, op_def in value.items(): + iface = InterfacesDef(self.type_definition, + interfacetype=interface_type, + node_template=self, + name=op, + value=op_def) + interfaces.append(iface) + return interfaces + + def get_capability(self, name): + """Provide named capability + + :param name: name of capability + :return: capability object if found, None otherwise + """ + caps = self.get_capabilities() + if caps and name in caps.keys(): + return caps[name] diff --git a/tosca2heat/tosca-parser/toscaparser/extensions/__init__.py b/tosca2heat/tosca-parser/toscaparser/extensions/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/extensions/__init__.py diff --git a/tosca2heat/tosca-parser/toscaparser/extensions/exttools.py b/tosca2heat/tosca-parser/toscaparser/extensions/exttools.py new file mode 100644 index 0000000..963b958 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/extensions/exttools.py @@ -0,0 +1,88 @@ +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import importlib +import logging +import os + +from toscaparser.common.exception import ToscaExtAttributeError +from toscaparser.common.exception import ToscaExtImportError + +log = logging.getLogger("tosca.model") + +REQUIRED_ATTRIBUTES = ['VERSION', 'DEFS_FILE'] + + +class ExtTools(object): + def __init__(self): + self.EXTENSION_INFO = self._load_extensions() + + def _load_extensions(self): + '''Dynamically load all the extensions .''' + extensions = {} + + # Use the absolute path of the class path + abs_path = os.path.dirname(os.path.abspath(__file__)) + + extdirs = [e for e in os.listdir(abs_path) if + not e.startswith('tests') and + not e.endswith('.pyc') and not e.endswith('.py')] + + for e in extdirs: + log.info(e) + extpath = abs_path + '/' + e + # Grab all the extension files in the given path + ext_files = [f for f in os.listdir(extpath) if f.endswith('.py') + and not f.startswith('__init__')] + + # For each module, pick out the target translation class + for f in ext_files: + log.info(f) + ext_name = 'toscaparser/extensions/' + e + '/' + f.strip('.py') + ext_name = ext_name.replace('/', '.') + try: + extinfo = importlib.import_module(ext_name) + version = getattr(extinfo, 'VERSION') + defs_file = extpath + '/' + getattr(extinfo, 'DEFS_FILE') + + # Sections is an optional attribute + sections = getattr(extinfo, 'SECTIONS', ()) + + extensions[version] = {'sections': sections, + 'defs_file': defs_file} + except ImportError: + raise ToscaExtImportError(ext_name=ext_name) + except AttributeError: + attrs = ', '.join(REQUIRED_ATTRIBUTES) + raise ToscaExtAttributeError(ext_name=ext_name, + attrs=attrs) + + return extensions + + def get_versions(self): + return self.EXTENSION_INFO.keys() + + def get_sections(self): + sections = {} + for version in self.EXTENSION_INFO.keys(): + sections[version] = self.EXTENSION_INFO[version]['sections'] + + return sections + + def get_defs_file(self, version): + versiondata = self.EXTENSION_INFO.get(version) + + if versiondata: + return versiondata.get('defs_file') + else: + return None diff --git a/tosca2heat/tosca-parser/toscaparser/extensions/nfv/TOSCA_nfv_definition_1_0.yaml b/tosca2heat/tosca-parser/toscaparser/extensions/nfv/TOSCA_nfv_definition_1_0.yaml new file mode 100644 index 0000000..b82250e --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/extensions/nfv/TOSCA_nfv_definition_1_0.yaml @@ -0,0 +1,251 @@ +# Licensed 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. + +########################################################################## +# The content of this file reflects TOSCA NFV Profile in YAML version +# 1.0.0. It describes the definition for TOSCA NFV types including Node Type, +# Relationship Type, Capability Type and Interfaces. +########################################################################## +tosca_definitions_version: tosca_simple_profile_for_nfv_1_0_0 + +########################################################################## +# Node Type. +# A Node Type is a reusable entity that defines the type of one or more +# Node Templates. +########################################################################## + +tosca.nodes.nfv.VNF: + derived_from: tosca.nodes.Root # Or should this be its own top - level type? + properties: + id: + type: string + description: ID of this VNF + vendor: + type: string + description: name of the vendor who generate this VNF + version: + type: version + description: version of the software for this VNF + requirements: + - virtualLink: + capability: tosca.capabilities.nfv.VirtualLinkable + relationship: tosca.relationships.nfv.VirtualLinksTo + node: tosca.nodes.nfv.VL + +tosca.nodes.nfv.VDU: + derived_from: tosca.nodes.Compute + capabilities: + high_availability: + type: tosca.capabilities.nfv.HA + virtualbinding: + type: tosca.capabilities.nfv.VirtualBindable + monitoring_parameter: + type: tosca.capabilities.nfv.Metric + requirements: + - high_availability: + capability: tosca.capabilities.nfv.HA + relationship: tosca.relationships.nfv.HA + node: tosca.nodes.nfv.VDU + occurrences: [ 0, 1 ] + +tosca.nodes.nfv.CP: + derived_from: tosca.nodes.network.Port + properties: + type: + type: string + required: false + requirements: + - virtualLink: + capability: tosca.capabilities.VirtualLinkable + relationship: tosca.relationships.nfv.VirtualLinksTo + node: tosca.nodes.nfv.VL + - virtualBinding: + capability: tosca.capabilities.nfv.VirtualBindable + relationship: tosca.relationships.nfv.VirtualBindsTo + node: tosca.nodes.nfv.VDU + attributes: + IP_address: + type: string + required: false + +tosca.nodes.nfv.VL: + derived_from: tosca.nodes.network.Network + properties: + vendor: + type: string + required: true + description: name of the vendor who generate this VL + capabilities: + virtual_linkable: + type: tosca.capabilities.nfv.VirtualLinkable + +tosca.nodes.nfv.VL.ELine: + derived_from: tosca.nodes.nfv.VL + capabilities: + virtual_linkable: + occurrences: 2 + +tosca.nodes.nfv.VL.ELAN: + derived_from: tosca.nodes.nfv.VL + +tosca.nodes.nfv.VL.ETree: + derived_from: tosca.nodes.nfv.VL + +tosca.nodes.nfv.FP: + derived_from: tosca.nodes.Root + properties: + policy: + type: string + required: false + description: name of the vendor who generate this VL + requirements: + - forwarder: + capability: tosca.capabilities.nfv.Forwarder + relationship: tosca.relationships.nfv.ForwardsTo + +########################################################################## +# Relationship Type. +# A Relationship Type is a reusable entity that defines the type of one +# or more relationships between Node Types or Node Templates. +########################################################################## + +tosca.relationships.nfv.VirtualLinksTo: + derived_from: tosca.relationships.network.LinksTo + valid_target_types: [ tosca.capabilities.nfv.VirtualLinkable ] + +tosca.relationships.nfv.VirtualBindsTo: + derived_from: tosca.relationships.network.BindsTo + valid_target_types: [ tosca.capabilities.nfv.VirtualBindable ] + +tosca.relationships.nfv.HA: + derived_from: tosca.relationships.Root + valid_target_types: [ tosca.capabilities.nfv.HA ] + +tosca.relationships.nfv.Monitor: + derived_from: tosca.relationships.ConnectsTo + valid_target_types: [ tosca.capabilities.nfv.Metric ] + +tosca.relationships.nfv.ForwardsTo: + derived_from: tosca.relationships.root + valid_target_types: [ tosca.capabilities.nfv.Forwarder] + +########################################################################## +# Capability Type. +# A Capability Type is a reusable entity that describes a kind of +# capability that a Node Type can declare to expose. +########################################################################## + +tosca.capabilities.nfv.VirtualLinkable: + derived_from: tosca.capabilities.network.Linkable + +tosca.capabilities.nfv.VirtualBindable: + derived_from: tosca.capabilities.network.Bindable + +tosca.capabilities.nfv.HA: + derived_from: tosca.capabilities.Root + valid_source_types: [ tosca.nodes.nfv.VDU ] + +tosca.capabilities.nfv.HA.ActiveActive: + derived_from: tosca.capabilities.nfv.HA + +tosca.capabilities.nfv.HA.ActivePassive: + derived_from: tosca.capabilities.nfv.HA + +tosca.capabilities.nfv.Metric: + derived_from: tosca.capabilities.Root + +tosca.capabilities.nfv.Forwarder: + derived_from: tosca.capabilities.Root + +########################################################################## + # Interfaces Type. + # The Interfaces element describes a list of one or more interface + # definitions for a modelable entity (e.g., a Node or Relationship Type) + # as defined within the TOSCA Simple Profile specification. +########################################################################## + +########################################################################## + # Data Type. + # A Datatype is a complex data type declaration which contains other + # complex or simple data types. +########################################################################## + +########################################################################## + # Artifact Type. + # An Artifact Type is a reusable entity that defines the type of one or more + # files which Node Types or Node Templates can have dependent relationships + # and used during operations such as during installation or deployment. +########################################################################## + +########################################################################## + # Policy Type. + # TOSCA Policy Types represent logical grouping of TOSCA nodes that have + # an implied relationship and need to be orchestrated or managed together + # to achieve some result. +########################################################################## + +########################################################################## + # Group Type + # +########################################################################## +tosca.groups.nfv.VNFFG: + derived_from: tosca.groups.Root + + properties: + vendor: + type: string + required: true + description: name of the vendor who generate this VNFFG + + version: + type: string + required: true + description: version of this VNFFG + + number_of_endpoints: + type: integer + required: true + description: count of the external endpoints included in this VNFFG + + dependent_virtual_link: + type: list + entry_schema: + type: string + required: true + description: Reference to a VLD used in this Forwarding Graph + + connection_point: + type: list + entry_schema: + type: string + required: true + description: Reference to Connection Points forming the VNFFG + + constituent_vnfs: + type: list + entry_schema: + type: string + required: true + description: Reference to a list of VNFD used in this VNF Forwarding Graph + + targets: + type: list + entry_schema: + type: string + required: false + description: list of Network Forwarding Path within the VNFFG + + requirements: + - forwarder: + capability: tosca.capabilities.nfv.Forwarder + relationship: tosca.relationships.nfv.ForwardsTo + diff --git a/tosca2heat/tosca-parser/toscaparser/extensions/nfv/__init__.py b/tosca2heat/tosca-parser/toscaparser/extensions/nfv/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/extensions/nfv/__init__.py diff --git a/tosca2heat/tosca-parser/toscaparser/extensions/nfv/nfv.py b/tosca2heat/tosca-parser/toscaparser/extensions/nfv/nfv.py new file mode 100644 index 0000000..0c7c2b9 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/extensions/nfv/nfv.py @@ -0,0 +1,19 @@ +# Licensed 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. + +# VERSION and DEFS_FILE are required for all extensions + +VERSION = 'tosca_simple_profile_for_nfv_1_0_0' + +DEFS_FILE = "TOSCA_nfv_definition_1_0.yaml" + +SECTIONS = ('metadata') diff --git a/tosca2heat/tosca-parser/toscaparser/extensions/nfv/tests/__init__.py b/tosca2heat/tosca-parser/toscaparser/extensions/nfv/tests/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/extensions/nfv/tests/__init__.py diff --git a/tosca2heat/tosca-parser/toscaparser/extensions/nfv/tests/data/tosca_helloworld_nfv.yaml b/tosca2heat/tosca-parser/toscaparser/extensions/nfv/tests/data/tosca_helloworld_nfv.yaml new file mode 100644 index 0000000..6afa9f0 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/extensions/nfv/tests/data/tosca_helloworld_nfv.yaml @@ -0,0 +1,31 @@ +tosca_definitions_version: tosca_simple_profile_for_nfv_1_0_0 + +description: Template for deploying a single server with predefined properties. + +metadata: + template_name: TOSCA NFV Sample Template + +topology_template: + node_templates: + VNF1: + type: tosca.nodes.nfv.VNF + properties: + id: vnf1 + vendor: acmetelco + version: 1.0 + + VDU1: + type: tosca.nodes.nfv.VDU + + CP1: + type: tosca.nodes.nfv.CP + properties: + type: vPort + requirements: + - virtualLink: PrivateNetwork + - virtualBinding: VDU1 + + PrivateNetwork: + type: tosca.nodes.nfv.VL + properties: + vendor: ACME Networks diff --git a/tosca2heat/tosca-parser/toscaparser/extensions/nfv/tests/test_tosca_nfv_tpl.py b/tosca2heat/tosca-parser/toscaparser/extensions/nfv/tests/test_tosca_nfv_tpl.py new file mode 100644 index 0000000..b166d83 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/extensions/nfv/tests/test_tosca_nfv_tpl.py @@ -0,0 +1,29 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import os + +from toscaparser.tests.base import TestCase +from toscaparser.tosca_template import ToscaTemplate + + +class ToscaNFVTemplateTest(TestCase): + + '''TOSCA NFV template.''' + tosca_tpl = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "data/tosca_helloworld_nfv.yaml") + tosca = ToscaTemplate(tosca_tpl) + + def test_version(self): + self.assertEqual(self.tosca.version, + "tosca_simple_profile_for_nfv_1_0_0") diff --git a/tosca2heat/tosca-parser/toscaparser/functions.py b/tosca2heat/tosca-parser/toscaparser/functions.py new file mode 100644 index 0000000..011efde --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/functions.py @@ -0,0 +1,601 @@ +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +import abc +import six + +from toscaparser.common.exception import ExceptionCollector +from toscaparser.common.exception import UnknownInputError +from toscaparser.dataentity import DataEntity +from toscaparser.elements.entity_type import EntityType +from toscaparser.elements.relationshiptype import RelationshipType +from toscaparser.utils.gettextutils import _ + + +GET_PROPERTY = 'get_property' +GET_ATTRIBUTE = 'get_attribute' +GET_INPUT = 'get_input' +CONCAT = 'concat' + +SELF = 'SELF' +HOST = 'HOST' +TARGET = 'TARGET' +SOURCE = 'SOURCE' + +HOSTED_ON = 'tosca.relationships.HostedOn' + + +@six.add_metaclass(abc.ABCMeta) +class Function(object): + """An abstract type for representing a Tosca template function.""" + + def __init__(self, tosca_tpl, context, name, args): + self.tosca_tpl = tosca_tpl + self.context = context + self.name = name + self.args = args + self.validate() + + @abc.abstractmethod + def result(self): + """Invokes the function and returns its result + + Some methods invocation may only be relevant on runtime (for example, + getting runtime properties) and therefore its the responsibility of + the orchestrator/translator to take care of such functions invocation. + + :return: Function invocation result. + """ + return {self.name: self.args} + + @abc.abstractmethod + def validate(self): + """Validates function arguments.""" + pass + + +class GetInput(Function): + """Get a property value declared within the input of the service template. + + Arguments: + + * Input name. + + Example: + + * get_input: port + """ + + def validate(self): + if len(self.args) != 1: + ExceptionCollector.appendException( + ValueError(_( + 'Expected one argument for function "get_input" but ' + 'received "%s".') % self.args)) + inputs = [input.name for input in self.tosca_tpl.inputs] + if self.args[0] not in inputs: + ExceptionCollector.appendException( + UnknownInputError(input_name=self.args[0])) + + def result(self): + if self.tosca_tpl.parsed_params and \ + self.input_name in self.tosca_tpl.parsed_params: + return DataEntity.validate_datatype( + self.tosca_tpl.tpl['inputs'][self.input_name]['type'], + self.tosca_tpl.parsed_params[self.input_name]) + + input = [input_def for input_def in self.tosca_tpl.inputs + if self.input_name == input_def.name][0] + return input.default + + @property + def input_name(self): + return self.args[0] + + +class GetAttribute(Function): + """Get an attribute value of an entity defined in the service template + + Node template attributes values are set in runtime and therefore its the + responsibility of the Tosca engine to implement the evaluation of + get_attribute functions. + + Arguments: + + * Node template name | HOST. + * Attribute name. + + If the HOST keyword is passed as the node template name argument the + function will search each node template along the HostedOn relationship + chain until a node which contains the attribute is found. + + Examples: + + * { get_attribute: [ server, private_address ] } + * { get_attribute: [ HOST, private_address ] } + * { get_attribute: [ HOST, private_address, 0 ] } + """ + + def validate(self): + if len(self.args) != 2 and len(self.args) != 3: + ExceptionCollector.appendException( + ValueError(_('Illegal arguments for function "{0}". Expected ' + 'arguments: "node-template-name", ' + '"attribute-name"').format(GET_ATTRIBUTE))) + node_tpl = self._find_node_template_containing_attribute() + if len(self.args) > 2: + # Currently we only check the first level + attrs_def = node_tpl.type_definition.get_attributes_def() + attr_def = attrs_def[self.attribute_name] + if attr_def.schema['type'] == "list": + if not isinstance(self.args[2], int): + ExceptionCollector.appendException( + ValueError(_('Illegal arguments for function "{0}". ' + 'Third argument must be a positive' + ' integer') .format(GET_ATTRIBUTE))) + elif attr_def.schema['type'] != "map": + ExceptionCollector.appendException( + ValueError(_('Illegal arguments for function "{0}". ' + 'Expected arguments: "node-template-name", ' + '"attribute-name"').format(GET_ATTRIBUTE))) + + def result(self): + return self + + def get_referenced_node_template(self): + """Gets the NodeTemplate instance the get_attribute function refers to. + + If HOST keyword was used as the node template argument, the node + template which contains the attribute along the HostedOn relationship + chain will be returned. + """ + return self._find_node_template_containing_attribute() + + def _find_node_template_containing_attribute(self): + if self.node_template_name == HOST: + # Currently this is the only way to tell whether the function + # is used within the outputs section of the TOSCA template. + if isinstance(self.context, list): + ExceptionCollector.appendException( + ValueError(_( + '"get_attribute: [ HOST, ... ]" is not allowed in ' + '"outputs" section of the TOSCA template.'))) + return + node_tpl = self._find_host_containing_attribute() + if not node_tpl: + ExceptionCollector.appendException( + ValueError(_( + '"get_attribute: [ HOST, ... ]" was used in node ' + 'template "{0}" but "{1}" was not found in ' + 'the relationship chain.').format(self.context.name, + HOSTED_ON))) + else: + node_tpl = self._find_node_template(self.args[0]) + if node_tpl and \ + not self._attribute_exists_in_type(node_tpl.type_definition): + ExceptionCollector.appendException( + KeyError(_('Attribute "%(att)s" was not found in node ' + 'template "%(ntpl)s".') % + {'att': self.attribute_name, + 'ntpl': node_tpl.name})) + return node_tpl + + def _attribute_exists_in_type(self, type_definition): + attrs_def = type_definition.get_attributes_def() + found = [attrs_def[self.attribute_name]] \ + if self.attribute_name in attrs_def else [] + return len(found) == 1 + + def _find_host_containing_attribute(self, node_template_name=SELF): + node_template = self._find_node_template(node_template_name) + if node_template: + hosted_on_rel = EntityType.TOSCA_DEF[HOSTED_ON] + for r in node_template.requirements: + for requirement, target_name in r.items(): + target_node = self._find_node_template(target_name) + target_type = target_node.type_definition + for capability in target_type.get_capabilities_objects(): + if capability.type in \ + hosted_on_rel['valid_target_types']: + if self._attribute_exists_in_type(target_type): + return target_node + return self._find_host_containing_attribute( + target_name) + + def _find_node_template(self, node_template_name): + if node_template_name == TARGET: + if not isinstance(self.context.type_definition, RelationshipType): + ExceptionCollector.appendException( + KeyError(_('"TARGET" keyword can only be used in context' + ' to "Relationships" target node'))) + return + return self.context.target + if node_template_name == SOURCE: + if not isinstance(self.context.type_definition, RelationshipType): + ExceptionCollector.appendException( + KeyError(_('"SOURCE" keyword can only be used in context' + ' to "Relationships" source node'))) + return + return self.context.source + name = self.context.name \ + if node_template_name == SELF and \ + not isinstance(self.context, list) \ + else node_template_name + for node_template in self.tosca_tpl.nodetemplates: + if node_template.name == name: + return node_template + ExceptionCollector.appendException( + KeyError(_( + 'Node template "{0}" was not found.' + ).format(node_template_name))) + + @property + def node_template_name(self): + return self.args[0] + + @property + def attribute_name(self): + return self.args[1] + + +class GetProperty(Function): + """Get a property value of an entity defined in the same service template. + + Arguments: + + * Node template name | SELF | HOST | SOURCE | TARGET. + * Requirement or capability name (optional). + * Property name. + + If requirement or capability name is specified, the behavior is as follows: + The req or cap name is first looked up in the specified node template's + requirements. + If found, it would search for a matching capability + of an other node template and get its property as specified in function + arguments. + Otherwise, the req or cap name would be looked up in the specified + node template's capabilities and if found, it would return the property of + the capability as specified in function arguments. + + Examples: + + * { get_property: [ mysql_server, port ] } + * { get_property: [ SELF, db_port ] } + * { get_property: [ SELF, database_endpoint, port ] } + * { get_property: [ SELF, database_endpoint, port, 1 ] } + """ + + def validate(self): + if len(self.args) < 2: + ExceptionCollector.appendException( + ValueError(_( + 'Expected arguments: "node-template-name", "req-or-cap" ' + '(optional), "property name".'))) + return + if len(self.args) == 2: + found_prop = self._find_property(self.args[1]) + if not found_prop: + return + prop = found_prop.value + if not isinstance(prop, Function): + get_function(self.tosca_tpl, self.context, prop) + elif len(self.args) >= 3: + # do not use _find_property to avoid raise KeyError + # if the prop is not found + # First check if there is property with this name + node_tpl = self._find_node_template(self.args[0]) + props = node_tpl.get_properties() if node_tpl else [] + index = 2 + found = [props[self.args[1]]] if self.args[1] in props else [] + if found: + property_value = found[0].value + else: + index = 3 + # then check the req or caps + property_value = self._find_req_or_cap_property(self.args[1], + self.args[2]) + if len(self.args) > index: + for elem in self.args[index:]: + if isinstance(property_value, list): + int_elem = int(elem) + property_value = self._get_index_value(property_value, + int_elem) + else: + property_value = self._get_attribute_value( + property_value, + elem) + + def _find_req_or_cap_property(self, req_or_cap, property_name): + node_tpl = self._find_node_template(self.args[0]) + # Find property in node template's requirements + for r in node_tpl.requirements: + for req, node_name in r.items(): + if req == req_or_cap: + node_template = self._find_node_template(node_name) + return self._get_capability_property( + node_template, + req, + property_name) + # If requirement was not found, look in node template's capabilities + return self._get_capability_property(node_tpl, + req_or_cap, + property_name) + + def _get_capability_property(self, + node_template, + capability_name, + property_name): + """Gets a node template capability property.""" + caps = node_template.get_capabilities() + if caps and capability_name in caps.keys(): + cap = caps[capability_name] + property = None + props = cap.get_properties() + if props and property_name in props.keys(): + property = props[property_name].value + if not property: + ExceptionCollector.appendException( + KeyError(_('Property "%(prop)s" was not found in ' + 'capability "%(cap)s" of node template ' + '"%(ntpl1)s" referenced from node template ' + '"%(ntpl2)s".') % {'prop': property_name, + 'cap': capability_name, + 'ntpl1': node_template.name, + 'ntpl2': self.context.name})) + return property + msg = _('Requirement/Capability "{0}" referenced from node template ' + '"{1}" was not found in node template "{2}".').format( + capability_name, + self.context.name, + node_template.name) + ExceptionCollector.appendException(KeyError(msg)) + + def _find_property(self, property_name): + node_tpl = self._find_node_template(self.args[0]) + if not node_tpl: + return + props = node_tpl.get_properties() + found = [props[property_name]] if property_name in props else [] + if len(found) == 0: + ExceptionCollector.appendException( + KeyError(_('Property "%(prop)s" was not found in node ' + 'template "%(ntpl)s".') % + {'prop': property_name, + 'ntpl': node_tpl.name})) + return None + return found[0] + + def _find_node_template(self, node_template_name): + if node_template_name == SELF: + return self.context + # enable the HOST value in the function + if node_template_name == HOST: + return self._find_host_containing_property() + if node_template_name == TARGET: + if not isinstance(self.context.type_definition, RelationshipType): + ExceptionCollector.appendException( + KeyError(_('"TARGET" keyword can only be used in context' + ' to "Relationships" target node'))) + return + return self.context.target + if node_template_name == SOURCE: + if not isinstance(self.context.type_definition, RelationshipType): + ExceptionCollector.appendException( + KeyError(_('"SOURCE" keyword can only be used in context' + ' to "Relationships" source node'))) + return + return self.context.source + if not hasattr(self.tosca_tpl, 'nodetemplates'): + return + for node_template in self.tosca_tpl.nodetemplates: + if node_template.name == node_template_name: + return node_template + ExceptionCollector.appendException( + KeyError(_( + 'Node template "{0}" was not found.' + ).format(node_template_name))) + + def _get_index_value(self, value, index): + if isinstance(value, list): + if index < len(value): + return value[index] + else: + ExceptionCollector.appendException( + KeyError(_( + "Property '{0}' found in capability '{1}'" + " referenced from node template {2}" + " must have an element with index {3}."). + format(self.args[2], + self.args[1], + self.context.name, + index))) + else: + ExceptionCollector.appendException( + KeyError(_( + "Property '{0}' found in capability '{1}'" + " referenced from node template {2}" + " must be a list.").format(self.args[2], + self.args[1], + self.context.name))) + + def _get_attribute_value(self, value, attibute): + if isinstance(value, dict): + if attibute in value: + return value[attibute] + else: + ExceptionCollector.appendException( + KeyError(_( + "Property '{0}' found in capability '{1}'" + " referenced from node template {2}" + " must have an attribute named {3}."). + format(self.args[2], + self.args[1], + self.context.name, + attibute))) + else: + ExceptionCollector.appendException( + KeyError(_( + "Property '{0}' found in capability '{1}'" + " referenced from node template {2}" + " must be a dict.").format(self.args[2], + self.args[1], + self.context.name))) + + # Add this functions similar to get_attribute case + def _find_host_containing_property(self, node_template_name=SELF): + node_template = self._find_node_template(node_template_name) + hosted_on_rel = EntityType.TOSCA_DEF[HOSTED_ON] + for r in node_template.requirements: + for requirement, target_name in r.items(): + target_node = self._find_node_template(target_name) + target_type = target_node.type_definition + for capability in target_type.get_capabilities_objects(): + if capability.type in hosted_on_rel['valid_target_types']: + if self._property_exists_in_type(target_type): + return target_node + return self._find_host_containing_property( + target_name) + return None + + def _property_exists_in_type(self, type_definition): + props_def = type_definition.get_properties_def() + found = [props_def[self.args[1]]] \ + if self.args[1] in props_def else [] + return len(found) == 1 + + def result(self): + if len(self.args) >= 3: + # First check if there is property with this name + node_tpl = self._find_node_template(self.args[0]) + props = node_tpl.get_properties() if node_tpl else [] + index = 2 + found = [props[self.args[1]]] if self.args[1] in props else [] + if found: + property_value = found[0].value + else: + index = 3 + # then check the req or caps + property_value = self._find_req_or_cap_property(self.args[1], + self.args[2]) + if len(self.args) > index: + for elem in self.args[index:]: + if isinstance(property_value, list): + int_elem = int(elem) + property_value = self._get_index_value(property_value, + int_elem) + else: + property_value = self._get_attribute_value( + property_value, + elem) + else: + property_value = self._find_property(self.args[1]).value + if isinstance(property_value, Function): + return property_value.result() + return get_function(self.tosca_tpl, + self.context, + property_value) + + @property + def node_template_name(self): + return self.args[0] + + @property + def property_name(self): + if len(self.args) > 2: + return self.args[2] + return self.args[1] + + @property + def req_or_cap(self): + if len(self.args) > 2: + return self.args[1] + return None + + +class Concat(Function): + """Validate the function and provide an instance of the function + + Concatenation of values are supposed to be produced at runtime and + therefore its the responsibility of the TOSCA engine to implement the + evaluation of Concat functions. + + Arguments: + + * List of strings that needs to be concatenated + + Example: + + [ 'http://', + get_attribute: [ server, public_address ], + ':' , + get_attribute: [ server, port ] ] + """ + + def validate(self): + if len(self.args) < 1: + ExceptionCollector.appendException( + ValueError(_('Invalid arguments for function "{0}". Expected ' + 'at least one arguments.').format(CONCAT))) + + def result(self): + return self + +function_mappings = { + GET_PROPERTY: GetProperty, + GET_INPUT: GetInput, + GET_ATTRIBUTE: GetAttribute, + CONCAT: Concat +} + + +def is_function(function): + """Returns True if the provided function is a Tosca intrinsic function. + + Examples: + + * "{ get_property: { SELF, port } }" + * "{ get_input: db_name }" + * Function instance + + :param function: Function as string or a Function instance. + :return: True if function is a Tosca intrinsic function, otherwise False. + """ + if isinstance(function, dict) and len(function) == 1: + func_name = list(function.keys())[0] + return func_name in function_mappings + return isinstance(function, Function) + + +def get_function(tosca_tpl, node_template, raw_function): + """Gets a Function instance representing the provided template function. + + If the format provided raw_function format is not relevant for template + functions or if the function name doesn't exist in function mapping the + method returns the provided raw_function. + + :param tosca_tpl: The tosca template. + :param node_template: The node template the function is specified for. + :param raw_function: The raw function as dict. + :return: Template function as Function instance or the raw_function if + parsing was unsuccessful. + """ + if is_function(raw_function): + func_name = list(raw_function.keys())[0] + if func_name in function_mappings: + func = function_mappings[func_name] + func_args = list(raw_function.values())[0] + if not isinstance(func_args, list): + func_args = [func_args] + return func(tosca_tpl, node_template, func_name, func_args) + return raw_function diff --git a/tosca2heat/tosca-parser/toscaparser/groups.py b/tosca2heat/tosca-parser/toscaparser/groups.py new file mode 100644 index 0000000..5fd5dec --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/groups.py @@ -0,0 +1,55 @@ +# Licensed 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 toscaparser.common.exception import ExceptionCollector +from toscaparser.common.exception import UnknownFieldError +from toscaparser.entity_template import EntityTemplate +from toscaparser.utils import validateutils + +SECTIONS = (TYPE, METADATA, DESCRIPTION, PROPERTIES, TARGETS, INTERFACES) = \ + ('type', 'metadata', 'description', + 'properties', 'members', 'interfaces') + + +class Group(EntityTemplate): + + def __init__(self, name, group_templates, member_nodes, custom_defs=None): + super(Group, self).__init__(name, + group_templates, + 'group_type', + custom_defs) + self.name = name + self.tpl = group_templates + self.meta_data = None + if self.METADATA in self.tpl: + self.meta_data = self.tpl.get(self.METADATA) + validateutils.validate_map(self.meta_data) + self.member_nodes = member_nodes + self._validate_keys() + + @property + def members(self): + return self.entity_tpl.get('members') + + @property + def description(self): + return self.entity_tpl.get('description') + + def get_member_nodes(self): + return self.member_nodes + + def _validate_keys(self): + for key in self.entity_tpl.keys(): + if key not in SECTIONS: + ExceptionCollector.appendException( + UnknownFieldError(what='Groups "%s"' % self.name, + field=key)) diff --git a/tosca2heat/tosca-parser/toscaparser/imports.py b/tosca2heat/tosca-parser/toscaparser/imports.py new file mode 100644 index 0000000..5149382 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/imports.py @@ -0,0 +1,270 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import logging +import os + +from toscaparser.common.exception import ExceptionCollector +from toscaparser.common.exception import MissingRequiredFieldError +from toscaparser.common.exception import UnknownFieldError +from toscaparser.common.exception import ValidationError +from toscaparser.elements.tosca_type_validation import TypeValidation +from toscaparser.utils.gettextutils import _ +import toscaparser.utils.urlutils +import toscaparser.utils.yamlparser + +YAML_LOADER = toscaparser.utils.yamlparser.load_yaml +log = logging.getLogger("tosca") + + +class ImportsLoader(object): + + IMPORTS_SECTION = (FILE, REPOSITORY, NAMESPACE_URI, NAMESPACE_PREFIX) = \ + ('file', 'repository', 'namespace_uri', + 'namespace_prefix') + + def __init__(self, importslist, path, type_definition_list=None, + tpl=None): + self.importslist = importslist + self.custom_defs = {} + if not path and not tpl: + msg = _('Input tosca template is not provided.') + log.warning(msg) + ExceptionCollector.appendException(ValidationError(message=msg)) + self.path = path + self.repositories = {} + if tpl and tpl.get('repositories'): + self.repositories = tpl.get('repositories') + self.type_definition_list = [] + if type_definition_list: + if isinstance(type_definition_list, list): + self.type_definition_list = type_definition_list + else: + self.type_definition_list.append(type_definition_list) + self._validate_and_load_imports() + + def get_custom_defs(self): + return self.custom_defs + + def _validate_and_load_imports(self): + imports_names = set() + + if not self.importslist: + msg = _('"imports" keyname is defined without including ' + 'templates.') + log.error(msg) + ExceptionCollector.appendException(ValidationError(message=msg)) + return + + for import_def in self.importslist: + if isinstance(import_def, dict): + for import_name, import_uri in import_def.items(): + if import_name in imports_names: + msg = (_('Duplicate import name "%s" was found.') % + import_name) + log.error(msg) + ExceptionCollector.appendException( + ValidationError(message=msg)) + imports_names.add(import_name) + + custom_type = self._load_import_template(import_name, + import_uri) + namespace_prefix = None + if isinstance(import_uri, dict): + namespace_prefix = import_uri.get( + self.NAMESPACE_PREFIX) + if custom_type: + TypeValidation(custom_type, import_def) + self._update_custom_def(custom_type, namespace_prefix) + else: # old style of imports + custom_type = self._load_import_template(None, + import_def) + if custom_type: + TypeValidation( + custom_type, import_def) + self._update_custom_def(custom_type, None) + + def _update_custom_def(self, custom_type, namespace_prefix): + outer_custom_types = {} + for type_def in self.type_definition_list: + outer_custom_types = custom_type.get(type_def) + if outer_custom_types: + if type_def == "imports": + self.custom_defs.update({'imports': outer_custom_types}) + else: + if namespace_prefix: + prefix_custom_types = {} + for type_def_key in outer_custom_types.keys(): + namespace_prefix_to_key = (namespace_prefix + + "." + type_def_key) + prefix_custom_types[namespace_prefix_to_key] = \ + outer_custom_types[type_def_key] + self.custom_defs.update(prefix_custom_types) + else: + self.custom_defs.update(outer_custom_types) + + def _validate_import_keys(self, import_name, import_uri_def): + if self.FILE not in import_uri_def.keys(): + log.warning(_('Missing keyname "file" in import "%(name)s".') + % {'name': import_name}) + ExceptionCollector.appendException( + MissingRequiredFieldError( + what='Import of template "%s"' % import_name, + required=self.FILE)) + for key in import_uri_def.keys(): + if key not in self.IMPORTS_SECTION: + log.warning(_('Unknown keyname "%(key)s" error in ' + 'imported definition "%(def)s".') + % {'key': key, 'def': import_name}) + ExceptionCollector.appendException( + UnknownFieldError( + what='Import of template "%s"' % import_name, + field=key)) + + def _load_import_template(self, import_name, import_uri_def): + """Handle custom types defined in imported template files + + This method loads the custom type definitions referenced in "imports" + section of the TOSCA YAML template by determining whether each import + is specified via a file reference (by relative or absolute path) or a + URL reference. + + Possibilities: + +----------+--------+------------------------------+ + | template | import | comment | + +----------+--------+------------------------------+ + | file | file | OK | + | file | URL | OK | + | preparsed| file | file must be a full path | + | preparsed| URL | OK | + | URL | file | file must be a relative path | + | URL | URL | OK | + +----------+--------+------------------------------+ + """ + + short_import_notation = False + if isinstance(import_uri_def, dict): + self._validate_import_keys(import_name, import_uri_def) + file_name = import_uri_def.get(self.FILE) + repository = import_uri_def.get(self.REPOSITORY) + else: + file_name = import_uri_def + repository = None + short_import_notation = True + + if not file_name: + msg = (_('A template file name is not provided with import ' + 'definition "%(import_name)s".') + % {'import_name': import_name}) + log.error(msg) + ExceptionCollector.appendException(ValidationError(message=msg)) + return + + if toscaparser.utils.urlutils.UrlUtils.validate_url(file_name): + return YAML_LOADER(file_name, False) + elif not repository: + import_template = None + if self.path: + if toscaparser.utils.urlutils.UrlUtils.validate_url(self.path): + if os.path.isabs(file_name): + msg = (_('Absolute file name "%(name)s" cannot be ' + 'used in a URL-based input template ' + '"%(template)s".') + % {'name': file_name, 'template': self.path}) + log.error(msg) + ExceptionCollector.appendException(ImportError(msg)) + return + import_template = toscaparser.utils.urlutils.UrlUtils.\ + join_url(self.path, file_name) + a_file = False + else: + a_file = True + main_a_file = os.path.isfile(self.path) + + if main_a_file: + if os.path.isfile(file_name): + import_template = file_name + else: + full_path = os.path.join( + os.path.dirname(os.path.abspath(self.path)), + file_name) + if os.path.isfile(full_path): + import_template = full_path + else: + file_path = file_name.rpartition("/") + dir_path = os.path.dirname(os.path.abspath( + self.path)) + if file_path[0] != '' and dir_path.endswith( + file_path[0]): + import_template = dir_path + "/" +\ + file_path[2] + if not os.path.isfile(import_template): + msg = (_('"%(import_template)s" is' + 'not a valid file') + % {'import_template': + import_template}) + log.error(msg) + ExceptionCollector.appendException + (ValueError(msg)) + else: # template is pre-parsed + if os.path.isabs(file_name) and os.path.isfile(file_name): + a_file = True + import_template = file_name + else: + msg = (_('Relative file name "%(name)s" cannot be used ' + 'in a pre-parsed input template.') + % {'name': file_name}) + log.error(msg) + ExceptionCollector.appendException(ImportError(msg)) + return + + if not import_template: + log.error(_('Import "%(name)s" is not valid.') % + {'name': import_uri_def}) + ExceptionCollector.appendException( + ImportError(_('Import "%s" is not valid.') % + import_uri_def)) + return + return YAML_LOADER(import_template, a_file) + + if short_import_notation: + log.error(_('Import "%(name)s" is not valid.') % import_uri_def) + ExceptionCollector.appendException( + ImportError(_('Import "%s" is not valid.') % import_uri_def)) + return + + full_url = "" + if repository: + if self.repositories: + for repo_name, repo_def in self.repositories.items(): + if repo_name == repository: + # Remove leading, ending spaces and strip + # the last character if "/" + repo_url = ((repo_def['url']).strip()).rstrip("//") + full_url = repo_url + "/" + file_name + + if not full_url: + msg = (_('referenced repository "%(n_uri)s" in import ' + 'definition "%(tpl)s" not found.') + % {'n_uri': repository, 'tpl': import_name}) + log.error(msg) + ExceptionCollector.appendException(ImportError(msg)) + return + + if toscaparser.utils.urlutils.UrlUtils.validate_url(full_url): + return YAML_LOADER(full_url, False) + else: + msg = (_('repository url "%(n_uri)s" is not valid in import ' + 'definition "%(tpl)s".') + % {'n_uri': repo_url, 'tpl': import_name}) + log.error(msg) + ExceptionCollector.appendException(ImportError(msg)) diff --git a/tosca2heat/tosca-parser/toscaparser/nodetemplate.py b/tosca2heat/tosca-parser/toscaparser/nodetemplate.py new file mode 100644 index 0000000..d969d51 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/nodetemplate.py @@ -0,0 +1,270 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +import logging + +from toscaparser.common.exception import ExceptionCollector +from toscaparser.common.exception import InvalidPropertyValueError +from toscaparser.common.exception import MissingRequiredFieldError +from toscaparser.common.exception import TypeMismatchError +from toscaparser.common.exception import UnknownFieldError +from toscaparser.common.exception import ValidationError +from toscaparser.dataentity import DataEntity +from toscaparser.elements.interfaces import CONFIGURE +from toscaparser.elements.interfaces import CONFIGURE_SHORTNAME +from toscaparser.elements.interfaces import InterfacesDef +from toscaparser.elements.interfaces import LIFECYCLE +from toscaparser.elements.interfaces import LIFECYCLE_SHORTNAME +from toscaparser.elements.relationshiptype import RelationshipType +from toscaparser.entity_template import EntityTemplate +from toscaparser.relationship_template import RelationshipTemplate +from toscaparser.utils.gettextutils import _ + +log = logging.getLogger('tosca') + + +class NodeTemplate(EntityTemplate): + '''Node template from a Tosca profile.''' + def __init__(self, name, node_templates, custom_def=None, + available_rel_tpls=None, available_rel_types=None): + super(NodeTemplate, self).__init__(name, node_templates[name], + 'node_type', + custom_def) + self.templates = node_templates + self._validate_fields(node_templates[name]) + self.custom_def = custom_def + self.related = {} + self.relationship_tpl = [] + self.available_rel_tpls = available_rel_tpls + self.available_rel_types = available_rel_types + self._relationships = {} + + @property + def relationships(self): + if not self._relationships: + requires = self.requirements + if requires: + for r in requires: + for r1, value in r.items(): + explicit = self._get_explicit_relationship(r, value) + if explicit: + for key, value in explicit.items(): + self._relationships[key] = value + return self._relationships + + def _get_explicit_relationship(self, req, value): + """Handle explicit relationship + + For example, + - req: + node: DBMS + relationship: tosca.relationships.HostedOn + """ + explicit_relation = {} + node = value.get('node') if isinstance(value, dict) else value + + if node: + # TODO(spzala) implement look up once Glance meta data is available + # to find a matching TOSCA node using the TOSCA types + msg = _('Lookup by TOSCA types is not supported. ' + 'Requirement for "%s" can not be full-filled.') % self.name + if (node in list(self.type_definition.TOSCA_DEF.keys()) + or node in self.custom_def): + ExceptionCollector.appendException(NotImplementedError(msg)) + return + + if node not in self.templates: + ExceptionCollector.appendException( + KeyError(_('Node template "%s" was not found.') % node)) + return + + related_tpl = NodeTemplate(node, self.templates, self.custom_def) + relationship = value.get('relationship') \ + if isinstance(value, dict) else None + # check if it's type has relationship defined + if not relationship: + parent_reqs = self.type_definition.get_all_requirements() + if parent_reqs is None: + ExceptionCollector.appendException( + ValidationError(message='parent_req is ' + + str(parent_reqs))) + else: + for key in req.keys(): + for req_dict in parent_reqs: + if key in req_dict.keys(): + relationship = (req_dict.get(key). + get('relationship')) + break + if relationship: + found_relationship_tpl = False + # apply available relationship templates if found + if self.available_rel_tpls: + for tpl in self.available_rel_tpls: + if tpl.name == relationship: + rtype = RelationshipType(tpl.type, None, + self.custom_def) + explicit_relation[rtype] = related_tpl + tpl.target = related_tpl + tpl.source = self + self.relationship_tpl.append(tpl) + found_relationship_tpl = True + # create relationship template object. + rel_prfx = self.type_definition.RELATIONSHIP_PREFIX + if not found_relationship_tpl: + if isinstance(relationship, dict): + relationship = relationship.get('type') + if relationship: + if self.available_rel_types and \ + relationship in self.available_rel_types.keys(): + pass + elif not relationship.startswith(rel_prfx): + relationship = rel_prfx + relationship + else: + ExceptionCollector.appendException( + MissingRequiredFieldError( + what=_('"relationship" used in template ' + '"%s"') % related_tpl.name, + required=self.TYPE)) + for rtype in self.type_definition.relationship.keys(): + if rtype.type == relationship: + explicit_relation[rtype] = related_tpl + related_tpl._add_relationship_template(req, + rtype.type, + self) + elif self.available_rel_types: + if relationship in self.available_rel_types.keys(): + rel_type_def = self.available_rel_types.\ + get(relationship) + if 'derived_from' in rel_type_def: + super_type = \ + rel_type_def.get('derived_from') + if not super_type.startswith(rel_prfx): + super_type = rel_prfx + super_type + if rtype.type == super_type: + explicit_relation[rtype] = related_tpl + related_tpl.\ + _add_relationship_template( + req, rtype.type, self) + return explicit_relation + + def _add_relationship_template(self, requirement, rtype, source): + req = requirement.copy() + req['type'] = rtype + tpl = RelationshipTemplate(req, rtype, self.custom_def, self, source) + self.relationship_tpl.append(tpl) + + def get_relationship_template(self): + return self.relationship_tpl + + def _add_next(self, nodetpl, relationship): + self.related[nodetpl] = relationship + + @property + def related_nodes(self): + if not self.related: + for relation, node in self.type_definition.relationship.items(): + for tpl in self.templates: + if tpl == node.type: + self.related[NodeTemplate(tpl)] = relation + return self.related.keys() + + def validate(self, tosca_tpl=None): + self._validate_capabilities() + self._validate_requirements() + self._validate_properties(self.entity_tpl, self.type_definition) + self._validate_interfaces() + for prop in self.get_properties_objects(): + prop.validate() + + def _validate_requirements(self): + type_requires = self.type_definition.get_all_requirements() + allowed_reqs = ["template"] + if type_requires: + for treq in type_requires: + for key, value in treq.items(): + allowed_reqs.append(key) + if isinstance(value, dict): + for key in value: + allowed_reqs.append(key) + + requires = self.type_definition.get_value(self.REQUIREMENTS, + self.entity_tpl) + if requires: + if not isinstance(requires, list): + ExceptionCollector.appendException( + TypeMismatchError( + what='"requirements" of template "%s"' % self.name, + type='list')) + for req in requires: + for r1, value in req.items(): + if isinstance(value, dict): + self._validate_requirements_keys(value) + self._validate_requirements_properties(value) + allowed_reqs.append(r1) + self._common_validate_field(req, allowed_reqs, 'requirements') + + def _validate_requirements_properties(self, requirements): + # TODO(anyone): Only occurrences property of the requirements is + # validated here. Validation of other requirement properties are being + # validated in different files. Better to keep all the requirements + # properties validation here. + for key, value in requirements.items(): + if key == 'occurrences': + self._validate_occurrences(value) + break + + def _validate_occurrences(self, occurrences): + DataEntity.validate_datatype('list', occurrences) + for value in occurrences: + DataEntity.validate_datatype('integer', value) + if len(occurrences) != 2 or not (0 <= occurrences[0] <= occurrences[1]) \ + or occurrences[1] == 0: + ExceptionCollector.appendException( + InvalidPropertyValueError(what=(occurrences))) + + def _validate_requirements_keys(self, requirement): + for key in requirement.keys(): + if key not in self.REQUIREMENTS_SECTION: + ExceptionCollector.appendException( + UnknownFieldError( + what='"requirements" of template "%s"' % self.name, + field=key)) + + def _validate_interfaces(self): + ifaces = self.type_definition.get_value(self.INTERFACES, + self.entity_tpl) + if ifaces: + for i in ifaces: + for name, value in ifaces.items(): + if name in (LIFECYCLE, LIFECYCLE_SHORTNAME): + self._common_validate_field( + value, InterfacesDef. + interfaces_node_lifecycle_operations, + 'interfaces') + elif name in (CONFIGURE, CONFIGURE_SHORTNAME): + self._common_validate_field( + value, InterfacesDef. + interfaces_relationship_configure_operations, + 'interfaces') + else: + ExceptionCollector.appendException( + UnknownFieldError( + what='"interfaces" of template "%s"' % + self.name, field=name)) + + def _validate_fields(self, nodetemplate): + for name in nodetemplate.keys(): + if name not in self.SECTIONS and name not in self.SPECIAL_SECTIONS: + ExceptionCollector.appendException( + UnknownFieldError(what='Node template "%s"' % self.name, + field=name)) diff --git a/tosca2heat/tosca-parser/toscaparser/parameters.py b/tosca2heat/tosca-parser/toscaparser/parameters.py new file mode 100644 index 0000000..983aee3 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/parameters.py @@ -0,0 +1,114 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +import logging + +from toscaparser.common.exception import ExceptionCollector +from toscaparser.common.exception import MissingRequiredFieldError +from toscaparser.common.exception import UnknownFieldError +from toscaparser.dataentity import DataEntity +from toscaparser.elements.constraints import Schema +from toscaparser.elements.entity_type import EntityType +from toscaparser.utils.gettextutils import _ + + +log = logging.getLogger('tosca') + + +class Input(object): + + INPUTFIELD = (TYPE, DESCRIPTION, DEFAULT, CONSTRAINTS) = \ + ('type', 'description', 'default', 'constraints') + + def __init__(self, name, schema_dict): + self.name = name + self.schema = Schema(name, schema_dict) + + @property + def type(self): + return self.schema.type + + @property + def description(self): + return self.schema.description + + @property + def default(self): + return self.schema.default + + @property + def constraints(self): + return self.schema.constraints + + def validate(self, value=None): + self._validate_field() + self.validate_type(self.type) + if value: + self._validate_value(value) + + def _validate_field(self): + for name in self.schema.schema: + if name not in self.INPUTFIELD: + ExceptionCollector.appendException( + UnknownFieldError(what='Input "%s"' % self.name, + field=name)) + + def validate_type(self, input_type): + if input_type not in Schema.PROPERTY_TYPES: + ExceptionCollector.appendException( + ValueError(_('Invalid type "%s".') % type)) + + def _validate_value(self, value): + tosca = EntityType.TOSCA_DEF + datatype = None + if self.type in tosca: + datatype = tosca[self.type] + elif EntityType.DATATYPE_PREFIX + self.type in tosca: + datatype = tosca[EntityType.DATATYPE_PREFIX + self.type] + + DataEntity.validate_datatype(self.type, value, None, datatype) + + +class Output(object): + + OUTPUTFIELD = (DESCRIPTION, VALUE) = ('description', 'value') + + def __init__(self, name, attrs): + self.name = name + self.attrs = attrs + + @property + def description(self): + return self.attrs.get(self.DESCRIPTION) + + @property + def value(self): + return self.attrs.get(self.VALUE) + + def validate(self): + self._validate_field() + + def _validate_field(self): + if not isinstance(self.attrs, dict): + ExceptionCollector.appendException( + MissingRequiredFieldError(what='Output "%s"' % self.name, + required=self.VALUE)) + if self.value is None: + ExceptionCollector.appendException( + MissingRequiredFieldError(what='Output "%s"' % self.name, + required=self.VALUE)) + for name in self.attrs: + if name not in self.OUTPUTFIELD: + ExceptionCollector.appendException( + UnknownFieldError(what='Output "%s"' % self.name, + field=name)) diff --git a/tosca2heat/tosca-parser/toscaparser/policy.py b/tosca2heat/tosca-parser/toscaparser/policy.py new file mode 100644 index 0000000..61c09ec --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/policy.py @@ -0,0 +1,77 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +import logging + +from toscaparser.common.exception import ExceptionCollector +from toscaparser.common.exception import UnknownFieldError +from toscaparser.entity_template import EntityTemplate +from toscaparser.triggers import Triggers +from toscaparser.utils import validateutils + + +SECTIONS = (TYPE, METADATA, DESCRIPTION, PROPERTIES, TARGETS, TRIGGERS) = \ + ('type', 'metadata', 'description', + 'properties', 'targets', 'triggers') + +log = logging.getLogger('tosca') + + +class Policy(EntityTemplate): + '''Policies defined in Topology template.''' + def __init__(self, name, policy, targets, targets_type, custom_def=None): + super(Policy, self).__init__(name, + policy, + 'policy_type', + custom_def) + self.meta_data = None + if self.METADATA in policy: + self.meta_data = policy.get(self.METADATA) + validateutils.validate_map(self.meta_data) + self.targets_list = targets + self.targets_type = targets_type + self.triggers = self._triggers(policy.get(TRIGGERS)) + self._validate_keys() + + @property + def targets(self): + return self.entity_tpl.get('targets') + + @property + def description(self): + return self.entity_tpl.get('description') + + @property + def metadata(self): + return self.entity_tpl.get('metadata') + + def get_targets_type(self): + return self.targets_type + + def get_targets_list(self): + return self.targets_list + + def _triggers(self, triggers): + triggerObjs = [] + if triggers: + for name, trigger_tpl in triggers.items(): + triggersObj = Triggers(name, trigger_tpl) + triggerObjs.append(triggersObj) + return triggerObjs + + def _validate_keys(self): + for key in self.entity_tpl.keys(): + if key not in SECTIONS: + ExceptionCollector.appendException( + UnknownFieldError(what='Policy "%s"' % self.name, + field=key)) diff --git a/tosca2heat/tosca-parser/toscaparser/prereq/__init__.py b/tosca2heat/tosca-parser/toscaparser/prereq/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/prereq/__init__.py diff --git a/tosca2heat/tosca-parser/toscaparser/prereq/csar.py b/tosca2heat/tosca-parser/toscaparser/prereq/csar.py new file mode 100644 index 0000000..1cba5c4 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/prereq/csar.py @@ -0,0 +1,286 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import os.path +import requests +import shutil +import six +import tempfile +import yaml +import zipfile + +from toscaparser.common.exception import ExceptionCollector +from toscaparser.common.exception import URLException +from toscaparser.common.exception import ValidationError +from toscaparser.imports import ImportsLoader +from toscaparser.utils.gettextutils import _ +from toscaparser.utils.urlutils import UrlUtils + +try: # Python 2.x + from BytesIO import BytesIO +except ImportError: # Python 3.x + from io import BytesIO + + +class CSAR(object): + + def __init__(self, csar_file, a_file=True): + self.path = csar_file + self.a_file = a_file + self.is_validated = False + self.error_caught = False + self.csar = None + self.temp_dir = None + + def validate(self): + """Validate the provided CSAR file.""" + + self.is_validated = True + + # validate that the file or URL exists + missing_err_msg = (_('"%s" does not exist.') % self.path) + if self.a_file: + if not os.path.isfile(self.path): + ExceptionCollector.appendException( + ValidationError(message=missing_err_msg)) + return False + else: + self.csar = self.path + else: # a URL + if not UrlUtils.validate_url(self.path): + ExceptionCollector.appendException( + ValidationError(message=missing_err_msg)) + return False + else: + response = requests.get(self.path) + self.csar = BytesIO(response.content) + + # validate that it is a valid zip file + if not zipfile.is_zipfile(self.csar): + err_msg = (_('"%s" is not a valid zip file.') % self.path) + ExceptionCollector.appendException( + ValidationError(message=err_msg)) + return False + + # validate that it contains the metadata file in the correct location + self.zfile = zipfile.ZipFile(self.csar, 'r') + filelist = self.zfile.namelist() + if 'TOSCA-Metadata/TOSCA.meta' not in filelist: + err_msg = (_('"%s" is not a valid CSAR as it does not contain the ' + 'required file "TOSCA.meta" in the folder ' + '"TOSCA-Metadata".') % self.path) + ExceptionCollector.appendException( + ValidationError(message=err_msg)) + return False + + # validate that 'Entry-Definitions' property exists in TOSCA.meta + data = self.zfile.read('TOSCA-Metadata/TOSCA.meta') + invalid_yaml_err_msg = (_('The file "TOSCA-Metadata/TOSCA.meta" in ' + 'the CSAR "%s" does not contain valid YAML ' + 'content.') % self.path) + try: + meta = yaml.load(data) + if type(meta) is dict: + self.metadata = meta + else: + ExceptionCollector.appendException( + ValidationError(message=invalid_yaml_err_msg)) + return False + except yaml.YAMLError: + ExceptionCollector.appendException( + ValidationError(message=invalid_yaml_err_msg)) + return False + + if 'Entry-Definitions' not in self.metadata: + err_msg = (_('The CSAR "%s" is missing the required metadata ' + '"Entry-Definitions" in ' + '"TOSCA-Metadata/TOSCA.meta".') + % self.path) + ExceptionCollector.appendException( + ValidationError(message=err_msg)) + return False + + # validate that 'Entry-Definitions' metadata value points to an + # existing file in the CSAR + entry = self.metadata.get('Entry-Definitions') + if entry and entry not in filelist: + err_msg = (_('The "Entry-Definitions" file defined in the ' + 'CSAR "%s" does not exist.') % self.path) + ExceptionCollector.appendException( + ValidationError(message=err_msg)) + return False + + # validate that external references in the main template actually + # exist and are accessible + self._validate_external_references() + return not self.error_caught + + def get_metadata(self): + """Return the metadata dictionary.""" + + # validate the csar if not already validated + if not self.is_validated: + self.validate() + + # return a copy to avoid changes overwrite the original + return dict(self.metadata) if self.metadata else None + + def _get_metadata(self, key): + if not self.is_validated: + self.validate() + return self.metadata.get(key) + + def get_author(self): + return self._get_metadata('Created-By') + + def get_version(self): + return self._get_metadata('CSAR-Version') + + def get_main_template(self): + entry_def = self._get_metadata('Entry-Definitions') + if entry_def in self.zfile.namelist(): + return entry_def + + def get_main_template_yaml(self): + main_template = self.get_main_template() + if main_template: + data = self.zfile.read(main_template) + invalid_tosca_yaml_err_msg = ( + _('The file "%(template)s" in the CSAR "%(csar)s" does not ' + 'contain valid TOSCA YAML content.') % + {'template': main_template, 'csar': self.path}) + try: + tosca_yaml = yaml.load(data) + if type(tosca_yaml) is not dict: + ExceptionCollector.appendException( + ValidationError(message=invalid_tosca_yaml_err_msg)) + return tosca_yaml + except Exception: + ExceptionCollector.appendException( + ValidationError(message=invalid_tosca_yaml_err_msg)) + + def get_description(self): + desc = self._get_metadata('Description') + if desc is not None: + return desc + + self.metadata['Description'] = \ + self.get_main_template_yaml().get('description') + return self.metadata['Description'] + + def decompress(self): + if not self.is_validated: + self.validate() + self.temp_dir = tempfile.NamedTemporaryFile().name + with zipfile.ZipFile(self.csar, "r") as zf: + zf.extractall(self.temp_dir) + + def _validate_external_references(self): + """Extracts files referenced in the main template + + These references are currently supported: + * imports + * interface implementations + * artifacts + """ + try: + self.decompress() + main_tpl_file = self.get_main_template() + if not main_tpl_file: + return + main_tpl = self.get_main_template_yaml() + + if 'imports' in main_tpl: + ImportsLoader(main_tpl['imports'], + os.path.join(self.temp_dir, main_tpl_file)) + + if 'topology_template' in main_tpl: + topology_template = main_tpl['topology_template'] + + if 'node_templates' in topology_template: + node_templates = topology_template['node_templates'] + + for node_template_key in node_templates: + node_template = node_templates[node_template_key] + if 'artifacts' in node_template: + artifacts = node_template['artifacts'] + for artifact_key in artifacts: + artifact = artifacts[artifact_key] + if isinstance(artifact, six.string_types): + self._validate_external_reference( + main_tpl_file, + artifact) + elif isinstance(artifact, dict): + if 'file' in artifact: + self._validate_external_reference( + main_tpl_file, + artifact['file']) + else: + ExceptionCollector.appendException( + ValueError(_('Unexpected artifact ' + 'definition for "%s".') + % artifact_key)) + self.error_caught = True + if 'interfaces' in node_template: + interfaces = node_template['interfaces'] + for interface_key in interfaces: + interface = interfaces[interface_key] + for opertation_key in interface: + operation = interface[opertation_key] + if isinstance(operation, six.string_types): + self._validate_external_reference( + main_tpl_file, + operation, + False) + elif isinstance(operation, dict): + if 'implementation' in operation: + self._validate_external_reference( + main_tpl_file, + operation['implementation']) + finally: + if self.temp_dir: + shutil.rmtree(self.temp_dir) + + def _validate_external_reference(self, tpl_file, resource_file, + raise_exc=True): + """Verify that the external resource exists + + If resource_file is a URL verify that the URL is valid. + If resource_file is a relative path verify that the path is valid + considering base folder (self.temp_dir) and tpl_file. + Note that in a CSAR resource_file cannot be an absolute path. + """ + if UrlUtils.validate_url(resource_file): + msg = (_('The resource at "%s" cannot be accessed.') % + resource_file) + try: + if UrlUtils.url_accessible(resource_file): + return + else: + ExceptionCollector.appendException( + URLException(what=msg)) + self.error_caught = True + except Exception: + ExceptionCollector.appendException( + URLException(what=msg)) + self.error_caught = True + + if os.path.isfile(os.path.join(self.temp_dir, + os.path.dirname(tpl_file), + resource_file)): + return + + if raise_exc: + ExceptionCollector.appendException( + ValueError(_('The resource "%s" does not exist.') + % resource_file)) + self.error_caught = True diff --git a/tosca2heat/tosca-parser/toscaparser/properties.py b/tosca2heat/tosca-parser/toscaparser/properties.py new file mode 100644 index 0000000..23c1db2 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/properties.py @@ -0,0 +1,76 @@ +# Licensed 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 toscaparser.dataentity import DataEntity +from toscaparser.elements.constraints import Schema +from toscaparser.functions import is_function + + +class Property(object): + '''TOSCA built-in Property type.''' + + PROPERTY_KEYS = ( + TYPE, REQUIRED, DESCRIPTION, DEFAULT, CONSTRAINTS + ) = ( + 'type', 'required', 'description', 'default', 'constraints' + ) + + ENTRY_SCHEMA_KEYS = ( + ENTRYTYPE, ENTRYPROPERTIES + ) = ( + 'type', 'properties' + ) + + def __init__(self, property_name, value, schema_dict, custom_def=None): + self.name = property_name + self.value = value + self.custom_def = custom_def + self.schema = Schema(property_name, schema_dict) + + @property + def type(self): + return self.schema.type + + @property + def required(self): + return self.schema.required + + @property + def description(self): + return self.schema.description + + @property + def default(self): + return self.schema.default + + @property + def constraints(self): + return self.schema.constraints + + @property + def entry_schema(self): + return self.schema.entry_schema + + def validate(self): + '''Validate if not a reference property.''' + if not is_function(self.value): + if self.type == Schema.STRING: + self.value = str(self.value) + self.value = DataEntity.validate_datatype(self.type, self.value, + self.entry_schema, + self.custom_def) + self._validate_constraints() + + def _validate_constraints(self): + if self.constraints: + for constraint in self.constraints: + constraint.validate(self.value) diff --git a/tosca2heat/tosca-parser/toscaparser/relationship_template.py b/tosca2heat/tosca-parser/toscaparser/relationship_template.py new file mode 100644 index 0000000..db334c4 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/relationship_template.py @@ -0,0 +1,78 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +import logging + +from toscaparser.entity_template import EntityTemplate +from toscaparser.properties import Property + +SECTIONS = (DERIVED_FROM, PROPERTIES, REQUIREMENTS, + INTERFACES, CAPABILITIES, TYPE) = \ + ('derived_from', 'properties', 'requirements', 'interfaces', + 'capabilities', 'type') + +log = logging.getLogger('tosca') + + +class RelationshipTemplate(EntityTemplate): + '''Relationship template.''' + def __init__(self, relationship_template, name, custom_def=None, + target=None, source=None): + super(RelationshipTemplate, self).__init__(name, + relationship_template, + 'relationship_type', + custom_def) + self.name = name.lower() + self.target = target + self.source = source + + def get_properties_objects(self): + '''Return properties objects for this template.''' + if self._properties is None: + self._properties = self._create_relationship_properties() + return self._properties + + def _create_relationship_properties(self): + props = [] + properties = {} + relationship = self.entity_tpl.get('relationship') + + if not relationship: + for value in self.entity_tpl.values(): + if isinstance(value, dict): + relationship = value.get('relationship') + break + + if relationship: + properties = self.type_definition.get_value(self.PROPERTIES, + relationship) or {} + if not properties: + properties = self.entity_tpl.get(self.PROPERTIES) or {} + + if properties: + for name, value in properties.items(): + props_def = self.type_definition.get_properties_def() + if props_def and name in props_def: + if name in properties.keys(): + value = properties.get(name) + prop = Property(name, value, + props_def[name].schema, self.custom_def) + props.append(prop) + for p in self.type_definition.get_properties_def_objects(): + if p.default is not None and p.name not in properties.keys(): + prop = Property(p.name, p.default, p.schema, self.custom_def) + props.append(prop) + return props + + def validate(self): + self._validate_properties(self.entity_tpl, self.type_definition) diff --git a/tosca2heat/tosca-parser/toscaparser/shell.py b/tosca2heat/tosca-parser/toscaparser/shell.py new file mode 100644 index 0000000..848726f --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/shell.py @@ -0,0 +1,106 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +import os +import sys + +from toscaparser.tosca_template import ToscaTemplate +from toscaparser.utils.gettextutils import _ +import toscaparser.utils.urlutils + +""" +CLI entry point to show how TOSCA Parser can be used programmatically + +This is a basic command line utility showing the entry point in the +TOSCA Parser and how to iterate over parsed template. It can be extended +or modified to fit an individual need. + +It can be used as, +#tosca-parser --template-file=<path to the YAML template> +#tosca-parser --template-file=<path to the CSAR zip file> +#tosca-parser --template-file=<URL to the template or CSAR> + +e.g. +#tosca-parser + --template-file=toscaparser/tests/data/tosca_helloworld.yaml +#tosca-parser + --template-file=toscaparser/tests/data/CSAR/csar_hello_world.zip +""" + + +class ParserShell(object): + + def _validate(self, args): + if len(args) < 1: + msg = _('The program requires a template or a CSAR file as an ' + 'argument. Please refer to the usage documentation.') + raise ValueError(msg) + if "--template-file=" not in args[0]: + msg = _('The program expects "--template-file" as the first ' + 'argument. Please refer to the usage documentation.') + raise ValueError(msg) + + def main(self, args): + self._validate(args) + path = args[0].split('--template-file=')[1] + if os.path.isfile(path): + self.parse(path) + elif toscaparser.utils.urlutils.UrlUtils.validate_url(path): + self.parse(path, False) + else: + raise ValueError(_('"%(path)s" is not a valid file.') + % {'path': path}) + + def parse(self, path, a_file=True): + output = None + tosca = ToscaTemplate(path, None, a_file) + + version = tosca.version + if tosca.version: + print ("\nversion: " + version) + + if hasattr(tosca, 'description'): + description = tosca.description + if description: + print ("\ndescription: " + description) + + if hasattr(tosca, 'inputs'): + inputs = tosca.inputs + if inputs: + print ("\ninputs:") + for input in inputs: + print ("\t" + input.name) + + if hasattr(tosca, 'nodetemplates'): + nodetemplates = tosca.nodetemplates + if nodetemplates: + print ("\nnodetemplates:") + for node in nodetemplates: + print ("\t" + node.name) + + if hasattr(tosca, 'outputs'): + outputs = tosca.outputs + if outputs: + print ("\noutputs:") + for output in outputs: + print ("\t" + output.name) + + +def main(args=None): + if args is None: + args = sys.argv[1:] + ParserShell().main(args) + + +if __name__ == '__main__': + main() diff --git a/tosca2heat/tosca-parser/toscaparser/tests/__init__.py b/tosca2heat/tosca-parser/toscaparser/tests/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/__init__.py diff --git a/tosca2heat/tosca-parser/toscaparser/tests/artifacts/collectd/config.py b/tosca2heat/tosca-parser/toscaparser/tests/artifacts/collectd/config.py new file mode 100644 index 0000000..686bbd1 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/artifacts/collectd/config.py @@ -0,0 +1,25 @@ +#!/usr/bin/python + +# Licensed 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. + +# This script configures collectd to send metric data to the +# logstash server port 25826 +# The environment variable logstash_ip is expected to be set up +import os +with open("/etc/collectd/collectd.conf.d/tosca_elk.conf", "w") as fh: + fh.write(""" + LoadPlugin network + <Plugin network> + Server "%s" "25826" + </Plugin> + """ % (os.environ['logstash_ip'])) diff --git a/tosca2heat/tosca-parser/toscaparser/tests/artifacts/collectd/create.sh b/tosca2heat/tosca-parser/toscaparser/tests/artifacts/collectd/create.sh new file mode 100644 index 0000000..a483b88 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/artifacts/collectd/create.sh @@ -0,0 +1,5 @@ +#!/bin/bash +# This script install collectd for monitoring data + +apt-get update +apt-get install -y collectd diff --git a/tosca2heat/tosca-parser/toscaparser/tests/artifacts/collectd/start.sh b/tosca2heat/tosca-parser/toscaparser/tests/artifacts/collectd/start.sh new file mode 100644 index 0000000..7e8e033 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/artifacts/collectd/start.sh @@ -0,0 +1,4 @@ +#!/bin/bash +# This script starts collectd as a service in init.d +service collectd stop +service collectd start diff --git a/tosca2heat/tosca-parser/toscaparser/tests/artifacts/elasticsearch/create.sh b/tosca2heat/tosca-parser/toscaparser/tests/artifacts/elasticsearch/create.sh new file mode 100644 index 0000000..c34126c --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/artifacts/elasticsearch/create.sh @@ -0,0 +1,14 @@ +#!/bin/bash +# This script installs java and elasticsearch + +apt-get update +apt-get install -y openjdk-7-jre-headless + +wget -qO - https://packages.elasticsearch.org/GPG-KEY-elasticsearch | apt-key add - +echo "deb http://packages.elasticsearch.org/elasticsearch/1.5/debian stable main" | tee -a /etc/apt/sources.list + +apt-get update +apt-get install -y elasticsearch + +# set up to run as service +update-rc.d elasticsearch defaults 95 10 diff --git a/tosca2heat/tosca-parser/toscaparser/tests/artifacts/elasticsearch/start.sh b/tosca2heat/tosca-parser/toscaparser/tests/artifacts/elasticsearch/start.sh new file mode 100644 index 0000000..bbc0347 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/artifacts/elasticsearch/start.sh @@ -0,0 +1,4 @@ +#!/bin/bash +# This script starts elasticsearch as a service in init.d +service elasticsearch stop +service elasticsearch start diff --git a/tosca2heat/tosca-parser/toscaparser/tests/artifacts/kibana/config.sh b/tosca2heat/tosca-parser/toscaparser/tests/artifacts/kibana/config.sh new file mode 100644 index 0000000..f28215a --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/artifacts/kibana/config.sh @@ -0,0 +1,7 @@ +#!/bin/bash +# This script configures kibana to connect to the elasticsearch server +# to access data and to export the app url on port 5601: +# The environment variable elasticsearch_ip and kibana_ip are expected +# to be set up. +sed -i 's/localhost/'$elasticsearch_ip'/' /opt/kibana/config/kibana.yml +sed -i 's/0.0.0.0/'$kibana_ip'/' /opt/kibana/config/kibana.yml diff --git a/tosca2heat/tosca-parser/toscaparser/tests/artifacts/kibana/create.sh b/tosca2heat/tosca-parser/toscaparser/tests/artifacts/kibana/create.sh new file mode 100644 index 0000000..41914b1 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/artifacts/kibana/create.sh @@ -0,0 +1,12 @@ +#!/bin/bash +# This script installs kibana and sets it up to run as a service in init.d +cd /opt +wget https://download.elastic.co/kibana/kibana/kibana-4.1.0-linux-x64.tar.gz +tar xzvf kibana-4.1.0-linux-x64.tar.gz +mv kibana-4.1.0-linux-x64 kibana + +# set up to run as service +cd /etc/init.d +wget https://gist.githubusercontent.com/thisismitch/8b15ac909aed214ad04a/raw/bce61d85643c2dcdfbc2728c55a41dab444dca20/kibana4 +chmod +x kibana4 +update-rc.d kibana4 defaults 96 9 diff --git a/tosca2heat/tosca-parser/toscaparser/tests/artifacts/kibana/start.sh b/tosca2heat/tosca-parser/toscaparser/tests/artifacts/kibana/start.sh new file mode 100644 index 0000000..5149bb3 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/artifacts/kibana/start.sh @@ -0,0 +1,4 @@ +#!/bin/bash +# This script starts kibana as a service in init.d +service kibana4 stop +service kibana4 start diff --git a/tosca2heat/tosca-parser/toscaparser/tests/artifacts/logstash/configure_collectd.py b/tosca2heat/tosca-parser/toscaparser/tests/artifacts/logstash/configure_collectd.py new file mode 100644 index 0000000..18fdacf --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/artifacts/logstash/configure_collectd.py @@ -0,0 +1,28 @@ +#!/usr/bin/python + +# Licensed 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. + +# This script configures the logstash input using the udp protocol on +# port 25826. This is intended to receive data from collectd from +# any source +with open("/etc/logstash/conf.d/collectd.conf", "w") as fh: + fh.write(""" + input { + udp { + port => 25826 # 25826 is the default for collectd + buffer_size => 1452 # 1452 is the default for collectd + codec => collectd { } + tags => ["metrics"] + type => "collectd" + } + }""") diff --git a/tosca2heat/tosca-parser/toscaparser/tests/artifacts/logstash/configure_elasticsearch.py b/tosca2heat/tosca-parser/toscaparser/tests/artifacts/logstash/configure_elasticsearch.py new file mode 100644 index 0000000..2e5389c --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/artifacts/logstash/configure_elasticsearch.py @@ -0,0 +1,26 @@ +#!/usr/bin/python + +# Licensed 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. + +# This script configures the logstash output to forward to elasticsearch +# The environment variable elasticsearch_ip is expected to be set up +import os +with open("/etc/logstash/conf.d/elasticsearch.conf", 'w') as fh: + fh.write(""" + output { + elasticsearch { + action => index + host => "%s" + protocol => "http" + } + }""" % (os.environ['elasticsearch_ip'])) diff --git a/tosca2heat/tosca-parser/toscaparser/tests/artifacts/logstash/configure_rsyslog.py b/tosca2heat/tosca-parser/toscaparser/tests/artifacts/logstash/configure_rsyslog.py new file mode 100644 index 0000000..fc610c2 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/artifacts/logstash/configure_rsyslog.py @@ -0,0 +1,25 @@ +#!/usr/bin/python + +# Licensed 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. + +# This script configures the logstash input using the RELP protocol on +# port 2514 This is intended to receive logs from rsyslog from +# any source +with open("/etc/logstash/conf.d/rsyslog.conf", "w") as fh: + fh.write(""" + input { + relp { + port => 2514 + tags => ["logs"] + } + }""") diff --git a/tosca2heat/tosca-parser/toscaparser/tests/artifacts/logstash/create.sh b/tosca2heat/tosca-parser/toscaparser/tests/artifacts/logstash/create.sh new file mode 100644 index 0000000..77cc8fd --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/artifacts/logstash/create.sh @@ -0,0 +1,20 @@ +#!/bin/bash +# This script installs java, logstash and the contrib package for logstash +# install java as prereq + +apt-get update +apt-get install -y openjdk-7-jre-headless +mkdir /etc/logstash + +# install by apt-get from repo +wget -O - http://packages.elasticsearch.org/GPG-KEY-elasticsearch | apt-key add - +echo "deb http://packages.elasticsearch.org/logstash/1.4/debian stable main" | tee -a /etc/apt/sources.list + +apt-get update +apt-get install -y logstash + +# install contrib to get the relp plugin +/opt/logstash/bin/plugin install contrib + +# set up to run as service +update-rc.d logstash defaults 95 10 diff --git a/tosca2heat/tosca-parser/toscaparser/tests/artifacts/logstash/start.sh b/tosca2heat/tosca-parser/toscaparser/tests/artifacts/logstash/start.sh new file mode 100644 index 0000000..a73cf61 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/artifacts/logstash/start.sh @@ -0,0 +1,4 @@ +#!/bin/bash +# Run logstash as service in init.d +service logstash stop +service logstash start diff --git a/tosca2heat/tosca-parser/toscaparser/tests/artifacts/mongodb/config.sh b/tosca2heat/tosca-parser/toscaparser/tests/artifacts/mongodb/config.sh new file mode 100644 index 0000000..78f484e --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/artifacts/mongodb/config.sh @@ -0,0 +1,7 @@ +#!/bin/bash +# Edit the file /etc/mongod.conf, update with real IP of Mongo server +# This script configures the mongodb server to export its service on +# the server IP +# bind_ip = 127.0.0.1 -> bind_ip = <IP for Mongo server> +# The environment variable mongodb_ip is expected to be set up +sed -i "s/= 127.0.0.1/= $mongodb_ip,127.0.0.1/" /etc/mongod.conf diff --git a/tosca2heat/tosca-parser/toscaparser/tests/artifacts/mongodb/create.sh b/tosca2heat/tosca-parser/toscaparser/tests/artifacts/mongodb/create.sh new file mode 100644 index 0000000..d84c275 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/artifacts/mongodb/create.sh @@ -0,0 +1,14 @@ +#!/bin/bash +# This script installs mongodb + +apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10 +echo "deb http://repo.mongodb.org/apt/ubuntu "$(lsb_release -sc)"/mongodb-org/3.0 multiverse" | tee /etc/apt/sources.list.d/mongodb-org-3.0.list + +apt-get update +apt-get install -y mongodb-org + +#Wait for mongodb initialization +while [[ ! -d "/var/lib/mongodb/_tmp" ]]; do + echo "Waiting for mongodb initialization ..." + sleep 5 +done diff --git a/tosca2heat/tosca-parser/toscaparser/tests/artifacts/mongodb/create_database.sh b/tosca2heat/tosca-parser/toscaparser/tests/artifacts/mongodb/create_database.sh new file mode 100644 index 0000000..16f1358 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/artifacts/mongodb/create_database.sh @@ -0,0 +1,5 @@ +#!/bin/bash +echo "conn = new Mongo();" > setup.js +echo "db = conn.getDB('paypal_pizza');" >> setup.js +echo "db.about.insert({'name': 'PayPal Pizza Store'});" >> setup.js +mongo setup.js diff --git a/tosca2heat/tosca-parser/toscaparser/tests/artifacts/mongodb/start.sh b/tosca2heat/tosca-parser/toscaparser/tests/artifacts/mongodb/start.sh new file mode 100644 index 0000000..ac200a5 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/artifacts/mongodb/start.sh @@ -0,0 +1,5 @@ +#!/bin/bash +# This script starts mongodb +service mongod stop +rm /var/lib/mongodb/mongod.lock +service mongod start diff --git a/tosca2heat/tosca-parser/toscaparser/tests/artifacts/mysql/mysql_database_configure.sh b/tosca2heat/tosca-parser/toscaparser/tests/artifacts/mysql/mysql_database_configure.sh new file mode 100644 index 0000000..092136a --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/artifacts/mysql/mysql_database_configure.sh @@ -0,0 +1,8 @@ +#!/bin/sh +cat << EOF | mysql -u root --password=$db_root_password +CREATE DATABASE $db_name; +GRANT ALL PRIVILEGES ON $db_name.* TO "$db_user"@"localhost" +IDENTIFIED BY "$db_password"; +FLUSH PRIVILEGES; +EXIT +EOF
\ No newline at end of file diff --git a/tosca2heat/tosca-parser/toscaparser/tests/artifacts/mysql/mysql_dbms_configure.sh b/tosca2heat/tosca-parser/toscaparser/tests/artifacts/mysql/mysql_dbms_configure.sh new file mode 100644 index 0000000..d4ef6b4 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/artifacts/mysql/mysql_dbms_configure.sh @@ -0,0 +1,5 @@ +#!/bin/sh +sed --regexp-extended "s/(port\s*=\s*)[0-9]*/\1$db_port/g" </etc/mysql/my.cnf >/tmp/my.cnf +mv -f /tmp/my.cnf /etc/mysql/my.cnf +/etc/init.d/mysql stop +/etc/init.d/mysql start
\ No newline at end of file diff --git a/tosca2heat/tosca-parser/toscaparser/tests/artifacts/mysql/mysql_dbms_install.sh b/tosca2heat/tosca-parser/toscaparser/tests/artifacts/mysql/mysql_dbms_install.sh new file mode 100644 index 0000000..38628b9 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/artifacts/mysql/mysql_dbms_install.sh @@ -0,0 +1,9 @@ +#!/bin/bash +#This script installs mysql server + +apt-get update + +debconf-set-selections <<< "mysql-server mysql-server/root_password password $db_root_password" +debconf-set-selections <<< "mysql-server mysql-server/root_password_again password $db_root_password" + +apt-get -y install --fix-missing mysql-server
\ No newline at end of file diff --git a/tosca2heat/tosca-parser/toscaparser/tests/artifacts/mysql/mysql_dbms_start.sh b/tosca2heat/tosca-parser/toscaparser/tests/artifacts/mysql/mysql_dbms_start.sh new file mode 100644 index 0000000..3378670 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/artifacts/mysql/mysql_dbms_start.sh @@ -0,0 +1,2 @@ +#!/bin/sh +/etc/init.d/mysql start
\ No newline at end of file diff --git a/tosca2heat/tosca-parser/toscaparser/tests/artifacts/nodejs/config.sh b/tosca2heat/tosca-parser/toscaparser/tests/artifacts/nodejs/config.sh new file mode 100644 index 0000000..1e149a2 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/artifacts/nodejs/config.sh @@ -0,0 +1,28 @@ +#!/bin/bash +# This script installs an app for nodejs: the app intended is the paypal app +# and it is configured to connect to the mongodb server +# The environment variables github_url and mongodb_ip are expected to be set up +export app_dir=/opt/app +git clone $github_url /opt/app +if [ -f /opt/app/package.json ]; then + cd /opt/app/ && npm install + sed -i "s/localhost/$mongodb_ip/" config.json +fi + +cat > /etc/init/nodeapp.conf <<EOS +description "node.js app" + +start on (net-device-up + and local-filesystems + and runlevel [2345]) +stop on runlevel [!2345] + +expect fork +respawn + +script + export HOME=/ + export NODE_PATH=/usr/lib/node + exec /usr/bin/node ${app_dir}/app.js >> /var/log/nodeapp.log 2>&1 & +end script +EOS diff --git a/tosca2heat/tosca-parser/toscaparser/tests/artifacts/nodejs/create.sh b/tosca2heat/tosca-parser/toscaparser/tests/artifacts/nodejs/create.sh new file mode 100644 index 0000000..04fd6c6 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/artifacts/nodejs/create.sh @@ -0,0 +1,7 @@ +#!/bin/bash +# This script installs nodejs and the prereq + +add-apt-repository ppa:chris-lea/node.js + +apt-get update +apt-get install -y nodejs build-essential diff --git a/tosca2heat/tosca-parser/toscaparser/tests/artifacts/nodejs/start.sh b/tosca2heat/tosca-parser/toscaparser/tests/artifacts/nodejs/start.sh new file mode 100644 index 0000000..6939cb7 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/artifacts/nodejs/start.sh @@ -0,0 +1,3 @@ +#!/bin/bash +# This script starts the nodejs application +start nodeapp diff --git a/tosca2heat/tosca-parser/toscaparser/tests/artifacts/rsyslog/config.sh b/tosca2heat/tosca-parser/toscaparser/tests/artifacts/rsyslog/config.sh new file mode 100644 index 0000000..630767d --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/artifacts/rsyslog/config.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +# This script configures the output for rsyslogd to send logs to the +# logstash server port 2514 using the RELP protocol +# The environment variable logstash_ip is expected to be set up +echo "module(load=\"omrelp\") +action(type=\"omrelp\" target=\"$logstash_ip\" port=\"2514\")" > /etc/rsyslog.d/tosca_elk.conf + +# Remove the /dev/xconsole configuration as xconsole +# is not available by default +l=`awk '/=warn.*\|.*\/dev\/xconsole/{print NR - 1}' /etc/rsyslog.d/50-default.conf` +if [ ! -z $l ]; then + l=`expr $l + 1` + line=`cat /etc/rsyslog.d/50-default.conf | head -n $l | tail -1` + if [[ ! $line == \#* ]]; then + l0=`expr $l - 3` + sed -i -r -e "${l0},${l}s/^.{0}/&#/" /etc/rsyslog.d/50-default.conf + fi +fi + +# Enable nodejs logs for rsyslog +if ! grep -q nodeapp "/etc/rsyslog.conf"; then + sed -i 's/\$PrivDropToGroup\ syslog/\$PrivDropToGroup adm/' /etc/rsyslog.conf + echo "\$ModLoad imfile.so +\$InputFileName /var/log/nodeapp.log +\$InputFileTag paypal_pizza: +\$InputFileStateFile stat-nodeapp +\$InputRunFileMonitor +\$InputFilePollInterval 1" >> /etc/rsyslog.conf +fi diff --git a/tosca2heat/tosca-parser/toscaparser/tests/artifacts/rsyslog/create.sh b/tosca2heat/tosca-parser/toscaparser/tests/artifacts/rsyslog/create.sh new file mode 100644 index 0000000..affdd6e --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/artifacts/rsyslog/create.sh @@ -0,0 +1,5 @@ +#!/bin/bash +# This script installs rsyslog and the library for RELP + +apt-get update +apt-get install -y rsyslog rsyslog-relp diff --git a/tosca2heat/tosca-parser/toscaparser/tests/artifacts/rsyslog/start.sh b/tosca2heat/tosca-parser/toscaparser/tests/artifacts/rsyslog/start.sh new file mode 100644 index 0000000..3de82d1 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/artifacts/rsyslog/start.sh @@ -0,0 +1,4 @@ +#!/bin/bash +# This script starts rsyslogd as a service in init.d +service rsyslog stop +service rsyslog start diff --git a/tosca2heat/tosca-parser/toscaparser/tests/artifacts/webserver/webserver_install.sh b/tosca2heat/tosca-parser/toscaparser/tests/artifacts/webserver/webserver_install.sh new file mode 100644 index 0000000..4ca9b4e --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/artifacts/webserver/webserver_install.sh @@ -0,0 +1,5 @@ +#!/bin/sh +#This script installs apache web server + +apt-get update +apt-get install -y apache2
\ No newline at end of file diff --git a/tosca2heat/tosca-parser/toscaparser/tests/artifacts/webserver/webserver_start.sh b/tosca2heat/tosca-parser/toscaparser/tests/artifacts/webserver/webserver_start.sh new file mode 100644 index 0000000..e962ca5 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/artifacts/webserver/webserver_start.sh @@ -0,0 +1,2 @@ +#!/bin/sh +service apache2 start
\ No newline at end of file diff --git a/tosca2heat/tosca-parser/toscaparser/tests/artifacts/wordpress/wordpress_configure.sh b/tosca2heat/tosca-parser/toscaparser/tests/artifacts/wordpress/wordpress_configure.sh new file mode 100644 index 0000000..5598b4f --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/artifacts/wordpress/wordpress_configure.sh @@ -0,0 +1,4 @@ +#!/bin/sh +ln -s /usr/share/wordpress /var/www/html/wordpress +gzip -d /usr/share/doc/wordpress/examples/setup-mysql.gz +echo $wp_db_password | bash /usr/share/doc/wordpress/examples/setup-mysql -e $wp_db_name -u $wp_db_user localhost
\ No newline at end of file diff --git a/tosca2heat/tosca-parser/toscaparser/tests/artifacts/wordpress/wordpress_install.sh b/tosca2heat/tosca-parser/toscaparser/tests/artifacts/wordpress/wordpress_install.sh new file mode 100644 index 0000000..1320443 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/artifacts/wordpress/wordpress_install.sh @@ -0,0 +1,5 @@ +#!/bin/sh +#This script installs wordpress + +apt-get update +apt-get install -y wordpress
\ No newline at end of file diff --git a/tosca2heat/tosca-parser/toscaparser/tests/base.py b/tosca2heat/tosca-parser/toscaparser/tests/base.py new file mode 100644 index 0000000..f6ff8d1 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/base.py @@ -0,0 +1,67 @@ +# -*- coding: utf-8 -*- + +# Copyright 2010-2011 OpenStack Foundation +# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import os + +import fixtures +import testscenarios +import testtools + +from toscaparser.tosca_template import ToscaTemplate + +_TRUE_VALUES = ('True', 'true', '1', 'yes') + + +class TestCase(testscenarios.TestWithScenarios, testtools.TestCase): + + """Test case base class for all unit tests.""" + + def setUp(self): + """Run before each test method to initialize test environment.""" + + super(TestCase, self).setUp() + test_timeout = os.environ.get('OS_TEST_TIMEOUT', 0) + try: + test_timeout = int(test_timeout) + except ValueError: + # If timeout value is invalid do not set a timeout. + test_timeout = 0 + if test_timeout > 0: + self.useFixture(fixtures.Timeout(test_timeout, gentle=True)) + + self.useFixture(fixtures.NestedTempfile()) + self.useFixture(fixtures.TempHomeDir()) + + if os.environ.get('OS_STDOUT_CAPTURE') in _TRUE_VALUES: + stdout = self.useFixture(fixtures.StringStream('stdout')).stream + self.useFixture(fixtures.MonkeyPatch('sys.stdout', stdout)) + if os.environ.get('OS_STDERR_CAPTURE') in _TRUE_VALUES: + stderr = self.useFixture(fixtures.StringStream('stderr')).stream + self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr)) + + self.log_fixture = self.useFixture(fixtures.FakeLogger()) + + def _load_template(self, filename): + """Load a Tosca template from tests data folder. + + :param filename: Tosca template file name to load. + :return: ToscaTemplate + """ + return ToscaTemplate(os.path.join( + os.path.dirname(os.path.abspath(__file__)), + 'data', + filename)) diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/csar_elk.csar b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/csar_elk.csar Binary files differnew file mode 100644 index 0000000..5fae801 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/csar_elk.csar diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/csar_elk.zip b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/csar_elk.zip Binary files differnew file mode 100644 index 0000000..5fae801 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/csar_elk.zip diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/csar_hello_world.zip b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/csar_hello_world.zip Binary files differnew file mode 100644 index 0000000..43ffbbc --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/csar_hello_world.zip diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/csar_invalid_entry_def.zip b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/csar_invalid_entry_def.zip Binary files differnew file mode 100644 index 0000000..382f790 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/csar_invalid_entry_def.zip diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/csar_metadata_not_yaml.zip b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/csar_metadata_not_yaml.zip Binary files differnew file mode 100644 index 0000000..3e6120b --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/csar_metadata_not_yaml.zip diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/csar_missing_metadata.zip b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/csar_missing_metadata.zip Binary files differnew file mode 100644 index 0000000..5ec7a99 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/csar_missing_metadata.zip diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/csar_no_metadata_file.zip b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/csar_no_metadata_file.zip Binary files differnew file mode 100644 index 0000000..b0df9b9 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/csar_no_metadata_file.zip diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/csar_not_zip.zip b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/csar_not_zip.zip new file mode 100644 index 0000000..43b7f5f --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/csar_not_zip.zip @@ -0,0 +1 @@ +This is an invalid CSAR file.
\ No newline at end of file diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/csar_wordpress.zip b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/csar_wordpress.zip Binary files differnew file mode 100644 index 0000000..5df7b48 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/csar_wordpress.zip diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/csar_wordpress_invalid_import_path.zip b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/csar_wordpress_invalid_import_path.zip Binary files differnew file mode 100644 index 0000000..9dc6c9a --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/csar_wordpress_invalid_import_path.zip diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/csar_wordpress_invalid_import_url.zip b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/csar_wordpress_invalid_import_url.zip Binary files differnew file mode 100644 index 0000000..c7a260f --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/csar_wordpress_invalid_import_url.zip diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/csar_wordpress_invalid_script_path.zip b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/csar_wordpress_invalid_script_path.zip Binary files differnew file mode 100644 index 0000000..5e4f9e0 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/csar_wordpress_invalid_script_path.zip diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/csar_wordpress_invalid_script_url.zip b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/csar_wordpress_invalid_script_url.zip Binary files differnew file mode 100644 index 0000000..b4133b6 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/csar_wordpress_invalid_script_url.zip diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/csar_wordpress_with_url_import_and_script.zip b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/csar_wordpress_with_url_import_and_script.zip Binary files differnew file mode 100644 index 0000000..5dedfcd --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/csar_wordpress_with_url_import_and_script.zip diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/csar_wrong_metadata_file.zip b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/csar_wrong_metadata_file.zip Binary files differnew file mode 100644 index 0000000..85d660a --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/csar_wrong_metadata_file.zip diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Definitions/collectd.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Definitions/collectd.yaml new file mode 100644 index 0000000..1ac0935 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Definitions/collectd.yaml @@ -0,0 +1,13 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + collectd is a daemon which gathers statistics about the system it is running on. + +node_types: + tosca.nodes.SoftwareComponent.Collectd: + derived_from: tosca.nodes.SoftwareComponent + requirements: + - log_endpoint: + capability: tosca.capabilities.Endpoint + node: tosca.nodes.SoftwareComponent.Logstash + relationship: tosca.relationships.ConnectsTo
\ No newline at end of file diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Definitions/elasticsearch.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Definitions/elasticsearch.yaml new file mode 100644 index 0000000..4a1770f --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Definitions/elasticsearch.yaml @@ -0,0 +1,11 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + Elasticsearch is an open-source search engine built on top of Apache Lucene, a full-text search-engine library. + +node_types: + tosca.nodes.SoftwareComponent.Elasticsearch: + derived_from: tosca.nodes.SoftwareComponent + capabilities: + search_endpoint: + type: tosca.capabilities.Endpoint diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Definitions/kibana.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Definitions/kibana.yaml new file mode 100644 index 0000000..3a4351c --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Definitions/kibana.yaml @@ -0,0 +1,16 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + Kibana is an open source analytics and visualization platform designed to work with Elasticsearch. + You use Kibana to search, view, and interact with data stored in Elasticsearch. + +node_types: + tosca.nodes.SoftwareComponent.Kibana: + derived_from: tosca.nodes.SoftwareComponent + requirements: + - search_endpoint: + capability: tosca.capabilities.Endpoint + node: tosca.nodes.SoftwareComponent.Elasticsearch + relationship: tosca.relationships.ConnectsTo + + diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Definitions/logstash.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Definitions/logstash.yaml new file mode 100644 index 0000000..8495954 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Definitions/logstash.yaml @@ -0,0 +1,25 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + Logstash is a tool for receiving, processing and outputting logs. All kinds of logs. System logs, webserver logs, + error logs, application logs, and just about anything you can throw at it. + +node_types: + tosca.nodes.SoftwareComponent.Logstash: + derived_from: tosca.nodes.SoftwareComponent + requirements: + - search_endpoint: + capability: tosca.capabilities.Endpoint + node: tosca.nodes.SoftwareComponent.Elasticsearch + relationship: + type: tosca.relationships.ConnectsTo + interfaces: + Configure: + pre_configure_source: + inputs: + elasticsearch_ip: + type: string + capabilities: + log_endpoint: + type: tosca.capabilities.Endpoint + diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Definitions/paypalpizzastore_nodejs_app.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Definitions/paypalpizzastore_nodejs_app.yaml new file mode 100644 index 0000000..cdabeae --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Definitions/paypalpizzastore_nodejs_app.yaml @@ -0,0 +1,29 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + Pizza store app that allows you to explore the features provided by PayPal's REST APIs. + More detail can be found at https://github.com/paypal/rest-api-sample-app-nodejs/ + +node_types: + tosca.nodes.WebApplication.PayPalPizzaStore: + derived_from: tosca.nodes.WebApplication + properties: + github_url: + required: false + type: string + description: location of the application on the github. + default: https://github.com/sample.git + requirements: + #WebApplication inherits Computer, so host implied. + - database_connection: + capability: tosca.capabilities.Endpoint.Database + node: tosca.nodes.Database + relationship: tosca.relationships.ConnectsTo + interfaces: + Standard: + configure: + inputs: + github_url: + type: string + mongodb_ip: + type: string diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Definitions/rsyslog.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Definitions/rsyslog.yaml new file mode 100644 index 0000000..4614ee7 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Definitions/rsyslog.yaml @@ -0,0 +1,13 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + RSYSLOG is the Rocket-fast SYStem for LOG processing. + +node_types: + tosca.nodes.SoftwareComponent.Rsyslog: + derived_from: tosca.nodes.SoftwareComponent + requirements: + - log_endpoint: + capability: tosca.capabilities.Endpoint + node: tosca.nodes.SoftwareComponent.Logstash + relationship: tosca.relationships.ConnectsTo diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Definitions/tosca_elk.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Definitions/tosca_elk.yaml new file mode 100644 index 0000000..932f131 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Definitions/tosca_elk.yaml @@ -0,0 +1,217 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + This TOSCA simple profile deploys nodejs, mongodb, elasticsearch, logstash and kibana each on a separate server + with monitoring enabled for nodejs server where a sample nodejs application is running. The rsyslog and collectd are + installed on a nodejs server. + +imports: + - paypalpizzastore_nodejs_app.yaml + - elasticsearch.yaml + - logstash.yaml + - kibana.yaml + - collectd.yaml + - rsyslog.yaml + +dsl_definitions: + host_capabilities: &host_capabilities + # container properties (flavor) + disk_size: 10 GB + num_cpus: { get_input: my_cpus } + mem_size: 4096 MB + os_capabilities: &os_capabilities + architecture: x86_64 + type: Linux + distribution: Ubuntu + version: 14.04 + +topology_template: + inputs: + my_cpus: + type: integer + description: Number of CPUs for the server. + constraints: + - valid_values: [ 1, 2, 4, 8 ] + github_url: + type: string + description: The URL to download nodejs. + default: http://github.com/paypal/rest-api-sample-app-nodejs.git + + node_templates: + paypal_pizzastore: + type: tosca.nodes.WebApplication.PayPalPizzaStore + properties: + github_url: { get_input: github_url } + requirements: + - host: nodejs + - database_connection: mongo_db + interfaces: + Standard: + configure: + implementation: ../Scripts/nodejs/config.sh + inputs: + github_url: { get_property: [ SELF, github_url ] } + mongodb_ip: { get_attribute: [mongo_server, private_address] } + start: ../Scripts/nodejs/start.sh + nodejs: + type: tosca.nodes.WebServer + requirements: + - host: + node: app_server + interfaces: + Standard: + create: ../Scripts/nodejs/create.sh + mongo_db: + type: tosca.nodes.Database + requirements: + - host: mongo_dbms + interfaces: + Standard: + create: ../Scripts/mongodb/create_database.sh + mongo_dbms: + type: tosca.nodes.DBMS + requirements: + - host: mongo_server + interfaces: + Standard: + create: ../Scripts/mongodb/create.sh + configure: + implementation: ../Scripts/mongodb/config.sh + inputs: + mongodb_ip: { get_attribute: [mongo_server, private_address] } + start: ../Scripts/mongodb/start.sh + elasticsearch: + type: tosca.nodes.SoftwareComponent.Elasticsearch + requirements: + - host: elasticsearch_server + interfaces: + Standard: + create: ../Scripts/elasticsearch/create.sh + start: ../Scripts/elasticsearch/start.sh + logstash: + type: tosca.nodes.SoftwareComponent.Logstash + requirements: + - host: logstash_server + - search_endpoint: + node: elasticsearch + capability: search_endpoint + relationship: + type: tosca.relationships.ConnectsTo + interfaces: + Configure: + pre_configure_source: + implementation: ../Python/logstash/configure_elasticsearch.py + inputs: + elasticsearch_ip: { get_attribute: [elasticsearch_server, private_address] } + interfaces: + Standard: + create: ../Scripts/logstash/create.sh + start: ../Scripts/logstash/start.sh + kibana: + type: tosca.nodes.SoftwareComponent.Kibana + requirements: + - host: kibana_server + - search_endpoint: elasticsearch + interfaces: + Standard: + create: ../Scripts/kibana/create.sh + configure: + implementation: ../Scripts/kibana/config.sh + inputs: + elasticsearch_ip: { get_attribute: [elasticsearch_server, private_address] } + kibana_ip: { get_attribute: [kibana_server, private_address] } + start: ../Scripts/kibana/start.sh + app_collectd: + type: tosca.nodes.SoftwareComponent.Collectd + requirements: + - host: app_server + - log_endpoint: + node: logstash + capability: log_endpoint + relationship: + type: tosca.relationships.ConnectsTo + interfaces: + Configure: + pre_configure_target: + implementation: ../Python/logstash/configure_collectd.py + interfaces: + Standard: + create: ../Scripts/collectd/create.sh + configure: + implementation: ../Python/collectd/config.py + inputs: + logstash_ip: { get_attribute: [logstash_server, private_address] } + start: ../Scripts/collectd/start.sh + app_rsyslog: + type: tosca.nodes.SoftwareComponent.Rsyslog + requirements: + - host: app_server + - log_endpoint: + node: logstash + capability: log_endpoint + relationship: + type: tosca.relationships.ConnectsTo + interfaces: + Configure: + pre_configure_target: + implementation: ../Python/logstash/configure_rsyslog.py + interfaces: + Standard: + create: ../Scripts/rsyslog/create.sh + configure: + implementation: ../Scripts/rsyslog/config.sh + inputs: + logstash_ip: { get_attribute: [logstash_server, private_address] } + start: ../Scripts/rsyslog/start.sh + app_server: + type: tosca.nodes.Compute + capabilities: + host: + properties: *host_capabilities + os: + properties: *os_capabilities + mongo_server: + type: tosca.nodes.Compute + capabilities: + host: + properties: *host_capabilities + os: + properties: *os_capabilities + elasticsearch_server: + type: tosca.nodes.Compute + capabilities: + host: + properties: *host_capabilities + os: + properties: *os_capabilities + logstash_server: + type: tosca.nodes.Compute + capabilities: + host: + properties: *host_capabilities + os: + properties: *os_capabilities + kibana_server: + type: tosca.nodes.Compute + capabilities: + host: + properties: *host_capabilities + os: + properties: *os_capabilities + + outputs: + nodejs_url: + description: URL for the nodejs server, http://<IP>:3000 + value: { get_attribute: [ app_server, private_address ] } + mongodb_url: + description: URL for the mongodb server. + value: { get_attribute: [ mongo_server, private_address ] } + elasticsearch_url: + description: URL for the elasticsearch server. + value: { get_attribute: [ elasticsearch_server, private_address ] } + logstash_url: + description: URL for the logstash server. + value: { get_attribute: [ logstash_server, private_address ] } + kibana_url: + description: URL for the kibana server. + value: { get_attribute: [ kibana_server, private_address ] } diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Python/collectd/config.py b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Python/collectd/config.py new file mode 100644 index 0000000..686bbd1 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Python/collectd/config.py @@ -0,0 +1,25 @@ +#!/usr/bin/python + +# Licensed 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. + +# This script configures collectd to send metric data to the +# logstash server port 25826 +# The environment variable logstash_ip is expected to be set up +import os +with open("/etc/collectd/collectd.conf.d/tosca_elk.conf", "w") as fh: + fh.write(""" + LoadPlugin network + <Plugin network> + Server "%s" "25826" + </Plugin> + """ % (os.environ['logstash_ip'])) diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Python/logstash/configure_collectd.py b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Python/logstash/configure_collectd.py new file mode 100644 index 0000000..18fdacf --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Python/logstash/configure_collectd.py @@ -0,0 +1,28 @@ +#!/usr/bin/python + +# Licensed 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. + +# This script configures the logstash input using the udp protocol on +# port 25826. This is intended to receive data from collectd from +# any source +with open("/etc/logstash/conf.d/collectd.conf", "w") as fh: + fh.write(""" + input { + udp { + port => 25826 # 25826 is the default for collectd + buffer_size => 1452 # 1452 is the default for collectd + codec => collectd { } + tags => ["metrics"] + type => "collectd" + } + }""") diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Python/logstash/configure_elasticsearch.py b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Python/logstash/configure_elasticsearch.py new file mode 100644 index 0000000..2e5389c --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Python/logstash/configure_elasticsearch.py @@ -0,0 +1,26 @@ +#!/usr/bin/python + +# Licensed 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. + +# This script configures the logstash output to forward to elasticsearch +# The environment variable elasticsearch_ip is expected to be set up +import os +with open("/etc/logstash/conf.d/elasticsearch.conf", 'w') as fh: + fh.write(""" + output { + elasticsearch { + action => index + host => "%s" + protocol => "http" + } + }""" % (os.environ['elasticsearch_ip'])) diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Python/logstash/configure_rsyslog.py b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Python/logstash/configure_rsyslog.py new file mode 100644 index 0000000..fc610c2 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Python/logstash/configure_rsyslog.py @@ -0,0 +1,25 @@ +#!/usr/bin/python + +# Licensed 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. + +# This script configures the logstash input using the RELP protocol on +# port 2514 This is intended to receive logs from rsyslog from +# any source +with open("/etc/logstash/conf.d/rsyslog.conf", "w") as fh: + fh.write(""" + input { + relp { + port => 2514 + tags => ["logs"] + } + }""") diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/README.txt b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/README.txt new file mode 100644 index 0000000..382c9b0 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/README.txt @@ -0,0 +1,5 @@ +README: + +This TOSCA simple profile deploys nodejs, mongodb, elasticsearch, logstash and kibana each on a separate server with monitoring enabled for nodejs server where a sample nodejs application is running. The syslog and collectd are installed on a nodejs server. + +Entry information for processing through an orchestrator is contained in file TOSCA-Metadata/TOSCA.meta. This file provides high-level information such as CSAR version or creator of the CSAR. Furthermore, it provides pointers to the entry template under 'Entry-Definitions' key. The entry template itself may contain pointers to one or more files that are used to define TOSCA base type, unless provided by orchestrator as built-in TOSCA basetypes, and other non-normative types. These are typically provided under 'imports' section in the entry template file. Those type definitions will be read and processed by orchestrator or TOSCA parser to create an internal graph showing dependencies and relationships between various TOSCA types. The entry template may have references to various artifacts required for deployment and will be processed accordingly. diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Scripts/collectd/create.sh b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Scripts/collectd/create.sh new file mode 100644 index 0000000..a483b88 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Scripts/collectd/create.sh @@ -0,0 +1,5 @@ +#!/bin/bash +# This script install collectd for monitoring data + +apt-get update +apt-get install -y collectd diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Scripts/collectd/start.sh b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Scripts/collectd/start.sh new file mode 100644 index 0000000..7e8e033 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Scripts/collectd/start.sh @@ -0,0 +1,4 @@ +#!/bin/bash +# This script starts collectd as a service in init.d +service collectd stop +service collectd start diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Scripts/elasticsearch/create.sh b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Scripts/elasticsearch/create.sh new file mode 100644 index 0000000..c34126c --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Scripts/elasticsearch/create.sh @@ -0,0 +1,14 @@ +#!/bin/bash +# This script installs java and elasticsearch + +apt-get update +apt-get install -y openjdk-7-jre-headless + +wget -qO - https://packages.elasticsearch.org/GPG-KEY-elasticsearch | apt-key add - +echo "deb http://packages.elasticsearch.org/elasticsearch/1.5/debian stable main" | tee -a /etc/apt/sources.list + +apt-get update +apt-get install -y elasticsearch + +# set up to run as service +update-rc.d elasticsearch defaults 95 10 diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Scripts/elasticsearch/start.sh b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Scripts/elasticsearch/start.sh new file mode 100644 index 0000000..bbc0347 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Scripts/elasticsearch/start.sh @@ -0,0 +1,4 @@ +#!/bin/bash +# This script starts elasticsearch as a service in init.d +service elasticsearch stop +service elasticsearch start diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Scripts/kibana/config.sh b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Scripts/kibana/config.sh new file mode 100644 index 0000000..f28215a --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Scripts/kibana/config.sh @@ -0,0 +1,7 @@ +#!/bin/bash +# This script configures kibana to connect to the elasticsearch server +# to access data and to export the app url on port 5601: +# The environment variable elasticsearch_ip and kibana_ip are expected +# to be set up. +sed -i 's/localhost/'$elasticsearch_ip'/' /opt/kibana/config/kibana.yml +sed -i 's/0.0.0.0/'$kibana_ip'/' /opt/kibana/config/kibana.yml diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Scripts/kibana/create.sh b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Scripts/kibana/create.sh new file mode 100644 index 0000000..41914b1 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Scripts/kibana/create.sh @@ -0,0 +1,12 @@ +#!/bin/bash +# This script installs kibana and sets it up to run as a service in init.d +cd /opt +wget https://download.elastic.co/kibana/kibana/kibana-4.1.0-linux-x64.tar.gz +tar xzvf kibana-4.1.0-linux-x64.tar.gz +mv kibana-4.1.0-linux-x64 kibana + +# set up to run as service +cd /etc/init.d +wget https://gist.githubusercontent.com/thisismitch/8b15ac909aed214ad04a/raw/bce61d85643c2dcdfbc2728c55a41dab444dca20/kibana4 +chmod +x kibana4 +update-rc.d kibana4 defaults 96 9 diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Scripts/kibana/start.sh b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Scripts/kibana/start.sh new file mode 100644 index 0000000..5149bb3 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Scripts/kibana/start.sh @@ -0,0 +1,4 @@ +#!/bin/bash +# This script starts kibana as a service in init.d +service kibana4 stop +service kibana4 start diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Scripts/logstash/create.sh b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Scripts/logstash/create.sh new file mode 100644 index 0000000..77cc8fd --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Scripts/logstash/create.sh @@ -0,0 +1,20 @@ +#!/bin/bash +# This script installs java, logstash and the contrib package for logstash +# install java as prereq + +apt-get update +apt-get install -y openjdk-7-jre-headless +mkdir /etc/logstash + +# install by apt-get from repo +wget -O - http://packages.elasticsearch.org/GPG-KEY-elasticsearch | apt-key add - +echo "deb http://packages.elasticsearch.org/logstash/1.4/debian stable main" | tee -a /etc/apt/sources.list + +apt-get update +apt-get install -y logstash + +# install contrib to get the relp plugin +/opt/logstash/bin/plugin install contrib + +# set up to run as service +update-rc.d logstash defaults 95 10 diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Scripts/logstash/start.sh b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Scripts/logstash/start.sh new file mode 100644 index 0000000..a73cf61 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Scripts/logstash/start.sh @@ -0,0 +1,4 @@ +#!/bin/bash +# Run logstash as service in init.d +service logstash stop +service logstash start diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Scripts/mongodb/config.sh b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Scripts/mongodb/config.sh new file mode 100644 index 0000000..78f484e --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Scripts/mongodb/config.sh @@ -0,0 +1,7 @@ +#!/bin/bash +# Edit the file /etc/mongod.conf, update with real IP of Mongo server +# This script configures the mongodb server to export its service on +# the server IP +# bind_ip = 127.0.0.1 -> bind_ip = <IP for Mongo server> +# The environment variable mongodb_ip is expected to be set up +sed -i "s/= 127.0.0.1/= $mongodb_ip,127.0.0.1/" /etc/mongod.conf diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Scripts/mongodb/create.sh b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Scripts/mongodb/create.sh new file mode 100644 index 0000000..d84c275 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Scripts/mongodb/create.sh @@ -0,0 +1,14 @@ +#!/bin/bash +# This script installs mongodb + +apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10 +echo "deb http://repo.mongodb.org/apt/ubuntu "$(lsb_release -sc)"/mongodb-org/3.0 multiverse" | tee /etc/apt/sources.list.d/mongodb-org-3.0.list + +apt-get update +apt-get install -y mongodb-org + +#Wait for mongodb initialization +while [[ ! -d "/var/lib/mongodb/_tmp" ]]; do + echo "Waiting for mongodb initialization ..." + sleep 5 +done diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Scripts/mongodb/create_database.sh b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Scripts/mongodb/create_database.sh new file mode 100644 index 0000000..16f1358 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Scripts/mongodb/create_database.sh @@ -0,0 +1,5 @@ +#!/bin/bash +echo "conn = new Mongo();" > setup.js +echo "db = conn.getDB('paypal_pizza');" >> setup.js +echo "db.about.insert({'name': 'PayPal Pizza Store'});" >> setup.js +mongo setup.js diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Scripts/mongodb/start.sh b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Scripts/mongodb/start.sh new file mode 100644 index 0000000..ac200a5 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Scripts/mongodb/start.sh @@ -0,0 +1,5 @@ +#!/bin/bash +# This script starts mongodb +service mongod stop +rm /var/lib/mongodb/mongod.lock +service mongod start diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Scripts/nodejs/config.sh b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Scripts/nodejs/config.sh new file mode 100644 index 0000000..1e149a2 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Scripts/nodejs/config.sh @@ -0,0 +1,28 @@ +#!/bin/bash +# This script installs an app for nodejs: the app intended is the paypal app +# and it is configured to connect to the mongodb server +# The environment variables github_url and mongodb_ip are expected to be set up +export app_dir=/opt/app +git clone $github_url /opt/app +if [ -f /opt/app/package.json ]; then + cd /opt/app/ && npm install + sed -i "s/localhost/$mongodb_ip/" config.json +fi + +cat > /etc/init/nodeapp.conf <<EOS +description "node.js app" + +start on (net-device-up + and local-filesystems + and runlevel [2345]) +stop on runlevel [!2345] + +expect fork +respawn + +script + export HOME=/ + export NODE_PATH=/usr/lib/node + exec /usr/bin/node ${app_dir}/app.js >> /var/log/nodeapp.log 2>&1 & +end script +EOS diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Scripts/nodejs/create.sh b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Scripts/nodejs/create.sh new file mode 100644 index 0000000..04fd6c6 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Scripts/nodejs/create.sh @@ -0,0 +1,7 @@ +#!/bin/bash +# This script installs nodejs and the prereq + +add-apt-repository ppa:chris-lea/node.js + +apt-get update +apt-get install -y nodejs build-essential diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Scripts/nodejs/start.sh b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Scripts/nodejs/start.sh new file mode 100644 index 0000000..6939cb7 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Scripts/nodejs/start.sh @@ -0,0 +1,3 @@ +#!/bin/bash +# This script starts the nodejs application +start nodeapp diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Scripts/rsyslog/config.sh b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Scripts/rsyslog/config.sh new file mode 100644 index 0000000..630767d --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Scripts/rsyslog/config.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +# This script configures the output for rsyslogd to send logs to the +# logstash server port 2514 using the RELP protocol +# The environment variable logstash_ip is expected to be set up +echo "module(load=\"omrelp\") +action(type=\"omrelp\" target=\"$logstash_ip\" port=\"2514\")" > /etc/rsyslog.d/tosca_elk.conf + +# Remove the /dev/xconsole configuration as xconsole +# is not available by default +l=`awk '/=warn.*\|.*\/dev\/xconsole/{print NR - 1}' /etc/rsyslog.d/50-default.conf` +if [ ! -z $l ]; then + l=`expr $l + 1` + line=`cat /etc/rsyslog.d/50-default.conf | head -n $l | tail -1` + if [[ ! $line == \#* ]]; then + l0=`expr $l - 3` + sed -i -r -e "${l0},${l}s/^.{0}/&#/" /etc/rsyslog.d/50-default.conf + fi +fi + +# Enable nodejs logs for rsyslog +if ! grep -q nodeapp "/etc/rsyslog.conf"; then + sed -i 's/\$PrivDropToGroup\ syslog/\$PrivDropToGroup adm/' /etc/rsyslog.conf + echo "\$ModLoad imfile.so +\$InputFileName /var/log/nodeapp.log +\$InputFileTag paypal_pizza: +\$InputFileStateFile stat-nodeapp +\$InputRunFileMonitor +\$InputFilePollInterval 1" >> /etc/rsyslog.conf +fi diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Scripts/rsyslog/create.sh b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Scripts/rsyslog/create.sh new file mode 100644 index 0000000..affdd6e --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Scripts/rsyslog/create.sh @@ -0,0 +1,5 @@ +#!/bin/bash +# This script installs rsyslog and the library for RELP + +apt-get update +apt-get install -y rsyslog rsyslog-relp diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Scripts/rsyslog/start.sh b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Scripts/rsyslog/start.sh new file mode 100644 index 0000000..3de82d1 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/Scripts/rsyslog/start.sh @@ -0,0 +1,4 @@ +#!/bin/bash +# This script starts rsyslogd as a service in init.d +service rsyslog stop +service rsyslog start diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/TOSCA-Metadata/TOSCA.meta b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/TOSCA-Metadata/TOSCA.meta new file mode 100644 index 0000000..feb3d4f --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_elk/TOSCA-Metadata/TOSCA.meta @@ -0,0 +1,4 @@ +TOSCA-Meta-File-Version: 1.0 +CSAR-Version: 1.1 +Created-By: OASIS TOSCA TC +Entry-Definitions: Definitions/tosca_elk.yaml
\ No newline at end of file diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_single_instance_wordpress/Definitions/tosca_single_instance_wordpress.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_single_instance_wordpress/Definitions/tosca_single_instance_wordpress.yaml new file mode 100644 index 0000000..1dd195a --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_single_instance_wordpress/Definitions/tosca_single_instance_wordpress.yaml @@ -0,0 +1,109 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + TOSCA simple profile with wordpress, web server and mysql on the same server. + +imports: + - wordpress.yaml + +topology_template: + inputs: + cpus: + type: integer + description: Number of CPUs for the server. + constraints: + - valid_values: [ 1, 2, 4, 8 ] + db_name: + type: string + description: The name of the database. + db_user: + type: string + description: The user name of the DB user. + db_pwd: + type: string + description: The WordPress database admin account password. + db_root_pwd: + type: string + description: Root password for MySQL. + db_port: + type: PortDef + description: Port for the MySQL database. + + node_templates: + wordpress: + type: tosca.nodes.WebApplication.WordPress + requirements: + - host: webserver + - database_endpoint: mysql_database + interfaces: + Standard: + create: ../Scripts/WordPress/install.sh + configure: + implementation: ../Scripts/WordPress/configure.sh + inputs: + wp_db_name: { get_property: [ mysql_database, name ] } + wp_db_user: { get_property: [ mysql_database, user ] } + wp_db_password: { get_property: [ mysql_database, password ] } + + mysql_database: + type: tosca.nodes.Database + properties: + name: { get_input: db_name } + user: { get_input: db_user } + password: { get_input: db_pwd } + requirements: + - host: mysql_dbms + interfaces: + Standard: + configure: + implementation: ../Scripts/MYSQLDatabase/configure.sh + inputs: + db_name: { get_property: [ SELF, name ] } + db_user: { get_property: [ SELF, user ] } + db_password: { get_property: [ SELF, password ] } + db_root_password: { get_property: [ mysql_dbms, root_password ] } + + mysql_dbms: + type: tosca.nodes.DBMS + properties: + root_password: { get_input: db_root_pwd } + port: { get_input: db_port } + requirements: + - host: server + interfaces: + Standard: + create: ../Scripts/MYSQLDBMS/install.sh + start: ../Scripts/MYSQLDBMS/start.sh + configure: + implementation: ../Scripts/MYSQLDBMS/configure.sh + inputs: + root_password: { get_property: [ mysql_dbms, root_password ] } + + webserver: + type: tosca.nodes.WebServer + requirements: + - host: server + interfaces: + Standard: + create: ../Scripts/WebServer/install.sh + start: ../Scripts/WebServer/start.sh + + server: + type: tosca.nodes.Compute + capabilities: + host: + properties: + disk_size: 10 GB + num_cpus: { get_input: cpus } + mem_size: 4096 MB + os: + properties: + architecture: x86_64 + type: Linux + distribution: Fedora + version: 18.0 + + outputs: + website_url: + description: IP address for Wordpress wiki. + value: { get_attribute: [server, private_address] } diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_single_instance_wordpress/Definitions/wordpress.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_single_instance_wordpress/Definitions/wordpress.yaml new file mode 100644 index 0000000..5899ed9 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_single_instance_wordpress/Definitions/wordpress.yaml @@ -0,0 +1,19 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +node_types: + tosca.nodes.WebApplication.WordPress: + derived_from: tosca.nodes.WebApplication + requirements: + - database_endpoint: + capability: tosca.capabilities.Endpoint.Database + node: tosca.nodes.Database + relationship: tosca.relationships.ConnectsTo + interfaces: + Standard: + inputs: + wp_db_name: + type: string + wp_db_user: + type: string + wp_db_password: + type: string diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_single_instance_wordpress/README.txt b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_single_instance_wordpress/README.txt new file mode 100644 index 0000000..e882ff6 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_single_instance_wordpress/README.txt @@ -0,0 +1,22 @@ +README: + +This CSAR contains all definitions that are required for deploying WordPress +and MySQL on a single compute instance. + +Entry information for processing through an orchestrator is contained in file +TOSCA-Metadata/TOSCA.meta. This file provides high-level information such as +CSAR version or creator of the CSAR. Furthermore, it provides pointers to the +various TOSCA definitions files that contain the real details. +The entry 'Entry-Definitions' points to the definitions file which holds the +service template for the workload. +'Entry-Definitions' is optional. An orchestrator can also process the contents +like this: +1) Read in and process each definitions file. +2) For each definitions file: + 2.1) Read in all * type definitions (node types, capability types, etc.) and + store them in an internal map +3) Verify and build dependencies (e.g. inheritance) between all type definitions + previously read in. Orchestrator built-in types (e.g. TOSCA base types) are + also considered in this step. +4) Process the actual service template (the file with a node_templates section). + Validate using previously obtained type information. diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_single_instance_wordpress/Scripts/MYSQLDBMS/configure.sh b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_single_instance_wordpress/Scripts/MYSQLDBMS/configure.sh new file mode 100644 index 0000000..d4ef6b4 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_single_instance_wordpress/Scripts/MYSQLDBMS/configure.sh @@ -0,0 +1,5 @@ +#!/bin/sh +sed --regexp-extended "s/(port\s*=\s*)[0-9]*/\1$db_port/g" </etc/mysql/my.cnf >/tmp/my.cnf +mv -f /tmp/my.cnf /etc/mysql/my.cnf +/etc/init.d/mysql stop +/etc/init.d/mysql start
\ No newline at end of file diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_single_instance_wordpress/Scripts/MYSQLDBMS/install.sh b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_single_instance_wordpress/Scripts/MYSQLDBMS/install.sh new file mode 100644 index 0000000..38628b9 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_single_instance_wordpress/Scripts/MYSQLDBMS/install.sh @@ -0,0 +1,9 @@ +#!/bin/bash +#This script installs mysql server + +apt-get update + +debconf-set-selections <<< "mysql-server mysql-server/root_password password $db_root_password" +debconf-set-selections <<< "mysql-server mysql-server/root_password_again password $db_root_password" + +apt-get -y install --fix-missing mysql-server
\ No newline at end of file diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_single_instance_wordpress/Scripts/MYSQLDBMS/start.sh b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_single_instance_wordpress/Scripts/MYSQLDBMS/start.sh new file mode 100644 index 0000000..3378670 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_single_instance_wordpress/Scripts/MYSQLDBMS/start.sh @@ -0,0 +1,2 @@ +#!/bin/sh +/etc/init.d/mysql start
\ No newline at end of file diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_single_instance_wordpress/Scripts/MYSQLDatabase/configure.sh b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_single_instance_wordpress/Scripts/MYSQLDatabase/configure.sh new file mode 100644 index 0000000..092136a --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_single_instance_wordpress/Scripts/MYSQLDatabase/configure.sh @@ -0,0 +1,8 @@ +#!/bin/sh +cat << EOF | mysql -u root --password=$db_root_password +CREATE DATABASE $db_name; +GRANT ALL PRIVILEGES ON $db_name.* TO "$db_user"@"localhost" +IDENTIFIED BY "$db_password"; +FLUSH PRIVILEGES; +EXIT +EOF
\ No newline at end of file diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_single_instance_wordpress/Scripts/WebServer/install.sh b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_single_instance_wordpress/Scripts/WebServer/install.sh new file mode 100644 index 0000000..4ca9b4e --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_single_instance_wordpress/Scripts/WebServer/install.sh @@ -0,0 +1,5 @@ +#!/bin/sh +#This script installs apache web server + +apt-get update +apt-get install -y apache2
\ No newline at end of file diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_single_instance_wordpress/Scripts/WebServer/start.sh b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_single_instance_wordpress/Scripts/WebServer/start.sh new file mode 100644 index 0000000..e962ca5 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_single_instance_wordpress/Scripts/WebServer/start.sh @@ -0,0 +1,2 @@ +#!/bin/sh +service apache2 start
\ No newline at end of file diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_single_instance_wordpress/Scripts/WordPress/configure.sh b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_single_instance_wordpress/Scripts/WordPress/configure.sh new file mode 100644 index 0000000..5598b4f --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_single_instance_wordpress/Scripts/WordPress/configure.sh @@ -0,0 +1,4 @@ +#!/bin/sh +ln -s /usr/share/wordpress /var/www/html/wordpress +gzip -d /usr/share/doc/wordpress/examples/setup-mysql.gz +echo $wp_db_password | bash /usr/share/doc/wordpress/examples/setup-mysql -e $wp_db_name -u $wp_db_user localhost
\ No newline at end of file diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_single_instance_wordpress/Scripts/WordPress/install.sh b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_single_instance_wordpress/Scripts/WordPress/install.sh new file mode 100644 index 0000000..1320443 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_single_instance_wordpress/Scripts/WordPress/install.sh @@ -0,0 +1,5 @@ +#!/bin/sh +#This script installs wordpress + +apt-get update +apt-get install -y wordpress
\ No newline at end of file diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_single_instance_wordpress/TOSCA-Metadata/TOSCA.meta b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_single_instance_wordpress/TOSCA-Metadata/TOSCA.meta new file mode 100644 index 0000000..5208113 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/CSAR/tosca_single_instance_wordpress/TOSCA-Metadata/TOSCA.meta @@ -0,0 +1,5 @@ +TOSCA-Meta-File-Version: 1.0 +CSAR-Version: 1.1 +Created-By: OASIS TOSCA TC +Entry-Definitions: Definitions/tosca_single_instance_wordpress.yaml +Content-Type: application/vnd.oasis.tosca.definitions.yaml diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/custom_types/collectd.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/custom_types/collectd.yaml new file mode 100644 index 0000000..1ac0935 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/custom_types/collectd.yaml @@ -0,0 +1,13 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + collectd is a daemon which gathers statistics about the system it is running on. + +node_types: + tosca.nodes.SoftwareComponent.Collectd: + derived_from: tosca.nodes.SoftwareComponent + requirements: + - log_endpoint: + capability: tosca.capabilities.Endpoint + node: tosca.nodes.SoftwareComponent.Logstash + relationship: tosca.relationships.ConnectsTo
\ No newline at end of file diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/custom_types/compute_with_attribute_list.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/custom_types/compute_with_attribute_list.yaml new file mode 100644 index 0000000..3487433 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/custom_types/compute_with_attribute_list.yaml @@ -0,0 +1,13 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: Compute node type with a list attribute + +node_types: + tosca.nodes.ComputeWithAttrList: + derived_from: tosca.nodes.Compute + attributes: + attr_list: + type: map + entry_schema: + type: string + diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/custom_types/compute_with_prop.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/custom_types/compute_with_prop.yaml new file mode 100644 index 0000000..93a82af --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/custom_types/compute_with_prop.yaml @@ -0,0 +1,13 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + Compute node type with a parameter for the get property with host test + +node_types: + tosca.nodes.ComputeWithProp: + derived_from: tosca.nodes.Compute + properties: + test: + required: false + type: integer + diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/custom_types/custom_caps_def.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/custom_types/custom_caps_def.yaml new file mode 100644 index 0000000..337c38f --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/custom_types/custom_caps_def.yaml @@ -0,0 +1,22 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + Definition of a node with a capiblity and a parent capability + defined in an imported file + +capability_types: + + tosca.capabilities.SomeCap: + derived_from: tosca.capabilities.Root + + tosca.capabilities.SomeChildCap: + derived_from: tosca.capabilities.SomeCap + +node_types: + + tosca.nodes.SomeNode: + derived_from: tosca.nodes.Root + capabilities: + lrms: + type: tosca.capabilities.SomeChildCap + diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/custom_types/custom_relationship_type_defs.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/custom_types/custom_relationship_type_defs.yaml new file mode 100644 index 0000000..cf5c2b4 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/custom_types/custom_relationship_type_defs.yaml @@ -0,0 +1,23 @@ +node_types: + tosca.nodes.HACompute: + derived_from: tosca.nodes.Compute + capabilities: + high_availability: + type: tosca.capabilities.HA + requirements: + - high_availability: + capability: tosca.capabilities.HA + relationship: tosca.relationships.HA + node: tosca.nodes.HACompute + occurences: [ 0, 1 ] + +relationship_types: + tosca.relationships.HA: + derived_from: tosca.relationships.Root + valid_target_types: [ tosca.capabilities.HA ] + +capability_types: + tosca.capabilities.HA: + derived_from: tosca.capabilities.Root + valid_source_types: [ tosca.nodes.HACompute ] + diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/custom_types/db_with_list_param.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/custom_types/db_with_list_param.yaml new file mode 100644 index 0000000..57ce279 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/custom_types/db_with_list_param.yaml @@ -0,0 +1,10 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +node_types: + tosca.nodes.DatabaseWithListParam: + derived_from: tosca.nodes.Database + properties: + list_prop: + type: list + entry_schema: + type: integer diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/custom_types/elasticsearch.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/custom_types/elasticsearch.yaml new file mode 100644 index 0000000..b140a32 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/custom_types/elasticsearch.yaml @@ -0,0 +1,12 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + Elasticsearch is an open-source search engine built on top of Apache Lucene, + a full-text search-engine library. + +node_types: + tosca.nodes.SoftwareComponent.Elasticsearch: + derived_from: tosca.nodes.SoftwareComponent + capabilities: + search_endpoint: + type: tosca.capabilities.Endpoint diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/custom_types/imported_sample.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/custom_types/imported_sample.yaml new file mode 100644 index 0000000..70d0b0f --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/custom_types/imported_sample.yaml @@ -0,0 +1,34 @@ +tosca1_definitions_version: tosca_simple_yaml_1_0 +tosca_definitions_version: tosca_simple_yaml_1_10 + +descriptions: > + Pizza store app that allows you to explore the features provided by PayPal's REST APIs. + More detail can be found at https://github.com/paypal/rest-api-sample-app-nodejs/ + +node_typess: +node_types: + tosca.nodes.SoftwareComponent.Logstash: + derived_from: tosca.nodes.SoftwareComponent + requirements: + - search_endpoint: + capability: tosca.capabilities.Endpoint + node: tosca.nodes.SoftwareComponent.Elasticsearch + relationship: + type: tosca.relationships.ConnectsTo + interfaces: + Configure: + pre_configure_source: + inputs: + elasticsearch_ip: + type: string + capabilities1: + log_endpoint: + type: tosca.capabilities.Endpoint +policy_types1: +policy_types: + mycompany.mytypes.myScalingPolicy: + derived1_from: tosca.policies.Scaling + metadata: + type: map + entry_schema: + type: string diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/custom_types/kibana.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/custom_types/kibana.yaml new file mode 100644 index 0000000..5701e69 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/custom_types/kibana.yaml @@ -0,0 +1,14 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + Kibana is an open source analytics and visualization platform designed to work with Elasticsearch. + You use Kibana to search, view, and interact with data stored in Elasticsearch. + +node_types: + tosca.nodes.SoftwareComponent.Kibana: + derived_from: tosca.nodes.SoftwareComponent + requirements: + - search_endpoint: + capability: tosca.capabilities.Endpoint + node: tosca.nodes.SoftwareComponent.Elasticsearch + relationship: tosca.relationships.ConnectsTo diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/custom_types/logstash.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/custom_types/logstash.yaml new file mode 100644 index 0000000..cf60521 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/custom_types/logstash.yaml @@ -0,0 +1,25 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + Logstash is a tool for receiving, processing and outputting logs. All kinds + of logs. System logs, webserver logs, error logs, application logs, and just + about anything you can throw at it. + +node_types: + tosca.nodes.SoftwareComponent.Logstash: + derived_from: tosca.nodes.SoftwareComponent + requirements: + - search_endpoint: + capability: tosca.capabilities.Endpoint + node: tosca.nodes.SoftwareComponent.Elasticsearch + relationship: + type: tosca.relationships.ConnectsTo + interfaces: + Configure: + pre_configure_source: + inputs: + elasticsearch_ip: + type: string + capabilities: + log_endpoint: + type: tosca.capabilities.Endpoint diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/custom_types/nested_rsyslog.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/custom_types/nested_rsyslog.yaml new file mode 100644 index 0000000..8c04171 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/custom_types/nested_rsyslog.yaml @@ -0,0 +1,17 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + RSYSLOG is the Rocket-fast SYStem for LOG processing. + +imports: + - test_import: + file: custom_types/logstash.yaml + +node_types: + Rsyslog: + derived_from: tosca.nodes.SoftwareComponent + requirements: + - log_endpoint: + capability: tosca.capabilities.Endpoint + node: tosca.nodes.SoftwareComponent.Logstash + relationship: tosca.relationships.ConnectsTo diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/custom_types/nested_test_wordpress.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/custom_types/nested_test_wordpress.yaml new file mode 100644 index 0000000..4df277d --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/custom_types/nested_test_wordpress.yaml @@ -0,0 +1,32 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 +imports: + - test_prefix_defs: + file: custom_types/nested_rsyslog.yaml + namespace_prefix: test_namespace_prefix + - test_second_time_with_another_prefix: + file: custom_types/nested_rsyslog.yaml + namespace_prefix: test_2nd_namespace_prefix + +node_types: + tosca.nodes.SoftwareComponent.Rsyslog.TestRsyslogType: + derived_from: test_namespace_prefix.Rsyslog + + Test2ndRsyslogType: + derived_from: test_2nd_namespace_prefix.Rsyslog + + tosca.nodes.WebApplication.WordPress: + derived_from: tosca.nodes.WebApplication + requirements: + - database_endpoint: + capability: tosca.capabilities.Endpoint.Database + node: tosca.nodes.Database + relationship: tosca.relationships.ConnectsTo + interfaces: + Standard: + inputs: + wp_db_name: + type: string + wp_db_user: + type: string + wp_db_password: + type: string diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/custom_types/node_with_cap.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/custom_types/node_with_cap.yaml new file mode 100644 index 0000000..11e1b51 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/custom_types/node_with_cap.yaml @@ -0,0 +1,30 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + Node type that has a requirement of a capability with a defined value + +node_types: + tosca.capabilities.SomeCap: + derived_from: tosca.capabilities.Root + properties: + type: + type: string + required: true + default: someval + constraints: + - equal: someval + + tosca.nodes.SomeNode: + derived_from: tosca.nodes.Root + requirements: + - some_req: + capability: tosca.capabilities.SomeCap + node: tosca.nodes.NodeWithCap + relationship: tosca.relationships.HostedOn + + tosca.nodes.NodeWithCap: + derived_from: tosca.nodes.Root + capabilities: + some_req: + type: tosca.capabilities.SomeCap + diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/custom_types/paypalpizzastore_nodejs_app.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/custom_types/paypalpizzastore_nodejs_app.yaml new file mode 100644 index 0000000..cdabeae --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/custom_types/paypalpizzastore_nodejs_app.yaml @@ -0,0 +1,29 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + Pizza store app that allows you to explore the features provided by PayPal's REST APIs. + More detail can be found at https://github.com/paypal/rest-api-sample-app-nodejs/ + +node_types: + tosca.nodes.WebApplication.PayPalPizzaStore: + derived_from: tosca.nodes.WebApplication + properties: + github_url: + required: false + type: string + description: location of the application on the github. + default: https://github.com/sample.git + requirements: + #WebApplication inherits Computer, so host implied. + - database_connection: + capability: tosca.capabilities.Endpoint.Database + node: tosca.nodes.Database + relationship: tosca.relationships.ConnectsTo + interfaces: + Standard: + configure: + inputs: + github_url: + type: string + mongodb_ip: + type: string diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/custom_types/rsyslog.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/custom_types/rsyslog.yaml new file mode 100644 index 0000000..4614ee7 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/custom_types/rsyslog.yaml @@ -0,0 +1,13 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + RSYSLOG is the Rocket-fast SYStem for LOG processing. + +node_types: + tosca.nodes.SoftwareComponent.Rsyslog: + derived_from: tosca.nodes.SoftwareComponent + requirements: + - log_endpoint: + capability: tosca.capabilities.Endpoint + node: tosca.nodes.SoftwareComponent.Logstash + relationship: tosca.relationships.ConnectsTo diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/custom_types/wordpress.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/custom_types/wordpress.yaml new file mode 100644 index 0000000..5899ed9 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/custom_types/wordpress.yaml @@ -0,0 +1,19 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +node_types: + tosca.nodes.WebApplication.WordPress: + derived_from: tosca.nodes.WebApplication + requirements: + - database_endpoint: + capability: tosca.capabilities.Endpoint.Database + node: tosca.nodes.Database + relationship: tosca.relationships.ConnectsTo + interfaces: + Standard: + inputs: + wp_db_name: + type: string + wp_db_user: + type: string + wp_db_password: + type: string diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/datatypes/custom_datatype_def.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/datatypes/custom_datatype_def.yaml new file mode 100644 index 0000000..b1fb402 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/datatypes/custom_datatype_def.yaml @@ -0,0 +1,53 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + Custom type and node definition used to test custom datatypes. + +node_types: + tosca.nodes.my.SomeNode: + derived_from: tosca.nodes.Root + properties: + people: + type: tosca.my.datatypes.People + +data_types: + tosca.my.datatypes.PeopleBase: + properties: + name: + type: string + required: true + constraints: + - min_length: 2 + gender: + type: string + required: false + default: unknown + + tosca.my.datatypes.People: + derived_from: tosca.my.datatypes.PeopleBase + properties: + addresses: + type: map + required: false + entry_schema: + type: string + contacts: + type: list + required: false + entry_schema: + type: tosca.my.datatypes.ContactInfo + + tosca.my.datatypes.ContactInfo: + description: simple contact information + properties: + contact_name: + type: string + required: true + constraints: + - min_length: 2 + contact_email: + type: string + required: false + contact_phone: + type: string + required: false diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/datatypes/test_custom_datatypes_in_current_template.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/datatypes/test_custom_datatypes_in_current_template.yaml new file mode 100644 index 0000000..befa198 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/datatypes/test_custom_datatypes_in_current_template.yaml @@ -0,0 +1,70 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + TOSCA templates used to test custom datatypes. + +node_types: + tosca.nodes.my.SomeNode: + derived_from: tosca.nodes.Root + properties: + people: + type: tosca.my.datatypes.People + +data_types: + tosca.my.datatypes.PeopleBase: + properties: + name: + type: string + required: true + constraints: + - min_length: 2 + gender: + type: string + required: false + default: unknown + + tosca.my.datatypes.People: + derived_from: tosca.my.datatypes.PeopleBase + properties: + addresses: + type: map + required: false + entry_schema: + type: string + contacts: + type: list + required: false + entry_schema: + type: tosca.my.datatypes.ContactInfo + + tosca.my.datatypes.ContactInfo: + description: simple contact information + properties: + contact_name: + type: string + required: true + constraints: + - min_length: 2 + contact_email: + type: string + required: false + contact_phone: + type: string + required: false + +topology_template: + node_templates: + positive: + type: tosca.nodes.my.SomeNode + properties: + people: + name: Mike + gender: male + addresses: {Home: 1 foo street, Office: 9 bar avenue} + contacts: + - {contact_name: Tom, + contact_email: tom@email.com, + contact_phone: '123456789'} + - {contact_name: Jerry, + contact_email: jerry@email.com, + contact_phone: '321654987'} diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/datatypes/test_custom_datatypes_nested_datatype_error.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/datatypes/test_custom_datatypes_nested_datatype_error.yaml new file mode 100644 index 0000000..b28f499 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/datatypes/test_custom_datatypes_nested_datatype_error.yaml @@ -0,0 +1,25 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + TOSCA templates used to test custom datatypes. + +imports: + - custom_datatype_def.yaml + +topology_template: + node_templates: + # 123456789 is not a string + error in nested datatype: + type: tosca.nodes.my.SomeNode + properties: + people: + name: Mike + gender: male + addresses: {Home: 1 foo street, Office: 9 bar avenue} + contacts: + - {contact_name: Tom, + contact_email: tom@email.com, + contact_phone: 123456789} + - {contact_name: Jerry, + contact_email: jerry@email.com, + contact_phone: '321654987'} diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/datatypes/test_custom_datatypes_positive.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/datatypes/test_custom_datatypes_positive.yaml new file mode 100644 index 0000000..f1762f4 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/datatypes/test_custom_datatypes_positive.yaml @@ -0,0 +1,24 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + TOSCA templates used to test custom datatypes. + +imports: + - custom_datatype_def.yaml + +topology_template: + node_templates: + positive: + type: tosca.nodes.my.SomeNode + properties: + people: + name: Mike + gender: male + addresses: {Home: 1 foo street, Office: 9 bar avenue} + contacts: + - {contact_name: Tom, + contact_email: tom@email.com, + contact_phone: '123456789'} + - {contact_name: Jerry, + contact_email: jerry@email.com, + contact_phone: '321654987'} diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/datatypes/test_custom_datatypes_value_error.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/datatypes/test_custom_datatypes_value_error.yaml new file mode 100644 index 0000000..31cf681 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/datatypes/test_custom_datatypes_value_error.yaml @@ -0,0 +1,18 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + TOSCA templates used to test custom datatypes. + +imports: + - custom_datatype_def.yaml + +topology_template: + node_templates: + # addresses is not a map + error in field value: + type: tosca.nodes.my.SomeNode + properties: + people: + name: Mike + gender: male + addresses: [1 foo street, 9 bar avenue] diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/functions/test_capabilties_inheritance.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/functions/test_capabilties_inheritance.yaml new file mode 100644 index 0000000..f0bec84 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/functions/test_capabilties_inheritance.yaml @@ -0,0 +1,25 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: TOSCA simple profile to test the attribute inheritance + +imports: + - ../custom_types/node_with_cap.yaml + +topology_template: + + node_templates: + + some_node: + type: tosca.nodes.SomeNode + requirements: + - some_req: node_cap + interfaces: + Standard: + configure: + implementation: some_script.sh + inputs: + some_input: { get_property: [ SELF, some_req, type ] } + + node_cap: + type: tosca.nodes.NodeWithCap + diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/functions/test_concat.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/functions/test_concat.yaml new file mode 100644 index 0000000..22fcfb4 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/functions/test_concat.yaml @@ -0,0 +1,30 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: Template for deploying a single server with concat function. + +topology_template: + node_templates: + server: + type: tosca.nodes.Compute + capabilities: + # Host container properties + host: + properties: + num_cpus: 2 + disk_size: 10 GB + mem_size: 512 MB + # Guest Operating System properties + os: + properties: + # host Operating System image properties + architecture: x86_64 + type: Linux + distribution: RHEL + version: 6.5 + outputs: + url: + description: Concatenate the URL for a server from template values. + value: { concat: [ 'http://', + get_attribute: [ server, public_address ], + ':' , + get_attribute: [ server, port ] ] }
\ No newline at end of file diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/functions/test_concat_invalid.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/functions/test_concat_invalid.yaml new file mode 100644 index 0000000..7c7b0aa --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/functions/test_concat_invalid.yaml @@ -0,0 +1,9 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: Template for deploying a single server with invalid concat function. + +topology_template: + outputs: + invalid_concat_syntax: + description: test concat with invalid syntax. + value: { concat: []}
\ No newline at end of file diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/functions/test_get_attribute_host_keyword.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/functions/test_get_attribute_host_keyword.yaml new file mode 100644 index 0000000..90ffbe2 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/functions/test_get_attribute_host_keyword.yaml @@ -0,0 +1,33 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + TOSCA template for testing get_attribute with HOST keyword. + +topology_template: + node_templates: + server: + type: tosca.nodes.Compute + capabilities: + host: + properties: + num_cpus: 2 + dbms: + type: tosca.nodes.DBMS + requirements: + - host: server + interfaces: + Standard: + configure: + implementation: configure.sh + inputs: + ip_address: { get_attribute: [ HOST, private_address ] } + database: + type: tosca.nodes.Database + requirements: + - host: dbms + interfaces: + Standard: + configure: + implementation: configure.sh + inputs: + ip_address: { get_attribute: [ HOST, private_address ] } diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/functions/test_get_attribute_host_not_found.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/functions/test_get_attribute_host_not_found.yaml new file mode 100644 index 0000000..69679ff --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/functions/test_get_attribute_host_not_found.yaml @@ -0,0 +1,20 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + TOSCA template for testing get_attribute with HOST keyword. + +topology_template: + node_templates: + server: + type: tosca.nodes.Compute + capabilities: + host: + properties: + num_cpus: 2 + interfaces: + Standard: + configure: + implementation: configure.sh + inputs: + ip_address: { get_attribute: [ HOST, private_address ] } + diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/functions/test_get_attribute_illegal_host_in_outputs.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/functions/test_get_attribute_illegal_host_in_outputs.yaml new file mode 100644 index 0000000..6c7d9bb --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/functions/test_get_attribute_illegal_host_in_outputs.yaml @@ -0,0 +1,17 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + TOSCA template for testing get_attribute with HOST keyword. + +topology_template: + node_templates: + server: + type: tosca.nodes.Compute + capabilities: + host: + properties: + num_cpus: 2 + + outputs: + ip_address: + value: { get_attribute: [ HOST, private_address ] } diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/functions/test_get_attribute_source_target_keywords.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/functions/test_get_attribute_source_target_keywords.yaml new file mode 100644 index 0000000..047387f --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/functions/test_get_attribute_source_target_keywords.yaml @@ -0,0 +1,30 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + TOSCA template for testing get_attribute with TARGET ans SOURCE keywords. + +topology_template: + + node_templates: + + mysql: + type: tosca.nodes.DBMS + properties: + root_password: rootpw + port: 3306 + requirements: + - host: + node: db_server + relationship: + type: tosca.relationships.HostedOn + interfaces: + Configure: + pre_configure_source: + implementation: some_script.sh + inputs: + target_test: { get_attribute: [ TARGET, public_address ] } + source_port: { get_attribute: [ SOURCE, tosca_name ] } + + db_server: + type: tosca.nodes.Compute + diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/functions/test_get_attribute_unknown_attribute_name.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/functions/test_get_attribute_unknown_attribute_name.yaml new file mode 100644 index 0000000..0570c7c --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/functions/test_get_attribute_unknown_attribute_name.yaml @@ -0,0 +1,28 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + Tosca template for testing unknown attribute name in get_attribute + function. + +topology_template: + inputs: + image_id: + type: string + + node_templates: + server: + type: tosca.nodes.Compute + capabilities: + host: + properties: + num_cpus: 2 + interfaces: + Standard: + configure: + implementation: start_server.sh + inputs: + image_id: { get_input: image_id } + + outputs: + ip_address: + value: { get_attribute: [ server, unknown_attribute ] } diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/functions/test_get_attribute_unknown_node_template_name.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/functions/test_get_attribute_unknown_node_template_name.yaml new file mode 100644 index 0000000..923305c --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/functions/test_get_attribute_unknown_node_template_name.yaml @@ -0,0 +1,28 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + Tosca template for testing unknown node template name in get_attribute + function. + +topology_template: + inputs: + image_id: + type: string + + node_templates: + server: + type: tosca.nodes.Compute + capabilities: + host: + properties: + num_cpus: 2 + interfaces: + Standard: + configure: + implementation: start_server.sh + inputs: + image_id: { get_input: image_id } + + outputs: + ip_address: + value: { get_attribute: [ unknown_node_template, private_address ] } diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/functions/test_get_attribute_with_index.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/functions/test_get_attribute_with_index.yaml new file mode 100644 index 0000000..5766490 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/functions/test_get_attribute_with_index.yaml @@ -0,0 +1,19 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + TOSCA template for testing get_attribute with a list attribute and an index + +imports: + - ../custom_types/compute_with_attribute_list.yaml + +topology_template: + node_templates: + server: + type: tosca.nodes.ComputeWithAttrList + interfaces: + Standard: + configure: + implementation: configure.sh + inputs: + ip_address: { get_attribute: [ SELF, attr_list, 0 ] } + diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/functions/test_get_attribute_with_index_error.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/functions/test_get_attribute_with_index_error.yaml new file mode 100644 index 0000000..88a2721 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/functions/test_get_attribute_with_index_error.yaml @@ -0,0 +1,19 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + TOSCA template for testing get_attribute with a list attribute and an index + +imports: + - ../custom_types/compute_with_attribute_list.yaml + +topology_template: + node_templates: + server: + type: tosca.nodes.ComputeWithAttrList + interfaces: + Standard: + configure: + implementation: configure.sh + inputs: + ip_address: { get_attribute: [ SELF, private_address, 0 ] } + diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/functions/test_get_property_source_target_keywords.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/functions/test_get_property_source_target_keywords.yaml new file mode 100644 index 0000000..c460257 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/functions/test_get_property_source_target_keywords.yaml @@ -0,0 +1,35 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + TOSCA template for testing get_property with TARGET ans SOURCE keywords. + +imports: + - ../custom_types/compute_with_prop.yaml + +topology_template: + + node_templates: + + mysql: + type: tosca.nodes.DBMS + properties: + root_password: rootpw + port: 3306 + requirements: + - host: + node: db_server + relationship: + type: tosca.relationships.HostedOn + interfaces: + Configure: + pre_configure_source: + implementation: some_script.sh + inputs: + target_test: { get_property: [ TARGET, test ] } + source_port: { get_property: [ SOURCE, port ] } + + db_server: + type: tosca.nodes.ComputeWithProp + properties: + test: 1 + diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/functions/test_get_property_with_host.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/functions/test_get_property_with_host.yaml new file mode 100644 index 0000000..1ca69ca --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/functions/test_get_property_with_host.yaml @@ -0,0 +1,65 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + TOSCA simple profile to test the get property function with HOST parameter + +imports: + - ../custom_types/compute_with_prop.yaml + +topology_template: + inputs: + db_name: + type: string + description: The name of the database. + default: wordpress + db_user: + type: string + description: The user name of the DB user. + default: wp_user + db_pwd: + type: string + description: The WordPress database admin account password. + default: wp_pass + db_root_pwd: + type: string + description: Root password for MySQL. + db_port: + type: PortDef + description: Port for the MySQL database. + default: 3306 + + node_templates: + + mysql_database: + type: tosca.nodes.Database + properties: + name: { get_input: db_name } + user: { get_input: db_user } + password: { get_input: db_pwd } + capabilities: + database_endpoint: + properties: + port: { get_input: db_port } + requirements: + - host: mysql_dbms + interfaces: + Standard: + configure: + implementation: mysql/mysql_database_configure.sh + inputs: + db_port: { get_property: [ HOST, port ] } + test: { get_property: [ HOST, test ] } + + mysql_dbms: + type: tosca.nodes.DBMS + properties: + root_password: { get_input: db_root_pwd } + port: { get_input: db_port } + requirements: + - host: server + + server: + type: tosca.nodes.ComputeWithProp + properties: + test: 1 + diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/functions/test_invalid_function_signature.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/functions/test_invalid_function_signature.yaml new file mode 100644 index 0000000..dde8427 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/functions/test_invalid_function_signature.yaml @@ -0,0 +1,34 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + TOSCA simple profile template to test invalid get_input function. + +topology_template: + inputs: + cpus: + type: integer + description: Number of CPUs for the server. + constraints: + - valid_values: [ 1, 2, 4, 8 ] + + node_templates: + server: + type: tosca.nodes.Compute + capabilities: + host: + properties: + # compute properties (flavor) + disk_size: 10 GB + num_cpus: { get_input: [cpus, cpus] } + mem_size: 4096 MB + os: + properties: + architecture: x86_64 + type: Linux + distribution: Fedora + version: 18.0 + + outputs: + server_address: + description: IP address of server instance. + value: { get_attribute: [server, private_address] } diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/functions/test_unknown_capability_property.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/functions/test_unknown_capability_property.yaml new file mode 100644 index 0000000..4a92530 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/functions/test_unknown_capability_property.yaml @@ -0,0 +1,36 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + Tosca template for testing an unknown capability property. + +topology_template: + node_templates: + server: + type: tosca.nodes.Compute + capabilities: + host: + properties: + num_cpus: 2 + dbms: + type: tosca.nodes.DBMS + properties: + root_password: 1234 + port: 3672 + database: + type: tosca.nodes.Database + properties: + name: my_db + user: abcd + password: 1234 + capabilities: + database_endpoint: + properties: + port: { get_property: [ dbms, port ] } + requirements: + - host: dbms + interfaces: + Standard: + configure: + implementation: database_configure.sh + inputs: + db_port: { get_property: [ SELF, database_endpoint, unknown ] } diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/functions/test_unknown_input_in_interface.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/functions/test_unknown_input_in_interface.yaml new file mode 100644 index 0000000..cbfb391 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/functions/test_unknown_input_in_interface.yaml @@ -0,0 +1,20 @@ + +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + Tosca template for testing an unknown input. + +topology_template: + node_templates: + server: + type: tosca.nodes.Compute + capabilities: + host: + properties: + num_cpus: 2 + interfaces: + Standard: + configure: + implementation: start_server.sh + inputs: + image_id: { get_input: image_id } diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/functions/test_unknown_input_in_property.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/functions/test_unknown_input_in_property.yaml new file mode 100644 index 0000000..9ba7ee5 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/functions/test_unknown_input_in_property.yaml @@ -0,0 +1,13 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + Tosca template for testing an unknown input. + +topology_template: + node_templates: + obj_store_server: + type: tosca.nodes.ObjectStorage + properties: + name: { get_input: objectstore_name } + size: 1024 MB + maxsize: 1 GB diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/functions/tosca_nested_property_names_indexes.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/functions/tosca_nested_property_names_indexes.yaml new file mode 100644 index 0000000..8fb7b96 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/functions/tosca_nested_property_names_indexes.yaml @@ -0,0 +1,47 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: TOSCA simple profile with nested property names or indexes. + +imports: + - ../custom_types/wordpress.yaml + - ../custom_types/db_with_list_param.yaml + +topology_template: + + node_templates: + + wordpress: + type: tosca.nodes.WebApplication.WordPress + requirements: + - host: server + - database_endpoint: mysql_database + interfaces: + Standard: + configure: + implementation: wordpress/wordpress_configure.sh + inputs: + wp_endpoint_protocol: { get_property: [ SELF, database_endpoint, ports, user_port, protocol ] } + wp_list_prop: { get_property: [ mysql_database, list_prop, 2 ] } + + mysql_database: + type: tosca.nodes.DatabaseWithListParam + properties: + list_prop: [1,2,3] + capabilities: + database_endpoint: + properties: + ports: + user_port: + protocol: tcp + target: 50000 + source: 9000 + requirements: + - host: mysql_dbms + + mysql_dbms: + type: tosca.nodes.DBMS + requirements: + - host: server + + server: + type: tosca.nodes.Compute diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/groups/definitions.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/groups/definitions.yaml new file mode 100644 index 0000000..40c1d8b --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/groups/definitions.yaml @@ -0,0 +1,10 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +group_types: + mycompany.mytypes.groups.placement: + description: My company's group type for placing nodes of type Compute + members: [ tosca.nodes.Compute ] + metadata: + type: map + entry_schema: + type: string diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/groups/tosca_group_template.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/groups/tosca_group_template.yaml new file mode 100644 index 0000000..0e94240 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/groups/tosca_group_template.yaml @@ -0,0 +1,54 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + Service template with topology_template, act as a nested system inside another system. + +imports: + - definitions.yaml + +topology_template: + description: Template of a database including its hosting stack. + + inputs: + mq_server_ip: + type: string + description: IP address of the message queuing server to receive messages from. + receiver_port: + type: string + description: Port to be used for receiving messages. + my_cpus: + type: integer + description: Number of CPUs for the server. + constraints: + - valid_values: [ 1, 2, 4, 8 ] + + node_templates: + websrv: + type: tosca.nodes.WebServer + capabilities: + data_endpoint: + properties: + port_name: { get_input: receiver_port } + requirements: + - host: + node: server + + server: + type: tosca.nodes.Compute + capabilities: + host: + properties: + disk_size: 10 GB + num_cpus: { get_input: my_cpus } + mem_size: 4096 MB + os: + properties: + architecture: x86_64 + type: Linux + distribution: Ubuntu + version: 14.04 + + groups: + webserver_group: + type: mycompany.mytypes.groups.placement + members: [ websrv, server ] diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/policies/custom_definitions.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/policies/custom_definitions.yaml new file mode 100644 index 0000000..7f15ade --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/policies/custom_definitions.yaml @@ -0,0 +1,10 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +policy_types: + mycompany.mytypes.myScalingPolicy: + derived_from: tosca.policies.Scaling + metadata: + type: map + entry_schema: + type: string + diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/policies/tosca_policy_template.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/policies/tosca_policy_template.yaml new file mode 100644 index 0000000..92bebe5 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/policies/tosca_policy_template.yaml @@ -0,0 +1,85 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + Template for deploying servers based on policies. + +imports: + - custom_definitions.yaml + +topology_template: + node_templates: + my_server_1: + type: tosca.nodes.Compute + capabilities: + # Host container properties + host: + properties: + num_cpus: 2 + disk_size: 10 GB + mem_size: 512 MB + # Guest Operating System properties + os: + properties: + # host Operating System image properties + architecture: x86_64 + type: Linux + distribution: RHEL + version: 6.5 + + my_server_2: + type: tosca.nodes.Compute + capabilities: + host: + properties: + disk_size: 10 GB + num_cpus: 2 + mem_size: 4096 MB + os: + properties: + architecture: x86_64 + type: Linux + distribution: Ubuntu + version: 14.04 + + groups: + webserver_group: + members: [ my_server_1, my_server_2 ] + type: tosca.groups.Root + metadata: { user1: 1008, user2: 1002 } + + + policies: + - my_compute_placement_policy: + type: tosca.policies.Placement + description: Apply placement policy to servers + metadata: { user1: 1001, user2: 1002 } + targets: [ my_server_1, my_server_2 ] + triggers: + resize_compute: + description: trigger + event_type: tosca.events.resource.utilization + schedule: + start_time: "2015-05-07T07:00:00Z" + end_time: "2015-06-07T07:00:00Z" + target_filter: + node: master-container + requirement: host + capability: Container + condition: + constraint: utilization greater_than 50% + period: 60 + evaluations: 1 + method: average + action: + resize: # Operation name + inputs: + strategy: LEAST_USED + implementation: Senlin.webhook() + - my_groups_placement: + type: mycompany.mytypes.myScalingPolicy + targets: [ webserver_group ] + description: my company scaling policy + metadata: + user1: 1001 + user2: 1003 + diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/test_attributes_inheritance.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/test_attributes_inheritance.yaml new file mode 100644 index 0000000..0649c11 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/test_attributes_inheritance.yaml @@ -0,0 +1,28 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: TOSCA simple profile to test the attribute inheritance + +imports: + - custom_types/compute_with_prop.yaml + +topology_template: + + node_templates: + + server: + type: tosca.nodes.ComputeWithProp + properties: + test: yes + capabilities: + host: + properties: + num_cpus: 1 + mem_size: 1 GB + os: + properties: + type: linux + + outputs: + server_ip: + value: { get_attribute: [ server, public_address ] } + diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/test_available_rel_tpls.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/test_available_rel_tpls.yaml new file mode 100644 index 0000000..e8d9045 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/test_available_rel_tpls.yaml @@ -0,0 +1,23 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: TOSCA test for bug 1527214 + +topology_template: + + node_templates: + + test_db: + type: tosca.nodes.Database + requirements: + - host: + node: mysql + + mysql: + type: tosca.nodes.DBMS + requirements: + - host: + node: db_server + + db_server: + type: tosca.nodes.Compute + diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/test_custom_caps_def.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/test_custom_caps_def.yaml new file mode 100644 index 0000000..0b0984a --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/test_custom_caps_def.yaml @@ -0,0 +1,13 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: TOSCA simple profile to test a custom defined capability + +imports: + - custom_types/custom_caps_def.yaml + +topology_template: + + node_templates: + + server: + type: tosca.nodes.SomeNode diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/test_custom_relationships.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/test_custom_relationships.yaml new file mode 100644 index 0000000..9c8171d --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/test_custom_relationships.yaml @@ -0,0 +1,48 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: Test template for deploying a single server with predefined properties and custom relationship types + +imports: + - custom_types/custom_relationship_type_defs.yaml + +topology_template: + node_templates: + server1: + type: tosca.nodes.HACompute + capabilities: + # Host container properties + host: + properties: + num_cpus: 2 + disk_size: 10 GB + mem_size: 512 MB + # Guest Operating System properties + os: + properties: + # host Operating System image properties + architecture: x86_64 + type: Linux + distribution: RHEL + version: 6.5 + requirements: + - high_availability: server2 + + server2: + type: tosca.nodes.HACompute + capabilities: + # Host container properties + host: + properties: + num_cpus: 2 + disk_size: 10 GB + mem_size: 512 MB + # Guest Operating System properties + os: + properties: + # host Operating System image properties + architecture: x86_64 + type: Linux + distribution: RHEL + version: 6.5 + requirements: + - high_availability: server1 diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/test_instance_nested_imports.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/test_instance_nested_imports.yaml new file mode 100644 index 0000000..6aa9307 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/test_instance_nested_imports.yaml @@ -0,0 +1,22 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + TOSCA simple profile with wordpress, web server and mysql on the same server. + +imports: + - wordpress: custom_types/nested_test_wordpress.yaml + +topology_template: + + node_templates: + wordpress: + type: tosca.nodes.WebApplication.WordPress + + testrsyslogtype: + type: tosca.nodes.SoftwareComponent.Rsyslog.TestRsyslogType + + rsyslog: + type: Test2ndRsyslogType + + logstash: + type: tosca.nodes.SoftwareComponent.Logstash diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/test_invalid_section_names.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/test_invalid_section_names.yaml new file mode 100644 index 0000000..6241585 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/test_invalid_section_names.yaml @@ -0,0 +1,25 @@ +tosca_definitions_versions: tosca_simple_yaml_1_0 + +descriptions: > + TOSCA profile with invalid top-level section names. + +import: + - imported.yaml + +topology_templates: + + node_templates: + server: + type: tosca.nodes.Compute + capabilities: + host: + properties: + disk_size: 10 GB + num_cpus: 1 + mem_size: 4096 MB + os: + properties: + architecture: x86_64 + type: Linux + distribution: Ubuntu + version: 14.04 diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/test_invalid_template_version.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/test_invalid_template_version.yaml new file mode 100644 index 0000000..86dce79 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/test_invalid_template_version.yaml @@ -0,0 +1,14 @@ +tosca_definitions_version: tosca_xyz + +description: > + Test template with an invalid template version. + +topology_template: + node_templates: + server: + type: tosca.nodes.Compute + capabilities: + host: + properties: + num_cpus: 2 + diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/test_multiple_validation_errors.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/test_multiple_validation_errors.yaml new file mode 100644 index 0000000..ccae4eb --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/test_multiple_validation_errors.yaml @@ -0,0 +1,125 @@ +tosca_definitions_version: tosca_simple_yaml_1 + +description: > + TOSCA simple profile with wordpress, web server and mysql on the same server. + +imports: + - custom_types/not_there.yaml + +topology_template: + inputs: + cpus: + type: integer + description: Number of CPUs for the server. + constraints: + - valid_values: [ 1, 2, 4, 8 ] + default: 1 + db_name: + type: string + description: The name of the database. + default: wordpress + db_user: + type: string + description: The user name of the DB user. + default: wp_user + db_pwd: + type: string + description: The WordPress database admin account password. + default: wp_pass + db_root_pwd: + type: string + description: Root password for MySQL. + db_port: + type: PortDef + description: Port for the MySQL database. + default: 3306 + + node_templates: + wordpress: + type: tosca.nodes.WebApplication.WordPress + requirement: + - host: webserver + - database_endpoint: mysql_database + interfaces: + Standard: + create: wordpress/wordpress_install.sh + configure: + implementation: wordpress/wordpress_configure.sh + inputs: + wp_db_name: { get_property: [ mysql_database, name ] } + wp_db_user: { get_property: [ mysql_database, user ] } + wp_db_password: { get_property: [ mysql_database, password ] } + + mysql_database: + type: tosca.nodes.Database + properties: + name: { get_input: db_name } + user: { get_input: db_user } + password: { get_input: db_pwd } + capabilities: + database_endpoint: + properties: + port: { get_input: db_port } + requirements: + - host: mysql_dbms + interfaces: + Standard: + configure: + implementation: mysql/mysql_database_configure.sh + inputs: + db_name: { get_property: [ SELF, name ] } + db_user: { get_property: [ SELF, user ] } + db_password: { get_property: [ SELF, passwords ] } + db_root_password: { get_property: [ mysql_dbms, root_password ] } + + mysql_dbms: + type1: tosca.nodes.DBMS + properties: + root_password: { get_input: db_root_pwd } + port: { get_input: db_port } + requirements: + - host: server + interfaces: + Standard: + create: + implementation: mysql/mysql_dbms_install.sh + inputs: + db_root_password: { get_property: [ mysql_dbms, root_password ] } + start: mysql/mysql_dbms_start.sh + configure: + implementation: mysql/mysql_dbms_configure.sh + inputs: + db_port: { get_property: [ mysql_dbms, port ] } + + webserver: + type: tosca.nodes.WebServer + requirements: + - host: server1 + - database_endpoint: + node: webserver + relationship: + type1: tosca.relationships.ConnectsTo + interfaces: + Standard: + create: webserver/webserver_install.sh + start: webserver/webserver_start.sh + + server: + type: tosca.nodes.Compute + capabilities: + host: + properties: + disk_size: 10 GB + num_cpus: { get_input: cpus } + mem_size: 4096 MB + os: + properties: + architecture: x86_64 + type: Linux + distribution: Ubuntu + version: 14.04 + + outputs: + website_url: + description: URL for Wordpress wiki. + value: { get_attribute: [server, private_address] } diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/test_no_inputs_in_template.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/test_no_inputs_in_template.yaml new file mode 100644 index 0000000..0b9da4c --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/test_no_inputs_in_template.yaml @@ -0,0 +1,17 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + Tosca template for testing a template with no inputs. + +metadata: test + +topology_template: + node_templates: + server: + type: tosca.nodes.Compute + capabilities: + host: + properties: + num_cpus: 2 + + outputs: diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/test_no_outputs_in_template.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/test_no_outputs_in_template.yaml new file mode 100644 index 0000000..51d42ff --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/test_no_outputs_in_template.yaml @@ -0,0 +1,15 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + Tosca template for testing a template with no outputs. + +topology_template: + inputs: + + node_templates: + server: + type: tosca.nodes.Compute + capabilities: + host: + properties: + num_cpus: 2 diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/test_node_filter.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/test_node_filter.yaml new file mode 100644 index 0000000..3dd8e26 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/test_node_filter.yaml @@ -0,0 +1,18 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: Template with requirements against hosting infrastructure. + +topology_template: + + node_templates: + test: + type: tosca.nodes.DBMS + requirements: + - host: + node_filter: + capabilities: + - host: + properties: + - num_cpus: { in_range: [ 1, 4 ] } + - mem_size: { greater_or_equal: 2 GB } + diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/test_repositories_definition.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/test_repositories_definition.yaml new file mode 100644 index 0000000..2145d8f --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/test_repositories_definition.yaml @@ -0,0 +1,23 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +repositories: + some_repository: + description: Some repo + url: https://raw.githubusercontent.com/openstack/tosca-parser/master/toscaparser/tests/data/custom_types/ + namespace_uri: http://docs.oasis-open.org/tosca/ns/simple/yaml/1.0a + namespace_prefix: oasis_tosca + +imports: + - some_import: + file: compute_with_prop.yaml + repository: some_repository + +description: > + TOSCA test for testing repositories definition + + node_templates: + + server: + type: tosca.nodes.ComputeWithProp + properties: + test: yes diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/test_requirements.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/test_requirements.yaml new file mode 100644 index 0000000..269c46d --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/test_requirements.yaml @@ -0,0 +1,67 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + Test Requirements. + +imports: + - custom_types/wordpress.yaml + +topology_template: + node_templates: + my_app: + description: > + Specify multiple requirement via node and relationship keyword, + as an explicit relationship. Also demonstrates relationship with + type keyword and without it as an in-line reference. + type: tosca.nodes.WebApplication.WordPress + requirements: + - req1: + node: my_webserver + relationship: tosca.relationships.HostedOn + - req2: + node: mysql_database + relationship: + type: tosca.relationships.ConnectsTo + mysql_database: + description: Specify requirement via a capability as an implicit relationship. + type: tosca.nodes.Database + requirements: + - host: + node: my_dbms + relationship: tosca.relationships.HostedOn + my_dbms: + type: tosca.nodes.DBMS + my_webserver: + type: tosca.nodes.WebServer + my_server: + description: > + Specify requirement via a relationship template, as an explicit relationship. + type: tosca.nodes.Compute + capabilities: + host: + properties: + num_cpus: 2 + disk_size: 10 GB + mem_size: 4 MB + os: + properties: + # host Operating System image properties + architecture: x86_64 + type: linux + distribution: rhel + version: 6.5 + requirements: + - req1: + node: my_storage + relationship: storage_attachment + my_storage: + type: tosca.nodes.BlockStorage + properties: + size: 1 GiB + snapshot_id: id + + relationship_templates: + storage_attachment: + type: tosca.relationships.AttachesTo + properties: + location: /temp diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/test_tosca_custom_rel_with_script.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/test_tosca_custom_rel_with_script.yaml new file mode 100644 index 0000000..18a94a3 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/test_tosca_custom_rel_with_script.yaml @@ -0,0 +1,23 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: Test template of a custom relationship with a configure script + +topology_template: + + node_templates: + apache: + type: tosca.nodes.WebServer + requirements: + - host: + node: web_server + relationship: my_custom_rel + + web_server: + type: tosca.nodes.Compute + + relationship_templates: + my_custom_rel: + type: HostedOn + interfaces: + Configure: + pre_configure_source: scripts/wp_db_configure.sh diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/test_tosca_normative_type_by_shortname.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/test_tosca_normative_type_by_shortname.yaml new file mode 100644 index 0000000..8a702fb --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/test_tosca_normative_type_by_shortname.yaml @@ -0,0 +1,33 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + TOSCA simple profile with short type name for Compute. + +topology_template: + inputs: + cpus: + type: integer + description: Number of CPUs for the server. + constraints: + - valid_values: [ 1, 2, 4, 8 ] + + node_templates: + server: + type: Compute + capabilities: + host: + properties: + disk_size: 10 GB + num_cpus: { get_input: cpus } + mem_size: 4096 MB + os: + properties: + architecture: x86_64 + type: Linux + distribution: Fedora + version: 18.0 + + outputs: + server_address: + description: IP address of server instance. + value: { get_attribute: [server, private_address] } diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/test_tosca_top_level_error1.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/test_tosca_top_level_error1.yaml new file mode 100644 index 0000000..d35c022 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/test_tosca_top_level_error1.yaml @@ -0,0 +1,2 @@ +description: > + TOSCA simple profile missing version section.
\ No newline at end of file diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/test_tosca_top_level_error2.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/test_tosca_top_level_error2.yaml new file mode 100644 index 0000000..b3e80f9 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/test_tosca_top_level_error2.yaml @@ -0,0 +1,11 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + TOSCA simple profile with invalid top-level key: 'node_template'. + +topology_template: + + node_template: + server: + type: tosca.nodes.Compute + diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/topology_template/definitions.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/topology_template/definitions.yaml new file mode 100644 index 0000000..cfa0614 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/topology_template/definitions.yaml @@ -0,0 +1,53 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +node_types: + example.TransactionSubsystem: + properties: + mq_server_ip: + type: string + receiver_port: + type: integer + attributes: + receiver_ip: + type: string + receiver_port: + type: integer + capabilities: + message_receiver: + type: example.capabilities.Receiver + requirements: + - database_endpoint: + capability: tosca.capabilities.Endpoint.Database + node: tosca.nodes.Database + relationship: tosca.relationships.ConnectsTo + + example.QueuingSubsystem: + derived_from: tosca.nodes.SoftwareComponent + requirements: + - receiver1: + node: example.TransactionSubsystem + relationship: tosca.relationships.ConnectsTo + - receiver2: + node: example.TransactionSubsystem + relationship: tosca.relationships.ConnectsTo + + example.DatabaseSubsystem: + derived_from: tosca.nodes.Database + + example.SomeApp: + derived_from: tosca.nodes.SoftwareComponent + properties: + admin_user: + type: string + pool_size: + type: integer + capabilities: + message_receiver: + type: example.capabilities.Receiver + +capability_types: + example.capabilities.Receiver: + derived_from: tosca.capabilities.Endpoint + properties: + server_ip: + type: string diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/topology_template/subsystem.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/topology_template/subsystem.yaml new file mode 100644 index 0000000..b27e698 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/topology_template/subsystem.yaml @@ -0,0 +1,83 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + Service template with topology_template, act as a nested system inside another system. + +imports: + - definitions.yaml + +topology_template: + description: Template of a database including its hosting stack. + + inputs: + mq_server_ip: + type: string + description: IP address of the message queuing server to receive messages from. + receiver_port: + type: string + description: Port to be used for receiving messages. + my_cpus: + type: integer + description: Number of CPUs for the server. + constraints: + - valid_values: [ 1, 2, 4, 8 ] + + substitution_mappings: + node_type: example.TransactionSubsystem + capabilities: + message_receiver: [ app, message_receiver ] + requirements: + database_endpoint: [ app, database ] + + node_templates: + app: + type: example.SomeApp + properties: + admin_user: foo + pool_size: 10 + capabilities: + message_receiver: + properties: + server_ip: { get_input: mq_server_ip } + requirements: + - host: + node: websrv + + websrv: + type: tosca.nodes.WebServer + capabilities: + data_endpoint: + properties: + port_name: { get_input: receiver_port } + requirements: + - host: + node: server + + server: + type: tosca.nodes.Compute + capabilities: + host: + properties: + disk_size: 10 GB + num_cpus: { get_input: my_cpus } + mem_size: 4096 MB + os: + properties: + architecture: x86_64 + type: Linux + distribution: Ubuntu + version: 14.04 + + outputs: + receiver_ip: + description: private IP address of the message receiver application + value: { get_attribute: [ server, private_address ] } +# It seems current _process_intrisic_function can not handle more than 2 arguments, save it for later +# receiver_port: +# description: Port of the message receiver endpoint +# value: { get_attribute: [ app, data_endpoint, port_name ] } + + groups: + webserver_group: + members: [ websrv, server ] + type: tosca.groups.Root diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/topology_template/system.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/topology_template/system.yaml new file mode 100644 index 0000000..2d459aa --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/topology_template/system.yaml @@ -0,0 +1,57 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +imports: + - definitions.yaml + +topology_template: + description: Template of online transaction processing service. + + node_templates: + mq: + type: example.QueuingSubsystem + # properties: + # to be updated when substitution_mapping is implemented + # capabilities: + # message_queue_endpoint: + # to be updated when substitution_mapping is implemented + requirements: + - receiver1: trans1 + - receiver2: trans2 + + trans1: + type: example.TransactionSubsystem + properties: + # TODO to be updated when substitution_mapping is implemented + # mq_server_ip: { get_attribute: [ mq, server_ip ] } + # for now, we will use the loopback address to avoid errors as + # this property is required in the schema + mq_server_ip: 127.0.0.1 + receiver_port: 8080 + # capabilities: + # message_receiver: + # to be updated when substitution_mapping is implemented + requirements: + - database_endpoint: dbsys + + trans2: + type: example.TransactionSubsystem + properties: + # TODO to be updated when substitution_mapping is implemented + # mq_server_ip: { get_attribute: [ mq, server_ip ] } + # for now, we will use the loopback address to avoid errors as + # this property is required in the schema + mq_server_ip: 127.0.0.1 + receiver_port: 8080 + # capabilities: + # message_receiver: + # to be updated when substitution_mapping is implemented + requirements: + - database_endpoint: dbsys + + dbsys: + type: example.DatabaseSubsystem + # properties: + # to be updated when substitution_mapping is implemented + # capabilities: + # database_endpoint: + # to be updated when substitution_mapping is implemented
\ No newline at end of file diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/tosca_elk.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/tosca_elk.yaml new file mode 100644 index 0000000..6fc1756 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/tosca_elk.yaml @@ -0,0 +1,217 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + This TOSCA simple profile deploys nodejs, mongodb, elasticsearch, logstash + and kibana each on a separate server with monitoring enabled for nodejs + server where a sample nodejs application is running. The rsyslog and collectd + are installed on a nodejs server. + +imports: + - custom_types/paypalpizzastore_nodejs_app.yaml + - custom_types/elasticsearch.yaml + - custom_types/logstash.yaml + - custom_types/kibana.yaml + - custom_types/collectd.yaml + - custom_types/rsyslog.yaml + +dsl_definitions: + host_capabilities: &host_capabilities + disk_size: 10 GB + num_cpus: { get_input: my_cpus } + mem_size: 4096 MB + os_capabilities: &os_capabilities + architecture: x86_64 + type: Linux + distribution: Ubuntu + version: 14.04 + +topology_template: + inputs: + my_cpus: + type: integer + description: Number of CPUs for the server. + constraints: + - valid_values: [ 1, 2, 4, 8 ] + default: 1 + github_url: + type: string + description: The URL to download nodejs. + default: http://github.com/paypal/rest-api-sample-app-nodejs.git + + node_templates: + paypal_pizzastore: + type: tosca.nodes.WebApplication.PayPalPizzaStore + properties: + github_url: { get_input: github_url } + requirements: + - host: nodejs + - database_connection: mongo_db + interfaces: + Standard: + configure: + implementation: nodejs/config.sh + inputs: + github_url: { get_property: [ SELF, github_url ] } + mongodb_ip: { get_attribute: [mongo_server, private_address] } + start: nodejs/start.sh + nodejs: + type: tosca.nodes.WebServer + requirements: + - host: app_server + interfaces: + Standard: + create: nodejs/create.sh + mongo_db: + type: tosca.nodes.Database + requirements: + - host: mongo_dbms + interfaces: + Standard: + create: mongodb/create_database.sh + mongo_dbms: + type: tosca.nodes.DBMS + requirements: + - host: mongo_server + interfaces: + Standard: + create: mongodb/create.sh + configure: + implementation: mongodb/config.sh + inputs: + mongodb_ip: { get_attribute: [mongo_server, private_address] } + start: mongodb/start.sh + elasticsearch: + type: tosca.nodes.SoftwareComponent.Elasticsearch + requirements: + - host: elasticsearch_server + interfaces: + Standard: + create: elasticsearch/create.sh + start: elasticsearch/start.sh + logstash: + type: tosca.nodes.SoftwareComponent.Logstash + requirements: + - host: logstash_server + - search_endpoint: + node: elasticsearch + capability: search_endpoint + relationship: + type: tosca.relationships.ConnectsTo + interfaces: + Configure: + pre_configure_source: + implementation: logstash/configure_elasticsearch.py + inputs: + elasticsearch_ip: { get_attribute: [elasticsearch_server, private_address] } + interfaces: + Standard: + create: logstash/create.sh + start: logstash/start.sh + kibana: + type: tosca.nodes.SoftwareComponent.Kibana + requirements: + - host: kibana_server + - search_endpoint: elasticsearch + interfaces: + Standard: + create: kibana/create.sh + configure: + implementation: kibana/config.sh + inputs: + elasticsearch_ip: { get_attribute: [elasticsearch_server, private_address] } + kibana_ip: { get_attribute: [kibana_server, private_address] } + start: kibana/start.sh + app_collectd: + type: tosca.nodes.SoftwareComponent.Collectd + requirements: + - host: app_server + - log_endpoint: + node: logstash + capability: log_endpoint + relationship: + type: tosca.relationships.ConnectsTo + interfaces: + Configure: + pre_configure_target: + implementation: logstash/configure_collectd.py + interfaces: + Standard: + create: collectd/create.sh + configure: + implementation: collectd/config.py + inputs: + logstash_ip: { get_attribute: [logstash_server, private_address] } + start: collectd/start.sh + app_rsyslog: + type: tosca.nodes.SoftwareComponent.Rsyslog + requirements: + - host: app_server + - log_endpoint: + node: logstash + capability: log_endpoint + relationship: + type: tosca.relationships.ConnectsTo + interfaces: + Configure: + pre_configure_target: + implementation: logstash/configure_rsyslog.py + interfaces: + Standard: + create: rsyslog/create.sh + configure: + implementation: rsyslog/config.sh + inputs: + logstash_ip: { get_attribute: [logstash_server, private_address] } + start: rsyslog/start.sh + app_server: + type: tosca.nodes.Compute + capabilities: + host: + properties: *host_capabilities + os: + properties: *os_capabilities + mongo_server: + type: tosca.nodes.Compute + capabilities: + host: + properties: *host_capabilities + os: + properties: *os_capabilities + elasticsearch_server: + type: tosca.nodes.Compute + capabilities: + host: + properties: *host_capabilities + os: + properties: *os_capabilities + logstash_server: + type: tosca.nodes.Compute + capabilities: + host: + properties: *host_capabilities + os: + properties: *os_capabilities + kibana_server: + type: tosca.nodes.Compute + capabilities: + host: + properties: *host_capabilities + os: + properties: *os_capabilities + + outputs: + nodejs_url: + description: URL for the nodejs server, http://<IP>:3000 + value: { get_attribute: [ app_server, private_address ] } + mongodb_url: + description: URL for the mongodb server. + value: { get_attribute: [ mongo_server, private_address ] } + elasticsearch_url: + description: URL for the elasticsearch server. + value: { get_attribute: [ elasticsearch_server, private_address ] } + logstash_url: + description: URL for the logstash server. + value: { get_attribute: [ logstash_server, private_address ] } + kibana_url: + description: URL for the kibana server. + value: { get_attribute: [ kibana_server, private_address ] } diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/tosca_helloworld.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/tosca_helloworld.yaml new file mode 100644 index 0000000..5b913ff --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/tosca_helloworld.yaml @@ -0,0 +1,23 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: Template for deploying a single server with predefined properties. + +topology_template: + node_templates: + my_server: + type: tosca.nodes.Compute + capabilities: + # Host container properties + host: + properties: + num_cpus: 2 + disk_size: 10 GB + mem_size: 512 MB + # Guest Operating System properties + os: + properties: + # host Operating System image properties + architecture: x86_64 + type: Linux + distribution: RHEL + version: 6.5 diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/tosca_imports_validation.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/tosca_imports_validation.yaml new file mode 100644 index 0000000..9c3fef4 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/tosca_imports_validation.yaml @@ -0,0 +1,39 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: Template to test invalid imports. + +imports: + - custom_types/imported_sample.yaml + +topology_template: + node_templates: + logstash: + type: tosca.nodes.SoftwareComponent.Logstash + requirements: + - search_endpoint: + capability: search_endpoint + relationship: + type: tosca.relationships.ConnectsTo + interfaces: + Configure: + pre_configure_source: + implementation: logstash/configure_elasticsearch.py + inputs: + elasticsearch_ip: { get_attribute: [elasticsearch_server, private_address] } + interfaces: + Standard: + create: logstash/create.sh + start: logstash/start.sh + policies: + - my_compute_placement_policy: + type: tosca.policies.Placement + description: Apply placement policy to servers + metadata: { user1: 1001, user2: 1002 } + targets: [ my_server_1, my_server_2 ] + - my_groups_placement: + type: mycompany.mytypes.myScalingPolicy + targets: [ webserver_group ] + description: my company scaling policy + metadata: + user1: 1001 + user2: 1003 diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/tosca_load_balancer.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/tosca_load_balancer.yaml new file mode 100644 index 0000000..2fcdb48 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/tosca_load_balancer.yaml @@ -0,0 +1,75 @@ +# Note: this could eventually be translated to a Neutron Load Balancer +# However, in Heat/HOT the preferred way of doing this is creating an Autoscale Group +# +#heat_template_version: 2015-04-30 ... +#resources: +#load_bal_resource: +# type: OS::Neutron::Pool +# properties: +# admin_state_up: Boolean +# description: String +# lb_method: String +# monitors: [Value, Value, ...] +# name: String +# protocol: String +# provider: String +# subnet: String +# vip: { +# "description": String, +# "name": String, +# "connection_limit": Integer, +# "protocol_port": Integer, +# "subnet": String, +# "address": String, +# "admin_state_up": Boolean, +# "session_persistence": +# { +# "cookie_name": String, +# "type": String} +# } +# +# example from: https://gist.github.com/therve/9231701 +# +#resources: +# web_server_group: +# type: AWS::AutoScaling::AutoScalingGroup +# properties: +# AvailabilityZones: [nova] +# LaunchConfigurationName: {get_resource: launch_config} +# MinSize: 1 +# MaxSize: 3 +# LoadBalancerNames: +# - {get_resource: mylb} +# mypool: +# type: OS::Neutron::Pool +# properties: +# protocol: HTTP +# monitors: [{get_resource: mymonitor}] +# subnet_id: {get_param: subnet_id} +# lb_method: ROUND_ROBIN +# vip: +# protocol_port: 80 +# mylb: +# type: OS::Neutron::LoadBalancer +# properties: +# protocol_port: 80 +# pool_id: {get_resource: mypool} + +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: Template for deploying a load balancer with predefined endpoint properties. + +topology_template: + node_templates: + simple_load_balancer: + type: tosca.nodes.LoadBalancer + capabilities: + # properties: + # algorithm: DEFAULT (define new keyword, ROUND_ROBIN?) + # Client, public facing endpoint + client: + properties: + network_name: PUBLIC + floating: true + dns_name: http://mycompany.com/ + diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/tosca_single_instance_wordpress.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/tosca_single_instance_wordpress.yaml new file mode 100644 index 0000000..9e686ab --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/tosca_single_instance_wordpress.yaml @@ -0,0 +1,121 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + TOSCA simple profile with wordpress, web server and mysql on the same server. + +imports: + - custom_types/wordpress.yaml + +topology_template: + inputs: + cpus: + type: integer + description: Number of CPUs for the server. + constraints: + - valid_values: [ 1, 2, 4, 8 ] + default: 1 + db_name: + type: string + description: The name of the database. + default: wordpress + db_user: + type: string + description: The user name of the DB user. + default: wp_user + db_pwd: + type: string + description: The WordPress database admin account password. + default: wp_pass + db_root_pwd: + type: string + description: Root password for MySQL. + db_port: + type: PortDef + description: Port for the MySQL database. + default: 3306 + + node_templates: + wordpress: + type: tosca.nodes.WebApplication.WordPress + requirements: + - host: webserver + - database_endpoint: mysql_database + interfaces: + Standard: + create: wordpress/wordpress_install.sh + configure: + implementation: wordpress/wordpress_configure.sh + inputs: + wp_db_name: { get_property: [ mysql_database, name ] } + wp_db_user: { get_property: [ mysql_database, user ] } + wp_db_password: { get_property: [ mysql_database, password ] } + + mysql_database: + type: tosca.nodes.Database + properties: + name: { get_input: db_name } + user: { get_input: db_user } + password: { get_input: db_pwd } + capabilities: + database_endpoint: + properties: + port: { get_input: db_port } + requirements: + - host: mysql_dbms + interfaces: + Standard: + configure: + implementation: mysql/mysql_database_configure.sh + inputs: + db_name: { get_property: [ SELF, name ] } + db_user: { get_property: [ SELF, user ] } + db_password: { get_property: [ SELF, password ] } + db_root_password: { get_property: [ mysql_dbms, root_password ] } + + mysql_dbms: + type: tosca.nodes.DBMS + properties: + root_password: { get_input: db_root_pwd } + port: { get_input: db_port } + requirements: + - host: server + interfaces: + Standard: + create: + implementation: mysql/mysql_dbms_install.sh + inputs: + db_root_password: { get_property: [ mysql_dbms, root_password ] } + start: mysql/mysql_dbms_start.sh + configure: + implementation: mysql/mysql_dbms_configure.sh + inputs: + db_port: { get_property: [ mysql_dbms, port ] } + + webserver: + type: tosca.nodes.WebServer + requirements: + - host: server + interfaces: + Standard: + create: webserver/webserver_install.sh + start: webserver/webserver_start.sh + + server: + type: tosca.nodes.Compute + capabilities: + host: + properties: + disk_size: 10 GB + num_cpus: { get_input: cpus } + mem_size: 4096 MB + os: + properties: + architecture: x86_64 + type: Linux + distribution: Ubuntu + version: 14.04 + + outputs: + website_url: + description: URL for Wordpress wiki. + value: { get_attribute: [server, private_address] } diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/tosca_single_instance_wordpress_with_local_abspath_import.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/tosca_single_instance_wordpress_with_local_abspath_import.yaml new file mode 100644 index 0000000..6caac11 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/tosca_single_instance_wordpress_with_local_abspath_import.yaml @@ -0,0 +1,122 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + TOSCA simple profile with wordpress, web server and mysql on the same server. + Parsing of this test template will fail with errors if provided imports does + not refer to a valid absolute path. + +imports: + - /tmp/tosca-parser/toscaparser/tests/data/custom_types/wordpress.yaml + +topology_template: + inputs: + cpus: + type: integer + description: Number of CPUs for the server. + constraints: + - valid_values: [ 1, 2, 4, 8 ] + default: 1 + db_name: + type: string + description: The name of the database. + default: wordpress + db_user: + type: string + description: The user name of the DB user. + default: wp_user + db_pwd: + type: string + description: The WordPress database admin account password. + default: wp_pass + db_root_pwd: + type: string + description: Root password for MySQL. + db_port: + type: PortDef + description: Port for the MySQL database. + default: 3306 + + node_templates: + wordpress: + type: tosca.nodes.WebApplication.WordPress + requirements: + - host: webserver + - database_endpoint: mysql_database + interfaces: + Standard: + create: wordpress/wordpress_install.sh + configure: + implementation: wordpress/wordpress_configure.sh + inputs: + wp_db_name: wordpress + wp_db_user: wp_user + wp_db_password: wp_pass + + mysql_database: + type: tosca.nodes.Database + properties: + name: { get_input: db_name } + user: { get_input: db_user } + password: { get_input: db_pwd } + capabilities: + database_endpoint: + properties: + port: { get_input: db_port } + requirements: + - host: + node: mysql_dbms + interfaces: + Standard: + configure: + implementation: mysql/mysql_database_configure.sh + inputs: + db_name: wordpress + db_user: wp_user + db_password: wp_pass + db_root_password: passw0rd + mysql_dbms: + type: tosca.nodes.DBMS + properties: + root_password: { get_input: db_root_pwd } + port: { get_input: db_port } + requirements: + - host: server + interfaces: + Standard: + create: + implementation: mysql/mysql_dbms_install.sh + inputs: + db_root_password: passw0rd + start: mysql/mysql_dbms_start.sh + configure: + implementation: mysql/mysql_dbms_configure.sh + inputs: + db_port: 3366 + + webserver: + type: tosca.nodes.WebServer + requirements: + - host: server + interfaces: + Standard: + create: webserver/webserver_install.sh + start: webserver/webserver_start.sh + server: + type: tosca.nodes.Compute + capabilities: + host: + properties: + disk_size: 10 GB + num_cpus: { get_input: cpus } + mem_size: 4096 MB + os: + properties: + architecture: x86_64 + type: Linux + distribution: Ubuntu + version: 14.04 + + outputs: + website_url: + description: URL for Wordpress wiki. + value: { get_attribute: [server, private_address] } diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/tosca_single_instance_wordpress_with_url_import.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/tosca_single_instance_wordpress_with_url_import.yaml new file mode 100644 index 0000000..e5f1580 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/tosca_single_instance_wordpress_with_url_import.yaml @@ -0,0 +1,120 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + TOSCA simple profile with wordpress, web server and mysql on the same server. + +imports: + - https://raw.githubusercontent.com/openstack/heat-translator/master/translator/tests/data/custom_types/wordpress.yaml + +topology_template: + inputs: + cpus: + type: integer + description: Number of CPUs for the server. + constraints: + - valid_values: [ 1, 2, 4, 8 ] + default: 1 + db_name: + type: string + description: The name of the database. + default: wordpress + db_user: + type: string + description: The user name of the DB user. + default: wp_user + db_pwd: + type: string + description: The WordPress database admin account password. + default: wp_pass + db_root_pwd: + type: string + description: Root password for MySQL. + db_port: + type: PortDef + description: Port for the MySQL database. + default: 3306 + + node_templates: + wordpress: + type: tosca.nodes.WebApplication.WordPress + requirements: + - host: webserver + - database_endpoint: mysql_database + interfaces: + Standard: + create: wordpress/wordpress_install.sh + configure: + implementation: wordpress/wordpress_configure.sh + inputs: + wp_db_name: wordpress + wp_db_user: wp_user + wp_db_password: wp_pass + + mysql_database: + type: tosca.nodes.Database + properties: + name: { get_input: db_name } + user: { get_input: db_user } + password: { get_input: db_pwd } + capabilities: + database_endpoint: + properties: + port: { get_input: db_port } + requirements: + - host: + node: mysql_dbms + interfaces: + Standard: + configure: + implementation: mysql/mysql_database_configure.sh + inputs: + db_name: wordpress + db_user: wp_user + db_password: wp_pass + db_root_password: passw0rd + mysql_dbms: + type: tosca.nodes.DBMS + properties: + root_password: { get_input: db_root_pwd } + port: { get_input: db_port } + requirements: + - host: server + interfaces: + Standard: + create: + implementation: mysql/mysql_dbms_install.sh + inputs: + db_root_password: passw0rd + start: mysql/mysql_dbms_start.sh + configure: + implementation: mysql/mysql_dbms_configure.sh + inputs: + db_port: 3366 + + webserver: + type: tosca.nodes.WebServer + requirements: + - host: server + interfaces: + Standard: + create: webserver/webserver_install.sh + start: webserver/webserver_start.sh + server: + type: tosca.nodes.Compute + capabilities: + host: + properties: + disk_size: 10 GB + num_cpus: { get_input: cpus } + mem_size: 4096 MB + os: + properties: + architecture: x86_64 + type: Linux + distribution: Ubuntu + version: 14.04 + + outputs: + website_url: + description: URL for Wordpress wiki. + value: { get_attribute: [server, private_address] } diff --git a/tosca2heat/tosca-parser/toscaparser/tests/spec_samples/v1.0/network/tosca_one_server_one_network.yaml b/tosca2heat/tosca-parser/toscaparser/tests/spec_samples/v1.0/network/tosca_one_server_one_network.yaml new file mode 100644 index 0000000..8e58fa9 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/spec_samples/v1.0/network/tosca_one_server_one_network.yaml @@ -0,0 +1,43 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + TOSCA simple profile with 1 server bound to a new network + +topology_template: + + inputs: + network_name: + type: string + description: Network name + + node_templates: + my_server: + type: tosca.nodes.Compute + capabilities: + host: + properties: + disk_size: 10 GB + num_cpus: 1 + mem_size: 512 MB + os: + properties: + architecture: x86_64 + type: Linux + distribution: CirrOS + version: 0.3.2 + + my_network: + type: tosca.nodes.network.Network + properties: + network_name: { get_input: network_name } + ip_version: 4 + cidr: '192.168.0.0/24' + start_ip: '192.168.0.50' + end_ip: '192.168.0.200' + gateway_ip: '192.168.0.1' + + my_port: + type: tosca.nodes.network.Port + requirements: + - binding: my_server + - link: my_network diff --git a/tosca2heat/tosca-parser/toscaparser/tests/spec_samples/v1.0/network/tosca_one_server_three_networks.yaml b/tosca2heat/tosca-parser/toscaparser/tests/spec_samples/v1.0/network/tosca_one_server_three_networks.yaml new file mode 100644 index 0000000..d791b17 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/spec_samples/v1.0/network/tosca_one_server_three_networks.yaml @@ -0,0 +1,64 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + TOSCA simple profile with 1 server bound to 3 networks + +topology_template: + + node_templates: + my_server: + type: tosca.nodes.Compute + capabilities: + host: + properties: + disk_size: 10 GB + num_cpus: 1 + mem_size: 512 MB + os: + properties: + architecture: x86_64 + type: Linux + distribution: CirrOS + version: 0.3.2 + + my_network1: + type: tosca.nodes.network.Network + properties: + cidr: '192.168.1.0/24' + network_name: net1 + + my_network2: + type: tosca.nodes.network.Network + properties: + cidr: '192.168.2.0/24' + network_name: net2 + + my_network3: + type: tosca.nodes.network.Network + properties: + cidr: '192.168.3.0/24' + network_name: net3 + + my_port1: + type: tosca.nodes.network.Port + properties: + order: 0 + requirements: + - binding: my_server + - link: my_network1 + + my_port2: + type: tosca.nodes.network.Port + properties: + order: 1 + requirements: + - binding: my_server + - link: my_network2 + + my_port3: + type: tosca.nodes.network.Port + properties: + order: 2 + requirements: + - binding: my_server + - link: my_network3 diff --git a/tosca2heat/tosca-parser/toscaparser/tests/spec_samples/v1.0/network/tosca_server_on_existing_network.yaml b/tosca2heat/tosca-parser/toscaparser/tests/spec_samples/v1.0/network/tosca_server_on_existing_network.yaml new file mode 100644 index 0000000..7fedc13 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/spec_samples/v1.0/network/tosca_server_on_existing_network.yaml @@ -0,0 +1,39 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + TOSCA simple profile with 1 server bound to an existing network + +topology_template: + inputs: + network_name: + type: string + description: Network name + + node_templates: + my_server: + type: tosca.nodes.Compute + capabilities: + host: + properties: + disk_size: 10 GB + num_cpus: 1 + mem_size: 512 MB + os: + properties: + architecture: x86_64 + type: Linux + distribution: CirrOS + version: 0.3.2 + + my_network: + type: tosca.nodes.network.Network + properties: + network_name: { get_input: network_name } + + my_port: + type: tosca.nodes.network.Port + requirements: + - binding: + node: my_server + - link: + node: my_network diff --git a/tosca2heat/tosca-parser/toscaparser/tests/spec_samples/v1.0/network/tosca_two_servers_one_network.yaml b/tosca2heat/tosca-parser/toscaparser/tests/spec_samples/v1.0/network/tosca_two_servers_one_network.yaml new file mode 100644 index 0000000..1473a8d --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/spec_samples/v1.0/network/tosca_two_servers_one_network.yaml @@ -0,0 +1,79 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + TOSCA simple profile with 2 servers bound to the 1 network + +topology_template: + + inputs: + network_name: + type: string + description: Network name + network_cidr: + type: string + default: 10.0.0.0/24 + description: CIDR for the network + network_start_ip: + type: string + default: 10.0.0.100 + description: Start IP for the allocation pool + network_end_ip: + type: string + default: 10.0.0.150 + description: End IP for the allocation pool + + node_templates: + my_server: + type: tosca.nodes.Compute + capabilities: + host: + properties: + disk_size: 10 GB + num_cpus: 1 + mem_size: 512 MB + os: + properties: + architecture: x86_64 + type: Linux + distribution: CirrOS + version: 0.3.2 + + my_server2: + type: tosca.nodes.Compute + capabilities: + host: + properties: + disk_size: 10 GB + num_cpus: 1 + mem_size: 512 MB + os: + properties: + architecture: x86_64 + type: Linux + distribution: CirrOS + version: 0.3.2 + + my_network: + type: tosca.nodes.network.Network + properties: + ip_version: 4 + cidr: { get_input: network_cidr } + network_name: { get_input: network_name } + start_ip: { get_input: network_start_ip } + end_ip: { get_input: network_end_ip } + + my_port: + type: tosca.nodes.network.Port + requirements: + - binding: + node: my_server + - link: + node: my_network + + my_port2: + type: tosca.nodes.network.Port + requirements: + - binding: + node: my_server2 + - link: + node: my_network diff --git a/tosca2heat/tosca-parser/toscaparser/tests/spec_samples/v1.0/storage/tosca_blockstorage_with_attachment.yaml b/tosca2heat/tosca-parser/toscaparser/tests/spec_samples/v1.0/storage/tosca_blockstorage_with_attachment.yaml new file mode 100644 index 0000000..460fa4c --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/spec_samples/v1.0/storage/tosca_blockstorage_with_attachment.yaml @@ -0,0 +1,61 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + TOSCA simple profile with server and attached block storage using the normative AttachesTo Relationship Type. + +topology_template: + + inputs: + cpus: + type: integer + description: Number of CPUs for the server. + constraints: + - valid_values: [ 1, 2, 4, 8 ] + storage_size: + type: scalar-unit.size + description: Size of the storage to be created. + default: 1 GB + storage_snapshot_id: + type: string + description: > + Optional identifier for an existing snapshot to use when creating storage. + storage_location: + type: string + description: Block storage mount point (filesystem path). + + node_templates: + my_server: + type: Compute + capabilities: + host: + properties: + disk_size: 10 GB + num_cpus: { get_input: cpus } + mem_size: 4096 kB + os: + properties: + architecture: x86_64 + type: linux + distribution: fedora + version: 18.0 + requirements: + - local_storage: + node: my_storage + relationship: + type: AttachesTo + properties: + location: { get_input: storage_location } + + my_storage: + type: BlockStorage + properties: + size: { get_input: storage_size } + snapshot_id: { get_input: storage_snapshot_id } + + outputs: + private_ip: + description: The private IP address of the newly created compute instance. + value: { get_attribute: [my_server, private_address] } + volume_id: + description: The volume id of the block storage instance. + value: { get_attribute: [my_storage, volume_id] } diff --git a/tosca2heat/tosca-parser/toscaparser/tests/spec_samples/v1.0/storage/tosca_blockstorage_with_attachment_notation1.yaml b/tosca2heat/tosca-parser/toscaparser/tests/spec_samples/v1.0/storage/tosca_blockstorage_with_attachment_notation1.yaml new file mode 100644 index 0000000..df22d72 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/spec_samples/v1.0/storage/tosca_blockstorage_with_attachment_notation1.yaml @@ -0,0 +1,87 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + TOSCA simple profile with a Single Block Storage node shared by 2-Tier Application with custom AttachesTo Type and implied relationships. + +relationship_types: + MyAttachesTo: + derived_from: tosca.relationships.AttachesTo + properties: + location: + type: string + default: /default_location + +topology_template: + inputs: + cpus: + type: integer + description: Number of CPUs for the server. + constraints: + - valid_values: [ 1, 2, 4, 8 ] + storage_size: + type: scalar-unit.size + default: 1 GB + description: Size of the storage to be created. + storage_snapshot_id: + type: string + description: > + Optional identifier for an existing snapshot to use when creating storage. + + node_templates: + my_web_app_tier_1: + type: tosca.nodes.Compute + capabilities: + host: + properties: + disk_size: 10 GB + num_cpus: { get_input: cpus } + mem_size: 4096 MB + os: + properties: + architecture: x86_64 + type: Linux + distribution: Fedora + version: 18.0 + requirements: + - local_storage: + node: my_storage + relationship: MyAttachesTo + + my_web_app_tier_2: + type: tosca.nodes.Compute + capabilities: + host: + properties: + disk_size: 10 GB + num_cpus: { get_input: cpus } + mem_size: 4096 MB + os: + properties: + architecture: x86_64 + type: Linux + distribution: Fedora + version: 18.0 + requirements: + - local_storage: + node: my_storage + relationship: + type: MyAttachesTo + properties: + location: /some_other_data_location + + my_storage: + type: tosca.nodes.BlockStorage + properties: + size: { get_input: storage_size } + snapshot_id: { get_input: storage_snapshot_id } + + outputs: + private_ip_1: + description: The private IP address of the application's first tier. + value: { get_attribute: [my_web_app_tier_1, private_address] } + private_ip_2: + description: The private IP address of the application's second tier. + value: { get_attribute: [my_web_app_tier_2, private_address] } + volume_id: + description: The volume id of the block storage instance. + value: { get_attribute: [my_storage, volume_id] } diff --git a/tosca2heat/tosca-parser/toscaparser/tests/spec_samples/v1.0/storage/tosca_blockstorage_with_attachment_notation2.yaml b/tosca2heat/tosca-parser/toscaparser/tests/spec_samples/v1.0/storage/tosca_blockstorage_with_attachment_notation2.yaml new file mode 100644 index 0000000..cb1c17a --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/spec_samples/v1.0/storage/tosca_blockstorage_with_attachment_notation2.yaml @@ -0,0 +1,99 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + TOSCA simple profile with a single Block Storage node shared by 2-Tier Application with custom AttachesTo Type and explicit Relationship Templates. + +relationship_types: + MyAttachesTo: + derived_from: tosca.relationships.AttachesTo + properties: + location: + type: string + default: /default_location + +topology_template: + inputs: + cpus: + type: integer + description: Number of CPUs for the server. + constraints: + - valid_values: [ 1, 2, 4, 8 ] + storage_size: + type: scalar-unit.size + default: 1 GB + description: Size of the storage to be created. + storage_snapshot_id: + type: string + description: > + Optional identifier for an existing snapshot to use when creating storage. + storage_location: + type: string + description: > + Block storage mount point (filesystem path). + + node_templates: + + my_web_app_tier_1: + type: tosca.nodes.Compute + capabilities: + host: + properties: + disk_size: 10 GB + num_cpus: { get_input: cpus } + mem_size: 4096 kB + os: + properties: + architecture: x86_64 + type: Linux + distribution: Fedora + version: 18.0 + requirements: + - local_storage: + node: my_storage + relationship: storage_attachesto_1 + + my_web_app_tier_2: + type: tosca.nodes.Compute + capabilities: + host: + properties: + disk_size: 10 GB + num_cpus: { get_input: cpus } + mem_size: 4096 kB + os: + properties: + architecture: x86_64 + type: Linux + distribution: Fedora + version: 18.0 + requirements: + - local_storage: + node: my_storage + relationship: storage_attachesto_2 + + my_storage: + type: tosca.nodes.BlockStorage + properties: + size: { get_input: storage_size } + snapshot_id: { get_input: storage_snapshot_id } + + relationship_templates: + storage_attachesto_1: + type: MyAttachesTo + properties: + location: /my_data_location + + storage_attachesto_2: + type: MyAttachesTo + properties: + location: /some_other_data_location + outputs: + private_ip_1: + description: The private IP address of the application's first tier. + value: { get_attribute: [my_web_app_tier_1, private_address] } + private_ip_2: + description: The private IP address of the application's second tier. + value: { get_attribute: [my_web_app_tier_2, private_address] } + volume_id: + description: The volume id of the block storage instance. + value: { get_attribute: [my_storage, volume_id] } diff --git a/tosca2heat/tosca-parser/toscaparser/tests/spec_samples/v1.0/storage/tosca_blockstorage_with_custom_relationship_type.yaml b/tosca2heat/tosca-parser/toscaparser/tests/spec_samples/v1.0/storage/tosca_blockstorage_with_custom_relationship_type.yaml new file mode 100644 index 0000000..932f89e --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/spec_samples/v1.0/storage/tosca_blockstorage_with_custom_relationship_type.yaml @@ -0,0 +1,64 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + TOSCA simple profile with server and attached block storage using a custom AttachesTo Relationship Type. + +relationship_types: + MyCustomAttachesTo: + derived_from: AttachesTo + +topology_template: + inputs: + cpus: + type: integer + description: Number of CPUs for the server. + constraints: + - valid_values: [ 1, 2, 4, 8 ] + storage_size: + type: scalar-unit.size + description: Size of the storage to be created. + default: 1 GB + storage_snapshot_id: + type: string + description: > + Optional identifier for an existing snapshot to use when creating storage. + storage_location: + type: string + description: Block storage mount point (filesystem path). + + node_templates: + my_server: + type: Compute + capabilities: + host: + properties: + disk_size: 10 GB + num_cpus: { get_input: cpus } + mem_size: 4 MB + os: + properties: + architecture: x86_64 + type: Linux + distribution: Fedora + version: 18.0 + requirements: + - local_storage: + node: my_storage + # Declare custom AttachesTo type using the 'relationship' keyword + relationship: + type: MyCustomAttachesTo + properties: + location: { get_input: storage_location } + my_storage: + type: BlockStorage + properties: + size: { get_input: storage_size } + snapshot_id: { get_input: storage_snapshot_id } + + outputs: + private_ip: + description: The private IP address of the newly created compute instance. + value: { get_attribute: [my_server, private_address] } + volume_id: + description: The volume id of the block storage instance. + value: { get_attribute: [my_storage, volume_id] } diff --git a/tosca2heat/tosca-parser/toscaparser/tests/spec_samples/v1.0/storage/tosca_blockstorage_with_relationship_template.yaml b/tosca2heat/tosca-parser/toscaparser/tests/spec_samples/v1.0/storage/tosca_blockstorage_with_relationship_template.yaml new file mode 100644 index 0000000..c31a4da --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/spec_samples/v1.0/storage/tosca_blockstorage_with_relationship_template.yaml @@ -0,0 +1,59 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + TOSCA simple profile with server and attached block storage using a named Relationship Template for the storage attachment. + +topology_template: + inputs: + cpus: + type: integer + description: Number of CPUs for the server. + constraints: + - valid_values: [ 1, 2, 4, 8 ] + storage_size: + type: scalar-unit.size + description: Size of the storage to be created. + default: 1 GB + storage_location: + type: string + description: Block storage mount point (filesystem path). + + node_templates: + my_server: + type: Compute + capabilities: + host: + properties: + disk_size: 10 GB + num_cpus: { get_input: cpus } + mem_size: 4 MB + os: + properties: + architecture: x86_64 + type: Linux + distribution: Fedora + version: 18.0 + requirements: + - local_storage: + node: my_storage + # Declare template to use with 'relationship' keyword + relationship: storage_attachment + + my_storage: + type: BlockStorage + properties: + size: { get_input: storage_size } + + relationship_templates: + storage_attachment: + type: AttachesTo + properties: + location: { get_input: storage_location } + + outputs: + private_ip: + description: The private IP address of the newly created compute instance. + value: { get_attribute: [my_server, private_address] } + volume_id: + description: The volume id of the block storage instance. + value: { get_attribute: [my_storage, volume_id] } diff --git a/tosca2heat/tosca-parser/toscaparser/tests/spec_samples/v1.0/storage/tosca_multiple_blockstorage_with_attachment.yaml b/tosca2heat/tosca-parser/toscaparser/tests/spec_samples/v1.0/storage/tosca_multiple_blockstorage_with_attachment.yaml new file mode 100644 index 0000000..aa4647e --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/spec_samples/v1.0/storage/tosca_multiple_blockstorage_with_attachment.yaml @@ -0,0 +1,93 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + TOSCA simple profile with 2 servers each with different attached block storage. + +topology_template: + inputs: + cpus: + type: integer + description: Number of CPUs for the server. + constraints: + - valid_values: [ 1, 2, 4, 8 ] + storage_size: + type: scalar-unit.size + default: 1 GB + description: Size of the storage to be created. + storage_snapshot_id: + type: string + description: > + Optional identifier for an existing snapshot to use when creating storage. + storage_location: + type: string + description: > + Block storage mount point (filesystem path). + + node_templates: + my_server: + type: tosca.nodes.Compute + capabilities: + host: + properties: + disk_size: 10 GB + num_cpus: { get_input: cpus } + mem_size: 4096 MB + os: + properties: + architecture: x86_64 + type: Linux + distribution: Fedora + version: 18.0 + requirements: + - local_storage: + node: my_storage + relationship: + type: AttachesTo + properties: + location: { get_input: storage_location } + my_storage: + type: tosca.nodes.BlockStorage + properties: + size: { get_input: storage_size } + snapshot_id: { get_input: storage_snapshot_id } + + my_server2: + type: tosca.nodes.Compute + capabilities: + host: + properties: + disk_size: 10 GB + num_cpus: { get_input: cpus } + mem_size: 4096 MB + os: + properties: + architecture: x86_64 + type: Linux + distribution: Fedora + version: 18.0 + requirements: + - local_storage: + node: my_storage2 + relationship: + type: AttachesTo + properties: + location: { get_input: storage_location } + my_storage2: + type: tosca.nodes.BlockStorage + properties: + size: { get_input: storage_size } + snapshot_id: { get_input: storage_snapshot_id } + + outputs: + server_ip_1: + description: The private IP address of the application's first server. + value: { get_attribute: [my_server, private_address] } + server_ip_2: + description: The private IP address of the application's second server. + value: { get_attribute: [my_server2, private_address] } + volume_id_1: + description: The volume id of the first block storage instance. + value: { get_attribute: [my_storage, volume_id] } + volume_id_2: + description: The volume id of the second block storage instance. + value: { get_attribute: [my_storage2, volume_id] } diff --git a/tosca2heat/tosca-parser/toscaparser/tests/spec_samples/v1.0/storage/tosca_single_object_store.yaml b/tosca2heat/tosca-parser/toscaparser/tests/spec_samples/v1.0/storage/tosca_single_object_store.yaml new file mode 100644 index 0000000..869af48 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/spec_samples/v1.0/storage/tosca_single_object_store.yaml @@ -0,0 +1,17 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + Tosca template for creating an object storage service. + +topology_template: + inputs: + objectstore_name: + type: string + + node_templates: + obj_store_server: + type: tosca.nodes.ObjectStorage + properties: + name: { get_input: objectstore_name } + size: 1024 kB + maxsize: 1 GB diff --git a/tosca2heat/tosca-parser/toscaparser/tests/spec_samples/v1.0/tosca_nodejs_mongodb_two_instances.yaml b/tosca2heat/tosca-parser/toscaparser/tests/spec_samples/v1.0/tosca_nodejs_mongodb_two_instances.yaml new file mode 100644 index 0000000..f611071 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/spec_samples/v1.0/tosca_nodejs_mongodb_two_instances.yaml @@ -0,0 +1,96 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + TOSCA simple profile with nodejs and mongodb. + +imports: + - custom_types/paypalpizzastore_nodejs_app.yaml + +dsl_definitions: + host_capabilities: &host_capabilities + disk_size: 10 GB + num_cpus: 1 + mem_size: 4096 MB + os_capabilities: &os_capabilities + architecture: x86_64 + type: Linux + distribution: Ubuntu + version: 14.04 + +topology_template: + inputs: + my_cpus: + type: integer + description: Number of CPUs for the server. + constraints: + - valid_values: [ 1, 2, 4, 8 ] + default: 1 + github_url: + type: string + description: The URL to download nodejs. + default: http://github.com/paypal/rest-api-sample-app-nodejs.git + + node_templates: + paypal_pizzastore: + type: tosca.nodes.WebApplication.PayPalPizzaStore + properties: + github_url: { get_input: github_url } + requirements: + - host: nodejs + - database_connection: mongo_db + interfaces: + Standard: + configure: + implementation: nodejs/config.sh + inputs: + github_url: http://github.com/paypal/rest-api-sample-app-nodejs.git + mongodb_ip: { get_attribute: [mongo_server, private_address] } + start: nodejs/start.sh + nodejs: + type: tosca.nodes.WebServer + requirements: + - host: app_server + interfaces: + Standard: + create: nodejs/create.sh + mongo_db: + type: tosca.nodes.Database + requirements: + - host: mongo_dbms + interfaces: + Standard: + create: mongodb/create_database.sh + mongo_dbms: + type: tosca.nodes.DBMS + requirements: + - host: mongo_server + interfaces: + Standard: + create: mongodb/create.sh + configure: + implementation: mongodb/config.sh + inputs: + mongodb_ip: { get_attribute: [mongo_server, private_address] } + start: mongodb/start.sh + mongo_server: + type: tosca.nodes.Compute + capabilities: + host: + properties: *host_capabilities + os: + properties: *os_capabilities + app_server: + type: tosca.nodes.Compute + capabilities: + host: + properties: *host_capabilities + os: + properties: *os_capabilities + + outputs: + nodejs_url: + description: URL for the nodejs server, http://<IP>:3000 + value: { get_attribute: [app_server, private_address] } + mongodb_url: + description: URL for the mongodb server. + value: { get_attribute: [mongo_server, private_address] } diff --git a/tosca2heat/tosca-parser/toscaparser/tests/spec_samples/v1.0/tosca_single_server.yaml b/tosca2heat/tosca-parser/toscaparser/tests/spec_samples/v1.0/tosca_single_server.yaml new file mode 100644 index 0000000..c4cce9d --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/spec_samples/v1.0/tosca_single_server.yaml @@ -0,0 +1,32 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + TOSCA simple profile that just defines a single compute instance and selects a (guest) host Operating System from the Compute node's properties. Note, this example does not include default values on inputs properties. + +topology_template: + inputs: + cpus: + type: integer + description: Number of CPUs for the server. + constraints: + - valid_values: [ 1, 2, 4, 8 ] + + node_templates: + my_server: + type: Compute + capabilities: + host: + properties: + disk_size: 10 GB + num_cpus: { get_input: cpus } + mem_size: 4 MB + os: + properties: + architecture: x86_64 + type: Linux + distribution: ubuntu + version: 12.04 + outputs: + private_ip: + description: The private IP address of the deployed server instance. + value: { get_attribute: [my_server, private_address] }
\ No newline at end of file diff --git a/tosca2heat/tosca-parser/toscaparser/tests/test_constraints.py b/tosca2heat/tosca-parser/toscaparser/tests/test_constraints.py new file mode 100644 index 0000000..07cb910 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/test_constraints.py @@ -0,0 +1,373 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import datetime +import yaml + +from toscaparser.common import exception +from toscaparser.elements.constraints import Constraint +from toscaparser.elements.constraints import Schema +from toscaparser.tests.base import TestCase +from toscaparser.utils.gettextutils import _ +from toscaparser.utils import yamlparser + + +class ConstraintTest(TestCase): + + def test_schema_dict(self): + tpl_snippet = ''' + cpus: + type: integer + description: Number of CPUs for the server. + ''' + schema = yamlparser.simple_parse(tpl_snippet) + cpus_schema = Schema('cpus', schema['cpus']) + self.assertEqual(len(cpus_schema), 2) + self.assertEqual('integer', cpus_schema.type) + self.assertEqual('Number of CPUs for the server.', + cpus_schema.description) + self.assertEqual(True, cpus_schema.required) + self.assertIsNone(cpus_schema.default) + + def test_schema_not_dict(self): + tpl_snippet = ''' + cpus: + - type: integer + - description: Number of CPUs for the server. + ''' + schema = yamlparser.simple_parse(tpl_snippet) + error = self.assertRaises(exception.InvalidSchemaError, Schema, + 'cpus', schema['cpus']) + self.assertEqual(_('Schema definition of "cpus" must be a dict.'), + str(error)) + + def test_schema_miss_type(self): + tpl_snippet = ''' + cpus: + description: Number of CPUs for the server. + ''' + schema = yamlparser.simple_parse(tpl_snippet) + error = self.assertRaises(exception.InvalidSchemaError, Schema, + 'cpus', schema['cpus']) + self.assertEqual(_('Schema definition of "cpus" must have a "type" ' + 'attribute.'), str(error)) + + def test_schema_none_description(self): + tpl_snippet = ''' + cpus: + type: integer + ''' + schema = yamlparser.simple_parse(tpl_snippet) + cpus_schema = Schema('cpus', schema['cpus']) + self.assertEqual('', cpus_schema.description) + + def test_invalid_constraint_type(self): + schema = {'invalid_type': 2} + error = self.assertRaises(exception.InvalidSchemaError, Constraint, + 'prop', Schema.INTEGER, + schema) + self.assertEqual(_('Invalid property "invalid_type".'), + str(error)) + + def test_invalid_prop_type(self): + schema = {'length': 5} + error = self.assertRaises(exception.InvalidSchemaError, Constraint, + 'prop', Schema.INTEGER, + schema) + self.assertEqual(_('Property "length" is not valid for data type ' + '"integer".'), str(error)) + + def test_invalid_validvalues(self): + schema = {'valid_values': 2} + error = self.assertRaises(exception.InvalidSchemaError, Constraint, + 'prop', Schema.INTEGER, + schema) + self.assertEqual(_('The property "valid_values" expects a list.'), + str(error)) + + def test_validvalues_validate(self): + schema = {'valid_values': [2, 4, 6, 8]} + constraint = Constraint('prop', Schema.INTEGER, schema) + self.assertIsNone(constraint.validate(4)) + + def test_validvalues_validate_fail(self): + schema = {'valid_values': [2, 4, 6, 8]} + constraint = Constraint('prop', Schema.INTEGER, schema) + error = self.assertRaises(exception.ValidationError, + constraint.validate, 5) + self.assertEqual(_('The value "5" of property "prop" is not valid. ' + 'Expected a value from "[2, 4, 6, 8]".'), + str(error)) + + def test_invalid_in_range(self): + snippet = 'in_range: {2, 6}' + schema = yaml.load(snippet) + error = self.assertRaises(exception.InvalidSchemaError, Constraint, + 'prop', Schema.INTEGER, + schema) + self.assertEqual(_('The property "in_range" expects a list.'), + str(error)) + + def test_in_range_min_max(self): + schema = {'in_range': [2, 6]} + constraint = Constraint('prop', Schema.INTEGER, schema) + self.assertEqual(2, constraint.min) + self.assertEqual(6, constraint.max) + + def test_in_range_validate(self): + schema = {'in_range': [2, 6]} + constraint = Constraint('prop', Schema.INTEGER, schema) + self.assertIsNone(constraint.validate(2)) + self.assertIsNone(constraint.validate(4)) + self.assertIsNone(constraint.validate(6)) + + def test_in_range_validate_fail(self): + schema = {'in_range': [2, 6]} + constraint = Constraint('prop', Schema.INTEGER, schema) + error = self.assertRaises(exception.ValidationError, + constraint.validate, 8) + self.assertEqual(_('The value "8" of property "prop" is out of range ' + '"(min:2, max:6)".'), str(error)) + + def test_equal_validate(self): + schema = {'equal': 4} + constraint = Constraint('prop', Schema.INTEGER, schema) + self.assertIsNone(constraint.validate(4)) + + def test_equal_validate_fail(self): + schema = {'equal': 4} + constraint = Constraint('prop', Schema.INTEGER, schema) + error = self.assertRaises(exception.ValidationError, + constraint.validate, 8) + self.assertEqual('The value "8" of property "prop" is not equal to ' + '"4".', str(error)) + + def test_greater_than_validate(self): + schema = {'greater_than': 4} + constraint = Constraint('prop', Schema.INTEGER, schema) + self.assertIsNone(constraint.validate(6)) + + def test_greater_than_validate_fail(self): + schema = {'greater_than': 4} + constraint = Constraint('prop', Schema.INTEGER, schema) + error = self.assertRaises(exception.ValidationError, + constraint.validate, 3) + self.assertEqual(_('The value "3" of property "prop" must be greater ' + 'than "4".'), str(error)) + + error = self.assertRaises(exception.ValidationError, + constraint.validate, 4) + self.assertEqual(_('The value "4" of property "prop" must be greater ' + 'than "4".'), str(error)) + + def test_greater_than_invalid(self): + snippet = 'greater_than: {4}' + schema = yaml.load(snippet) + error = self.assertRaises(exception.InvalidSchemaError, Constraint, + 'prop', Schema.INTEGER, + schema) + self.assertEqual(_('The property "greater_than" expects comparable ' + 'values.'), str(error)) + + def test_greater_or_equal_validate(self): + schema = {'greater_or_equal': 3.9} + constraint = Constraint('prop', Schema.FLOAT, schema) + self.assertIsNone(constraint.validate(3.9)) + self.assertIsNone(constraint.validate(4.0)) + + def test_greater_or_equal_validate_fail(self): + schema = {'greater_or_equal': 3.9} + constraint = Constraint('prop', Schema.FLOAT, schema) + error = self.assertRaises(exception.ValidationError, + constraint.validate, 3.0) + self.assertEqual(_('The value "3.0" of property "prop" must be ' + 'greater than or equal to "3.9".'), + str(error)) + + error = self.assertRaises(exception.ValidationError, + constraint.validate, 3.8) + self.assertEqual(_('The value "3.8" of property "prop" must be ' + 'greater than or equal to "3.9".'), + str(error)) + + def test_greater_or_equal_invalid(self): + snippet = 'greater_or_equal: {3.9}' + schema = yaml.load(snippet) + error = self.assertRaises(exception.InvalidSchemaError, Constraint, + 'prop', Schema.INTEGER, + schema) + self.assertEqual(_('The property "greater_or_equal" expects ' + 'comparable values.'), str(error)) + + def test_less_than_validate(self): + schema = {'less_than': datetime.date(2014, 0o7, 25)} + constraint = Constraint('prop', Schema.TIMESTAMP, schema) + self.assertIsNone(constraint.validate(datetime.date(2014, 0o7, 20))) + self.assertIsNone(constraint.validate(datetime.date(2014, 0o7, 24))) + + def test_less_than_validate_fail(self): + schema = {'less_than': datetime.date(2014, 0o7, 25)} + constraint = Constraint('prop', Schema.TIMESTAMP, schema) + error = self.assertRaises(exception.ValidationError, + constraint.validate, + datetime.date(2014, 0o7, 25)) + self.assertEqual(_('The value "2014-07-25" of property "prop" must be ' + 'less than "2014-07-25".'), + str(error)) + + error = self.assertRaises(exception.ValidationError, + constraint.validate, + datetime.date(2014, 0o7, 27)) + self.assertEqual(_('The value "2014-07-27" of property "prop" must be ' + 'less than "2014-07-25".'), + str(error)) + + def test_less_than_invalid(self): + snippet = 'less_than: {3.9}' + schema = yaml.load(snippet) + error = self.assertRaises(exception.InvalidSchemaError, Constraint, + 'prop', Schema.INTEGER, + schema) + self.assertEqual(_('The property "less_than" expects comparable ' + 'values.'), str(error)) + + def test_less_or_equal_validate(self): + schema = {'less_or_equal': 4} + constraint = Constraint('prop', Schema.INTEGER, schema) + self.assertIsNone(constraint.validate(4)) + self.assertIsNone(constraint.validate(3)) + + def test_less_or_equal_validate_fail(self): + schema = {'less_or_equal': 4} + constraint = Constraint('prop', Schema.INTEGER, schema) + error = self.assertRaises(exception.ValidationError, + constraint.validate, 5) + self.assertEqual(_('The value "5" of property "prop" must be less ' + 'than or equal to "4".'), str(error)) + + def test_less_or_equal_invalid(self): + snippet = 'less_or_equal: {3.9}' + schema = yaml.load(snippet) + error = self.assertRaises(exception.InvalidSchemaError, Constraint, + 'prop', Schema.INTEGER, + schema) + self.assertEqual(_('The property "less_or_equal" expects comparable ' + 'values.'), str(error)) + + def test_invalid_length(self): + schema = {'length': 'four'} + error = self.assertRaises(exception.InvalidSchemaError, Constraint, + 'prop', Schema.STRING, + schema) + self.assertEqual(_('The property "length" expects an integer.'), + str(error)) + + schema = {'length': 4.5} + error = self.assertRaises(exception.InvalidSchemaError, Constraint, + 'prop', Schema.STRING, + schema) + self.assertEqual(_('The property "length" expects an integer.'), + str(error)) + + def test_length_validate(self): + schema = {'length': 4} + constraint = Constraint('prop', Schema.STRING, schema) + self.assertIsNone(constraint.validate('abcd')) + + def test_length_validate_fail(self): + schema = {'length': 4} + constraint = Constraint('prop', Schema.STRING, schema) + error = self.assertRaises(exception.ValidationError, + constraint.validate, 'abc') + self.assertEqual(_('Length of value "abc" of property "prop" must ' + 'be equal to "4".'), str(error)) + + error = self.assertRaises(exception.ValidationError, + constraint.validate, + 'abcde') + self.assertEqual(_('Length of value "abcde" of property "prop" must ' + 'be equal to "4".'), str(error)) + + def test_invalid_min_length(self): + schema = {'min_length': 'four'} + error = self.assertRaises(exception.InvalidSchemaError, Constraint, + 'prop', Schema.STRING, + schema) + self.assertEqual(_('The property "min_length" expects an integer.'), + str(error)) + + def test_min_length_validate(self): + schema = {'min_length': 4} + constraint = Constraint('prop', Schema.STRING, schema) + self.assertIsNone(constraint.validate('abcd')) + self.assertIsNone(constraint.validate('abcde')) + + def test_min_length_validate_fail(self): + schema = {'min_length': 4} + constraint = Constraint('prop', Schema.STRING, schema) + error = self.assertRaises(exception.ValidationError, + constraint.validate, 'abc') + self.assertEqual(_('Length of value "abc" of property "prop" must ' + 'be at least "4".'), str(error)) + + def test_invalid_max_length(self): + schema = {'max_length': 'four'} + error = self.assertRaises(exception.InvalidSchemaError, Constraint, + 'prop', Schema.STRING, + schema) + self.assertEqual(_('The property "max_length" expects an integer.'), + str(error)) + + def test_max_length_validate(self): + schema = {'max_length': 4} + constraint = Constraint('prop', Schema.STRING, schema) + self.assertIsNone(constraint.validate('abcd')) + self.assertIsNone(constraint.validate('abc')) + + def test_max_length_validate_fail(self): + schema = {'max_length': 4} + constraint = Constraint('prop', Schema.STRING, schema) + error = self.assertRaises(exception.ValidationError, + constraint.validate, + 'abcde') + self.assertEqual(_('Length of value "abcde" of property "prop" ' + 'must be no greater than "4".'), + str(error)) + + def test_pattern_validate(self): + schema = {'pattern': '[0-9]*'} + constraint = Constraint('prop', Schema.STRING, schema) + self.assertIsNone(constraint.validate('123')) + + def test_pattern_validate_fail(self): + schema = {'pattern': '[0-9]*'} + constraint = Constraint('prop', Schema.STRING, schema) + error = self.assertRaises(exception.ValidationError, + constraint.validate, 'abc') + self.assertEqual(_('The value "abc" of property "prop" does not ' + 'match pattern "[0-9]*".'), str(error)) + + def test_min_length_with_map(self): + schema = {'min_length': 1} + constraint = Constraint('prop', Schema.MAP, schema) + try: + constraint.validate({"k": "v"}) + except Exception as ex: + self.fail(ex) + + def test_max_length_with_map(self): + schema = {'max_length': 1} + constraint = Constraint('prop', Schema.MAP, schema) + try: + constraint.validate({"k": "v"}) + except Exception as ex: + self.fail(ex) diff --git a/tosca2heat/tosca-parser/toscaparser/tests/test_custom_relationships.py b/tosca2heat/tosca-parser/toscaparser/tests/test_custom_relationships.py new file mode 100644 index 0000000..71de0c2 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/test_custom_relationships.py @@ -0,0 +1,35 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import os + +from toscaparser.tests.base import TestCase +from toscaparser.tosca_template import ToscaTemplate + + +class CustomRelationshipTypesTest(TestCase): + + '''TOSCA template.''' + tosca_tpl = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "data/test_custom_relationships.yaml") + tosca = ToscaTemplate(tosca_tpl) + + def test_version(self): + self.assertEqual(self.tosca.version, "tosca_simple_yaml_1_0") + + def test_custom_types(self): + expected_custom_types = ['tosca.capabilities.HA', + 'tosca.nodes.HACompute', + 'tosca.relationships.HA'] + self.assertItemsEqual(self.tosca.topology_template.custom_defs.keys(), + expected_custom_types) diff --git a/tosca2heat/tosca-parser/toscaparser/tests/test_datatypes.py b/tosca2heat/tosca-parser/toscaparser/tests/test_datatypes.py new file mode 100644 index 0000000..0e613b2 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/test_datatypes.py @@ -0,0 +1,408 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import os + +from testtools.testcase import skip +from toscaparser.common import exception +from toscaparser.dataentity import DataEntity +from toscaparser.elements.datatype import DataType +from toscaparser.parameters import Input +from toscaparser.tests.base import TestCase +from toscaparser.tosca_template import ToscaTemplate +from toscaparser.utils.gettextutils import _ +from toscaparser.utils import yamlparser + + +class DataTypeTest(TestCase): + + custom_type_schema = ''' + tosca.my.datatypes.PeopleBase: + properties: + name: + type: string + required: true + constraints: + - min_length: 2 + gender: + type: string + default: unknown + + tosca.my.datatypes.People: + derived_from: tosca.my.datatypes.PeopleBase + properties: + addresses: + type: map + required: false + entry_schema: + type: string + contacts: + type: list + required: false + entry_schema: + type: tosca.my.datatypes.ContactInfo + + tosca.my.datatypes.ContactInfo: + description: simple contact information + properties: + contact_name: + type: string + required: true + constraints: + - min_length: 2 + contact_email: + type: string + contact_phone: + type: string + + tosca.my.datatypes.TestLab: + properties: + temperature: + type: range + required: false + constraints: + - in_range: [-256, UNBOUNDED] + humidity: + type: range + required: false + constraints: + - in_range: [-256, INFINITY] + ''' + custom_type_def = yamlparser.simple_parse(custom_type_schema) + + def test_empty_template(self): + value_snippet = '' + value = yamlparser.simple_parse(value_snippet) + self.assertEqual(value, {}) + + # TODO(Matt) - opened as bug 1555300 + # Need a test for PortSpec normative data type + # that tests the spec. requirement: "A valid PortSpec + # must have at least one of the following properties: + # target, target_range, source or source_range." + # TODO(Matt) - opened as bug 1555310 + # test PortSpec value for source and target + # against the source_range and target_range + # when specified. + def test_built_in_datatype(self): + value_snippet = ''' + private_network: + network_name: private + network_id: 3e54214f-5c09-1bc9-9999-44100326da1b + addresses: [ 10.111.128.10 ] + ''' + value = yamlparser.simple_parse(value_snippet) + data = DataEntity('tosca.datatypes.network.NetworkInfo', + value.get('private_network')) + self.assertIsNotNone(data.validate()) + + value_snippet = ''' + portspec_valid: + protocol: tcp + ''' + value = yamlparser.simple_parse(value_snippet) + data = DataEntity('tosca.datatypes.network.PortSpec', + value.get('portspec_valid')) + self.assertIsNotNone(data.validate()) + + value_snippet = ''' + portspec_invalid: + protocol: xyz + ''' + value = yamlparser.simple_parse(value_snippet) + data = DataEntity('tosca.datatypes.network.PortSpec', + value.get('portspec_invalid')) + err = self.assertRaises(exception.ValidationError, data.validate) + self.assertEqual(_('The value "xyz" of property "protocol" is not ' + 'valid. Expected a value from "[udp, tcp, igmp]".' + ), + err.__str__()) + + def test_built_in_datatype_with_short_name(self): + value_snippet = ''' + ethernet_port: + port_name: port1 + port_id: 2c0c7a37-691a-23a6-7709-2d10ad041467 + network_id: 3e54214f-5c09-1bc9-9999-44100326da1b + mac_address: f1:18:3b:41:92:1e + addresses: [ 172.24.9.102 ] + ''' + value = yamlparser.simple_parse(value_snippet) + data = DataEntity('PortInfo', value.get('ethernet_port')) + self.assertIsNotNone(data.validate()) + + def test_built_in_datatype_without_properties(self): + value_snippet = ''' + 2 + ''' + value = yamlparser.simple_parse(value_snippet) + datatype = DataType('PortDef') + self.assertEqual('integer', datatype.value_type) + data = DataEntity('PortDef', value) + self.assertIsNotNone(data.validate()) + + @skip('The example in TOSCA spec may have some problem.') + def test_built_in_nested_datatype(self): + value_snippet = ''' + user_port: + protocol: tcp + target: [50000] + source: [9000] + ''' + value = yamlparser.simple_parse(value_snippet) + data = DataEntity('PortSpec', value.get('user_port')) + self.assertIsNotNone(data.validate()) + + def test_built_in_nested_datatype_portdef(self): + tpl_snippet = ''' + inputs: + db_port: + type: PortDef + description: Port for the MySQL database + ''' + inputs = yamlparser.simple_parse(tpl_snippet)['inputs'] + name, attrs = list(inputs.items())[0] + input = Input(name, attrs) + self.assertIsNone(input.validate(3360)) + err = self.assertRaises(exception.ValidationError, input.validate, + 336000) + self.assertEqual(_('The value "336000" of property "None" is out of ' + 'range "(min:1, max:65535)".'), + err.__str__()) + + def test_custom_datatype(self): + value_snippet = ''' + name: Mike + gender: male + ''' + value = yamlparser.simple_parse(value_snippet) + data = DataEntity('tosca.my.datatypes.PeopleBase', value, + DataTypeTest.custom_type_def) + self.assertIsNotNone(data.validate()) + + def test_custom_datatype_with_parent(self): + value_snippet = ''' + name: Mike + gender: male + contacts: + - {contact_name: Tom, + contact_email: tom@email.com, + contact_phone: '123456789'} + - {contact_name: Jerry, + contact_email: jerry@email.com, + contact_phone: '321654987'} + ''' + value = yamlparser.simple_parse(value_snippet) + data = DataEntity('tosca.my.datatypes.People', value, + DataTypeTest.custom_type_def) + self.assertIsNotNone(data.validate()) + + # [Tom, Jerry] is not a dict, it can't be a value of datatype PeopleBase + def test_non_dict_value_for_datatype(self): + value_snippet = ''' + [Tom, Jerry] + ''' + value = yamlparser.simple_parse(value_snippet) + data = DataEntity('tosca.my.datatypes.PeopleBase', value, + DataTypeTest.custom_type_def) + error = self.assertRaises(exception.TypeMismatchError, data.validate) + self.assertEqual(_('[\'Tom\', \'Jerry\'] must be of type ' + '"tosca.my.datatypes.PeopleBase".'), + error.__str__()) + + # 'nema' is an invalid field name + def test_field_error_in_dataentity(self): + value_snippet = ''' + nema: Mike + gender: male + ''' + value = yamlparser.simple_parse(value_snippet) + data = DataEntity('tosca.my.datatypes.PeopleBase', value, + DataTypeTest.custom_type_def) + error = self.assertRaises(exception.UnknownFieldError, data.validate) + self.assertEqual(_('Data value of type ' + '"tosca.my.datatypes.PeopleBase" contains unknown ' + 'field "nema". Refer to the definition to verify ' + 'valid values.'), + error.__str__()) + + def test_default_field_in_dataentity(self): + value_snippet = ''' + name: Mike + ''' + value = yamlparser.simple_parse(value_snippet) + data = DataEntity('tosca.my.datatypes.PeopleBase', value, + DataTypeTest.custom_type_def) + data = data.validate() + self.assertEqual('unknown', data.get('gender')) + + # required field 'name' is missing + def test_missing_field_in_dataentity(self): + value_snippet = ''' + gender: male + ''' + value = yamlparser.simple_parse(value_snippet) + data = DataEntity('tosca.my.datatypes.PeopleBase', value, + DataTypeTest.custom_type_def) + error = self.assertRaises(exception.MissingRequiredFieldError, + data.validate) + self.assertEqual(_('Data value of type ' + '"tosca.my.datatypes.PeopleBase" is missing ' + 'required field "[\'name\']".'), + error.__str__()) + + # the value of name field is not a string + def test_type_error_in_dataentity(self): + value_snippet = ''' + name: 123 + gender: male + ''' + value = yamlparser.simple_parse(value_snippet) + data = DataEntity('tosca.my.datatypes.PeopleBase', value, + DataTypeTest.custom_type_def) + error = self.assertRaises(ValueError, data.validate) + self.assertEqual(_('"123" is not a string.'), error.__str__()) + + # the value of name doesn't meet the defined constraint + def test_value_error_in_dataentity(self): + value_snippet = ''' + name: M + gender: male + ''' + value = yamlparser.simple_parse(value_snippet) + data = DataEntity('tosca.my.datatypes.PeopleBase', value, + DataTypeTest.custom_type_def) + error = self.assertRaises(exception.ValidationError, data.validate) + self.assertEqual(_('Length of value "M" of property "name" must be ' + 'at least "2".'), error.__str__()) + + # value of addresses doesn't fit the entry_schema + def test_validation_in_collection_entry(self): + value_snippet = ''' + name: Mike + gender: male + addresses: {Home: 1, Office: 9 bar avenue} + ''' + value = yamlparser.simple_parse(value_snippet) + data = DataEntity('tosca.my.datatypes.People', value, + DataTypeTest.custom_type_def) + error = self.assertRaises(ValueError, data.validate) + self.assertEqual(_('"1" is not a string.'), error.__str__()) + + # 'contact_pone' is an invalid attribute name in nested datatype below + def test_validation_in_nested_datatype(self): + value_snippet = ''' + name: Mike + gender: male + contacts: + - {contact_name: Tom, + contact_email: tom@email.com, + contact_pone: '123456789'} + - {contact_name: Jerry, + contact_email: jerry@email.com, + contact_phone: '321654987'} + ''' + value = yamlparser.simple_parse(value_snippet) + data = DataEntity('tosca.my.datatypes.People', value, + DataTypeTest.custom_type_def) + error = self.assertRaises(exception.UnknownFieldError, data.validate) + self.assertEqual(_('Data value of type ' + '"tosca.my.datatypes.ContactInfo" contains unknown ' + 'field "contact_pone". Refer to the definition to ' + 'verify valid values.'), + error.__str__()) + + def test_datatype_in_current_template(self): + tpl_path = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "data/datatypes/test_custom_datatypes_in_current_template.yaml") + self.assertIsNotNone(ToscaTemplate(tpl_path)) + + def test_datatype_in_template_positive(self): + tpl_path = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "data/datatypes/test_custom_datatypes_positive.yaml") + self.assertIsNotNone(ToscaTemplate(tpl_path)) + + def test_datatype_in_template_invalid_value(self): + tpl_path = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "data/datatypes/test_custom_datatypes_value_error.yaml") + self.assertRaises(exception.ValidationError, ToscaTemplate, tpl_path) + exception.ExceptionCollector.assertExceptionMessage( + ValueError, + _('"[\'1 foo street\', \'9 bar avenue\']" is not a map.')) + + def test_datatype_in_template_nested_datatype_error(self): + tpl_path = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "data/datatypes/test_custom_datatypes_nested_datatype_error.yaml") + self.assertRaises(exception.ValidationError, ToscaTemplate, tpl_path) + exception.ExceptionCollector.assertExceptionMessage( + ValueError, _('"123456789" is not a string.')) + + def test_valid_range_type(self): + value_snippet = ''' + user_port: + protocol: tcp + target_range: [20000, 60000] + source_range: [1000, 3000] + ''' + value = yamlparser.simple_parse(value_snippet) + data = DataEntity('PortSpec', value.get('user_port')) + self.assertIsNotNone(data.validate()) + + def test_invalid_range_datatype(self): + value_snippet = ''' + user_port: + protocol: tcp + target_range: [20000] + ''' + value = yamlparser.simple_parse(value_snippet) + data = DataEntity('PortSpec', value.get('user_port')) + err = self.assertRaises(ValueError, data.validate) + self.assertEqual(_('"[20000]" is not a valid range.' + ), + err.__str__()) + + value_snippet = ''' + user_port: + protocol: tcp + target_range: [20000, 3000] + ''' + value = yamlparser.simple_parse(value_snippet) + data = DataEntity('PortSpec', value.get('user_port')) + err = self.assertRaises(ValueError, data.validate) + self.assertEqual(_('"[20000, 3000]" is not a valid range.' + ), + err.__str__()) + + value_snippet = ''' + humidity: [-100, 100] + ''' + value = yamlparser.simple_parse(value_snippet) + data = DataEntity('tosca.my.datatypes.TestLab', + value, DataTypeTest.custom_type_def) + err = self.assertRaises(exception.InvalidSchemaError, + lambda: data.validate()) + self.assertEqual(_('The property "in_range" expects comparable values.' + ), + err.__str__()) + + def test_range_unbounded(self): + value_snippet = ''' + temperature: [-100, 999999] + ''' + value = yamlparser.simple_parse(value_snippet) + data = DataEntity('tosca.my.datatypes.TestLab', value, + DataTypeTest.custom_type_def) + self.assertIsNotNone(data.validate()) diff --git a/tosca2heat/tosca-parser/toscaparser/tests/test_exception.py b/tosca2heat/tosca-parser/toscaparser/tests/test_exception.py new file mode 100644 index 0000000..a404f4f --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/test_exception.py @@ -0,0 +1,42 @@ +# Licensed 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 toscaparser.common import exception +from toscaparser.tests.base import TestCase +from toscaparser.utils.gettextutils import _ + + +class ExceptionTest(TestCase): + + def setUp(self): + super(TestCase, self).setUp() + exception.TOSCAException.set_fatal_format_exception(False) + + def test_message(self): + ex = exception.MissingRequiredFieldError(what='Template', + required='type') + self.assertEqual(_('Template is missing required field "type".'), + ex.__str__()) + + def test_set_flag(self): + exception.TOSCAException.set_fatal_format_exception('True') + self.assertFalse( + exception.TOSCAException._FATAL_EXCEPTION_FORMAT_ERRORS) + + def test_format_error(self): + ex = exception.UnknownFieldError(what='Template') + self.assertEqual(_('An unknown exception occurred.'), ex.__str__(),) + self.assertRaises(KeyError, self._formate_exception) + + def _formate_exception(self): + exception.UnknownFieldError.set_fatal_format_exception(True) + raise exception.UnknownFieldError(what='Template') diff --git a/tosca2heat/tosca-parser/toscaparser/tests/test_functions.py b/tosca2heat/tosca-parser/toscaparser/tests/test_functions.py new file mode 100644 index 0000000..2a6225d --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/test_functions.py @@ -0,0 +1,324 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import os +import six +from toscaparser.common import exception +from toscaparser import functions +from toscaparser.tests.base import TestCase +from toscaparser.tosca_template import ToscaTemplate +from toscaparser.utils.gettextutils import _ + + +class IntrinsicFunctionsTest(TestCase): + + tosca_tpl = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "data/tosca_single_instance_wordpress.yaml") + params = {'db_name': 'my_wordpress', 'db_user': 'my_db_user'} + tosca = ToscaTemplate(tosca_tpl, parsed_params=params) + + def _get_node(self, node_name, tosca=None): + if tosca is None: + tosca = self.tosca + return [ + node for node in tosca.nodetemplates + if node.name == node_name][0] + + def _get_operation(self, interfaces, operation): + return [ + interface for interface in interfaces + if interface.name == operation][0] + + def _get_property(self, node_template, property_name): + return [prop.value for prop in node_template.get_properties_objects() + if prop.name == property_name][0] + + def _get_inputs_dict(self): + inputs = {} + for input in self.tosca.inputs: + inputs[input.name] = input.default + return inputs + + def _get_input(self, name): + self._get_inputs_dict()[name] + + def test_get_property(self): + wordpress = self._get_node('wordpress') + operation = self._get_operation(wordpress.interfaces, 'configure') + wp_db_password = operation.inputs['wp_db_password'] + self.assertTrue(isinstance(wp_db_password, functions.GetProperty)) + result = wp_db_password.result() + self.assertEqual('wp_pass', result) + + def test_get_property_with_input_param(self): + wordpress = self._get_node('wordpress') + operation = self._get_operation(wordpress.interfaces, 'configure') + wp_db_user = operation.inputs['wp_db_user'] + self.assertTrue(isinstance(wp_db_user, functions.GetProperty)) + result = wp_db_user.result() + self.assertEqual('my_db_user', result) + + def test_unknown_capability_property(self): + self.assertRaises(exception.ValidationError, self._load_template, + 'functions/test_unknown_capability_property.yaml') + exception.ExceptionCollector.assertExceptionMessage( + KeyError, + _('\'Property "unknown" was not found in capability ' + '"database_endpoint" of node template "database" referenced ' + 'from node template "database".\'')) + + def test_get_input_in_properties(self): + mysql_dbms = self._get_node('mysql_dbms') + expected_inputs = ['db_root_pwd', 'db_port'] + props = mysql_dbms.get_properties() + for key in props.keys(): + prop = props[key] + self.assertTrue(isinstance(prop.value, functions.GetInput)) + expected_inputs.remove(prop.value.input_name) + self.assertListEqual(expected_inputs, []) + + def test_get_input_validation(self): + self.assertRaises( + exception.ValidationError, self._load_template, + 'functions/test_unknown_input_in_property.yaml') + exception.ExceptionCollector.assertExceptionMessage( + exception.UnknownInputError, + _('Unknown input "objectstore_name".')) + + self.assertRaises( + exception.ValidationError, self._load_template, + 'functions/test_unknown_input_in_interface.yaml') + exception.ExceptionCollector.assertExceptionMessage( + exception.UnknownInputError, + _('Unknown input "image_id".')) + + self.assertRaises( + exception.ValidationError, self._load_template, + 'functions/test_invalid_function_signature.yaml') + exception.ExceptionCollector.assertExceptionMessage( + ValueError, + _('Expected one argument for function "get_input" but received ' + '"[\'cpus\', \'cpus\']".')) + + def test_get_input_default_value_result(self): + mysql_dbms = self._get_node('mysql_dbms') + dbms_port = self._get_property(mysql_dbms, 'port') + self.assertEqual(3306, dbms_port.result()) + dbms_root_password = self._get_property(mysql_dbms, + 'root_password') + self.assertIsNone(dbms_root_password.result()) + + def test_get_property_with_host(self): + tosca_tpl = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "data/functions/test_get_property_with_host.yaml") + mysql_database = self._get_node('mysql_database', + ToscaTemplate(tosca_tpl)) + operation = self._get_operation(mysql_database.interfaces, 'configure') + db_port = operation.inputs['db_port'] + self.assertTrue(isinstance(db_port, functions.GetProperty)) + result = db_port.result() + self.assertEqual(3306, result) + test = operation.inputs['test'] + self.assertTrue(isinstance(test, functions.GetProperty)) + result = test.result() + self.assertEqual(1, result) + + def test_get_property_with_nested_params(self): + tosca_tpl = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "data/functions/tosca_nested_property_names_indexes.yaml") + webserver = self._get_node('wordpress', ToscaTemplate(tosca_tpl)) + operation = self._get_operation(webserver.interfaces, 'configure') + wp_endpoint_prot = operation.inputs['wp_endpoint_protocol'] + self.assertTrue(isinstance(wp_endpoint_prot, functions.GetProperty)) + self.assertEqual('tcp', wp_endpoint_prot.result()) + wp_list_prop = operation.inputs['wp_list_prop'] + self.assertTrue(isinstance(wp_list_prop, functions.GetProperty)) + self.assertEqual(3, wp_list_prop.result()) + + def test_get_property_with_capabilties_inheritance(self): + tosca_tpl = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "data/functions/test_capabilties_inheritance.yaml") + some_node = self._get_node('some_node', ToscaTemplate(tosca_tpl)) + operation = self._get_operation(some_node.interfaces, 'configure') + some_input = operation.inputs['some_input'] + self.assertTrue(isinstance(some_input, functions.GetProperty)) + self.assertEqual('someval', some_input.result()) + + def test_get_property_source_target_keywords(self): + tosca_tpl = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "data/functions/test_get_property_source_target_keywords.yaml") + tosca = ToscaTemplate(tosca_tpl) + + for node in tosca.nodetemplates: + for relationship, trgt in node.relationships.items(): + rel_template = trgt.get_relationship_template()[0] + break + + operation = self._get_operation(rel_template.interfaces, + 'pre_configure_source') + target_test = operation.inputs['target_test'] + self.assertTrue(isinstance(target_test, functions.GetProperty)) + self.assertEqual(1, target_test.result()) + source_port = operation.inputs['source_port'] + self.assertTrue(isinstance(source_port, functions.GetProperty)) + self.assertEqual(3306, source_port.result()) + + +class GetAttributeTest(TestCase): + + def _load_template(self, filename): + return ToscaTemplate(os.path.join( + os.path.dirname(os.path.abspath(__file__)), + 'data', + filename)) + + def _get_operation(self, interfaces, operation): + return [ + interface for interface in interfaces + if interface.name == operation][0] + + def test_get_attribute_in_outputs(self): + tpl = self._load_template('tosca_single_instance_wordpress.yaml') + website_url_output = [ + x for x in tpl.outputs if x.name == 'website_url'][0] + self.assertIsInstance(website_url_output.value, functions.GetAttribute) + self.assertEqual('server', website_url_output.value.node_template_name) + self.assertEqual('private_address', + website_url_output.value.attribute_name) + + def test_get_attribute_invalid_args(self): + expected_msg = _('Expected arguments: "node-template-name", ' + '"attribute-name"') + err = self.assertRaises(ValueError, + functions.get_function, None, None, + {'get_attribute': []}) + self.assertIn(expected_msg, six.text_type(err)) + err = self.assertRaises(ValueError, + functions.get_function, None, None, + {'get_attribute': ['x']}) + self.assertIn(expected_msg, six.text_type(err)) + err = self.assertRaises(ValueError, + functions.get_function, None, None, + {'get_attribute': ['x', 'y', 'z', 'k']}) + self.assertIn(expected_msg, six.text_type(err)) + + def test_get_attribute_unknown_node_template_name(self): + self.assertRaises( + exception.ValidationError, self._load_template, + 'functions/test_get_attribute_unknown_node_template_name.yaml') + exception.ExceptionCollector.assertExceptionMessage( + KeyError, + _('\'Node template "unknown_node_template" was not found.\'')) + + def test_get_attribute_unknown_attribute(self): + self.assertRaises( + exception.ValidationError, self._load_template, + 'functions/test_get_attribute_unknown_attribute_name.yaml') + exception.ExceptionCollector.assertExceptionMessage( + KeyError, + _('\'Attribute "unknown_attribute" was not found in node template ' + '"server".\'')) + + def test_get_attribute_host_keyword(self): + tpl = self._load_template( + 'functions/test_get_attribute_host_keyword.yaml') + + def assert_get_attribute_host_functionality(node_template_name): + node = [x for x in tpl.nodetemplates + if x.name == node_template_name][0] + configure_op = [ + x for x in node.interfaces if x.name == 'configure'][0] + ip_addr_input = configure_op.inputs['ip_address'] + self.assertIsInstance(ip_addr_input, functions.GetAttribute) + self.assertEqual('server', + ip_addr_input.get_referenced_node_template().name) + + assert_get_attribute_host_functionality('dbms') + assert_get_attribute_host_functionality('database') + + def test_get_attribute_host_not_found(self): + self.assertRaises( + exception.ValidationError, self._load_template, + 'functions/test_get_attribute_host_not_found.yaml') + exception.ExceptionCollector.assertExceptionMessage( + ValueError, + _('"get_attribute: [ HOST, ... ]" was used in node template ' + '"server" but "tosca.relationships.HostedOn" was not found in ' + 'the relationship chain.')) + + def test_get_attribute_illegal_host_in_outputs(self): + self.assertRaises( + exception.ValidationError, self._load_template, + 'functions/test_get_attribute_illegal_host_in_outputs.yaml') + exception.ExceptionCollector.assertExceptionMessage( + ValueError, + _('"get_attribute: [ HOST, ... ]" is not allowed in "outputs" ' + 'section of the TOSCA template.')) + + def test_get_attribute_with_index(self): + self._load_template( + 'functions/test_get_attribute_with_index.yaml') + + def test_get_attribute_with_index_error(self): + self.assertRaises( + exception.ValidationError, self._load_template, + 'functions/test_get_attribute_with_index_error.yaml') + exception.ExceptionCollector.assertExceptionMessage( + ValueError, + _('Illegal arguments for function "get_attribute". ' + 'Expected arguments: "node-template-name", "attribute-name"')) + + def test_get_attribute_source_target_keywords(self): + tosca_tpl = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "data/functions/test_get_attribute_source_target_keywords.yaml") + tosca = ToscaTemplate(tosca_tpl) + + for node in tosca.nodetemplates: + for relationship, trgt in node.relationships.items(): + rel_template = trgt.get_relationship_template()[0] + break + + operation = self._get_operation(rel_template.interfaces, + 'pre_configure_source') + target_test = operation.inputs['target_test'] + self.assertTrue(isinstance(target_test, functions.GetAttribute)) + source_port = operation.inputs['source_port'] + self.assertTrue(isinstance(source_port, functions.GetAttribute)) + + +class ConcatTest(TestCase): + + def _load_template(self, filename): + return ToscaTemplate(os.path.join( + os.path.dirname(os.path.abspath(__file__)), + filename)) + + def test_validate_concat(self): + tosca = self._load_template("data/functions/test_concat.yaml") + server_url_output = [ + output for output in tosca.outputs if output.name == 'url'][0] + func = functions.get_function(self, tosca.outputs, + server_url_output.value) + self.assertIsInstance(func, functions.Concat) + + self.assertRaises(exception.ValidationError, self._load_template, + 'data/functions/test_concat_invalid.yaml') + exception.ExceptionCollector.assertExceptionMessage( + ValueError, + _('Invalid arguments for function "concat". Expected at least ' + 'one arguments.')) diff --git a/tosca2heat/tosca-parser/toscaparser/tests/test_prereq.py b/tosca2heat/tosca-parser/toscaparser/tests/test_prereq.py new file mode 100644 index 0000000..11f4471 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/test_prereq.py @@ -0,0 +1,230 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import os +import shutil +import zipfile + +from toscaparser.common.exception import URLException +from toscaparser.common.exception import ValidationError +from toscaparser.prereq.csar import CSAR +from toscaparser.tests.base import TestCase +import toscaparser.utils +from toscaparser.utils.gettextutils import _ + + +class CSARPrereqTest(TestCase): + + base_path = os.path.dirname(os.path.abspath(__file__)) + + def test_file_exists(self): + path = os.path.join(self.base_path, "data/CSAR/csar_not_there.zip") + csar = CSAR(path) + error = self.assertRaises(ValidationError, csar.validate) + self.assertEqual(_('"%s" does not exist.') % path, str(error)) + self.assertTrue(csar.temp_dir is None or + not os.path.exists(csar.temp_dir)) + + def test_file_is_zip(self): + path = os.path.join(self.base_path, "data/CSAR/csar_not_zip.zip") + csar = CSAR(path) + error = self.assertRaises(ValidationError, csar.validate) + self.assertEqual(_('"%s" is not a valid zip file.') % path, str(error)) + + def test_url_is_zip(self): + path = "https://github.com/openstack/tosca-parser/raw/master/" \ + "toscaparser/tests/data/CSAR/csar_not_zip.zip" + csar = CSAR(path, False) + error = self.assertRaises(ValidationError, csar.validate) + self.assertEqual(_('"%s" is not a valid zip file.') % path, str(error)) + self.assertTrue(csar.temp_dir is None or + not os.path.exists(csar.temp_dir)) + + def test_metadata_file_exists(self): + path = os.path.join(self.base_path, + "data/CSAR/csar_no_metadata_file.zip") + csar = CSAR(path) + error = self.assertRaises(ValidationError, csar.validate) + self.assertEqual(_('"%s" is not a valid CSAR as it does not contain ' + 'the required file "TOSCA.meta" in the folder ' + '"TOSCA-Metadata".') % path, str(error)) + self.assertTrue(csar.temp_dir is None or + not os.path.exists(csar.temp_dir)) + + def test_valid_metadata_file_exists(self): + path = os.path.join(self.base_path, + "data/CSAR/csar_wrong_metadata_file.zip") + csar = CSAR(path) + error = self.assertRaises(ValidationError, csar.validate) + self.assertEqual(_('"%s" is not a valid CSAR as it does not contain ' + 'the required file "TOSCA.meta" in the folder ' + '"TOSCA-Metadata".') % path, str(error)) + self.assertTrue(csar.temp_dir is None or + not os.path.exists(csar.temp_dir)) + + def test_metadata_is_yaml(self): + path = os.path.join(self.base_path, + "data/CSAR/csar_metadata_not_yaml.zip") + csar = CSAR(path) + error = self.assertRaises(ValidationError, csar.validate) + self.assertEqual(_('The file "TOSCA-Metadata/TOSCA.meta" in the CSAR ' + '"%s" does not contain valid YAML content.') % path, + str(error)) + self.assertTrue(csar.temp_dir is None or + not os.path.exists(csar.temp_dir)) + + def test_metadata_exists(self): + path = os.path.join(self.base_path, + "data/CSAR/csar_missing_metadata.zip") + csar = CSAR(path) + error = self.assertRaises(ValidationError, csar.validate) + self.assertEqual(_('The CSAR "%s" is missing the required metadata ' + '"Entry-Definitions" in ' + '"TOSCA-Metadata/TOSCA.meta".') % path, str(error)) + self.assertTrue(csar.temp_dir is None or + not os.path.exists(csar.temp_dir)) + + def test_entry_def_exists(self): + path = os.path.join(self.base_path, + "data/CSAR/csar_invalid_entry_def.zip") + csar = CSAR(path) + error = self.assertRaises(ValidationError, csar.validate) + self.assertEqual(_('The "Entry-Definitions" file defined in the CSAR ' + '"%s" does not exist.') % path, str(error)) + self.assertTrue(csar.temp_dir is None or + not os.path.exists(csar.temp_dir)) + + def test_csar_invalid_import_path(self): + path = os.path.join(self.base_path, + "data/CSAR/csar_wordpress_invalid_import_path.zip") + csar = CSAR(path) + error = self.assertRaises(ImportError, csar.validate) + self.assertEqual(_('Import "Invalid_import_path/wordpress.yaml" is' + ' not valid.'), str(error)) + self.assertTrue(csar.temp_dir is None or + not os.path.exists(csar.temp_dir)) + + def test_csar_invalid_import_url(self): + path = os.path.join(self.base_path, + "data/CSAR/csar_wordpress_invalid_import_url.zip") + csar = CSAR(path) + error = self.assertRaises(URLException, csar.validate) + self.assertEqual(_('Failed to reach server ' + '"https://raw.githubusercontent.com/openstack/' + 'tosca-parser/master/toscaparser/tests/data/CSAR/' + 'tosca_single_instance_wordpress/Definitions/' + 'wordpress1.yaml". Reason is: Not Found.'), + str(error)) + self.assertTrue(csar.temp_dir is None or + not os.path.exists(csar.temp_dir)) + + def test_csar_invalid_script_path(self): + path = os.path.join(self.base_path, + "data/CSAR/csar_wordpress_invalid_script_path.zip") + csar = CSAR(path) + error = self.assertRaises(ValueError, csar.validate) + self.assertTrue( + str(error) == _('The resource "Scripts/WordPress/install.sh" does ' + 'not exist.') or + str(error) == _('The resource "Scripts/WordPress/configure.sh" ' + 'does not exist.')) + self.assertTrue(csar.temp_dir is None or + not os.path.exists(csar.temp_dir)) + + def test_csar_invalid_script_url(self): + path = os.path.join(self.base_path, + "data/CSAR/csar_wordpress_invalid_script_url.zip") + csar = CSAR(path) + error = self.assertRaises(URLException, csar.validate) + self.assertEqual(_('The resource at ' + '"https://raw.githubusercontent.com/openstack/' + 'tosca-parser/master/toscaparser/tests/data/CSAR/' + 'tosca_single_instance_wordpress/Scripts/WordPress/' + 'install1.sh" cannot be accessed.'), + str(error)) + self.assertTrue(csar.temp_dir is None or + not os.path.exists(csar.temp_dir)) + + def test_valid_csar(self): + path = os.path.join(self.base_path, "data/CSAR/csar_hello_world.zip") + csar = CSAR(path) + self.assertTrue(csar.validate()) + self.assertTrue(csar.temp_dir is None or + not os.path.exists(csar.temp_dir)) + + def test_valid_csar_with_url_import_and_script(self): + path = os.path.join(self.base_path, "data/CSAR/csar_wordpress_with_url" + "_import_and_script.zip") + csar = CSAR(path) + self.assertTrue(csar.validate()) + self.assertTrue(csar.temp_dir is None or + not os.path.exists(csar.temp_dir)) + + def test_metadata_invalid_csar(self): + path = os.path.join(self.base_path, + "data/CSAR/csar_metadata_not_yaml.zip") + csar = CSAR(path) + error = self.assertRaises(ValidationError, csar.get_author) + self.assertEqual(_('The file "TOSCA-Metadata/TOSCA.meta" in the CSAR ' + '"%s" does not contain valid YAML content.') % path, + str(error)) + self.assertTrue(csar.temp_dir is None or + not os.path.exists(csar.temp_dir)) + + def test_metadata_valid_csar(self): + path = os.path.join(self.base_path, "data/CSAR/csar_hello_world.zip") + csar = CSAR(path) + expected_meta = {'TOSCA-Meta-File-Version': 1.0, + 'CSAR-Version': 1.1, + 'Created-By': 'OASIS TOSCA TC', + 'Entry-Definitions': 'tosca_helloworld.yaml'} + self.assertEqual(expected_meta, csar.get_metadata(), + 'The extracted metadata of the CSAR %(csar)s does ' + 'not match the expected metadata %(meta)s' + % {'csar': path, 'meta': expected_meta.__str__()}) + self.assertEqual(1.1, csar.get_version()) + self.assertEqual('OASIS TOSCA TC', csar.get_author()) + self.assertEqual('tosca_helloworld.yaml', csar.get_main_template()) + self.assertEqual('Template for deploying a single server with ' + 'predefined properties.', csar.get_description()) + self.assertTrue(csar.temp_dir is None or + not os.path.exists(csar.temp_dir)) + + def test_csar_main_template(self): + path = os.path.join(self.base_path, "data/CSAR/csar_hello_world.zip") + csar = CSAR(path) + yaml_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), + "data/tosca_helloworld.yaml") + expected_yaml = toscaparser.utils.yamlparser.load_yaml(yaml_file) + self.assertEqual(expected_yaml, csar.get_main_template_yaml()) + self.assertTrue(csar.temp_dir is None or + not os.path.exists(csar.temp_dir)) + + def test_decompress(self): + path = os.path.join(self.base_path, "data/CSAR/csar_hello_world.zip") + csar = CSAR(path) + csar.decompress() + zf = zipfile.ZipFile(path) + for name in zf.namelist(): + tmp_path = os.path.join(csar.temp_dir, name) + self.assertTrue(os.path.isdir(tmp_path) or + os.path.isfile(tmp_path)) + shutil.rmtree(csar.temp_dir) + self.assertTrue(csar.temp_dir is None or + not os.path.exists(csar.temp_dir)) + + def test_alternate_csar_extension(self): + path = os.path.join(self.base_path, "data/CSAR/csar_elk.csar") + csar = CSAR(path) + self.assertTrue(csar.validate()) + self.assertTrue(csar.temp_dir is None or + not os.path.exists(csar.temp_dir)) diff --git a/tosca2heat/tosca-parser/toscaparser/tests/test_properties.py b/tosca2heat/tosca-parser/toscaparser/tests/test_properties.py new file mode 100644 index 0000000..6b95537 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/test_properties.py @@ -0,0 +1,368 @@ +# Licensed 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 testtools import matchers + +from toscaparser.common import exception +from toscaparser.elements.property_definition import PropertyDef +from toscaparser.nodetemplate import NodeTemplate +from toscaparser.properties import Property +from toscaparser.tests.base import TestCase +from toscaparser.utils.gettextutils import _ +from toscaparser.utils import yamlparser + + +class PropertyTest(TestCase): + + def test_type(self): + test_property_schema = {'type': 'string'} + propertyInstance = Property('test_property', 'Hughes', + test_property_schema) + self.assertEqual('string', propertyInstance.type) + + def test_type_invalid(self): + test_property_schema = {'type': 'Fish'} + propertyInstance = Property('test_property', 'Hughes', + test_property_schema) + error = self.assertRaises(exception.InvalidTypeError, + propertyInstance.validate) + self.assertEqual(_('Type "Fish" is not a valid type.'), str(error)) + + def test_list(self): + test_property_schema = {'type': 'list'} + propertyInstance = Property('test_property', ['a', 'b'], + test_property_schema) + self.assertIsNone(propertyInstance.validate()) + self.assertEqual(['a', 'b'], propertyInstance.value) + + def test_list_invalid(self): + test_property_schema = {'type': 'list'} + propertyInstance = Property('test_property', 'a', + test_property_schema) + error = self.assertRaises(ValueError, propertyInstance.validate) + self.assertEqual(_('"a" is not a list.'), str(error)) + + def test_list_entry_schema(self): + test_property_schema = {'type': 'list', + 'entry_schema': {'type': 'string'}} + propertyInstance = Property('test_property', ['a', 'b'], + test_property_schema) + self.assertIsNone(propertyInstance.validate()) + self.assertEqual(['a', 'b'], propertyInstance.value) + + schema_snippet = ''' + type: list + entry_schema: + type: string + constraints: + - min_length: 2 + ''' + test_property_schema = yamlparser.simple_parse(schema_snippet) + propertyInstance = Property('test_property', ['ab', 'cd'], + test_property_schema) + self.assertIsNone(propertyInstance.validate()) + self.assertEqual(['ab', 'cd'], propertyInstance.value) + + def test_list_entry_schema_invalid(self): + test_property_schema = {'type': 'list', + 'entry_schema': {'type': 'integer'}} + propertyInstance = Property('test_property', [1, 'b'], + test_property_schema) + error = self.assertRaises(ValueError, propertyInstance.validate) + self.assertEqual(_('"b" is not an integer.'), str(error)) + + def test_map(self): + test_property_schema = {'type': 'map'} + propertyInstance = Property('test_property', {'a': 'b'}, + test_property_schema) + self.assertIsNone(propertyInstance.validate()) + self.assertEqual({'a': 'b'}, propertyInstance.value) + + def test_map_invalid(self): + test_property_schema = {'type': 'map'} + propertyInstance = Property('test_property', 12, + test_property_schema) + error = self.assertRaises(ValueError, propertyInstance.validate) + self.assertEqual(_('"12" is not a map.'), str(error)) + + def test_map_entry_schema(self): + test_property_schema = {'type': 'map', + 'entry_schema': {'type': 'boolean'}} + propertyInstance = Property('test_property', + {'valid': True, 'required': True}, + test_property_schema) + self.assertIsNone(propertyInstance.validate()) + self.assertEqual({'valid': True, 'required': True}, + propertyInstance.value) + + def test_map_entry_schema_invalid(self): + test_property_schema = {'type': 'map', + 'entry_schema': {'type': 'boolean'}} + propertyInstance = Property('test_property', + {'valid': True, 'contact_name': 123}, + test_property_schema) + error = self.assertRaises(ValueError, propertyInstance.validate) + self.assertEqual(_('"123" is not a boolean.'), str(error)) + + def test_boolean(self): + test_property_schema = {'type': 'boolean'} + propertyInstance = Property('test_property', 'true', + test_property_schema) + self.assertIsNone(propertyInstance.validate()) + propertyInstance = Property('test_property', True, + test_property_schema) + self.assertIsNone(propertyInstance.validate()) + self.assertEqual(True, propertyInstance.value) + + def test_boolean_invalid(self): + test_property_schema = {'type': 'boolean'} + propertyInstance = Property('test_property', 12, + test_property_schema) + error = self.assertRaises(ValueError, propertyInstance.validate) + self.assertEqual(_('"12" is not a boolean.'), str(error)) + + def test_float(self): + test_property_schema = {'type': 'float'} + propertyInstance = Property('test_property', 0.1, + test_property_schema) + self.assertIsNone(propertyInstance.validate()) + self.assertEqual(0.1, propertyInstance.value) + + def test_float_invalid(self): + test_property_schema = {'type': 'float'} + propertyInstance = Property('test_property', 12, + test_property_schema) + error = self.assertRaises(ValueError, propertyInstance.validate) + self.assertEqual(_('"12" is not a float.'), str(error)) + + def test_timestamp(self): + test_property_schema = {'type': 'timestamp'} + # canonical timestamp + propertyInstance = Property('test_property', '2015-04-01T02:59:43.1Z', + test_property_schema) + self.assertIsNone(propertyInstance.validate()) + self.assertEqual("2015-04-01T02:59:43.1Z", propertyInstance.value) + + # iso8601 timestamp + propertyInstance = Property('test_property', + '2015-04-01t21:59:43.10-05:00', + test_property_schema) + self.assertIsNone(propertyInstance.validate()) + self.assertEqual("2015-04-01t21:59:43.10-05:00", + propertyInstance.value) + + # space separated timestamp + propertyInstance = Property('test_property', + '2015-04-01 21:59:43.10 -5', + test_property_schema) + self.assertIsNone(propertyInstance.validate()) + self.assertEqual("2015-04-01 21:59:43.10 -5", propertyInstance.value) + + # no time zone timestamp + propertyInstance = Property('test_property', '2015-04-01 21:59:43.10', + test_property_schema) + self.assertIsNone(propertyInstance.validate()) + self.assertEqual("2015-04-01 21:59:43.10", propertyInstance.value) + + # date (00:00:00Z) + propertyInstance = Property('test_property', '2015-04-01', + test_property_schema) + self.assertIsNone(propertyInstance.validate()) + self.assertEqual("2015-04-01", propertyInstance.value) + + def test_timestamp_invalid(self): + test_property_schema = {'type': 'timestamp'} + # invalid timestamp - day out of range + value = '2015-04-115T02:59:43.1Z' + propertyInstance = Property('test_property', value, + test_property_schema) + error = self.assertRaises(ValueError, propertyInstance.validate) + expected_message = (_('"%s" is not a valid timestamp.') % value) + self.assertThat(str(error), matchers.StartsWith(expected_message)) + + def test_required(self): + test_property_schema = {'type': 'string'} + propertyInstance = Property('test_property', 'Foo', + test_property_schema) + self.assertEqual(True, propertyInstance.required) + + def test_proprety_inheritance(self): + + tosca_custom_def = ''' + tosca.nodes.SoftwareComponent.MySoftware: + derived_from: SoftwareComponent + properties: + install_path: + required: false + type: string + default: /opt/mysoftware + ''' + + tosca_node_template = ''' + node_templates: + mysoftware_instance: + type: tosca.nodes.SoftwareComponent.MySoftware + properties: + component_version: 3.1 + ''' + + expected_properties = ['component_version', + 'install_path'] + + tpl = self._get_nodetemplate(tosca_node_template, tosca_custom_def) + self.assertIsNone(tpl.validate()) + self.assertEqual(expected_properties, + sorted(tpl.get_properties().keys())) + + def test_missing_property_type(self): + tpl_snippet = ''' + properties: + prop: + typo: tosca.mytesttype.Test + ''' + schema = yamlparser.simple_parse(tpl_snippet) + error = self.assertRaises(exception.InvalidSchemaError, PropertyDef, + 'prop', None, schema['properties']['prop']) + self.assertEqual(_('Schema definition of "prop" must have a "type" ' + 'attribute.'), str(error)) + + def test_invalid_required_value(self): + tpl_snippet = ''' + properties: + prop: + type: tosca.mytesttype.Test + required: dunno + ''' + schema = yamlparser.simple_parse(tpl_snippet) + error = self.assertRaises(exception.InvalidSchemaError, PropertyDef, + 'prop', None, schema['properties']['prop']) + + valid_values = ', '.join(PropertyDef.VALID_REQUIRED_VALUES) + expected_message = (_('Schema definition of "prop" has "required" ' + 'attribute with invalid value "dunno". The ' + 'value must be one of "%s".') % valid_values) + self.assertEqual(expected_message, str(error)) + + def test_invalid_property_status(self): + tpl_snippet = ''' + properties: + prop: + type: string + status: unknown + ''' + schema = yamlparser.simple_parse(tpl_snippet) + error = self.assertRaises(exception.InvalidSchemaError, PropertyDef, + 'prop', None, schema['properties']['prop']) + + valid_values = ', '.join(PropertyDef.VALID_STATUS_VALUES) + expected_message = (_('Schema definition of "prop" has "status" ' + 'attribute with invalid value "unknown". The ' + 'value must be one of "%s".') % valid_values) + self.assertEqual(expected_message, str(error)) + + def test_capability_proprety_inheritance(self): + tosca_custom_def_example1 = ''' + tosca.capabilities.ScalableNew: + derived_from: tosca.capabilities.Scalable + properties: + max_instances: + type: integer + default: 0 + required: no + + tosca.nodes.ComputeNew: + derived_from: tosca.nodes.Compute + capabilities: + scalable: + type: tosca.capabilities.ScalableNew + ''' + + tosca_node_template_example1 = ''' + node_templates: + compute_instance: + type: tosca.nodes.ComputeNew + capabilities: + scalable: + properties: + min_instances: 1 + ''' + + tosca_custom_def_example2 = ''' + tosca.nodes.ComputeNew: + derived_from: tosca.nodes.Compute + capabilities: + new_cap: + type: tosca.capabilities.Scalable + ''' + + tosca_node_template_example2 = ''' + node_templates: + db_server: + type: tosca.nodes.ComputeNew + capabilities: + host: + properties: + num_cpus: 1 + ''' + + tpl1 = self._get_nodetemplate(tosca_node_template_example1, + tosca_custom_def_example1) + self.assertIsNone(tpl1.validate()) + + tpl2 = self._get_nodetemplate(tosca_node_template_example2, + tosca_custom_def_example2) + self.assertIsNone(tpl2.validate()) + + def _get_nodetemplate(self, tpl_snippet, + custom_def_snippet=None): + nodetemplates = yamlparser.\ + simple_parse(tpl_snippet)['node_templates'] + custom_def = [] + if custom_def_snippet: + custom_def = yamlparser.simple_parse(custom_def_snippet) + name = list(nodetemplates.keys())[0] + tpl = NodeTemplate(name, nodetemplates, custom_def) + return tpl + + def test_explicit_relationship_proprety(self): + + tosca_node_template = ''' + node_templates: + + client_node: + type: tosca.nodes.Compute + requirements: + - local_storage: + node: my_storage + relationship: + type: AttachesTo + properties: + location: /mnt/disk + + my_storage: + type: tosca.nodes.BlockStorage + properties: + size: 1 GB + ''' + + expected_properties = ['location'] + + nodetemplates = yamlparser.\ + simple_parse(tosca_node_template)['node_templates'] + tpl = NodeTemplate('client_node', nodetemplates, []) + + self.assertIsNone(tpl.validate()) + rel_tpls = [] + for relationship, trgt in tpl.relationships.items(): + rel_tpls.extend(trgt.get_relationship_template()) + self.assertEqual(expected_properties, + sorted(rel_tpls[0].get_properties().keys())) diff --git a/tosca2heat/tosca-parser/toscaparser/tests/test_scalarunit.py b/tosca2heat/tosca-parser/toscaparser/tests/test_scalarunit.py new file mode 100644 index 0000000..fcd1c83 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/test_scalarunit.py @@ -0,0 +1,355 @@ +# Licensed 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 toscaparser.common import exception +from toscaparser.elements.scalarunit import ScalarUnit_Frequency +from toscaparser.elements.scalarunit import ScalarUnit_Size +from toscaparser.elements.scalarunit import ScalarUnit_Time +from toscaparser.nodetemplate import NodeTemplate +from toscaparser.tests.base import TestCase +from toscaparser.utils.gettextutils import _ +from toscaparser.utils import yamlparser + + +class ScalarUnitPositiveTest(TestCase): + + scenarios = [ + ( + # tpl_snippet with mem_size given as number+space+MB + 'mem_size_is_number_Space_MB', + dict(tpl_snippet=''' + server: + type: tosca.nodes.Compute + capabilities: + host: + properties: + mem_size: 1024 MB + ''', + property='mem_size', + expected='1024 MB') + ), + ( + # tpl_snippet with mem_size given as number+spaces+GB + 'mem_size_is_number_Space_GB', + dict(tpl_snippet=''' + server: + type: tosca.nodes.Compute + capabilities: + host: + properties: + mem_size: 1 GB + ''', + property='mem_size', + expected='1 GB') + ), + ( + # tpl_snippet with mem_size given as number+tiB + 'mem_size_is_number_NoSpace_GB', + dict(tpl_snippet=''' + server: + type: tosca.nodes.Compute + capabilities: + host: + properties: + mem_size: 1tiB + ''', + property='mem_size', + expected='1 TiB') + ), + ( + # tpl_snippet with mem_size given as number+Spaces+GIB + 'mem_size_is_number_Spaces_GB', + dict(tpl_snippet=''' + server: + type: tosca.nodes.Compute + capabilities: + host: + properties: + mem_size: 1 GIB + ''', + property='mem_size', + expected='1 GiB') + ), + ( + # tpl_snippet with mem_size given as number+Space+tib + 'mem_size_is_number_Spaces_GB', + dict(tpl_snippet=''' + server: + type: tosca.nodes.Compute + capabilities: + host: + properties: + mem_size: 1 tib + ''', + property='mem_size', + expected='1 TiB') + ), + ( + 'cpu_frequency_is_float_Space_GHz', + dict(tpl_snippet=''' + server: + type: tosca.nodes.Compute + capabilities: + host: + properties: + cpu_frequency: 2.5 GHz + ''', + property='cpu_frequency', + expected='2.5 GHz') + ), + ( + 'cpu_frequency_is_float_Space_MHz', + dict(tpl_snippet=''' + server: + type: tosca.nodes.Compute + capabilities: + host: + properties: + cpu_frequency: 800 MHz + ''', + property='cpu_frequency', + expected='800 MHz') + ), + ] + + def test_scenario_scalar_unit_positive(self): + tpl = self.tpl_snippet + nodetemplates = yamlparser.simple_parse(tpl) + nodetemplate = NodeTemplate('server', nodetemplates) + props = nodetemplate.get_capability('host').get_properties() + prop_name = self.property + if props and prop_name in props.keys(): + prop = props[prop_name] + self.assertIsNone(prop.validate()) + resolved = prop.value + self.assertEqual(resolved, self.expected) + + +class GetNumFromScalarUnitSizePositive(TestCase): + + scenarios = [ + ( # Note that (1 TB) / (1 GB) = 1000 + 'Input is TB, user input is GB', + dict(InputMemSize='1 TB', + UserInputUnit='gB', + expected=1000) + ), + ( # Note that (1 Tib)/ (1 GB) = 1099 + 'Input is TiB, user input is GB', + dict(InputMemSize='1 TiB', + UserInputUnit='gB', + expected=1099.511627776) + ), + ] + + def test_scenario_get_num_from_scalar_unit_size(self): + resolved = (ScalarUnit_Size(self.InputMemSize). + get_num_from_scalar_unit(self.UserInputUnit)) + self.assertEqual(resolved, self.expected) + + +class GetNumFromScalarUnitFrequencyPositive(TestCase): + + scenarios = [ + ( # Note that (1 GHz) / (1 Hz) = 1000000000 + 'Input is GHz, user input is Hz', + dict(InputMemSize='1 GHz', + UserInputUnit='Hz', + expected=1000000000) + ), + ( + 'Input is GHz, user input is Hz', + dict(InputMemSize='2.4 GHz', + UserInputUnit='Hz', + expected=2400000000) + ), + ( # Note that (1 GHz)/ (1 MHz) = 1000 + 'Input is MHz, user input is GHz', + dict(InputMemSize='800 MHz', + UserInputUnit='GHz', + expected=0.8) + ), + ( + 'Input is GHz, user input is Hz', + dict(InputMemSize='0.9 GHz', + UserInputUnit='MHz', + expected=900) + ), + ( + 'Input is GHz, user input is Hz', + dict(InputMemSize='2.7GHz', + UserInputUnit='MHz', + expected=2700) + ), + ] + + def test_scenario_get_num_from_scalar_unit_frequency(self): + resolved = (ScalarUnit_Frequency(self.InputMemSize). + get_num_from_scalar_unit(self.UserInputUnit)) + self.assertEqual(resolved, self.expected) + + +class GetNumFromScalarUnitTimePositive(TestCase): + + scenarios = [ + ( # Note that (1 s) / (1 ms) = 1000 + 'Input is 500ms, user input is s', + dict(InputMemSize='500 ms', + UserInputUnit='s', + expected=0.5) + ), + ( # Note that (1 h)/ (1 s) = 3600 + 'Input is h, user input is s', + dict(InputMemSize='1 h', + UserInputUnit='s', + expected=3600) + ), + ( # Note that (1 m)/ (1 s) = 60 + 'Input is m, user input is s', + dict(InputMemSize='0.5 m', + UserInputUnit='s', + expected=30) + ), + ( # Note that (1 d)/ (1 h) = 24 + 'Input is d, user input is h', + dict(InputMemSize='1 d', + UserInputUnit='h', + expected=24) + ), + ] + + def test_scenario_get_num_from_scalar_unit_time(self): + resolved = (ScalarUnit_Time(self.InputMemSize). + get_num_from_scalar_unit(self.UserInputUnit)) + self.assertEqual(resolved, self.expected) + + +class GetNumFromScalarUnitSizeNegative(TestCase): + + InputMemSize = '1 GB' + UserInputUnit = 'qB' + + def test_get_num_from_scalar_unit_size_negative(self): + try: + (ScalarUnit_Size(self.InputMemSize). + get_num_from_scalar_unit(self.UserInputUnit)) + except Exception as error: + self.assertTrue(isinstance(error, ValueError)) + self.assertEqual(_('The unit "qB" is not valid. Valid units are ' + '"[\'B\', \'GB\', \'GiB\', \'KiB\', \'MB\', ' + '\'MiB\', \'TB\', \'TiB\', \'kB\']".'), + error.__str__()) + + +class GetNumFromScalarUnitFrequencyNegative(TestCase): + + InputFrequency = '2.7 GHz' + UserInputUnit = 'Jz' + + def test_get_num_from_scalar_unit_frequency_negative(self): + try: + (ScalarUnit_Frequency(self.InputFrequency). + get_num_from_scalar_unit(self.UserInputUnit)) + except Exception as error: + self.assertTrue(isinstance(error, ValueError)) + self.assertEqual(_('The unit "Jz" is not valid. Valid units are ' + '"[\'GHz\', \'Hz\', \'MHz\', \'kHz\']".'), + error.__str__()) + + +class GetNumFromScalarUnitTimeNegative(TestCase): + + InputTime = '5 ms' + UserInputUnit = 'D' + + def test_get_num_from_scalar_unit_frequency_negative(self): + try: + (ScalarUnit_Time(self.InputTime). + get_num_from_scalar_unit(self.UserInputUnit)) + except Exception as error: + self.assertTrue(isinstance(error, ValueError)) + self.assertEqual(_('"Jz" is not a valid scalar-unit.'), + error.__str__()) + + +class ScalarUnitNegativeTest(TestCase): + + custom_def_snippet = ''' + tosca.my.nodes.Compute: + derived_from: tosca.nodes.Root + properties: + cpu_frequency: + required: false + type: scalar-unit.frequency + constraints: + - greater_or_equal: 0.1 GHz + disk_size: + required: false + type: scalar-unit.size + constraints: + - greater_or_equal: 1 GB + mem_size: + required: false + type: scalar-unit.size + constraints: + - in_range: [1 MiB, 1 GiB] + ''' + custom_def = yamlparser.simple_parse(custom_def_snippet) + + # disk_size doesn't provide a value, mem_size uses an invalid unit. + def test_invalid_scalar_unit(self): + tpl_snippet = ''' + server: + type: tosca.my.nodes.Compute + properties: + cpu_frequency: 50.3.6 GHZ + disk_size: MB + mem_size: 1 QB + ''' + nodetemplates = yamlparser.simple_parse(tpl_snippet) + nodetemplate = NodeTemplate('server', nodetemplates, self.custom_def) + for p in nodetemplate.get_properties_objects(): + self.assertRaises(ValueError, p.validate) + + # disk_size is less than 1 GB, mem_size is not in the required range. + # Note: in the spec, the minimum value of mem_size is 1 MiB (> 1 MB) + def test_constraint_for_scalar_unit(self): + tpl_snippet = ''' + server: + type: tosca.my.nodes.Compute + properties: + cpu_frequency: 0.05 GHz + disk_size: 500 MB + mem_size: 1 MB + ''' + nodetemplates = yamlparser.simple_parse(tpl_snippet) + nodetemplate = NodeTemplate('server', nodetemplates, self.custom_def) + props = nodetemplate.get_properties() + if 'cpu_frequency' in props.keys(): + error = self.assertRaises(exception.ValidationError, + props['cpu_frequency'].validate) + self.assertEqual(_('The value "0.05 GHz" of property ' + '"cpu_frequency" must be greater than or equal ' + 'to "0.1 GHz".'), error.__str__()) + if 'disk_size' in props.keys(): + error = self.assertRaises(exception.ValidationError, + props['disk_size'].validate) + self.assertEqual(_('The value "500 MB" of property "disk_size" ' + 'must be greater than or equal to "1 GB".'), + error.__str__()) + + if 'mem_size' in props.keys(): + error = self.assertRaises(exception.ValidationError, + props['mem_size'].validate) + self.assertEqual(_('The value "1 MB" of property "mem_size" is ' + 'out of range "(min:1 MiB, max:1 GiB)".'), + error.__str__()) diff --git a/tosca2heat/tosca-parser/toscaparser/tests/test_shell.py b/tosca2heat/tosca-parser/toscaparser/tests/test_shell.py new file mode 100644 index 0000000..e0b5a4e --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/test_shell.py @@ -0,0 +1,57 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import os + +from toscaparser.common import exception +import toscaparser.shell as shell +from toscaparser.tests.base import TestCase +from toscaparser.utils.gettextutils import _ + + +class ShellTest(TestCase): + + tosca_helloworld = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "data/tosca_helloworld.yaml") + + errornous_template = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "data/test_multiple_validation_errors.yaml") + + def test_missing_arg(self): + error = self.assertRaises(ValueError, shell.main, '') + err_msg = _('The program requires a template or a CSAR file as an ' + 'argument. Please refer to the usage documentation.') + self.assertEqual(err_msg, str(error)) + + def test_invalid_arg(self): + error = self.assertRaises(ValueError, shell.main, 'parse me') + err_msg = _('The program expects "--template-file" as the first ' + 'argument. Please refer to the usage documentation.') + self.assertEqual(err_msg, str(error)) + + def test_template_not_exist(self): + error = self.assertRaises( + ValueError, shell.main, ['--template-file=template.txt']) + self.assertEqual(_('"template.txt" is not a valid file.'), str(error)) + + def test_template_invalid(self): + arg = '--template-file=' + self.errornous_template + self.assertRaises(exception.ValidationError, shell.main, [arg]) + + def test_template_valid(self): + arg = '--template-file=' + self.tosca_helloworld + try: + shell.main([arg]) + except Exception: + self.fail(_('The program raised an exception unexpectedly.')) diff --git a/tosca2heat/tosca-parser/toscaparser/tests/test_topology_template.py b/tosca2heat/tosca-parser/toscaparser/tests/test_topology_template.py new file mode 100644 index 0000000..0f1a33e --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/test_topology_template.py @@ -0,0 +1,160 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import os + +from toscaparser.tests.base import TestCase +from toscaparser.topology_template import TopologyTemplate +from toscaparser.tosca_template import ToscaTemplate +import toscaparser.utils.yamlparser + +YAML_LOADER = toscaparser.utils.yamlparser.load_yaml + + +class TopologyTemplateTest(TestCase): + + def setUp(self): + TestCase.setUp(self) + '''TOSCA template.''' + self.tosca_tpl_path = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "data/topology_template/subsystem.yaml") + self.tpl = YAML_LOADER(self.tosca_tpl_path) + self.topo_tpl = self.tpl.get('topology_template') + self.imports = self.tpl.get('imports') + self.topo = TopologyTemplate(self.topo_tpl, + self._get_all_custom_def()) + + def _get_custom_def(self, type_definition): + custom_defs = {} + for definition in self.imports: + if os.path.isabs(definition): + def_file = definition + else: + tpl_dir = os.path.dirname(os.path.abspath(self.tosca_tpl_path)) + def_file = os.path.join(tpl_dir, definition) + custom_type = YAML_LOADER(def_file) + custom_defs.update(custom_type.get(type_definition)) + return custom_defs + + def _get_all_custom_def(self): + custom_defs = {} + custom_defs.update(self._get_custom_def('node_types')) + custom_defs.update(self._get_custom_def('capability_types')) + return custom_defs + + def test_description(self): + expected_desc = 'Template of a database including its hosting stack.' + self.assertEqual(expected_desc, self.topo.description) + + def test_inputs(self): + self.assertEqual( + ['mq_server_ip', 'my_cpus', 'receiver_port'], + sorted([input.name for input in self.topo.inputs])) + + input_name = "receiver_port" + expected_description = "Port to be used for receiving messages." + for input in self.topo.inputs: + if input.name == input_name: + self.assertEqual(expected_description, input.description) + + def test_node_tpls(self): + '''Test nodetemplate names.''' + self.assertEqual( + ['app', 'server', 'websrv'], + sorted([tpl.name for tpl in self.topo.nodetemplates])) + + tpl_name = "app" + expected_type = "example.SomeApp" + expected_properties = ['admin_user', 'pool_size'] + expected_capabilities = ['feature', 'message_receiver'] + expected_requirements = [{'host': {'node': 'websrv'}}] + expected_relationshp = ['tosca.relationships.HostedOn'] + expected_host = ['websrv'] + for tpl in self.topo.nodetemplates: + if tpl_name == tpl.name: + '''Test node type.''' + self.assertEqual(tpl.type, expected_type) + + '''Test properties.''' + self.assertEqual( + expected_properties, + sorted(tpl.get_properties().keys())) + + '''Test capabilities.''' + self.assertEqual( + expected_capabilities, + sorted(tpl.get_capabilities().keys())) + + '''Test requirements.''' + self.assertEqual( + expected_requirements, tpl.requirements) + + '''Test relationship.''' + ''' TODO : skip tempororily. need to fix it + ''' + self.assertEqual( + expected_relationshp, + [x.type for x in tpl.relationships.keys()]) + self.assertEqual( + expected_host, + [y.name for y in tpl.relationships.values()]) + '''Test interfaces.''' + # TODO(hurf) add interface test when new template is available + + if tpl.name == 'server': + '''Test property value''' + props = tpl.get_properties() + if props and 'mem_size' in props.keys(): + self.assertEqual(props['mem_size'].value, '4096 MB') + '''Test capability''' + caps = tpl.get_capabilities() + self.assertIn('os', caps.keys()) + os_props_objs = None + os_props = None + os_type_prop = None + if caps and 'os' in caps.keys(): + capability = caps['os'] + os_props_objs = capability.get_properties_objects() + os_props = capability.get_properties() + os_type_prop = capability.get_property_value('type') + break + self.assertEqual( + ['Linux'], + [p.value for p in os_props_objs if p.name == 'type']) + self.assertEqual( + 'Linux', + os_props['type'].value if 'type' in os_props else '') + self.assertEqual('Linux', os_props['type'].value) + self.assertEqual('Linux', os_type_prop) + + def test_outputs(self): + self.assertEqual( + ['receiver_ip'], + sorted([output.name for output in self.topo.outputs])) + + def test_groups(self): + group = self.topo.groups[0] + self.assertEqual('webserver_group', group.name) + self.assertEqual(['websrv', 'server'], group.members) + for node in group.get_member_nodes(): + if node.name == 'server': + '''Test property value''' + props = node.get_properties() + if props and 'mem_size' in props.keys(): + self.assertEqual(props['mem_size'].value, '4096 MB') + + def test_system_template(self): + tpl_path = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "data/topology_template/system.yaml") + self.assertIsNotNone(ToscaTemplate(tpl_path)) diff --git a/tosca2heat/tosca-parser/toscaparser/tests/test_toscadef.py b/tosca2heat/tosca-parser/toscaparser/tests/test_toscadef.py new file mode 100644 index 0000000..f0a87ac --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/test_toscadef.py @@ -0,0 +1,342 @@ +# Licensed 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 toscaparser.common import exception +from toscaparser.elements.artifacttype import ArtifactTypeDef +from toscaparser.elements.entity_type import EntityType +from toscaparser.elements.grouptype import GroupType +import toscaparser.elements.interfaces as ifaces +from toscaparser.elements.nodetype import NodeType +from toscaparser.elements.policytype import PolicyType +from toscaparser.tests.base import TestCase + +compute_type = NodeType('tosca.nodes.Compute') +component_type = NodeType('tosca.nodes.SoftwareComponent') +network_type = NodeType('tosca.nodes.network.Network') +network_port_type = NodeType('tosca.nodes.network.Port') +webserver_type = NodeType('tosca.nodes.WebServer') +database_type = NodeType('tosca.nodes.Database') +artif_root_type = ArtifactTypeDef('tosca.artifacts.Root') +artif_file_type = ArtifactTypeDef('tosca.artifacts.File') +artif_bash_type = ArtifactTypeDef('tosca.artifacts.Implementation.Bash') +artif_python_type = ArtifactTypeDef('tosca.artifacts.Implementation.Python') +artif_container_docker_type = ArtifactTypeDef('tosca.artifacts.' + 'Deployment.Image.' + 'Container.Docker') +artif_vm_iso_type = ArtifactTypeDef('tosca.artifacts.' + 'Deployment.Image.VM.ISO') +artif_vm_qcow2_type = ArtifactTypeDef('tosca.artifacts.' + 'Deployment.Image.VM.QCOW2') +policy_root_type = PolicyType('tosca.policies.Root') +policy_placement_type = PolicyType('tosca.policies.Placement') +policy_scaling_type = PolicyType('tosca.policies.Scaling') +policy_update_type = PolicyType('tosca.policies.Update') +policy_performance_type = PolicyType('tosca.policies.Performance') +group_type = GroupType('tosca.groups.Root') + + +class ToscaDefTest(TestCase): + def test_type(self): + self.assertEqual(compute_type.type, "tosca.nodes.Compute") + self.assertRaises(exception.InvalidTypeError, NodeType, + 'tosca.nodes.Invalid') + self.assertEqual(network_type.type, "tosca.nodes.network.Network") + self.assertEqual(network_port_type.type, "tosca.nodes.network.Port") + + def test_parent_type(self): + self.assertEqual(compute_type.parent_type.type, "tosca.nodes.Root") + self.assertEqual(network_type.parent_type.type, "tosca.nodes.Root") + self.assertEqual(network_port_type.parent_type.type, + "tosca.nodes.Root") + + def test_group(self): + self.assertEqual(group_type.type, "tosca.groups.Root") + self.assertIn(ifaces.LIFECYCLE_SHORTNAME, group_type.interfaces) + + def test_capabilities(self): + # Assure the normative Compute node type + # has all the required Capability types + # regardless of symbloc name + # TODO(Matt) - since Compute IS a normative node type + # we SHOULD test symbolic capability names as well + self.assertEqual( + ['tosca.capabilities.Container', + 'tosca.capabilities.Node', + 'tosca.capabilities.OperatingSystem', + 'tosca.capabilities.Scalable', + 'tosca.capabilities.network.Bindable'], + sorted([c.type for c in compute_type.get_capabilities_objects()])) + # Assure the normative Network node type + # hsa all the required Capability types + # TODO(Matt) - since Network IS a normative node type + # we SHOULD test symbolic capability names as well + self.assertEqual( + ['tosca.capabilities.Node', + 'tosca.capabilities.network.Linkable'], + sorted([c.type for c in network_type.get_capabilities_objects()])) + + # Assure the normative WebServer node type's + # Endpoint cap. has all required property names + # Note: we are testing them in alphabetic sort order + endpoint_props_def_objects = \ + self._get_capability_properties_def_objects( + webserver_type.get_capabilities_objects(), + 'tosca.capabilities.Endpoint') + # Assure WebServer's Endpoint capability's properties have their + # required keyname value set correctly + self.assertEqual( + [('initiator', False), ('network_name', False), ('port', False), + ('port_name', False), ('ports', False), ('protocol', True), + ('secure', False), ('url_path', False)], + sorted([(p.name, p.required) for p in endpoint_props_def_objects])) + + os_props = self._get_capability_properties_def_objects( + compute_type.get_capabilities_objects(), + 'tosca.capabilities.OperatingSystem') + self.assertEqual( + [('architecture', False), ('distribution', False), ('type', False), + ('version', False)], + sorted([(p.name, p.required) for p in os_props])) + + host_props = self._get_capability_properties_def_objects( + compute_type.get_capabilities_objects(), + 'tosca.capabilities.Container') + self.assertEqual( + [('cpu_frequency', False), ('disk_size', False), + ('mem_size', False), ('num_cpus', False)], + sorted([(p.name, p.required) for p in host_props])) + endpoint_admin_properties = 'secure' + endpoint_admin_props_def_objects = \ + self._get_capability_properties_def_objects( + webserver_type.get_capabilities_objects(), + 'tosca.capabilities.Endpoint.Admin') + self.assertIn( + endpoint_admin_properties, + sorted([p.name for p in endpoint_admin_props_def_objects])) + + def _get_capability_properties_def_objects(self, caps, type): + properties_def = None + for cap in caps: + if cap.type == type: + properties_def = cap.get_properties_def_objects() + break + return properties_def + + def _get_capability_properties_def(self, caps, type): + properties_def = None + for cap in caps: + if cap.type == type: + properties_def = cap.get_properties_def() + break + return properties_def + + def test_properties_def(self): + self.assertEqual( + ['name', 'password', 'port', 'user'], + sorted(database_type.get_properties_def().keys())) + + def test_attributes_def(self): + self.assertEqual( + ['networks', 'ports', 'private_address', 'public_address', + 'state', 'tosca_id', 'tosca_name'], + sorted(compute_type.get_attributes_def().keys())) + + def test_requirements(self): + self.assertEqual( + [{'host': {'capability': 'tosca.capabilities.Container', + 'node': 'tosca.nodes.Compute', + 'relationship': 'tosca.relationships.HostedOn'}}, + {'dependency': {'capability': 'tosca.capabilities.Node', + 'node': 'tosca.nodes.Root', + 'occurrences': [0, 'UNBOUNDED'], + 'relationship': 'tosca.relationships.DependsOn'}} + ], + [r for r in component_type.requirements]) + + def test_relationship(self): + self.assertEqual( + [('tosca.relationships.DependsOn', 'tosca.nodes.Root'), + ('tosca.relationships.HostedOn', 'tosca.nodes.Compute')], + sorted([(relation.type, node.type) for + relation, node in component_type.relationship.items()])) + self.assertIn( + ('tosca.relationships.HostedOn', ['tosca.capabilities.Container']), + [(relation.type, relation.valid_target_types) for + relation in list(component_type.relationship.keys())]) + self.assertIn( + ('tosca.relationships.network.BindsTo', 'tosca.nodes.Compute'), + [(relation.type, node.type) for + relation, node in network_port_type.relationship.items()]) + self.assertIn( + ('tosca.relationships.network.LinksTo', + 'tosca.nodes.network.Network'), + [(relation.type, node.type) for + relation, node in network_port_type.relationship.items()]) + + def test_interfaces(self): + self.assertEqual(compute_type.interfaces, None) + root_node = NodeType('tosca.nodes.Root') + self.assertIn(ifaces.LIFECYCLE_SHORTNAME, root_node.interfaces) + + def test_artifacts(self): + self.assertEqual('tosca.artifacts.Root', + artif_file_type.parent_type) + self.assertEqual({}, artif_file_type.parent_artifacts) + self.assertEqual(sorted(['tosca.artifacts.Root'], + key=lambda x: str(x)), + sorted([artif_file_type.get_artifact(name) + for name in artif_file_type.defs], + key=lambda x: str(x))) + + self.assertEqual('tosca.artifacts.Implementation', + artif_bash_type.parent_type) + self.assertEqual({'tosca.artifacts.Implementation': + {'derived_from': 'tosca.artifacts.Root', + 'description': + 'TOSCA base type for implementation artifacts'}}, + artif_bash_type.parent_artifacts) + self.assertEqual(sorted([['sh'], 'tosca.artifacts.Implementation', + 'Script artifact for the Unix Bash shell', + 'application/x-sh'], key=lambda x: str(x)), + sorted([artif_bash_type.get_artifact(name) + for name in artif_bash_type.defs], + key=lambda x: str(x))) + + self.assertEqual('tosca.artifacts.Implementation', + artif_python_type.parent_type) + self.assertEqual({'tosca.artifacts.Implementation': + {'derived_from': 'tosca.artifacts.Root', + 'description': + 'TOSCA base type for implementation artifacts'}}, + artif_python_type.parent_artifacts) + self.assertEqual(sorted([['py'], 'tosca.artifacts.Implementation', + 'Artifact for the interpreted Python' + ' language', 'application/x-python'], + key=lambda x: str(x)), + sorted([artif_python_type.get_artifact(name) + for name in artif_python_type.defs], + key=lambda x: str(x))) + + self.assertEqual('tosca.artifacts.Deployment.Image', + artif_container_docker_type.parent_type) + self.assertEqual({'tosca.artifacts.Deployment': + {'derived_from': 'tosca.artifacts.Root', + 'description': + 'TOSCA base type for deployment artifacts'}, + 'tosca.artifacts.Deployment.Image': + {'derived_from': 'tosca.artifacts.Deployment'}}, + artif_container_docker_type.parent_artifacts) + self.assertEqual(sorted(['tosca.artifacts.Deployment.Image', + 'Docker container image'], + key=lambda x: str(x)), + sorted([artif_container_docker_type. + get_artifact(name) for name in + artif_container_docker_type.defs], + key=lambda x: str(x))) + + self.assertEqual('tosca.artifacts.Deployment.Image', + artif_vm_iso_type.parent_type) + self.assertEqual({'tosca.artifacts.Deployment': + {'derived_from': 'tosca.artifacts.Root', + 'description': + 'TOSCA base type for deployment artifacts'}, + 'tosca.artifacts.Deployment.Image': + {'derived_from': 'tosca.artifacts.Deployment'}}, + artif_vm_iso_type.parent_artifacts) + self.assertEqual(sorted(['tosca.artifacts.Deployment.Image', + 'Virtual Machine (VM) image in ' + 'ISO disk format', + 'application/octet-stream', ['iso']], + key=lambda x: str(x)), + sorted([artif_vm_iso_type. + get_artifact(name) for name in + artif_vm_iso_type.defs], + key=lambda x: str(x))) + + self.assertEqual('tosca.artifacts.Deployment.Image', + artif_vm_qcow2_type.parent_type) + self.assertEqual({'tosca.artifacts.Deployment': + {'derived_from': 'tosca.artifacts.Root', + 'description': + 'TOSCA base type for deployment artifacts'}, + 'tosca.artifacts.Deployment.Image': + {'derived_from': 'tosca.artifacts.Deployment'}}, + artif_vm_qcow2_type.parent_artifacts) + self.assertEqual(sorted(['tosca.artifacts.Deployment.Image', + 'Virtual Machine (VM) image in QCOW v2 ' + 'standard disk format', + 'application/octet-stream', ['qcow2']], + key=lambda x: str(x)), + sorted([artif_vm_qcow2_type. + get_artifact(name) for name in + artif_vm_qcow2_type.defs], + key=lambda x: str(x))) + + def test_policies(self): + self.assertEqual('tosca.policies.Root', + policy_placement_type.parent_type) + self.assertEqual({}, policy_placement_type.parent_policies) + self.assertEqual(sorted(['tosca.policies.Root', + 'The TOSCA Policy Type definition that is ' + 'used to govern placement of TOSCA nodes or ' + 'groups of nodes.'], + key=lambda x: str(x)), + sorted([policy_placement_type.get_policy(name) + for name in policy_placement_type.defs], + key=lambda x: str(x))) + + self.assertEqual('tosca.policies.Root', + policy_scaling_type.parent_type) + self.assertEqual({}, policy_scaling_type.parent_policies) + self.assertEqual(sorted(['tosca.policies.Root', + 'The TOSCA Policy Type definition that is ' + 'used to govern scaling of TOSCA nodes or ' + 'groups of nodes.'], + key=lambda x: str(x)), + sorted([policy_scaling_type.get_policy(name) + for name in policy_scaling_type.defs], + key=lambda x: str(x))) + + self.assertEqual('tosca.policies.Root', + policy_update_type.parent_type) + self.assertEqual({}, policy_update_type.parent_policies) + self.assertEqual(sorted(['tosca.policies.Root', + 'The TOSCA Policy Type definition that is ' + 'used to govern update of TOSCA nodes or ' + 'groups of nodes.'], + key=lambda x: str(x)), + sorted([policy_update_type.get_policy(name) + for name in policy_update_type.defs], + key=lambda x: str(x))) + + self.assertEqual('tosca.policies.Root', + policy_performance_type.parent_type) + self.assertEqual({}, policy_performance_type.parent_policies) + self.assertEqual(sorted(['tosca.policies.Root', + 'The TOSCA Policy Type definition that is ' + 'used to declare performance requirements ' + 'for TOSCA nodes or groups of nodes.'], + key=lambda x: str(x)), + sorted([policy_performance_type.get_policy(name) + for name in policy_performance_type.defs], + key=lambda x: str(x))) + + def test_port_spec(self): + tosca_def = EntityType.TOSCA_DEF + port_spec = tosca_def.get('tosca.datatypes.network.PortSpec') + self.assertEqual(port_spec.get('derived_from'), + 'tosca.datatypes.Root') + properties = port_spec.get('properties') + self.assertEqual( + sorted(['protocol', 'target', 'target_range', 'source', + 'source_range']), + sorted(properties.keys())) diff --git a/tosca2heat/tosca-parser/toscaparser/tests/test_toscatpl.py b/tosca2heat/tosca-parser/toscaparser/tests/test_toscatpl.py new file mode 100644 index 0000000..3fd49bf --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/test_toscatpl.py @@ -0,0 +1,732 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import os +import six + +from toscaparser.common import exception +import toscaparser.elements.interfaces as ifaces +from toscaparser.elements.nodetype import NodeType +from toscaparser.functions import GetInput +from toscaparser.functions import GetProperty +from toscaparser.nodetemplate import NodeTemplate +from toscaparser.tests.base import TestCase +from toscaparser.tosca_template import ToscaTemplate +from toscaparser.utils.gettextutils import _ +import toscaparser.utils.yamlparser + + +class ToscaTemplateTest(TestCase): + + '''TOSCA template.''' + tosca_tpl = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "data/tosca_single_instance_wordpress.yaml") + tosca = ToscaTemplate(tosca_tpl) + + tosca_elk_tpl = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "data/tosca_elk.yaml") + + def test_version(self): + self.assertEqual(self.tosca.version, "tosca_simple_yaml_1_0") + + def test_description(self): + expected_description = "TOSCA simple profile with wordpress, " \ + "web server and mysql on the same server." + self.assertEqual(self.tosca.description, expected_description) + + def test_inputs(self): + self.assertEqual( + ['cpus', 'db_name', 'db_port', + 'db_pwd', 'db_root_pwd', 'db_user'], + sorted([input.name for input in self.tosca.inputs])) + + input_name = "db_port" + expected_description = "Port for the MySQL database." + for input in self.tosca.inputs: + if input.name == input_name: + self.assertEqual(input.description, expected_description) + + def test_node_tpls(self): + '''Test nodetemplate names.''' + self.assertEqual( + ['mysql_database', 'mysql_dbms', 'server', + 'webserver', 'wordpress'], + sorted([tpl.name for tpl in self.tosca.nodetemplates])) + + tpl_name = "mysql_database" + expected_type = "tosca.nodes.Database" + expected_properties = ['name', 'password', 'user'] + expected_capabilities = ['database_endpoint', 'feature'] + expected_requirements = [{'host': 'mysql_dbms'}] + ''' TODO: needs enhancement in tosca_elk.yaml.. + expected_relationshp = ['tosca.relationships.HostedOn'] + expected_host = ['mysql_dbms'] + ''' + expected_interface = [ifaces.LIFECYCLE_SHORTNAME] + + for tpl in self.tosca.nodetemplates: + if tpl_name == tpl.name: + '''Test node type.''' + self.assertEqual(tpl.type, expected_type) + + '''Test properties.''' + self.assertEqual( + expected_properties, + sorted(tpl.get_properties().keys())) + + '''Test capabilities.''' + self.assertEqual( + expected_capabilities, + sorted(tpl.get_capabilities().keys())) + + '''Test requirements.''' + self.assertEqual( + expected_requirements, tpl.requirements) + + '''Test relationship.''' + ''' needs enhancements in tosca_elk.yaml + self.assertEqual( + expected_relationshp, + [x.type for x in tpl.relationships.keys()]) + self.assertEqual( + expected_host, + [y.name for y in tpl.relationships.values()]) + ''' + '''Test interfaces.''' + self.assertEqual( + expected_interface, + [x.type for x in tpl.interfaces]) + + if tpl.name == 'server': + '''Test property value''' + props = tpl.get_properties() + if props and 'mem_size' in props.keys(): + self.assertEqual(props['mem_size'].value, '4096 MB') + '''Test capability''' + caps = tpl.get_capabilities() + self.assertIn('os', caps.keys()) + os_props_objs = None + os_props = None + os_type_prop = None + if caps and 'os' in caps.keys(): + capability = caps['os'] + os_props_objs = capability.get_properties_objects() + os_props = capability.get_properties() + os_type_prop = capability.get_property_value('type') + break + self.assertEqual( + ['Linux'], + [p.value for p in os_props_objs if p.name == 'type']) + self.assertEqual( + 'Linux', + os_props['type'].value if 'type' in os_props else '') + self.assertEqual('Linux', os_props['type'].value) + self.assertEqual('Linux', os_type_prop) + + def test_outputs(self): + self.assertEqual( + ['website_url'], + sorted([output.name for output in self.tosca.outputs])) + + def test_interfaces(self): + wordpress_node = [ + node for node in self.tosca.nodetemplates + if node.name == 'wordpress'][0] + interfaces = wordpress_node.interfaces + self.assertEqual(2, len(interfaces)) + for interface in interfaces: + if interface.name == 'create': + self.assertEqual(ifaces.LIFECYCLE_SHORTNAME, + interface.type) + self.assertEqual('wordpress/wordpress_install.sh', + interface.implementation) + self.assertIsNone(interface.inputs) + elif interface.name == 'configure': + self.assertEqual(ifaces.LIFECYCLE_SHORTNAME, + interface.type) + self.assertEqual('wordpress/wordpress_configure.sh', + interface.implementation) + self.assertEqual(3, len(interface.inputs)) + TestCase.skip(self, 'bug #1440247') + wp_db_port = interface.inputs['wp_db_port'] + self.assertTrue(isinstance(wp_db_port, GetProperty)) + self.assertEqual('get_property', wp_db_port.name) + self.assertEqual(['SELF', + 'database_endpoint', + 'port'], + wp_db_port.args) + result = wp_db_port.result() + self.assertTrue(isinstance(result, GetInput)) + else: + raise AssertionError( + 'Unexpected interface: {0}'.format(interface.name)) + + def test_normative_type_by_short_name(self): + # test template with a short name Compute + template = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "data/test_tosca_normative_type_by_shortname.yaml") + + tosca_tpl = ToscaTemplate(template) + expected_type = "tosca.nodes.Compute" + for tpl in tosca_tpl.nodetemplates: + self.assertEqual(tpl.type, expected_type) + for tpl in tosca_tpl.nodetemplates: + compute_type = NodeType(tpl.type) + self.assertEqual( + sorted(['tosca.capabilities.Container', + 'tosca.capabilities.Node', + 'tosca.capabilities.OperatingSystem', + 'tosca.capabilities.network.Bindable', + 'tosca.capabilities.Scalable']), + sorted([c.type + for c in compute_type.get_capabilities_objects()])) + + def test_template_with_no_inputs(self): + tosca_tpl = self._load_template('test_no_inputs_in_template.yaml') + self.assertEqual(0, len(tosca_tpl.inputs)) + + def test_template_with_no_outputs(self): + tosca_tpl = self._load_template('test_no_outputs_in_template.yaml') + self.assertEqual(0, len(tosca_tpl.outputs)) + + def test_relationship_interface(self): + template = ToscaTemplate(self.tosca_elk_tpl) + for node_tpl in template.nodetemplates: + if node_tpl.name == 'logstash': + config_interface = 'Configure' + artifact = 'logstash/configure_elasticsearch.py' + relation = node_tpl.relationships + for key in relation.keys(): + rel_tpl = relation.get(key).get_relationship_template() + if rel_tpl: + interfaces = rel_tpl[0].interfaces + for interface in interfaces: + self.assertEqual(config_interface, + interface.type) + self.assertEqual('pre_configure_source', + interface.name) + self.assertEqual(artifact, + interface.implementation) + + def test_relationship(self): + template = ToscaTemplate(self.tosca_elk_tpl) + for node_tpl in template.nodetemplates: + if node_tpl.name == 'paypal_pizzastore': + expected_relationships = ['tosca.relationships.ConnectsTo', + 'tosca.relationships.HostedOn'] + expected_hosts = ['tosca.nodes.Database', + 'tosca.nodes.WebServer'] + self.assertEqual(len(node_tpl.relationships), 2) + self.assertEqual( + expected_relationships, + sorted([k.type for k in node_tpl.relationships.keys()])) + self.assertEqual( + expected_hosts, + sorted([v.type for v in node_tpl.relationships.values()])) + + def test_template_macro(self): + template = ToscaTemplate(self.tosca_elk_tpl) + for node_tpl in template.nodetemplates: + if node_tpl.name == 'mongo_server': + self.assertEqual( + ['disk_size', 'mem_size', 'num_cpus'], + sorted(node_tpl.get_capability('host'). + get_properties().keys())) + + def test_template_requirements(self): + """Test different formats of requirements + + The requirements can be defined in few different ways, + 1. Requirement expressed as a capability with an implicit relationship. + 2. Requirement expressed with explicit relationship. + 3. Requirement expressed with a relationship template. + 4. Requirement expressed via TOSCA types to provision a node + with explicit relationship. + 5. Requirement expressed via TOSCA types with a filter. + """ + tosca_tpl = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "data/test_requirements.yaml") + tosca = ToscaTemplate(tosca_tpl) + for node_tpl in tosca.nodetemplates: + if node_tpl.name == 'my_app': + expected_relationship = [ + ('tosca.relationships.ConnectsTo', 'mysql_database'), + ('tosca.relationships.HostedOn', 'my_webserver')] + actual_relationship = sorted([ + (relation.type, node.name) for + relation, node in node_tpl.relationships.items()]) + self.assertEqual(expected_relationship, actual_relationship) + if node_tpl.name == 'mysql_database': + self.assertEqual( + [('tosca.relationships.HostedOn', 'my_dbms')], + [(relation.type, node.name) for + relation, + node in node_tpl.relationships.items()]) + if node_tpl.name == 'my_server': + self.assertEqual( + [('tosca.relationships.AttachesTo', 'my_storage')], + [(relation.type, node.name) for + relation, + node in node_tpl.relationships.items()]) + + def test_template_requirements_not_implemented(self): + # TODO(spzala): replace this test with new one once TOSCA types look up + # support is implemented. + """Requirements that yet need to be implemented + + The following requirement formats are not yet implemented, + due to look up dependency: + 1. Requirement expressed via TOSCA types to provision a node + with explicit relationship. + 2. Requirement expressed via TOSCA types with a filter. + """ + tpl_snippet_1 = ''' + node_templates: + mysql_database: + type: tosca.nodes.Database + description: Requires a particular node type and relationship. + To be full-filled via lookup into node repository. + requirements: + - req1: + node: tosca.nodes.DBMS + relationship: tosca.relationships.HostedOn + ''' + + tpl_snippet_2 = ''' + node_templates: + my_webserver: + type: tosca.nodes.WebServer + description: Requires a particular node type with a filter. + To be full-filled via lookup into node repository. + requirements: + - req1: + node: tosca.nodes.Compute + target_filter: + properties: + num_cpus: { in_range: [ 1, 4 ] } + mem_size: { greater_or_equal: 2 } + capabilities: + - tosca.capabilities.OS: + properties: + architecture: x86_64 + type: linux + ''' + + tpl_snippet_3 = ''' + node_templates: + my_webserver2: + type: tosca.nodes.WebServer + description: Requires a node type with a particular capability. + To be full-filled via lookup into node repository. + requirements: + - req1: + node: tosca.nodes.Compute + relationship: tosca.relationships.HostedOn + capability: tosca.capabilities.Container + ''' + self._requirements_not_implemented(tpl_snippet_1, 'mysql_database') + self._requirements_not_implemented(tpl_snippet_2, 'my_webserver') + self._requirements_not_implemented(tpl_snippet_3, 'my_webserver2') + + def _requirements_not_implemented(self, tpl_snippet, tpl_name): + nodetemplates = (toscaparser.utils.yamlparser. + simple_parse(tpl_snippet))['node_templates'] + self.assertRaises( + NotImplementedError, + lambda: NodeTemplate(tpl_name, nodetemplates).relationships) + + # Test the following: + # 1. Custom node type derived from 'WebApplication' named 'TestApp' + # with a custom Capability Type 'TestCapability' + # 2. Same as #1, but referencing a custom 'TestCapability' Capability Type + # that is not defined + def test_custom_capability_type_definition(self): + tpl_snippet = ''' + node_templates: + test_app: + type: tosca.nodes.WebApplication.TestApp + capabilities: + test_cap: + properties: + test: 1 + ''' + # custom node type definition with custom capability type definition + custom_def = ''' + tosca.nodes.WebApplication.TestApp: + derived_from: tosca.nodes.WebApplication + capabilities: + test_cap: + type: tosca.capabilities.TestCapability + tosca.capabilities.TestCapability: + derived_from: tosca.capabilities.Root + properties: + test: + type: integer + required: false + ''' + expected_capabilities = ['app_endpoint', 'feature', 'test_cap'] + nodetemplates = (toscaparser.utils.yamlparser. + simple_parse(tpl_snippet))['node_templates'] + custom_def = (toscaparser.utils.yamlparser. + simple_parse(custom_def)) + name = list(nodetemplates.keys())[0] + tpl = NodeTemplate(name, nodetemplates, custom_def) + self.assertEqual( + expected_capabilities, + sorted(tpl.get_capabilities().keys())) + + # custom definition without valid capability type definition + custom_def = ''' + tosca.nodes.WebApplication.TestApp: + derived_from: tosca.nodes.WebApplication + capabilities: + test_cap: + type: tosca.capabilities.TestCapability + ''' + custom_def = (toscaparser.utils.yamlparser. + simple_parse(custom_def)) + tpl = NodeTemplate(name, nodetemplates, custom_def) + err = self.assertRaises( + exception.InvalidTypeError, + lambda: NodeTemplate(name, nodetemplates, + custom_def).get_capabilities_objects()) + self.assertEqual('Type "tosca.capabilities.TestCapability" is not ' + 'a valid type.', six.text_type(err)) + + def test_local_template_with_local_relpath_import(self): + tosca_tpl = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "data/tosca_single_instance_wordpress.yaml") + tosca = ToscaTemplate(tosca_tpl) + self.assertTrue(tosca.topology_template.custom_defs) + + def test_local_template_with_url_import(self): + tosca_tpl = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "data/tosca_single_instance_wordpress_with_url_import.yaml") + tosca = ToscaTemplate(tosca_tpl) + self.assertTrue(tosca.topology_template.custom_defs) + + def test_url_template_with_local_relpath_import(self): + tosca_tpl = ('https://raw.githubusercontent.com/openstack/' + 'tosca-parser/master/toscaparser/tests/data/' + 'tosca_single_instance_wordpress.yaml') + tosca = ToscaTemplate(tosca_tpl, None, False) + self.assertTrue(tosca.topology_template.custom_defs) + + def test_url_template_with_local_abspath_import(self): + tosca_tpl = ('https://raw.githubusercontent.com/openstack/' + 'tosca-parser/master/toscaparser/tests/data/' + 'tosca_single_instance_wordpress_with_local_abspath_' + 'import.yaml') + self.assertRaises(exception.ValidationError, ToscaTemplate, tosca_tpl, + None, False) + err_msg = (_('Absolute file name "/tmp/tosca-parser/toscaparser/tests' + '/data/custom_types/wordpress.yaml" cannot be used in a ' + 'URL-based input template "%(tpl)s".') + % {'tpl': tosca_tpl}) + exception.ExceptionCollector.assertExceptionMessage(ImportError, + err_msg) + + def test_url_template_with_url_import(self): + tosca_tpl = ('https://raw.githubusercontent.com/openstack/' + 'tosca-parser/master/toscaparser/tests/data/' + 'tosca_single_instance_wordpress_with_url_import.yaml') + tosca = ToscaTemplate(tosca_tpl, None, False) + self.assertTrue(tosca.topology_template.custom_defs) + + def test_csar_parsing_wordpress(self): + csar_archive = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + 'data/CSAR/csar_wordpress.zip') + self.assertTrue(ToscaTemplate(csar_archive)) + + def test_csar_parsing_elk_url_based(self): + csar_archive = ('https://github.com/openstack/tosca-parser/raw/master/' + 'toscaparser/tests/data/CSAR/csar_elk.zip') + self.assertTrue(ToscaTemplate(csar_archive, None, False)) + + def test_nested_imports_in_templates(self): + tosca_tpl = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "data/test_instance_nested_imports.yaml") + tosca = ToscaTemplate(tosca_tpl) + expected_custom_types = ['tosca.nodes.WebApplication.WordPress', + 'test_namespace_prefix.Rsyslog', + 'Test2ndRsyslogType', + 'test_2nd_namespace_prefix.Rsyslog', + 'tosca.nodes.SoftwareComponent.Logstash', + 'tosca.nodes.SoftwareComponent.Rsyslog.' + 'TestRsyslogType'] + self.assertItemsEqual(tosca.topology_template.custom_defs.keys(), + expected_custom_types) + + def test_invalid_template_file(self): + template_file = 'invalid template file' + expected_msg = (_('"%s" is not a valid file.') % template_file) + self.assertRaises( + exception.ValidationError, + ToscaTemplate, template_file, None, False) + exception.ExceptionCollector.assertExceptionMessage(ValueError, + expected_msg) + + def test_multiple_validation_errors(self): + tosca_tpl = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "data/test_multiple_validation_errors.yaml") + self.assertRaises(exception.ValidationError, ToscaTemplate, tosca_tpl, + None) + valid_versions = ', '.join(ToscaTemplate.VALID_TEMPLATE_VERSIONS) + err1_msg = (_('The template version "tosca_simple_yaml_1" is invalid. ' + 'Valid versions are "%s".') % valid_versions) + exception.ExceptionCollector.assertExceptionMessage( + exception.InvalidTemplateVersion, err1_msg) + + err2_msg = _('Import "custom_types/not_there.yaml" is not valid.') + exception.ExceptionCollector.assertExceptionMessage( + ImportError, err2_msg) + + err3_msg = _('Type "tosca.nodes.WebApplication.WordPress" is not a ' + 'valid type.') + exception.ExceptionCollector.assertExceptionMessage( + exception.InvalidTypeError, err3_msg) + + err4_msg = _('Node template "wordpress" contains unknown field ' + '"requirement". Refer to the definition to verify valid ' + 'values.') + exception.ExceptionCollector.assertExceptionMessage( + exception.UnknownFieldError, err4_msg) + + err5_msg = _('\'Property "passwords" was not found in node template ' + '"mysql_database".\'') + exception.ExceptionCollector.assertExceptionMessage( + KeyError, err5_msg) + + err6_msg = _('Template "mysql_dbms" is missing required field "type".') + exception.ExceptionCollector.assertExceptionMessage( + exception.MissingRequiredFieldError, err6_msg) + + err7_msg = _('Node template "mysql_dbms" contains unknown field ' + '"type1". Refer to the definition to verify valid ' + 'values.') + exception.ExceptionCollector.assertExceptionMessage( + exception.UnknownFieldError, err7_msg) + + err8_msg = _('\'Node template "server1" was not found.\'') + exception.ExceptionCollector.assertExceptionMessage( + KeyError, err8_msg) + + err9_msg = _('"relationship" used in template "webserver" is missing ' + 'required field "type".') + exception.ExceptionCollector.assertExceptionMessage( + exception.MissingRequiredFieldError, err9_msg) + + def test_invalid_section_names(self): + tosca_tpl = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "data/test_invalid_section_names.yaml") + self.assertRaises(exception.ValidationError, ToscaTemplate, tosca_tpl, + None) + err1_msg = _('Template contains unknown field ' + '"tosca_definitions_versions". Refer to the definition ' + 'to verify valid values.') + exception.ExceptionCollector.assertExceptionMessage( + exception.UnknownFieldError, err1_msg) + + err2_msg = _('Template contains unknown field "descriptions". ' + 'Refer to the definition to verify valid values.') + exception.ExceptionCollector.assertExceptionMessage( + exception.UnknownFieldError, err2_msg) + + err3_msg = _('Template contains unknown field "import". Refer to ' + 'the definition to verify valid values.') + exception.ExceptionCollector.assertExceptionMessage( + exception.UnknownFieldError, err3_msg) + + err4_msg = _('Template contains unknown field "topology_templates". ' + 'Refer to the definition to verify valid values.') + exception.ExceptionCollector.assertExceptionMessage( + exception.UnknownFieldError, err4_msg) + + def test_csar_with_alternate_extenstion(self): + tosca_tpl = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "data/CSAR/csar_elk.csar") + tosca = ToscaTemplate(tosca_tpl) + self.assertTrue(tosca.topology_template.custom_defs) + + def test_available_rel_tpls(self): + tosca_tpl = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "data/test_available_rel_tpls.yaml") + tosca = ToscaTemplate(tosca_tpl) + for node in tosca.nodetemplates: + for relationship, target in node.relationships.items(): + try: + target.relationships + except TypeError as error: + self.fail(error) + + def test_no_input(self): + self.assertRaises(exception.ValidationError, ToscaTemplate, None, + None, False, None) + err_msg = (('No path or yaml_dict_tpl was provided. ' + 'There is nothing to parse.')) + exception.ExceptionCollector.assertExceptionMessage(ValueError, + err_msg) + + def test_path_and_yaml_dict_tpl_input(self): + test_tpl = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "data/tosca_helloworld.yaml") + + yaml_dict_tpl = toscaparser.utils.yamlparser.load_yaml(test_tpl) + + tosca = ToscaTemplate(test_tpl, yaml_dict_tpl=yaml_dict_tpl) + + self.assertEqual(tosca.version, "tosca_simple_yaml_1_0") + + def test_yaml_dict_tpl_input(self): + test_tpl = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "data/tosca_helloworld.yaml") + + yaml_dict_tpl = toscaparser.utils.yamlparser.load_yaml(test_tpl) + + tosca = ToscaTemplate(yaml_dict_tpl=yaml_dict_tpl) + + self.assertEqual(tosca.version, "tosca_simple_yaml_1_0") + + def test_yaml_dict_tpl_with_params_and_url_import(self): + test_tpl = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "data/tosca_single_instance_wordpress_with_url_import.yaml") + + yaml_dict_tpl = toscaparser.utils.yamlparser.load_yaml(test_tpl) + + params = {'db_name': 'my_wordpress', 'db_user': 'my_db_user', + 'db_root_pwd': 'mypasswd'} + + tosca = ToscaTemplate(parsed_params=params, + yaml_dict_tpl=yaml_dict_tpl) + + self.assertEqual(tosca.version, "tosca_simple_yaml_1_0") + + def test_yaml_dict_tpl_with_rel_import(self): + test_tpl = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "data/tosca_single_instance_wordpress.yaml") + + yaml_dict_tpl = toscaparser.utils.yamlparser.load_yaml(test_tpl) + + self.assertRaises(exception.ValidationError, ToscaTemplate, None, + None, False, yaml_dict_tpl) + err_msg = (_('Relative file name "custom_types/wordpress.yaml" ' + 'cannot be used in a pre-parsed input template.')) + exception.ExceptionCollector.assertExceptionMessage(ImportError, + err_msg) + + def test_yaml_dict_tpl_with_fullpath_import(self): + test_tpl = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "data/tosca_single_instance_wordpress.yaml") + + yaml_dict_tpl = toscaparser.utils.yamlparser.load_yaml(test_tpl) + + yaml_dict_tpl['imports'] = [os.path.join(os.path.dirname( + os.path.abspath(__file__)), "data/custom_types/wordpress.yaml")] + + params = {'db_name': 'my_wordpress', 'db_user': 'my_db_user', + 'db_root_pwd': 'mypasswd'} + + tosca = ToscaTemplate(parsed_params=params, + yaml_dict_tpl=yaml_dict_tpl) + + self.assertEqual(tosca.version, "tosca_simple_yaml_1_0") + + def test_policies_for_node_templates(self): + tosca_tpl = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "data/policies/tosca_policy_template.yaml") + tosca = ToscaTemplate(tosca_tpl) + + for policy in tosca.topology_template.policies: + if policy.name == 'my_compute_placement_policy': + self.assertEqual('tosca.policies.Placement', policy.type) + self.assertEqual(['my_server_1', 'my_server_2'], + policy.targets) + self.assertEqual('node_templates', policy.get_targets_type()) + for node in policy.targets_list: + if node.name == 'my_server_1': + '''Test property value''' + props = node.get_properties() + if props and 'mem_size' in props.keys(): + self.assertEqual(props['mem_size'].value, + '4096 MB') + + def test_policies_for_groups(self): + tosca_tpl = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "data/policies/tosca_policy_template.yaml") + tosca = ToscaTemplate(tosca_tpl) + + for policy in tosca.topology_template.policies: + if policy.name == 'my_groups_placement': + self.assertEqual('mycompany.mytypes.myScalingPolicy', + policy.type) + self.assertEqual(['webserver_group'], policy.targets) + self.assertEqual('groups', policy.get_targets_type()) + group = policy.get_targets_list()[0] + for node in group.get_member_nodes(): + if node.name == 'my_server_2': + '''Test property value''' + props = node.get_properties() + if props and 'mem_size' in props.keys(): + self.assertEqual(props['mem_size'].value, + '4096 MB') + + def test_node_filter(self): + tosca_tpl = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "data/test_node_filter.yaml") + ToscaTemplate(tosca_tpl) + + def test_attributes_inheritance(self): + tosca_tpl = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "data/test_attributes_inheritance.yaml") + ToscaTemplate(tosca_tpl) + + def test_repositories_definition(self): + tosca_tpl = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "data/test_repositories_definition.yaml") + ToscaTemplate(tosca_tpl) + + def test_custom_caps_def(self): + tosca_tpl = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "data/test_custom_caps_def.yaml") + ToscaTemplate(tosca_tpl) + + def test_custom_rel_with_script(self): + tosca_tpl = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "data/test_tosca_custom_rel_with_script.yaml") + tosca = ToscaTemplate(tosca_tpl) + rel = tosca.relationship_templates[0] + self.assertEqual(len(rel.interfaces), 1) + self.assertEqual(rel.interfaces[0].type, "Configure") diff --git a/tosca2heat/tosca-parser/toscaparser/tests/test_toscatplvalidation.py b/tosca2heat/tosca-parser/toscaparser/tests/test_toscatplvalidation.py new file mode 100644 index 0000000..81a1a6c --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/test_toscatplvalidation.py @@ -0,0 +1,1381 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import os +import six + +from toscaparser.common import exception +from toscaparser.imports import ImportsLoader +from toscaparser.nodetemplate import NodeTemplate +from toscaparser.parameters import Input +from toscaparser.parameters import Output +from toscaparser.policy import Policy +from toscaparser.relationship_template import RelationshipTemplate +from toscaparser.tests.base import TestCase +from toscaparser.topology_template import TopologyTemplate +from toscaparser.tosca_template import ToscaTemplate +from toscaparser.triggers import Triggers +from toscaparser.utils.gettextutils import _ + +import toscaparser.utils.yamlparser + + +class ToscaTemplateValidationTest(TestCase): + + def test_well_defined_template(self): + tpl_path = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "data/tosca_single_instance_wordpress.yaml") + self.assertIsNotNone(ToscaTemplate(tpl_path)) + + def test_first_level_sections(self): + tpl_path = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "data/test_tosca_top_level_error1.yaml") + self.assertRaises(exception.ValidationError, ToscaTemplate, tpl_path) + exception.ExceptionCollector.assertExceptionMessage( + exception.MissingRequiredFieldError, + _('Template is missing required field ' + '"tosca_definitions_version".')) + + tpl_path = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "data/test_tosca_top_level_error2.yaml") + self.assertRaises(exception.ValidationError, ToscaTemplate, tpl_path) + exception.ExceptionCollector.assertExceptionMessage( + exception.UnknownFieldError, + _('Template contains unknown field "node_template". Refer to the ' + 'definition to verify valid values.')) + + def test_template_with_imports_validation(self): + tpl_path = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "data/tosca_imports_validation.yaml") + self.assertRaises(exception.ValidationError, ToscaTemplate, tpl_path) + exception.ExceptionCollector.assertExceptionMessage( + exception.UnknownFieldError, + _('Template custom_types/imported_sample.yaml contains unknown ' + 'field "descriptions". Refer to the definition' + ' to verify valid values.')) + exception.ExceptionCollector.assertExceptionMessage( + exception.UnknownFieldError, + _('Template custom_types/imported_sample.yaml contains unknown ' + 'field "node_typess". Refer to the definition to ' + 'verify valid values.')) + exception.ExceptionCollector.assertExceptionMessage( + exception.UnknownFieldError, + _('Template custom_types/imported_sample.yaml contains unknown ' + 'field "tosca1_definitions_version". Refer to the definition' + ' to verify valid values.')) + exception.ExceptionCollector.assertExceptionMessage( + exception.InvalidTemplateVersion, + _('The template version "tosca_simple_yaml_1_10 in ' + 'custom_types/imported_sample.yaml" is invalid. ' + 'Valid versions are "tosca_simple_yaml_1_0, ' + 'tosca_simple_profile_for_nfv_1_0_0".')) + exception.ExceptionCollector.assertExceptionMessage( + exception.UnknownFieldError, + _('Template custom_types/imported_sample.yaml contains unknown ' + 'field "policy_types1". Refer to the definition to ' + 'verify valid values.')) + exception.ExceptionCollector.assertExceptionMessage( + exception.UnknownFieldError, + _('Nodetype"tosca.nodes.SoftwareComponent.Logstash" contains ' + 'unknown field "capabilities1". Refer to the definition ' + 'to verify valid values.')) + exception.ExceptionCollector.assertExceptionMessage( + exception.UnknownFieldError, + _('Policy "mycompany.mytypes.myScalingPolicy" contains unknown ' + 'field "derived1_from". Refer to the definition to ' + 'verify valid values.')) + + def test_inputs(self): + tpl_snippet = ''' + inputs: + cpus: + type: integer + description: Number of CPUs for the server. + constraint: + - valid_values: [ 1, 2, 4, 8 ] + ''' + inputs = (toscaparser.utils.yamlparser. + simple_parse(tpl_snippet)['inputs']) + name, attrs = list(inputs.items())[0] + input = Input(name, attrs) + err = self.assertRaises(exception.UnknownFieldError, input.validate) + self.assertEqual(_('Input "cpus" contains unknown field "constraint". ' + 'Refer to the definition to verify valid values.'), + err.__str__()) + + def _imports_content_test(self, tpl_snippet, path, custom_type_def): + imports = (toscaparser.utils.yamlparser. + simple_parse(tpl_snippet)['imports']) + loader = ImportsLoader(imports, path, custom_type_def) + return loader.get_custom_defs() + + def test_imports_without_templates(self): + tpl_snippet = ''' + imports: + # omitted here for brevity + ''' + path = 'toscaparser/tests/data/tosca_elk.yaml' + errormsg = _('"imports" keyname is defined without including ' + 'templates.') + err = self.assertRaises(exception.ValidationError, + self._imports_content_test, + tpl_snippet, + path, + "node_types") + self.assertEqual(errormsg, err.__str__()) + + def test_imports_with_name_without_templates(self): + tpl_snippet = ''' + imports: + - some_definitions: + ''' + path = 'toscaparser/tests/data/tosca_elk.yaml' + errormsg = _('A template file name is not provided with import ' + 'definition "some_definitions".') + err = self.assertRaises(exception.ValidationError, + self._imports_content_test, + tpl_snippet, path, None) + self.assertEqual(errormsg, err.__str__()) + + def test_imports_without_import_name(self): + tpl_snippet = ''' + imports: + - custom_types/paypalpizzastore_nodejs_app.yaml + - https://raw.githubusercontent.com/openstack/\ +tosca-parser/master/toscaparser/tests/data/custom_types/wordpress.yaml + ''' + path = 'toscaparser/tests/data/tosca_elk.yaml' + custom_defs = self._imports_content_test(tpl_snippet, + path, + "node_types") + self.assertTrue(custom_defs) + + def test_imports_wth_import_name(self): + tpl_snippet = ''' + imports: + - some_definitions: custom_types/paypalpizzastore_nodejs_app.yaml + - more_definitions: + file: 'https://raw.githubusercontent.com/openstack/tosca-parser\ +/master/toscaparser/tests/data/custom_types/wordpress.yaml' + namespace_prefix: single_instance_wordpress + ''' + path = 'toscaparser/tests/data/tosca_elk.yaml' + custom_defs = self._imports_content_test(tpl_snippet, + path, + "node_types") + self.assertTrue(custom_defs.get("single_instance_wordpress.tosca." + "nodes.WebApplication.WordPress")) + + def test_imports_wth_namespace_prefix(self): + tpl_snippet = ''' + imports: + - more_definitions: + file: custom_types/nested_rsyslog.yaml + namespace_prefix: testprefix + ''' + path = 'toscaparser/tests/data/tosca_elk.yaml' + custom_defs = self._imports_content_test(tpl_snippet, + path, + "node_types") + self.assertTrue(custom_defs.get("testprefix.Rsyslog")) + + def test_imports_with_no_main_template(self): + tpl_snippet = ''' + imports: + - some_definitions: https://raw.githubusercontent.com/openstack/\ +tosca-parser/master/toscaparser/tests/data/custom_types/wordpress.yaml + - some_definitions: + file: my_defns/my_typesdefs_n.yaml + ''' + errormsg = _('Input tosca template is not provided.') + err = self.assertRaises(exception.ValidationError, + self._imports_content_test, + tpl_snippet, None, None) + self.assertEqual(errormsg, err.__str__()) + + def test_imports_duplicate_name(self): + tpl_snippet = ''' + imports: + - some_definitions: https://raw.githubusercontent.com/openstack/\ +tosca-parser/master/toscaparser/tests/data/custom_types/wordpress.yaml + - some_definitions: + file: my_defns/my_typesdefs_n.yaml + ''' + errormsg = _('Duplicate import name "some_definitions" was found.') + path = 'toscaparser/tests/data/tosca_elk.yaml' + err = self.assertRaises(exception.ValidationError, + self._imports_content_test, + tpl_snippet, path, None) + self.assertEqual(errormsg, err.__str__()) + + def test_imports_missing_req_field_in_def(self): + tpl_snippet = ''' + imports: + - more_definitions: + file1: my_defns/my_typesdefs_n.yaml + repository: my_company_repo + namespace_uri: http://mycompany.com/ns/tosca/2.0 + namespace_prefix: mycompany + ''' + errormsg = _('Import of template "more_definitions" is missing ' + 'required field "file".') + path = 'toscaparser/tests/data/tosca_elk.yaml' + err = self.assertRaises(exception.MissingRequiredFieldError, + self._imports_content_test, + tpl_snippet, path, None) + self.assertEqual(errormsg, err.__str__()) + + def test_imports_file_with_uri(self): + tpl_snippet = ''' + imports: + - more_definitions: + file: https://raw.githubusercontent.com/openstack/\ +tosca-parser/master/toscaparser/tests/data/custom_types/wordpress.yaml + ''' + path = 'https://raw.githubusercontent.com/openstack/\ +tosca-parser/master/toscaparser/tests/data/\ +tosca_single_instance_wordpress_with_url_import.yaml' + custom_defs = self._imports_content_test(tpl_snippet, + path, + "node_types") + self.assertTrue(custom_defs.get("tosca.nodes." + "WebApplication.WordPress")) + + def test_imports_file_namespace_fields(self): + tpl_snippet = ''' + imports: + - more_definitions: + file: https://raw.githubusercontent.com/openstack/\ +heat-translator/master/translator/tests/data/custom_types/wordpress.yaml + namespace_prefix: mycompany + namespace_uri: http://docs.oasis-open.org/tosca/ns/simple/yaml/1.0 + ''' + path = 'toscaparser/tests/data/tosca_elk.yaml' + custom_defs = self._imports_content_test(tpl_snippet, + path, + "node_types") + self.assertTrue(custom_defs.get("mycompany.tosca.nodes." + "WebApplication.WordPress")) + + def test_import_error_file_uri(self): + tpl_snippet = ''' + imports: + - more_definitions: + file: mycompany.com/ns/tosca/2.0/toscaparser/tests/data\ +/tosca_elk.yaml + namespace_prefix: mycompany + namespace_uri: http://docs.oasis-open.org/tosca/ns/simple/yaml/1.0 + ''' + path = 'toscaparser/tests/data/tosca_elk.yaml' + self.assertRaises(ImportError, + self._imports_content_test, + tpl_snippet, path, None) + + def test_import_single_line_error(self): + tpl_snippet = ''' + imports: + - some_definitions: abc.com/tests/data/tosca_elk.yaml + ''' + errormsg = _('Import "abc.com/tests/data/tosca_elk.yaml" is not ' + 'valid.') + path = 'toscaparser/tests/data/tosca_elk.yaml' + err = self.assertRaises(ImportError, + self._imports_content_test, + tpl_snippet, path, None) + self.assertEqual(errormsg, err.__str__()) + + def test_outputs(self): + tpl_snippet = ''' + outputs: + server_address: + description: IP address of server instance. + values: { get_property: [server, private_address] } + ''' + outputs = (toscaparser.utils.yamlparser. + simple_parse(tpl_snippet)['outputs']) + name, attrs = list(outputs.items())[0] + output = Output(name, attrs) + try: + output.validate() + except Exception as err: + self.assertTrue( + isinstance(err, exception.MissingRequiredFieldError)) + self.assertEqual(_('Output "server_address" is missing required ' + 'field "value".'), err.__str__()) + + tpl_snippet = ''' + outputs: + server_address: + descriptions: IP address of server instance. + value: { get_property: [server, private_address] } + ''' + outputs = (toscaparser.utils.yamlparser. + simple_parse(tpl_snippet)['outputs']) + name, attrs = list(outputs.items())[0] + output = Output(name, attrs) + try: + output.validate() + except Exception as err: + self.assertTrue(isinstance(err, exception.UnknownFieldError)) + self.assertEqual(_('Output "server_address" contains unknown ' + 'field "descriptions". Refer to the definition ' + 'to verify valid values.'), + err.__str__()) + + def test_groups(self): + tpl_snippet = ''' + node_templates: + server: + type: tosca.nodes.Compute + requirements: + - log_endpoint: + capability: log_endpoint + + mysql_dbms: + type: tosca.nodes.DBMS + properties: + root_password: aaa + port: 3376 + + groups: + webserver_group: + type: tosca.groups.Root + members: [ server, mysql_dbms ] + ''' + tpl = (toscaparser.utils.yamlparser.simple_parse(tpl_snippet)) + TopologyTemplate(tpl, None) + + def test_groups_with_missing_required_field(self): + tpl_snippet = ''' + node_templates: + server: + type: tosca.nodes.Compute + requirements: + - log_endpoint: + capability: log_endpoint + + mysql_dbms: + type: tosca.nodes.DBMS + properties: + root_password: aaa + port: 3376 + + groups: + webserver_group: + members: ['server', 'mysql_dbms'] + ''' + tpl = (toscaparser.utils.yamlparser.simple_parse(tpl_snippet)) + err = self.assertRaises(exception.MissingRequiredFieldError, + TopologyTemplate, tpl, None) + expectedmessage = _('Template "webserver_group" is missing ' + 'required field "type".') + self.assertEqual(expectedmessage, err.__str__()) + + def test_groups_with_unknown_target(self): + tpl_snippet = ''' + node_templates: + server: + type: tosca.nodes.Compute + requirements: + - log_endpoint: + capability: log_endpoint + + mysql_dbms: + type: tosca.nodes.DBMS + properties: + root_password: aaa + port: 3376 + + groups: + webserver_group: + type: tosca.groups.Root + members: [ serv, mysql_dbms ] + ''' + tpl = (toscaparser.utils.yamlparser.simple_parse(tpl_snippet)) + expectedmessage = _('"Target member "serv" is not found in ' + 'node_templates"') + err = self.assertRaises(exception.InvalidGroupTargetException, + TopologyTemplate, tpl, None) + self.assertEqual(expectedmessage, err.__str__()) + + def test_groups_with_repeated_targets(self): + tpl_snippet = ''' + node_templates: + server: + type: tosca.nodes.Compute + requirements: + - log_endpoint: + capability: log_endpoint + + mysql_dbms: + type: tosca.nodes.DBMS + properties: + root_password: aaa + port: 3376 + + groups: + webserver_group: + type: tosca.groups.Root + members: [ server, server, mysql_dbms ] + ''' + tpl = (toscaparser.utils.yamlparser.simple_parse(tpl_snippet)) + expectedmessage = _('"Member nodes ' + '"[\'server\', \'server\', \'mysql_dbms\']" ' + 'should be >= 1 and not repeated"') + err = self.assertRaises(exception.InvalidGroupTargetException, + TopologyTemplate, tpl, None) + self.assertEqual(expectedmessage, err.__str__()) + + def test_groups_with_only_one_target(self): + tpl_snippet = ''' + node_templates: + server: + type: tosca.nodes.Compute + requirements: + - log_endpoint: + capability: log_endpoint + + mysql_dbms: + type: tosca.nodes.DBMS + properties: + root_password: aaa + port: 3376 + + groups: + webserver_group: + type: tosca.groups.Root + members: [] + ''' + tpl = (toscaparser.utils.yamlparser.simple_parse(tpl_snippet)) + expectedmessage = _('"Member nodes "[]" should be >= 1 ' + 'and not repeated"') + err = self.assertRaises(exception.InvalidGroupTargetException, + TopologyTemplate, tpl, None) + self.assertEqual(expectedmessage, err.__str__()) + + def _custom_types(self): + custom_types = {} + def_file = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "data/custom_types/wordpress.yaml") + custom_type = toscaparser.utils.yamlparser.load_yaml(def_file) + node_types = custom_type['node_types'] + for name in node_types: + defintion = node_types[name] + custom_types[name] = defintion + return custom_types + + def _single_node_template_content_test(self, tpl_snippet): + nodetemplates = (toscaparser.utils.yamlparser. + simple_ordered_parse(tpl_snippet))['node_templates'] + name = list(nodetemplates.keys())[0] + nodetemplate = NodeTemplate(name, nodetemplates, + self._custom_types()) + nodetemplate.validate() + nodetemplate.requirements + nodetemplate.get_capabilities_objects() + nodetemplate.get_properties_objects() + nodetemplate.interfaces + + def test_node_templates(self): + tpl_snippet = ''' + node_templates: + server: + capabilities: + host: + properties: + disk_size: 10 + num_cpus: 4 + mem_size: 4096 + os: + properties: + architecture: x86_64 + type: Linux + distribution: Fedora + version: 18.0 + ''' + expectedmessage = _('Template "server" is missing required field ' + '"type".') + err = self.assertRaises( + exception.MissingRequiredFieldError, + lambda: self._single_node_template_content_test(tpl_snippet)) + self.assertEqual(expectedmessage, err.__str__()) + + def test_node_template_with_wrong_properties_keyname(self): + """Node template keyname 'properties' given as 'propertiessss'.""" + tpl_snippet = ''' + node_templates: + mysql_dbms: + type: tosca.nodes.DBMS + propertiessss: + root_password: aaa + port: 3376 + ''' + expectedmessage = _('Node template "mysql_dbms" contains unknown ' + 'field "propertiessss". Refer to the definition ' + 'to verify valid values.') + err = self.assertRaises( + exception.UnknownFieldError, + lambda: self._single_node_template_content_test(tpl_snippet)) + self.assertEqual(expectedmessage, err.__str__()) + + def test_node_template_with_wrong_requirements_keyname(self): + """Node template keyname 'requirements' given as 'requirement'.""" + tpl_snippet = ''' + node_templates: + mysql_dbms: + type: tosca.nodes.DBMS + properties: + root_password: aaa + port: 3376 + requirement: + - host: server + ''' + expectedmessage = _('Node template "mysql_dbms" contains unknown ' + 'field "requirement". Refer to the definition to ' + 'verify valid values.') + err = self.assertRaises( + exception.UnknownFieldError, + lambda: self._single_node_template_content_test(tpl_snippet)) + self.assertEqual(expectedmessage, err.__str__()) + + def test_node_template_with_wrong_interfaces_keyname(self): + """Node template keyname 'interfaces' given as 'interfac'.""" + tpl_snippet = ''' + node_templates: + mysql_dbms: + type: tosca.nodes.DBMS + properties: + root_password: aaa + port: 3376 + requirements: + - host: server + interfac: + Standard: + configure: mysql_database_configure.sh + ''' + expectedmessage = _('Node template "mysql_dbms" contains unknown ' + 'field "interfac". Refer to the definition to ' + 'verify valid values.') + err = self.assertRaises( + exception.UnknownFieldError, + lambda: self._single_node_template_content_test(tpl_snippet)) + self.assertEqual(expectedmessage, err.__str__()) + + def test_node_template_with_wrong_capabilities_keyname(self): + """Node template keyname 'capabilities' given as 'capabilitiis'.""" + tpl_snippet = ''' + node_templates: + mysql_database: + type: tosca.nodes.Database + properties: + db_name: { get_input: db_name } + db_user: { get_input: db_user } + db_password: { get_input: db_pwd } + capabilitiis: + database_endpoint: + properties: + port: { get_input: db_port } + ''' + expectedmessage = _('Node template "mysql_database" contains unknown ' + 'field "capabilitiis". Refer to the definition to ' + 'verify valid values.') + err = self.assertRaises( + exception.UnknownFieldError, + lambda: self._single_node_template_content_test(tpl_snippet)) + self.assertEqual(expectedmessage, err.__str__()) + + def test_node_template_with_wrong_artifacts_keyname(self): + """Node template keyname 'artifacts' given as 'artifactsss'.""" + tpl_snippet = ''' + node_templates: + mysql_database: + type: tosca.nodes.Database + artifactsss: + db_content: + implementation: files/my_db_content.txt + type: tosca.artifacts.File + ''' + expectedmessage = _('Node template "mysql_database" contains unknown ' + 'field "artifactsss". Refer to the definition to ' + 'verify valid values.') + err = self.assertRaises( + exception.UnknownFieldError, + lambda: self._single_node_template_content_test(tpl_snippet)) + self.assertEqual(expectedmessage, err.__str__()) + + def test_node_template_with_multiple_wrong_keynames(self): + """Node templates given with multiple wrong keynames.""" + tpl_snippet = ''' + node_templates: + mysql_dbms: + type: tosca.nodes.DBMS + propertieees: + root_password: aaa + port: 3376 + requirements: + - host: server + interfacs: + Standard: + configure: mysql_database_configure.sh + ''' + expectedmessage = _('Node template "mysql_dbms" contains unknown ' + 'field "propertieees". Refer to the definition to ' + 'verify valid values.') + err = self.assertRaises( + exception.UnknownFieldError, + lambda: self._single_node_template_content_test(tpl_snippet)) + self.assertEqual(expectedmessage, err.__str__()) + + tpl_snippet = ''' + node_templates: + mysql_database: + type: tosca.nodes.Database + properties: + name: { get_input: db_name } + user: { get_input: db_user } + password: { get_input: db_pwd } + capabilitiiiies: + database_endpoint: + properties: + port: { get_input: db_port } + requirementsss: + - host: + node: mysql_dbms + interfac: + Standard: + configure: mysql_database_configure.sh + + ''' + expectedmessage = _('Node template "mysql_database" contains unknown ' + 'field "capabilitiiiies". Refer to the definition ' + 'to verify valid values.') + err = self.assertRaises( + exception.UnknownFieldError, + lambda: self._single_node_template_content_test(tpl_snippet)) + self.assertEqual(expectedmessage, err.__str__()) + + def test_node_template_type(self): + tpl_snippet = ''' + node_templates: + mysql_database: + type: tosca.nodes.Databases + properties: + db_name: { get_input: db_name } + db_user: { get_input: db_user } + db_password: { get_input: db_pwd } + capabilities: + database_endpoint: + properties: + port: { get_input: db_port } + requirements: + - host: mysql_dbms + interfaces: + Standard: + configure: mysql_database_configure.sh + ''' + expectedmessage = _('Type "tosca.nodes.Databases" is not ' + 'a valid type.') + err = self.assertRaises( + exception.InvalidTypeError, + lambda: self._single_node_template_content_test(tpl_snippet)) + self.assertEqual(expectedmessage, err.__str__()) + + def test_node_template_requirements(self): + tpl_snippet = ''' + node_templates: + webserver: + type: tosca.nodes.WebServer + requirements: + host: server + interfaces: + Standard: + create: webserver_install.sh + start: d.sh + ''' + expectedmessage = _('"requirements" of template "webserver" must be ' + 'of type "list".') + err = self.assertRaises( + exception.TypeMismatchError, + lambda: self._single_node_template_content_test(tpl_snippet)) + self.assertEqual(expectedmessage, err.__str__()) + + tpl_snippet = ''' + node_templates: + mysql_database: + type: tosca.nodes.Database + properties: + db_name: { get_input: db_name } + db_user: { get_input: db_user } + db_password: { get_input: db_pwd } + capabilities: + database_endpoint: + properties: + port: { get_input: db_port } + requirements: + - host: mysql_dbms + - database_endpoint: mysql_database + interfaces: + Standard: + configure: mysql_database_configure.sh + ''' + expectedmessage = _('"requirements" of template "mysql_database" ' + 'contains unknown field "database_endpoint". ' + 'Refer to the definition to verify valid values.') + err = self.assertRaises( + exception.UnknownFieldError, + lambda: self._single_node_template_content_test(tpl_snippet)) + self.assertEqual(expectedmessage, err.__str__()) + + def test_node_template_requirements_with_wrong_node_keyname(self): + """Node template requirements keyname 'node' given as 'nodes'.""" + tpl_snippet = ''' + node_templates: + mysql_database: + type: tosca.nodes.Database + requirements: + - host: + nodes: mysql_dbms + + ''' + expectedmessage = _('"requirements" of template "mysql_database" ' + 'contains unknown field "nodes". Refer to the ' + 'definition to verify valid values.') + err = self.assertRaises( + exception.UnknownFieldError, + lambda: self._single_node_template_content_test(tpl_snippet)) + self.assertEqual(expectedmessage, err.__str__()) + + def test_node_template_requirements_with_wrong_capability_keyname(self): + """Incorrect node template requirements keyname + + Node template requirements keyname 'capability' given as + 'capabilityy'. + """ + tpl_snippet = ''' + node_templates: + mysql_database: + type: tosca.nodes.Database + requirements: + - host: + node: mysql_dbms + - log_endpoint: + node: logstash + capabilityy: log_endpoint + relationship: + type: tosca.relationships.ConnectsTo + + ''' + expectedmessage = _('"requirements" of template "mysql_database" ' + 'contains unknown field "capabilityy". Refer to ' + 'the definition to verify valid values.') + err = self.assertRaises( + exception.UnknownFieldError, + lambda: self._single_node_template_content_test(tpl_snippet)) + self.assertEqual(expectedmessage, err.__str__()) + + def test_node_template_requirements_with_wrong_relationship_keyname(self): + """Incorrect node template requirements keyname + + Node template requirements keyname 'relationship' given as + 'relationshipppp'. + """ + tpl_snippet = ''' + node_templates: + mysql_database: + type: tosca.nodes.Database + requirements: + - host: + node: mysql_dbms + - log_endpoint: + node: logstash + capability: log_endpoint + relationshipppp: + type: tosca.relationships.ConnectsTo + + ''' + expectedmessage = _('"requirements" of template "mysql_database" ' + 'contains unknown field "relationshipppp". Refer ' + 'to the definition to verify valid values.') + err = self.assertRaises( + exception.UnknownFieldError, + lambda: self._single_node_template_content_test(tpl_snippet)) + self.assertEqual(expectedmessage, err.__str__()) + + def test_node_template_requirements_with_wrong_occurrences_keyname(self): + """Incorrect node template requirements keyname + + Node template requirements keyname 'occurrences' given as + 'occurences'. + """ + tpl_snippet = ''' + node_templates: + mysql_database: + type: tosca.nodes.Database + requirements: + - host: + node: mysql_dbms + - log_endpoint: + node: logstash + capability: log_endpoint + relationship: + type: tosca.relationships.ConnectsTo + occurences: [0, UNBOUNDED] + ''' + expectedmessage = _('"requirements" of template "mysql_database" ' + 'contains unknown field "occurences". Refer to ' + 'the definition to verify valid values.') + err = self.assertRaises( + exception.UnknownFieldError, + lambda: self._single_node_template_content_test(tpl_snippet)) + self.assertEqual(expectedmessage, err.__str__()) + + def test_node_template_requirements_with_multiple_wrong_keynames(self): + """Node templates given with multiple wrong requirements keynames.""" + tpl_snippet = ''' + node_templates: + mysql_database: + type: tosca.nodes.Database + requirements: + - host: + node: mysql_dbms + - log_endpoint: + nod: logstash + capabilit: log_endpoint + relationshipppp: + type: tosca.relationships.ConnectsTo + + ''' + expectedmessage = _('"requirements" of template "mysql_database" ' + 'contains unknown field "nod". Refer to the ' + 'definition to verify valid values.') + err = self.assertRaises( + exception.UnknownFieldError, + lambda: self._single_node_template_content_test(tpl_snippet)) + self.assertEqual(expectedmessage, err.__str__()) + + tpl_snippet = ''' + node_templates: + mysql_database: + type: tosca.nodes.Database + requirements: + - host: + node: mysql_dbms + - log_endpoint: + node: logstash + capabilit: log_endpoint + relationshipppp: + type: tosca.relationships.ConnectsTo + + ''' + expectedmessage = _('"requirements" of template "mysql_database" ' + 'contains unknown field "capabilit". Refer to the ' + 'definition to verify valid values.') + err = self.assertRaises( + exception.UnknownFieldError, + lambda: self._single_node_template_content_test(tpl_snippet)) + self.assertEqual(expectedmessage, err.__str__()) + + def test_node_template_requirements_invalid_occurrences(self): + tpl_snippet = ''' + node_templates: + server: + type: tosca.nodes.Compute + requirements: + - log_endpoint: + capability: log_endpoint + occurrences: [0, -1] + ''' + expectedmessage = _('Value of property "[0, -1]" is invalid.') + err = self.assertRaises( + exception.InvalidPropertyValueError, + lambda: self._single_node_template_content_test(tpl_snippet)) + self.assertEqual(expectedmessage, err.__str__()) + + tpl_snippet = ''' + node_templates: + server: + type: tosca.nodes.Compute + requirements: + - log_endpoint: + capability: log_endpoint + occurrences: [a, w] + ''' + expectedmessage = _('"a" is not an integer.') + err = self.assertRaises( + ValueError, + lambda: self._single_node_template_content_test(tpl_snippet)) + self.assertEqual(expectedmessage, err.__str__()) + + tpl_snippet = ''' + node_templates: + server: + type: tosca.nodes.Compute + requirements: + - log_endpoint: + capability: log_endpoint + occurrences: -1 + ''' + expectedmessage = _('"-1" is not a list.') + err = self.assertRaises( + ValueError, + lambda: self._single_node_template_content_test(tpl_snippet)) + self.assertEqual(expectedmessage, err.__str__()) + + tpl_snippet = ''' + node_templates: + server: + type: tosca.nodes.Compute + requirements: + - log_endpoint: + capability: log_endpoint + occurrences: [5, 1] + ''' + expectedmessage = _('Value of property "[5, 1]" is invalid.') + err = self.assertRaises( + exception.InvalidPropertyValueError, + lambda: self._single_node_template_content_test(tpl_snippet)) + self.assertEqual(expectedmessage, err.__str__()) + + tpl_snippet = ''' + node_templates: + server: + type: tosca.nodes.Compute + requirements: + - log_endpoint: + capability: log_endpoint + occurrences: [0, 0] + ''' + expectedmessage = _('Value of property "[0, 0]" is invalid.') + err = self.assertRaises( + exception.InvalidPropertyValueError, + lambda: self._single_node_template_content_test(tpl_snippet)) + self.assertEqual(expectedmessage, err.__str__()) + + def test_node_template_requirements_valid_occurrences(self): + tpl_snippet = ''' + node_templates: + server: + type: tosca.nodes.Compute + requirements: + - log_endpoint: + capability: log_endpoint + occurrences: [2, 2] + ''' + self._single_node_template_content_test(tpl_snippet) + + def test_node_template_capabilities(self): + tpl_snippet = ''' + node_templates: + mysql_database: + type: tosca.nodes.Database + properties: + db_name: { get_input: db_name } + db_user: { get_input: db_user } + db_password: { get_input: db_pwd } + capabilities: + http_endpoint: + properties: + port: { get_input: db_port } + requirements: + - host: mysql_dbms + interfaces: + Standard: + configure: mysql_database_configure.sh + ''' + expectedmessage = _('"capabilities" of template "mysql_database" ' + 'contains unknown field "http_endpoint". Refer to ' + 'the definition to verify valid values.') + err = self.assertRaises( + exception.UnknownFieldError, + lambda: self._single_node_template_content_test(tpl_snippet)) + self.assertEqual(expectedmessage, err.__str__()) + + def test_node_template_properties(self): + tpl_snippet = ''' + node_templates: + server: + type: tosca.nodes.Compute + properties: + os_image: F18_x86_64 + capabilities: + host: + properties: + disk_size: 10 GB + num_cpus: { get_input: cpus } + mem_size: 4096 MB + os: + properties: + architecture: x86_64 + type: Linux + distribution: Fedora + version: 18.0 + ''' + expectedmessage = _('"properties" of template "server" contains ' + 'unknown field "os_image". Refer to the ' + 'definition to verify valid values.') + err = self.assertRaises( + exception.UnknownFieldError, + lambda: self._single_node_template_content_test(tpl_snippet)) + self.assertEqual(expectedmessage, err.__str__()) + + def test_node_template_interfaces(self): + tpl_snippet = ''' + node_templates: + wordpress: + type: tosca.nodes.WebApplication.WordPress + requirements: + - host: webserver + - database_endpoint: mysql_database + interfaces: + Standards: + create: wordpress_install.sh + configure: + implementation: wordpress_configure.sh + inputs: + wp_db_name: { get_property: [ mysql_database, db_name ] } + wp_db_user: { get_property: [ mysql_database, db_user ] } + wp_db_password: { get_property: [ mysql_database, \ + db_password ] } + wp_db_port: { get_property: [ SELF, \ + database_endpoint, port ] } + ''' + expectedmessage = _('"interfaces" of template "wordpress" contains ' + 'unknown field "Standards". Refer to the ' + 'definition to verify valid values.') + err = self.assertRaises( + exception.UnknownFieldError, + lambda: self._single_node_template_content_test(tpl_snippet)) + self.assertEqual(expectedmessage, err.__str__()) + + tpl_snippet = ''' + node_templates: + wordpress: + type: tosca.nodes.WebApplication.WordPress + requirements: + - host: webserver + - database_endpoint: mysql_database + interfaces: + Standard: + create: wordpress_install.sh + config: + implementation: wordpress_configure.sh + inputs: + wp_db_name: { get_property: [ mysql_database, db_name ] } + wp_db_user: { get_property: [ mysql_database, db_user ] } + wp_db_password: { get_property: [ mysql_database, \ + db_password ] } + wp_db_port: { get_property: [ SELF, \ + database_endpoint, port ] } + ''' + expectedmessage = _('"interfaces" of template "wordpress" contains ' + 'unknown field "config". Refer to the definition ' + 'to verify valid values.') + err = self.assertRaises( + exception.UnknownFieldError, + lambda: self._single_node_template_content_test(tpl_snippet)) + self.assertEqual(expectedmessage, err.__str__()) + + tpl_snippet = ''' + node_templates: + wordpress: + type: tosca.nodes.WebApplication.WordPress + requirements: + - host: webserver + - database_endpoint: mysql_database + interfaces: + Standard: + create: wordpress_install.sh + configure: + implementation: wordpress_configure.sh + input: + wp_db_name: { get_property: [ mysql_database, db_name ] } + wp_db_user: { get_property: [ mysql_database, db_user ] } + wp_db_password: { get_property: [ mysql_database, \ + db_password ] } + wp_db_port: { get_ref_property: [ database_endpoint, \ + database_endpoint, port ] } + ''' + expectedmessage = _('"interfaces" of template "wordpress" contains ' + 'unknown field "input". Refer to the definition ' + 'to verify valid values.') + err = self.assertRaises( + exception.UnknownFieldError, + lambda: self._single_node_template_content_test(tpl_snippet)) + self.assertEqual(expectedmessage, err.__str__()) + + def test_relationship_template_properties(self): + tpl_snippet = ''' + relationship_templates: + storage_attachto: + type: AttachesTo + properties: + device: test_device + ''' + expectedmessage = _('"properties" of template "storage_attachto" is ' + 'missing required field "[\'location\']".') + rel_template = (toscaparser.utils.yamlparser. + simple_parse(tpl_snippet))['relationship_templates'] + name = list(rel_template.keys())[0] + rel_template = RelationshipTemplate(rel_template[name], name) + err = self.assertRaises(exception.MissingRequiredFieldError, + rel_template.validate) + self.assertEqual(expectedmessage, six.text_type(err)) + + def test_invalid_template_version(self): + tosca_tpl = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "data/test_invalid_template_version.yaml") + self.assertRaises(exception.ValidationError, ToscaTemplate, tosca_tpl) + valid_versions = ', '.join(ToscaTemplate.VALID_TEMPLATE_VERSIONS) + exception.ExceptionCollector.assertExceptionMessage( + exception.InvalidTemplateVersion, + (_('The template version "tosca_xyz" is invalid. Valid versions ' + 'are "%s".') % valid_versions)) + + def test_node_template_capabilities_properties(self): + # validating capability property values + tpl_snippet = ''' + node_templates: + server: + type: tosca.nodes.WebServer + capabilities: + data_endpoint: + properties: + initiator: test + ''' + expectedmessage = _('The value "test" of property "initiator" is ' + 'not valid. Expected a value from "[source, ' + 'target, peer]".') + + err = self.assertRaises( + exception.ValidationError, + lambda: self._single_node_template_content_test(tpl_snippet)) + self.assertEqual(expectedmessage, err.__str__()) + + tpl_snippet = ''' + node_templates: + server: + type: tosca.nodes.Compute + capabilities: + host: + properties: + disk_size: 10 GB + num_cpus: { get_input: cpus } + mem_size: 4096 MB + os: + properties: + architecture: x86_64 + type: Linux + distribution: Fedora + version: 18.0 + scalable: + properties: + min_instances: 1 + max_instances: 3 + default_instances: 5 + ''' + expectedmessage = _('"properties" of template "server": ' + '"default_instances" value is not between ' + '"min_instances" and "max_instances".') + err = self.assertRaises( + exception.ValidationError, + lambda: self._single_node_template_content_test(tpl_snippet)) + self.assertEqual(expectedmessage, err.__str__()) + + def test_node_template_objectstorage_without_required_property(self): + tpl_snippet = ''' + node_templates: + server: + type: tosca.nodes.ObjectStorage + properties: + maxsize: 1 GB + ''' + expectedmessage = _('"properties" of template "server" is missing ' + 'required field "[\'name\']".') + err = self.assertRaises( + exception.MissingRequiredFieldError, + lambda: self._single_node_template_content_test(tpl_snippet)) + self.assertEqual(expectedmessage, err.__str__()) + + def test_node_template_objectstorage_with_invalid_scalar_unit(self): + tpl_snippet = ''' + node_templates: + server: + type: tosca.nodes.ObjectStorage + properties: + name: test + maxsize: -1 + ''' + expectedmessage = _('"-1" is not a valid scalar-unit.') + err = self.assertRaises( + ValueError, + lambda: self._single_node_template_content_test(tpl_snippet)) + self.assertEqual(expectedmessage, err.__str__()) + + def test_node_template_objectstorage_with_invalid_scalar_type(self): + tpl_snippet = ''' + node_templates: + server: + type: tosca.nodes.ObjectStorage + properties: + name: test + maxsize: 1 XB + ''' + expectedmessage = _('"1 XB" is not a valid scalar-unit.') + err = self.assertRaises( + ValueError, + lambda: self._single_node_template_content_test(tpl_snippet)) + self.assertEqual(expectedmessage, err.__str__()) + + def test_special_keywords(self): + """Test special keywords + + Test that special keywords, e.g. metadata, which are not part + of specification do not throw any validation error. + """ + tpl_snippet_metadata_map = ''' + node_templates: + server: + type: tosca.nodes.Compute + metadata: + name: server A + role: master + ''' + self._single_node_template_content_test(tpl_snippet_metadata_map) + + tpl_snippet_metadata_inline = ''' + node_templates: + server: + type: tosca.nodes.Compute + metadata: none + ''' + self._single_node_template_content_test(tpl_snippet_metadata_inline) + + def test_policy_valid_keynames(self): + tpl_snippet = ''' + policies: + - servers_placement: + type: tosca.policies.Placement + description: Apply placement policy to servers + metadata: { user1: 1001, user2: 1002 } + targets: [ serv1, serv2 ] + ''' + policies = (toscaparser.utils.yamlparser. + simple_parse(tpl_snippet))['policies'][0] + name = list(policies.keys())[0] + Policy(name, policies[name], None, None) + + def test_policy_invalid_keyname(self): + tpl_snippet = ''' + policies: + - servers_placement: + type: tosca.policies.Placement + testkey: testvalue + ''' + policies = (toscaparser.utils.yamlparser. + simple_parse(tpl_snippet))['policies'][0] + name = list(policies.keys())[0] + + expectedmessage = _('Policy "servers_placement" contains ' + 'unknown field "testkey". Refer to the ' + 'definition to verify valid values.') + err = self.assertRaises( + exception.UnknownFieldError, + lambda: Policy(name, policies[name], None, None)) + self.assertEqual(expectedmessage, err.__str__()) + + def test_policy_trigger_valid_keyname(self): + tpl_snippet = ''' + triggers: + - resize_compute: + description: trigger + event_type: tosca.events.resource.utilization + schedule: + start_time: "2015-05-07T07:00:00Z" + end_time: "2015-06-07T07:00:00Z" + target_filter: + node: master-container + requirement: host + capability: Container + condition: + constraint: utilization greater_than 50% + period: 60 + evaluations: 1 + method : average + action: + resize: # Operation name + inputs: + strategy: LEAST_USED + implementation: Senlin.webhook() + ''' + triggers = (toscaparser.utils.yamlparser. + simple_parse(tpl_snippet))['triggers'][0] + name = list(triggers.keys())[0] + Triggers(name, triggers[name]) + + def test_policy_trigger_invalid_keyname(self): + tpl_snippet = ''' + triggers: + - resize_compute: + description: trigger + event_type: tosca.events.resource.utilization + schedule: + start_time: "2015-05-07T07:00:00Z" + end_time: "2015-06-07T07:00:00Z" + target_filter1: + node: master-container + requirement: host + capability: Container + condition: + constraint: utilization greater_than 50% + period1: 60 + evaluations: 1 + method: average + action: + resize: # Operation name + inputs: + strategy: LEAST_USED + implementation: Senlin.webhook() + ''' + triggers = (toscaparser.utils.yamlparser. + simple_parse(tpl_snippet))['triggers'][0] + name = list(triggers.keys())[0] + expectedmessage = _( + 'Triggers "resize_compute" contains unknown field ' + '"target_filter1". Refer to the definition ' + 'to verify valid values.') + err = self.assertRaises( + exception.UnknownFieldError, + lambda: Triggers(name, triggers[name])) + self.assertEqual(expectedmessage, err.__str__()) + + def test_policy_missing_required_keyname(self): + tpl_snippet = ''' + policies: + - servers_placement: + description: test description + ''' + policies = (toscaparser.utils.yamlparser. + simple_parse(tpl_snippet))['policies'][0] + name = list(policies.keys())[0] + + expectedmessage = _('Template "servers_placement" is missing ' + 'required field "type".') + err = self.assertRaises( + exception.MissingRequiredFieldError, + lambda: Policy(name, policies[name], None, None)) + self.assertEqual(expectedmessage, err.__str__()) diff --git a/tosca2heat/tosca-parser/toscaparser/tests/test_utils.py b/tosca2heat/tosca-parser/toscaparser/tests/test_utils.py new file mode 100644 index 0000000..215fa0a --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/test_utils.py @@ -0,0 +1,47 @@ +# Licensed 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 toscaparser.tests.base import TestCase +import toscaparser.utils.urlutils +import toscaparser.utils.yamlparser + +YAML_LOADER = toscaparser.utils.yamlparser.load_yaml + + +class UrlUtilsTest(TestCase): + + url_utils = toscaparser.utils.urlutils.UrlUtils + + def test_urlutils_validate_url(self): + self.assertTrue(self.url_utils.validate_url("http://www.github.com/")) + self.assertTrue( + self.url_utils.validate_url("https://github.com:81/a/2/a.b")) + self.assertTrue(self.url_utils.validate_url("ftp://github.com")) + self.assertFalse(self.url_utils.validate_url("github.com")) + self.assertFalse(self.url_utils.validate_url("123")) + self.assertFalse(self.url_utils.validate_url("a/b/c")) + + def test_urlutils_join_url(self): + self.assertEqual( + self.url_utils.join_url("http://github.com/proj1", "proj2"), + "http://github.com/proj2") + self.assertEqual( + self.url_utils.join_url("http://github.com/proj1/scripts/a.js", + "b.js"), + "http://github.com/proj1/scripts/b.js") + self.assertEqual( + self.url_utils.join_url("http://github.com/proj1/scripts", "b.js"), + "http://github.com/proj1/b.js") + self.assertEqual( + self.url_utils.join_url("http://github.com/proj1/scripts", + "scripts/b.js"), + "http://github.com/proj1/scripts/b.js") diff --git a/tosca2heat/tosca-parser/toscaparser/tests/test_validate_tosca_version.py b/tosca2heat/tosca-parser/toscaparser/tests/test_validate_tosca_version.py new file mode 100644 index 0000000..e9a8ac2 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/test_validate_tosca_version.py @@ -0,0 +1,132 @@ +# Licensed 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 toscaparser.common.exception import ( + InvalidTOSCAVersionPropertyException) +from toscaparser.tests.base import TestCase +from toscaparser.utils.gettextutils import _ +from toscaparser.utils.validateutils import TOSCAVersionProperty + + +class TOSCAVersionPropertyTest(TestCase): + + def test_tosca_version_property(self): + version = '18.0.3.beta-1' + expected_output = '18.0.3.beta-1' + output = TOSCAVersionProperty(version).get_version() + self.assertEqual(output, expected_output) + + version = 18 + expected_output = '18.0' + output = TOSCAVersionProperty(version).get_version() + self.assertEqual(output, expected_output) + + version = 18.0 + expected_output = '18.0' + output = TOSCAVersionProperty(version).get_version() + self.assertEqual(output, expected_output) + + version = '18.0.3' + expected_output = '18.0.3' + output = TOSCAVersionProperty(version).get_version() + self.assertEqual(output, expected_output) + + version = 0 + expected_output = None + output = TOSCAVersionProperty(version).get_version() + self.assertEqual(output, expected_output) + + version = 00 + expected_output = None + output = TOSCAVersionProperty(version).get_version() + self.assertEqual(output, expected_output) + + version = 0.0 + expected_output = None + output = TOSCAVersionProperty(version).get_version() + self.assertEqual(output, expected_output) + + version = 00.00 + expected_output = None + output = TOSCAVersionProperty(version).get_version() + self.assertEqual(output, expected_output) + + version = '0.0.0' + expected_output = None + output = TOSCAVersionProperty(version).get_version() + self.assertEqual(output, expected_output) + + def test_tosca_version_property_invalid_major_version(self): + + version = 'x' + exp_msg = _('Value of TOSCA version property "x" is invalid.') + err = self.assertRaises(InvalidTOSCAVersionPropertyException, + TOSCAVersionProperty, version) + self.assertEqual(exp_msg, err.__str__()) + + def test_tosca_version_property_invalid_minor_version(self): + + version = '18.x' + exp_msg = _('Value of TOSCA version property "18.x" is invalid.') + err = self.assertRaises(InvalidTOSCAVersionPropertyException, + TOSCAVersionProperty, version) + self.assertEqual(exp_msg, err.__str__()) + + version = '18.x.y' + exp_msg = _('Value of TOSCA version property "18.x.y" is invalid.') + err = self.assertRaises(InvalidTOSCAVersionPropertyException, + TOSCAVersionProperty, version) + self.assertEqual(exp_msg, err.__str__()) + + version = '18-2' + exp_msg = _('Value of TOSCA version property "18-2" is invalid.') + err = self.assertRaises(InvalidTOSCAVersionPropertyException, + TOSCAVersionProperty, version) + self.assertEqual(exp_msg, err.__str__()) + + def test_tosca_version_property_invalid_fix_version(self): + + version = '18.0.a' + exp_msg = _('Value of TOSCA version property "18.0.a" is invalid.') + err = self.assertRaises(InvalidTOSCAVersionPropertyException, + TOSCAVersionProperty, version) + self.assertEqual(exp_msg, err.__str__()) + + def test_tosca_version_property_invalid_qualifier(self): + + version = '18.0.1-xyz' + exp_msg = _('Value of TOSCA version property "18.0.1-xyz" is invalid.') + err = self.assertRaises(InvalidTOSCAVersionPropertyException, + TOSCAVersionProperty, version) + self.assertEqual(exp_msg, err.__str__()) + + version = '0.0.0.abc' + exp_msg = _('Value of TOSCA version property "0.0.0.abc" is invalid.') + err = self.assertRaises(InvalidTOSCAVersionPropertyException, + TOSCAVersionProperty, version) + self.assertEqual(exp_msg, err.__str__()) + + def test_tosca_version_property_invalid_build_version(self): + + version = '18.0.1.abc-x' + exp_msg = _('Value of TOSCA version property ' + '"18.0.1.abc-x" is invalid.') + err = self.assertRaises(InvalidTOSCAVersionPropertyException, + TOSCAVersionProperty, version) + self.assertEqual(exp_msg, err.__str__()) + + version = '0.0.0.abc-x' + exp_msg = _('Value of TOSCA version property "0.0.0.abc-x" is ' + 'invalid.') + err = self.assertRaises(InvalidTOSCAVersionPropertyException, + TOSCAVersionProperty, version) + self.assertEqual(exp_msg, err.__str__()) diff --git a/tosca2heat/tosca-parser/toscaparser/topology_template.py b/tosca2heat/tosca-parser/toscaparser/topology_template.py new file mode 100644 index 0000000..f8f8875 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/topology_template.py @@ -0,0 +1,276 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +import logging + +from toscaparser.common import exception +from toscaparser.dataentity import DataEntity +from toscaparser import functions +from toscaparser.groups import Group +from toscaparser.nodetemplate import NodeTemplate +from toscaparser.parameters import Input +from toscaparser.parameters import Output +from toscaparser.policy import Policy +from toscaparser.relationship_template import RelationshipTemplate +from toscaparser.tpl_relationship_graph import ToscaGraph +from toscaparser.utils.gettextutils import _ + + +# Topology template key names +SECTIONS = (DESCRIPTION, INPUTS, NODE_TEMPLATES, + RELATIONSHIP_TEMPLATES, OUTPUTS, GROUPS, + SUBSTITUION_MAPPINGS, POLICIES) = \ + ('description', 'inputs', 'node_templates', + 'relationship_templates', 'outputs', 'groups', + 'substitution_mappings', 'policies') + +log = logging.getLogger("tosca.model") + + +class TopologyTemplate(object): + + '''Load the template data.''' + def __init__(self, template, custom_defs, + rel_types=None, parsed_params=None): + self.tpl = template + if self.tpl: + self.custom_defs = custom_defs + self.rel_types = rel_types + self.parsed_params = parsed_params + self._validate_field() + self.description = self._tpl_description() + self.inputs = self._inputs() + self.relationship_templates = self._relationship_templates() + self.nodetemplates = self._nodetemplates() + self.outputs = self._outputs() + if hasattr(self, 'nodetemplates'): + self.graph = ToscaGraph(self.nodetemplates) + self.groups = self._groups() + self.policies = self._policies() + self._process_intrinsic_functions() + + def _inputs(self): + inputs = [] + for name, attrs in self._tpl_inputs().items(): + input = Input(name, attrs) + if self.parsed_params and name in self.parsed_params: + input.validate(self.parsed_params[name]) + inputs.append(input) + return inputs + + def _nodetemplates(self): + nodetemplates = [] + tpls = self._tpl_nodetemplates() + if tpls: + for name in tpls: + tpl = NodeTemplate(name, tpls, self.custom_defs, + self.relationship_templates, + self.rel_types) + if (tpl.type_definition and + (tpl.type in tpl.type_definition.TOSCA_DEF or + (tpl.type not in tpl.type_definition.TOSCA_DEF and + bool(tpl.custom_def)))): + tpl.validate(self) + nodetemplates.append(tpl) + return nodetemplates + + def _relationship_templates(self): + rel_templates = [] + tpls = self._tpl_relationship_templates() + for name in tpls: + tpl = RelationshipTemplate(tpls[name], name, self.custom_defs) + rel_templates.append(tpl) + return rel_templates + + def _outputs(self): + outputs = [] + for name, attrs in self._tpl_outputs().items(): + output = Output(name, attrs) + output.validate() + outputs.append(output) + return outputs + + def _substitution_mappings(self): + pass + + def _policies(self): + policies = [] + for policy in self._tpl_policies(): + for policy_name, policy_tpl in policy.items(): + target_list = policy_tpl.get('targets') + if target_list and len(target_list) >= 1: + target_objects = [] + targets_type = "groups" + target_objects = self._get_policy_groups(target_list) + if not target_objects: + targets_type = "node_templates" + target_objects = self._get_group_members(target_list) + policyObj = Policy(policy_name, policy_tpl, + target_objects, targets_type, + self.custom_defs) + policies.append(policyObj) + return policies + + def _groups(self): + groups = [] + member_nodes = None + for group_name, group_tpl in self._tpl_groups().items(): + member_names = group_tpl.get('members') + if member_names is not None: + DataEntity.validate_datatype('list', member_names) + if len(member_names) < 1 or \ + len(member_names) != len(set(member_names)): + exception.ExceptionCollector.appendException( + exception.InvalidGroupTargetException( + message=_('Member nodes "%s" should be >= 1 ' + 'and not repeated') % member_names)) + else: + member_nodes = self._get_group_members(member_names) + group = Group(group_name, group_tpl, + member_nodes, + self.custom_defs) + groups.append(group) + return groups + + def _get_group_members(self, member_names): + member_nodes = [] + self._validate_group_members(member_names) + for member in member_names: + for node in self.nodetemplates: + if node.name == member: + member_nodes.append(node) + return member_nodes + + def _get_policy_groups(self, member_names): + member_groups = [] + for member in member_names: + for group in self.groups: + if group.name == member: + member_groups.append(group) + return member_groups + + def _validate_group_members(self, members): + node_names = [] + for node in self.nodetemplates: + node_names.append(node.name) + for member in members: + if member not in node_names: + exception.ExceptionCollector.appendException( + exception.InvalidGroupTargetException( + message=_('Target member "%s" is not found in ' + 'node_templates') % member)) + + # topology template can act like node template + # it is exposed by substitution_mappings. + def nodetype(self): + pass + + def capabilities(self): + pass + + def requirements(self): + pass + + def _tpl_description(self): + description = self.tpl.get(DESCRIPTION) + if description: + return description.rstrip() + + def _tpl_inputs(self): + return self.tpl.get(INPUTS) or {} + + def _tpl_nodetemplates(self): + return self.tpl.get(NODE_TEMPLATES) + + def _tpl_relationship_templates(self): + return self.tpl.get(RELATIONSHIP_TEMPLATES) or {} + + def _tpl_outputs(self): + return self.tpl.get(OUTPUTS) or {} + + def _tpl_substitution_mappings(self): + return self.tpl.get(SUBSTITUION_MAPPINGS) or {} + + def _tpl_groups(self): + return self.tpl.get(GROUPS) or {} + + def _tpl_policies(self): + return self.tpl.get(POLICIES) or {} + + def _validate_field(self): + for name in self.tpl: + if name not in SECTIONS: + exception.ExceptionCollector.appendException( + exception.UnknownFieldError(what='Template', field=name)) + + def _process_intrinsic_functions(self): + """Process intrinsic functions + + Current implementation processes functions within node template + properties, requirements, interfaces inputs and template outputs. + """ + if hasattr(self, 'nodetemplates'): + for node_template in self.nodetemplates: + for prop in node_template.get_properties_objects(): + prop.value = functions.get_function(self, + node_template, + prop.value) + for interface in node_template.interfaces: + if interface.inputs: + for name, value in interface.inputs.items(): + interface.inputs[name] = functions.get_function( + self, + node_template, + value) + if node_template.requirements: + for req in node_template.requirements: + rel = req + for req_name, req_item in req.items(): + if isinstance(req_item, dict): + rel = req_item.get('relationship') + break + if rel and 'properties' in rel: + for key, value in rel['properties'].items(): + rel['properties'][key] = \ + functions.get_function(self, + req, + value) + if node_template.get_capabilities_objects(): + for cap in node_template.get_capabilities_objects(): + if cap.get_properties_objects(): + for prop in cap.get_properties_objects(): + propvalue = functions.get_function( + self, + node_template, + prop.value) + if isinstance(propvalue, functions.GetInput): + propvalue = propvalue.result() + for p, v in cap._properties.items(): + if p == prop.name: + cap._properties[p] = propvalue + for rel, node in node_template.relationships.items(): + rel_tpls = node.relationship_tpl + if rel_tpls: + for rel_tpl in rel_tpls: + for interface in rel_tpl.interfaces: + if interface.inputs: + for name, value in \ + interface.inputs.items(): + interface.inputs[name] = \ + functions.get_function(self, + rel_tpl, + value) + for output in self.outputs: + func = functions.get_function(self, self.outputs, output.value) + if isinstance(func, functions.GetAttribute): + output.attrs[output.VALUE] = func diff --git a/tosca2heat/tosca-parser/toscaparser/tosca_template.py b/tosca2heat/tosca-parser/toscaparser/tosca_template.py new file mode 100644 index 0000000..ba056da --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tosca_template.py @@ -0,0 +1,255 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +import logging +import os + +from toscaparser.common.exception import ExceptionCollector +from toscaparser.common.exception import InvalidTemplateVersion +from toscaparser.common.exception import MissingRequiredFieldError +from toscaparser.common.exception import UnknownFieldError +from toscaparser.common.exception import ValidationError +from toscaparser.elements.entity_type import update_definitions +from toscaparser.extensions.exttools import ExtTools +import toscaparser.imports +from toscaparser.prereq.csar import CSAR +from toscaparser.topology_template import TopologyTemplate +from toscaparser.tpl_relationship_graph import ToscaGraph +from toscaparser.utils.gettextutils import _ +import toscaparser.utils.yamlparser + + +# TOSCA template key names +SECTIONS = (DEFINITION_VERSION, DEFAULT_NAMESPACE, TEMPLATE_NAME, + TOPOLOGY_TEMPLATE, TEMPLATE_AUTHOR, TEMPLATE_VERSION, + DESCRIPTION, IMPORTS, DSL_DEFINITIONS, NODE_TYPES, + RELATIONSHIP_TYPES, RELATIONSHIP_TEMPLATES, + CAPABILITY_TYPES, ARTIFACT_TYPES, DATA_TYPES, + POLICY_TYPES, GROUP_TYPES, REPOSITORIES) = \ + ('tosca_definitions_version', 'tosca_default_namespace', + 'template_name', 'topology_template', 'template_author', + 'template_version', 'description', 'imports', 'dsl_definitions', + 'node_types', 'relationship_types', 'relationship_templates', + 'capability_types', 'artifact_types', 'data_types', + 'policy_types', 'group_types', 'repositories') +# Sections that are specific to individual template definitions +SPECIAL_SECTIONS = (METADATA) = ('metadata') + +log = logging.getLogger("tosca.model") + +YAML_LOADER = toscaparser.utils.yamlparser.load_yaml + + +class ToscaTemplate(object): + exttools = ExtTools() + + VALID_TEMPLATE_VERSIONS = ['tosca_simple_yaml_1_0'] + + VALID_TEMPLATE_VERSIONS.extend(exttools.get_versions()) + + ADDITIONAL_SECTIONS = {'tosca_simple_yaml_1_0': SPECIAL_SECTIONS} + + ADDITIONAL_SECTIONS.update(exttools.get_sections()) + + '''Load the template data.''' + def __init__(self, path=None, parsed_params=None, a_file=True, + yaml_dict_tpl=None): + ExceptionCollector.start() + self.a_file = a_file + self.input_path = None + self.path = None + self.tpl = None + if path: + self.input_path = path + self.path = self._get_path(path) + if self.path: + self.tpl = YAML_LOADER(self.path, self.a_file) + if yaml_dict_tpl: + msg = (_('Both path and yaml_dict_tpl arguments were ' + 'provided. Using path and ignoring yaml_dict_tpl.')) + log.info(msg) + print(msg) + else: + if yaml_dict_tpl: + self.tpl = yaml_dict_tpl + else: + ExceptionCollector.appendException( + ValueError(_('No path or yaml_dict_tpl was provided. ' + 'There is nothing to parse.'))) + + if self.tpl: + self.parsed_params = parsed_params + self._validate_field() + self.version = self._tpl_version() + self.relationship_types = self._tpl_relationship_types() + self.description = self._tpl_description() + self.topology_template = self._topology_template() + if self.topology_template.tpl: + self.inputs = self._inputs() + self.relationship_templates = self._relationship_templates() + self.nodetemplates = self._nodetemplates() + self.outputs = self._outputs() + self.graph = ToscaGraph(self.nodetemplates) + + ExceptionCollector.stop() + self.verify_template() + + def _topology_template(self): + return TopologyTemplate(self._tpl_topology_template(), + self._get_all_custom_defs(), + self.relationship_types, + self.parsed_params) + + def _inputs(self): + return self.topology_template.inputs + + def _nodetemplates(self): + return self.topology_template.nodetemplates + + def _relationship_templates(self): + return self.topology_template.relationship_templates + + def _outputs(self): + return self.topology_template.outputs + + def _tpl_version(self): + return self.tpl.get(DEFINITION_VERSION) + + def _tpl_description(self): + desc = self.tpl.get(DESCRIPTION) + if desc: + return desc.rstrip() + + def _tpl_imports(self): + return self.tpl.get(IMPORTS) + + def _tpl_relationship_types(self): + return self._get_custom_types(RELATIONSHIP_TYPES) + + def _tpl_relationship_templates(self): + topology_template = self._tpl_topology_template() + return topology_template.get(RELATIONSHIP_TEMPLATES) + + def _tpl_topology_template(self): + return self.tpl.get(TOPOLOGY_TEMPLATE) + + def _get_all_custom_defs(self, imports=None): + types = [IMPORTS, NODE_TYPES, CAPABILITY_TYPES, RELATIONSHIP_TYPES, + DATA_TYPES, POLICY_TYPES, GROUP_TYPES] + custom_defs_final = {} + custom_defs = self._get_custom_types(types, imports) + if custom_defs: + custom_defs_final.update(custom_defs) + if custom_defs.get(IMPORTS): + import_defs = self._get_all_custom_defs( + custom_defs.get(IMPORTS)) + custom_defs_final.update(import_defs) + + # As imports are not custom_types, removing from the dict + custom_defs_final.pop(IMPORTS, None) + return custom_defs_final + + def _get_custom_types(self, type_definitions, imports=None): + """Handle custom types defined in imported template files + + This method loads the custom type definitions referenced in "imports" + section of the TOSCA YAML template. + """ + + custom_defs = {} + type_defs = [] + if not isinstance(type_definitions, list): + type_defs.append(type_definitions) + else: + type_defs = type_definitions + + if not imports: + imports = self._tpl_imports() + + if imports: + custom_defs = toscaparser.imports.\ + ImportsLoader(imports, self.path, + type_defs, self.tpl).get_custom_defs() + if not custom_defs: + return + + # Handle custom types defined in current template file + for type_def in type_defs: + if type_def != IMPORTS: + inner_custom_types = self.tpl.get(type_def) or {} + if inner_custom_types: + custom_defs.update(inner_custom_types) + return custom_defs + + def _validate_field(self): + version = self._tpl_version() + if not version: + ExceptionCollector.appendException( + MissingRequiredFieldError(what='Template', + required=DEFINITION_VERSION)) + else: + self._validate_version(version) + self.version = version + + for name in self.tpl: + if (name not in SECTIONS and + name not in self.ADDITIONAL_SECTIONS.get(version, ())): + ExceptionCollector.appendException( + UnknownFieldError(what='Template', field=name)) + + def _validate_version(self, version): + if version not in self.VALID_TEMPLATE_VERSIONS: + ExceptionCollector.appendException( + InvalidTemplateVersion( + what=version, + valid_versions=', '. join(self.VALID_TEMPLATE_VERSIONS))) + else: + if version != 'tosca_simple_yaml_1_0': + update_definitions(version) + + def _get_path(self, path): + if path.lower().endswith('.yaml'): + return path + elif path.lower().endswith(('.zip', '.csar')): + # a CSAR archive + csar = CSAR(path, self.a_file) + if csar.validate(): + csar.decompress() + self.a_file = True # the file has been decompressed locally + return os.path.join(csar.temp_dir, csar.get_main_template()) + else: + ExceptionCollector.appendException( + ValueError(_('"%(path)s" is not a valid file.') + % {'path': path})) + + def verify_template(self): + if ExceptionCollector.exceptionsCaught(): + if self.input_path: + raise ValidationError( + message=(_('\nThe input "%(path)s" failed validation with ' + 'the following error(s): \n\n\t') + % {'path': self.input_path}) + + '\n\t'.join(ExceptionCollector.getExceptionsReport())) + else: + raise ValidationError( + message=_('\nThe pre-parsed input failed validation with ' + 'the following error(s): \n\n\t') + + '\n\t'.join(ExceptionCollector.getExceptionsReport())) + else: + if self.input_path: + msg = (_('The input "%(path)s" successfully passed ' + 'validation.') % {'path': self.input_path}) + else: + msg = _('The pre-parsed input successfully passed validation.') + + log.info(msg) diff --git a/tosca2heat/tosca-parser/toscaparser/tpl_relationship_graph.py b/tosca2heat/tosca-parser/toscaparser/tpl_relationship_graph.py new file mode 100644 index 0000000..1a5ea7b --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tpl_relationship_graph.py @@ -0,0 +1,46 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +class ToscaGraph(object): + '''Graph of Tosca Node Templates.''' + def __init__(self, nodetemplates): + self.nodetemplates = nodetemplates + self.vertices = {} + self._create() + + def _create_vertex(self, node): + if node not in self.vertices: + self.vertices[node.name] = node + + def _create_edge(self, node1, node2, relationship): + if node1 not in self.vertices: + self._create_vertex(node1) + self.vertices[node1.name]._add_next(node2, + relationship) + + def vertex(self, node): + if node in self.vertices: + return self.vertices[node] + + def __iter__(self): + return iter(self.vertices.values()) + + def _create(self): + for node in self.nodetemplates: + relation = node.relationships + if relation: + for rel, nodetpls in relation.items(): + for tpl in self.nodetemplates: + if tpl.name == nodetpls.name: + self._create_edge(node, tpl, rel) + self._create_vertex(node) diff --git a/tosca2heat/tosca-parser/toscaparser/triggers.py b/tosca2heat/tosca-parser/toscaparser/triggers.py new file mode 100644 index 0000000..9edeef4 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/triggers.py @@ -0,0 +1,68 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +import logging + +from toscaparser.common.exception import ExceptionCollector +from toscaparser.common.exception import UnknownFieldError +from toscaparser.entity_template import EntityTemplate + +SECTIONS = (DESCRIPTION, EVENT, SCHEDULE, TARGET_FILTER, CONDITION, ACTION) = \ + ('description', 'event_type', 'schedule', + 'target_filter', 'condition', 'action') +CONDITION_KEYNAMES = (CONTRAINT, PERIOD, EVALUATIONS, METHOD) = \ + ('constraint', 'period', 'evaluations', 'method') +log = logging.getLogger('tosca') + + +class Triggers(EntityTemplate): + + '''Triggers defined in policies of topology template''' + + def __init__(self, name, trigger_tpl): + self.name = name + self.trigger_tpl = trigger_tpl + self._validate_keys() + self._validate_condition() + + def get_description(self): + return self.trigger_tpl['description'] + + def get_event(self): + return self.trigger_tpl['event_type'] + + def get_schedule(self): + return self.trigger_tpl['schedule'] + + def get_target_filter(self): + return self.trigger_tpl['target_filter'] + + def get_condition(self): + return self.trigger_tpl['condition'] + + def get_action(self): + return self.trigger_tpl['action'] + + def _validate_keys(self): + for key in self.trigger_tpl.keys(): + if key not in SECTIONS: + ExceptionCollector.appendException( + UnknownFieldError(what='Triggers "%s"' % self.name, + field=key)) + + def _validate_condition(self): + for key in self.get_condition(): + if key not in CONDITION_KEYNAMES: + ExceptionCollector.appendException( + UnknownFieldError(what='Triggers "%s"' % self.name, + field=key)) diff --git a/tosca2heat/tosca-parser/toscaparser/utils/__init__.py b/tosca2heat/tosca-parser/toscaparser/utils/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/utils/__init__.py diff --git a/tosca2heat/tosca-parser/toscaparser/utils/gettextutils.py b/tosca2heat/tosca-parser/toscaparser/utils/gettextutils.py new file mode 100644 index 0000000..f5562e2 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/utils/gettextutils.py @@ -0,0 +1,22 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import gettext +import os + +_localedir = os.environ.get('tosca-parser'.upper() + '_LOCALEDIR') +_t = gettext.translation('tosca-parser', localedir=_localedir, + fallback=True) + + +def _(msg): + return _t.gettext(msg) diff --git a/tosca2heat/tosca-parser/toscaparser/utils/urlutils.py b/tosca2heat/tosca-parser/toscaparser/utils/urlutils.py new file mode 100644 index 0000000..8022158 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/utils/urlutils.py @@ -0,0 +1,61 @@ +# Licensed 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 six.moves.urllib.parse import urljoin +from six.moves.urllib.parse import urlparse +from toscaparser.common.exception import ExceptionCollector +from toscaparser.utils.gettextutils import _ + +try: + # Python 3.x + import urllib.request as urllib +except ImportError: + # Python 2.x + import urllib + + +class UrlUtils(object): + + @staticmethod + def validate_url(path): + """Validates whether the given path is a URL or not. + + If the given path includes a scheme (http, https, ftp, ...) and a net + location (a domain name such as www.github.com) it is validated as a + URL. + """ + parsed = urlparse(path) + return bool(parsed.scheme) and bool(parsed.netloc) + + @staticmethod + def join_url(url, relative_path): + """Builds a new URL from the given URL and the relative path. + + Example: + url: http://www.githib.com/openstack/heat + relative_path: heat-translator + - joined: http://www.githib.com/openstack/heat-translator + """ + if not UrlUtils.validate_url(url): + ExceptionCollector.appendException( + ValueError(_('"%s" is not a valid URL.') % url)) + return urljoin(url, relative_path) + + @staticmethod + def url_accessible(url): + """Validates whether the given URL is accessible. + + Returns true if the get call returns a 200 response code. + Otherwise, returns false. + """ + return urllib.urlopen(url).getcode() == 200 diff --git a/tosca2heat/tosca-parser/toscaparser/utils/validateutils.py b/tosca2heat/tosca-parser/toscaparser/utils/validateutils.py new file mode 100644 index 0000000..f9b9fc5 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/utils/validateutils.py @@ -0,0 +1,189 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import collections +import dateutil.parser +import logging +import numbers +import re +import six + +from toscaparser.common.exception import ExceptionCollector +from toscaparser.common.exception import InvalidTOSCAVersionPropertyException +from toscaparser.utils.gettextutils import _ +log = logging.getLogger('tosca') + + +def str_to_num(value): + '''Convert a string representation of a number into a numeric type.''' + if isinstance(value, numbers.Number): + return value + try: + return int(value) + except ValueError: + return float(value) + + +def validate_number(value): + return str_to_num(value) + + +def validate_integer(value): + if not isinstance(value, int): + try: + value = int(value) + except Exception: + ExceptionCollector.appendException( + ValueError(_('"%s" is not an integer.') % value)) + return value + + +def validate_float(value): + if not isinstance(value, float): + ExceptionCollector.appendException( + ValueError(_('"%s" is not a float.') % value)) + return validate_number(value) + + +def validate_string(value): + if not isinstance(value, six.string_types): + ExceptionCollector.appendException( + ValueError(_('"%s" is not a string.') % value)) + return value + + +def validate_list(value): + if not isinstance(value, list): + ExceptionCollector.appendException( + ValueError(_('"%s" is not a list.') % value)) + return value + + +def validate_range(value): + validate_list(value) + if isinstance(value, list): + if len(value) != 2 or not (value[0] <= value[1]): + ExceptionCollector.appendException( + ValueError(_('"%s" is not a valid range.') % value)) + validate_integer(value[0]) + if not value[1] == "UNBOUNDED": + validate_integer(value[1]) + return value + + +def validate_map(value): + if not isinstance(value, collections.Mapping): + ExceptionCollector.appendException( + ValueError(_('"%s" is not a map.') % value)) + return value + + +def validate_boolean(value): + if isinstance(value, bool): + return value + + if isinstance(value, str): + normalised = value.lower() + if normalised in ['true', 'false']: + return normalised == 'true' + + ExceptionCollector.appendException( + ValueError(_('"%s" is not a boolean.') % value)) + + +def validate_timestamp(value): + try: + # Note: we must return our own exception message + # as dateutil's parser returns different types / values on + # different systems. OSX, for example, returns a tuple + # containing a different error message than Linux + dateutil.parser.parse(value) + except Exception as e: + original_err_msg = str(e) + log.error(original_err_msg) + ExceptionCollector.appendException( + ValueError(_('"%(val)s" is not a valid timestamp. "%(msg)s"') % + {'val': value, 'msg': original_err_msg})) + return + + +class TOSCAVersionProperty(object): + + VERSION_RE = re.compile('^(?P<major_version>([0-9][0-9]*))' + '(\.(?P<minor_version>([0-9][0-9]*)))?' + '(\.(?P<fix_version>([0-9][0-9]*)))?' + '(\.(?P<qualifier>([0-9A-Za-z]+)))?' + '(\-(?P<build_version>[0-9])*)?$') + + def __init__(self, version): + self.version = str(version) + match = self.VERSION_RE.match(self.version) + if not match: + ExceptionCollector.appendException( + InvalidTOSCAVersionPropertyException(what=(self.version))) + return + ver = match.groupdict() + if self.version in ['0', '0.0', '0.0.0']: + log.warning(_('Version assumed as not provided')) + self.version = None + self.minor_version = ver['minor_version'] + self.major_version = ver['major_version'] + self.fix_version = ver['fix_version'] + self.qualifier = self._validate_qualifier(ver['qualifier']) + self.build_version = self._validate_build(ver['build_version']) + self._validate_major_version(self.major_version) + + def _validate_major_version(self, value): + """Validate major version + + Checks if only major version is provided and assumes + minor version as 0. + Eg: If version = 18, then it returns version = '18.0' + """ + + if self.minor_version is None and self.build_version is None and \ + value != '0': + log.warning(_('Minor version assumed "0".')) + self.version = '.'.join([value, '0']) + return value + + def _validate_qualifier(self, value): + """Validate qualifier + + TOSCA version is invalid if a qualifier is present without the + fix version or with all of major, minor and fix version 0s. + + For example, the following versions are invalid + 18.0.abc + 0.0.0.abc + """ + if (self.fix_version is None and value) or \ + (self.minor_version == self.major_version == + self.fix_version == '0' and value): + ExceptionCollector.appendException( + InvalidTOSCAVersionPropertyException(what=(self.version))) + return value + + def _validate_build(self, value): + """Validate build version + + TOSCA version is invalid if build version is present without the + qualifier. + Eg: version = 18.0.0-1 is invalid. + """ + if not self.qualifier and value: + ExceptionCollector.appendException( + InvalidTOSCAVersionPropertyException(what=(self.version))) + return value + + def get_version(self): + return self.version diff --git a/tosca2heat/tosca-parser/toscaparser/utils/yamlparser.py b/tosca2heat/tosca-parser/toscaparser/utils/yamlparser.py new file mode 100644 index 0000000..713650d --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/utils/yamlparser.py @@ -0,0 +1,86 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import codecs +from collections import OrderedDict + +from six.moves import urllib +import yaml + +from toscaparser.common.exception import ExceptionCollector +from toscaparser.common.exception import URLException +from toscaparser.utils.gettextutils import _ + + +if hasattr(yaml, 'CSafeLoader'): + yaml_loader = yaml.CSafeLoader +else: + yaml_loader = yaml.SafeLoader + + +def load_yaml(path, a_file=True): + f = None + try: + f = codecs.open(path, encoding='utf-8', errors='strict') if a_file \ + else urllib.request.urlopen(path) + except urllib.error.URLError as e: + if hasattr(e, 'reason'): + msg = (_('Failed to reach server "%(path)s". Reason is: ' + '%(reason)s.') + % {'path': path, 'reason': e.reason}) + ExceptionCollector.appendException(URLException(what=msg)) + return + elif hasattr(e, 'code'): + msg = (_('The server "%(path)s" couldn\'t fulfill the request. ' + 'Error code: "%(code)s".') + % {'path': path, 'code': e.code}) + ExceptionCollector.appendException(URLException(what=msg)) + return + except Exception as e: + raise + return yaml.load(f.read(), Loader=yaml_loader) + + +def simple_parse(tmpl_str): + try: + tpl = yaml.load(tmpl_str, Loader=yaml_loader) + except yaml.YAMLError as yea: + ExceptionCollector.appendException(ValueError(yea)) + else: + if tpl is None: + tpl = {} + return tpl + + +def ordered_load(stream, Loader=yaml.Loader, object_pairs_hook=OrderedDict): + class OrderedLoader(Loader): + pass + + def construct_mapping(loader, node): + loader.flatten_mapping(node) + return object_pairs_hook(loader.construct_pairs(node)) + + OrderedLoader.add_constructor( + yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, + construct_mapping) + return yaml.load(stream, OrderedLoader) + + +def simple_ordered_parse(tmpl_str): + try: + tpl = ordered_load(tmpl_str) + except yaml.YAMLError as yea: + ExceptionCollector.appendException(ValueError(yea)) + else: + if tpl is None: + tpl = {} + return tpl diff --git a/tosca2heat/tosca-parser/tox.ini b/tosca2heat/tosca-parser/tox.ini new file mode 100644 index 0000000..9e5f365 --- /dev/null +++ b/tosca2heat/tosca-parser/tox.ini @@ -0,0 +1,37 @@ +[tox] +minversion = 1.6 +envlist = py34,py27,pypy,pep8 +skipsdist = True + +[testenv] +usedevelop = True +install_command = pip install -U {opts} {packages} +setenv = + VIRTUAL_ENV={envdir} +deps = -r{toxinidir}/requirements.txt + -r{toxinidir}/test-requirements.txt +commands = python setup.py test --slowest --testr-args='{posargs}' + +[testenv:pep8] +commands = flake8 + +[testenv:venv] +commands = {posargs} + +[testenv:cover] +commands = python setup.py test --coverage --coverage-package-name=toscaparser --testr-args='{posargs}' + +[testenv:docs] +commands = python setup.py build_sphinx + +[testenv:debug] +commands = oslo_debug_helper -t toscaparser/tests {posargs} + +[flake8] +# H803 skipped on purpose per list discussion. +# E123, E125 skipped as they are invalid PEP-8. + +show-source = True +ignore = E123,E125,H803 +builtins = _ +exclude=.venv,.git,.tox,dist,doc,*openstack/common*,*lib/python*,*egg,build |