diff --git a/.testr.conf b/.testr.conf deleted file mode 100644 index 6c1541ea..00000000 --- a/.testr.conf +++ /dev/null @@ -1,4 +0,0 @@ -[DEFAULT] -test_command=OS_STDOUT_CAPTURE=1 OS_STDERR_CAPTURE=1 OS_TEST_TIMEOUT=60 ${PYTHON:-python} -m subunit.run discover -t ./ ./ $LISTOPT $IDOPTION -test_id_option=--load-list $IDFILE -test_list_option=--list diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 68c771a0..00000000 --- a/LICENSE +++ /dev/null @@ -1,176 +0,0 @@ - - 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/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index c978a52d..00000000 --- a/MANIFEST.in +++ /dev/null @@ -1,6 +0,0 @@ -include AUTHORS -include ChangeLog -exclude .gitignore -exclude .gitreview - -global-exclude *.pyc diff --git a/README b/README new file mode 100644 index 00000000..3a1e7f83 --- /dev/null +++ b/README @@ -0,0 +1,10 @@ +This project is no longer maintained. + +The contents of this repository are still available in the Git +source code management system. To see the contents of this +repository before it reached its end of life, please check out the +previous commit with "git checkout HEAD^1". + +For any further questions, please email +openstack-dev@lists.openstack.org or join #openstack-dev or #tripleo +on Freenode. diff --git a/README.rst b/README.rst deleted file mode 100644 index 7c9295d0..00000000 --- a/README.rst +++ /dev/null @@ -1,45 +0,0 @@ -====== -Tuskar -====== - -What is Tuskar? ---------------- - -Tuskar is a management service for planning TripleO deployments. - -Interested in seeing the full Tuskar and Tuskar UI setup? `Watch -the demo. `_ - -For additional information, take a look at the `Tuskar -documentation `_. - - -Installation Information ------------------------- - -Please see `install.rst `_ for an -installation guide. - - -Building the Docs ------------------ - -From the root directory:: - - python setup.py build_sphinx - - -Contributing ------------- - -Interested in contributing? Take a look at `contributing.rst -`_ for details on how to do so. - - -Contact Us ----------- - -Join us on IRC (Internet Relay Chat):: - - Network: Freenode (irc.freenode.net/tuskar) - Channel: #tripleo and #tuskar diff --git a/devstack/plugin.sh b/devstack/plugin.sh deleted file mode 100755 index 769cc7a4..00000000 --- a/devstack/plugin.sh +++ /dev/null @@ -1,159 +0,0 @@ -#!/bin/bash - -# Install and start the **Tuskar** service - -# library code (equivalent to lib/tuskar) -# --------- -# - install_tuskarclient -# - install_tuskar -# - configure_tuskarclient -# - configure_tuskar -# - init_tuskar -# - start_tuskar -# - stop_tuskar -# - cleanup_tuskar - -# Save trace setting -XTRACE=$(set +o | grep xtrace) -set +o xtrace - -# Functions -# --------- - -# Test if any Tuskar services are enabled -# is_tuskar_enabled -function is_tuskar_enabled { - [[ ,${ENABLED_SERVICES} =~ ,"tuskar-" ]] && return 0 - return 1 -} - -# cleanup_tuskar() - Remove residual data files, anything left over from previous -# runs that a clean run would need to clean up -function cleanup_tuskar { - sudo rm -rf $TUSKAR_AUTH_CACHE_DIR -} - -# configure_tuskar() - Set config files, create data dirs, etc -function configure_tuskar { - if [[ ! -d $TUSKAR_CONF_DIR ]]; then - sudo mkdir -p $TUSKAR_CONF_DIR - fi - sudo chown $STACK_USER $TUSKAR_CONF_DIR - # remove old config files - rm -f $TUSKAR_CONF_DIR/tuskar-*.conf - - TUSKAR_POLICY_FILE=$TUSKAR_CONF_DIR/policy.json - - cp $TUSKAR_DIR/etc/tuskar/policy.json $TUSKAR_POLICY_FILE - cp $TUSKAR_DIR/etc/tuskar/tuskar.conf.sample $TUSKAR_CONF - - # common options - iniset $TUSKAR_CONF database connection `database_connection_url tuskar` - - # logging - iniset $TUSKAR_CONF DEFAULT debug $ENABLE_DEBUG_LOG_LEVEL - iniset $TUSKAR_CONF DEFAULT use_syslog $SYSLOG - if [ "$LOG_COLOR" == "True" ] && [ "$SYSLOG" == "False" ]; then - # Add color to logging output - setup_colorized_logging $TUSKAR_CONF DEFAULT tenant user - fi - - configure_auth_token_middleware $TUSKAR_CONF tuskar $TUSKAR_AUTH_CACHE_DIR - - if is_ssl_enabled_service "key"; then - iniset $TUSKAR_CONF clients_keystone ca_file $SSL_BUNDLE_FILE - fi - - iniset $TUSKAR_CONF tuskar_api bind_port $TUSKAR_API_PORT - -} - -# init_tuskar() - Initialize database -function init_tuskar { - - # (re)create tuskar database - recreate_database tuskar - - tuskar-dbsync --config-file $TUSKAR_CONF - create_tuskar_cache_dir -} - -# create_tuskar_cache_dir() - Part of the init_tuskar() process -function create_tuskar_cache_dir { - # Create cache dirs - sudo mkdir -p $TUSKAR_AUTH_CACHE_DIR - sudo chown $STACK_USER $TUSKAR_AUTH_CACHE_DIR -} - -# install_tuskar() - Collect source and prepare -function install_tuskar { - setup_develop $TUSKAR_DIR -} - -# start_tuskar() - Start running processes, including screen -function start_tuskar { - run_process tuskar-api "tuskar-api --config-file=$TUSKAR_CONF" -} - -# stop_tuskar() - Stop running processes -function stop_tuskar { - # Kill the screen windows - local serv - for serv in tuskar-api; do - stop_process $serv - done -} - -# create_tuskar_accounts() - Set up common required tuskar accounts -function create_tuskar_accounts { - # migrated from files/keystone_data.sh - local service_tenant=$(openstack project list | awk "/ $SERVICE_TENANT_NAME / { print \$2 }") - local admin_role=$(openstack role list | awk "/ admin / { print \$2 }") - - local tuskar_user=$(get_or_create_user "tuskar" \ - "$SERVICE_PASSWORD" $service_tenant) - get_or_add_user_role $admin_role $tuskar_user $service_tenant - - if [[ "$KEYSTONE_CATALOG_BACKEND" = 'sql' ]]; then - - local tuskar_service=$(get_or_create_service "tuskar" \ - "management" "Tuskar Management Service") - get_or_create_endpoint $tuskar_service \ - "$REGION_NAME" \ - "$SERVICE_PROTOCOL://$TUSKAR_API_HOST:$TUSKAR_API_PORT" \ - "$SERVICE_PROTOCOL://$TUSKAR_API_HOST:$TUSKAR_API_PORT" \ - "$SERVICE_PROTOCOL://$TUSKAR_API_HOST:$TUSKAR_API_PORT" - fi -} - -# Main dispatcher -if [[ "$1" == "source" ]]; then - # Initial source, do nothing as functions sourced - # are below rather than in lib/tuskar - echo_summary "source extras tuskar" -elif [[ "$1" == "stack" && "$2" == "install" ]]; then - echo_summary "Installing Tuskar" - install_tuskar -elif [[ "$1" == "stack" && "$2" == "post-config" ]]; then - echo_summary "Configuring Tuskar" - configure_tuskar - if is_service_enabled key; then - create_tuskar_accounts - fi -elif [[ "$1" == "stack" && "$2" == "extra" ]]; then - echo_summary "Initializing Tuskar" - init_tuskar - start_tuskar -fi - -if [[ "$1" == "unstack" ]]; then - stop_tuskar -fi - -# Restore xtrace -$XTRACE - -# Tell emacs to use shell-script-mode -## Local variables: -## mode: shell-script -## End: diff --git a/devstack/settings b/devstack/settings deleted file mode 100644 index 259f4779..00000000 --- a/devstack/settings +++ /dev/null @@ -1,15 +0,0 @@ -# tuskar plugin settings - -TUSKAR_DIR=$DEST/tuskar -TUSKARCLIENT_DIR=$DEST/python-tuskarclient -TUSKAR_AUTH_CACHE_DIR=${TUSKAR_AUTH_CACHE_DIR:-/var/cache/tuskar} -TUSKAR_STANDALONE=$(trueorfalse False TUSKAR_STANDALONE) -TUSKAR_CONF_DIR=/etc/tuskar -TUSKAR_CONF=$TUSKAR_CONF_DIR/tuskar.conf -TUSKAR_API_HOST=${TUSKAR_API_HOST:-$HOST_IP} -TUSKAR_API_PORT=${TUSKAR_API_PORT:-8585} - -# Tell Tempest this project is present -TEMPEST_SERVICES+=,tuskar - -enable_service tuskar-api diff --git a/doc/source/_static/basic.css b/doc/source/_static/basic.css deleted file mode 100644 index d909ce37..00000000 --- a/doc/source/_static/basic.css +++ /dev/null @@ -1,416 +0,0 @@ -/** - * Sphinx stylesheet -- basic theme - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - */ - -/* -- main layout ----------------------------------------------------------- */ - -div.clearer { - clear: both; -} - -/* -- relbar ---------------------------------------------------------------- */ - -div.related { - width: 100%; - font-size: 90%; -} - -div.related h3 { - display: none; -} - -div.related ul { - margin: 0; - padding: 0 0 0 10px; - list-style: none; -} - -div.related li { - display: inline; -} - -div.related li.right { - float: right; - margin-right: 5px; -} - -/* -- sidebar --------------------------------------------------------------- */ - -div.sphinxsidebarwrapper { - padding: 10px 5px 0 10px; -} - -div.sphinxsidebar { - float: left; - width: 230px; - margin-left: -100%; - font-size: 90%; -} - -div.sphinxsidebar ul { - list-style: none; -} - -div.sphinxsidebar ul ul, -div.sphinxsidebar ul.want-points { - margin-left: 20px; - list-style: square; -} - -div.sphinxsidebar ul ul { - margin-top: 0; - margin-bottom: 0; -} - -div.sphinxsidebar form { - margin-top: 10px; -} - -div.sphinxsidebar input { - border: 1px solid #98dbcc; - font-family: sans-serif; - font-size: 1em; -} - -img { - border: 0; -} - -/* -- search page ----------------------------------------------------------- */ - -ul.search { - margin: 10px 0 0 20px; - padding: 0; -} - -ul.search li { - padding: 5px 0 5px 20px; - background-image: url(file.png); - background-repeat: no-repeat; - background-position: 0 7px; -} - -ul.search li a { - font-weight: bold; -} - -ul.search li div.context { - color: #888; - margin: 2px 0 0 30px; - text-align: left; -} - -ul.keywordmatches li.goodmatch a { - font-weight: bold; -} - -/* -- index page ------------------------------------------------------------ */ - -table.contentstable { - width: 90%; -} - -table.contentstable p.biglink { - line-height: 150%; -} - -a.biglink { - font-size: 1.3em; -} - -span.linkdescr { - font-style: italic; - padding-top: 5px; - font-size: 90%; -} - -/* -- general index --------------------------------------------------------- */ - -table.indextable td { - text-align: left; - vertical-align: top; -} - -table.indextable dl, table.indextable dd { - margin-top: 0; - margin-bottom: 0; -} - -table.indextable tr.pcap { - height: 10px; -} - -table.indextable tr.cap { - margin-top: 10px; - background-color: #f2f2f2; -} - -img.toggler { - margin-right: 3px; - margin-top: 3px; - cursor: pointer; -} - -/* -- general body styles --------------------------------------------------- */ - -a.headerlink { - visibility: hidden; -} - -h1:hover > a.headerlink, -h2:hover > a.headerlink, -h3:hover > a.headerlink, -h4:hover > a.headerlink, -h5:hover > a.headerlink, -h6:hover > a.headerlink, -dt:hover > a.headerlink { - visibility: visible; -} - -div.body p.caption { - text-align: inherit; -} - -div.body td { - text-align: left; -} - -.field-list ul { - padding-left: 1em; -} - -.first { -} - -p.rubric { - margin-top: 30px; - font-weight: bold; -} - -/* -- sidebars -------------------------------------------------------------- */ - -div.sidebar { - margin: 0 0 0.5em 1em; - border: 1px solid #ddb; - padding: 7px 7px 0 7px; - background-color: #ffe; - width: 40%; - float: right; -} - -p.sidebar-title { - font-weight: bold; -} - -/* -- topics ---------------------------------------------------------------- */ - -div.topic { - border: 1px solid #ccc; - padding: 7px 7px 0 7px; - margin: 10px 0 10px 0; -} - -p.topic-title { - font-size: 1.1em; - font-weight: bold; - margin-top: 10px; -} - -/* -- admonitions ----------------------------------------------------------- */ - -div.admonition { - margin-top: 10px; - margin-bottom: 10px; - padding: 7px; -} - -div.admonition dt { - font-weight: bold; -} - -div.admonition dl { - margin-bottom: 0; -} - -p.admonition-title { - margin: 0px 10px 5px 0px; - font-weight: bold; -} - -div.body p.centered { - text-align: center; - margin-top: 25px; -} - -/* -- tables ---------------------------------------------------------------- */ - -table.docutils { - border: 0; - border-collapse: collapse; -} - -table.docutils td, table.docutils th { - padding: 1px 8px 1px 0; - border-top: 0; - border-left: 0; - border-right: 0; - border-bottom: 1px solid #aaa; -} - -table.field-list td, table.field-list th { - border: 0 !important; -} - -table.footnote td, table.footnote th { - border: 0 !important; -} - -th { - text-align: left; - padding-right: 5px; -} - -/* -- other body styles ----------------------------------------------------- */ - -dl { - margin-bottom: 15px; -} - -dd p { - margin-top: 0px; -} - -dd ul, dd table { - margin-bottom: 10px; -} - -dd { - margin-top: 3px; - margin-bottom: 10px; - margin-left: 30px; -} - -dt:target, .highlight { - background-color: #fbe54e; -} - -dl.glossary dt { - font-weight: bold; - font-size: 1.1em; -} - -.field-list ul { - margin: 0; - padding-left: 1em; -} - -.field-list p { - margin: 0; -} - -.refcount { - color: #060; -} - -.optional { - font-size: 1.3em; -} - -.versionmodified { - font-style: italic; -} - -.system-message { - background-color: #fda; - padding: 5px; - border: 3px solid red; -} - -.footnote:target { - background-color: #ffa -} - -.line-block { - display: block; - margin-top: 1em; - margin-bottom: 1em; -} - -.line-block .line-block { - margin-top: 0; - margin-bottom: 0; - margin-left: 1.5em; -} - -/* -- code displays --------------------------------------------------------- */ - -pre { - overflow: auto; -} - -td.linenos pre { - padding: 5px 0px; - border: 0; - background-color: transparent; - color: #aaa; -} - -table.highlighttable { - margin-left: 0.5em; -} - -table.highlighttable td { - padding: 0 0.5em 0 0.5em; -} - -tt.descname { - background-color: transparent; - font-weight: bold; - font-size: 1.2em; -} - -tt.descclassname { - background-color: transparent; -} - -tt.xref, a tt { - background-color: transparent; - font-weight: bold; -} - -h1 tt, h2 tt, h3 tt, h4 tt, h5 tt, h6 tt { - background-color: transparent; -} - -/* -- math display ---------------------------------------------------------- */ - -img.math { - vertical-align: middle; -} - -div.body div.math p { - text-align: center; -} - -span.eqno { - float: right; -} - -/* -- printout stylesheet --------------------------------------------------- */ - -@media print { - div.document, - div.documentwrapper, - div.bodywrapper { - margin: 0 !important; - width: 100%; - } - - div.sphinxsidebar, - div.related, - div.footer, - #top-link { - display: none; - } -} diff --git a/doc/source/_static/default.css b/doc/source/_static/default.css deleted file mode 100644 index c8091ecb..00000000 --- a/doc/source/_static/default.css +++ /dev/null @@ -1,230 +0,0 @@ -/** - * Sphinx stylesheet -- default theme - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - */ - -@import url("basic.css"); - -/* -- page layout ----------------------------------------------------------- */ - -body { - font-family: sans-serif; - font-size: 100%; - background-color: #11303d; - color: #000; - margin: 0; - padding: 0; -} - -div.document { - background-color: #1c4e63; -} - -div.documentwrapper { - float: left; - width: 100%; -} - -div.bodywrapper { - margin: 0 0 0 230px; -} - -div.body { - background-color: #ffffff; - color: #000000; - padding: 0 20px 30px 20px; -} - -div.footer { - color: #ffffff; - width: 100%; - padding: 9px 0 9px 0; - text-align: center; - font-size: 75%; -} - -div.footer a { - color: #ffffff; - text-decoration: underline; -} - -div.related { - background-color: #133f52; - line-height: 30px; - color: #ffffff; -} - -div.related a { - color: #ffffff; -} - -div.sphinxsidebar { -} - -div.sphinxsidebar h3 { - font-family: 'Trebuchet MS', sans-serif; - color: #ffffff; - font-size: 1.4em; - font-weight: normal; - margin: 0; - padding: 0; -} - -div.sphinxsidebar h3 a { - color: #ffffff; -} - -div.sphinxsidebar h4 { - font-family: 'Trebuchet MS', sans-serif; - color: #ffffff; - font-size: 1.3em; - font-weight: normal; - margin: 5px 0 0 0; - padding: 0; -} - -div.sphinxsidebar p { - color: #ffffff; -} - -div.sphinxsidebar p.topless { - margin: 5px 10px 10px 10px; -} - -div.sphinxsidebar ul { - margin: 10px; - padding: 0; - color: #ffffff; -} - -div.sphinxsidebar a { - color: #98dbcc; -} - -div.sphinxsidebar input { - border: 1px solid #98dbcc; - font-family: sans-serif; - font-size: 1em; -} - -/* -- body styles ----------------------------------------------------------- */ - -a { - color: #355f7c; - text-decoration: none; -} - -a:hover { - text-decoration: underline; -} - -div.body p, div.body dd, div.body li { - text-align: left; - line-height: 130%; -} - -div.body h1, -div.body h2, -div.body h3, -div.body h4, -div.body h5, -div.body h6 { - font-family: 'Trebuchet MS', sans-serif; - background-color: #f2f2f2; - font-weight: normal; - color: #20435c; - border-bottom: 1px solid #ccc; - margin: 20px -20px 10px -20px; - padding: 3px 0 3px 10px; -} - -div.body h1 { margin-top: 0; font-size: 200%; } -div.body h2 { font-size: 160%; } -div.body h3 { font-size: 140%; } -div.body h4 { font-size: 120%; } -div.body h5 { font-size: 110%; } -div.body h6 { font-size: 100%; } - -a.headerlink { - color: #c60f0f; - font-size: 0.8em; - padding: 0 4px 0 4px; - text-decoration: none; -} - -a.headerlink:hover { - background-color: #c60f0f; - color: white; -} - -div.body p, div.body dd, div.body li { - text-align: left; - line-height: 130%; -} - -div.admonition p.admonition-title + p { - display: inline; -} - -div.admonition p { - margin-bottom: 5px; -} - -div.admonition pre { - margin-bottom: 5px; -} - -div.admonition ul, div.admonition ol { - margin-bottom: 5px; -} - -div.note { - background-color: #eee; - border: 1px solid #ccc; -} - -div.seealso { - background-color: #ffc; - border: 1px solid #ff6; -} - -div.topic { - background-color: #eee; -} - -div.warning { - background-color: #ffe4e4; - border: 1px solid #f66; -} - -p.admonition-title { - display: inline; -} - -p.admonition-title:after { - content: ":"; -} - -pre { - padding: 5px; - background-color: #eeffcc; - color: #333333; - line-height: 120%; - border: 1px solid #ac9; - border-left: none; - border-right: none; -} - -tt { - background-color: #ecf0f3; - padding: 0 1px 0 1px; - font-size: 0.95em; -} - -.warning tt { - background: #efc2c2; -} - -.note tt { - background: #d6d6d6; -} diff --git a/doc/source/_static/header-line.gif b/doc/source/_static/header-line.gif deleted file mode 100644 index 3601730e..00000000 Binary files a/doc/source/_static/header-line.gif and /dev/null differ diff --git a/doc/source/_static/header_bg.jpg b/doc/source/_static/header_bg.jpg deleted file mode 100644 index f788c41c..00000000 Binary files a/doc/source/_static/header_bg.jpg and /dev/null differ diff --git a/doc/source/_static/jquery.tweet.js b/doc/source/_static/jquery.tweet.js deleted file mode 100644 index 79bf0bdb..00000000 --- a/doc/source/_static/jquery.tweet.js +++ /dev/null @@ -1,154 +0,0 @@ -(function($) { - - $.fn.tweet = function(o){ - var s = { - username: ["seaofclouds"], // [string] required, unless you want to display our tweets. :) it can be an array, just do ["username1","username2","etc"] - list: null, //[string] optional name of list belonging to username - avatar_size: null, // [integer] height and width of avatar if displayed (48px max) - count: 3, // [integer] how many tweets to display? - intro_text: null, // [string] do you want text BEFORE your your tweets? - outro_text: null, // [string] do you want text AFTER your tweets? - join_text: null, // [string] optional text in between date and tweet, try setting to "auto" - auto_join_text_default: "i said,", // [string] auto text for non verb: "i said" bullocks - auto_join_text_ed: "i", // [string] auto text for past tense: "i" surfed - auto_join_text_ing: "i am", // [string] auto tense for present tense: "i was" surfing - auto_join_text_reply: "i replied to", // [string] auto tense for replies: "i replied to" @someone "with" - auto_join_text_url: "i was looking at", // [string] auto tense for urls: "i was looking at" http:... - loading_text: null, // [string] optional loading text, displayed while tweets load - query: null // [string] optional search query - }; - - if(o) $.extend(s, o); - - $.fn.extend({ - linkUrl: function() { - var returning = []; - var regexp = /((ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?)/gi; - this.each(function() { - returning.push(this.replace(regexp,"$1")); - }); - return $(returning); - }, - linkUser: function() { - var returning = []; - var regexp = /[\@]+([A-Za-z0-9-_]+)/gi; - this.each(function() { - returning.push(this.replace(regexp,"@$1")); - }); - return $(returning); - }, - linkHash: function() { - var returning = []; - var regexp = / [\#]+([A-Za-z0-9-_]+)/gi; - this.each(function() { - returning.push(this.replace(regexp, ' #$1')); - }); - return $(returning); - }, - capAwesome: function() { - var returning = []; - this.each(function() { - returning.push(this.replace(/\b(awesome)\b/gi, '$1')); - }); - return $(returning); - }, - capEpic: function() { - var returning = []; - this.each(function() { - returning.push(this.replace(/\b(epic)\b/gi, '$1')); - }); - return $(returning); - }, - makeHeart: function() { - var returning = []; - this.each(function() { - returning.push(this.replace(/(<)+[3]/gi, "")); - }); - return $(returning); - } - }); - - function relative_time(time_value) { - var parsed_date = Date.parse(time_value); - var relative_to = (arguments.length > 1) ? arguments[1] : new Date(); - var delta = parseInt((relative_to.getTime() - parsed_date) / 1000); - var pluralize = function (singular, n) { - return '' + n + ' ' + singular + (n == 1 ? '' : 's'); - }; - if(delta < 60) { - return 'less than a minute ago'; - } else if(delta < (45*60)) { - return 'about ' + pluralize("minute", parseInt(delta / 60)) + ' ago'; - } else if(delta < (24*60*60)) { - return 'about ' + pluralize("hour", parseInt(delta / 3600)) + ' ago'; - } else { - return 'about ' + pluralize("day", parseInt(delta / 86400)) + ' ago'; - } - } - - function build_url() { - var proto = ('https:' == document.location.protocol ? 'https:' : 'http:'); - if (s.list) { - return proto+"//api.twitter.com/1/"+s.username[0]+"/lists/"+s.list+"/statuses.json?per_page="+s.count+"&callback=?"; - } else if (s.query == null && s.username.length == 1) { - return proto+'//twitter.com/status/user_timeline/'+s.username[0]+'.json?count='+s.count+'&callback=?'; - } else { - var query = (s.query || 'from:'+s.username.join('%20OR%20from:')); - return proto+'//search.twitter.com/search.json?&q='+query+'&rpp='+s.count+'&callback=?'; - } - } - - return this.each(function(){ - var list = $('
    ').appendTo(this); - var intro = '

    '+s.intro_text+'

    '; - var outro = '

    '+s.outro_text+'

    '; - var loading = $('

    '+s.loading_text+'

    '); - - if(typeof(s.username) == "string"){ - s.username = [s.username]; - } - - if (s.loading_text) $(this).append(loading); - $.getJSON(build_url(), function(data){ - if (s.loading_text) loading.remove(); - if (s.intro_text) list.before(intro); - $.each((data.results || data), function(i,item){ - // auto join text based on verb tense and content - if (s.join_text == "auto") { - if (item.text.match(/^(@([A-Za-z0-9-_]+)) .*/i)) { - var join_text = s.auto_join_text_reply; - } else if (item.text.match(/(^\w+:\/\/[A-Za-z0-9-_]+\.[A-Za-z0-9-_:%&\?\/.=]+) .*/i)) { - var join_text = s.auto_join_text_url; - } else if (item.text.match(/^((\w+ed)|just) .*/im)) { - var join_text = s.auto_join_text_ed; - } else if (item.text.match(/^(\w*ing) .*/i)) { - var join_text = s.auto_join_text_ing; - } else { - var join_text = s.auto_join_text_default; - } - } else { - var join_text = s.join_text; - }; - - var from_user = item.from_user || item.user.screen_name; - var profile_image_url = item.profile_image_url || item.user.profile_image_url; - var join_template = ' '+join_text+' '; - var join = ((s.join_text) ? join_template : ' '); - var avatar_template = ''+from_user+'\'s avatar'; - var avatar = (s.avatar_size ? avatar_template : ''); - var date = ''+relative_time(item.created_at)+''; - var text = '' +$([item.text]).linkUrl().linkUser().linkHash().makeHeart().capAwesome().capEpic()[0]+ ''; - - // until we create a template option, arrange the items below to alter a tweet's display. - list.append('
  • ' + avatar + date + join + text + '
  • '); - - list.children('li:first').addClass('tweet_first'); - list.children('li:odd').addClass('tweet_even'); - list.children('li:even').addClass('tweet_odd'); - }); - if (s.outro_text) list.after(outro); - }); - - }); - }; -})(jQuery); \ No newline at end of file diff --git a/doc/source/_static/nature.css b/doc/source/_static/nature.css deleted file mode 100644 index a98bd420..00000000 --- a/doc/source/_static/nature.css +++ /dev/null @@ -1,245 +0,0 @@ -/* - * nature.css_t - * ~~~~~~~~~~~~ - * - * Sphinx stylesheet -- nature theme. - * - * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * - */ - -@import url("basic.css"); - -/* -- page layout ----------------------------------------------------------- */ - -body { - font-family: Arial, sans-serif; - font-size: 100%; - background-color: #111; - color: #555; - margin: 0; - padding: 0; -} - -div.documentwrapper { - float: left; - width: 100%; -} - -div.bodywrapper { - margin: 0 0 0 {{ theme_sidebarwidth|toint }}px; -} - -hr { - border: 1px solid #B1B4B6; -} - -div.document { - background-color: #eee; -} - -div.body { - background-color: #ffffff; - color: #3E4349; - padding: 0 30px 30px 30px; - font-size: 0.9em; -} - -div.footer { - color: #555; - width: 100%; - padding: 13px 0; - text-align: center; - font-size: 75%; -} - -div.footer a { - color: #444; - text-decoration: underline; -} - -div.related { - background-color: #6BA81E; - line-height: 32px; - color: #fff; - text-shadow: 0px 1px 0 #444; - font-size: 0.9em; -} - -div.related a { - color: #E2F3CC; -} - -div.sphinxsidebar { - font-size: 0.75em; - line-height: 1.5em; -} - -div.sphinxsidebarwrapper{ - padding: 20px 0; -} - -div.sphinxsidebar h3, -div.sphinxsidebar h4 { - font-family: Arial, sans-serif; - color: #222; - font-size: 1.2em; - font-weight: normal; - margin: 0; - padding: 5px 10px; - background-color: #ddd; - text-shadow: 1px 1px 0 white -} - -div.sphinxsidebar h4{ - font-size: 1.1em; -} - -div.sphinxsidebar h3 a { - color: #444; -} - - -div.sphinxsidebar p { - color: #888; - padding: 5px 20px; -} - -div.sphinxsidebar p.topless { -} - -div.sphinxsidebar ul { - margin: 10px 20px; - padding: 0; - color: #000; -} - -div.sphinxsidebar a { - color: #444; -} - -div.sphinxsidebar input { - border: 1px solid #ccc; - font-family: sans-serif; - font-size: 1em; -} - -div.sphinxsidebar input[type=text]{ - margin-left: 20px; -} - -/* -- body styles ----------------------------------------------------------- */ - -a { - color: #005B81; - text-decoration: none; -} - -a:hover { - color: #E32E00; - text-decoration: underline; -} - -div.body h1, -div.body h2, -div.body h3, -div.body h4, -div.body h5, -div.body h6 { - font-family: Arial, sans-serif; - background-color: #BED4EB; - font-weight: normal; - color: #212224; - margin: 30px 0px 10px 0px; - padding: 5px 0 5px 10px; - text-shadow: 0px 1px 0 white -} - -div.body h1 { border-top: 20px solid white; margin-top: 0; font-size: 200%; } -div.body h2 { font-size: 150%; background-color: #C8D5E3; } -div.body h3 { font-size: 120%; background-color: #D8DEE3; } -div.body h4 { font-size: 110%; background-color: #D8DEE3; } -div.body h5 { font-size: 100%; background-color: #D8DEE3; } -div.body h6 { font-size: 100%; background-color: #D8DEE3; } - -a.headerlink { - color: #c60f0f; - font-size: 0.8em; - padding: 0 4px 0 4px; - text-decoration: none; -} - -a.headerlink:hover { - background-color: #c60f0f; - color: white; -} - -div.body p, div.body dd, div.body li { - line-height: 1.5em; -} - -div.admonition p.admonition-title + p { - display: inline; -} - -div.highlight{ - background-color: white; -} - -div.note { - background-color: #eee; - border: 1px solid #ccc; -} - -div.seealso { - background-color: #ffc; - border: 1px solid #ff6; -} - -div.topic { - background-color: #eee; -} - -div.warning { - background-color: #ffe4e4; - border: 1px solid #f66; -} - -p.admonition-title { - display: inline; -} - -p.admonition-title:after { - content: ":"; -} - -pre { - padding: 10px; - background-color: White; - color: #222; - line-height: 1.2em; - border: 1px solid #C6C9CB; - font-size: 1.1em; - margin: 1.5em 0 1.5em 0; - -webkit-box-shadow: 1px 1px 1px #d8d8d8; - -moz-box-shadow: 1px 1px 1px #d8d8d8; -} - -tt { - background-color: #ecf0f3; - color: #222; - /* padding: 1px 2px; */ - font-size: 1.1em; - font-family: monospace; -} - -.viewcode-back { - font-family: Arial, sans-serif; -} - -div.viewcode-block:target { - background-color: #f4debf; - border-top: 1px solid #ac9; - border-bottom: 1px solid #ac9; -} diff --git a/doc/source/_static/openstack_logo.png b/doc/source/_static/openstack_logo.png deleted file mode 100644 index 146faec5..00000000 Binary files a/doc/source/_static/openstack_logo.png and /dev/null differ diff --git a/doc/source/_static/pygments.css b/doc/source/_static/pygments.css deleted file mode 100644 index d79caa15..00000000 --- a/doc/source/_static/pygments.css +++ /dev/null @@ -1,62 +0,0 @@ -.highlight .hll { background-color: #ffffcc } -.highlight { background: #eeffcc; } -.highlight .c { color: #408090; font-style: italic } /* Comment */ -.highlight .err { border: 1px solid #FF0000 } /* Error */ -.highlight .k { color: #007020; font-weight: bold } /* Keyword */ -.highlight .o { color: #666666 } /* Operator */ -.highlight .cm { color: #408090; font-style: italic } /* Comment.Multiline */ -.highlight .cp { color: #007020 } /* Comment.Preproc */ -.highlight .c1 { color: #408090; font-style: italic } /* Comment.Single */ -.highlight .cs { color: #408090; background-color: #fff0f0 } /* Comment.Special */ -.highlight .gd { color: #A00000 } /* Generic.Deleted */ -.highlight .ge { font-style: italic } /* Generic.Emph */ -.highlight .gr { color: #FF0000 } /* Generic.Error */ -.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ -.highlight .gi { color: #00A000 } /* Generic.Inserted */ -.highlight .go { color: #333333 } /* Generic.Output */ -.highlight .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */ -.highlight .gs { font-weight: bold } /* Generic.Strong */ -.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ -.highlight .gt { color: #0044DD } /* Generic.Traceback */ -.highlight .kc { color: #007020; font-weight: bold } /* Keyword.Constant */ -.highlight .kd { color: #007020; font-weight: bold } /* Keyword.Declaration */ -.highlight .kn { color: #007020; font-weight: bold } /* Keyword.Namespace */ -.highlight .kp { color: #007020 } /* Keyword.Pseudo */ -.highlight .kr { color: #007020; font-weight: bold } /* Keyword.Reserved */ -.highlight .kt { color: #902000 } /* Keyword.Type */ -.highlight .m { color: #208050 } /* Literal.Number */ -.highlight .s { color: #4070a0 } /* Literal.String */ -.highlight .na { color: #4070a0 } /* Name.Attribute */ -.highlight .nb { color: #007020 } /* Name.Builtin */ -.highlight .nc { color: #0e84b5; font-weight: bold } /* Name.Class */ -.highlight .no { color: #60add5 } /* Name.Constant */ -.highlight .nd { color: #555555; font-weight: bold } /* Name.Decorator */ -.highlight .ni { color: #d55537; font-weight: bold } /* Name.Entity */ -.highlight .ne { color: #007020 } /* Name.Exception */ -.highlight .nf { color: #06287e } /* Name.Function */ -.highlight .nl { color: #002070; font-weight: bold } /* Name.Label */ -.highlight .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */ -.highlight .nt { color: #062873; font-weight: bold } /* Name.Tag */ -.highlight .nv { color: #bb60d5 } /* Name.Variable */ -.highlight .ow { color: #007020; font-weight: bold } /* Operator.Word */ -.highlight .w { color: #bbbbbb } /* Text.Whitespace */ -.highlight .mf { color: #208050 } /* Literal.Number.Float */ -.highlight .mh { color: #208050 } /* Literal.Number.Hex */ -.highlight .mi { color: #208050 } /* Literal.Number.Integer */ -.highlight .mo { color: #208050 } /* Literal.Number.Oct */ -.highlight .sb { color: #4070a0 } /* Literal.String.Backtick */ -.highlight .sc { color: #4070a0 } /* Literal.String.Char */ -.highlight .sd { color: #4070a0; font-style: italic } /* Literal.String.Doc */ -.highlight .s2 { color: #4070a0 } /* Literal.String.Double */ -.highlight .se { color: #4070a0; font-weight: bold } /* Literal.String.Escape */ -.highlight .sh { color: #4070a0 } /* Literal.String.Heredoc */ -.highlight .si { color: #70a0d0; font-style: italic } /* Literal.String.Interpol */ -.highlight .sx { color: #c65d09 } /* Literal.String.Other */ -.highlight .sr { color: #235388 } /* Literal.String.Regex */ -.highlight .s1 { color: #4070a0 } /* Literal.String.Single */ -.highlight .ss { color: #517918 } /* Literal.String.Symbol */ -.highlight .bp { color: #007020 } /* Name.Builtin.Pseudo */ -.highlight .vc { color: #bb60d5 } /* Name.Variable.Class */ -.highlight .vg { color: #bb60d5 } /* Name.Variable.Global */ -.highlight .vi { color: #bb60d5 } /* Name.Variable.Instance */ -.highlight .il { color: #208050 } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/doc/source/_static/tweaks.css b/doc/source/_static/tweaks.css deleted file mode 100644 index 3f3fb3f0..00000000 --- a/doc/source/_static/tweaks.css +++ /dev/null @@ -1,94 +0,0 @@ -body { - background: #fff url(../_static/header_bg.jpg) top left no-repeat; -} - -#header { - width: 950px; - margin: 0 auto; - height: 102px; -} - -#header h1#logo { - background: url(../_static/openstack_logo.png) top left no-repeat; - display: block; - float: left; - text-indent: -9999px; - width: 175px; - height: 55px; -} - -#navigation { - background: url(../_static/header-line.gif) repeat-x 0 bottom; - display: block; - float: left; - margin: 27px 0 0 25px; - padding: 0; -} - -#navigation li{ - float: left; - display: block; - margin-right: 25px; -} - -#navigation li a { - display: block; - font-weight: normal; - text-decoration: none; - background-position: 50% 0; - padding: 20px 0 5px; - color: #353535; - font-size: 14px; -} - -#navigation li a.current, #navigation li a.section { - border-bottom: 3px solid #cf2f19; - color: #cf2f19; -} - -div.related { - background-color: #cde2f8; - border: 1px solid #b0d3f8; -} - -div.related a { - color: #4078ba; - text-shadow: none; -} - -div.sphinxsidebarwrapper { - padding-top: 0; -} - -pre { - color: #555; -} - -div.documentwrapper h1, div.documentwrapper h2, div.documentwrapper h3, div.documentwrapper h4, div.documentwrapper h5, div.documentwrapper h6 { - font-family: 'PT Sans', sans-serif !important; - color: #264D69; - border-bottom: 1px dotted #C5E2EA; - padding: 0; - background: none; - padding-bottom: 5px; -} - -div.documentwrapper h3 { - color: #CF2F19; -} - -a.headerlink { - color: #fff !important; - margin-left: 5px; - background: #CF2F19 !important; -} - -div.body { - margin-top: -25px; - margin-left: 230px; -} - -div.document { - width: 960px; - margin: 0 auto; -} \ No newline at end of file diff --git a/doc/source/_theme/layout.html b/doc/source/_theme/layout.html deleted file mode 100644 index 750b7822..00000000 --- a/doc/source/_theme/layout.html +++ /dev/null @@ -1,83 +0,0 @@ -{% extends "basic/layout.html" %} -{% set css_files = css_files + ['_static/tweaks.css'] %} -{% set script_files = script_files + ['_static/jquery.tweet.js'] %} - -{%- macro sidebar() %} - {%- if not embedded %}{% if not theme_nosidebar|tobool %} -
    -
    - {%- block sidebarlogo %} - {%- if logo %} - - {%- endif %} - {%- endblock %} - {%- block sidebartoc %} - {%- if display_toc %} -

    {{ _('Table Of Contents') }}

    - {{ toc }} - {%- endif %} - {%- endblock %} - {%- block sidebarrel %} - {%- if prev %} -

    {{ _('Previous topic') }}

    -

    {{ prev.title }}

    - {%- endif %} - {%- if next %} -

    {{ _('Next topic') }}

    -

    {{ next.title }}

    - {%- endif %} - {%- endblock %} - {%- block sidebarsourcelink %} - {%- if show_source and has_source and sourcename %} -

    {{ _('This Page') }}

    - - {%- endif %} - {%- endblock %} - {%- if customsidebar %} - {% include customsidebar %} - {%- endif %} - {%- block sidebarsearch %} - {%- if pagename != "search" %} - - - {%- endif %} - {%- endblock %} -
    -
    - {%- endif %}{% endif %} -{%- endmacro %} - -{% block relbar1 %}{% endblock relbar1 %} - -{% block header %} - -{% endblock %} \ No newline at end of file diff --git a/doc/source/_theme/theme.conf b/doc/source/_theme/theme.conf deleted file mode 100644 index 1cc40044..00000000 --- a/doc/source/_theme/theme.conf +++ /dev/null @@ -1,4 +0,0 @@ -[theme] -inherit = basic -stylesheet = nature.css -pygments_style = tango diff --git a/doc/source/api/curl-v2.rst b/doc/source/api/curl-v2.rst deleted file mode 100644 index b660c5ff..00000000 --- a/doc/source/api/curl-v2.rst +++ /dev/null @@ -1,197 +0,0 @@ -============================ -cURL Commands for API ver. 2 -============================ - -.. _index: - -Resources ---------- -- `Plan`_ -- `Role`_ - - -Plan ----- - - -Example of JSON Representation of Plan -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -:: - - { - "created_at": "2014-09-26T20:23:14.222815", - "description": "Development testing cloud", - "name": "dev-cloud", - "parameters": - [ - { - "constraints": - [ - { - "constraint_type": "range", - "definition": - { - "min": "0" - }, - "description": "Can't be less than zero" - } - ], - "default": "0", - "description": "The number of cinder storage nodes to deploy" - "hidden": false, - "label": "The number of cinder storage nodes to deploy", - "name": "Cinder-Storage-1::count", - "parameter_type": "number", - "value": "0" - }, - { - "constraints": [] - "default": "guest", - "description": "The password for RabbitMQ", - "hidden": true, - "label": null, - "name": "compute-1::RabbitPassword", - "parameter_type: "string" - "value": "secret-password" - } - ], - "roles": - [ - { - "description": "OpenStack hypervisor node. Can be wrapped in a ResourceGroup for scaling.\n", - "name": "compute", - "uuid": "b7b1583c-5c80-481f-a25b-708ed4a39734", - "version": 1 - } - ], - "updated_at": null, - "uuid": "53268a27-afc8-4b21-839f-90227dd7a001" - } - - - -List All Plans -~~~~~~~~~~ - -:: - - curl -v -X GET -H 'Content-Type: application/json' -H 'Accept: application/json' http://0.0.0.0:8585/v2/plans/ - - -Retrieve a Single Plan -~~~~~~~~~~~~~~~~~~~~~~ - -:: - - curl -v -X GET -H 'Content-Type: application/json' -H 'Accept: application/json' http://0.0.0.0:8585/v2/plans/53268a27-afc8-4b21-839f-90227dd7a001 - - -Create a New Plan -~~~~~~~~~~~~~~~~~ - -:: - - curl -v -X POST -H 'Content-Type: application/json' -H 'Accept: application/json' -d ' - { - "name": "dev-cloud", - "description": "Development testing cloud", - } - ' http://0.0.0.0:8585/v2/plans - -This command will create new Plan without any Roles associated with it. -To assign a Role to Plan see `How to Add a Role to a Plan <#adding-a-role-to-a-plan>`_. - - -Delete an Existing Plan -~~~~~~~~~~~~~~~~~~~~~~~ - -:: - - curl -v -X DELETE http://localhost:8585/v2/plans/53268a27-afc8-4b21-839f-90227dd7a001 - - -Changing a Plan’s Configuration Values -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -:: - - curl -v -X PATCH -H 'Content-Type: application/json' -H 'Accept: application/json' -d ' - [ - { - "name" : "database_host", - "value" : "10.11.12.13" - }, - { - "name" : "database_password", - "value" : "secret" - } - ] - ' http://0.0.0.0:8585/v2/plans/53268a27-afc8-4b21-839f-90227dd7a001 - -You can change only existing parameters in Plan. - -Retrieve a Plan’s Template Files -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -:: - - curl -v -X GET -H 'Content-Type: application/json' -H 'Accept: application/json' http://0.0.0.0:8585/v2/plans/53268a27-afc8-4b21-839f-90227dd7a001/templates - -Example of JSON representation: - -:: - - { - "environment.yaml" : "... content of template file ...", - "plan.yaml" : "... content of template file ...", - "provider-compute-1.yaml" : "... content of template file ..." - } - -`back to top <#index>`_ - - -Role ----- - - -Example of JSON Representation of Role -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -:: - - { - "description": "OpenStack hypervisor node. Can be wrapped in a ResourceGroup for scaling.\n", - "name": "compute", - "uuid": "b7b1583c-5c80-481f-a25b-708ed4a39734", - "version": 1 - } - -Retrieving Possible Roles -~~~~~~~~~~~~~~~~~~~~~~~~~ - -:: - - curl -v -X GET -H 'Content-Type: application/json' -H 'Accept: application/json' http://0.0.0.0:8585/v2/roles/ - - -Adding a Role to a Plan -~~~~~~~~~~~~~~~~~~~~~~~ - -:: - - curl -v -X POST -H 'Content-Type: application/json' -H 'Accept: application/json' -d ' - { - "uuid": "b7b1583c-5c80-481f-a25b-708ed4a39734" - } - ' http://0.0.0.0:8585/v2/plans/53268a27-afc8-4b21-839f-90227dd7a001 - - -Removing a Role from a Plan -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -:: - - curl -v -X DELETE http://localhost:8585/v2/plans/53268a27-afc8-4b21-839f-90227dd7a001/roles/b7b1583c-5c80-481f-a25b-708ed4a39734 - -`back to top <#index>`_ diff --git a/doc/source/conf.py b/doc/source/conf.py deleted file mode 100644 index 5fc5ab99..00000000 --- a/doc/source/conf.py +++ /dev/null @@ -1,92 +0,0 @@ -# -# 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. - -# -- 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.viewcode', - 'sphinxcontrib.httpdomain', - 'sphinxcontrib.pecanwsme.rest', - 'wsmeext.sphinxext', - ] - -wsme_protocols = ['restjson', 'restxml'] - -# 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 - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix of source filenames. -source_suffix = '.rst' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = u'Tuskar' -copyright = u'OpenStack Foundation' - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -from tuskar import version as tuskar_version - -# The full version, including alpha/beta/rc tags. -release = tuskar_version.version_info.release_string() -# The short X.Y version. -version = tuskar_version.version_info.version_string() - -# A list of ignored prefixes for module index sorting. -modindex_common_prefix = ['tuskar.'] - -# 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 = '_theme' -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' - ), -] diff --git a/doc/source/contributing.rst b/doc/source/contributing.rst deleted file mode 100644 index 192b01c2..00000000 --- a/doc/source/contributing.rst +++ /dev/null @@ -1,157 +0,0 @@ -====================== -Contributing to Tuskar -====================== - -Tuskar follows the OpenStack development processes for code and -communication. The `repository is hosted on git.openstack.org -`_, `bugs and -blueprints are on Launchpad `_ and -we use the openstack-dev mailing list (subject `[tuskar]`) and -the `#tripleo` IRC channel for communication. - -As Tuskar is under the TripleO umbrella of projects you will also -want to look at the `TripleO contributing guidelines -`_. - - -Coding Standards ----------------- - -We comply with the `OpenStack coding standards -`_. - -Be sure to familiarise yourself with `OpenStack's Gerrit Workflow -`_. - -Before submitting your code, please make sure you have completed -the following checklist: - -#. Update the API docs (if needed) -#. Update the tests (if needed) - - -Finding your way around -~~~~~~~~~~~~~~~~~~~~~~~ - -There are various pieces of the codebase that may not be -immediately obvious to a newcomer to the project, so we attempt -to explain some of that in this section. - -Where do the tuskar commands come from? (tuskar-api, tuskar-dbsync, etc) -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -The project-specific commands live in tuskar/cmd, and are -implementations that use the oslo.config project as a base. They -are generated and put into your venv when you run 'python -setup.py develop'. Adding a new one consists of: - -#. Creating a new file in tuskar/cmd -#. Adding the appropriate name and package reference to the - entry\_points section of setup.cfg - -How do I add a new controller? -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Controllers are contained in tuskar/api/controllers/v2.py. To add -a new controller, you need to add an 'HTTP Representation' of -whatever model you wish to expose with this controller. This is a -simple python object that extends Base, and describes the key and -value types that the object will return. For example, say there -is a Foo model object you wish to return. - -.. code-block:: python - - class Foo(Base): - id = int - name = wtypes.text - fred = Fred # Fred is another object defined in this file - -Then add a controller for it (anywhere above the Controller class, -which is the last in the file. For example: - -.. code-block:: python - - class FoosController(rest.RestController): - @wsme_pecan.wsexpose([Foo]) - def get_all(self) - result = [] - """Do some things to get your list of Foos""" - return result - -Lastly, add a reference to the controller in the Controller class at -the bottom of the file as so. - -.. code-block:: python - - class Controller(object): - foos = FoosController() - -The name you give the controller above will be how it is accessed by -the client, so in the above case, you could get the list of foos -with. - -.. code-block:: bash - - curl http://0.0.0.0:8585/v1/foos - -For doing something simple, like a poc controller that doesn't -return any objects, you can return plain text as so - -.. code-block:: python - - class FarkleController(rest.RestController): - @wsme_pecan.wsexpose(None, wtypes.text) - def get_all(self): - return "Hi, I am farkle!" - -Where are my changes to the app? -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -You may make a change to, say, a controller, and wonder why your -change does not seem to happen when you call your curl command on -that resource. This is because, at least at the current time, you -must ctrl+c to kill the tuskar-api server, and then restart it -again to pick up your changes. - -How do I create a new model? -^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Models live in tuskar/db/sqlalchemy/. There are two files here of -relevance for describing the model (we will get to defining the -table in the next section), api.py and models.py. The models.py -file contains the definition of the columns to expose to the -client for the model objects, as well as a mapping of the object -in this file to the tablename define in the migration (below). In -api.py, we have utility methods, as well as validation rules and -other custom methods for interacting with the models. - -How do I define the table for my new model? -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -This is described in a migration file, located in -tuskar/db/sqlalchemy/migrate\_repo/versions/. Each new table or -change to an existing table should get a new file here with a -descriptive name, starting with a 3 digit number. Each new file -should increment the number to avoid collisions. The primary part of -this file is the definition of your table, which s done via a Table -object, and you describe the columns, using, surprisingly enough, a -Column object. There are upgrade nd downgrade methods in these -migrations to describe what to do for creating a given set of -tables, as well as dropping them, or rolling back to what was done -before the upgrade. - -Writing and Running tests -~~~~~~~~~~~~~~~~~~~~~~~~~ - -We use testtools for our unit tests, and mox for mock objects. - -You can run tests using Tox: - - .. code-block:: bash - - $ tox - -This will run tests under Python 2.7 and verify `PEP 8 -`_ compliance. The identical test -suite is run by OpenStack's Jenkins whenever you send a patch. diff --git a/doc/source/index.rst b/doc/source/index.rst deleted file mode 100644 index 225c257f..00000000 --- a/doc/source/index.rst +++ /dev/null @@ -1,60 +0,0 @@ -====== -Tuskar -====== - -Tuskar is a management service for planning TripleO deployments. - -Interested in seeing the full Tuskar and Tuskar UI setup? `Watch -the demo. `_ - - -High-Level Overview -=================== - -*TODO* Add project overview - -- *TODO* feature examples -- *TODO* link to high-level portion of FAQ -- :doc:`Recommended reading ` - -Related Projects ----------------- - -- `tuskar-ui `_ - tuskar-ui provides dashboard access to Tuskar - functionality as a Horizon plugin. See the `Tuskar UI - documentation `_ -- `python-tuskarclient `_ - A Python client for the Tuskar API, - python-tuskarclient is - utilized by tuskar-ui. - -Developer Information -===================== - -Install and Contribute ----------------------- - -.. toctree:: - :maxdepth: 2 - - install - contributing - recommended-reading - -API version 2 -------------- - -.. toctree:: - :maxdepth: 2 - - api/curl-v2 - -Contact Us -========== - -Join us on IRC (Internet Relay Chat):: - - Network: Freenode (irc.freenode.net/tuskar) - Channel: #tripleo - diff --git a/doc/source/install.rst b/doc/source/install.rst deleted file mode 100644 index 193af106..00000000 --- a/doc/source/install.rst +++ /dev/null @@ -1,184 +0,0 @@ -============================ -Developer Installation Guide -============================ - -The Tuskar source code should be pulled directly from git. - -.. code-block:: bash - - git clone https://git.openstack.org/openstack/tuskar - - -Dependencies ------------- - -Setting up a local environment for development can be done with -tox. - -.. code-block:: bash - - # install prerequisites - * Fedora/RHEL: - $ sudo yum install python-devel python-pip libxml2-devel \ - libxslt-devel postgresql-devel mariadb-devel - - * Ubuntu/Debian: - $ sudo apt-get install python-dev python-pip libxml2-dev \ - libxslt-dev libpq-dev libmysqlclient-dev - -.. note:: - - If you wish you run Tuskar against MySQL or PostgreSQL you - will need also install and configure these at this point. - Otherwise you can run Tuskar with an sqlite database. - -To run the Tuskar test suite you will also need to install Tox. - -.. code-block:: bash - - $ sudo pip install tox - -.. note:: - An `issue with tox `_ - requires that you use a version <1.70 or >= 1.7.2. - -Now create your virtualenv. - -.. code-block:: bash - - $ cd /tuskar - $ tox -e venv - - -.. note:: - - If ``pip install`` fails due to an outdated setuptools, you - can try to update it first. - - .. code-block:: bash - - $ sudo pip install --upgrade setuptools - -To run the test suite use the following command. This will run -against Python 2.7 and run the `flake8 -`_ code linting. - -.. code-block:: bash - - $ tox - - -Configuration -------------- - -Copy the sample configuration file: - -.. code-block:: bash - - $ cp etc/tuskar/tuskar.conf.sample etc/tuskar/tuskar.conf - -We need to tell tuskar where to connect to database. Edit the -config file in ``database`` section and change - -.. code-block:: ini - - #connection= - -to - -.. code-block:: ini - - connection=sqlite:///tuskar/tuskar.sqlite - -.. note:: - - If you are using a different database backend, you will need - to enter a `SQLAlchemy compatible conection string - `_ for this setting. - -We need to initialise the database schema. - -.. code-block:: bash - - # activate the virtualenv - $ source .tox/venv/bin/activate - - # if you delete tuskar.sqlite this will force creation of tables again - e.g. - # if you added a new resource table definitions etc in an existing migration - # file - $ tuskar-dbsync --config-file etc/tuskar/tuskar.conf - -You can verify this was successful (in addition to seeing no -error output) with. - -.. code-block:: bash - - $ sqlite3 tuskar/tuskar.sqlite .schema - -Then, launch the app. - -.. code-block:: bash - - $ tuskar-api --config-file etc/tuskar/tuskar.conf - -You can then verify that everything worked by running. - -.. code-block:: bash - - $ curl -v -X GET -H 'Accept: application/json' http://0.0.0.0:8585/v2/plans/ | python -mjson.tool - -This command should return JSON with an empty result set. - - -Running Tuskar API ------------------- - -Whenever you want to run the API again, just switch to the -virtualenv and run `tuskar-api` command. - -.. code-block:: bash - - $ source .tox/venv/bin/activate - $ tuskar-api --config-file etc/tuskar/tuskar.conf - - -Loading Initial Roles ---------------------- - -Tuskar needs to be provided with a set of roles that can be added -to a deployment plan. The following steps will add the roles from -the TripleO Heat Templates repository. - -.. code-block:: bash - - $ git clone https://git.openstack.org/openstack/tripleo-heat-templates - $ tuskar-load-roles --config-file etc/tuskar/tuskar.conf \ - -r tripleo-heat-templates/compute.yaml \ - -r tripleo-heat-templates/controller.yaml - -After this, if the Tuskar API isn't running, start it with the -above command and the following curl command should show you the -loaded roles. - -.. code-block:: bash - - $ curl -v -X GET -H 'Accept: application/json' http://0.0.0.0:8585/v2/roles/ | python -mjson.tool - - - -Keystone Configuration ----------------------- - -By default, Tuskar is configured to skip authentication for REST -API calls. Keystone authentication can be enabled by making the -appropriate changes to the ``tuskar.conf`` file as described in -the `keystone documentation `_ - - -Contributing ------------- - -For additional developer information, take a look at -:doc:`the contributing guide `. diff --git a/doc/source/recommended-reading.rst b/doc/source/recommended-reading.rst deleted file mode 100644 index 6194edc5..00000000 --- a/doc/source/recommended-reading.rst +++ /dev/null @@ -1,30 +0,0 @@ -=================== -Recommended Reading -=================== - -Tuskar Design Discussions -------------------------- - -- `Juno Planning `_ -- `Template storage planning `_ -- `TripleO Specifications `_ - -Relevant OpenStack Projects ---------------------------- - -- `TripleO `_ -- `Heat `_ -- `oslo.db `_ -- `oslo.config `_ -- `hacking `_ This enforces - openstack community coding style guidelines - -General Python/Frameworks -------------------------- - -- `dive into python `_ -- `pecan `_ -- `sqlalchemy `_ -- `style guide `_ This guide - is the baseline for 'hacking' above. - diff --git a/etc/tuskar/nova_overcloud_config.yml b/etc/tuskar/nova_overcloud_config.yml deleted file mode 100644 index cb08d2f1..00000000 --- a/etc/tuskar/nova_overcloud_config.yml +++ /dev/null @@ -1,4 +0,0 @@ -keystone_url: "http://10.34.32.181:5000/v2.0/" -nova_username: "admin" -nova_tenantname: "admin" -nova_password: "bcfa838f13e64436" diff --git a/etc/tuskar/policy.json b/etc/tuskar/policy.json deleted file mode 100644 index 0c2f6154..00000000 --- a/etc/tuskar/policy.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "admin_api": "is_admin:True", - "admin_or_owner": "is_admin:True or project_id:%(project_id)s", - "context_is_admin": "role:admin", - "default": "rule:admin_or_owner" -} diff --git a/etc/tuskar/tuskar.conf.sample b/etc/tuskar/tuskar.conf.sample deleted file mode 100644 index 5f1fbb85..00000000 --- a/etc/tuskar/tuskar.conf.sample +++ /dev/null @@ -1,484 +0,0 @@ -[DEFAULT] - -# -# Options defined in tuskar.netconf -# - -# ip address of this host (string value) -#my_ip=10.0.0.1 - -# use ipv6 (boolean value) -#use_ipv6=false - - -# -# Options defined in tuskar.api -# - -# IP for the Tuskar API server to bind to (string value) -#tuskar_api_bind_ip=0.0.0.0 - -# The port for the Tuskar API server (integer value) -#tuskar_api_port=8585 - -# Local path holding tripleo-heat-templates (string value) -#tht_local_dir=/etc/tuskar/tripleo-heat-templates/ - - -# -# Options defined in tuskar.api.app -# - -# Method to use for auth: noauth or keystone. (string value) -#auth_strategy=keystone - - -# -# Options defined in tuskar.common.exception -# - -# make exception message format errors fatal (boolean value) -#fatal_exception_format_errors=false - - -# -# Options defined in tuskar.common.paths -# - -# Directory where the nova python module is installed (string -# value) -#pybasedir=/usr/lib/python/site-packages/tuskar - -# Directory where nova binaries are installed (string value) -#bindir=$pybasedir/bin - -# Top-level directory for maintaining nova's state (string -# value) -#state_path=$pybasedir - - -# -# Options defined in tuskar.db.sqlalchemy.models -# - -# MySQL engine (string value) -#mysql_engine=InnoDB - - -# -# Options defined in tuskar.openstack.common.log -# - -# Print debugging output (set logging level to DEBUG instead -# of default WARNING level). (boolean value) -#debug=false - -# Print more verbose output (set logging level to INFO instead -# of default WARNING level). (boolean value) -#verbose=false - -# Log output to standard error (boolean value) -#use_stderr=true - -# Format string to use for log messages with context (string -# value) -#logging_context_format_string=%(asctime)s.%(msecs)03d %(process)d %(levelname)s %(name)s [%(request_id)s %(user_identity)s] %(instance)s%(message)s - -# Format string to use for log messages without context -# (string value) -#logging_default_format_string=%(asctime)s.%(msecs)03d %(process)d %(levelname)s %(name)s [-] %(instance)s%(message)s - -# Data to append to log format when level is DEBUG (string -# value) -#logging_debug_format_suffix=%(funcName)s %(pathname)s:%(lineno)d - -# Prefix each line of exception output with this format -# (string value) -#logging_exception_prefix=%(asctime)s.%(msecs)03d %(process)d TRACE %(name)s %(instance)s - -# List of logger=LEVEL pairs (list value) -#default_log_levels=amqp=WARN,amqplib=WARN,boto=WARN,qpid=WARN,sqlalchemy=WARN,suds=INFO,oslo.messaging=INFO,iso8601=WARN,requests.packages.urllib3.connectionpool=WARN - -# Publish error events (boolean value) -#publish_errors=false - -# Make deprecations fatal (boolean value) -#fatal_deprecations=false - -# If an instance is passed with the log message, format it -# like this (string value) -#instance_format="[instance: %(uuid)s] " - -# If an instance UUID is passed with the log message, format -# it like this (string value) -#instance_uuid_format="[instance: %(uuid)s] " - -# The name of logging configuration file. It does not disable -# existing loggers, but just appends specified logging -# configuration to any other existing logging options. Please -# see the Python logging module documentation for details on -# logging configuration files. (string value) -# Deprecated group/name - [DEFAULT]/log_config -#log_config_append= - -# DEPRECATED. A logging.Formatter log message format string -# which may use any of the available logging.LogRecord -# attributes. This option is deprecated. Please use -# logging_context_format_string and -# logging_default_format_string instead. (string value) -#log_format= - -# Format string for %%(asctime)s in log records. Default: -# %(default)s (string value) -#log_date_format=%Y-%m-%d %H:%M:%S - -# (Optional) Name of log file to output to. If no default is -# set, logging will go to stdout. (string value) -# Deprecated group/name - [DEFAULT]/logfile -#log_file= - -# (Optional) The base directory used for relative --log-file -# paths (string value) -# Deprecated group/name - [DEFAULT]/logdir -#log_dir= - -# Use syslog for logging. Existing syslog format is DEPRECATED -# during I, and then will be changed in J to honor RFC5424 -# (boolean value) -#use_syslog=false - -# (Optional) Use syslog rfc5424 format for logging. If -# enabled, will add APP-NAME (RFC5424) before the MSG part of -# the syslog message. The old format without APP-NAME is -# deprecated in I, and will be removed in J. (boolean value) -#use_syslog_rfc_format=false - -# Syslog facility to receive log lines (string value) -#syslog_log_facility=LOG_USER - - -# -# Options defined in tuskar.storage.drivers.sqlalchemy -# - -# MySQL engine (string value) -#mysql_engine=InnoDB - - -[database] - -# -# Options defined in oslo.db -# - -# The file name to use with SQLite. (string value) -#sqlite_db=oslo.sqlite - -# If True, SQLite uses synchronous mode. (boolean value) -#sqlite_synchronous=true - -# The back end to use for the database. (string value) -# Deprecated group/name - [DEFAULT]/db_backend -#backend=sqlalchemy - -# The SQLAlchemy connection string to use to connect to the -# database. (string value) -# Deprecated group/name - [DEFAULT]/sql_connection -# Deprecated group/name - [DATABASE]/sql_connection -# Deprecated group/name - [sql]/connection -#connection= - -# The SQLAlchemy connection string to use to connect to the -# slave database. (string value) -#slave_connection= - -# The SQL mode to be used for MySQL sessions. This option, -# including the default, overrides any server-set SQL mode. To -# use whatever SQL mode is set by the server configuration, -# set this to no value. Example: mysql_sql_mode= (string -# value) -#mysql_sql_mode=TRADITIONAL - -# Timeout before idle SQL connections are reaped. (integer -# value) -# Deprecated group/name - [DEFAULT]/sql_idle_timeout -# Deprecated group/name - [DATABASE]/sql_idle_timeout -# Deprecated group/name - [sql]/idle_timeout -#idle_timeout=3600 - -# Minimum number of SQL connections to keep open in a pool. -# (integer value) -# Deprecated group/name - [DEFAULT]/sql_min_pool_size -# Deprecated group/name - [DATABASE]/sql_min_pool_size -#min_pool_size=1 - -# Maximum number of SQL connections to keep open in a pool. -# (integer value) -# Deprecated group/name - [DEFAULT]/sql_max_pool_size -# Deprecated group/name - [DATABASE]/sql_max_pool_size -#max_pool_size= - -# Maximum number of database connection retries during -# startup. Set to -1 to specify an infinite retry count. -# (integer value) -# Deprecated group/name - [DEFAULT]/sql_max_retries -# Deprecated group/name - [DATABASE]/sql_max_retries -#max_retries=10 - -# Interval between retries of opening a SQL connection. -# (integer value) -# Deprecated group/name - [DEFAULT]/sql_retry_interval -# Deprecated group/name - [DATABASE]/reconnect_interval -#retry_interval=10 - -# If set, use this value for max_overflow with SQLAlchemy. -# (integer value) -# Deprecated group/name - [DEFAULT]/sql_max_overflow -# Deprecated group/name - [DATABASE]/sqlalchemy_max_overflow -#max_overflow= - -# Verbosity of SQL debugging information: 0=None, -# 100=Everything. (integer value) -# Deprecated group/name - [DEFAULT]/sql_connection_debug -#connection_debug=0 - -# Add Python stack traces to SQL as comment strings. (boolean -# value) -# Deprecated group/name - [DEFAULT]/sql_connection_trace -#connection_trace=false - -# If set, use this value for pool_timeout with SQLAlchemy. -# (integer value) -# Deprecated group/name - [DATABASE]/sqlalchemy_pool_timeout -#pool_timeout= - -# Enable the experimental use of database reconnect on -# connection lost. (boolean value) -#use_db_reconnect=false - -# Seconds between retries of a database transaction. (integer -# value) -#db_retry_interval=1 - -# If True, increases the interval between retries of a -# database operation up to db_max_retry_interval. (boolean -# value) -#db_inc_retry_interval=true - -# If db_inc_retry_interval is set, the maximum seconds between -# retries of a database operation. (integer value) -#db_max_retry_interval=10 - -# Maximum retries in case of connection error or deadlock -# error before error is raised. Set to -1 to specify an -# infinite retry count. (integer value) -#db_max_retries=20 - - -[heat] - -# -# Options defined in tuskar.heat.client -# - -# Name of the overcloud Heat stack (string value) -#stack_name=overcloud - -# Heat API service type registered in keystone (string value) -#service_type=orchestration - -# Heat API service endpoint type in keystone (string value) -#endpoint_type=publicURL - - -[heat_keystone] - -# -# Options defined in tuskar.heat.client -# - -# The name of a user the overcloud is deployed on behalf of -# (string value) -#username=admin - -# The pass of a user the overcloud is deployed on behalf of -# (string value) -#password= - -# The tenant name the overcloud is deployed on behalf of -# (string value) -#tenant_name=admin - -# Keystone authentication URL (string value) -#auth_url=http://localhost:35357/v2.0 - -# Set to False when Heat API uses HTTPS (boolean value) -#insecure=true - - -[keystone_authtoken] - -# -# Options defined in keystoneclient.middleware.auth_token -# - -# Prefix to prepend at the beginning of the path. Deprecated, -# use identity_uri. (string value) -#auth_admin_prefix= - -# Host providing the admin Identity API endpoint. Deprecated, -# use identity_uri. (string value) -#auth_host=127.0.0.1 - -# Port of the admin Identity API endpoint. Deprecated, use -# identity_uri. (integer value) -#auth_port=35357 - -# Protocol of the admin Identity API endpoint (http or https). -# Deprecated, use identity_uri. (string value) -#auth_protocol=https - -# Complete public Identity API endpoint (string value) -#auth_uri= - -# Complete admin Identity API endpoint. This should specify -# the unversioned root endpoint e.g. https://localhost:35357/ -# (string value) -#identity_uri= - -# API version of the admin Identity API endpoint (string -# value) -#auth_version= - -# Do not handle authorization requests within the middleware, -# but delegate the authorization decision to downstream WSGI -# components (boolean value) -#delay_auth_decision=false - -# Request timeout value for communicating with Identity API -# server. (boolean value) -#http_connect_timeout= - -# How many times are we trying to reconnect when communicating -# with Identity API Server. (integer value) -#http_request_max_retries=3 - -# This option is deprecated and may be removed in a future -# release. Single shared secret with the Keystone -# configuration used for bootstrapping a Keystone -# installation, or otherwise bypassing the normal -# authentication process. This option should not be used, use -# `admin_user` and `admin_password` instead. (string value) -#admin_token= - -# Keystone account username (string value) -#admin_user= - -# Keystone account password (string value) -#admin_password= - -# Keystone service account tenant name to validate user tokens -# (string value) -#admin_tenant_name=admin - -# Env key for the swift cache (string value) -#cache= - -# Required if Keystone server requires client certificate -# (string value) -#certfile= - -# Required if Keystone server requires client certificate -# (string value) -#keyfile= - -# A PEM encoded Certificate Authority to use when verifying -# HTTPs connections. Defaults to system CAs. (string value) -#cafile= - -# Verify HTTPS connections. (boolean value) -#insecure=false - -# Directory used to cache files related to PKI tokens (string -# value) -#signing_dir= - -# Optionally specify a list of memcached server(s) to use for -# caching. If left undefined, tokens will instead be cached -# in-process. (list value) -# Deprecated group/name - [DEFAULT]/memcache_servers -#memcached_servers= - -# In order to prevent excessive effort spent validating -# tokens, the middleware caches previously-seen tokens for a -# configurable duration (in seconds). Set to -1 to disable -# caching completely. (integer value) -#token_cache_time=300 - -# Determines the frequency at which the list of revoked tokens -# is retrieved from the Identity service (in seconds). A high -# number of revocation events combined with a low cache -# duration may significantly reduce performance. (integer -# value) -#revocation_cache_time=10 - -# (optional) if defined, indicate whether token data should be -# authenticated or authenticated and encrypted. Acceptable -# values are MAC or ENCRYPT. If MAC, token data is -# authenticated (with HMAC) in the cache. If ENCRYPT, token -# data is encrypted and authenticated in the cache. If the -# value is not one of these options or empty, auth_token will -# raise an exception on initialization. (string value) -#memcache_security_strategy= - -# (optional, mandatory if memcache_security_strategy is -# defined) this string is used for key derivation. (string -# value) -#memcache_secret_key= - -# (optional) indicate whether to set the X-Service-Catalog -# header. If False, middleware will not ask for service -# catalog on token validation and will not set the X-Service- -# Catalog header. (boolean value) -#include_service_catalog=true - -# Used to control the use and type of token binding. Can be -# set to: "disabled" to not check token binding. "permissive" -# (default) to validate binding information if the bind type -# is of a form known to the server and ignore it if not. -# "strict" like "permissive" but if the bind type is unknown -# the token will be rejected. "required" any form of token -# binding is needed to be allowed. Finally the name of a -# binding method that must be present in tokens. (string -# value) -#enforce_token_bind=permissive - -# If true, the revocation list will be checked for cached -# tokens. This requires that PKI tokens are configured on the -# Keystone server. (boolean value) -#check_revocations_for_cached=false - -# Hash algorithms to use for hashing PKI tokens. This may be a -# single algorithm or multiple. The algorithms are those -# supported by Python standard hashlib.new(). The hashes will -# be tried in the order given, so put the preferred one first -# for performance. The result of the first hash will be stored -# in the cache. This will typically be set to multiple values -# only while migrating from a less secure algorithm to a more -# secure one. Once all the old tokens are expired this option -# should be set to a single value for better performance. -# (list value) -#hash_algorithms=md5 - - -[storage] - -# -# Options defined in tuskar.storage -# - -# Storage driver to store Deployment Plans and Heat -# Orchestration Templates (string value) -#driver=tuskar.storage.drivers.sqlalchemy.SQLAlchemyDriver - - diff --git a/openstack-common.conf b/openstack-common.conf deleted file mode 100644 index 92dd1881..00000000 --- a/openstack-common.conf +++ /dev/null @@ -1,21 +0,0 @@ -[DEFAULT] -module=config.generator -module=db -module=db.sqlalchemy -module=excutils -module=fileutils -module=gettextutils -module=importutils -module=jsonutils -module=local -module=lockutils -module=log -module=policy -module=strutils -module=timeutils - -# Tools -script=tools/install_venv_common - -# The base module to hold the copy of openstack.common -base=tuskar diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 586f2ddf..00000000 --- a/requirements.txt +++ /dev/null @@ -1,27 +0,0 @@ -# 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 - -anyjson>=0.3.3 -Babel>=1.3 -# The egg=tripleo-heat-templates suffix is needed by pbr's requirements -# processing code -http://tarballs.openstack.org/tripleo-heat-templates/tripleo-heat-templates-master.tar.gz#egg=tripleo_heat_templates -eventlet>=0.17.4 -greenlet>=0.3.2 -iso8601>=0.1.9 -kombu>=3.0.7 -lxml>=2.3 -oslo.config>=2.3.0 # Apache-2.0 -oslo.db>=2.4.1 # Apache-2.0 -pecan>=1.0.0 -posix-ipc -python-heatclient>=0.3.0 -python-keystoneclient>=1.6.0 -PyYAML>=3.1.0 -six>=1.9.0 -SQLAlchemy<1.1.0,>=0.9.9 -sqlalchemy-migrate>=0.9.6 -WebOb>=1.2.3 -WSME>=0.7 diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 2a82e913..00000000 --- a/setup.cfg +++ /dev/null @@ -1,55 +0,0 @@ -[metadata] -name = tuskar -version = 2013.2 -summary = An OpenStack Management Service -description-file = - README.rst -author = Mark McLoughlin -author-email = markmc@redhat.com -home-page = http://git.openstack.org/cgit/openstack/tuskar -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 - -[files] -packages = - tuskar - -[entry_points] -console_scripts = - tuskar-api = tuskar.cmd.api:main - tuskar-dbsync = tuskar.cmd.dbsync:main - tuskar-load-roles = tuskar.cmd.load_roles:main - tuskar-load-seed = tuskar.cmd.load_seed:main - tuskar-delete-roles = tuskar.cmd.delete_roles:main - tuskar-load-role = tuskar.cmd.load_role:main - -[build_sphinx] -all_files = 1 -build-dir = doc/build -source-dir = doc/source - -[egg_info] -tag_build = -tag_date = 0 -tag_svn_revision = 0 - -[compile_catalog] -directory = tuskar/locale -domain = tuskar - -[update_catalog] -domain = tuskar -output_dir = tuskar/locale -input_file = tuskar/locale/tuskar.pot - -[extract_messages] -keywords = _ gettext ngettext l_ lazy_gettext -mapping_file = babel.cfg -output_file = tuskar/locale/tuskar.pot diff --git a/setup.py b/setup.py deleted file mode 100644 index 782bb21f..00000000 --- a/setup.py +++ /dev/null @@ -1,29 +0,0 @@ -# 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/test-requirements.txt b/test-requirements.txt deleted file mode 100644 index ee050b2a..00000000 --- a/test-requirements.txt +++ /dev/null @@ -1,20 +0,0 @@ -# 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 already pins down pep8, pyflakes and flake8 -hacking<0.10,>=0.9.2 - -coverage>=3.6 -discover -fixtures>=1.3.1 -mock>=1.2 -oslotest>=1.10.0 # Apache-2.0 -# Doc requirements -sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2 -sphinxcontrib-pecanwsme>=0.8 -stevedore>=1.5.0 # Apache-2.0 -testrepository>=0.0.18 -testtools>=1.4.0 -unittest2 -psycopg2>=2.5 -PyMySQL>=0.6.2 # MIT License diff --git a/tools/__init__.py b/tools/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tools/config/check_uptodate.sh b/tools/config/check_uptodate.sh deleted file mode 100755 index a64b5ed0..00000000 --- a/tools/config/check_uptodate.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env bash - -PROJECT_NAME=${PROJECT_NAME:-tuskar} -CFGFILE_NAME=${PROJECT_NAME}.conf.sample - -if [ -e etc/${PROJECT_NAME}/${CFGFILE_NAME} ]; then - CFGFILE=etc/${PROJECT_NAME}/${CFGFILE_NAME} -elif [ -e etc/${CFGFILE_NAME} ]; then - CFGFILE=etc/${CFGFILE_NAME} -else - echo "${0##*/}: can not find config file" - exit 1 -fi - -TEMPDIR=`mktemp -d /tmp/${PROJECT_NAME}.XXXXXX` -trap "rm -rf $TEMPDIR" EXIT - -tools/config/generate_sample.sh -b ./ -p ${PROJECT_NAME} -o ${TEMPDIR} - -if ! diff -u ${TEMPDIR}/${CFGFILE_NAME} ${CFGFILE} -then - echo "${0##*/}: ${PROJECT_NAME}.conf.sample is not up to date." - echo "${0##*/}: Please run ${0%%${0##*/}}generate_sample.sh." - exit 1 -fi diff --git a/tools/config/generate_sample.sh b/tools/config/generate_sample.sh deleted file mode 100755 index 4b6cd9b8..00000000 --- a/tools/config/generate_sample.sh +++ /dev/null @@ -1,119 +0,0 @@ -#!/usr/bin/env bash - -print_hint() { - echo "Try \`${0##*/} --help' for more information." >&2 -} - -PARSED_OPTIONS=$(getopt -n "${0##*/}" -o hb:p:m:l:o: \ - --long help,base-dir:,package-name:,output-dir:,module:,library: -- "$@") - -if [ $? != 0 ] ; then print_hint ; exit 1 ; fi - -eval set -- "$PARSED_OPTIONS" - -while true; do - case "$1" in - -h|--help) - echo "${0##*/} [options]" - echo "" - echo "options:" - echo "-h, --help show brief help" - echo "-b, --base-dir=DIR project base directory" - echo "-p, --package-name=NAME project package name" - echo "-o, --output-dir=DIR file output directory" - echo "-m, --module=MOD extra python module to interrogate for options" - echo "-l, --library=LIB extra library that registers options for discovery" - exit 0 - ;; - -b|--base-dir) - shift - BASEDIR=`echo $1 | sed -e 's/\/*$//g'` - shift - ;; - -p|--package-name) - shift - PACKAGENAME=`echo $1` - shift - ;; - -o|--output-dir) - shift - OUTPUTDIR=`echo $1 | sed -e 's/\/*$//g'` - shift - ;; - -m|--module) - shift - MODULES="$MODULES -m $1" - shift - ;; - -l|--library) - shift - LIBRARIES="$LIBRARIES -l $1" - shift - ;; - --) - break - ;; - esac -done - -BASEDIR=${BASEDIR:-`pwd`} -if ! [ -d $BASEDIR ] -then - echo "${0##*/}: missing project base directory" >&2 ; print_hint ; exit 1 -elif [[ $BASEDIR != /* ]] -then - BASEDIR=$(cd "$BASEDIR" && pwd) -fi - -PACKAGENAME=${PACKAGENAME:-${BASEDIR##*/}} -TARGETDIR=$BASEDIR/$PACKAGENAME -if ! [ -d $TARGETDIR ] -then - echo "${0##*/}: invalid project package name" >&2 ; print_hint ; exit 1 -fi - -OUTPUTDIR=${OUTPUTDIR:-$BASEDIR/etc} -# NOTE(bnemec): Some projects put their sample config in etc/, -# some in etc/$PACKAGENAME/ -if [ -d $OUTPUTDIR/$PACKAGENAME ] -then - OUTPUTDIR=$OUTPUTDIR/$PACKAGENAME -elif ! [ -d $OUTPUTDIR ] -then - echo "${0##*/}: cannot access \`$OUTPUTDIR': No such file or directory" >&2 - exit 1 -fi - -BASEDIRESC=`echo $BASEDIR | sed -e 's/\//\\\\\//g'` -find $TARGETDIR -type f -name "*.pyc" -delete -FILES=$(find $TARGETDIR -type f -name "*.py" ! -path "*/tests/*" \ - -exec grep -l "Opt(" {} + | sed -e "s/^$BASEDIRESC\///g" | sort -u) - -RC_FILE="`dirname $0`/oslo.config.generator.rc" -if test -r "$RC_FILE" -then - source "$RC_FILE" -fi - -for mod in ${TUSKAR_CONFIG_GENERATOR_EXTRA_MODULES}; do - MODULES="$MODULES -m $mod" -done - -for lib in ${TUSKAR_CONFIG_GENERATOR_EXTRA_LIBRARIES}; do - LIBRARIES="$LIBRARIES -l $lib" -done - -export EVENTLET_NO_GREENDNS=yes - -OS_VARS=$(set | sed -n '/^OS_/s/=[^=]*$//gp' | xargs) -[ "$OS_VARS" ] && eval "unset \$OS_VARS" -DEFAULT_CONFIG_GENERATOR=tuskar.openstack.common.config.generator -CONFIG_GENERATOR=${CONFIG_GENERATOR:-$DEFAULT_CONFIG_GENERATOR} -OUTPUTFILE=$OUTPUTDIR/$PACKAGENAME.conf.sample -python -m $CONFIG_GENERATOR $MODULES $LIBRARIES $FILES > $OUTPUTFILE - -# Hook to allow projects to append custom config file snippets -CONCAT_FILES=$(ls $BASEDIR/tools/config/*.conf.sample 2>/dev/null) -for CONCAT_FILE in $CONCAT_FILES; do - cat $CONCAT_FILE >> $OUTPUTFILE -done diff --git a/tools/config/oslo.config.generator.rc b/tools/config/oslo.config.generator.rc deleted file mode 100644 index 711c219e..00000000 --- a/tools/config/oslo.config.generator.rc +++ /dev/null @@ -1,2 +0,0 @@ -export TUSKAR_CONFIG_GENERATOR_EXTRA_MODULES=keystoneclient.middleware.auth_token -export TUSKAR_CONFIG_GENERATOR_EXTRA_LIBRARIES=oslo.db diff --git a/tools/initial_data.py b/tools/initial_data.py deleted file mode 100644 index da5b4650..00000000 --- a/tools/initial_data.py +++ /dev/null @@ -1,138 +0,0 @@ -#!/usr/bin/env 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. - -# Using the REST API, populates the DB with some sample data -# Based on python-ceilometerclient/ceilometerclient/common/http.py - -import httplib -import json -import logging -import socket -import six.moves.urllib.parse as urlparse -import uuid - - -LOG = logging.getLogger(__name__) - - -def log_curl_request(conn, base_url, url, method, kwargs): - curl = ['curl -i -X %s' % method] - - for (key, value) in kwargs['headers'].items(): - header = '-H \'%s: %s\'' % (key, value) - curl.append(header) - - if 'body' in kwargs: - curl.append('-d \'%s\'' % kwargs['body']) - - curl.append('http://%s:%d%s%s' % (conn.host, conn.port, base_url, url)) - LOG.debug(' '.join(curl)) - - -def log_http_response(resp, body=None): - status = (resp.version / 10.0, resp.status, resp.reason) - dump = ['\nHTTP/%.1f %s %s' % status] - dump.extend(['%s: %s' % (k, v) for k, v in resp.getheaders()]) - dump.append('') - if body: - dump.extend([body, '']) - LOG.debug('\n'.join(dump)) - - -def make_connection_url(base_url, url): - return '%s/%s' % (base_url.rstrip('/'), url.lstrip('/')) - - -def http_request(conn, base_url, url, method, **kwargs): - log_curl_request(conn, base_url, url, method, kwargs) - - try: - conn_url = make_connection_url(base_url, url) - conn.request(method, conn_url, **kwargs) - resp = conn.getresponse() - except socket.gaierror as e: - message = ('Error finding address for %(url)s: %(e)s' % - {'url': url, 'e': e}) - raise RuntimeError(message) - except (socket.error, socket.timeout) as e: - message = ('Error communicating with %(endpoint)s %(e)s' % - {'endpoint': 'http://%s:%d' % (conn.host, conn.port), - 'e': e}) - raise RuntimeError(message) - - body = resp.read() - log_http_response(resp, body) - - if 300 <= resp.status < 600: - LOG.warn('Request returned failure/redirect status.') - raise RuntimeError('Status code %d returned' % resp.status) - - return resp, body - - -def json_request(conn, base_url, url, method, **kwargs): - kwargs.setdefault('headers', {}) - kwargs['headers'].setdefault('Content-Type', 'application/json') - kwargs['headers'].setdefault('Accept', 'application/json') - - if 'body' in kwargs: - kwargs['body'] = json.dumps(kwargs['body']) - - resp, body = http_request(conn, base_url, url, method, **kwargs) - content_type = resp.getheader('content-type', None) - - if resp.status == 204 or resp.status == 205 or content_type is None: - body = None - elif 'application/json' in content_type: - try: - body = json.loads(body) - except ValueError: - LOG.error('Could not decode response body as JSON') - else: - body = None - - return resp, body - - -def create_overcloud_role(conn, base_url, name, description, image_name): - return json_request(conn, base_url, '/overcloud_roles', 'POST', - body=dict(name=name, description=description, - image_name=image_name)) - - -def generate_data(): - conn = httplib.HTTPConnection('localhost', 8585) - base_url = '/v1' - - create_overcloud_role(conn, base_url, - name='Controller', - description='controller role', - image_name='overcloud-control') - create_overcloud_role(conn, base_url, - name='Compute', - description='compute role', - image_name='overcloud-compute') - create_overcloud_role(conn, base_url, - name='Block Storage', - description='block storage role', - image_name='overcloud-cinder-volume') - create_overcloud_role(conn, base_url, - name='Object Storage', - description='object storage role', - image_name='overcloud-swift-storage') - - -if __name__ == '__main__': - logging.basicConfig(format='%(message)s', level=logging.DEBUG) - generate_data() diff --git a/tools/install_venv_common.py b/tools/install_venv_common.py deleted file mode 100644 index faa9796a..00000000 --- a/tools/install_venv_common.py +++ /dev/null @@ -1,182 +0,0 @@ -# Copyright 2013 OpenStack Foundation -# Copyright 2013 IBM Corp. -# -# 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. - -"""Provides methods needed by installation script for OpenStack development -virtual environments. - -Since this script is used to bootstrap a virtualenv from the system's Python -environment, it should be kept strictly compatible with Python 2.6. - -Synced in from openstack-common -""" - -from __future__ import print_function - -import optparse -import os -import subprocess -import sys - - -class InstallVenv(object): - - def __init__(self, root, venv, pip_requires, test_requires, py_version, - project): - self.root = root - self.venv = venv - self.pip_requires = pip_requires - self.test_requires = test_requires - self.py_version = py_version - self.project = project - - def die(self, message, *args): - print(message % args, file=sys.stderr) - sys.exit(1) - - def check_python_version(self): - if sys.version_info < (2, 6): - self.die("Need Python Version >= 2.6") - - def run_command_with_code(self, cmd, redirect_output=True, - check_exit_code=True): - """Runs a command in an out-of-process shell. - - Returns the output of that command. Working directory is self.root. - """ - if redirect_output: - stdout = subprocess.PIPE - else: - stdout = None - - proc = subprocess.Popen(cmd, cwd=self.root, stdout=stdout) - output = proc.communicate()[0] - if check_exit_code and proc.returncode != 0: - self.die('Command "%s" failed.\n%s', ' '.join(cmd), output) - return (output, proc.returncode) - - def run_command(self, cmd, redirect_output=True, check_exit_code=True): - return self.run_command_with_code(cmd, redirect_output, - check_exit_code)[0] - - def get_distro(self): - if (os.path.exists('/etc/fedora-release') or - os.path.exists('/etc/redhat-release')): - return Fedora(self.root, self.venv, self.pip_requires, - self.test_requires, self.py_version, self.project) - else: - return Distro(self.root, self.venv, self.pip_requires, - self.test_requires, self.py_version, self.project) - - def check_dependencies(self): - self.get_distro().install_virtualenv() - - def create_virtualenv(self, no_site_packages=True): - """Creates the virtual environment and installs PIP. - - Creates the virtual environment and installs PIP only into the - virtual environment. - """ - if not os.path.isdir(self.venv): - print('Creating venv...', end=' ') - if no_site_packages: - self.run_command(['virtualenv', '-q', '--no-site-packages', - self.venv]) - else: - self.run_command(['virtualenv', '-q', self.venv]) - print('done.') - print('Installing pip in venv...', end=' ') - if not self.run_command(['tools/with_venv.sh', 'easy_install', - 'pip>1.0']).strip(): - self.die("Failed to install pip.") - print('done.') - else: - print("venv already exists...") - pass - - def pip_install(self, *args): - self.run_command(['tools/with_venv.sh', - 'pip', 'install', '--upgrade'] + list(args), - redirect_output=False) - - def install_dependencies(self): - print('Installing dependencies with pip (this can take a while)...') - - # First things first, make sure our venv has the latest pip and - # distribute. - # NOTE: we keep pip at version 1.1 since the most recent version causes - # the .venv creation to fail. See: - # https://bugs.launchpad.net/nova/+bug/1047120 - self.pip_install('pip==1.1') - self.pip_install('distribute') - - # Install greenlet by hand - just listing it in the requires file does - # not - # get it installed in the right order - self.pip_install('greenlet') - - self.pip_install('-r', self.pip_requires) - self.pip_install('-r', self.test_requires) - - def parse_args(self, argv): - """Parses command-line arguments.""" - parser = optparse.OptionParser() - parser.add_option('-n', '--no-site-packages', - action='store_true', - help="Do not inherit packages from global Python " - "install") - return parser.parse_args(argv[1:])[0] - - -class Distro(InstallVenv): - - def check_cmd(self, cmd): - return bool(self.run_command(['which', cmd], - check_exit_code=False).strip()) - - def install_virtualenv(self): - if self.check_cmd('virtualenv'): - return - - if self.check_cmd('easy_install'): - print('Installing virtualenv via easy_install...', end=' ') - if self.run_command(['easy_install', 'virtualenv']): - print('Succeeded') - return - else: - print('Failed') - - self.die('ERROR: virtualenv not found.\n\n%s development' - ' requires virtualenv, please install it using your' - ' favorite package management tool' % self.project) - - -class Fedora(Distro): - """This covers all Fedora-based distributions. - - Includes: Fedora, RHEL, CentOS, Scientific Linux - """ - - def check_pkg(self, pkg): - return self.run_command_with_code(['rpm', '-q', pkg], - check_exit_code=False)[1] == 0 - - def install_virtualenv(self): - if self.check_cmd('virtualenv'): - return - - if not self.check_pkg('python-virtualenv'): - self.die("Please install 'python-virtualenv'.") - - super(Fedora, self).install_virtualenv() diff --git a/tools/with_venv.sh b/tools/with_venv.sh deleted file mode 100755 index 262b86d4..00000000 --- a/tools/with_venv.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash -tools_path=${tools_path:-$(dirname $0)} -venv_path=${venv_path:-${tools_path}} -tox_env=$(cd ${venv_path} && find ../.tox -maxdepth 1 -name "py*" | sort | tail -n1) -venv_dir=${venv_name:-${tox_env}} -TOOLS=${tools_path} -VENV=${venv:-${venv_path}/${venv_dir}} -source ${VENV}/bin/activate && "$@" diff --git a/tox.ini b/tox.ini deleted file mode 100644 index 09a4eb67..00000000 --- a/tox.ini +++ /dev/null @@ -1,36 +0,0 @@ -[tox] -minversion = 1.6 -skipsdist = True -envlist = py27,pep8 - -[testenv] -usedevelop = True -install_command = pip install {opts} {packages} -setenv = VIRTUAL_ENV={envdir} -deps = -r{toxinidir}/requirements.txt - -r{toxinidir}/test-requirements.txt -commands = - python setup.py testr --slowest --testr-args='{posargs}' - {toxinidir}/tools/config/check_uptodate.sh - -[tox:jenkins] -downloadcache = ~/cache/pip - -[testenv:pep8] -commands = - flake8 - -[testenv:cover] -setenv = VIRTUAL_ENV={envdir} -commands = - python setup.py testr --coverage {posargs} - -[testenv:venv] -commands = {posargs} - -[flake8] -# H302 import only modules. -# H405 multi line docstring summary not separated with an empty line -ignore = H302,H405 -builtins = _ -exclude = .venv,.git,.tox,dist,doc,*openstack/common*,*lib/python*,*egg,build,tools diff --git a/tuskar/__init__.py b/tuskar/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tuskar/api/__init__.py b/tuskar/api/__init__.py deleted file mode 100644 index ede13b70..00000000 --- a/tuskar/api/__init__.py +++ /dev/null @@ -1,37 +0,0 @@ -# Copyright 2013 Hewlett-Packard Development Company, L.P. -# All Rights Reserved. -# -# 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 oslo_config import cfg - -API_SERVICE_OPTS = [ - cfg.StrOpt( - 'tuskar_api_bind_ip', - default='0.0.0.0', - help='IP for the Tuskar API server to bind to', - ), - cfg.IntOpt( - 'tuskar_api_port', - default=8585, - help='The port for the Tuskar API server', - ), - cfg.StrOpt( - 'tht_local_dir', - default='/etc/tuskar/tripleo-heat-templates/', - help='Local path holding tripleo-heat-templates', - ) -] - -CONF = cfg.CONF -CONF.register_opts(API_SERVICE_OPTS) diff --git a/tuskar/api/acl.py b/tuskar/api/acl.py deleted file mode 100644 index b2e20289..00000000 --- a/tuskar/api/acl.py +++ /dev/null @@ -1,36 +0,0 @@ -# -# Copyright 2012 New Dream Network, LLC (DreamHost) -# -# 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. - -"""Access Control Lists (ACL's) control access the API server.""" - -from keystoneclient.middleware import auth_token -from oslo_config import cfg - - -OPT_GROUP_NAME = 'keystone_authtoken' - - -def register_opts(conf): - """Register keystoneclient middleware options - """ - conf.register_opts(auth_token.opts, group=OPT_GROUP_NAME) - auth_token.CONF = conf - - -def install(app, conf): - """Install ACL check on application.""" - register_opts(cfg.CONF) - return auth_token.AuthProtocol(app, - conf=dict(conf.get(OPT_GROUP_NAME))) diff --git a/tuskar/api/app.py b/tuskar/api/app.py deleted file mode 100644 index 4fc6c547..00000000 --- a/tuskar/api/app.py +++ /dev/null @@ -1,77 +0,0 @@ - -# Copyright 2012 New Dream Network, LLC (DreamHost) -# All Rights Reserved. -# -# 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 oslo_config import cfg -import pecan - -from tuskar.api import acl -from tuskar.api import config -from tuskar.api import hooks -from tuskar.api import renderers - -auth_opts = [ - cfg.StrOpt( - 'auth_strategy', - default='keystone', - help='Method to use for auth: noauth or keystone.'), -] - -CONF = cfg.CONF -CONF.register_opts(auth_opts) - - -def get_pecan_config(): - # Set up the pecan configuration - filename = config.__file__.replace('.pyc', '.py') - return pecan.configuration.conf_from_file(filename) - - -def setup_app(pecan_config=None, extra_hooks=None): - app_hooks = [hooks.ConfigHook(), - hooks.DBHook()] - if extra_hooks: - app_hooks.extend(extra_hooks) - - if not pecan_config: - pecan_config = get_pecan_config() - - pecan.configuration.set_config(dict(pecan_config), overwrite=True) - - # TODO(deva): add middleware.ParsableErrorMiddleware from Ceilometer - app = pecan.make_app( - pecan_config.app.root, - custom_renderers=dict(wsmejson=renderers.JSONRenderer), - static_root=pecan_config.app.static_root, - template_path=pecan_config.app.template_path, - debug=CONF.debug, - force_canonical=getattr(pecan_config.app, 'force_canonical', True), - hooks=app_hooks, - ) - - if pecan_config.app.enable_acl: - return acl.install(app, cfg.CONF) - - return app - - -class VersionSelectorApplication(object): - def __init__(self): - pc = get_pecan_config() - pc.app.enable_acl = (CONF.auth_strategy == 'keystone') - self.v1 = setup_app(pecan_config=pc) - - def __call__(self, environ, start_response): - return self.v1(environ, start_response) diff --git a/tuskar/api/config.py b/tuskar/api/config.py deleted file mode 100644 index 8377f272..00000000 --- a/tuskar/api/config.py +++ /dev/null @@ -1,35 +0,0 @@ -# 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. - -# Server Specific Configurations -server = { - 'port': '6382', - 'host': '0.0.0.0' -} - -# Pecan Application Configurations -app = { - 'root': 'tuskar.api.controllers.root.RootController', - 'modules': ['tuskar.api'], - 'static_root': '%(confdir)s/public', - 'template_path': '%(confdir)s/templates', - 'debug': False, - 'enable_acl': False, -} - -# Custom Configurations must be in Python dictionary format:: -# -# foo = {'bar':'baz'} -# -# All configurations are accessible at:: -# pecan.conf diff --git a/tuskar/api/controllers/__init__.py b/tuskar/api/controllers/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tuskar/api/controllers/root.py b/tuskar/api/controllers/root.py deleted file mode 100644 index e3635278..00000000 --- a/tuskar/api/controllers/root.py +++ /dev/null @@ -1,50 +0,0 @@ -# -# 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 pecan - -from tuskar.api.controllers.v1 import controller as v1_controller -from tuskar.api.controllers.v2 import controller as v2_controller - - -class RootController(object): - - v1 = v1_controller.Controller() - v2 = v2_controller.Controller() - - @pecan.expose('json') - def index(self): - return { - 'versions': { - 'values': [ - { - 'status': 'development', - 'media-types': [{'base': 'application/json'}], - 'id': 'v1.0', - 'links': [{ - 'href': '/v1/', - 'rel': 'self', - }] - }, - { - 'status': 'development', - 'media-types': [{'base': 'application/json'}], - 'id': 'v2.0', - 'links': [{ - 'href': '/v2/', - 'rel': 'self', - }] - } - ] - } - } diff --git a/tuskar/api/controllers/v1/__init__.py b/tuskar/api/controllers/v1/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tuskar/api/controllers/v1/controller.py b/tuskar/api/controllers/v1/controller.py deleted file mode 100644 index 84b59712..00000000 --- a/tuskar/api/controllers/v1/controller.py +++ /dev/null @@ -1,38 +0,0 @@ -# -# 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 pecan - -from tuskar.api.controllers.v1.overcloud import OvercloudsController -from tuskar.api.controllers.v1.overcloud_roles import OvercloudRolesController - - -class Controller(object): - """Version 1 API controller root.""" - - overcloud_roles = OvercloudRolesController() - overclouds = OvercloudsController() - - @pecan.expose('json') - def index(self): - return { - 'version': { - 'status': 'stable', - 'media-types': [{'base': 'application/json'}], - 'id': 'v1.0', - 'links': [{ - 'href': '/v1/', - 'rel': 'self', - }] - } - } diff --git a/tuskar/api/controllers/v1/models.py b/tuskar/api/controllers/v1/models.py deleted file mode 100644 index 0b193291..00000000 --- a/tuskar/api/controllers/v1/models.py +++ /dev/null @@ -1,160 +0,0 @@ -# -# 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. - -""" -Contains transfer objects for use with WSME REST APIs. The objects in this -module also contain the translations between the REST transfer objects and -the internal Tuskar domain model. -""" - -import logging - -from wsme import types as wtypes - -from tuskar.db.sqlalchemy import models as db_models - - -LOG = logging.getLogger(__name__) - - -class Base(wtypes.Base): - """Base functionality for all API models. - - This class should never be directly instantiated. Subclasses must be sure - to define an attribute named _db_class for the to_db_model to use - when instantiating DB models. - """ - - @classmethod - def from_db_model(cls, db_model, skip_fields=None): - """Returns the database representation of the given transfer object.""" - skip_fields = skip_fields or [] - data = dict((k, v) for k, v in db_model.as_dict().items() - if k not in skip_fields) - return cls(**data) - - def to_db_model(self, omit_unset=False, skip_fields=None): - """Converts this object into its database representation.""" - skip_fields = skip_fields or [] - attribute_names = [a.name for a in self._wsme_attributes - if a.name not in skip_fields] - - if omit_unset: - attribute_names = [n for n in attribute_names - if getattr(self, n) != wtypes.Unset] - - values = dict((name, self._lookup(name)) for name in attribute_names) - db_object = self._db_class(**values) - return db_object - - def _lookup(self, key): - """Looks up a key, translating WSME's Unset into Python's None. - - :return: value of the given attribute; None if it is not set - """ - value = getattr(self, key) - if value == wtypes.Unset: - value = None - return value - - -class OvercloudRole(Base): - """Transfer object for overcloud roles.""" - - _db_class = db_models.OvercloudRole - - id = int - name = wtypes.text - description = wtypes.text - image_name = wtypes.text - flavor_id = wtypes.text - - -class OvercloudRoleCount(Base): - """Transfer object for overcloud role counts.""" - - _db_class = db_models.OvercloudRoleCount - - id = int - overcloud_role_id = int - overcloud_id = int - num_nodes = int - - -class Overcloud(Base): - """Transfer object for overclouds.""" - - _db_class = db_models.Overcloud - - id = int - stack_id = wtypes.text - name = wtypes.text - description = wtypes.text - attributes = {wtypes.text: wtypes.text} - counts = [OvercloudRoleCount] - - @classmethod - def from_db_model(cls, db_overcloud, skip_fields=None, - mask_passwords=True): - # General Data - transfer_overcloud = super(Overcloud, cls).from_db_model( - db_overcloud, skip_fields=['attributes', 'counts']) - - # Attributes - translated = {} - for db_attribute in db_overcloud.attributes: - # FIXME(rpodolyaka): a workaround for bug 1308172. To fix this - # properly we should either stop storing passwords in Tuskar API - # or delegate this task to another service. - if mask_passwords and 'password' in db_attribute.key.lower(): - value = '******' - else: - value = db_attribute.value - - translated[db_attribute.key] = value - transfer_overcloud.attributes = translated - - # Counts - transfer_overcloud.counts = [OvercloudRoleCount.from_db_model(c) - for c in db_overcloud.counts] - return transfer_overcloud - - def to_db_model(self, omit_unset=False, skip_fields=None): - # General Data - db_model = super(Overcloud, self).to_db_model( - omit_unset=omit_unset, - skip_fields=['attributes', 'counts']) - - # Attributes - if self.attributes != wtypes.Unset: - - translated = [] - for key, value in self.attributes.items(): - translated.append(db_models.OvercloudAttribute( - key=key, value=value, overcloud_id=self.id - )) - db_model.attributes = translated - - # Counts - if self.counts != wtypes.Unset: - - translated = [] - for count in self.counts: - translated.append(db_models.OvercloudRoleCount( - num_nodes=count.num_nodes, - overcloud_role_id=count.overcloud_role_id, - overcloud_id=self.id - )) - db_model.counts = translated - - return db_model diff --git a/tuskar/api/controllers/v1/overcloud.py b/tuskar/api/controllers/v1/overcloud.py deleted file mode 100644 index 155284c0..00000000 --- a/tuskar/api/controllers/v1/overcloud.py +++ /dev/null @@ -1,377 +0,0 @@ -# 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 pecan -from pecan import rest -import six -import wsme -from wsmeext import pecan as wsme_pecan - -from tuskar.api.controllers.v1 import models -from tuskar.common import exception -from tuskar.heat.client import HeatClient -import tuskar.heat.template_tools as template_tools - -LOG = logging.getLogger(__name__) - - -def parse_counts_and_flavors(counts, overcloud_roles): - """Helper for parsing the OvercloudRoleCount object - - Given a list of OvercloudRoleCount and dict of OverlcoudRole objects - return a dict of (image_name, count) and (image_name, flavor_id) in a - format used for building a template. - - :param counts: List of tuskar.api.controllers.v1.models.OvercloudRoleCount - :type counts: list - - :param overcloud_roles: Dict of (overcloud_role_id, overcloud_role) so - we can access image_name and flavor_id of roles - :type overcloud_roles: dict - - :return: Tuple of dicts {(image_name, count)}, {(image_name, flavor_id)} - :rtype: two dict objects - """ - parsed_counts = {} - parsed_flavors = {} - for count_obj in counts: - image_name = overcloud_roles[count_obj.overcloud_role_id].image_name - flavor_id = overcloud_roles[count_obj.overcloud_role_id].flavor_id - count = count_obj.num_nodes - parsed_counts[image_name] = count - parsed_flavors[image_name] = flavor_id - - return parsed_counts, parsed_flavors - - -def filter_template_attributes(allowed_data, attributes): - """Helper filtering attributes for template - - Given a list of allowed data and attributes, filter the attributes - only with keys of allowed data and return filtered data. - - :param allowed_data: Dict of allowed attributes for template returned by - validating of template. - :type allowed_data: dict - - :param attributes: Dict of attributes sent from user in deploying stack - operation - :type attributes: Dict - - :return: Dict of filtered attributes - :rtype: dict - """ - allowed_keys = allowed_data.get("Parameters", {}).keys() - - filtered_data = dict([(key, value) for key, value in attributes.items() - if key in allowed_keys]) - - return filtered_data - - -def get_overcloud_roles_dict(): - return dict((overcloud_role.id, overcloud_role) - for overcloud_role in - pecan.request.dbapi.get_overcloud_roles()) - - -def get_flavor_attributes(parsed_flavors): - """Helper for building dict of flavor attributes - - Given a dict of parsed flavors, it will put a flavor_ids stored in - role into attributes that will be fed to heat stack create/update. - - Mapping of image name to flavor_param is stored in template_tools.ROLES. - - :param parsed_flavors: Dict of (image_name, flavor_id) - :type parsed_flavors: dict - - :return: Dict of (flavor_param, flavor_id) for Heat Template params - :rtype: dict - """ - flavor_attributes = {} - for image_name, flavor_id in parsed_flavors.items(): - role = template_tools.ROLES.get(image_name, None) - if role: - flavor_param = role['flavor_param'] - flavor_attributes[flavor_param] = flavor_id - - return flavor_attributes - - -def process_stack(attributes, counts, overcloud_roles, create=False): - """Helper function for processing the stack. - - Given a params dict containing the Overcloud Roles and initialization - parameters create or update the stack. - - :param attributes: Dictionary of initialization params and overcloud roles - for heat template and initialization of stack - :type attributes: dict - - :param counts: Dictionary of counts of roles to be deployed - :type counts: dict - - :param overcloud_roles: Dict of (overcloud_role_id, overcloud_role) so - we can access image_name and flavor_id of roles - :type overcloud_roles: dict - - :param create: A flag to designate if we are creating or updating the stack - :type create: bool - """ - heat_client = HeatClient() - - try: - # Get how many of each role we want and what flavor each role uses. - parsed_counts, parsed_flavors = parse_counts_and_flavors( - counts, overcloud_roles) - except Exception as e: - raise exception.ParseCountsAndFlavorsFailed(six.text_type(e)) - - try: - # Build the template - overcloud = template_tools.merge_templates(parsed_counts) - except Exception as e: - raise exception.HeatTemplateCreateFailed(six.text_type(e)) - - try: - # Get the parameters that the template accepts and validate - allowed_data = heat_client.validate_template(overcloud) - except Exception as e: - raise exception.HeatTemplateValidateFailed(six.text_type(e)) - - stack_exists = heat_client.exists_stack() - if stack_exists and create: - raise exception.StackAlreadyCreated() - - elif not stack_exists and not create: - raise exception.StackNotFound() - - try: - # Put flavors from OverloudRoles into attributes - attributes.update(get_flavor_attributes(parsed_flavors)) - - # Filter the attributes to allowed only - filtered_attributes = filter_template_attributes(allowed_data, - attributes) - except Exception as e: - raise exception.HeatStackProcessingAttributesFailed(six.text_type(e)) - - if create: - operation = heat_client.create_stack - else: - operation = heat_client.update_stack - - try: - result = operation(overcloud, filtered_attributes) - except Exception as e: - if create: - raise exception.HeatStackCreateFailed(six.text_type(e)) - else: - raise exception.HeatStackUpdateFailed(six.text_type(e)) - - return result - - -class OvercloudsController(rest.RestController): - """REST controller for the Overcloud class.""" - - _custom_actions = {'template_parameters': ['GET']} - - @pecan.expose('json') - def template_parameters(self): - # TODO(lsmola) returning all possible parameters now, later in J - # user should pick what to build first and we should return - # appropriate parameters. - fixed_params = {template_tools.OVERCLOUD_COMPUTE_ROLE: 1, - template_tools.OVERCLOUD_VOLUME_ROLE: 1, - template_tools.OVERCLOUD_OBJECT_STORAGE_ROLE: 1} - - # We don't want user to fill flavor based parameters, cause - # it is already stored in OvercloudRoles, also Image parameters - # are expected to be default, otherwise our associations - # will not work. - except_parameters = ('OvercloudControlFlavor', - 'OvercloudComputeFlavor', - 'OvercloudBlockStorageFlavor', - 'OvercloudSwiftStorageFlavor', - 'NovaImage', - 'notcomputeImage', - 'BlockStorageImage', - 'SwiftStorageImage',) - - overcloud = template_tools.merge_templates(fixed_params) - - heat_client = HeatClient() - try: - allowed_data = heat_client.validate_template(overcloud) - except Exception as e: - raise exception.HeatTemplateValidateFailed(unicode(e)) - - # Send back only wanted parameters - template_parameters = dict((key, value) for key, value - in allowed_data['Parameters'].items() - if key not in except_parameters) - - return template_parameters - - @wsme.validate(models.Overcloud) - @wsme_pecan.wsexpose(models.Overcloud, - body=models.Overcloud, - status_code=201) - def post(self, transfer_overcloud): - """Creates a new overcloud. - - :param transfer_overcloud: data submitted by the user - :type transfer_overcloud: - tuskar.api.controllers.v1.models.Overcloud - - :return: created overcloud - :rtype: tuskar.api.controllers.v1.models.Overcloud - - :raises: tuskar.common.exception.OvercloudExists: if an overcloud - with the given name exists - """ - LOG.debug('Creating overcloud: %s' % transfer_overcloud) - - # FIXME(lsmola) This is just POC of creating a stack - # this has to be done properly with proper Work-flow abstraction of: - # step 1- build template and start stack-create - # step 2- put the right stack_id to the overcloud - # step 3- initialize the stack - # step 4- set the correct overcloud status - stack = process_stack(transfer_overcloud.attributes, - transfer_overcloud.counts, - get_overcloud_roles_dict(), - create=True) - - # Persist to the database - transfer_overcloud.stack_id = stack['stack']['id'] - db_overcloud = transfer_overcloud.to_db_model() - result = pecan.request.dbapi.create_overcloud(db_overcloud) - - # Package for transfer back to the user - saved_overcloud = models.Overcloud.from_db_model(result) - - return saved_overcloud - - @wsme.validate(models.Overcloud) - @wsme_pecan.wsexpose(models.Overcloud, - int, - body=models.Overcloud) - def put(self, overcloud_id, overcloud_delta): - """Updates an existing overcloud, including its attributes and counts. - - :param overcloud_id: identifies the overcloud being deleted - :type overcloud_id: int - - :param overcloud_delta: contains only values that are to be affected - by the update - :type overcloud_delta: - tuskar.api.controllers.v1.models.Overcloud - - :return: created overcloud - :rtype: tuskar.api.controllers.v1.models.Overcloud - - :raises: tuskar.common.exception.OvercloudNotFound if there - is no overcloud with the given ID - """ - LOG.debug('Updating overcloud: %s' % overcloud_id) - - # ID is in the URL so make sure it's in the transfer object - # before translation - overcloud_delta.id = overcloud_id - db_delta = overcloud_delta.to_db_model(omit_unset=True) - - # Will raise a not found if there is no overcloud with the ID - result = pecan.request.dbapi.update_overcloud(db_delta) - - updated_overcloud = models.Overcloud.from_db_model( - result, mask_passwords=False) - - # FIXME(lsmola) This is just POC of updating a stack - # this probably should also have workflow - # step 1- build template and stack-update - # step 2- set the correct overcloud status - process_stack(updated_overcloud.attributes, result.counts, - get_overcloud_roles_dict()) - - return models.Overcloud.from_db_model(result) - - @wsme_pecan.wsexpose(None, int, status_code=204) - def delete(self, overcloud_id): - """Deletes the given overcloud. - - :param overcloud_id: identifies the overcloud being deleted - :type overcloud_id: int - - :raises: tuskar.common.exception.OvercloudNotFound if there - is no overcloud with the given ID - """ - - # FIXME(lsmola) this should always try to delete both overcloud - # and stack. So it requires some exception catch over below. - # FIXME(lsmola) there is also a workflow needed - # step 1- delete stack and set status deleting in progress to - # overcloud - # step 2 - once stack is deleted, delete the overcloud - LOG.debug('Deleting overcloud with ID: %s' % overcloud_id) - pecan.request.dbapi.delete_overcloud_by_id(overcloud_id) - - heat_client = HeatClient() - if not heat_client.exists_stack(): - # If the stack doesn't exist, we have nothing else to do here. - return - - try: - heat_client.delete_stack() - except Exception: - raise exception.HeatStackDeleteFailed() - - @wsme_pecan.wsexpose(models.Overcloud, int) - def get_one(self, overcloud_id): - """Returns a specific overcloud. - - An exception is raised if no overcloud is found with the - given ID. - - :param overcloud_id: identifies the overcloud being deleted - :type overcloud_id: int - - :return: matching overcloud - :rtype: tuskar.api.controllers.v1.models.Overcloud - - :raises: tuskar.common.exception.OvercloudNotFound if there - is no overcloud with the given ID - """ - - LOG.debug('Retrieving overcloud with ID: %s' % overcloud_id) - overcloud = pecan.request.dbapi.get_overcloud_by_id(overcloud_id) - transfer_overcloud = models.Overcloud.from_db_model(overcloud) - return transfer_overcloud - - @wsme_pecan.wsexpose([models.Overcloud]) - def get_all(self): - """Returns all overclouds. - - An empty list is returned if no overclouds are present. - - :return: list of overclouds; empty list if none are found - :rtype: list of tuskar.api.controllers.v1.models.Overcloud - """ - LOG.debug('Retrieving all overclouds') - overclouds = pecan.request.dbapi.get_overclouds() - transfer_overclouds = [models.Overcloud.from_db_model(o) - for o in overclouds] - return transfer_overclouds diff --git a/tuskar/api/controllers/v1/overcloud_roles.py b/tuskar/api/controllers/v1/overcloud_roles.py deleted file mode 100644 index db9352af..00000000 --- a/tuskar/api/controllers/v1/overcloud_roles.py +++ /dev/null @@ -1,143 +0,0 @@ -# -# 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 pecan -from pecan import rest -import wsme -from wsmeext import pecan as wsme_pecan - -from tuskar.api.controllers.v1 import models - - -LOG = logging.getLogger(__name__) - - -class OvercloudRolesController(rest.RestController): - """REST controller for the OvercloudRole class.""" - - @wsme.validate(models.OvercloudRole) - @wsme_pecan.wsexpose(models.OvercloudRole, - body=models.OvercloudRole, - status_code=201) - def post(self, transfer_role): - """Creates a new overcloud role. - - :param transfer_role: data submitted by the user - :type transfer_role: - tuskar.api.controllers.v1.models.OvercloudRole - - :return: created role - :rtype: tuskar.api.controllers.v1.models.OvercloudRole - - :raises: tuskar.common.exception.OvercloudRoleExists: if an overcloud - role with the given name exists - """ - - LOG.debug('Creating overcloud role: %s' % transfer_role) - - # Persist to the database - db_role = transfer_role.to_db_model() - result = pecan.request.dbapi.create_overcloud_role(db_role) - - # Package for transfer back to the user - saved_role = models.OvercloudRole.from_db_model(result) - - return saved_role - - @wsme.validate(models.OvercloudRole) - @wsme_pecan.wsexpose(models.OvercloudRole, - int, - body=models.OvercloudRole) - def put(self, role_id, role_delta): - """Updates an existing overcloud role. - - :param role_id: identifies the role being deleted - :type role_id: int - - :param role_delta: contains only values that are to be affected - by the update operation - :type role_delta: - tuskar.api.controllers.v1.models.OvercloudRole - - :return: role with updated values - :rtype: tuskar.api.controllers.v1.models.OvercloudRole - - :raises: tuskar.common.exception.OvercloudRoleNotFound if there - is no role with the given ID - """ - - LOG.debug('Updating overcloud role: %s' % role_id) - - # ID is in the URL so make sure it's in the transfer object - # before translation - role_delta.id = role_id - - db_delta = role_delta.to_db_model(omit_unset=True) - - # Will raise a not found if there is no role with the ID - updated = pecan.request.dbapi.update_overcloud_role(db_delta) - - return updated - - @wsme_pecan.wsexpose(None, int, status_code=204) - def delete(self, role_id): - """Deletes the given overcloud role. - - :param role_id: identifies the role being deleted - :type role_id: int - - :raises: tuskar.common.exception.OvercloudRoleNotFound if there - is no role with the given ID - """ - - LOG.debug('Deleting overcloud role with ID: %s' % role_id) - pecan.request.dbapi.delete_overcloud_role_by_id(role_id) - - @wsme_pecan.wsexpose(models.OvercloudRole, int) - def get_one(self, role_id): - """Returns a specific overcloud role. - - An exception is raised if no overcloud role is found with the - given ID. - - :param role_id: identifies the role being deleted - :type role_id: int - - :return: matching resource role - :rtype: tuskar.api.controllers.v1.models.OvercloudRole - - :raises: tuskar.common.exception.OvercloudRoleNotFound if there - is no role with the given ID - """ - - LOG.debug('Retrieving overcloud role with ID: %s' % role_id) - db_role = pecan.request.dbapi.get_overcloud_role_by_id(role_id) - transfer_role = models.OvercloudRole.from_db_model(db_role) - return transfer_role - - @wsme_pecan.wsexpose([models.OvercloudRole]) - def get_all(self): - """Returns all overcloud roles. - - An empty list is returned if no overcloud roles are present. - - :return: list of roles; empty list if none are found - :rtype: list of tuskar.api.controllers.v1.models.OvercloudRole - """ - LOG.debug('Retrieving all overcloud roles') - db_roles = pecan.request.dbapi.get_overcloud_roles() - transfer_roles = [models.OvercloudRole.from_db_model(c) - for c in db_roles] - return transfer_roles diff --git a/tuskar/api/controllers/v2/__init__.py b/tuskar/api/controllers/v2/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tuskar/api/controllers/v2/controller.py b/tuskar/api/controllers/v2/controller.py deleted file mode 100644 index f735ad22..00000000 --- a/tuskar/api/controllers/v2/controller.py +++ /dev/null @@ -1,38 +0,0 @@ -# -# 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 pecan - -from tuskar.api.controllers.v2.plans import PlansController -from tuskar.api.controllers.v2.roles import RolesController - - -class Controller(object): - """Version 2 API controller root.""" - - plans = PlansController() - roles = RolesController() - - @pecan.expose('json') - def index(self): - return { - 'version': { - 'status': 'development', - 'media-types': [{'base': 'application/json'}], - 'id': 'v2.0', - 'links': [{ - 'href': '/v2/', - 'rel': 'self', - }] - } - } diff --git a/tuskar/api/controllers/v2/models.py b/tuskar/api/controllers/v2/models.py deleted file mode 100644 index 27e67c84..00000000 --- a/tuskar/api/controllers/v2/models.py +++ /dev/null @@ -1,171 +0,0 @@ -# -# 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. - -""" -Contains transfer objects for use with WSME REST APIs. The objects in this -module also contain the translations between the REST transfer objects and -the internal Tuskar domain model. -""" - -import datetime -import logging - -import six -from wsme import types as wtypes - -from tuskar.api.controllers.v2 import types as v2types -from tuskar.manager import models as manager_models - -LOG = logging.getLogger(__name__) - - -class Base(wtypes.Base): - """Base functionality for all API models. - - This class should never be directly instantiated. - """ - - def _lookup(self, key): - """Looks up a key, translating WSME's Unset into Python's None. - - :return: value of the given attribute; None if it is not set - """ - value = getattr(self, key) - if value == wtypes.Unset: - value = None - return value - - -class Role(Base): - """Transfer object for roles.""" - - uuid = wtypes.text - name = wtypes.text - version = int - description = wtypes.text - - @classmethod - def from_tuskar_model(cls, role): - """Translates from the Tuskar domain model. - - :type role: tuskar.manager.models.Role - """ - r = cls(**{ - 'uuid': role.uuid, - 'name': role.name, - 'version': role.version, - 'description': role.description - }) - return r - - -class ParameterConstraint(Base): - - constraint_type = wtypes.text - definition = v2types.MultiType(list, dict, wtypes.text) - description = wtypes.text - - @classmethod - def from_tuskar_model(cls, constraint): - return cls(**{'constraint_type': constraint.constraint_type, - 'definition': constraint.definition, - 'description': constraint.description}) - - -class PlanParameter(Base): - - name = wtypes.text - label = wtypes.text - default = v2types.MultiType(wtypes.text, six.integer_types, list, dict) - description = wtypes.text - hidden = bool - value = v2types.MultiType(wtypes.text, six.integer_types, list, dict) - constraints = [ParameterConstraint] - parameter_type = wtypes.text - - @classmethod - def from_tuskar_model(cls, param): - """Translates from the Tuskar domain model. - - :type param: tuskar.manager.models.PlanParameter - """ - constraints = [ParameterConstraint.from_tuskar_model(c) - for c in param.constraints] - p = cls(**{ - 'name': param.name, - 'label': param.label, - 'default': param.default, - 'description': param.description, - 'hidden': param.hidden, - 'value': param.value, - 'constraints': constraints, - 'parameter_type': param.param_type - }) - return p - - -class Plan(Base): - - uuid = wtypes.text - name = wtypes.text - description = wtypes.text - created_at = datetime.datetime - updated_at = datetime.datetime - roles = [Role] - parameters = [PlanParameter] - - @classmethod - def from_tuskar_model(cls, plan): - """Translates from the Tuskar domain model. - - :type plan: tuskar.manager.models.DeploymentPlan - """ - roles = [Role.from_tuskar_model(r) for r in plan.roles] - params = [PlanParameter.from_tuskar_model(p) for p in plan.parameters] - - p = cls(**{ - 'uuid': plan.uuid, - 'name': plan.name, - 'description': plan.description, - 'created_at': plan.created_at, - 'updated_at': plan.updated_at, - 'roles': roles, - 'parameters': params, - }) - return p - - -class ParameterValue(Base): - - name = wtypes.text - value = wtypes.text - - @classmethod - def from_tuskar_model(cls, param_value): - """Translates from the Tuskar domain model. - - :type param_value: tuskar.manager.models.ParameterValue - """ - p = cls(**{ - 'name': param_value.name, - 'value': param_value.value, - }) - return p - - def to_tuskar_model(self): - """Translates into the Tuskar domain model. - - :rtype: tuskar.manager.models.ParameterValue - """ - p = manager_models.ParameterValue(self.name, self.value) - return p diff --git a/tuskar/api/controllers/v2/plans.py b/tuskar/api/controllers/v2/plans.py deleted file mode 100644 index 4c14df5b..00000000 --- a/tuskar/api/controllers/v2/plans.py +++ /dev/null @@ -1,171 +0,0 @@ -# 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 pecan import rest -import wsme -from wsme.types import UnsetType -from wsmeext import pecan as wsme_pecan - -from tuskar.api.controllers.v2 import models -from tuskar.api.controllers.v2 import roles -from tuskar.common import exception -from tuskar.manager.plan import PlansManager -from tuskar.storage import exceptions as storage_exceptions - - -LOG = logging.getLogger(__name__) - - -class PlansController(rest.RestController): - """REST controller for the Plan class.""" - - _custom_actions = {'templates': ['GET']} - - roles = roles.RolesController() - - @wsme_pecan.wsexpose([models.Plan]) - def get_all(self): - """Returns all plans. - - An empty list is returned if no plans are present. - - :return: list of plans; empty list if none are found - :rtype: list of tuskar.api.controllers.v2.models.Plan - """ - LOG.debug('Retrieving all plans') - manager = PlansManager() - all_plans = manager.list_plans() - transfer_plans = [models.Plan.from_tuskar_model(p) - for p in all_plans] - return transfer_plans - - @wsme_pecan.wsexpose(models.Plan, str) - def get_one(self, plan_uuid): - """Returns a specific plan. - - An exception is raised if no plan is found with the - given UUID. - - :param plan_uuid: identifies the plan being fetched - :type plan_uuid: str - - :return: matching plan - :rtype: tuskar.api.controllers.v2.models.Plan - - :raises: tuskar.common.exception.PlanNotFound if there - is no plan with the given UUID - """ - LOG.debug('Retrieving plan with UUID: %s' % plan_uuid) - manager = PlansManager() - try: - found = manager.retrieve_plan(plan_uuid) - except storage_exceptions.UnknownUUID: - LOG.exception('Could not retrieve plan: %s' % plan_uuid) - raise exception.PlanNotFound() - transfer = models.Plan.from_tuskar_model(found) - return transfer - - @wsme_pecan.wsexpose(None, str, status_code=204) - def delete(self, plan_uuid): - """Deletes the given plan. - - :param plan_uuid: identifies the plan being deleted - :type plan_uuid: str - - :raises: tuskar.common.exception.PlanNotFound if there - is no plan with the given UUID - """ - - LOG.debug('Deleting plan with UUID: %s' % plan_uuid) - manager = PlansManager() - try: - manager.delete_plan(plan_uuid) - except storage_exceptions.UnknownUUID: - LOG.exception('Could not delete plan: %s' % plan_uuid) - raise exception.PlanNotFound() - - @wsme_pecan.wsexpose(models.Plan, - body=models.Plan, - status_code=201) - def post(self, transfer_plan): - """Creates a new plan. - - :param transfer_plan: data submitted by the user - :type transfer_plan: - tuskar.api.controllers.v1.models.Plan - - :return: created plan - :rtype: tuskar.api.controllers.v1.models.Plan - - :raises: tuskar.common.exception.PlanExists: if a plan - with the given name exists - """ - LOG.debug('Creating plan: %s' % transfer_plan) - - # We don't want the wsme types bleed into the rest of Tuskar, so - # explicitly set to None if it wasn't specified. - description = transfer_plan.description - if isinstance(description, UnsetType): - description = None - - manager = PlansManager() - try: - created = manager.create_plan(transfer_plan.name, - description) - except storage_exceptions.NameAlreadyUsed: - LOG.exception('Plan already exists with this name') - raise exception.PlanExists(transfer_plan.name) - transfer = models.Plan.from_tuskar_model(created) - return transfer - - @wsme_pecan.wsexpose({str: str}, str) - def templates(self, plan_uuid): - """Returns the template files for a given plan. - - :return: dictionary of filenames to contents for each template file - involved in the plan - :rtype: dict - """ - LOG.debug('Retrieving templates for plan: %s' % plan_uuid) - - manager = PlansManager() - try: - templates = manager.package_templates(plan_uuid) - except storage_exceptions.UnknownUUID: - LOG.exception('Could not retrieve templates for plan: %s' % - plan_uuid) - raise exception.PlanNotFound() - - return templates - - @wsme.validate(models.Plan) - @wsme_pecan.wsexpose(models.Plan, - str, - body=[models.ParameterValue], - status_code=201) - def patch(self, plan_uuid, param_list): - """Patches existing plan. - - :return: patched plan - :rtype: tuskar.api.controllers.v1.models.Plan - """ - manager = PlansManager() - params = [p.to_tuskar_model() for p in param_list] - try: - updated_plan = manager.set_parameter_values(plan_uuid, params) - except storage_exceptions.UnknownUUID: - LOG.exception('Could not patch plan: %s' % plan_uuid) - raise exception.PlanNotFound() - transfer_plan = models.Plan.from_tuskar_model(updated_plan) - return transfer_plan diff --git a/tuskar/api/controllers/v2/roles.py b/tuskar/api/controllers/v2/roles.py deleted file mode 100644 index d9f9ab6f..00000000 --- a/tuskar/api/controllers/v2/roles.py +++ /dev/null @@ -1,150 +0,0 @@ -# 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 pecan import rest -from wsmeext import pecan as wsme_pecan - -from tuskar.api.controllers.v2 import models -from tuskar.common import exception -from tuskar.common import utils -from tuskar.manager.plan import PlansManager -from tuskar.manager.role import RoleManager -from tuskar.storage import exceptions as storage_exceptions - - -LOG = logging.getLogger(__name__) - - -class RolesController(rest.RestController): - """REST controller for the Role class.""" - - _custom_actions = {'extra_data': ['GET']} - - @wsme_pecan.wsexpose([models.Role]) - def get_all(self): - """Returns all roles. - - An empty list is returned if no roles are present. - - :return: list of roles; empty list if none are found - :rtype: list of tuskar.api.controllers.v2.models.Role - """ - LOG.debug('Retrieving all roles') - manager = RoleManager() - all_roles = manager.list_roles(only_latest=False) - transfer_roles = [models.Role.from_tuskar_model(r) for r in all_roles] - return transfer_roles - - @wsme_pecan.wsexpose({str: str}, str) - def extra_data(self, role_uuid): - """Retrieve the extra data files associated with a given role. - - :param role_uuid: identifies the role - :type role_uuid: str - - :return: a dict where keys are filenames and values are their contents - :rtype: dict - - This method will retrieve all stored role_extra records (these are - created at the same time that the Roles are, by using --role-extra - parameter to tuskar-load-roles). - - The internal representation for a given role-extra file encodes the - file extension into the name. For instance 'hieradata/compute.yaml' - is stored as 'extra_compute_yaml'. - - The given role's template is searched for 'get_file' directives and - then matched against the stored role-extra records (based on their - name... e.g. 'extra_controller_yaml' we look for 'controller.yaml' - after a get_file directive). - - This method thus returns all the matched role-extra files for the - given role. The keys will include the relative path if one is - used in the role template: - { - "hieradata/common.yaml": "CONTENTS", - "hieradata/controller.yaml": "CONTENTS", - "hieradata/object.yaml": "CONTENTS" - } - - """ - manager = RoleManager() - db_role = manager.retrieve_db_role_by_uuid(role_uuid) - db_role_extra = manager.retrieve_db_role_extra() - role_extra_paths = utils.resolve_template_extra_data( - db_role, db_role_extra) - return manager.template_extra_data_for_output(role_extra_paths) - - @wsme_pecan.wsexpose(models.Plan, - str, - body=models.Role, - status_code=201) - def post(self, plan_uuid, role): - """Adds a new role to plan. - - :param plan_uuid: identifies the plan - :type plan_uuid: str - - :param role: identifies the role to add - :type role: tuskar.api.controllers.v2.models.Role - - :return: modified plan - :rtype: tuskar.api.controllers.v2.models.Plan - - :raises: tuskar.common.exception.PlanAlreadyHasRole if the role has - already been added to the plan. - """ - LOG.debug('Adding role: %(role_uuid)s to plan: %(plan_uuid)s' % - {'role_uuid': role.uuid, 'plan_uuid': plan_uuid}) - manager = PlansManager() - try: - updated_plan = manager.add_role_to_plan(plan_uuid, role.uuid) - except ValueError: - LOG.debug('The role has already been added to the plan.') - raise exception.PlanAlreadyHasRole( - plan_uuid=plan_uuid, - role_uuid=role.uuid - ) - except storage_exceptions.UnknownUUID as e: - LOG.debug(('Either the plan UUID {0} or role UUID {1} could not be' - 'found').format(plan_uuid, role.uuid)) - raise exception.NotFound( - message=str(e)) - transfer_plan = models.Plan.from_tuskar_model(updated_plan) - return transfer_plan - - @wsme_pecan.wsexpose(models.Plan, - str, - str) - def delete(self, plan_uuid, role_uuid): - """Removes a role from given plan. - - :param plan_uuid: identifies the plan - :type plan_uuid: str - - :param role_uuid: identifies the role to be deleted from plan - :type role_uuid: str - """ - LOG.debug('Removing role: %(role_uuid)s from plan: %(plan_uuid)s' % - {'role_uuid': role_uuid, 'plan_uuid': plan_uuid}) - manager = PlansManager() - try: - updated_plan = manager.remove_role_from_plan(plan_uuid, role_uuid) - except storage_exceptions.UnknownUUID as e: - LOG.debug(('Either the plan UUID {0} or role UUID {1} could not be' - 'found').format(plan_uuid, role_uuid)) - raise exception.NotFound( - message=str(e)) - transfer_plan = models.Plan.from_tuskar_model(updated_plan) - return transfer_plan diff --git a/tuskar/api/controllers/v2/types.py b/tuskar/api/controllers/v2/types.py deleted file mode 100644 index 7a36040b..00000000 --- a/tuskar/api/controllers/v2/types.py +++ /dev/null @@ -1,40 +0,0 @@ -# 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 wsme -from wsme import types as wtypes - - -class MultiType(wtypes.UserType): - """A complex type that represents one or more types. - - Used for validating that a value is an instance of one of the types. - - :param *types: Variable-length list of types. - - """ - def __init__(self, *types): - self.types = types - - def __str__(self): - return ' | '.join(map(str, self.types)) - - def validate(self, value): - for t in self.types: - if t is wsme.types.text and isinstance(value, wsme.types.bytes): - value = value.decode() - if isinstance(value, t): - return value - else: - raise ValueError( - _("Wrong type. Expected '%(type)s', got '%(value)s'") - % {'type': self.types, 'value': type(value)}) diff --git a/tuskar/api/hooks.py b/tuskar/api/hooks.py deleted file mode 100644 index 80d2a9a0..00000000 --- a/tuskar/api/hooks.py +++ /dev/null @@ -1,34 +0,0 @@ -# -# Copyright 2012 New Dream Network, LLC (DreamHost) -# -# 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 oslo_config import cfg -from pecan import hooks - -from tuskar.db import api as dbapi - - -class ConfigHook(hooks.PecanHook): - """Attach the configuration object to the request - so controllers can get to it. - """ - - def before(self, state): - state.request.cfg = cfg.CONF - - -class DBHook(hooks.PecanHook): - - def before(self, state): - state.request.dbapi = dbapi.get_instance() diff --git a/tuskar/api/renderers.py b/tuskar/api/renderers.py deleted file mode 100644 index 7b60cdab..00000000 --- a/tuskar/api/renderers.py +++ /dev/null @@ -1,71 +0,0 @@ -# Copyright 2013 Red Hat, Inc. -# -# 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 pecan -import wsme -from wsme import api - - -class JSONRenderer(object): - """Custom JSON renderer. - - Renders to JSON and handles responses for various HTTP status codes. - """ - - def __init__(self, path, extra_vars): - """Create an empty __init__ to accept the arguments provided to a - Renderer but ignore them as they are not needed. - """ - - def _render_fault(self, message, details, code=500): - """Given the namespace dictionary render a JSON error response for the - fault in the format defined by the OpenStack identity service - documentation. - """ - - body = { - 'identityFault': { - "message": message, - "details": details, - "code": code - } - } - return wsme.rest.json.encode_error(None, body) - - def render(self, template_path, namespace): - """Given a namespace dict render the response as JSON and return. - - If the dict contains a faultcode or wsme.api.Response its a fault from - user code and is rendered via _render_fault. - - template_path is a required parameter for renderers but unused in - this context. - """ - - if 'faultcode' in namespace: - return self._render_fault( - namespace['faultstring'], - namespace['debuginfo']) - - result = namespace['result'] - if isinstance(namespace['result'], api.Response): - pecan.response.status_code = result.status_code - return self._render_fault( - result.obj.faultstring, result.obj.debuginfo, - code=result.status_code) - - return wsme.rest.json.encode_result( - result, - namespace['datatype'] - ) diff --git a/tuskar/api/templates/compute.yaml b/tuskar/api/templates/compute.yaml deleted file mode 100644 index 53e21447..00000000 --- a/tuskar/api/templates/compute.yaml +++ /dev/null @@ -1,87 +0,0 @@ -<%def name="render(x)">\ - NovaCompute${x}: - Metadata: - OpenStack::ImageBuilder::Elements: - - nova-compute - admin-password: unset - glance: - host: - Fn::GetAtt: - - notcompute - - PrivateIp - heat: - access_key_id: - Ref: Key - refresh: - - resource: NovaCompute - secret_key: - Fn::GetAtt: - - Key - - SecretAccessKey - stack: - name: - Ref: AWS::StackName - region: - Ref: AWS::Region - interfaces: - control: - Ref: NovaInterfaces - keystone: - host: - Fn::GetAtt: - - notcompute - - PrivateIp - neutron: - host: - Fn::GetAtt: - - notcompute - - PrivateIp - ovs: - bridge_mappings: '' - enable_tunneling: 'True' - local_ip: 0.0.0.0 - network_vlan_ranges: '' - tenant_network_type: gre - ovs_db: - Fn::Join: - - '' - - - mysql+pymysql://neutron:unset@ - - Fn::GetAtt: - - notcompute - - PrivateIp - - /neutron - nova: - compute_driver: - Ref: NovaComputeDriver - db: - Fn::Join: - - '' - - - mysql+pymysql://nova:unset@ - - Fn::GetAtt: - - notcompute - - PrivateIp - - /nova - host: - Fn::GetAtt: - - notcompute - - PrivateIp - rabbit: - host: - Fn::GetAtt: - - notcompute - - PrivateIp - password: guest - service-password: unset - swift: - store_key: '' - store_user: '' - Properties: - ImageId: - Ref: NovaImage - InstanceType: - Ref: InstanceType - KeyName: - Ref: KeyName - AvailabilityZone: nova::${x} - Type: AWS::EC2::Instance -\ diff --git a/tuskar/api/templates/index.html b/tuskar/api/templates/index.html deleted file mode 100644 index a18a7b02..00000000 --- a/tuskar/api/templates/index.html +++ /dev/null @@ -1,12 +0,0 @@ -<%def name="title()"> - Tuskar API v1 - - -
    -
    - -
    - -

    TODO

    - -
    diff --git a/tuskar/api/templates/not_compute.yaml b/tuskar/api/templates/not_compute.yaml deleted file mode 100644 index dcff486d..00000000 --- a/tuskar/api/templates/not_compute.yaml +++ /dev/null @@ -1,112 +0,0 @@ -<%namespace name="conf" file="provision.conf.mako"/>\ -<%namespace name="sh" file="provision.sh.mako"/>\ -<%def name="render(x=0)">\ - notcompute: - Metadata: - AWS::CloudFormation::Init: - config: - files: - /root/tuskar/provision.conf: - content: - Fn::Base64: - | -<% conf.render() %>\ - mode: "000644" - owner: root - group: root - /root/tuskar/provision.sh: - content: - Fn::Base64: - | -<% sh.render() %>\ - mode: "000700" - owner: root - group: root - OpenStack::Heat::Stack: {} - OpenStack::ImageBuilder::Elements: - - boot-stack - - heat-cfntools - - heat-localip - - neutron-network-node - admin-password: unset - admin-token: unset - cinder: - db: mysql+pymysql://cinder:unset@localhost/cinder - volume_size_mb: '5000' - controller-address: 192.0.2.5 - db-password: unset - glance: - db: mysql+pymysql://glance:unset@localhost/glance - host: 192.0.2.5 - heat: - access_key_id: - Ref: Key - admin_password: unset - admin_tenant_name: service - admin_user: heat - auth_encryption_key: unset___________ - db: mysql+pymysql://heat:unset@localhost/heat - heat_watch_server_url: http://192.0.2.5:8003 - metadata_server_url: http://192.0.2.5:8000 - refresh: - - resource: notcompute - secret_key: - Fn::GetAtt: - - Key - - SecretAccessKey - stack: - name: - Ref: AWS::StackName - region: - Ref: AWS::Region - waitcondition_server_url: http://192.0.2.5:8000/v1/waitcondition - interfaces: - control: eth2 - keystone: - db: mysql+pymysql://keystone:unset@localhost/keystone - host: 192.0.2.5 - neutron: - floatingip_end: 192.0.2.64 - floatingip_range: 192.0.2.0/24 - floatingip_start: 192.0.2.45 - host: 192.0.2.5 - metadata_proxy_shared_secret: unset - ovs: - enable_tunneling: 'True' - fixed_range: - end: 10.255.255.254 - start: 10.0.0.2 - local_ip: 192.0.2.5 - ovs_range: 10.0.0.0/8 - public_interface: eth0 - tenant_network_type: gre - ovs_db: mysql+pymysql://neutron:unset@localhost/ovs_neutron?charset=utf8 - nova: - compute_driver: libvirt.LibvirtDriver - db: mysql+pymysql://nova:unset@localhost/nova - host: 192.0.2.5 - metadata-proxy: true - rabbit: - host: 192.0.2.5 - password: guest - service-password: unset - Properties: - ImageId: - Ref: notcomputeImage - InstanceType: - Ref: InstanceType - KeyName: - Ref: KeyName - AvailabilityZone: nova::${x} - UserData: - Fn::Base64: - | - #!/bin/bash -v - /opt/aws/bin/cfn-init - # We need to set the undercloud Heat IP in the boto config. Atm this - # is hardcoded. We should set a hostname in the tuskar conf. - sed -e "s/0.0.0.0/192.0.2.2/g" /var/lib/heat-cfntools/cfn-boto-cfg > /tmp/cfn-boto-cfg - mv -f /tmp/cfn-boto-cfg /var/lib/heat-cfntools/cfn-boto-cfg - exec /root/tuskar/provision.sh &> /root/tuskar/provision.log & - Type: AWS::EC2::Instance -\ diff --git a/tuskar/api/templates/overcloud.yaml b/tuskar/api/templates/overcloud.yaml deleted file mode 100644 index 8e02cb57..00000000 --- a/tuskar/api/templates/overcloud.yaml +++ /dev/null @@ -1,63 +0,0 @@ -<%namespace name="not_compute" file="not_compute.yaml"/>\ -<%namespace name="compute" file="compute.yaml"/>\ -<% # Mapping between resource class service type and a HEAT template - # TODO (mtaylor) Move this into Config file or add to model via API call. - templates = {"compute": compute, "not_compute": not_compute, "controller": not_compute} %>\ -Description: Nova API,Keystone,Heat Engine and API,Glance,Neutron,Dedicated MySQL - server,Dedicated RabbitMQ Server,Group of Nova Computes -HeatTemplateFormatVersion: '2012-12-12' -Parameters: - InstanceType: - Default: baremetal - Description: Flavor to request when deploying. - Type: String - KeyName: - Default: default - Description: Name of an existing EC2 KeyPair to enable SSH access to the instances - Type: String - NovaComputeDriver: - Default: libvirt.LibvirtDriver - Type: String - NovaImage: - Default: overcloud-compute - Type: String - NovaInterfaces: - Default: eth0 - Type: String - PowerUserName: - Default: stack - Description: What username to ssh to the virtual power host with. - Type: String - notcomputeImage: - Type: String - Default: overcloud-control -Resources: - AccessPolicy: - Properties: - AllowedResources: - - notcompute - Type: OS::Heat::AccessPolicy - Key: - Properties: - UserName: - Ref: User - Type: AWS::IAM::AccessKey - User: - Properties: - Policies: - - Ref: AccessPolicy - Type: AWS::IAM::User -<% for rc in resource_classes: - for r in rc.racks: - for n in r.nodes: - templates[rc.service_type].render(n.node_id) -%> -Outputs: - KeystoneURL: - Description: URL for the Overcloud Keystone service - Value: - Fn::Join: - - '' - - - http:// - - Fn::GetAtt: [notcompute, PublicIp] - - :5000/v2.0/ diff --git a/tuskar/api/templates/provision.conf.mako b/tuskar/api/templates/provision.conf.mako deleted file mode 100644 index 9915471e..00000000 --- a/tuskar/api/templates/provision.conf.mako +++ /dev/null @@ -1,26 +0,0 @@ -<%def name="render()">\ - # Aggregates # - declare -A AGGREGATES - % for rc in resource_classes: - AGGREGATES[${rc.name}]=-1 - % endfor - # Nodes # - declare -A BM_HOSTS - % for rc in resource_classes: - %if rc.service_type=="compute": - %for r in rc.racks: - %for n in r.nodes: - BM_HOSTS[${n.node_id}]=${rc.name} - % endfor - % endfor - % endif - % endfor - # Flavors # - declare -A FLAVORS - % for rc in resource_classes: - % for f in rc.flavors: -<% ram, vcpu, disk, ephemeral, swap = nova_util.extract_from_capacities(f) %>\ - FLAVORS[${rc.name}.${f.name}]='--ephemeral=${ephemeral} --swap=${swap} ${rc.name}.${f.name} auto ${ram} ${disk} ${vcpu}' - % endfor - % endfor -\ \ No newline at end of file diff --git a/tuskar/api/templates/provision.sh.mako b/tuskar/api/templates/provision.sh.mako deleted file mode 100644 index 4c3705db..00000000 --- a/tuskar/api/templates/provision.sh.mako +++ /dev/null @@ -1,83 +0,0 @@ -<%def name="render()">\ - #!/bin/bash -v - CONF=/root/tuskar/provision.conf - source /root/stackrc - wait_for(){ - LOOPS=$1 - SLEEPTIME=$2 - shift ; shift - i=0 - while [ $i -lt $LOOPS ] ; do - i=$((i + 1)) - eval "$@" && return 0 || true - sleep $SLEEPTIME - done - return 1 - } - wait_for 60 10 test -f /opt/stack/boot-stack.ok - wait_for 60 10 nova list - # We must enable host aggregate matching when scheduling - echo "scheduler_default_filters=AggregateInstanceExtraSpecsFilter,AvailabilityZoneFilter,RamFilter,ComputeFilter" >> /etc/nova/nova.conf - service nova-scheduler restart - # Remove default flavors - for i in {1..5} - do - nova flavor-delete $i - done - # Set to not empty - HASH="md5sum" - declare -A EXISTING_AGGREGATES - while true - do - if [ "$HASH" != "`md5sum $CONF`" ] - then - HASH="`md5sum $CONF`" - echo "New Resources Found, Registering" - source $CONF - # Register Host Aggregates - aggs=`nova aggregate-list` - for a in ${'${!AGGREGATES[@]}'} - do - # Check to see - if [ `expr "$aggs" : ".*\s$a\s"` == 0 ] - then - ${"EXISTING_AGGREGATES[$a]=$(nova aggregate-create $a | tail -n +4 | head -n 1 | tr -s ' ' | cut -d '|' -f2)"} - ${"nova aggregate-set-metadata ${EXISTING_AGGREGATES[$a]} class=$a-hosts"} - fi - done - # Register Flavors - for f in ${'${!FLAVORS[@]}'} - do - ${'nova flavor-show $f &> /dev/null'} - if [ $? == 1 ]; then - ${'nova flavor-create ${FLAVORS[$f]}'} - ${'nova flavor-key $f set class=`expr $f : "\(.*\)\."`-hosts'} - fi - done - # Register Hosts - ${'while [ ${#BM_HOSTS[@]} -gt 0 ]'} - do - LIST=`nova host-list` - for i in ${'${!BM_HOSTS[@]}'} - do - HOST_ID=`expr "$LIST" : ".*\(overcloud-novacompute$i-\(\w\)\{6\}\)"` - if [ $HOST_ID ] - then - # Check to see if this host is already added to this aggregate - ${'AGG_DETAILS=`nova aggregate-details ${EXISTING_AGGREGATES[${BM_HOSTS[$i]}]}`'} - ${'if [ `expr "$AGG_DETAILS" : ".*$HOST_ID"` ]'} - then - ${'nova aggregate-add-host ${EXISTING_AGGREGATES[${BM_HOSTS[$i]}]} $HOST_ID'} - fi - unset BM_HOSTS[$i] - fi - done - sleep 1m - done - echo "Resource Registration Complete" - else - cat /var/cache/heat-cfntools/last_metadata | python -c 'import sys;import json;print json.load(sys.stdin)["AWS::CloudFormation::Init"]["config"]["files"]["/root/tuskar/provision.conf"]["content"]' > /root/tuskar/provision.conf - fi - sleep 1m - done -\ \ No newline at end of file diff --git a/tuskar/cmd/__init__.py b/tuskar/cmd/__init__.py deleted file mode 100644 index d2434949..00000000 --- a/tuskar/cmd/__init__.py +++ /dev/null @@ -1,26 +0,0 @@ -# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. -# All Rights Reserved. -# -# 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. - -# TODO(deva): move eventlet imports to tuskar.__init__ once we move to PBR -import os - -os.environ['EVENTLET_NO_GREENDNS'] = 'yes' - -import eventlet - -eventlet.monkey_patch(os=False) - -from tuskar.openstack.common import gettextutils -gettextutils.install('tuskar') diff --git a/tuskar/cmd/api.py b/tuskar/cmd/api.py deleted file mode 100644 index 36239e2c..00000000 --- a/tuskar/cmd/api.py +++ /dev/null @@ -1,53 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2013 Hewlett-Packard Development Company, L.P. -# All Rights Reserved. -# -# 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 Tuskar Service API.""" - -import logging -import sys -from wsgiref import simple_server - -from oslo_config import cfg - -from tuskar.api import app -from tuskar.common import service as tuskar_service -from tuskar.openstack.common import log - - -def main(argv=None): - - if argv is None: - argv = sys.argv - - tuskar_service.prepare_service(argv) - - # Build and start the WSGI app - host = cfg.CONF.tuskar_api_bind_ip - port = cfg.CONF.tuskar_api_port - wsgi = simple_server.make_server( - host, port, - app.VersionSelectorApplication()) - - LOG = log.getLogger(__name__) - LOG.info("Serving on http://%s:%s" % (host, port)) - LOG.info("Configuration:") - cfg.CONF.log_opt_values(LOG, logging.INFO) - - try: - wsgi.serve_forever() - except KeyboardInterrupt: - pass diff --git a/tuskar/cmd/dbsync.py b/tuskar/cmd/dbsync.py deleted file mode 100644 index 1fc0678f..00000000 --- a/tuskar/cmd/dbsync.py +++ /dev/null @@ -1,37 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2013 Hewlett-Packard Development Company, L.P. -# All Rights Reserved. -# -# 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. - -""" -Run storage database migration. -""" -import sys - -from tuskar.common import service -from tuskar.db import migration -from tuskar.db.sqlalchemy.api import get_backend - - -def main(argv=None): - - if argv is None: - argv = sys.argv - - # Prepare the Tuskar service and load the database backend. - service.prepare_service(argv) - get_backend() - - migration.db_sync() diff --git a/tuskar/cmd/delete_roles.py b/tuskar/cmd/delete_roles.py deleted file mode 100644 index 8293d636..00000000 --- a/tuskar/cmd/delete_roles.py +++ /dev/null @@ -1,59 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2015 Red Hat -# -# 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 __future__ import print_function - -import sys - -from oslo_config import cfg - -from tuskar.common import service -from tuskar.storage import delete_roles as dr - - -def _print_names(message, names): - print("{0}: \n {1}".format(message, '\n '.join(names))) - -cfg.CONF.register_cli_opt(cfg.BoolOpt('dryrun', default=False)) - -cfg.CONF.register_cli_opt(cfg.MultiStrOpt( - 'uuid', short='u', help='List of role uuid to delete')) - -cfg.CONF.register_cli_opt(cfg.BoolOpt( - 'all', default=False, - help='If specified, all roles will be deleted; overrides the ' - '--uuids argument')) - - -def main(argv=None): - if argv is None: - argv = sys.argv - - service.prepare_service(argv) - - if not cfg.CONF.uuid and not cfg.CONF.all: - sys.stderr.write( - 'Either specific roles must be specified using the --uuid ' - 'argument or --all must be specified\n') - sys.exit(1) - - if cfg.CONF.uuid: - deleted = dr.delete_roles(cfg.CONF.uuid, noop=cfg.CONF.dryrun) - else: - deleted = dr.delete_all_roles(noop=cfg.CONF.dryrun) - - if len(deleted): - _print_names("Deleted", deleted) diff --git a/tuskar/cmd/load_role.py b/tuskar/cmd/load_role.py deleted file mode 100644 index aca47827..00000000 --- a/tuskar/cmd/load_role.py +++ /dev/null @@ -1,58 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2015 Red Hat -# All Rights Reserved. -# -# 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 __future__ import print_function - -import sys - -from oslo_config import cfg - -from tuskar.common import service -from tuskar.storage.load_roles import load_role - - -def _print_names(message, names): - print("{0}: \n {1}".format(message, '\n '.join(names))) - -cfg.CONF.register_cli_opt(cfg.StrOpt('name', short='n', dest='name')) -cfg.CONF.register_cli_opt(cfg.StrOpt( - 'filepath', dest='file_path', short='f')) -cfg.CONF.register_cli_opt(cfg.StrOpt('relative-path', dest='relative_path')) -cfg.CONF.register_cli_opt(cfg.MultiStrOpt('extra-data', short='e')) - - -def main(argv=None): - if argv is None: - argv = sys.argv - - service.prepare_service(argv) - if not cfg.CONF.file_path: - sys.stderr.write("You must specify the path to the main template " - "which defines this role.") - sys.exit(1) - - name = cfg.CONF.name if cfg.CONF.name else '' - relative_path = cfg.CONF.relative_path if cfg.CONF.relative_path else None - created, updated = load_role(name, cfg.CONF.file_path, - extra_data=cfg.CONF.extra_data, - relative_path=relative_path) - - if len(created): - _print_names("Created", created) - - if len(updated): - _print_names("Updated", updated) diff --git a/tuskar/cmd/load_roles.py b/tuskar/cmd/load_roles.py deleted file mode 100644 index 80217d35..00000000 --- a/tuskar/cmd/load_roles.py +++ /dev/null @@ -1,69 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2013 Hewlett-Packard Development Company, L.P. -# All Rights Reserved. -# -# 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 __future__ import print_function - -import sys - -from oslo_config import cfg - -from tuskar.common import service -from tuskar.storage.load_roles import load_roles - - -def _print_names(message, names): - print("{0}: \n {1}".format(message, '\n '.join(names))) - - -seed_help = ('Full path to the template that should be loaded ' - 'as the master seed') -resource_registry_help = ('Path to the Heat environment file which maps the' - 'custom resource types to template paths.') -cfg.CONF.register_cli_opt(cfg.StrOpt('master-seed', dest='master_seed', - help=seed_help)) -cfg.CONF.register_cli_opt(cfg.StrOpt('resource-registry', - dest='resource_registry', - help=resource_registry_help)) -cfg.CONF.register_cli_opt(cfg.MultiStrOpt('role', short='r')) -cfg.CONF.register_cli_opt(cfg.MultiStrOpt('role-extra', short='re')) - - -def main(argv=None): - - if argv is None: - argv = sys.argv - - service.prepare_service(argv) - - if cfg.CONF.master_seed and not cfg.CONF.resource_registry: - sys.stderr.write("When using `master-seed` you must also specify " - "`resource-registry`.") - sys.exit(1) - - all_roles, created, updated = load_roles( - cfg.CONF.role, - seed_file=cfg.CONF.master_seed, - resource_registry_path=cfg.CONF.resource_registry, - role_extra=cfg.CONF.role_extra) - - if len(created): - _print_names("Created", created) - - if len(updated): - _print_names("Updated", updated) - - print("Imported {0} roles".format(len(all_roles))) diff --git a/tuskar/cmd/load_seed.py b/tuskar/cmd/load_seed.py deleted file mode 100644 index 272545fb..00000000 --- a/tuskar/cmd/load_seed.py +++ /dev/null @@ -1,60 +0,0 @@ -#!/usr/bin/env 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. - -from __future__ import print_function - -import sys - -from oslo_config import cfg - -from tuskar.common import service -from tuskar.storage.load_roles import load_seed - - -def _print_names(message, names): - print("{0}: \n {1}".format(message, '\n '.join(names))) - - -seed_help = ('Full path to the template that should be loaded ' - 'as the master seed') -resource_registry_help = ('Path to the Heat environment file which maps the' - 'custom resource types to template paths.') -cfg.CONF.register_cli_opt(cfg.StrOpt('master-seed', dest='master_seed', - help=seed_help)) -cfg.CONF.register_cli_opt(cfg.StrOpt('resource-registry', - dest='resource_registry', - help=resource_registry_help)) - - -def main(argv=None): - - if argv is None: - argv = sys.argv - - service.prepare_service(argv) - - if not cfg.CONF.master_seed or not cfg.CONF.resource_registry: - sys.stderr.write("You must specify both `master-seed` and " - "`resource-registry`.") - sys.exit(1) - - created, updated = load_seed( - seed_file=cfg.CONF.master_seed, - resource_registry_path=cfg.CONF.resource_registry) - - if len(created): - _print_names("Created", created) - - if len(updated): - _print_names("Updated", updated) diff --git a/tuskar/common/__init__.py b/tuskar/common/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tuskar/common/config.py b/tuskar/common/config.py deleted file mode 100644 index 045135c2..00000000 --- a/tuskar/common/config.py +++ /dev/null @@ -1,30 +0,0 @@ -# Copyright 2010 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# All Rights Reserved. -# Copyright 2012 Red Hat, Inc. -# -# 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 oslo_config import cfg -from oslo_db import options as db_options - -from tuskar import version - - -def parse_args(argv, default_config_files=None): - db_options.set_defaults(cfg.CONF, sqlite_db='tuskar.sqlite') - - cfg.CONF(argv[1:], - project='tuskar', - version=version.version_info.release_string(), - default_config_files=default_config_files) diff --git a/tuskar/common/exception.py b/tuskar/common/exception.py deleted file mode 100644 index 0bd94a9f..00000000 --- a/tuskar/common/exception.py +++ /dev/null @@ -1,214 +0,0 @@ -# -# 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. - -"""Tuskar base exception handling. - -Includes decorator for re-raising Tuskar-type exceptions. - -SHOULD include dedicated exception logging. -""" - -from oslo_config import cfg -import six - -from tuskar.openstack.common.gettextutils import _ # noqa -from tuskar.openstack.common import log as logging - - -LOG = logging.getLogger(__name__) - -exc_log_opts = [ - cfg.BoolOpt('fatal_exception_format_errors', - default=False, - help='make exception message format errors fatal'), -] - -CONF = cfg.CONF -CONF.register_opts(exc_log_opts) - - -def _cleanse_dict(original): - """Strip all admin_password, new_pass, rescue_pass keys from a dict.""" - return dict((k, v) for k, v in original.iteritems() if "_pass" not in k) - - -class TuskarException(Exception): - """Base Tuskar Exception - - To correctly use this class, inherit from it and define - a 'message' property. That message will get printf'd - with the keyword arguments provided to the constructor. - - """ - message = _("An unknown exception occurred.") - code = 500 - headers = {} - safe = False - - def __init__(self, message=None, **kwargs): - self.kwargs = kwargs - - if 'code' not in self.kwargs: - try: - self.kwargs['code'] = self.code - except AttributeError: - pass - - if not message: - try: - message = self.message % kwargs - - except Exception as e: - # kwargs doesn't match a variable in the message - # log the issue and the kwargs - LOG.exception(_('Exception in string format operation')) - for name, value in kwargs.iteritems(): - LOG.error("%s: %s" % (name, value)) - - if CONF.fatal_exception_format_errors: - raise e - else: - # at least get the core message out if something happened - message = self.message - - super(TuskarException, self).__init__(message) - - def format_message(self): - if self.__class__.__name__.endswith('_Remote'): - return self.args[0] - else: - return six.text_type(self) - - -class Invalid(TuskarException): - message = _("Invalid.") - code = 400 - - -class NotAuthorized(TuskarException): - message = _("Not authorized.") - code = 403 - - -class AdminRequired(NotAuthorized): - message = _("User does not have admin privileges") - - -class PolicyNotAuthorized(NotAuthorized): - message = _("Policy doesn't allow %(action)s to be performed.") - - -class NotFound(TuskarException): - message = _("Resource could not be found.") - code = 404 - - -class OvercloudRoleNotFound(NotFound): - message = _('Overcloud role could not be found.') - - -class OvercloudRoleCountNotFound(NotFound): - message = _('Overcloud role count could not be found.') - - -class OvercloudNotFound(NotFound): - message = _('Overcloud could not be found.') - - -class DuplicateEntry(TuskarException): - message = _("Duplicate entry found.") - code = 409 - - -class OvercloudRoleExists(DuplicateEntry): - message = _("Overcloud role with name %(name)s already exists.") - - -class OvercloudRoleInUse(Invalid): - message = _('Role %(name)s is in use by an overcloud.') - - -class OvercloudRoleCountExists(DuplicateEntry): - message = _("Count for overcloud %(cloud)s and " - "role %(role)s already exists.") - - -class OvercloudExists(DuplicateEntry): - message = _("Overcloud with name %(name)s already exists.") - - -class DuplicateAttribute(DuplicateEntry): - message = _("One or more attributes is duplicated for the overcloud.") - - -class ConfigNotFound(TuskarException): - message = _("Could not find config at %(path)s") - - -class StackNotFound(NotFound): - message = _("The Stack for this Overcloud can't be found.") - - -class StackAlreadyCreated(DuplicateEntry): - message = _("The Stack for this Overcloud already exists.") - - -class ParseCountsAndFlavorsFailed(DuplicateEntry): - message = _("Parsing of counts and flavors from roles failed.") - - -class HeatTemplateCreateFailed(Invalid): - message = _("The Heat template failed to create.") - - -class HeatTemplateValidateFailed(Invalid): - message = _("Validation of the Heat template failed.") - - -class HeatStackProcessingAttributesFailed(Invalid): - message = _("Processing of Heat stack attributes failed") - - -class HeatStackUpdateFailed(Invalid): - message = _("The Heat stack failed to update.") - - -class HeatStackCreateFailed(Invalid): - message = _("The Heat stack failed to update.") - - -class HeatStackDeleteFailed(Invalid): - message = _("The Heat stack failed to delete.") - - -class PlanNotFound(NotFound): - message = _('Plan could not be found.') - - -class PlanExists(DuplicateEntry): - message = _("Plan with name %(name)s already exists.") - - -class PlanAlreadyHasRole(DuplicateEntry): - message = _("Plan %(plan_uuid)s already has role %(role_uuid)s.") - - -class PlanParametersNotExist(Invalid): - message = _("There are no parameters named %(param_names)s" - " in plan %(plan_uuid)s.") - - -class InvalidTemplateExtraStoredName(TuskarException): - # code 500 definitely internal server error. Default for TuskarException - message = _("Unexpected name for stored template extra file " - "%(name)s . Expected to start with 'extra_'") diff --git a/tuskar/common/paths.py b/tuskar/common/paths.py deleted file mode 100644 index 66fea171..00000000 --- a/tuskar/common/paths.py +++ /dev/null @@ -1,66 +0,0 @@ -# Copyright 2010 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# All Rights Reserved. -# Copyright 2012 Red Hat, Inc. -# -# 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 oslo_config import cfg - -path_opts = [ - cfg.StrOpt('pybasedir', - default=os.path.abspath(os.path.join(os.path.dirname(__file__), - '../')), - help='Directory where the nova python module is installed'), - cfg.StrOpt('bindir', - default='$pybasedir/bin', - help='Directory where nova binaries are installed'), - cfg.StrOpt('state_path', - default='$pybasedir', - help="Top-level directory for maintaining nova's state"), -] - -CONF = cfg.CONF -CONF.register_opts(path_opts) - - -def basedir_def(*args): - """Return an uninterpolated path relative to $pybasedir.""" - return os.path.join('$pybasedir', *args) - - -def bindir_def(*args): - """Return an uninterpolated path relative to $bindir.""" - return os.path.join('$bindir', *args) - - -def state_path_def(*args): - """Return an uninterpolated path relative to $state_path.""" - return os.path.join('$state_path', *args) - - -def basedir_rel(*args): - """Return a path relative to $pybasedir.""" - return os.path.join(CONF.pybasedir, *args) - - -def bindir_rel(*args): - """Return a path relative to $bindir.""" - return os.path.join(CONF.bindir, *args) - - -def state_path_rel(*args): - """Return a path relative to $state_path.""" - return os.path.join(CONF.state_path, *args) diff --git a/tuskar/common/service.py b/tuskar/common/service.py deleted file mode 100644 index d6c29a14..00000000 --- a/tuskar/common/service.py +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2012 eNovance -# -# 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 socket - -from oslo_config import cfg - -from tuskar.openstack.common import log - -cfg.CONF.register_opts([ - cfg.IntOpt('periodic_interval', - default=60, - help='seconds between running periodic tasks'), - cfg.StrOpt('host', - default=socket.getfqdn(), - help='Name of this node. This can be an opaque identifier. ' - 'It is not necessarily a hostname, FQDN, or IP address. ' - 'However, the node name must be valid within ' - 'an AMQP key, and if using ZeroMQ, a valid ' - 'hostname, FQDN, or IP address'), -]) - - -def prepare_service(argv=[]): - cfg.set_defaults(log.log_opts, - default_log_levels=['amqp=WARN', - 'amqplib=WARN', - 'qpid.messaging=INFO', - 'sqlalchemy=WARN', - 'keystoneclient=INFO', - 'stevedore=INFO', - 'eventlet.wsgi.server=WARN', - 'iso8601=WARN' - ]) - cfg.CONF(argv[1:], project='tuskar') - log.setup('tuskar') diff --git a/tuskar/common/utils.py b/tuskar/common/utils.py deleted file mode 100644 index c814c5b1..00000000 --- a/tuskar/common/utils.py +++ /dev/null @@ -1,175 +0,0 @@ -# Copyright 2010 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# Copyright 2011 Justin Santa Barbara -# Copyright (c) 2012 NTT DOCOMO, INC. -# All Rights Reserved. -# -# 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. - -"""Utilities and helper functions.""" - -import os -import re - -from oslo_config import cfg - -from tuskar.common import exception -from tuskar.openstack.common import log as logging - -CONF = cfg.CONF -LOG = logging.getLogger(__name__) - - -class LazyPluggable(object): - """A pluggable backend loaded lazily based on some value.""" - - def __init__(self, pivot, config_group=None, **backends): - self.__backends = backends - self.__pivot = pivot - self.__backend = None - self.__config_group = config_group - - def __get_backend(self): - if not self.__backend: - if self.__config_group is None: - backend_name = CONF[self.__pivot] - else: - backend_name = CONF[self.__config_group][self.__pivot] - if backend_name not in self.__backends: - msg = _('Invalid backend: %s') % backend_name - raise exception.TuskarException(msg) - - backend = self.__backends[backend_name] - if isinstance(backend, tuple): - name = backend[0] - fromlist = backend[1] - else: - name = backend - fromlist = backend - - self.__backend = __import__(name, None, None, fromlist) - return self.__backend - - def __getattr__(self, key): - backend = self.__get_backend() - return getattr(backend, key) - - -def is_int_like(val): - """Check if a value looks like an int.""" - try: - return str(int(val)) == str(val) - except Exception: - return False - - -def read_cached_file(filename, cache_info, reload_func=None): - """Read from a file if it has been modified. - - :param cache_info: dictionary to hold opaque cache. - :param reload_func: optional function to be called with data when - file is reloaded due to a modification. - - :returns: data from file - - """ - mtime = os.path.getmtime(filename) - if not cache_info or mtime != cache_info.get('mtime'): - LOG.debug("Reloading cached file %s" % filename) - with open(filename) as fap: - cache_info['data'] = fap.read() - cache_info['mtime'] = mtime - if reload_func: - reload_func(cache_info['data']) - return cache_info['data'] - - -def resolve_role_extra_name_from_path(role_extra_path): - """Get the name we will use to store a role-extra file based on its name - - We want to capture the filename and extension into the name of the - store role-extra object. The name is constructed by prepending 'extra_' - and using the final '_' to include the extension. Any paths used before - the filename are dropped at this point (these are resolved relative to - a given template, i.e. where they are used and referenced). - - For instance 'hieradata/compute.yaml' is stored as - 'extra_compute_yaml'. - """ - name_ext = os.path.basename(role_extra_path) - name, extension = os.path.splitext(name_ext) - return "extra_%s_%s" % (name, extension.replace('.', '')) - - -def resolve_template_file_name_from_role_extra_name(role_extra_name): - """Return the name of the included file based on the role-extra name - - The internal representation for a given role-extra file encodes the - file extension into the name. For instance 'compute.yaml' - is stored as 'extra_compute_yaml'. Here, given the stored name, - return name.extension - - Raises a InvalidTemplateExtraStoredName exception if the given - role_extra_name doesn't start with 'extra_' as a prefix. - - :param role_extra_name: the name as stored for the role-extra - :type role_extra_name: string - - :return: the name as used in the template - :rtype: string - - Returns 'compute.yaml' from 'extra_compute_yaml'. - """ - if not role_extra_name.startswith("extra_"): - raise exception.InvalidTemplateExtraStoredName(name=role_extra_name) - role_extra_name = role_extra_name[6:] - name_extension = role_extra_name.rsplit("_", 1) - if name_extension[1] == '': - return name_extension[0] - return ".".join(name_extension) - - -def resolve_template_extra_data(template, template_extra=[]): - """Match all occurences of get_file against the stored role-extra data. - - :param template: the given heat template to search for "get_file"(s) - :type template: tuskar.storage.models.StoredFile - - :param template_extra: a list of all stored role-extra data - :type template_extra: list of tuskar.storage.models.StoredFile - - :return: a dict of 'name'=>'path' for each matched role-extra - :rtype: dict - - Using regex, compile a list of all occurences of 'get_file:' in the - template. Match each of the stored role-extra data based on their name. - - For each match capture the full path as it appears in the template - and couple it to the name of the role-extra we have on record. For - example: - - [{'extra_common_yaml': 'hieradata/common.yaml'}, - {'extra_object_yaml': 'hieradata/object.yaml'}] - - """ - included_files = [] - all_get_files = re.findall("get_file:.*\n", template.contents) - # looks like: ["get_file: hieradata/common.yaml}", ... ] - for te in template_extra: - token = resolve_template_file_name_from_role_extra_name(te.name) - for get_file in all_get_files: - if re.match("get_file:.*%s[}]*\n" % token, get_file): - path = get_file.replace("get_file:", "").lstrip().replace( - "}", "").rstrip() - included_files.append({te.name: path}) - return included_files diff --git a/tuskar/db/__init__.py b/tuskar/db/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tuskar/db/api.py b/tuskar/db/api.py deleted file mode 100644 index e54431ab..00000000 --- a/tuskar/db/api.py +++ /dev/null @@ -1,41 +0,0 @@ -# -# Copyright 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. -""" -Base classes for storage engines -""" - -import abc - -from oslo_config import cfg -from oslo_db import api as db_api -import six - -_BACKEND_MAPPING = {'sqlalchemy': 'tuskar.db.sqlalchemy.api'} -IMPL = db_api.DBAPI.from_config(cfg.CONF, backend_mapping=_BACKEND_MAPPING, - lazy=True) - - -def get_instance(): - """Return a DB API instance.""" - return IMPL - - -@six.add_metaclass(abc.ABCMeta) -class Connection(object): - """Base class for storage system connections.""" - - @abc.abstractmethod - def __init__(self): - """Constructor.""" diff --git a/tuskar/db/migration.py b/tuskar/db/migration.py deleted file mode 100644 index 29f6a66b..00000000 --- a/tuskar/db/migration.py +++ /dev/null @@ -1,36 +0,0 @@ -# Copyright 2010 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# All Rights Reserved. -# -# 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. - -"""Database setup and migration commands.""" - -from tuskar.common import utils - -IMPL = utils.LazyPluggable( - pivot='backend', - config_group='database', - sqlalchemy='tuskar.db.sqlalchemy.migration') - -INIT_VERSION = 0 - - -def db_sync(version=None): - """Migrate the database to `version` or the most recent version.""" - return IMPL.db_sync(version=version) - - -def db_version(): - """Display the current database version.""" - return IMPL.db_version() diff --git a/tuskar/db/sqlalchemy/__init__.py b/tuskar/db/sqlalchemy/__init__.py deleted file mode 100644 index d6c8a856..00000000 --- a/tuskar/db/sqlalchemy/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -import logging - -# There is probably a better way of doing this, but it was being done -# in the models.py while I was cleaning that up. I suspect this will -# be moved elsewhere in the future. - -logging.basicConfig() -logging.getLogger('sqlalchemy.engine').setLevel(logging.INFO) diff --git a/tuskar/db/sqlalchemy/api.py b/tuskar/db/sqlalchemy/api.py deleted file mode 100644 index a5be1d0b..00000000 --- a/tuskar/db/sqlalchemy/api.py +++ /dev/null @@ -1,420 +0,0 @@ -# -# Copyright 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. - -"""SQLAlchemy storage backend.""" - -import threading - -from oslo_config import cfg -from oslo_db import exception as db_exception -from oslo_db.sqlalchemy import session as db_session -from sqlalchemy.exc import IntegrityError -from sqlalchemy.orm.exc import NoResultFound -from sqlalchemy.orm import subqueryload - -from tuskar.common import exception -from tuskar.db import api -from tuskar.db.sqlalchemy import models -from tuskar.openstack.common import log - - -CONF = cfg.CONF -LOG = log.getLogger(__name__) - - -_FACADE = None -_LOCK = threading.Lock() - - -def _create_facade_lazily(): - global _LOCK, _FACADE - if _FACADE is None: - with _LOCK: - if _FACADE is None: - _FACADE = db_session.EngineFacade.from_config(CONF) - - return _FACADE - - -def get_engine(): - facade = _create_facade_lazily() - return facade.get_engine() - - -def get_session(**kwargs): - facade = _create_facade_lazily() - return facade.get_session(**kwargs) - - -def get_backend(): - """The backend is this module itself.""" - return Connection() - - -def model_query(model, *args, **kwargs): - """Query helper for simpler session usage. - - :param session: if present, the session to use - """ - - session = kwargs.get('session') or get_session() - query = session.query(model, *args) - return query - - -class Connection(api.Connection): - """SqlAlchemy connection.""" - - def __init__(self): - - # The superclass __init__ is abstract and prevents the class - # from being instantiated unless we explicitly remove that - # here. - pass - - def get_overcloud_roles(self): - """Returns all overcloud roles known to Tuskar. - - :return: list of roles; empty list if none are found - :rtype: list of tuskar.db.sqlalchemy.models.OvercloudRole - """ - - session = get_session() - roles = session.query(models.OvercloudRole).all() - session.close() - return roles - - def get_overcloud_role_by_id(self, role_id): - """Single overcloud role query. - - :return: role if one exists with the given ID - :rtype: tuskar.db.sqlalchemy.models.OvercloudRole - - :raises: tuskar.common.exception.OvercloudRoleNotFound: if no - role with the given ID exists - """ - - session = get_session() - try: - query = session.query(models.OvercloudRole).filter_by( - id=role_id) - result = query.one() - - except NoResultFound: - raise exception.OvercloudRoleNotFound() - - finally: - session.close() - - return result - - def create_overcloud_role(self, overcloud_role): - """Creates a new overcloud role in the database. - - :param overcloud_role: role instance to save - :type overcloud_role: tuskar.db.sqlalchemy.models.OvercloudRole - - :return: the role instance that was saved with its - ID populated - :rtype: tuskar.db.sqlalchemy.models.OvercloudRole - - :raises: tuskar.common.exception.OvercloudRoleExists: if a role - with the given name exists - """ - session = get_session() - session.begin() - - try: - session.add(overcloud_role) - session.commit() - return overcloud_role - - except db_exception.DBDuplicateEntry: - raise exception.OvercloudRoleExists(name=overcloud_role.name) - - finally: - session.close() - - def update_overcloud_role(self, updated): - """Updates the given overcloud role. - - :param updated: role instance containing changed values - :type updated: tuskar.db.sqlalchemy.models.OvercloudRole - - :return: the role instance that was saved - :rtype: tuskar.db.sqlalchemy.models.OvercloudRole - - :raises: tuskar.common.exception.OvercloudRoleNotFound if there - is no role with the given ID - """ - existing = self.get_overcloud_role_by_id(updated.id) - - for a in ('name', 'description', 'image_name', 'flavor_id'): - if getattr(updated, a) is not None: - setattr(existing, a, getattr(updated, a)) - - return self.create_overcloud_role(existing) - - def delete_overcloud_role_by_id(self, role_id): - """Deletes an overcloud role from the database. - - :param role_id: database ID of the role - :type role_id: int - - :raises: tuskar.common.exception.OvercloudRoleNotFound if there - is no role with the given ID - """ - role = self.get_overcloud_role_by_id(role_id) - - session = get_session() - session.begin() - - try: - session.delete(role) - session.commit() - - except db_exception.DBError as e: - if isinstance(e.inner_exception, IntegrityError): - raise exception.OvercloudRoleInUse(name=role.name) - else: - raise - - finally: - session.close() - - def get_overclouds(self): - """Returns all overcloud instances from the database. - - :return: list of overcloud instances; empty list if none are found - :rtype: list of tuskar.db.sqlalchemy.models.Overcloud - """ - - session = get_session() - overclouds = ( - session.query(models.Overcloud). - options(subqueryload(models.Overcloud.attributes)). - options(subqueryload(models.Overcloud.counts)). - all() - ) - session.close() - return overclouds - - def get_overcloud_by_id(self, overcloud_id): - """Returns a specific overcloud instance. - - :return: overcloud if one exists with the given ID - :rtype: tuskar.db.sqlalchemy.models.Overcloud - - :raises: tuskar.common.exception.OvercloudNotFound: if no - overcloud with the given ID exists - """ - - session = get_session() - try: - query = ( - session.query(models.Overcloud). - options(subqueryload(models.Overcloud.attributes)). - options(subqueryload(models.Overcloud.counts)). - options(subqueryload('counts.overcloud_role')). - filter_by(id=overcloud_id) - ) - result = query.one() - - except NoResultFound: - raise exception.OvercloudNotFound() - - finally: - session.close() - - return result - - def create_overcloud(self, overcloud): - """Creates a new overcloud instance to the database. - - :param overcloud: overcloud instance to save - :type overcloud: tuskar.db.sqlalchemy.models.Overcloud - - :return: the overcloud instance that was saved with its - ID populated - :rtype: tuskar.db.sqlalchemy.models.Overcloud - - :raises: tuskar.common.exception.OvercloudExists: if an overcloud - role with the given name exists - """ - session = get_session() - session.begin() - - try: - session.add(overcloud) - session.commit() - - # Reload from the database to load all of the joined table data - overcloud = self.get_overcloud_by_id(overcloud.id) - return overcloud - - except db_exception.DBDuplicateEntry as e: - if 'name' in e.columns: - raise exception.OvercloudExists(name=overcloud.name) - else: - raise exception.DuplicateAttribute() - - finally: - session.close() - - def update_overcloud(self, updated): - """Updates the configuration of an existing overcloud. - - The specified parameter is an instance of the domain model with - the changes to be made. Updating follows the given rules: - - The updated overcloud must include the ID of the overcloud - being updated. - - Any direct attributes on the overcloud that are *not* being changed - should have their values set to None. - - For attributes and counts, only differences are specified according - to the following rules: - - New items are specified in the updated object's lists - - Updated items are specified in the updated object's lists with - the new value and existing key - - Removed items are specified in the updated object's lists with - a value of None (zero in the case of a count). - - Unchanged items are *not* specified. - - :param updated: overcloud instance containing changed values - :type updated: tuskar.db.sqlalchemy.models.Overcloud - - :return: the overcloud instance that was saved - :rtype: tuskar.db.sqlalchemy.models.Overcloud - - :raises: tuskar.common.exception.OvercloudNotFound if there - is no overcloud with the given ID - """ - - existing = self.get_overcloud_by_id(updated.id) - - session = get_session() - session.begin() - - try: - # First class attributes on the overcloud - for name in ('stack_id', 'name', 'description'): - new_value = getattr(updated, name) - if new_value is not None: - setattr(existing, name, new_value) - - self._update_overcloud_attributes(existing, session, updated) - self._update_overcloud_counts(existing, session, updated) - - # Save the modified object - session.add(existing) - session.commit() - - return existing - - finally: - session.close() - - @staticmethod - def _update_overcloud_attributes(existing, session, updated): - if updated.attributes is not None: - existing_keys = [a.key for a in existing.attributes] - existing_attributes_by_key = ( - dict((a.key, a) for a in existing.attributes)) - - delete_keys = [] - for a in updated.attributes: - - # Deleted - if a.value is None: - delete_keys.append(a.key) - continue - - # Updated - if a.key in existing_keys: - updating = existing_attributes_by_key[a.key] - updating.value = a.value - session.add(updating) - continue - - # Added - if a.key not in existing_keys: - existing_attributes_by_key[a.key] = a - a.overcloud_id = updated.id - existing.attributes.append(a) - session.add(a) - continue - - # Purge deleted attributes - for a in existing.attributes: - if a.key in delete_keys: - existing.attributes.remove(a) - session.delete(a) - - @staticmethod - def _update_overcloud_counts(existing, session, updated): - if updated.counts is not None: - existing_count_role_ids = [c.overcloud_role_id - for c in existing.counts] - existing_counts_by_role_id = ( - dict((c.overcloud_role_id, c) for c in existing.counts)) - - delete_role_ids = [] - for c in updated.counts: - - # Deleted - if c.num_nodes == 0: - delete_role_ids.append(c.overcloud_role_id) - continue - - # Updated - if c.overcloud_role_id in existing_count_role_ids: - updating = existing_counts_by_role_id[c.overcloud_role_id] - updating.num_nodes = c.num_nodes - session.add(updating) - continue - - # New - if c.overcloud_role_id not in existing_count_role_ids: - existing_counts_by_role_id[c.overcloud_role_id] = c - c.overcloud_id = updated.id - existing.counts.append(c) - session.add(c) - continue - - # Purge deleted counts - for c in existing.counts: - if c.overcloud_role_id in delete_role_ids: - existing.counts.remove(c) - session.delete(c) - - def delete_overcloud_by_id(self, overcloud_id): - """Deletes a overcloud from the database. - - :param overcloud_id: database ID of the overcloud - :type overcloud_id: int - - :raises: tuskar.common.exception.OvercloudNotFound if there - is no overcloud with the given ID - """ - overcloud = self.get_overcloud_by_id(overcloud_id) - - session = get_session() - session.begin() - - try: - session.delete(overcloud) - session.commit() - - finally: - session.close() diff --git a/tuskar/db/sqlalchemy/migrate_repo/__init__.py b/tuskar/db/sqlalchemy/migrate_repo/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tuskar/db/sqlalchemy/migrate_repo/manage.py b/tuskar/db/sqlalchemy/migrate_repo/manage.py deleted file mode 100644 index f3bbed26..00000000 --- a/tuskar/db/sqlalchemy/migrate_repo/manage.py +++ /dev/null @@ -1,20 +0,0 @@ -# -# Copyright 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. - -from migrate.versioning.shell import main - - -if __name__ == '__main__': - main(debug='False', repository='.') diff --git a/tuskar/db/sqlalchemy/migrate_repo/migrate.cfg b/tuskar/db/sqlalchemy/migrate_repo/migrate.cfg deleted file mode 100644 index f54a8de1..00000000 --- a/tuskar/db/sqlalchemy/migrate_repo/migrate.cfg +++ /dev/null @@ -1,20 +0,0 @@ -[db_settings] -# Used to identify which repository this database is versioned under. -# You can use the name of your project. -repository_id=tuskar - -# The name of the database table used to track the schema version. -# This name shouldn't already be used by your project. -# If this is changed once a database is under version control, you'll need to -# change the table name in each database too. -version_table=migrate_version - -# When committing a change script, Migrate will attempt to generate the -# sql for all supported databases; normally, if one of them fails - probably -# because you don't have that database installed - it is ignored and the -# commit continues, perhaps ending successfully. -# Databases in this list MUST compile successfully during a commit, or the -# entire commit will fail. List the databases your application will actually -# be using to ensure your updates to that database work properly. -# This must be a list; example: ['postgres','sqlite'] -required_dbs=[] diff --git a/tuskar/db/sqlalchemy/migrate_repo/versions/001_init.py b/tuskar/db/sqlalchemy/migrate_repo/versions/001_init.py deleted file mode 100644 index 1795c1f4..00000000 --- a/tuskar/db/sqlalchemy/migrate_repo/versions/001_init.py +++ /dev/null @@ -1,133 +0,0 @@ -# -# 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 migrate.changeset import UniqueConstraint -from sqlalchemy import (Column, DateTime, ForeignKey, Integer, - MetaData, String, Table, Text) - -from tuskar.db.sqlalchemy import models -from tuskar.openstack.common.gettextutils import _ # noqa -from tuskar.openstack.common import log as logging - - -LOG = logging.getLogger(__name__) - -ENGINE = 'InnoDB' -CHARSET = 'utf8' - - -def upgrade(migrate_engine): - meta = MetaData() - meta.bind = migrate_engine - - overcloud_roles = Table( - 'overcloud_roles', - meta, - Column('id', Integer, primary_key=True, nullable=False), - Column('name', String(length=models.LENGTH_NAME), unique=True), - Column('description', String(length=models.LENGTH_DESCRIPTION)), - Column('image_name', String(length=64)), - Column('flavor_id', String(length=36)), - Column('created_at', DateTime), - Column('updated_at', DateTime), - mysql_engine=ENGINE, - mysql_charset=CHARSET, - ) - - overcloud = Table( - 'overclouds', - meta, - Column('id', Integer, primary_key=True, nullable=False), - Column('name', String(length=models.LENGTH_NAME), unique=True), - Column('description', String(length=models.LENGTH_DESCRIPTION)), - Column('stack_id', String(length=36)), - Column('created_at', DateTime), - Column('updated_at', DateTime), - mysql_engine=ENGINE, - mysql_charset=CHARSET, - ) - - overcloud_role_counts = Table( - 'overcloud_role_counts', - meta, - Column('id', Integer, primary_key=True, nullable=False), - Column('overcloud_role_id', - Integer, - ForeignKey('overcloud_roles.id'), - nullable=False), - Column('overcloud_id', - Integer, - ForeignKey('overclouds.id'), - nullable=False), - Column('num_nodes', Integer, nullable=False), - Column('created_at', DateTime), - Column('updated_at', DateTime), - mysql_engine=ENGINE, - mysql_charset=CHARSET, - ) - - overcloud_attributes = Table( - 'overcloud_attributes', - meta, - Column('id', Integer, primary_key=True, nullable=False), - Column('key', String(length=64), nullable=False), - Column('value', Text()), - Column('overcloud_id', - Integer, - ForeignKey('overclouds.id'), - nullable=False), - Column('created_at', DateTime), - Column('updated_at', DateTime), - mysql_engine=ENGINE, - mysql_charset=CHARSET, - ) - - tables = [overcloud_roles, overcloud, overcloud_role_counts, - overcloud_attributes] - - for table in tables: - try: - LOG.info(repr(table)) - table.create() - except Exception: - LOG.info(repr(table)) - LOG.exception(_('Exception while creating table.')) - raise - - indexes = [ - ] - - # There eventually needs to be a uniqueness constraint for - # overcloud role counts across overcloud role, - # overcloud, and profile. I'm skipping it for now until we decide - # on a plan for the node profiles in Icehouse. - # jdob, Jan 16, 2014 - - uniques = [ - UniqueConstraint('name', table=overcloud_roles, - name='uniq_overcloud_roles0name'), - UniqueConstraint('name', table=overcloud, - name='uniq_overcloud0name'), - UniqueConstraint('overcloud_id', 'key', table=overcloud_attributes, - name='uniq_overcloud_attributes0overcloud_name') - ] - - for index in indexes: - index.create(migrate_engine) - - for index in uniques: - index.create(migrate_engine) - - -def downgrade(migrate_engine): - raise NotImplementedError('Downgrade is unsupported.') diff --git a/tuskar/db/sqlalchemy/migrate_repo/versions/002_add_stored_file.py b/tuskar/db/sqlalchemy/migrate_repo/versions/002_add_stored_file.py deleted file mode 100644 index fcb1feee..00000000 --- a/tuskar/db/sqlalchemy/migrate_repo/versions/002_add_stored_file.py +++ /dev/null @@ -1,54 +0,0 @@ -# -# 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 sqlalchemy import Column, DateTime, Integer, MetaData, String, Table, Text - -from tuskar.openstack.common.gettextutils import _ # noqa -from tuskar.openstack.common import log as logging - - -LOG = logging.getLogger(__name__) - -ENGINE = 'InnoDB' -CHARSET = 'utf8' - - -def upgrade(migrate_engine): - meta = MetaData() - meta.bind = migrate_engine - - stored_file = Table( - 'stored_file', - meta, - Column('uuid', String(length=36), primary_key=True, nullable=False), - Column('contents', Text(), nullable=False), - Column('object_type', String(length=20), nullable=False), - Column('name', String(length=64), nullable=True), - Column('version', Integer(), nullable=True), - Column('created_at', DateTime), - Column('updated_at', DateTime), - mysql_engine=ENGINE, - mysql_charset=CHARSET, - ) - - try: - LOG.info(repr(stored_file)) - stored_file.create() - except Exception: - LOG.info(repr(stored_file)) - LOG.exception(_('Exception while creating table.')) - raise - - -def downgrade(migrate_engine): - raise NotImplementedError('Downgrade is unsupported.') diff --git a/tuskar/db/sqlalchemy/migrate_repo/versions/003_add_relative_path.py b/tuskar/db/sqlalchemy/migrate_repo/versions/003_add_relative_path.py deleted file mode 100644 index 84ee4db6..00000000 --- a/tuskar/db/sqlalchemy/migrate_repo/versions/003_add_relative_path.py +++ /dev/null @@ -1,24 +0,0 @@ -# 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 oslo_db.sqlalchemy import utils -from sqlalchemy import Column, String - - -def upgrade(migrate_engine): - stored_file = utils.get_table(migrate_engine, 'stored_file') - relative_path = Column('relative_path', String(256), nullable=True) - stored_file.create_column(relative_path) - - -def downgrade(migrate_engine): - raise NotImplementedError('Downgrade is unsupported.') diff --git a/tuskar/db/sqlalchemy/migrate_repo/versions/004_add_registry_path.py b/tuskar/db/sqlalchemy/migrate_repo/versions/004_add_registry_path.py deleted file mode 100644 index 5e1b6a8a..00000000 --- a/tuskar/db/sqlalchemy/migrate_repo/versions/004_add_registry_path.py +++ /dev/null @@ -1,24 +0,0 @@ -# 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 oslo_db.sqlalchemy import utils -from sqlalchemy import Column, String - - -def upgrade(migrate_engine): - stored_file = utils.get_table(migrate_engine, 'stored_file') - registry_path = Column('registry_path', String(256), nullable=True) - stored_file.create_column(registry_path) - - -def downgrade(migrate_engine): - raise NotImplementedError('Downgrade is unsupported.') diff --git a/tuskar/db/sqlalchemy/migrate_repo/versions/005_fix_stored_file_contents.py b/tuskar/db/sqlalchemy/migrate_repo/versions/005_fix_stored_file_contents.py deleted file mode 100644 index 5eeb7b9c..00000000 --- a/tuskar/db/sqlalchemy/migrate_repo/versions/005_fix_stored_file_contents.py +++ /dev/null @@ -1,43 +0,0 @@ -# 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 oslo_db.sqlalchemy import utils -from sqlalchemy import MetaData - -from tuskar.db.sqlalchemy.types import LongText -from tuskar.openstack.common.gettextutils import _ # noqa -from tuskar.openstack.common import log as logging - - -LOG = logging.getLogger(__name__) - -ENGINE = 'InnoDB' -CHARSET = 'utf8' - - -def upgrade(migrate_engine): - meta = MetaData() - meta.bind = migrate_engine - stored_file = utils.get_table(migrate_engine, 'stored_file') - - try: - LOG.info(repr(stored_file)) - col = stored_file._columns.get('contents') - col.alter(type=LongText) - except Exception: - LOG.info(repr(stored_file)) - LOG.exception(_('Exception while creating table.')) - raise - - -def downgrade(migrate_engine): - raise NotImplementedError('Downgrade is unsupported.') diff --git a/tuskar/db/sqlalchemy/migrate_repo/versions/__init__.py b/tuskar/db/sqlalchemy/migrate_repo/versions/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tuskar/db/sqlalchemy/migration.py b/tuskar/db/sqlalchemy/migration.py deleted file mode 100644 index 503db6da..00000000 --- a/tuskar/db/sqlalchemy/migration.py +++ /dev/null @@ -1,82 +0,0 @@ -# Copyright 2010 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# All Rights Reserved. -# -# 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 migrate import exceptions as versioning_exceptions -from migrate.versioning import api as versioning_api -from migrate.versioning.repository import Repository -import sqlalchemy - -from tuskar.common import exception -from tuskar.db import migration -from tuskar.db.sqlalchemy import api as sqla_api - -_REPOSITORY = None - -get_engine = sqla_api.get_engine - - -def db_sync(version=None): - if version is not None: - try: - version = int(version) - except ValueError: - raise exception.TuskarException(_("version should be an integer")) - - current_version = db_version() - repository = _find_migrate_repo() - if version is None or version > current_version: - return versioning_api.upgrade(get_engine(), repository, version) - else: - return versioning_api.downgrade(get_engine(), repository, - version) - - -def db_version(): - repository = _find_migrate_repo() - try: - return versioning_api.db_version(get_engine(), repository) - except versioning_exceptions.DatabaseNotControlledError: - meta = sqlalchemy.MetaData() - engine = get_engine() - meta.reflect(bind=engine) - tables = meta.tables - if len(tables) == 0: - db_version_control(migration.INIT_VERSION) - return versioning_api.db_version(get_engine(), repository) - else: - # Some pre-Essex DB's may not be version controlled. - # Require them to upgrade using Essex first. - raise exception.TuskarException( - _("Upgrade DB using Essex release first.")) - - -def db_version_control(version=None): - repository = _find_migrate_repo() - versioning_api.version_control(get_engine(), repository, version) - return version - - -def _find_migrate_repo(): - """Get the path for the migrate repository.""" - global _REPOSITORY - path = os.path.join(os.path.abspath(os.path.dirname(__file__)), - 'migrate_repo') - assert os.path.exists(path) - if _REPOSITORY is None: - _REPOSITORY = Repository(path) - return _REPOSITORY diff --git a/tuskar/db/sqlalchemy/models.py b/tuskar/db/sqlalchemy/models.py deleted file mode 100644 index 84f77ffb..00000000 --- a/tuskar/db/sqlalchemy/models.py +++ /dev/null @@ -1,246 +0,0 @@ -# -# 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. - -""" -Tuskar domain models for use with SQLAlchemy. -""" - -from oslo_config import cfg -from oslo_db.sqlalchemy import models - -from sqlalchemy import (Column, ForeignKey, Integer, String, Text) -from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy.orm import relationship - -from tuskar.db.sqlalchemy.types import LongText - - -sql_opts = [ - cfg.StrOpt('mysql_engine', - default='InnoDB', - help='MySQL engine') -] - -cfg.CONF.register_opts(sql_opts) - - -# Column lengths for common attributes -LENGTH_NAME = 64 -LENGTH_DESCRIPTION = 256 -LENGTH_HOST = 32 -LENGTH_USERNAME = 64 -LENGTH_PASSWORD = 64 - -TABLE_NODE_PROFILE = 'node_profiles' -TABLE_OVERCLOUD = 'overclouds' -TABLE_OVERCLOUD_ATTRIBUTES = 'overcloud_attributes' -TABLE_OVERCLOUD_ROLE = 'overcloud_roles' -TABLE_OVERCLOUD_ROLE_COUNT = 'overcloud_role_counts' - - -class TuskarBase(models.TimestampMixin, models.ModelBase): - """Base class for all Tuskar domain models.""" - metadata = None - - def as_dict(self): - d = dict([(c.name, self[c.name]) for c in self.__table__.columns]) - return d - - -Base = declarative_base(cls=TuskarBase) - - -class OvercloudRole(Base): - """Overcloud role domain model. - - Represents a type of entity that is deployed into the undercloud to create - the overcloud. For example, a controller or a compute node. - """ - - __tablename__ = TABLE_OVERCLOUD_ROLE - - # Unique identifier for the role - id = Column(Integer, primary_key=True) - - # User-readable display name of the role - name = Column(String(length=LENGTH_NAME), nullable=False, unique=True) - - # User-readable text describing what the role does - description = Column(String(length=LENGTH_DESCRIPTION)) - - # Name of the image, in Glance, that is used when creating an instance of - # this role. - # Note: This should be the image UUID, but due to Icehouse time constraints - # the user will create the image on their own with a pre-defined - # name and the image referenced through that. - # Note: In the future, we will likely support multiple images for a - # role, so this will likely change to its own table and a FK - # relationship. jdob, Jan 10, 2014 - image_name = Column(String(length=64)) - - # UUID of the flavor of node this role should be deployed on. - # Example: f03266e8-5c99-471c-9eac-375772b45a43 - # Note: In the future, we will likely support multiple flavors for - # a role, so this will likely change. jdob, Feb 5, 2014 - flavor_id = Column(String(length=36)) - - def __eq__(self, other): - return self.name == other.name - - -class OvercloudRoleCount(Base): - """Configuration for an overcloud role's deployment in an overcloud. - - Maps an overcloud role definition to number of instances to be - deployed into an overcloud. - - Note: In the future this will likely be enhanced to include the - flavor of node being deployed on. - """ - - __tablename__ = TABLE_OVERCLOUD_ROLE_COUNT - - # Unique identifier for the deployment configuration - id = Column(Integer, primary_key=True) - - # Role being configured - overcloud_role_id = Column( - Integer, - ForeignKey('%s.id' % TABLE_OVERCLOUD_ROLE), - nullable=False - ) - - # Overcloud in which the role is being deployed - overcloud_id = Column( - Integer, - ForeignKey('%s.id' % TABLE_OVERCLOUD, ondelete='CASCADE'), - nullable=False - ) - - # Number of nodes of this configuration that should be deployed - num_nodes = Column(Integer, nullable=False) - - # Reference to the full role (this is not the foreign key relationship, - # that's overcloud_role_id above, this is to eager load the role data). - overcloud_role = relationship(OvercloudRole.__name__) - - def __eq__(self, other): - return (self.overcloud_role_id == other.overcloud_role_id - and self.overcloud_id == other.overcloud_id) - - -class OvercloudAttribute(Base): - """Overcloud-level configuration attribute domain model. - - Contains a single configuration parameter for an overcloud. These - attributes include configuration for the overcloud database, - message bus, and keystone instance. - """ - - __tablename__ = TABLE_OVERCLOUD_ATTRIBUTES - - # Unique identifier for the overcloud - id = Column(Integer, primary_key=True) - - # Reference back to the overcloud being configured - overcloud_id = Column(Integer, - ForeignKey('%s.id' % TABLE_OVERCLOUD, - ondelete='CASCADE'), - nullable=False) - - # Identifier and value of the configuration attribute - key = Column(String(length=64), nullable=False) - value = Column(Text()) - - def __eq__(self, other): - return (self.overcloud_id == other.overcloud_id - and self.key == other.key) - - -class Overcloud(Base): - """Overcloud domain model. - - Represents the configuration of a cloud deployed into the undercloud by - Tuskar. - """ - - __tablename__ = TABLE_OVERCLOUD - - # Unique identifier for the overcloud - id = Column(Integer, primary_key=True) - - # UUID of the stack, in Heat, that was created from this configuration - stack_id = Column(String(length=36)) - - # User-readable name of the overcloud - name = Column(String(length=LENGTH_NAME), nullable=False, unique=True) - - # User-readable text describing the overcloud - description = Column(String(length=LENGTH_DESCRIPTION)) - - # List of configuration attributes for the overcloud - attributes = relationship(OvercloudAttribute.__name__, - cascade='all,delete') - - # List of counts of overcloud roles to deploy - counts = relationship(OvercloudRoleCount.__name__, - cascade='all,delete') - - def __eq__(self, other): - return self.name == other.name - - def as_dict(self): - d = dict([(c.name, self[c.name]) for c in self.__table__.columns]) - - # Foreign keys aren't picked up by the base as_dict, so add them in - # here - attribute_dicts = [a.as_dict() for a in self.attributes] - d['attributes'] = attribute_dicts - - count_dicts = [c.as_dict() for c in self.counts] - d['counts'] = count_dicts - - return d - - -class StoredFile(Base): - """Tuskar Stored File - - The StoredFile model is used by the tuskar.storage package and more - specifically for the SQLAlchemy storage driver. Simply put it is a - collection of text files with some metadata. - """ - - __tablename__ = "stored_file" - - #: UUID's are used as the unique identifier. - uuid = Column(String(length=36), primary_key=True) - - #: contents contains the full file contents as a string. - contents = Column(LongText(), nullable=False) - - #: Object type flags the type of file that this is, i.e. template or - #: environment file. - object_type = Column(String(length=20), nullable=False) - - #: Names provide a short human readable description of a file. - name = Column(String(length=64), nullable=True) - - #: Relative path to which the file belongs - relative_path = Column(String(length=256), nullable=True) - - #: Resource registry path for the file belongs - registry_path = Column(String(length=256), nullable=True) - - #: Versions are an automatic incrementing count. - version = Column(Integer(), nullable=True) diff --git a/tuskar/db/sqlalchemy/types.py b/tuskar/db/sqlalchemy/types.py deleted file mode 100644 index 38216c4d..00000000 --- a/tuskar/db/sqlalchemy/types.py +++ /dev/null @@ -1,24 +0,0 @@ -# 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 sqlalchemy.dialects import mysql -from sqlalchemy import types - - -class LongText(types.TypeDecorator): - impl = types.Text - - def load_dialect_impl(self, dialect): - if dialect.name == 'mysql': - return dialect.type_descriptor(mysql.LONGTEXT()) - else: - return self.impl diff --git a/tuskar/heat/__init__.py b/tuskar/heat/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tuskar/heat/client.py b/tuskar/heat/client.py deleted file mode 100644 index 4fbf41a6..00000000 --- a/tuskar/heat/client.py +++ /dev/null @@ -1,140 +0,0 @@ -# Copyright 2013 Red Hat # All Rights Reserved. - -# 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. - - -# Most of the following was copied shamelessly from nova @ -# https://github.com/openstack/nova/blob/master/nova/image/glance.py -# It's the way nova talks to glance, though obviously -# s/python-glanceclient/python-novaclient - - -"""A client library for accessing Heat CloudFormations API using Boto""" - -from os import environ as env - -from oslo_config import cfg - -from tuskar.openstack.common import log as logging - -heat_opts = [ - cfg.StrOpt('stack_name', - default='overcloud', - help='Name of the overcloud Heat stack' - ), - cfg.StrOpt('service_type', - default='orchestration', - help='Heat API service type registered in keystone' - ), - cfg.StrOpt('endpoint_type', - default='publicURL', - help='Heat API service endpoint type in keystone' - ) -] - -heat_keystone_opts = [ - # TODO(rpodolyaka): https://bugs.launchpad.net/tuskar/+bug/1236703 - cfg.StrOpt('username', - default=env.get('OS_USERNAME') or 'admin', - help='The name of a user the overcloud is deployed on behalf of' - ), - cfg.StrOpt('password', - help='The pass of a user the overcloud is deployed on behalf of' - ), - cfg.StrOpt('tenant_name', - default=env.get('OS_TENANT_NAME') or 'admin', - help='The tenant name the overcloud is deployed on behalf of' - ), - cfg.StrOpt('auth_url', - default=env.get('OS_AUTH_URL') or 'http://localhost:35357/v2.0', - help='Keystone authentication URL' - ), - cfg.BoolOpt('insecure', - default=True, - help='Set to False when Heat API uses HTTPS' - ) -] - -CONF = cfg.CONF -CONF.register_opts(heat_opts, group='heat') -CONF.register_opts(heat_keystone_opts, group='heat_keystone') -LOG = logging.getLogger(__name__) - -from heatclient.exc import HTTPNotFound as HeatStackNotFound -from heatclient.v1.client import Client as heatclient -from keystoneclient.v2_0 import client as ksclient - - -class HeatClient(object): - """Heat CloudFormations API client to use in Tuskar.""" - - def __init__(self): - try: - keystone = ksclient.Client(**CONF.heat_keystone) - endpoint = keystone.service_catalog.url_for( - service_type=CONF.heat['service_type'], - endpoint_type=CONF.heat['endpoint_type'] - ) - self.connection = heatclient( - endpoint=endpoint, - token=keystone.auth_token, - username=CONF.heat_keystone['username'], - password=CONF.heat_keystone['password']) - except Exception: - LOG.exception("An error occurred initialising the HeatClient") - self.connection = None - - def validate_template(self, template_body): - """Validate given Heat template.""" - return self.connection.stacks.validate( - template=template_body) - - def get_stack(self, name=None): - """Get overcloud Heat template.""" - if name is None: - name = CONF.heat['stack_name'] - if self.connection: - return self.connection.stacks.get(name) - - def get_template(self): - """Get JSON representation of the Heat overcloud template.""" - return self.connection.stacks.template( - stack_id=CONF.heat['stack_name'] - ) - - def update_stack(self, template_body, params): - """Update the Heat overcloud stack.""" - return self.connection.stacks.update(stack_id=CONF.heat['stack_name'], - template=template_body, - parameters=params) - - def delete_stack(self): - """Delete the Heat overcloud stack.""" - return self.connection.stacks.delete(stack_id=CONF.heat['stack_name']) - - def create_stack(self, template_body, params): - """Update the Heat overcloud stack.""" - return self.connection.stacks.create( - stack_name=CONF.heat['stack_name'], - template=template_body, - parameters=params) - - def exists_stack(self, name=None): - if name is None: - name = CONF.heat['stack_name'] - try: - self.get_stack(name) - return True - # return false if 404 - except HeatStackNotFound: - return False diff --git a/tuskar/heat/template_tools.py b/tuskar/heat/template_tools.py deleted file mode 100644 index 0eeca0e6..00000000 --- a/tuskar/heat/template_tools.py +++ /dev/null @@ -1,109 +0,0 @@ -# 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. - -""" -Utilities for using merge.py to generate overcloud.yaml to hand over to Heat. -Translates Tuskar resources into the overcloud heat template, using merge.py -from upstream tripleo-heat-templates. -""" - -import os - -from oslo_config import cfg -from tripleo_heat_merge import merge - - -# TODO(lsmola) For now static definition of roles for Icehouse -# we will need to load these associations from somewhere. -OVERCLOUD_CONTROL_ROLE = 'overcloud-control' -OVERCLOUD_COMPUTE_ROLE = 'overcloud-compute' -OVERCLOUD_VOLUME_ROLE = 'overcloud-cinder-volume' -OVERCLOUD_OBJECT_STORAGE_ROLE = 'overcloud-swift-storage' - -ROLES = {} -ROLES[OVERCLOUD_CONTROL_ROLE] = { - 'template_param': 'Control', - 'flavor_param': 'OvercloudControlFlavor', } -ROLES[OVERCLOUD_COMPUTE_ROLE] = { - 'template_param': 'NovaCompute', - 'flavor_param': 'OvercloudComputeFlavor', } -ROLES[OVERCLOUD_VOLUME_ROLE] = { - 'template_param': 'BlockStorage', - 'flavor_param': 'OvercloudBlockStorageFlavor', } -ROLES[OVERCLOUD_OBJECT_STORAGE_ROLE] = { - 'template_param': 'SwiftStorage', - 'flavor_param': 'OvercloudSwiftStorageFlavor', } - - -def generate_scaling_params(overcloud_roles): - """Given a dictionary containing a key value mapping of Overcloud Role name - to a count of the nodes return the scaling parameters to be used by - tripleo_heat_merge - - :param overcloud_roles: Dictionary with role names and a count of the nodes - :type overcloud_roles: dict - - :return: scaling parameters dict - :rtype: dict - """ - - # Default values, merge.py needs also the 0 counts. - scaling_defaults = ['NovaCompute=0', 'SwiftStorage=0', 'BlockStorage=0'] - - scaling = merge.parse_scaling(scaling_defaults) - - for overcloud_role, count in overcloud_roles.items(): - overcloud_role = overcloud_role.lower() - if overcloud_role in ROLES: - scale_str = "%s=%s" % ( - ROLES[overcloud_role]['template_param'], count) - scaling.update(merge.parse_scaling([scale_str])) - - return scaling - - -def _join_template_path(file_name): - return os.path.abspath( - os.path.join(os.path.dirname(cfg.CONF.tht_local_dir), file_name) - ) - - -def merge_templates(overcloud_roles): - """Merge the Overcloud Roles with overcloud.yaml using merge from - tripleo_heat_merge - - See tripleo-heat-templates for further details. - """ - - # TODO(dmatthews): Add exception handling to catch merge errors - - scale_params = generate_scaling_params(overcloud_roles) - overcloud_source = _join_template_path("overcloud-source.yaml") - block_storage = _join_template_path("block-storage.yaml") - swift_source = _join_template_path("swift-source.yaml") - swift_storage_source = _join_template_path("swift-storage-source.yaml") - ssl_src_path = _join_template_path("ssl-source.yaml") - swift_deploy = _join_template_path("swift-deploy.yaml") - nova_compute_config = _join_template_path("nova-compute-config.yaml") - - # Adding all templates like in tripleo-heat-templates Makefile. - # They will be used by merge.py according to scale_params. So the - # decision what template to pick will not be here. - merged_paths = [overcloud_source, block_storage, swift_source, - swift_storage_source, ssl_src_path, swift_deploy, - nova_compute_config] - - template = merge.merge(merged_paths, None, None, - included_template_dir=cfg.CONF.tht_local_dir, - scaling=scale_params) - - return template diff --git a/tuskar/manager/__init__.py b/tuskar/manager/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tuskar/manager/models.py b/tuskar/manager/models.py deleted file mode 100644 index a6e474ac..00000000 --- a/tuskar/manager/models.py +++ /dev/null @@ -1,89 +0,0 @@ -# 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 Role(object): - - def __init__(self, uuid, name, version, description, template, - relative_path=None, registry_path=None): - super(Role, self).__init__() - - self.uuid = uuid - self.name = name - self.version = version - self.description = description - self.template = template - self.relative_path = relative_path - self.registry_path = registry_path - - -class DeploymentPlan(object): - - def __init__(self, uuid, name, description, - created_at=None, updated_at=None): - super(DeploymentPlan, self).__init__() - - self.uuid = uuid - self.name = name - self.description = description - self.created_at = created_at - self.updated_at = updated_at - - self._roles = [] # list of Role - self._parameters = [] # list of PlanParameter - - @property - def roles(self): - return tuple(self._roles) - - @property - def parameters(self): - return tuple(self._parameters) - - def add_roles(self, *role): - """Adds one or more roles to the plan. - - :type role: tuskar.manager.models.Role - """ - for r in role: - self._roles.append(r) - - def add_parameters(self, *parameters): - """Adds one or more parameters to the plan. - - :type parameters: tuskar.manager.models.PlanParameter - """ - for p in parameters: - self._parameters.append(p) - - -class PlanParameter(object): - - def __init__(self, name, value, param_type, description, - label, default, hidden, constraints): - super(PlanParameter, self).__init__() - self.name = name - self.value = value - self.param_type = param_type - self.description = description - self.label = label - self.default = default - self.hidden = hidden - self.constraints = constraints - - -class ParameterValue(object): - - def __init__(self, name, value): - super(ParameterValue, self).__init__() - self.name = name - self.value = value diff --git a/tuskar/manager/name_utils.py b/tuskar/manager/name_utils.py deleted file mode 100644 index 5aa33cfd..00000000 --- a/tuskar/manager/name_utils.py +++ /dev/null @@ -1,84 +0,0 @@ -# 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. - -""" -Utilities for generating and parsing terms used in the deployment plan, -such as: - -* generating a single unique string for a role name/version pairing -* generating the names of each file that will be present in the - plan, such as the master template, environment file, and each provider - resource -""" - -import os - - -def generate_role_namespace(role_name, role_version): - """Creates a unique namespace for the given role name and version. - The returned namespace can be later converted back into its name and - version using parse_role_namespace. - - :type role_name: str - :type role_version: str or int - :rtype: str - """ - return '%s-%s' % (role_name, role_version) - - -def parse_role_namespace(role_namespace): - """Splits a role's namespace into it's role identification pieces. This - method should be used in conjunction with generate_role_namespace. - - :type role_namespace: str - :return: tuple of role name and version - :rtype: (str, str) - """ - return role_namespace.rsplit('-', 1) - - -def role_template_filename(role_name, role_version, role_relative_path): - """Generates the filename a role's template should be stored in when - creating the deployment plan's Heat files. - - :type role_name: str - :type role_version: str - :type role_relative_path: str or None - :rtype: str - """ - namespace = generate_role_namespace(role_name, role_version) - - filename = 'provider-%s.yaml' % namespace - if role_relative_path: - filename = os.path.join(role_relative_path, filename) - - return filename - - -def master_template_filename(plan_name): - """Generates the filename of a deployment plan's master template when - it is written out as a Heat template. - - :type plan_name: str - :rtype: str - """ - return '%s-%s' % (plan_name, 'template.yaml') - - -def environment_filename(plan_name): - """Generates the filename of a deployment plan's environment file - when it is written out as a file for Heat. - - :type plan_name: str - :rtype: str - """ - return '%s-%s' % (plan_name, 'environment.yaml') diff --git a/tuskar/manager/plan.py b/tuskar/manager/plan.py deleted file mode 100644 index e8358646..00000000 --- a/tuskar/manager/plan.py +++ /dev/null @@ -1,504 +0,0 @@ -# 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 os import path as os_path - -from tuskar.common import exception -from tuskar.common import utils -from tuskar.manager import models -from tuskar.manager import name_utils -from tuskar.manager.role import RoleManager -from tuskar.storage.exceptions import UnknownName -from tuskar.storage.load_roles import RESOURCE_REGISTRY_NAME -from tuskar.storage.stores import DeploymentPlanStore -from tuskar.storage.stores import EnvironmentFileStore -from tuskar.storage.stores import MasterSeedStore -from tuskar.storage.stores import MasterTemplateStore -from tuskar.storage.stores import ResourceRegistryMappingStore -from tuskar.storage.stores import ResourceRegistryStore -from tuskar.storage.stores import TemplateExtraStore -from tuskar.storage.stores import TemplateStore -from tuskar.templates import composer -from tuskar.templates.heat import RegistryEntry -from tuskar.templates import namespace as ns_utils -from tuskar.templates import parser -from tuskar.templates import plan -from tuskar.templates import template_seed - - -LOG = logging.getLogger(__name__) -MASTER_SEED_NAME = '_master_seed' - - -class PlansManager(object): - - def __init__(self): - super(PlansManager, self).__init__() - self.plan_store = DeploymentPlanStore() - self.seed_store = MasterSeedStore() - self.registry_store = ResourceRegistryStore() - self.registry_mapping_store = ResourceRegistryMappingStore() - self.template_store = TemplateStore() - self.template_extra_store = TemplateExtraStore() - self.master_template_store = MasterTemplateStore() - self.environment_store = EnvironmentFileStore() - - def create_plan(self, name, description): - """Creates a new plan, persisting it to Tuskar's storage backend. - - :type name: str - :type description: str - :return: domain model instance of the created plan - :rtype: tuskar.manager.models.DeploymentPlan - :raises tuskar.storage.exceptions.NameAlreadyUsed: if there is a plan - with the given name - """ - - # Create the plan using the template generation code first to - # stub out the master template and environment files correctly. - new_plan = plan.DeploymentPlan(description=description) - - # Save the newly created master template and environment to the - # storage layer so they can be associated with the plan. - master_template_contents = composer.compose_template( - new_plan.master_template - ) - master_template_file = self.master_template_store.create( - name_utils.master_template_filename(name), - master_template_contents, - ) - - environment_contents = composer.compose_environment( - new_plan.environment - ) - environment_file = self.environment_store.create( - environment_contents - ) - - # Create the plan in storage, seeding it with the stored files for - # the template and environment. - db_plan = self.plan_store.create( - name, - master_template_uuid=master_template_file.uuid, - environment_uuid=environment_file.uuid, - ) - - # Return the created plan. - created_plan = self.retrieve_plan(db_plan.uuid) - return created_plan - - def delete_plan(self, plan_uuid): - """Deletes an existing plan. - - :type plan_uuid: str - :raises tuskar.storage.exceptions.UnknownUUID: if there is no plan - with the given UUID - """ - self.plan_store.delete(plan_uuid) - - def add_role_to_plan(self, plan_uuid, role_uuid): - """Adds a role to the given plan, storing the changes in Tuskar's - storage. - - :type plan_uuid: str - :type role_uuid: str - :return: updated plan model instance - :rtype: tuskar.manager.models.DeploymentPlan - :raises tuskar.storage.exceptions.UnknownUUID: if either the plan - or the role cannot be found - """ - # Load the plan and role from storage - db_plan = self.plan_store.retrieve(plan_uuid) - db_role = self.template_store.retrieve(role_uuid) - - # Parse the plan and role template into template objects. - deployment_plan = self._plan_to_template_object(db_plan) - role_template = self._role_to_template_object(db_role) - - # See if a master seed template has been set. - try: - db_master_seed = self.seed_store.retrieve_by_name(MASTER_SEED_NAME) - master_seed = parser.parse_template(db_master_seed.contents) - except UnknownName: - master_seed = None - special_properties = None - - def _find_role_type(registry): - for path in registry.keys(): - if path in db_role.registry_path: - return registry[path] - - if master_seed is not None: - try: - db_registry_env = self.registry_store.retrieve_by_name( - RESOURCE_REGISTRY_NAME).contents - except UnknownName: - LOG.error("Could not load resource_registry. Make sure you " - "pass --resource-registry to tuskar-load-roles.") - raise - - parsed_registry_env = parser.parse_environment(db_registry_env) - registry = dict((e.filename, e.alias) - for e in parsed_registry_env.registry_entries) - role_type = _find_role_type(registry) - special_properties = template_seed.get_property_map_for_role( - master_seed, role_type) - - # Use the combination logic to perform the addition. - role_namespace = name_utils.generate_role_namespace(db_role.name, - db_role.version) - template_filename = ( - name_utils.role_template_filename(db_role.name, db_role.version, - db_role.relative_path)) - deployment_plan.add_template(role_namespace, role_template, - template_filename, - override_properties=special_properties) - - # If there is a master seed, add its top-level elements to the plan. - # These calls are idempotent, so it's safe to call each time a role - # is added. - if master_seed is not None: - template_seed.add_top_level_parameters( - master_seed, - deployment_plan.master_template, - deployment_plan.environment) - template_seed.add_top_level_resources( - master_seed, deployment_plan.master_template) - template_seed.add_top_level_outputs( - master_seed, deployment_plan.master_template) - template_seed.preserve_defaults( - master_seed, deployment_plan.master_template) - - if role_type is None: - LOG.error( - "Role '%s' not found in seed template." % db_role.name) - raise ValueError(db_role.name) - seed_role = template_seed.find_role_from_type( - master_seed.resources, role_type) - if seed_role is None: - LOG.error( - "Role '%s' of type '%s' not found in seed template." % - (db_role.name, role_type)) - raise ValueError(db_role.name) - - # These calls are idempotent, but must be called on each role as - # new references may have been added. - template_seed.update_role_resource_references( - deployment_plan.master_template, - seed_role, - db_role.name) - - template_seed.update_role_property_references( - deployment_plan.master_template, - seed_role, - role_namespace) - - # Update environment file to add top level mappings, which is made - # up of all non-role files present in the resource registry, plus - # required aliases - reg_mapping = self.registry_mapping_store.list() - - environment = deployment_plan.environment - for entry in parsed_registry_env.registry_entries: - # check if registry_mapping is in database, if so add to - # environment (later will become environment.yaml) - if any(x.name == entry.filename for x in reg_mapping): - additem = RegistryEntry(entry.alias, entry.filename) - environment.add_registry_entry(additem, unique=True) - - # similarly defaults from master_seed to to environment parameters - template_seed.preserve_defaults(master_seed, environment) - - # Save the updated plan. - updated = self._save_updated_plan(plan_uuid, deployment_plan) - - return updated - - def remove_role_from_plan(self, plan_uuid, role_uuid): - """Removes a role from the given plan. - - :type plan_uuid: str - :type role_uuid: str - :raise tuskar.storage.exceptions.UnknownUUID: if the plan or role - doesn't exist - """ - - # Load the objects from storage. - db_plan = self.plan_store.retrieve(plan_uuid) - db_role = self.template_store.retrieve(role_uuid) - - # Parse the plan into template objects. - deployment_plan = self._plan_to_template_object(db_plan) - - # Delete the role from the plan by it's namespace. - role_namespace = name_utils.generate_role_namespace(db_role.name, - db_role.version) - deployment_plan.remove_template(role_namespace) - - # Save the updated plan. - updated = self._save_updated_plan(plan_uuid, deployment_plan) - - return updated - - def retrieve_plan(self, plan_uuid): - """Loads the given plan. - - :type plan_uuid: str - :rtype: tuskar.manager.models.DeploymentPlan - :raises tuskar.storage.exceptions.UnknownUUID: if there is no plan - with the given UUID - """ - - # Load the plan from the database. - db_plan = self.plan_store.retrieve(plan_uuid) - - # Parse the plan into the template model. - master_template = parser.parse_template( - db_plan.master_template.contents - ) - environment = parser.parse_environment( - db_plan.environment_file.contents - ) - - # Create the Tuskar model for the plan. - deployment_plan = models.DeploymentPlan( - plan_uuid, - db_plan.name, - master_template.description, - created_at=db_plan.created_at, - updated_at=db_plan.updated_at, - ) - - roles = self._find_roles(environment) - deployment_plan.add_roles(*roles) - - params = self._find_parameters(master_template, environment) - deployment_plan.add_parameters(*params) - - return deployment_plan - - def list_plans(self): - """Returns a list of all plans stored in Tuskar. - - :return: list of plan instances; empty list if there are no plans - :rtype: [tuskar.manager.models.DeploymentPlan] - """ - - # Given the expected number of plans being managed by Tuskar (in the - # tens), this should be sufficient. If our scale gets larger, we may - # need a smarter batch operation here than simply iterating over the - # list of all plans. jdob, Aug 7, 2014 - - plan_uuids = [p.uuid for p in self.plan_store.list()] - plans = [self.retrieve_plan(p) for p in plan_uuids] - - return plans - - def set_parameter_values(self, plan_uuid, params): - """Sets the values for a plan's parameters. - - :type plan_uuid: str - :type params: [tuskar.manager.models.ParameterValue] - - :return: plan instance with the updated values - :rtype: tuskar.manager.models.DeploymentPlan - """ - - # Load the plan from the database. - db_plan = self.plan_store.retrieve(plan_uuid) - - # Save the values to the parsed environment. - environment = parser.parse_environment( - db_plan.environment_file.contents - ) - - non_existent_params = [] - for param_value in params: - p = environment.find_parameter_by_name(param_value.name) - if p: - p.value = param_value.value - else: - non_existent_params.append(param_value.name) - - if non_existent_params: - param_names = ', '.join(non_existent_params) - LOG.error( - 'There are no parameters named %(param_names)s' - ' in plan %(plan_uuid)s.' % - {'param_names': param_names, 'plan_uuid': plan_uuid}) - raise exception.PlanParametersNotExist( - plan_uuid=plan_uuid, - param_names=param_names - ) - - # Save the updated environment. - env_contents = composer.compose_environment(environment) - self.plan_store.update_environment(plan_uuid, env_contents) - - updated_plan = self.retrieve_plan(plan_uuid) - return updated_plan - - def package_templates(self, plan_uuid): - """Packages and returns all of the templates related to the given plan. - The returned dictionary is keyed by filename and contains the contents - of that file (a template or an environment file). - - :type plan_uuid: str - - :return: mapping of filename to contents for each file in the plan - :rtype: dict - - :raises tuskar.storage.exceptions.UnknownUUID: if there is no plan - with the given UUID - """ - - # Load and parse the plan. - db_plan = self.plan_store.retrieve(plan_uuid) - master_template = parser.parse_template( - db_plan.master_template.contents - ) - environment = parser.parse_environment( - db_plan.environment_file.contents - ) - - # Compose the plan files and all plan roles and package them into - # a single dictionary. - plan_contents = composer.compose_template(master_template) - env_contents = composer.compose_environment(environment) - - files_dict = { - 'plan.yaml': plan_contents, - 'environment.yaml': env_contents, - } - - plan_roles = self._find_roles(environment) - manager = RoleManager() - for role in plan_roles: - contents = composer.compose_template(role.template) - filename = name_utils.role_template_filename(role.name, - role.version, - role.relative_path) - files_dict[filename] = contents - - def _add_template_extra_data_for(templates, template_store): - template_extra_data = manager.retrieve_db_role_extra() - for template in templates: - db_template = template_store.retrieve_by_name(template.name) - prefix = os_path.split(db_template.name)[0] - template_extra_paths = utils.resolve_template_extra_data( - db_template, template_extra_data) - extra_data_output = manager.template_extra_data_for_output( - template_extra_paths, prefix) - files_dict.update(extra_data_output) - - # also grab any extradata files for the role - _add_template_extra_data_for(plan_roles, self.template_store) - - # in addition to provider roles above, return non-role template files - reg_mapping = self.registry_mapping_store.list() - for entry in reg_mapping: - if os_path.splitext(entry.name)[1] in ('.yaml', '.yml'): - # if entry is an alias, don't include it - files_dict[entry.name] = entry.contents - - # similarly, also grab extradata files for the non role templates - _add_template_extra_data_for(reg_mapping, self.registry_mapping_store) - - return files_dict - - def _find_roles(self, environment): - """Returns a list of roles for a plan (identified by the given - environment). - - :type environment: tuskar.templates.heat.Environment - :return: list of role instances; empty list if none are in the - environment - :rtype: [tuskar.manager.models.Role] - """ - - def load_role(entry): - # Figure out the role name/version for the given entry. - namespace = ns_utils.remove_resource_alias_namespace(entry.alias) - name, version = name_utils.parse_role_namespace(namespace) - - # Load the role from the database and parse into the - # template objects. - db_role = self.template_store.retrieve_by_name(name, int(version)) - role = self._role_to_template_object(db_role) - - # Convert to the Tuskar domain model. - tuskar_role = models.Role(db_role.uuid, name, version, - role.description, role, - relative_path=db_role.relative_path) - return tuskar_role - - reg_mapping = self.registry_mapping_store.list() - roles = [load_role(e) for e in environment.registry_entries - if not any(x.name == e.filename for x in reg_mapping)] - return roles - - @staticmethod - def _find_parameters(template, environment): - """Returns a list of parameters for a plan. The parameters will contain - both metadata about the parameter itself (name, label, description, - etc.) as well as it's current value for the plan. - - :type template: tuskar.templates.heat.Template - :type environment: tuskar.templates.heat.Environment - :return: list of parameter instances; empty list if there are no - parameters in the plan - :rtype: [tuskar.manager.models.PlanParameter] - """ - - def generate_param(p): - env_param = environment.find_parameter_by_name(p.name) - return models.PlanParameter( - p.name, env_param.value, p.param_type, - p.description, p.label, p.default, p.hidden, p.constraints - ) - params = [generate_param(tp) for tp in template.parameters] - return params - - @staticmethod - def _plan_to_template_object(db_plan): - master_template = parser.parse_template( - db_plan.master_template.contents - ) - environment = parser.parse_environment( - db_plan.environment_file.contents - ) - deployment_plan = plan.DeploymentPlan(master_template=master_template, - environment=environment) - return deployment_plan - - @staticmethod - def _role_to_template_object(db_role): - role_template = parser.parse_template(db_role.contents) - return role_template - - def _save_updated_plan(self, plan_uuid, deployment_plan): - new_template_contents = composer.compose_template( - deployment_plan.master_template - ) - new_env_contents = composer.compose_environment( - deployment_plan.environment - ) - - self.plan_store.update_master_template(plan_uuid, - new_template_contents) - self.plan_store.update_environment(plan_uuid, - new_env_contents) - - # Retrieve and return the updated plan - updated_plan = self.retrieve_plan(plan_uuid) - return updated_plan diff --git a/tuskar/manager/role.py b/tuskar/manager/role.py deleted file mode 100644 index 4ac1b6d2..00000000 --- a/tuskar/manager/role.py +++ /dev/null @@ -1,117 +0,0 @@ -# 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 os import path as os_path - -from tuskar.manager import models -from tuskar.storage.stores import TemplateExtraStore -from tuskar.storage.stores import TemplateStore -from tuskar.templates import parser - - -class RoleManager(object): - - def __init__(self): - super(RoleManager, self).__init__() - self.template_store = TemplateStore() - self.template_extra_store = TemplateExtraStore() - - def list_roles(self, only_latest=False): - """Returns a list of all roles known to Tuskar. - - :param only_latest: if true, only the highest version of each role - will be returned - :type only_latest: bool - :return: list of tuskar model instances for each role - :rtype: [tuskar.manager.models.Role] - """ - db_roles = self.template_store.list(only_latest=only_latest) - roles = [self._role_to_tuskar_object(r) for r in db_roles] - return roles - - def retrieve_role_by_uuid(self, role_uuid): - """Returns the role with the given UUID. - - :type role_uuid: str - :rtype: tuskar.manager.models.Role - :raises tuskar.storage.exceptions.UnknownUUID: if there is no role with - the given ID - """ - db_role = self.template_store.retrieve(role_uuid) - role = self._role_to_tuskar_object(db_role) - return role - - def retrieve_db_role_by_uuid(self, role_uuid): - return self.template_store.retrieve(role_uuid) - - def retrieve_db_role_extra(self): - return self.template_extra_store.list(only_latest=False) - - def template_extra_data_for_output(self, template_extra_paths, prefix=''): - """Compile and return role-extra data for output as a string - - :param template_extra_paths: a list of {k,v} (name=>path) - :type template_extra_paths: list of dict - - :param prefix: a prefix path - :type prefix: string - - :return: a dict of path=>contents - :rtype: dict - - The keys in template_extra_paths correspond to the names of stored - role-extra data and the values are the paths at which the - corresponding files ares expected to be. This list is returned by - common.utils.resolve_template_extra_data for example: - - [{'extra_common_yaml': 'hieradata/common.yaml'}, - {'extra_object_yaml': 'hieradata/object.yaml'}] - - Using this create a new dict that maps the path (values above) as - key to the contents of the corresponding stored role-extra object - (using the name above to retrieve it). For the example input - above, the output would be like: - - { - "hieradata/common.yaml": "CONTENTS", - "hieradata/object.yaml": "CONTENTS" - } - - In those cases that the template_extra_paths were generated for a - non Role template (i.e. those templates read from the resource - registry), include their path prefix - so that the extra data files - are created relative to the template. For example the template - 'path/to/some_template.yaml' has a reference to the extra-data file - 'hieradata/common.yaml'. The resulting extra-data file returned by - tuskar must then be: - - { - "path/to/hieradata/common.yaml": "CONTENTS", - } - - """ - res = {} - for path in template_extra_paths: - role_extra_name = path.keys()[0] - role_extra_path = path[role_extra_name] - db_role_extra = self.template_extra_store.retrieve_by_name( - role_extra_name) - role_extra_path = os_path.join(prefix, role_extra_path) - res[role_extra_path] = db_role_extra.contents - return res - - @staticmethod - def _role_to_tuskar_object(db_role): - parsed = parser.parse_template(db_role.contents) - role = models.Role(db_role.uuid, db_role.name, db_role.version, - parsed.description, parsed) - return role diff --git a/tuskar/netconf.py b/tuskar/netconf.py deleted file mode 100644 index 6c3f2be4..00000000 --- a/tuskar/netconf.py +++ /dev/null @@ -1,52 +0,0 @@ -# Copyright 2010 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# All Rights Reserved. -# Copyright 2012 Red Hat, Inc. -# -# 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 socket - -from oslo_config import cfg - -CONF = cfg.CONF - - -def _get_my_ip(): - """Returns the actual ip of the local machine. - - This code figures out what source address would be used if some traffic - were to be sent out to some well known address on the Internet. In this - case, a Google DNS server is used, but the specific address does not - matter much. No traffic is actually sent. - """ - try: - csock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - csock.connect(('8.8.8.8', 80)) - (addr, port) = csock.getsockname() - csock.close() - return addr - except socket.error: - return "127.0.0.1" - - -netconf_opts = [ - cfg.StrOpt('my_ip', - default=_get_my_ip(), - help='ip address of this host'), - cfg.BoolOpt('use_ipv6', - default=False, - help='use ipv6'), -] - -CONF.register_opts(netconf_opts) diff --git a/tuskar/openstack/__init__.py b/tuskar/openstack/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tuskar/openstack/common/__init__.py b/tuskar/openstack/common/__init__.py deleted file mode 100644 index d1223eaf..00000000 --- a/tuskar/openstack/common/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -# -# 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 six - - -six.add_move(six.MovedModule('mox', 'mox', 'mox3.mox')) diff --git a/tuskar/openstack/common/config/__init__.py b/tuskar/openstack/common/config/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tuskar/openstack/common/config/generator.py b/tuskar/openstack/common/config/generator.py deleted file mode 100644 index 06db9e61..00000000 --- a/tuskar/openstack/common/config/generator.py +++ /dev/null @@ -1,301 +0,0 @@ -# Copyright 2012 SINA Corporation -# Copyright 2014 Cisco Systems, Inc. -# All Rights Reserved. -# -# 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. -# - -"""Extracts OpenStack config option info from module(s).""" - -from __future__ import print_function - -import argparse -import imp -import os -import re -import socket -import sys -import textwrap - -from oslo_config import cfg -import six -import stevedore.named - -from tuskar.openstack.common import gettextutils -from tuskar.openstack.common import importutils - -gettextutils.install('tuskar') - -STROPT = "StrOpt" -BOOLOPT = "BoolOpt" -INTOPT = "IntOpt" -FLOATOPT = "FloatOpt" -LISTOPT = "ListOpt" -DICTOPT = "DictOpt" -MULTISTROPT = "MultiStrOpt" - -OPT_TYPES = { - STROPT: 'string value', - BOOLOPT: 'boolean value', - INTOPT: 'integer value', - FLOATOPT: 'floating point value', - LISTOPT: 'list value', - DICTOPT: 'dict value', - MULTISTROPT: 'multi valued', -} - -OPTION_REGEX = re.compile(r"(%s)" % "|".join([STROPT, BOOLOPT, INTOPT, - FLOATOPT, LISTOPT, DICTOPT, - MULTISTROPT])) - -PY_EXT = ".py" -BASEDIR = os.path.abspath(os.path.join(os.path.dirname(__file__), - "../../../../")) -WORDWRAP_WIDTH = 60 - - -def generate(argv): - parser = argparse.ArgumentParser( - description='generate sample configuration file', - ) - parser.add_argument('-m', dest='modules', action='append') - parser.add_argument('-l', dest='libraries', action='append') - parser.add_argument('srcfiles', nargs='*') - parsed_args = parser.parse_args(argv) - - mods_by_pkg = dict() - for filepath in parsed_args.srcfiles: - pkg_name = filepath.split(os.sep)[1] - mod_str = '.'.join(['.'.join(filepath.split(os.sep)[:-1]), - os.path.basename(filepath).split('.')[0]]) - mods_by_pkg.setdefault(pkg_name, list()).append(mod_str) - # NOTE(lzyeval): place top level modules before packages - pkg_names = sorted(pkg for pkg in mods_by_pkg if pkg.endswith(PY_EXT)) - ext_names = sorted(pkg for pkg in mods_by_pkg if pkg not in pkg_names) - pkg_names.extend(ext_names) - - # opts_by_group is a mapping of group name to an options list - # The options list is a list of (module, options) tuples - opts_by_group = {'DEFAULT': []} - - if parsed_args.modules: - for module_name in parsed_args.modules: - module = _import_module(module_name) - if module: - for group, opts in _list_opts(module): - opts_by_group.setdefault(group, []).append((module_name, - opts)) - - # Look for entry points defined in libraries (or applications) for - # option discovery, and include their return values in the output. - # - # Each entry point should be a function returning an iterable - # of pairs with the group name (or None for the default group) - # and the list of Opt instances for that group. - if parsed_args.libraries: - loader = stevedore.named.NamedExtensionManager( - 'oslo.config.opts', - names=list(set(parsed_args.libraries)), - invoke_on_load=False, - ) - for ext in loader: - for group, opts in ext.plugin(): - opt_list = opts_by_group.setdefault(group or 'DEFAULT', []) - opt_list.append((ext.name, opts)) - - for pkg_name in pkg_names: - mods = mods_by_pkg.get(pkg_name) - mods.sort() - for mod_str in mods: - if mod_str.endswith('.__init__'): - mod_str = mod_str[:mod_str.rfind(".")] - - mod_obj = _import_module(mod_str) - if not mod_obj: - raise RuntimeError("Unable to import module %s" % mod_str) - - for group, opts in _list_opts(mod_obj): - opts_by_group.setdefault(group, []).append((mod_str, opts)) - - print_group_opts('DEFAULT', opts_by_group.pop('DEFAULT', [])) - for group in sorted(opts_by_group.keys()): - print_group_opts(group, opts_by_group[group]) - - -def _import_module(mod_str): - try: - if mod_str.startswith('bin.'): - imp.load_source(mod_str[4:], os.path.join('bin', mod_str[4:])) - return sys.modules[mod_str[4:]] - else: - return importutils.import_module(mod_str) - except Exception as e: - sys.stderr.write("Error importing module %s: %s\n" % (mod_str, str(e))) - return None - - -def _is_in_group(opt, group): - "Check if opt is in group." - for value in group._opts.values(): - # NOTE(llu): Temporary workaround for bug #1262148, wait until - # newly released oslo.config support '==' operator. - if not(value['opt'] != opt): - return True - return False - - -def _guess_groups(opt, mod_obj): - # is it in the DEFAULT group? - if _is_in_group(opt, cfg.CONF): - return 'DEFAULT' - - # what other groups is it in? - for value in cfg.CONF.values(): - if isinstance(value, cfg.CONF.GroupAttr): - if _is_in_group(opt, value._group): - return value._group.name - - raise RuntimeError( - "Unable to find group for option %s, " - "maybe it's defined twice in the same group?" - % opt.name - ) - - -def _list_opts(obj): - def is_opt(o): - return (isinstance(o, cfg.Opt) and - not isinstance(o, cfg.SubCommandOpt)) - - opts = list() - for attr_str in dir(obj): - attr_obj = getattr(obj, attr_str) - if is_opt(attr_obj): - opts.append(attr_obj) - elif (isinstance(attr_obj, list) and - all(map(lambda x: is_opt(x), attr_obj))): - opts.extend(attr_obj) - - ret = {} - for opt in opts: - ret.setdefault(_guess_groups(opt, obj), []).append(opt) - return ret.items() - - -def print_group_opts(group, opts_by_module): - print("[%s]" % group) - print('') - for mod, opts in opts_by_module: - print('#') - print('# Options defined in %s' % mod) - print('#') - print('') - for opt in opts: - _print_opt(opt) - print('') - - -def _get_my_ip(): - try: - csock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - csock.connect(('8.8.8.8', 80)) - (addr, port) = csock.getsockname() - csock.close() - return addr - except socket.error: - return None - - -def _sanitize_default(name, value): - """Set up a reasonably sensible default for pybasedir, my_ip and host.""" - if value.startswith(sys.prefix): - # NOTE(jd) Don't use os.path.join, because it is likely to think the - # second part is an absolute pathname and therefore drop the first - # part. - value = os.path.normpath("/usr/" + value[len(sys.prefix):]) - elif value.startswith(BASEDIR): - return value.replace(BASEDIR, '/usr/lib/python/site-packages') - elif BASEDIR in value: - return value.replace(BASEDIR, '') - elif value == _get_my_ip(): - return '10.0.0.1' - elif value == socket.gethostname() and 'host' in name: - return 'tuskar' - elif value.strip() != value: - return '"%s"' % value - return value - - -def _print_opt(opt): - opt_name, opt_default, opt_help = opt.dest, opt.default, opt.help - if not opt_help: - sys.stderr.write('WARNING: "%s" is missing help string.\n' % opt_name) - opt_help = "" - opt_type = None - try: - opt_type = OPTION_REGEX.search(str(type(opt))).group(0) - except (ValueError, AttributeError) as err: - sys.stderr.write("%s\n" % str(err)) - sys.exit(1) - opt_help += ' (' + OPT_TYPES[opt_type] + ')' - print('#', "\n# ".join(textwrap.wrap(opt_help, WORDWRAP_WIDTH))) - if opt.deprecated_opts: - for deprecated_opt in opt.deprecated_opts: - if deprecated_opt.name: - deprecated_group = (deprecated_opt.group if - deprecated_opt.group else "DEFAULT") - print('# Deprecated group/name - [%s]/%s' % - (deprecated_group, - deprecated_opt.name)) - try: - if opt_default is None: - print('#%s=' % opt_name) - elif opt_type == STROPT: - assert(isinstance(opt_default, six.string_types)) - print('#%s=%s' % (opt_name, _sanitize_default(opt_name, - opt_default))) - elif opt_type == BOOLOPT: - assert(isinstance(opt_default, bool)) - print('#%s=%s' % (opt_name, str(opt_default).lower())) - elif opt_type == INTOPT: - assert(isinstance(opt_default, int) and - not isinstance(opt_default, bool)) - print('#%s=%s' % (opt_name, opt_default)) - elif opt_type == FLOATOPT: - assert(isinstance(opt_default, float)) - print('#%s=%s' % (opt_name, opt_default)) - elif opt_type == LISTOPT: - assert(isinstance(opt_default, list)) - print('#%s=%s' % (opt_name, ','.join(opt_default))) - elif opt_type == DICTOPT: - assert(isinstance(opt_default, dict)) - opt_default_strlist = [str(key) + ':' + str(value) - for (key, value) in opt_default.items()] - print('#%s=%s' % (opt_name, ','.join(opt_default_strlist))) - elif opt_type == MULTISTROPT: - assert(isinstance(opt_default, list)) - if not opt_default: - opt_default = [''] - for default in opt_default: - print('#%s=%s' % (opt_name, default)) - print('') - except Exception: - sys.stderr.write('Error in option "%s"\n' % opt_name) - sys.exit(1) - - -def main(): - generate(sys.argv[1:]) - -if __name__ == '__main__': - main() diff --git a/tuskar/openstack/common/gettextutils.py b/tuskar/openstack/common/gettextutils.py deleted file mode 100644 index c056c8db..00000000 --- a/tuskar/openstack/common/gettextutils.py +++ /dev/null @@ -1,498 +0,0 @@ -# Copyright 2012 Red Hat, Inc. -# Copyright 2013 IBM Corp. -# All Rights Reserved. -# -# 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. - -""" -gettext for openstack-common modules. - -Usual usage in an openstack.common module: - - from tuskar.openstack.common.gettextutils import _ -""" - -import copy -import functools -import gettext -import locale -from logging import handlers -import os - -from babel import localedata -import six - -_AVAILABLE_LANGUAGES = {} - -# FIXME(dhellmann): Remove this when moving to oslo.i18n. -USE_LAZY = False - - -class TranslatorFactory(object): - """Create translator functions - """ - - def __init__(self, domain, lazy=False, localedir=None): - """Establish a set of translation functions for the domain. - - :param domain: Name of translation domain, - specifying a message catalog. - :type domain: str - :param lazy: Delays translation until a message is emitted. - Defaults to False. - :type lazy: Boolean - :param localedir: Directory with translation catalogs. - :type localedir: str - """ - self.domain = domain - self.lazy = lazy - if localedir is None: - localedir = os.environ.get(domain.upper() + '_LOCALEDIR') - self.localedir = localedir - - def _make_translation_func(self, domain=None): - """Return a new translation function ready for use. - - Takes into account whether or not lazy translation is being - done. - - The domain can be specified to override the default from the - factory, but the localedir from the factory is always used - because we assume the log-level translation catalogs are - installed in the same directory as the main application - catalog. - - """ - if domain is None: - domain = self.domain - if self.lazy: - return functools.partial(Message, domain=domain) - t = gettext.translation( - domain, - localedir=self.localedir, - fallback=True, - ) - if six.PY3: - return t.gettext - return t.ugettext - - @property - def primary(self): - "The default translation function." - return self._make_translation_func() - - def _make_log_translation_func(self, level): - return self._make_translation_func(self.domain + '-log-' + level) - - @property - def log_info(self): - "Translate info-level log messages." - return self._make_log_translation_func('info') - - @property - def log_warning(self): - "Translate warning-level log messages." - return self._make_log_translation_func('warning') - - @property - def log_error(self): - "Translate error-level log messages." - return self._make_log_translation_func('error') - - @property - def log_critical(self): - "Translate critical-level log messages." - return self._make_log_translation_func('critical') - - -# NOTE(dhellmann): When this module moves out of the incubator into -# oslo.i18n, these global variables can be moved to an integration -# module within each application. - -# Create the global translation functions. -_translators = TranslatorFactory('tuskar') - -# The primary translation function using the well-known name "_" -_ = _translators.primary - -# Translators for log levels. -# -# The abbreviated names are meant to reflect the usual use of a short -# name like '_'. The "L" is for "log" and the other letter comes from -# the level. -_LI = _translators.log_info -_LW = _translators.log_warning -_LE = _translators.log_error -_LC = _translators.log_critical - -# NOTE(dhellmann): End of globals that will move to the application's -# integration module. - - -def enable_lazy(): - """Convenience function for configuring _() to use lazy gettext - - Call this at the start of execution to enable the gettextutils._ - function to use lazy gettext functionality. This is useful if - your project is importing _ directly instead of using the - gettextutils.install() way of importing the _ function. - """ - # FIXME(dhellmann): This function will be removed in oslo.i18n, - # because the TranslatorFactory makes it superfluous. - global _, _LI, _LW, _LE, _LC, USE_LAZY - tf = TranslatorFactory('tuskar', lazy=True) - _ = tf.primary - _LI = tf.log_info - _LW = tf.log_warning - _LE = tf.log_error - _LC = tf.log_critical - USE_LAZY = True - - -def install(domain, lazy=False): - """Install a _() function using the given translation domain. - - Given a translation domain, install a _() function using gettext's - install() function. - - The main difference from gettext.install() is that we allow - overriding the default localedir (e.g. /usr/share/locale) using - a translation-domain-specific environment variable (e.g. - NOVA_LOCALEDIR). - - :param domain: the translation domain - :param lazy: indicates whether or not to install the lazy _() function. - The lazy _() introduces a way to do deferred translation - of messages by installing a _ that builds Message objects, - instead of strings, which can then be lazily translated into - any available locale. - """ - if lazy: - from six import moves - tf = TranslatorFactory(domain, lazy=True) - moves.builtins.__dict__['_'] = tf.primary - else: - localedir = '%s_LOCALEDIR' % domain.upper() - if six.PY3: - gettext.install(domain, - localedir=os.environ.get(localedir)) - else: - gettext.install(domain, - localedir=os.environ.get(localedir), - unicode=True) - - -class Message(six.text_type): - """A Message object is a unicode object that can be translated. - - Translation of Message is done explicitly using the translate() method. - For all non-translation intents and purposes, a Message is simply unicode, - and can be treated as such. - """ - - def __new__(cls, msgid, msgtext=None, params=None, - domain='tuskar', *args): - """Create a new Message object. - - In order for translation to work gettext requires a message ID, this - msgid will be used as the base unicode text. It is also possible - for the msgid and the base unicode text to be different by passing - the msgtext parameter. - """ - # If the base msgtext is not given, we use the default translation - # of the msgid (which is in English) just in case the system locale is - # not English, so that the base text will be in that locale by default. - if not msgtext: - msgtext = Message._translate_msgid(msgid, domain) - # We want to initialize the parent unicode with the actual object that - # would have been plain unicode if 'Message' was not enabled. - msg = super(Message, cls).__new__(cls, msgtext) - msg.msgid = msgid - msg.domain = domain - msg.params = params - return msg - - def translate(self, desired_locale=None): - """Translate this message to the desired locale. - - :param desired_locale: The desired locale to translate the message to, - if no locale is provided the message will be - translated to the system's default locale. - - :returns: the translated message in unicode - """ - - translated_message = Message._translate_msgid(self.msgid, - self.domain, - desired_locale) - if self.params is None: - # No need for more translation - return translated_message - - # This Message object may have been formatted with one or more - # Message objects as substitution arguments, given either as a single - # argument, part of a tuple, or as one or more values in a dictionary. - # When translating this Message we need to translate those Messages too - translated_params = _translate_args(self.params, desired_locale) - - translated_message = translated_message % translated_params - - return translated_message - - @staticmethod - def _translate_msgid(msgid, domain, desired_locale=None): - if not desired_locale: - system_locale = locale.getdefaultlocale() - # If the system locale is not available to the runtime use English - if not system_locale[0]: - desired_locale = 'en_US' - else: - desired_locale = system_locale[0] - - locale_dir = os.environ.get(domain.upper() + '_LOCALEDIR') - lang = gettext.translation(domain, - localedir=locale_dir, - languages=[desired_locale], - fallback=True) - if six.PY3: - translator = lang.gettext - else: - translator = lang.ugettext - - translated_message = translator(msgid) - return translated_message - - def __mod__(self, other): - # When we mod a Message we want the actual operation to be performed - # by the parent class (i.e. unicode()), the only thing we do here is - # save the original msgid and the parameters in case of a translation - params = self._sanitize_mod_params(other) - unicode_mod = super(Message, self).__mod__(params) - modded = Message(self.msgid, - msgtext=unicode_mod, - params=params, - domain=self.domain) - return modded - - def _sanitize_mod_params(self, other): - """Sanitize the object being modded with this Message. - - - Add support for modding 'None' so translation supports it - - Trim the modded object, which can be a large dictionary, to only - those keys that would actually be used in a translation - - Snapshot the object being modded, in case the message is - translated, it will be used as it was when the Message was created - """ - if other is None: - params = (other,) - elif isinstance(other, dict): - # Merge the dictionaries - # Copy each item in case one does not support deep copy. - params = {} - if isinstance(self.params, dict): - for key, val in self.params.items(): - params[key] = self._copy_param(val) - for key, val in other.items(): - params[key] = self._copy_param(val) - else: - params = self._copy_param(other) - return params - - def _copy_param(self, param): - try: - return copy.deepcopy(param) - except Exception: - # Fallback to casting to unicode this will handle the - # python code-like objects that can't be deep-copied - return six.text_type(param) - - def __add__(self, other): - msg = _('Message objects do not support addition.') - raise TypeError(msg) - - def __radd__(self, other): - return self.__add__(other) - - if six.PY2: - def __str__(self): - # NOTE(luisg): Logging in python 2.6 tries to str() log records, - # and it expects specifically a UnicodeError in order to proceed. - msg = _('Message objects do not support str() because they may ' - 'contain non-ascii characters. ' - 'Please use unicode() or translate() instead.') - raise UnicodeError(msg) - - -def get_available_languages(domain): - """Lists the available languages for the given translation domain. - - :param domain: the domain to get languages for - """ - if domain in _AVAILABLE_LANGUAGES: - return copy.copy(_AVAILABLE_LANGUAGES[domain]) - - localedir = '%s_LOCALEDIR' % domain.upper() - find = lambda x: gettext.find(domain, - localedir=os.environ.get(localedir), - languages=[x]) - - # NOTE(mrodden): en_US should always be available (and first in case - # order matters) since our in-line message strings are en_US - language_list = ['en_US'] - # NOTE(luisg): Babel <1.0 used a function called list(), which was - # renamed to locale_identifiers() in >=1.0, the requirements master list - # requires >=0.9.6, uncapped, so defensively work with both. We can remove - # this check when the master list updates to >=1.0, and update all projects - list_identifiers = (getattr(localedata, 'list', None) or - getattr(localedata, 'locale_identifiers')) - locale_identifiers = list_identifiers() - - for i in locale_identifiers: - if find(i) is not None: - language_list.append(i) - - # NOTE(luisg): Babel>=1.0,<1.3 has a bug where some OpenStack supported - # locales (e.g. 'zh_CN', and 'zh_TW') aren't supported even though they - # are perfectly legitimate locales: - # https://github.com/mitsuhiko/babel/issues/37 - # In Babel 1.3 they fixed the bug and they support these locales, but - # they are still not explicitly "listed" by locale_identifiers(). - # That is why we add the locales here explicitly if necessary so that - # they are listed as supported. - aliases = {'zh': 'zh_CN', - 'zh_Hant_HK': 'zh_HK', - 'zh_Hant': 'zh_TW', - 'fil': 'tl_PH'} - for (locale_, alias) in six.iteritems(aliases): - if locale_ in language_list and alias not in language_list: - language_list.append(alias) - - _AVAILABLE_LANGUAGES[domain] = language_list - return copy.copy(language_list) - - -def translate(obj, desired_locale=None): - """Gets the translated unicode representation of the given object. - - If the object is not translatable it is returned as-is. - If the locale is None the object is translated to the system locale. - - :param obj: the object to translate - :param desired_locale: the locale to translate the message to, if None the - default system locale will be used - :returns: the translated object in unicode, or the original object if - it could not be translated - """ - message = obj - if not isinstance(message, Message): - # If the object to translate is not already translatable, - # let's first get its unicode representation - message = six.text_type(obj) - if isinstance(message, Message): - # Even after unicoding() we still need to check if we are - # running with translatable unicode before translating - return message.translate(desired_locale) - return obj - - -def _translate_args(args, desired_locale=None): - """Translates all the translatable elements of the given arguments object. - - This method is used for translating the translatable values in method - arguments which include values of tuples or dictionaries. - If the object is not a tuple or a dictionary the object itself is - translated if it is translatable. - - If the locale is None the object is translated to the system locale. - - :param args: the args to translate - :param desired_locale: the locale to translate the args to, if None the - default system locale will be used - :returns: a new args object with the translated contents of the original - """ - if isinstance(args, tuple): - return tuple(translate(v, desired_locale) for v in args) - if isinstance(args, dict): - translated_dict = {} - for (k, v) in six.iteritems(args): - translated_v = translate(v, desired_locale) - translated_dict[k] = translated_v - return translated_dict - return translate(args, desired_locale) - - -class TranslationHandler(handlers.MemoryHandler): - """Handler that translates records before logging them. - - The TranslationHandler takes a locale and a target logging.Handler object - to forward LogRecord objects to after translating them. This handler - depends on Message objects being logged, instead of regular strings. - - The handler can be configured declaratively in the logging.conf as follows: - - [handlers] - keys = translatedlog, translator - - [handler_translatedlog] - class = handlers.WatchedFileHandler - args = ('/var/log/api-localized.log',) - formatter = context - - [handler_translator] - class = openstack.common.log.TranslationHandler - target = translatedlog - args = ('zh_CN',) - - If the specified locale is not available in the system, the handler will - log in the default locale. - """ - - def __init__(self, locale=None, target=None): - """Initialize a TranslationHandler - - :param locale: locale to use for translating messages - :param target: logging.Handler object to forward - LogRecord objects to after translation - """ - # NOTE(luisg): In order to allow this handler to be a wrapper for - # other handlers, such as a FileHandler, and still be able to - # configure it using logging.conf, this handler has to extend - # MemoryHandler because only the MemoryHandlers' logging.conf - # parsing is implemented such that it accepts a target handler. - handlers.MemoryHandler.__init__(self, capacity=0, target=target) - self.locale = locale - - def setFormatter(self, fmt): - self.target.setFormatter(fmt) - - def emit(self, record): - # We save the message from the original record to restore it - # after translation, so other handlers are not affected by this - original_msg = record.msg - original_args = record.args - - try: - self._translate_and_log_record(record) - finally: - record.msg = original_msg - record.args = original_args - - def _translate_and_log_record(self, record): - record.msg = translate(record.msg, self.locale) - - # In addition to translating the message, we also need to translate - # arguments that were passed to the log method that were not part - # of the main message e.g., log.info(_('Some message %s'), this_one)) - record.args = _translate_args(record.args, self.locale) - - self.target.emit(record) diff --git a/tuskar/openstack/common/importutils.py b/tuskar/openstack/common/importutils.py deleted file mode 100644 index eba5712b..00000000 --- a/tuskar/openstack/common/importutils.py +++ /dev/null @@ -1,73 +0,0 @@ -# Copyright 2011 OpenStack Foundation. -# All Rights Reserved. -# -# 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 related utilities and helper functions. -""" - -import sys -import traceback - - -def import_class(import_str): - """Returns a class from a string including module and class.""" - mod_str, _sep, class_str = import_str.rpartition('.') - __import__(mod_str) - try: - return getattr(sys.modules[mod_str], class_str) - except AttributeError: - raise ImportError('Class %s cannot be found (%s)' % - (class_str, - traceback.format_exception(*sys.exc_info()))) - - -def import_object(import_str, *args, **kwargs): - """Import a class and return an instance of it.""" - return import_class(import_str)(*args, **kwargs) - - -def import_object_ns(name_space, import_str, *args, **kwargs): - """Tries to import object from default namespace. - - Imports a class and return an instance of it, first by trying - to find the class in a default namespace, then failing back to - a full path if not found in the default namespace. - """ - import_value = "%s.%s" % (name_space, import_str) - try: - return import_class(import_value)(*args, **kwargs) - except ImportError: - return import_class(import_str)(*args, **kwargs) - - -def import_module(import_str): - """Import a module.""" - __import__(import_str) - return sys.modules[import_str] - - -def import_versioned_module(version, submodule=None): - module = 'tuskar.v%s' % version - if submodule: - module = '.'.join((module, submodule)) - return import_module(module) - - -def try_import(import_str, default=None): - """Try to import a module and if it fails return default.""" - try: - return import_module(import_str) - except ImportError: - return default diff --git a/tuskar/openstack/common/jsonutils.py b/tuskar/openstack/common/jsonutils.py deleted file mode 100644 index d4041e24..00000000 --- a/tuskar/openstack/common/jsonutils.py +++ /dev/null @@ -1,186 +0,0 @@ -# Copyright 2010 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# Copyright 2011 Justin Santa Barbara -# All Rights Reserved. -# -# 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. - -''' -JSON related utilities. - -This module provides a few things: - - 1) A handy function for getting an object down to something that can be - JSON serialized. See to_primitive(). - - 2) Wrappers around loads() and dumps(). The dumps() wrapper will - automatically use to_primitive() for you if needed. - - 3) This sets up anyjson to use the loads() and dumps() wrappers if anyjson - is available. -''' - - -import codecs -import datetime -import functools -import inspect -import itertools -import sys - -if sys.version_info < (2, 7): - # On Python <= 2.6, json module is not C boosted, so try to use - # simplejson module if available - try: - import simplejson as json - except ImportError: - import json -else: - import json - -import six -import six.moves.xmlrpc_client as xmlrpclib - -from tuskar.openstack.common import gettextutils -from tuskar.openstack.common import importutils -from tuskar.openstack.common import strutils -from tuskar.openstack.common import timeutils - -netaddr = importutils.try_import("netaddr") - -_nasty_type_tests = [inspect.ismodule, inspect.isclass, inspect.ismethod, - inspect.isfunction, inspect.isgeneratorfunction, - inspect.isgenerator, inspect.istraceback, inspect.isframe, - inspect.iscode, inspect.isbuiltin, inspect.isroutine, - inspect.isabstract] - -_simple_types = (six.string_types + six.integer_types - + (type(None), bool, float)) - - -def to_primitive(value, convert_instances=False, convert_datetime=True, - level=0, max_depth=3): - """Convert a complex object into primitives. - - Handy for JSON serialization. We can optionally handle instances, - but since this is a recursive function, we could have cyclical - data structures. - - To handle cyclical data structures we could track the actual objects - visited in a set, but not all objects are hashable. Instead we just - track the depth of the object inspections and don't go too deep. - - Therefore, convert_instances=True is lossy ... be aware. - - """ - # handle obvious types first - order of basic types determined by running - # full tests on nova project, resulting in the following counts: - # 572754 - # 460353 - # 379632 - # 274610 - # 199918 - # 114200 - # 51817 - # 26164 - # 6491 - # 283 - # 19 - if isinstance(value, _simple_types): - return value - - if isinstance(value, datetime.datetime): - if convert_datetime: - return timeutils.strtime(value) - else: - return value - - # value of itertools.count doesn't get caught by nasty_type_tests - # and results in infinite loop when list(value) is called. - if type(value) == itertools.count: - return six.text_type(value) - - # FIXME(vish): Workaround for LP bug 852095. Without this workaround, - # tests that raise an exception in a mocked method that - # has a @wrap_exception with a notifier will fail. If - # we up the dependency to 0.5.4 (when it is released) we - # can remove this workaround. - if getattr(value, '__module__', None) == 'mox': - return 'mock' - - if level > max_depth: - return '?' - - # The try block may not be necessary after the class check above, - # but just in case ... - try: - recursive = functools.partial(to_primitive, - convert_instances=convert_instances, - convert_datetime=convert_datetime, - level=level, - max_depth=max_depth) - if isinstance(value, dict): - return dict((k, recursive(v)) for k, v in six.iteritems(value)) - elif isinstance(value, (list, tuple)): - return [recursive(lv) for lv in value] - - # It's not clear why xmlrpclib created their own DateTime type, but - # for our purposes, make it a datetime type which is explicitly - # handled - if isinstance(value, xmlrpclib.DateTime): - value = datetime.datetime(*tuple(value.timetuple())[:6]) - - if convert_datetime and isinstance(value, datetime.datetime): - return timeutils.strtime(value) - elif isinstance(value, gettextutils.Message): - return value.data - elif hasattr(value, 'iteritems'): - return recursive(dict(value.iteritems()), level=level + 1) - elif hasattr(value, '__iter__'): - return recursive(list(value)) - elif convert_instances and hasattr(value, '__dict__'): - # Likely an instance of something. Watch for cycles. - # Ignore class member vars. - return recursive(value.__dict__, level=level + 1) - elif netaddr and isinstance(value, netaddr.IPAddress): - return six.text_type(value) - else: - if any(test(value) for test in _nasty_type_tests): - return six.text_type(value) - return value - except TypeError: - # Class objects are tricky since they may define something like - # __iter__ defined but it isn't callable as list(). - return six.text_type(value) - - -def dumps(value, default=to_primitive, **kwargs): - return json.dumps(value, default=default, **kwargs) - - -def loads(s, encoding='utf-8'): - return json.loads(strutils.safe_decode(s, encoding)) - - -def load(fp, encoding='utf-8'): - return json.load(codecs.getreader(encoding)(fp)) - - -try: - import anyjson -except ImportError: - pass -else: - anyjson._modules.append((__name__, 'dumps', TypeError, - 'loads', ValueError, 'load')) - anyjson.force_implementation(__name__) diff --git a/tuskar/openstack/common/local.py b/tuskar/openstack/common/local.py deleted file mode 100644 index 0819d5b9..00000000 --- a/tuskar/openstack/common/local.py +++ /dev/null @@ -1,45 +0,0 @@ -# Copyright 2011 OpenStack Foundation. -# All Rights Reserved. -# -# 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. - -"""Local storage of variables using weak references""" - -import threading -import weakref - - -class WeakLocal(threading.local): - def __getattribute__(self, attr): - rval = super(WeakLocal, self).__getattribute__(attr) - if rval: - # NOTE(mikal): this bit is confusing. What is stored is a weak - # reference, not the value itself. We therefore need to lookup - # the weak reference and return the inner value here. - rval = rval() - return rval - - def __setattr__(self, attr, value): - value = weakref.ref(value) - return super(WeakLocal, self).__setattr__(attr, value) - - -# NOTE(mikal): the name "store" should be deprecated in the future -store = WeakLocal() - -# A "weak" store uses weak references and allows an object to fall out of scope -# when it falls out of scope in the code that uses the thread local storage. A -# "strong" store will hold a reference to the object so that it never falls out -# of scope. -weak_store = WeakLocal() -strong_store = threading.local() diff --git a/tuskar/openstack/common/log.py b/tuskar/openstack/common/log.py deleted file mode 100644 index 7492d99e..00000000 --- a/tuskar/openstack/common/log.py +++ /dev/null @@ -1,713 +0,0 @@ -# Copyright 2011 OpenStack Foundation. -# Copyright 2010 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# All Rights Reserved. -# -# 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. - -"""OpenStack logging handler. - -This module adds to logging functionality by adding the option to specify -a context object when calling the various log methods. If the context object -is not specified, default formatting is used. Additionally, an instance uuid -may be passed as part of the log message, which is intended to make it easier -for admins to find messages related to a specific instance. - -It also allows setting of formatting information through conf. - -""" - -import inspect -import itertools -import logging -import logging.config -import logging.handlers -import os -import re -import sys -import traceback - -from oslo_config import cfg -import six -from six import moves - -from tuskar.openstack.common.gettextutils import _ -from tuskar.openstack.common import importutils -from tuskar.openstack.common import jsonutils -from tuskar.openstack.common import local - - -_DEFAULT_LOG_DATE_FORMAT = "%Y-%m-%d %H:%M:%S" - -_SANITIZE_KEYS = ['adminPass', 'admin_pass', 'password', 'admin_password'] - -# NOTE(ldbragst): Let's build a list of regex objects using the list of -# _SANITIZE_KEYS we already have. This way, we only have to add the new key -# to the list of _SANITIZE_KEYS and we can generate regular expressions -# for XML and JSON automatically. -_SANITIZE_PATTERNS = [] -_FORMAT_PATTERNS = [r'(%(key)s\s*[=]\s*[\"\']).*?([\"\'])', - r'(<%(key)s>).*?()', - r'([\"\']%(key)s[\"\']\s*:\s*[\"\']).*?([\"\'])', - r'([\'"].*?%(key)s[\'"]\s*:\s*u?[\'"]).*?([\'"])'] - -for key in _SANITIZE_KEYS: - for pattern in _FORMAT_PATTERNS: - reg_ex = re.compile(pattern % {'key': key}, re.DOTALL) - _SANITIZE_PATTERNS.append(reg_ex) - - -common_cli_opts = [ - cfg.BoolOpt('debug', - short='d', - default=False, - help='Print debugging output (set logging level to ' - 'DEBUG instead of default WARNING level).'), - cfg.BoolOpt('verbose', - short='v', - default=False, - help='Print more verbose output (set logging level to ' - 'INFO instead of default WARNING level).'), -] - -logging_cli_opts = [ - cfg.StrOpt('log-config-append', - metavar='PATH', - deprecated_name='log-config', - help='The name of logging configuration file. It does not ' - 'disable existing loggers, but just appends specified ' - 'logging configuration to any other existing logging ' - 'options. Please see the Python logging module ' - 'documentation for details on logging configuration ' - 'files.'), - cfg.StrOpt('log-format', - default=None, - metavar='FORMAT', - help='DEPRECATED. ' - 'A logging.Formatter log message format string which may ' - 'use any of the available logging.LogRecord attributes. ' - 'This option is deprecated. Please use ' - 'logging_context_format_string and ' - 'logging_default_format_string instead.'), - cfg.StrOpt('log-date-format', - default=_DEFAULT_LOG_DATE_FORMAT, - metavar='DATE_FORMAT', - help='Format string for %%(asctime)s in log records. ' - 'Default: %(default)s'), - cfg.StrOpt('log-file', - metavar='PATH', - deprecated_name='logfile', - help='(Optional) Name of log file to output to. ' - 'If no default is set, logging will go to stdout.'), - cfg.StrOpt('log-dir', - deprecated_name='logdir', - help='(Optional) The base directory used for relative ' - '--log-file paths'), - cfg.BoolOpt('use-syslog', - default=False, - help='Use syslog for logging. ' - 'Existing syslog format is DEPRECATED during I, ' - 'and then will be changed in J to honor RFC5424'), - cfg.BoolOpt('use-syslog-rfc-format', - # TODO(bogdando) remove or use True after existing - # syslog format deprecation in J - default=False, - help='(Optional) Use syslog rfc5424 format for logging. ' - 'If enabled, will add APP-NAME (RFC5424) before the ' - 'MSG part of the syslog message. The old format ' - 'without APP-NAME is deprecated in I, ' - 'and will be removed in J.'), - cfg.StrOpt('syslog-log-facility', - default='LOG_USER', - help='Syslog facility to receive log lines') -] - -generic_log_opts = [ - cfg.BoolOpt('use_stderr', - default=True, - help='Log output to standard error') -] - -log_opts = [ - cfg.StrOpt('logging_context_format_string', - default='%(asctime)s.%(msecs)03d %(process)d %(levelname)s ' - '%(name)s [%(request_id)s %(user_identity)s] ' - '%(instance)s%(message)s', - help='Format string to use for log messages with context'), - cfg.StrOpt('logging_default_format_string', - default='%(asctime)s.%(msecs)03d %(process)d %(levelname)s ' - '%(name)s [-] %(instance)s%(message)s', - help='Format string to use for log messages without context'), - cfg.StrOpt('logging_debug_format_suffix', - default='%(funcName)s %(pathname)s:%(lineno)d', - help='Data to append to log format when level is DEBUG'), - cfg.StrOpt('logging_exception_prefix', - default='%(asctime)s.%(msecs)03d %(process)d TRACE %(name)s ' - '%(instance)s', - help='Prefix each line of exception output with this format'), - cfg.ListOpt('default_log_levels', - default=[ - 'amqp=WARN', - 'amqplib=WARN', - 'boto=WARN', - 'qpid=WARN', - 'sqlalchemy=WARN', - 'suds=INFO', - 'oslo.messaging=INFO', - 'iso8601=WARN', - 'requests.packages.urllib3.connectionpool=WARN' - ], - help='List of logger=LEVEL pairs'), - cfg.BoolOpt('publish_errors', - default=False, - help='Publish error events'), - cfg.BoolOpt('fatal_deprecations', - default=False, - help='Make deprecations fatal'), - - # NOTE(mikal): there are two options here because sometimes we are handed - # a full instance (and could include more information), and other times we - # are just handed a UUID for the instance. - cfg.StrOpt('instance_format', - default='[instance: %(uuid)s] ', - help='If an instance is passed with the log message, format ' - 'it like this'), - cfg.StrOpt('instance_uuid_format', - default='[instance: %(uuid)s] ', - help='If an instance UUID is passed with the log message, ' - 'format it like this'), -] - -CONF = cfg.CONF -CONF.register_cli_opts(common_cli_opts) -CONF.register_cli_opts(logging_cli_opts) -CONF.register_opts(generic_log_opts) -CONF.register_opts(log_opts) - -# our new audit level -# NOTE(jkoelker) Since we synthesized an audit level, make the logging -# module aware of it so it acts like other levels. -logging.AUDIT = logging.INFO + 1 -logging.addLevelName(logging.AUDIT, 'AUDIT') - - -try: - NullHandler = logging.NullHandler -except AttributeError: # NOTE(jkoelker) NullHandler added in Python 2.7 - class NullHandler(logging.Handler): - def handle(self, record): - pass - - def emit(self, record): - pass - - def createLock(self): - self.lock = None - - -def _dictify_context(context): - if context is None: - return None - if not isinstance(context, dict) and getattr(context, 'to_dict', None): - context = context.to_dict() - return context - - -def _get_binary_name(): - return os.path.basename(inspect.stack()[-1][1]) - - -def _get_log_file_path(binary=None): - logfile = CONF.log_file - logdir = CONF.log_dir - - if logfile and not logdir: - return logfile - - if logfile and logdir: - return os.path.join(logdir, logfile) - - if logdir: - binary = binary or _get_binary_name() - return '%s.log' % (os.path.join(logdir, binary),) - - return None - - -def mask_password(message, secret="***"): - """Replace password with 'secret' in message. - - :param message: The string which includes security information. - :param secret: value with which to replace passwords. - :returns: The unicode value of message with the password fields masked. - - For example: - - >>> mask_password("'adminPass' : 'aaaaa'") - "'adminPass' : '***'" - >>> mask_password("'admin_pass' : 'aaaaa'") - "'admin_pass' : '***'" - >>> mask_password('"password" : "aaaaa"') - '"password" : "***"' - >>> mask_password("'original_password' : 'aaaaa'") - "'original_password' : '***'" - >>> mask_password("u'original_password' : u'aaaaa'") - "u'original_password' : u'***'" - """ - message = six.text_type(message) - - # NOTE(ldbragst): Check to see if anything in message contains any key - # specified in _SANITIZE_KEYS, if not then just return the message since - # we don't have to mask any passwords. - if not any(key in message for key in _SANITIZE_KEYS): - return message - - secret = r'\g<1>' + secret + r'\g<2>' - for pattern in _SANITIZE_PATTERNS: - message = re.sub(pattern, secret, message) - return message - - -class BaseLoggerAdapter(logging.LoggerAdapter): - - def audit(self, msg, *args, **kwargs): - self.log(logging.AUDIT, msg, *args, **kwargs) - - -class LazyAdapter(BaseLoggerAdapter): - def __init__(self, name='unknown', version='unknown'): - self._logger = None - self.extra = {} - self.name = name - self.version = version - - @property - def logger(self): - if not self._logger: - self._logger = getLogger(self.name, self.version) - return self._logger - - -class ContextAdapter(BaseLoggerAdapter): - warn = logging.LoggerAdapter.warning - - def __init__(self, logger, project_name, version_string): - self.logger = logger - self.project = project_name - self.version = version_string - self._deprecated_messages_sent = dict() - - @property - def handlers(self): - return self.logger.handlers - - def deprecated(self, msg, *args, **kwargs): - """Call this method when a deprecated feature is used. - - If the system is configured for fatal deprecations then the message - is logged at the 'critical' level and :class:`DeprecatedConfig` will - be raised. - - Otherwise, the message will be logged (once) at the 'warn' level. - - :raises: :class:`DeprecatedConfig` if the system is configured for - fatal deprecations. - - """ - stdmsg = _("Deprecated: %s") % msg - if CONF.fatal_deprecations: - self.critical(stdmsg, *args, **kwargs) - raise DeprecatedConfig(msg=stdmsg) - - # Using a list because a tuple with dict can't be stored in a set. - sent_args = self._deprecated_messages_sent.setdefault(msg, list()) - - if args in sent_args: - # Already logged this message, so don't log it again. - return - - sent_args.append(args) - self.warn(stdmsg, *args, **kwargs) - - def process(self, msg, kwargs): - # NOTE(mrodden): catch any Message/other object and - # coerce to unicode before they can get - # to the python logging and possibly - # cause string encoding trouble - if not isinstance(msg, six.string_types): - msg = six.text_type(msg) - - if 'extra' not in kwargs: - kwargs['extra'] = {} - extra = kwargs['extra'] - - context = kwargs.pop('context', None) - if not context: - context = getattr(local.store, 'context', None) - if context: - extra.update(_dictify_context(context)) - - instance = kwargs.pop('instance', None) - instance_uuid = (extra.get('instance_uuid') or - kwargs.pop('instance_uuid', None)) - instance_extra = '' - if instance: - instance_extra = CONF.instance_format % instance - elif instance_uuid: - instance_extra = (CONF.instance_uuid_format - % {'uuid': instance_uuid}) - extra['instance'] = instance_extra - - extra.setdefault('user_identity', kwargs.pop('user_identity', None)) - - extra['project'] = self.project - extra['version'] = self.version - extra['extra'] = extra.copy() - return msg, kwargs - - -class JSONFormatter(logging.Formatter): - def __init__(self, fmt=None, datefmt=None): - # NOTE(jkoelker) we ignore the fmt argument, but its still there - # since logging.config.fileConfig passes it. - self.datefmt = datefmt - - def formatException(self, ei, strip_newlines=True): - lines = traceback.format_exception(*ei) - if strip_newlines: - lines = [moves.filter( - lambda x: x, - line.rstrip().splitlines()) for line in lines] - lines = list(itertools.chain(*lines)) - return lines - - def format(self, record): - message = {'message': record.getMessage(), - 'asctime': self.formatTime(record, self.datefmt), - 'name': record.name, - 'msg': record.msg, - 'args': record.args, - 'levelname': record.levelname, - 'levelno': record.levelno, - 'pathname': record.pathname, - 'filename': record.filename, - 'module': record.module, - 'lineno': record.lineno, - 'funcname': record.funcName, - 'created': record.created, - 'msecs': record.msecs, - 'relative_created': record.relativeCreated, - 'thread': record.thread, - 'thread_name': record.threadName, - 'process_name': record.processName, - 'process': record.process, - 'traceback': None} - - if hasattr(record, 'extra'): - message['extra'] = record.extra - - if record.exc_info: - message['traceback'] = self.formatException(record.exc_info) - - return jsonutils.dumps(message) - - -def _create_logging_excepthook(product_name): - def logging_excepthook(exc_type, value, tb): - extra = {} - if CONF.verbose or CONF.debug: - extra['exc_info'] = (exc_type, value, tb) - getLogger(product_name).critical( - "".join(traceback.format_exception_only(exc_type, value)), - **extra) - return logging_excepthook - - -class LogConfigError(Exception): - - message = _('Error loading logging config %(log_config)s: %(err_msg)s') - - def __init__(self, log_config, err_msg): - self.log_config = log_config - self.err_msg = err_msg - - def __str__(self): - return self.message % dict(log_config=self.log_config, - err_msg=self.err_msg) - - -def _load_log_config(log_config_append): - try: - logging.config.fileConfig(log_config_append, - disable_existing_loggers=False) - except moves.configparser.Error as exc: - raise LogConfigError(log_config_append, str(exc)) - - -def setup(product_name, version='unknown'): - """Setup logging.""" - if CONF.log_config_append: - _load_log_config(CONF.log_config_append) - else: - _setup_logging_from_conf(product_name, version) - sys.excepthook = _create_logging_excepthook(product_name) - - -def set_defaults(logging_context_format_string): - cfg.set_defaults(log_opts, - logging_context_format_string= - logging_context_format_string) - - -def _find_facility_from_conf(): - facility_names = logging.handlers.SysLogHandler.facility_names - facility = getattr(logging.handlers.SysLogHandler, - CONF.syslog_log_facility, - None) - - if facility is None and CONF.syslog_log_facility in facility_names: - facility = facility_names.get(CONF.syslog_log_facility) - - if facility is None: - valid_facilities = facility_names.keys() - consts = ['LOG_AUTH', 'LOG_AUTHPRIV', 'LOG_CRON', 'LOG_DAEMON', - 'LOG_FTP', 'LOG_KERN', 'LOG_LPR', 'LOG_MAIL', 'LOG_NEWS', - 'LOG_AUTH', 'LOG_SYSLOG', 'LOG_USER', 'LOG_UUCP', - 'LOG_LOCAL0', 'LOG_LOCAL1', 'LOG_LOCAL2', 'LOG_LOCAL3', - 'LOG_LOCAL4', 'LOG_LOCAL5', 'LOG_LOCAL6', 'LOG_LOCAL7'] - valid_facilities.extend(consts) - raise TypeError(_('syslog facility must be one of: %s') % - ', '.join("'%s'" % fac - for fac in valid_facilities)) - - return facility - - -class RFCSysLogHandler(logging.handlers.SysLogHandler): - def __init__(self, *args, **kwargs): - self.binary_name = _get_binary_name() - super(RFCSysLogHandler, self).__init__(*args, **kwargs) - - def format(self, record): - msg = super(RFCSysLogHandler, self).format(record) - msg = self.binary_name + ' ' + msg - return msg - - -def _setup_logging_from_conf(project, version): - log_root = getLogger(None).logger - for handler in log_root.handlers: - log_root.removeHandler(handler) - - if CONF.use_syslog: - facility = _find_facility_from_conf() - # TODO(bogdando) use the format provided by RFCSysLogHandler - # after existing syslog format deprecation in J - if CONF.use_syslog_rfc_format: - syslog = RFCSysLogHandler(address='/dev/log', - facility=facility) - else: - syslog = logging.handlers.SysLogHandler(address='/dev/log', - facility=facility) - log_root.addHandler(syslog) - - logpath = _get_log_file_path() - if logpath: - filelog = logging.handlers.WatchedFileHandler(logpath) - log_root.addHandler(filelog) - - if CONF.use_stderr: - streamlog = ColorHandler() - log_root.addHandler(streamlog) - - elif not logpath: - # pass sys.stdout as a positional argument - # python2.6 calls the argument strm, in 2.7 it's stream - streamlog = logging.StreamHandler(sys.stdout) - log_root.addHandler(streamlog) - - if CONF.publish_errors: - handler = importutils.import_object( - "tuskar.openstack.common.log_handler.PublishErrorsHandler", - logging.ERROR) - log_root.addHandler(handler) - - datefmt = CONF.log_date_format - for handler in log_root.handlers: - # NOTE(alaski): CONF.log_format overrides everything currently. This - # should be deprecated in favor of context aware formatting. - if CONF.log_format: - handler.setFormatter(logging.Formatter(fmt=CONF.log_format, - datefmt=datefmt)) - log_root.info('Deprecated: log_format is now deprecated and will ' - 'be removed in the next release') - else: - handler.setFormatter(ContextFormatter(project=project, - version=version, - datefmt=datefmt)) - - if CONF.debug: - log_root.setLevel(logging.DEBUG) - elif CONF.verbose: - log_root.setLevel(logging.INFO) - else: - log_root.setLevel(logging.WARNING) - - for pair in CONF.default_log_levels: - mod, _sep, level_name = pair.partition('=') - level = logging.getLevelName(level_name) - logger = logging.getLogger(mod) - logger.setLevel(level) - -_loggers = {} - - -def getLogger(name='unknown', version='unknown'): - if name not in _loggers: - _loggers[name] = ContextAdapter(logging.getLogger(name), - name, - version) - return _loggers[name] - - -def getLazyLogger(name='unknown', version='unknown'): - """Returns lazy logger. - - Creates a pass-through logger that does not create the real logger - until it is really needed and delegates all calls to the real logger - once it is created. - """ - return LazyAdapter(name, version) - - -class WritableLogger(object): - """A thin wrapper that responds to `write` and logs.""" - - def __init__(self, logger, level=logging.INFO): - self.logger = logger - self.level = level - - def write(self, msg): - self.logger.log(self.level, msg.rstrip()) - - -class ContextFormatter(logging.Formatter): - """A context.RequestContext aware formatter configured through flags. - - The flags used to set format strings are: logging_context_format_string - and logging_default_format_string. You can also specify - logging_debug_format_suffix to append extra formatting if the log level is - debug. - - For information about what variables are available for the formatter see: - http://docs.python.org/library/logging.html#formatter - - If available, uses the context value stored in TLS - local.store.context - - """ - - def __init__(self, *args, **kwargs): - """Initialize ContextFormatter instance - - Takes additional keyword arguments which can be used in the message - format string. - - :keyword project: project name - :type project: string - :keyword version: project version - :type version: string - - """ - - self.project = kwargs.pop('project', 'unknown') - self.version = kwargs.pop('version', 'unknown') - - logging.Formatter.__init__(self, *args, **kwargs) - - def format(self, record): - """Uses contextstring if request_id is set, otherwise default.""" - - # store project info - record.project = self.project - record.version = self.version - - # store request info - context = getattr(local.store, 'context', None) - if context: - d = _dictify_context(context) - for k, v in d.items(): - setattr(record, k, v) - - # NOTE(sdague): default the fancier formatting params - # to an empty string so we don't throw an exception if - # they get used - for key in ('instance', 'color', 'user_identity'): - if key not in record.__dict__: - record.__dict__[key] = '' - - if record.__dict__.get('request_id'): - self._fmt = CONF.logging_context_format_string - else: - self._fmt = CONF.logging_default_format_string - - if (record.levelno == logging.DEBUG and - CONF.logging_debug_format_suffix): - self._fmt += " " + CONF.logging_debug_format_suffix - - # Cache this on the record, Logger will respect our formatted copy - if record.exc_info: - record.exc_text = self.formatException(record.exc_info, record) - return logging.Formatter.format(self, record) - - def formatException(self, exc_info, record=None): - """Format exception output with CONF.logging_exception_prefix.""" - if not record: - return logging.Formatter.formatException(self, exc_info) - - stringbuffer = moves.StringIO() - traceback.print_exception(exc_info[0], exc_info[1], exc_info[2], - None, stringbuffer) - lines = stringbuffer.getvalue().split('\n') - stringbuffer.close() - - if CONF.logging_exception_prefix.find('%(asctime)') != -1: - record.asctime = self.formatTime(record, self.datefmt) - - formatted_lines = [] - for line in lines: - pl = CONF.logging_exception_prefix % record.__dict__ - fl = '%s%s' % (pl, line) - formatted_lines.append(fl) - return '\n'.join(formatted_lines) - - -class ColorHandler(logging.StreamHandler): - LEVEL_COLORS = { - logging.DEBUG: '\033[00;32m', # GREEN - logging.INFO: '\033[00;36m', # CYAN - logging.AUDIT: '\033[01;36m', # BOLD CYAN - logging.WARN: '\033[01;33m', # BOLD YELLOW - logging.ERROR: '\033[01;31m', # BOLD RED - logging.CRITICAL: '\033[01;31m', # BOLD RED - } - - def format(self, record): - record.color = self.LEVEL_COLORS[record.levelno] - return logging.StreamHandler.format(self, record) - - -class DeprecatedConfig(Exception): - message = _("Fatal call to deprecated config: %(msg)s") - - def __init__(self, msg): - super(Exception, self).__init__(self.message % dict(msg=msg)) diff --git a/tuskar/openstack/common/strutils.py b/tuskar/openstack/common/strutils.py deleted file mode 100644 index 729200d6..00000000 --- a/tuskar/openstack/common/strutils.py +++ /dev/null @@ -1,239 +0,0 @@ -# Copyright 2011 OpenStack Foundation. -# All Rights Reserved. -# -# 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. - -""" -System-level utilities and helper functions. -""" - -import math -import re -import sys -import unicodedata - -import six - -from tuskar.openstack.common.gettextutils import _ - - -UNIT_PREFIX_EXPONENT = { - 'k': 1, - 'K': 1, - 'Ki': 1, - 'M': 2, - 'Mi': 2, - 'G': 3, - 'Gi': 3, - 'T': 4, - 'Ti': 4, -} -UNIT_SYSTEM_INFO = { - 'IEC': (1024, re.compile(r'(^[-+]?\d*\.?\d+)([KMGT]i?)?(b|bit|B)$')), - 'SI': (1000, re.compile(r'(^[-+]?\d*\.?\d+)([kMGT])?(b|bit|B)$')), -} - -TRUE_STRINGS = ('1', 't', 'true', 'on', 'y', 'yes') -FALSE_STRINGS = ('0', 'f', 'false', 'off', 'n', 'no') - -SLUGIFY_STRIP_RE = re.compile(r"[^\w\s-]") -SLUGIFY_HYPHENATE_RE = re.compile(r"[-\s]+") - - -def int_from_bool_as_string(subject): - """Interpret a string as a boolean and return either 1 or 0. - - Any string value in: - - ('True', 'true', 'On', 'on', '1') - - is interpreted as a boolean True. - - Useful for JSON-decoded stuff and config file parsing - """ - return bool_from_string(subject) and 1 or 0 - - -def bool_from_string(subject, strict=False, default=False): - """Interpret a string as a boolean. - - A case-insensitive match is performed such that strings matching 't', - 'true', 'on', 'y', 'yes', or '1' are considered True and, when - `strict=False`, anything else returns the value specified by 'default'. - - Useful for JSON-decoded stuff and config file parsing. - - If `strict=True`, unrecognized values, including None, will raise a - ValueError which is useful when parsing values passed in from an API call. - Strings yielding False are 'f', 'false', 'off', 'n', 'no', or '0'. - """ - if not isinstance(subject, six.string_types): - subject = six.text_type(subject) - - lowered = subject.strip().lower() - - if lowered in TRUE_STRINGS: - return True - elif lowered in FALSE_STRINGS: - return False - elif strict: - acceptable = ', '.join( - "'%s'" % s for s in sorted(TRUE_STRINGS + FALSE_STRINGS)) - msg = _("Unrecognized value '%(val)s', acceptable values are:" - " %(acceptable)s") % {'val': subject, - 'acceptable': acceptable} - raise ValueError(msg) - else: - return default - - -def safe_decode(text, incoming=None, errors='strict'): - """Decodes incoming text/bytes string using `incoming` if they're not - already unicode. - - :param incoming: Text's current encoding - :param errors: Errors handling policy. See here for valid - values http://docs.python.org/2/library/codecs.html - :returns: text or a unicode `incoming` encoded - representation of it. - :raises TypeError: If text is not an instance of str - """ - if not isinstance(text, (six.string_types, six.binary_type)): - raise TypeError("%s can't be decoded" % type(text)) - - if isinstance(text, six.text_type): - return text - - if not incoming: - incoming = (sys.stdin.encoding or - sys.getdefaultencoding()) - - try: - return text.decode(incoming, errors) - except UnicodeDecodeError: - # Note(flaper87) If we get here, it means that - # sys.stdin.encoding / sys.getdefaultencoding - # didn't return a suitable encoding to decode - # text. This happens mostly when global LANG - # var is not set correctly and there's no - # default encoding. In this case, most likely - # python will use ASCII or ANSI encoders as - # default encodings but they won't be capable - # of decoding non-ASCII characters. - # - # Also, UTF-8 is being used since it's an ASCII - # extension. - return text.decode('utf-8', errors) - - -def safe_encode(text, incoming=None, - encoding='utf-8', errors='strict'): - """Encodes incoming text/bytes string using `encoding`. - - If incoming is not specified, text is expected to be encoded with - current python's default encoding. (`sys.getdefaultencoding`) - - :param incoming: Text's current encoding - :param encoding: Expected encoding for text (Default UTF-8) - :param errors: Errors handling policy. See here for valid - values http://docs.python.org/2/library/codecs.html - :returns: text or a bytestring `encoding` encoded - representation of it. - :raises TypeError: If text is not an instance of str - """ - if not isinstance(text, (six.string_types, six.binary_type)): - raise TypeError("%s can't be encoded" % type(text)) - - if not incoming: - incoming = (sys.stdin.encoding or - sys.getdefaultencoding()) - - if isinstance(text, six.text_type): - return text.encode(encoding, errors) - elif text and encoding != incoming: - # Decode text before encoding it with `encoding` - text = safe_decode(text, incoming, errors) - return text.encode(encoding, errors) - else: - return text - - -def string_to_bytes(text, unit_system='IEC', return_int=False): - """Converts a string into an float representation of bytes. - - The units supported for IEC :: - - Kb(it), Kib(it), Mb(it), Mib(it), Gb(it), Gib(it), Tb(it), Tib(it) - KB, KiB, MB, MiB, GB, GiB, TB, TiB - - The units supported for SI :: - - kb(it), Mb(it), Gb(it), Tb(it) - kB, MB, GB, TB - - Note that the SI unit system does not support capital letter 'K' - - :param text: String input for bytes size conversion. - :param unit_system: Unit system for byte size conversion. - :param return_int: If True, returns integer representation of text - in bytes. (default: decimal) - :returns: Numerical representation of text in bytes. - :raises ValueError: If text has an invalid value. - - """ - try: - base, reg_ex = UNIT_SYSTEM_INFO[unit_system] - except KeyError: - msg = _('Invalid unit system: "%s"') % unit_system - raise ValueError(msg) - match = reg_ex.match(text) - if match: - magnitude = float(match.group(1)) - unit_prefix = match.group(2) - if match.group(3) in ['b', 'bit']: - magnitude /= 8 - else: - msg = _('Invalid string format: %s') % text - raise ValueError(msg) - if not unit_prefix: - res = magnitude - else: - res = magnitude * pow(base, UNIT_PREFIX_EXPONENT[unit_prefix]) - if return_int: - return int(math.ceil(res)) - return res - - -def to_slug(value, incoming=None, errors="strict"): - """Normalize string. - - Convert to lowercase, remove non-word characters, and convert spaces - to hyphens. - - Inspired by Django's `slugify` filter. - - :param value: Text to slugify - :param incoming: Text's current encoding - :param errors: Errors handling policy. See here for valid - values http://docs.python.org/2/library/codecs.html - :returns: slugified unicode representation of `value` - :raises TypeError: If text is not an instance of str - """ - value = safe_decode(value, incoming, errors) - # NOTE(aababilov): no need to use safe_(encode|decode) here: - # encodings are always "ascii", error handling is always "ignore" - # and types are always known (first: unicode; second: str) - value = unicodedata.normalize("NFKD", value).encode( - "ascii", "ignore").decode("ascii") - value = SLUGIFY_STRIP_RE.sub("", value).strip().lower() - return SLUGIFY_HYPHENATE_RE.sub("-", value) diff --git a/tuskar/openstack/common/timeutils.py b/tuskar/openstack/common/timeutils.py deleted file mode 100644 index c48da95f..00000000 --- a/tuskar/openstack/common/timeutils.py +++ /dev/null @@ -1,210 +0,0 @@ -# Copyright 2011 OpenStack Foundation. -# All Rights Reserved. -# -# 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. - -""" -Time related utilities and helper functions. -""" - -import calendar -import datetime -import time - -import iso8601 -import six - - -# ISO 8601 extended time format with microseconds -_ISO8601_TIME_FORMAT_SUBSECOND = '%Y-%m-%dT%H:%M:%S.%f' -_ISO8601_TIME_FORMAT = '%Y-%m-%dT%H:%M:%S' -PERFECT_TIME_FORMAT = _ISO8601_TIME_FORMAT_SUBSECOND - - -def isotime(at=None, subsecond=False): - """Stringify time in ISO 8601 format.""" - if not at: - at = utcnow() - st = at.strftime(_ISO8601_TIME_FORMAT - if not subsecond - else _ISO8601_TIME_FORMAT_SUBSECOND) - tz = at.tzinfo.tzname(None) if at.tzinfo else 'UTC' - st += ('Z' if tz == 'UTC' else tz) - return st - - -def parse_isotime(timestr): - """Parse time from ISO 8601 format.""" - try: - return iso8601.parse_date(timestr) - except iso8601.ParseError as e: - raise ValueError(six.text_type(e)) - except TypeError as e: - raise ValueError(six.text_type(e)) - - -def strtime(at=None, fmt=PERFECT_TIME_FORMAT): - """Returns formatted utcnow.""" - if not at: - at = utcnow() - return at.strftime(fmt) - - -def parse_strtime(timestr, fmt=PERFECT_TIME_FORMAT): - """Turn a formatted time back into a datetime.""" - return datetime.datetime.strptime(timestr, fmt) - - -def normalize_time(timestamp): - """Normalize time in arbitrary timezone to UTC naive object.""" - offset = timestamp.utcoffset() - if offset is None: - return timestamp - return timestamp.replace(tzinfo=None) - offset - - -def is_older_than(before, seconds): - """Return True if before is older than seconds.""" - if isinstance(before, six.string_types): - before = parse_strtime(before).replace(tzinfo=None) - else: - before = before.replace(tzinfo=None) - - return utcnow() - before > datetime.timedelta(seconds=seconds) - - -def is_newer_than(after, seconds): - """Return True if after is newer than seconds.""" - if isinstance(after, six.string_types): - after = parse_strtime(after).replace(tzinfo=None) - else: - after = after.replace(tzinfo=None) - - return after - utcnow() > datetime.timedelta(seconds=seconds) - - -def utcnow_ts(): - """Timestamp version of our utcnow function.""" - if utcnow.override_time is None: - # NOTE(kgriffs): This is several times faster - # than going through calendar.timegm(...) - return int(time.time()) - - return calendar.timegm(utcnow().timetuple()) - - -def utcnow(): - """Overridable version of utils.utcnow.""" - if utcnow.override_time: - try: - return utcnow.override_time.pop(0) - except AttributeError: - return utcnow.override_time - return datetime.datetime.utcnow() - - -def iso8601_from_timestamp(timestamp): - """Returns an iso8601 formatted date from timestamp.""" - return isotime(datetime.datetime.utcfromtimestamp(timestamp)) - - -utcnow.override_time = None - - -def set_time_override(override_time=None): - """Overrides utils.utcnow. - - Make it return a constant time or a list thereof, one at a time. - - :param override_time: datetime instance or list thereof. If not - given, defaults to the current UTC time. - """ - utcnow.override_time = override_time or datetime.datetime.utcnow() - - -def advance_time_delta(timedelta): - """Advance overridden time using a datetime.timedelta.""" - assert utcnow.override_time is not None - try: - for dt in utcnow.override_time: - dt += timedelta - except TypeError: - utcnow.override_time += timedelta - - -def advance_time_seconds(seconds): - """Advance overridden time by seconds.""" - advance_time_delta(datetime.timedelta(0, seconds)) - - -def clear_time_override(): - """Remove the overridden time.""" - utcnow.override_time = None - - -def marshall_now(now=None): - """Make an rpc-safe datetime with microseconds. - - Note: tzinfo is stripped, but not required for relative times. - """ - if not now: - now = utcnow() - return dict(day=now.day, month=now.month, year=now.year, hour=now.hour, - minute=now.minute, second=now.second, - microsecond=now.microsecond) - - -def unmarshall_time(tyme): - """Unmarshall a datetime dict.""" - return datetime.datetime(day=tyme['day'], - month=tyme['month'], - year=tyme['year'], - hour=tyme['hour'], - minute=tyme['minute'], - second=tyme['second'], - microsecond=tyme['microsecond']) - - -def delta_seconds(before, after): - """Return the difference between two timing objects. - - Compute the difference in seconds between two date, time, or - datetime objects (as a float, to microsecond resolution). - """ - delta = after - before - return total_seconds(delta) - - -def total_seconds(delta): - """Return the total seconds of datetime.timedelta object. - - Compute total seconds of datetime.timedelta, datetime.timedelta - doesn't have method total_seconds in Python2.6, calculate it manually. - """ - try: - return delta.total_seconds() - except AttributeError: - return ((delta.days * 24 * 3600) + delta.seconds + - float(delta.microseconds) / (10 ** 6)) - - -def is_soon(dt, window): - """Determines if time is going to happen in the next window seconds. - - :param dt: the time - :param window: minimum seconds to remain to consider the time not soon - - :return: True if expiration is within the given duration - """ - soon = (utcnow() + datetime.timedelta(seconds=window)) - return normalize_time(dt) <= soon diff --git a/tuskar/storage/__init__.py b/tuskar/storage/__init__.py deleted file mode 100644 index aa485d35..00000000 --- a/tuskar/storage/__init__.py +++ /dev/null @@ -1,55 +0,0 @@ -# -*- encoding: 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. - -from oslo_config import cfg - -from tuskar.openstack.common import log as logging - -tuskar_opts = [ - cfg.StrOpt( - 'driver', - default='tuskar.storage.drivers.sqlalchemy.SQLAlchemyDriver', - help=('Storage driver to store Deployment Plans and Heat ' - 'Orchestration Templates') - ) -] - -CONF = cfg.CONF -CONF.register_opts(tuskar_opts, group='storage') -LOG = logging.getLogger(__name__) - - -def _import_class(dotted_path): - module, object_ = dotted_path.rsplit('.', 1) - mod = __import__(module, fromlist=[object_, ]) - return getattr(mod, object_) - - -def get_driver(store_class): - """Given the store class, look up the configuration and load the storage - driver being used for that store. - - :param store_class: Store instance that is requesting a driver. - :type store_class: tuskar.storage.stores._BaseStore - - :return: Instance of the driver - :rtype: tuskar.storage.drivers.base.BaseDriver - """ - - # TODO(dmatthew): Currently this only loads one fixed driver, we need to - # either change the function signature or add more config - # options. Depending which makes most sense. - driver_path = CONF.storage['driver'] - Driver = _import_class(driver_path) - return Driver() diff --git a/tuskar/storage/delete_roles.py b/tuskar/storage/delete_roles.py deleted file mode 100644 index e3777d6c..00000000 --- a/tuskar/storage/delete_roles.py +++ /dev/null @@ -1,77 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2015 Red Hat -# -# 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 __future__ import print_function - -import sys - -from tuskar.common.exception import OvercloudRoleInUse -from tuskar.manager.plan import PlansManager -from tuskar.storage.exceptions import UnknownUUID -from tuskar.storage.stores import DeploymentPlanStore -from tuskar.storage.stores import TemplateStore -from tuskar.templates import parser - - -def _check_roles_exist(role_ids): - store = TemplateStore() - for i in role_ids: - try: - store.retrieve(i) - except UnknownUUID: - sys.stderr.write("No role with id %s " % i) - raise - - -def _check_roles_in_use(role_ids): - manager = PlansManager() - plan_list = manager.list_plans() - plan_store = DeploymentPlanStore() - for plan in plan_list: - db_plan = plan_store.retrieve(plan.uuid) - environment = parser.parse_environment( - db_plan.environment_file.contents - ) - roles_in_use = ( - [role.uuid for role in manager._find_roles(environment)]) - intersection = set(roles_in_use) & set(role_ids) - if intersection: - raise OvercloudRoleInUse(name=", ".join(intersection)) - - -def _delete_role(role_id): - TemplateStore().delete(role_id) - - -def delete_all_roles(noop=False): - store = TemplateStore() - all_uuids = [role.uuid for role in store.list(only_latest=True)] - return delete_roles(all_uuids, noop=noop) - - -def delete_roles(role_ids=None, noop=False): - deleted = [] - # if any of the roles are in use, or invalid, do nothing - _check_roles_in_use(role_ids) - _check_roles_exist(role_ids) - if noop: - role_ids.append("No deletions, dryrun") - return role_ids - else: - for i in role_ids: - _delete_role(i) - deleted.append(i) - return deleted diff --git a/tuskar/storage/drivers/__init__.py b/tuskar/storage/drivers/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tuskar/storage/drivers/base.py b/tuskar/storage/drivers/base.py deleted file mode 100644 index 0fe41c6e..00000000 --- a/tuskar/storage/drivers/base.py +++ /dev/null @@ -1,158 +0,0 @@ -# -*- encoding: 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. - - -from abc import ABCMeta -from abc import abstractmethod - -from six import add_metaclass - - -@add_metaclass(ABCMeta) -class BaseDriver(object): - """Base Storage Drivers - - The BaseDriver provides the abstract interface for storage drivers. It is - intended that a driver will be used by multiple stores (which are defined) - in tuskar.storages.stores). - - Each method is passed an instance of the store that is using it, this - allows the driver to direct operations. For example, a database backend - may use different tables for different stores (templates and environments) - or a swift backend implementation could use different containers. - """ - - @abstractmethod - def create(self, store, name, contents, relative_path): - """Given the store, name and contents create a new file and return a - `StoredFile` instance representing it. - - Some of the stored items such as environment files do not have names. - When working with these, name must be passed explicitly as None. This - is why the name has a type of "str or None" below. - - :param store: The store class, used for routing the storage. - :type store: tuskar.storage.stores._BaseStore - - :param name: name of the object to store (optional) - :type name: str or None - - :param contents: String containing the file contents - :type contents: str - - :return: StoredFile instance containing the file metadata and contents - :rtype: tuskar.storage.models.StoredFile - - :raises: tuskar.storage.exceptions.NameAlreadyUsed if the name is - already in use - """ - - @abstractmethod - def retrieve(self, store, uuid): - """Returns the stored file for a given store that matches the provided - UUID. - - :param store: The store class, used for routing the storage. - :type store: tuskar.storage.stores._BaseStore - - :param uuid: UUID of the object to retrieve. - :type uuid: str - - :return: StoredFile instance containing the file metadata and contents - :rtype: tuskar.storage.models.StoredFile - - :raises: tuskar.storage.exceptions.UnknownUUID if the UUID can't be - found - """ - - @abstractmethod - def update(self, store, uuid, contents, relative_path): - """Given the store, uuid, name and contents update the existing stored - file and return an instance of StoredFile that reflects the updates. - Either name and/or contents can be provided. If they are not then they - will remain unchanged. - - :param store: The store class, used for routing the storage. - :type store: tuskar.storage.stores._BaseStore - - :param uuid: UUID of the object to update. - :type uuid: str - - :param contents: String containing the file contents (optional) - :type contents: str - - :return: StoredFile instance containing the file metadata and contents - :rtype: tuskar.storage.models.StoredFile - - :raises: tuskar.storage.exceptions.UnknownUUID if the UUID can't be - found - """ - - @abstractmethod - def delete(self, store, uuid): - """Delete the stored file with the UUID under the given store. - - :param store: The store class, used for routing the storage. - :type store: tuskar.storage.stores._BaseStore - - :param uuid: UUID of the object to delete. - :type uuid: str - - :return: Returns nothing on success. Exceptions are expected for errors - :rtype: None - - :raises: tuskar.storage.exceptions.UnknownUUID if the UUID can't be - found - """ - - @abstractmethod - def list(self, store, only_latest=False): - """Return a list of all the stored objects for a given store. - Optionally only_latest can be set to True to return only the most - recent version of each objects (grouped by name). - - :param store: The store class, used for routing the storage. - :type store: tuskar.storage.stores._BaseStore - - :param only_latest: If set to True only the latest versions of each - object will be returned. - :type only_latest: bool - - :return: List of StoredFile instances - :rtype: [tuskar.storage.models.StoredFile] - """ - - @abstractmethod - def retrieve_by_name(self, store, name, version=None): - """Returns the stored file for a given store that matches the provided - name and optionally version. - - :param store: The store class, used for routing the storage. - :type store: tuskar.storage.stores._BaseStore - - :param name: name of the object to retrieve. - :type name: str - - :param version: Version of the object to retrieve. If the version isn't - provided, the latest will be returned. - :type version: int - - :return: StoredFile instance containing the file metadata and contents - :rtype: tuskar.storage.models.StoredFile - - :raises: tuskar.storage.exceptions.UnknownName if the name can't be - found - :raises: tuskar.storage.exceptions.UnknownVersion if the version can't - be found - """ diff --git a/tuskar/storage/drivers/sqlalchemy.py b/tuskar/storage/drivers/sqlalchemy.py deleted file mode 100644 index d8684359..00000000 --- a/tuskar/storage/drivers/sqlalchemy.py +++ /dev/null @@ -1,373 +0,0 @@ -# -*- encoding: 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. - -from __future__ import absolute_import - -from uuid import uuid4 - -from oslo_config import cfg -from sqlalchemy import and_ -from sqlalchemy import func -from sqlalchemy.orm.exc import NoResultFound - -from tuskar.db.sqlalchemy.api import get_session -from tuskar.db.sqlalchemy.models import StoredFile -from tuskar.storage.drivers.base import BaseDriver -from tuskar.storage.exceptions import NameAlreadyUsed -from tuskar.storage.exceptions import UnknownName -from tuskar.storage.exceptions import UnknownUUID -from tuskar.storage.exceptions import UnknownVersion -from tuskar.storage.models import StoredFile as StorageModel - -sql_opts = [ - cfg.StrOpt('mysql_engine', - default='InnoDB', - help='MySQL engine') -] - -cfg.CONF.register_opts(sql_opts) - - -class SQLAlchemyDriver(BaseDriver): - - def _generate_uuid(self): - return str(uuid4()) - - def _to_storage_model(self, store, result): - """Convert a result from SQLAlchemy into an instance of the common - model used in the tuskar.storage. - - :param store: Instance of the storage store - :type store: tuskat.storage.stores._BaseStore - - :param result: Instance of the SQLAlchemy model as returned by a query. - :type result: tuskar.db.sqlalchemy.models.StoredFile - - :return: Instance of the StoredFile class. - :rtype: tuskar.storage.models.StoredFile - """ - file_dict = result.as_dict() - file_dict.pop('object_type') - file_dict['store'] = store - return StorageModel(**file_dict) - - def _upsert(self, store, stored_file): - - session = get_session() - session.begin() - - try: - session.add(stored_file) - session.commit() - return self._to_storage_model(store, stored_file) - finally: - session.close() - - def _get_latest_version(self, store, name): - - session = get_session() - - try: - return session.query( - func.max(StoredFile.version) - ).filter_by( - object_type=store.object_type, name=name - ).scalar() - finally: - session.close() - - def _create(self, store, name, contents, version, relative_path='', - registry_path=''): - - stored_file = StoredFile( - uuid=self._generate_uuid(), - contents=contents, - object_type=store.object_type, - name=name, - version=version, - relative_path=relative_path, - registry_path=registry_path - ) - - return self._upsert(store, stored_file) - - def create(self, store, name, contents, relative_path='', - registry_path=''): - """Given the store, name and contents create a new file and return a - `StoredFile` instance representing it. The optional relative_path - is appended to the generated template directory structure. - - Some of the stored items such as environment files do not have names. - When working with these, name must be passed explicitly as None. This - is why the name has a type of "str or None" below. - - :param store: The store class, used for routing the storage. - :type store: tuskar.storage.stores._BaseStore - - :param name: name of the object to store (optional) - :type name: str or None - - :param contents: String containing the file contents - :type contents: str - - :param relative_path: String relative path to place the template under - : type relative_path: str - - :param registry_path: String path with which a Role will appear in - the resource registry. - : type relative_path: str - - :return: StoredFile instance containing the file metadata and contents - :rtype: tuskar.storage.models.StoredFile - """ - - if store.versioned: - version = 1 - else: - version = None - - if name is not None: - try: - self.retrieve_by_name(store, name) - msg = "A {0} with the name '{1}' already exists".format( - store.object_type, - name - ) - raise NameAlreadyUsed(msg) - except UnknownName: - pass - - return self._create(store, name, contents, version, relative_path, - registry_path) - - def _retrieve(self, object_type, uuid): - - session = get_session() - try: - return session.query(StoredFile).filter_by( - uuid=uuid, - object_type=object_type - ).one() - except NoResultFound: - msg = "No {0}s for the UUID: {1}".format(object_type, uuid) - raise UnknownUUID(msg) - finally: - session.close() - - def retrieve(self, store, uuid): - """Returns the stored file for a given store that matches the provided - UUID. - - :param store: The store class, used for routing the storage. - :type store: tuskar.storage.stores._BaseStore - - :param uuid: UUID of the object to retrieve. - :type uuid: str - - :return: StoredFile instance containing the file metadata and contents - :rtype: tuskar.storage.models.StoredFile - - :raises: tuskar.storage.exceptions.UnknownUUID if the UUID can't be - found - """ - - stored_file = self._retrieve(store.object_type, uuid) - return self._to_storage_model(store, stored_file) - - def update(self, store, uuid, contents, relative_path='', - registry_path=''): - """Given the store, uuid, name and contents update the existing stored - file and return an instance of StoredFile that reflects the updates. - Either name and/or contents can be provided. If they are not then they - will remain unchanged. - - :param store: The store class, used for routing the storage. - :type store: tuskar.storage.stores._BaseStore - - :param uuid: UUID of the object to update. - :type uuid: str - - :param name: name of the object to store (optional) - :type name: str - - :param contents: String containing the file contents (optional) - :type contents: str - - :return: StoredFile instance containing the file metadata and contents - :rtype: tuskar.storage.models.StoredFile - - :raises: tuskar.storage.exceptions.UnknownUUID if the UUID can't be - found - """ - - stored_file = self._retrieve(store.object_type, uuid) - - stored_file.contents = contents - - stored_file.relative_path = relative_path if relative_path else None - - stored_file.registry_path = registry_path if registry_path else None - - if store.versioned: - version = self._get_latest_version(store, stored_file.name) + 1 - return self._create( - store, stored_file.name, stored_file.contents, version, - relative_path, registry_path) - - return self._upsert(store, stored_file) - - def delete(self, store, uuid): - """Delete the stored file with the UUID under the given store. - - :param store: The store class, used for routing the storage. - :type store: tuskar.storage.stores._BaseStore - - :param uuid: UUID of the object to delete. - :type uuid: str - - :return: Returns nothing on success. Exceptions are expected for errors - :rtype: None - - :raises: tuskar.storage.exceptions.UnknownUUID if the UUID can't be - found - """ - - session = get_session() - session.begin() - - stored_file = self._retrieve(store.object_type, uuid) - - try: - session.delete(stored_file) - session.commit() - finally: - session.close() - - def list(self, store, only_latest=False): - """Return a list of all the stored objects for a given store. - Optionally only_latest can be set to True to return only the most - recent version of each objects (grouped by name). - - :param store: The store class, used for routing the storage. - :type store: tuskar.storage.stores._BaseStore - - :param only_latest: If set to True only the latest versions of each - object will be returned. - :type only_latest: bool - - :return: List of StoredFile instances - :rtype: [tuskar.storage.models.StoredFile] - """ - - object_type = store.object_type - - session = get_session() - try: - files = session.query(StoredFile).filter_by( - object_type=object_type - ) - - if only_latest: - # When only_latest is provided, then we want to select only the - # stored files with the latest version. To do this we use a - # subquery to get a set of names and latest versions for the - # object type. After we have that, we join in the name and - # version to make sure we match it. - stmt = session.query( - StoredFile.name, - func.max(StoredFile.version).label("version") - ).filter_by( - object_type=object_type - ).group_by( - StoredFile.name - ).subquery() - - # join our existing query on the subquery. - files = files.join( - stmt, - and_( - StoredFile.name == stmt.c.name, - StoredFile.version == stmt.c.version, - ) - ) - - return [self._to_storage_model(store, file_) for file_ in files] - finally: - session.close() - - def retrieve_by_name(self, store, name, version=None): - """Returns the stored file for a given store that matches the provided - name and optionally version. - - :param store: The store class, used for routing the storage. - :type store: tuskar.storage.stores._BaseStore - - :param name: name of the object to retrieve. - :type name: str - - :param version: Version of the object to retrieve. If the version isn't - provided, the latest will be returned. - :type version: int - - :return: StoredFile instance containing the file metadata and contents - :rtype: tuskar.storage.models.StoredFile - - :raises: tuskar.storage.exceptions.UnknownName if the name can't be - found - :raises: tuskar.storage.exceptions.UnknownVersion if the version can't - be found - """ - - object_type = store.object_type - - session = get_session() - - try: - query = session.query(StoredFile).filter_by( - name=name, - object_type=object_type, - ) - if version is not None: - query = query.filter_by(version=version) - else: - query = query.filter_by( - version=self._get_latest_version(store, name) - ) - - stored_file = query.one() - return self._to_storage_model(store, stored_file) - except NoResultFound: - - name_query = session.query(StoredFile).filter_by( - name=name, - object_type=object_type, - ) - - if name_query.count() == 0: - msg = "No {0}s found for the name: {1}".format( - object_type, - name - ) - raise UnknownName(msg) - elif name_query.filter_by(version=version).count() == 0: - msg = "No {0}s found for the Version: {1}".format( - object_type, - name - ) - raise UnknownVersion(msg) - - raise - - finally: - session.close() diff --git a/tuskar/storage/exceptions.py b/tuskar/storage/exceptions.py deleted file mode 100644 index b598185e..00000000 --- a/tuskar/storage/exceptions.py +++ /dev/null @@ -1,35 +0,0 @@ -# -*- encoding: 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. - -from tuskar.common import exception - - -class StorageException(exception.TuskarException): - pass - - -class UnknownUUID(StorageException): - """Raised when an object with the given UUID can't be found.""" - - -class UnknownName(StorageException): - """Raised when an object with the given Name can't be found.""" - - -class UnknownVersion(StorageException): - """Raised when an object with the given Version can't be found.""" - - -class NameAlreadyUsed(StorageException): - """Raised when creating an object with a name that is already in use.""" diff --git a/tuskar/storage/load_roles.py b/tuskar/storage/load_roles.py deleted file mode 100644 index 3c944a03..00000000 --- a/tuskar/storage/load_roles.py +++ /dev/null @@ -1,134 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2013 Hewlett-Packard Development Company, L.P. -# All Rights Reserved. -# -# 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 __future__ import print_function - -from os import path - -from tuskar.common import utils -from tuskar.storage.load_utils import create_or_update -from tuskar.storage.load_utils import load_file -from tuskar.storage.load_utils import process_role -from tuskar.storage.stores import MasterSeedStore -from tuskar.storage.stores import ResourceRegistryMappingStore -from tuskar.storage.stores import ResourceRegistryStore -from tuskar.storage.stores import TemplateExtraStore -from tuskar.storage.stores import TemplateStore -from tuskar.templates import parser - -MASTER_SEED_NAME = '_master_seed' -RESOURCE_REGISTRY_NAME = '_registry' - - -def role_name_from_path(role_path): - return path.splitext(path.basename(role_path))[0] - - -def load_seed(seed_file, resource_registry_path): - - # enforced in CLI - assert seed_file is not None - assert resource_registry_path is not None - - all_roles, created, updated = load_roles([], seed_file, - resource_registry_path) - return created, updated - - -def load_role(name, file_path, extra_data=None, relative_path=''): - name = role_name_from_path(file_path) if (name == '') else name - all_roles, created, updated = load_roles( - roles=[], seed_file=None, - resource_registry_path=None, role_extra=extra_data) - process_role(file_path, name, TemplateStore(), all_roles, created, - updated, relative_path) - return created, updated - - -def load_roles(roles, seed_file=None, resource_registry_path=None, - role_extra=None): - """Given a list of roles files import them into the - add any to the store. TemplateStore. - - The returned tuple contains all the role names and then the names split - over where were created and updated. On a dry run the first item will - contain all of the roles found while the second two will be empty lists as - no files were updated or created. - - :param roles: A list of yaml files (as strings) - :type roles: [str] - - :param seed_file: full path to the template seed that should be used for - plan master templates - :type seed_file: str - - :param resource_registry_path: path to the Heat environment which - declares the custom types for Tuskar roles. - :type resource_registry_path: str - - :param role_extra: A list of yaml files (as strings) that may be consumed - (referenced) by any of the role files. - :type roles: [str] - - :return: Summary of the results as a tuple with the total count and then - the names of the created and updated roles. - :rtype: tuple(list, list, list) - """ - all_roles, created, updated = [], [], [] - - def _process_roles(roles, store=None): - for role_name, role_path in roles: - process_role(role_path, role_name, store, all_roles, created, - updated) - - roles = [(role_name_from_path(r), r) for r in roles] - _process_roles(roles) - - template_extra_store = TemplateExtraStore() - if role_extra is not None: - role_extra = [(utils.resolve_role_extra_name_from_path(re), re) - for re in role_extra] - _process_roles(role_extra, template_extra_store) - - if seed_file is not None: - process_role(seed_file, MASTER_SEED_NAME, - MasterSeedStore(), all_roles, created, updated) - - if resource_registry_path is not None: - process_role(resource_registry_path, RESOURCE_REGISTRY_NAME, - ResourceRegistryStore(), all_roles, created, updated) - - contents = load_file(resource_registry_path) - parsed_env = parser.parse_environment(contents) - - mapping_store = ResourceRegistryMappingStore() - dirname = path.dirname(resource_registry_path) - role_paths = [r[1] for r in roles] - for entry in parsed_env.registry_entries: - complete_path = path.join(dirname, entry.filename) - # skip adding if template has already been stored as a role - if (complete_path in role_paths): - continue - - if (entry.is_filename()): - process_role(complete_path, entry.filename, mapping_store, - None, created, updated) - else: - # if entry is not a filename, (is an alias) add to mappings - create_or_update(entry.filename, entry.alias, mapping_store) - - return all_roles, created, updated diff --git a/tuskar/storage/load_utils.py b/tuskar/storage/load_utils.py deleted file mode 100644 index b06fbde8..00000000 --- a/tuskar/storage/load_utils.py +++ /dev/null @@ -1,52 +0,0 @@ -# -# 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 tuskar.storage.exceptions import UnknownName -from tuskar.storage.stores import TemplateStore - - -def load_file(role_path): - with open(role_path) as role_file: - return role_file.read() - - -def create_or_update(name, contents, store=None, relative_path='', - registry_path=''): - if store is None: - store = TemplateStore() - try: - role = store.retrieve_by_name(name) - if role.contents != contents: - role = store.update(role.uuid, contents, relative_path, - registry_path) - - return False, role - except UnknownName: - return True, store.create(name, contents, relative_path, registry_path) - - -def process_role(role_path, role_name, store, all_roles, created, updated, - relative_path=''): - contents = load_file(role_path) - # if bigger than 255 chars, truncate to the last 255 - registry_path = role_path[-255:] - role_created, _ = create_or_update(role_name, contents, store, - relative_path, registry_path) - - if all_roles is not None: - all_roles.append(role_name) - - if role_created: - created.append(role_name) - else: - updated.append(role_name) diff --git a/tuskar/storage/models.py b/tuskar/storage/models.py deleted file mode 100644 index 2b529d5c..00000000 --- a/tuskar/storage/models.py +++ /dev/null @@ -1,139 +0,0 @@ -# -*- encoding: 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. - - -class StoredFile(object): - """The StoredFile model represents the stored objects and their metadata. - - This simple model provides a common representation to be used by the - different storage drivers when returning results. - """ - - def __init__(self, uuid, contents, store, name=None, created_at=None, - updated_at=None, version=None, relative_path=None, - registry_path=None): - """The constructor requires uuid, contents and store which are the - common attributes in all drivers. - - The other attributes are optional - - :param uuid: The UUID for the stored object - :type uuid: str - - :param contents: A string containing the full contents. - :type contents: str - - :param store: The store where this object is located. - :type store: tuskar.storage.models.BaseStore - - :param name: The name of the stored object (optional) - :type name: str or None - - :param created_at: A datetime for when object was created (optional) - :type created_at: datetime.datetime - - :param updated_at: A datetime for when object was updated (optional) - :type updated_at: datetime.datetime - - :param version: A version number for the given object (optional) - :type version: int - """ - - self.uuid = uuid - self.contents = contents - self.store = store - self.name = name - self.created_at = created_at - self.updated_at = updated_at - self.version = version - self.relative_path = relative_path - self.registry_path = registry_path - - def __eq__(self, other): - return (isinstance(other, self.__class__) - and self.__dict__ == other.__dict__) - - def __str__(self): - - name = " and name {0}".format(self.name) if self.name else '' - - return "{0} with ID {1}{2}".format( - self.store.object_type, self.uuid, name) - - -class DeploymentPlan(StoredFile): - """The DeploymentPlan model represents the deployment plan file which is - essentially the relationship between a master template and an environment - file. - """ - - def __init__(self, uuid, contents, store, name=None, created_at=None, - updated_at=None, version=None, master_template=None, - environment_file=None): - """The constructor requires uuid, contents and store which are the - common attributes in all drivers. - - The other attributes are optional - - :param uuid: The UUID for the stored object - :type uuid: str - - :param contents: A string containing the full contents. - :type contents: str - - :param store: The store where this object is located. - :type store: tuskar.storage.models.BaseStore - - :param name: The name of the stored object (optional) - :type name: str or None - - :param created_at: A datetime for when object was created (optional) - :type created_at: datetime.datetime - - :param updated_at: A datetime for when object was updated (optional) - :type updated_at: datetime.datetime - - :param version: A version number for the given object (optional) - :type version: int - - :param master_template: The master_template for this plan. - :type master_template: tuskar.storage.models.StoredFile - - :param environment_file: The environment file for this plan. - :type environment_file: tuskar.storage.models.StoredFile - """ - - super(DeploymentPlan, self).__init__( - uuid, contents, store, name=name, created_at=created_at, - updated_at=updated_at, version=version - ) - - self.master_template = master_template - self.environment_file = environment_file - - @classmethod - def from_stored_file(cls, stored_file, master_template=None, - environment_file=None): - - return DeploymentPlan( - uuid=stored_file.uuid, - contents=stored_file.contents, - store=stored_file.store, - name=stored_file.name, - created_at=stored_file.created_at, - updated_at=stored_file.updated_at, - version=stored_file.version, - master_template=master_template, - environment_file=environment_file, - ) diff --git a/tuskar/storage/stores.py b/tuskar/storage/stores.py deleted file mode 100644 index f566d856..00000000 --- a/tuskar/storage/stores.py +++ /dev/null @@ -1,522 +0,0 @@ -# -*- encoding: 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. - -from tuskar.openstack.common import jsonutils -from tuskar.openstack.common import log -from tuskar.storage.exceptions import UnknownUUID -from tuskar.storage import get_driver -from tuskar.storage.models import DeploymentPlan - -LOG = log.getLogger(__name__) - - -class _BaseStore(object): - """Base class for all stores. - - This defines the basic CRUD operations and how they will be called to the - underlying driver. - """ - - #: Object type is used to help the driver know how to store an object. For - #: example a database may use different tables for objects and a service - #: like swift may use different containers. - object_type = None - - #: Flag to designate if the objects should be versioned by the driver. - versioned = False - - def __init__(self, driver=None): - """Given the driver to be used, set up an instance of the store. The - driver can then be re-used between different stores. - - If the driver isn't provided, load it based on the Tuskar ini with the - tuskar.storage.get_driver method. - - :param driver: The type of object, used for routing the storage. - :type driver: tuskar.storage.drivers.base.BaseDriver - """ - self._driver = driver or get_driver(self.__class__) - - def create(self, contents): - """Given the contents create a new file in this store and return a - `StoredFile` instance representing it. - - :param contents: String containing the file contents - :type contents: str - - :return: StoredFile instance containing the file metadata and contents - :rtype: tuskar.storage.models.StoredFile - """ - - return self._driver.create(self, None, contents) - - def retrieve(self, uuid): - """Returns the file in this store that matches the provided UUID. - - :param uuid: UUID of the object to retrieve. - :type uuid: str - - :return: StoredFile instance containing the file metadata and contents - :rtype: tuskar.storage.models.StoredFile - - :raises: tuskar.storage.exceptions.UnknownUUID if the UUID can't be - found - """ - return self._driver.retrieve(self, uuid) - - def update(self, uuid, contents, relative_path='', registry_path=''): - """Given the uuid and contents update the existing stored file - and return an instance of StoredFile that reflects the updates. - - :param uuid: UUID of the object to update. - :type uuid: str - - :param contents: String containing the file contents - :type contents: str - - :return: StoredFile instance containing the file metadata and contents - :rtype: tuskar.storage.models.StoredFile - - :raises: tuskar.storage.exceptions.UnknownUUID if the UUID can't be - found - """ - return self._driver.update(self, uuid, contents, relative_path, - registry_path) - - def delete(self, uuid): - """Delete the file in this store with the matching uuid. - - :param uuid: UUID of the object to delete. - :type uuid: str - - :raises: tuskar.storage.exceptions.UnknownUUID if the UUID can't be - found - """ - return self._driver.delete(self, uuid) - - def list(self): - """Return a list of all the stored objects in this store. - - :return: List of StoredFile instances - :rtype: [tuskar.storage.models.StoredFile] - """ - return self._driver.list(self) - - -class _NamedStore(_BaseStore): - """The Named Store adds the requirement of a name attribute. - - This extends the base CRUD operations to add the requirement of a name - where required. - """ - - def create(self, name, contents, relative_path='', registry_path=''): - """Given the name and contents create a new file and return a - `StoredFile` instance representing it. - - When working with files that are not named, such as an EnvironmentFile - name must be explicitly passed as None. - - :param name: name of the object to store (optional) - :type name: str or None - - :param contents: String containing the file contents - :type contents: str - - :return: StoredFile instance containing the file metadata and contents - :rtype: tuskar.storage.models.StoredFile - - :raises: tuskar.storage.exceptions.NameAlreadyUsed if the name is - already in use - """ - return self._driver.create(self, name, contents, relative_path, - registry_path) - - def retrieve_by_name(self, name): - """Returns the stored file for a given store that matches the provided - name. - - :param name: name of the object to retrieve. - :type name: str - - :return: StoredFile instance containing the file metadata and contents - :rtype: tuskar.storage.models.StoredFile - - :raises: tuskar.storage.exceptions.UnknownName if the name can't be - found - """ - return self._driver.retrieve_by_name(self, name) - - -class _VersionedStore(_NamedStore): - - #: Flag to designate if the objects should be versioned by the driver. - versioned = True - - def retrieve_by_name(self, name, version=None): - """Returns the stored file for a given store that matches the provided - name and optionally version. If the version isn't provided, the latest - is returned. - - :param name: name of the object to retrieve. - :type name: str - - :param version: Version of the object to retrieve. If the version isn't - provided, the latest will be returned. - :type version: int - - :return: StoredFile instance containing the file metadata and contents - :rtype: tuskar.storage.models.StoredFile - - :raises: tuskar.storage.exceptions.UnknownName if the name can't be - found - :raises: tuskar.storage.exceptions.UnknownVersion if the version can't - be found - """ - return self._driver.retrieve_by_name(self, name, version) - - def list(self, only_latest=False): - """Return a list of all the stored objects in this store. - - :param only_latest: If set to True only the latest versions of each - object will be returned. - :type only_latest: bool - - :return: List of StoredFile instances - :rtype: [tuskar.storage.models.StoredFile] - """ - return self._driver.list(self, only_latest=only_latest) - - -class TemplateExtraStore(_VersionedStore): - """Template Store for Extra template files that are referenced by the - Heat Orchestration Templates held in the TemplateStore - - """ - object_type = "template_extra" - - -class TemplateStore(_VersionedStore): - """Template Store for Heat Orchestration TemplateStore - - Templates are named and versioned. - """ - object_type = "template" - - -class MasterTemplateStore(_NamedStore): - """Template Store for Heat Orchestration TemplateStore - - Master Templates are named and but not versioned. - """ - object_type = "master_template" - - -class MasterSeedStore(_NamedStore): - """Store for the master template seed file. - - The seed files are named but not versioned. - """ - object_type = "master_seed" - - -class ResourceRegistryStore(_NamedStore): - object_type = "registry" - - -class ResourceRegistryMappingStore(_NamedStore): - object_type = "registry_mapping" - - -class EnvironmentFileStore(_BaseStore): - """Environment File for Heat environment files. - - Environment Files are not named and don't contain versions. - """ - object_type = "environment" - - -class DeploymentPlanStore(_NamedStore): - """Deployment Plan Store - - Deployment plans are named but not versioned. - """ - object_type = "deployment_plan" - - def __init__(self, master_template_store=None, environment_store=None, - *args, **kwargs): - super(DeploymentPlanStore, self).__init__(*args, **kwargs) - - self._template_store = master_template_store or MasterTemplateStore() - self._env_file_store = environment_store or EnvironmentFileStore() - - def _serialise(self, master_template, environment_file): - """Given the master_template and environment_file UUID's create a - simple dictionary representation of the Plan and serialise it to JSON. - - :param master_template: Master template UUID - :type master_template: str - - :param environment_file: Environment File UUID. - :type environment_file: str - - :return: JSON String - :rtype: str - """ - - plan = { - "master_template_uuid": master_template, - "environment_file_uuid": environment_file - } - - return jsonutils.dumps(plan, sort_keys=True) - - def _deserialise(self, plan_stored_file): - """Load the Plan from a JSON string containing the master template - UUID and the environment file UUID. - - :param plan_stored_file: Stored File model instance - :type plan_stored_file: tuskar.storage.models.StoredFile - - :return: Deployment Plan model instance - :rtype: tuskar.storage.models.DeploymentPlan - """ - - metadata = jsonutils.loads(plan_stored_file.contents) - - plan_uuid = plan_stored_file.uuid - template_uuid = metadata['master_template_uuid'] - env_uuid = metadata['environment_file_uuid'] - - try: - master = self._template_store.retrieve(template_uuid) - except UnknownUUID: - LOG.warn("Deployment Plan {0} had a relation to Template {1} " - "which doesn't exist.".format(plan_uuid, template_uuid)) - master = None - - try: - env = self._env_file_store.retrieve(env_uuid) - except UnknownUUID: - LOG.warn("Deployment Plan {0} had a relation to Environment {1} " - "which doesn't exist.".format(plan_uuid, env_uuid)) - env = None - - return DeploymentPlan.from_stored_file(plan_stored_file, - master_template=master, - environment_file=env) - - def _create_empty_template(self, name): - empty_file = "" - master_template = self._template_store.create(name, empty_file) - return master_template - - def _create_empty_environment(self): - empty_file = "" - environment_file = self._env_file_store.create(empty_file) - return environment_file - - def create(self, name, master_template_uuid=None, environment_uuid=None): - """Given the UUID's for a template and environment, create the Plan - relationship between the two. If one or either are not provided then - a new, empty template or environment will be created for them. - - :param master_template_uuid: Template UUID - :type master_template_uuid: str or None - - :param environment_uuid: environment UUID - :type environment_uuid: str or None - - :return: DeploymentPlan instance containing the relationship - :rtype: tuskar.storage.models.DeploymentPlan - - :raises: tuskar.storage.exceptions.NameAlreadyUsed if the name is - already in use - """ - - if master_template_uuid is None: - master_template = self._create_empty_template(name) - master_template_uuid = master_template.uuid - - if environment_uuid is None: - environment = self._create_empty_environment() - environment_uuid = environment.uuid - - contents = self._serialise(master_template_uuid, environment_uuid) - plan_file = super(DeploymentPlanStore, self).create(name, contents) - return self._deserialise(plan_file) - - def retrieve(self, uuid): - """Returns the deployment plan relationship for the given UUID. - - :param uuid: Deployment Plan UUID - :type uuid: str - - :return: DeploymentPlan instance containing the relationship - :rtype: tuskar.storage.models.DeploymentPlan - - :raises: tuskar.storage.exceptions.UnknownUUID if the UUID can't be - found - """ - plan_file = super(DeploymentPlanStore, self).retrieve(uuid) - return self._deserialise(plan_file) - - def update(self, uuid, master_template_uuid=None, environment_uuid=None): - """Given the UUID's for a template and environment, update the Plan - relationship. If they are not provided, then the UUID will not be - updated. - - :param uuid: Deployment Plan UUID - :type uuid: str - - :param master_template_uuid: Template UUID - :type master_template_uuid: str or None - - :param environment_uuid: environment UUID - :type environment_uuid: str or None - - :return: DeploymentPlan instance containing the relationship - :rtype: tuskar.storage.models.DeploymentPlan - - :raises: tuskar.storage.exceptions.UnknownUUID if the UUID can't be - found - :raises: ValueError if neither master_template_uuid or environment_uuid - are provided. - """ - - plan = self.retrieve(uuid) - - if master_template_uuid is None and environment_uuid is None: - raise ValueError("Either the master_template_uuid and/or " - "environment_uuid must be provided for an update") - - if master_template_uuid is None: - master_template_uuid = plan.master_template.uuid - - if environment_uuid is None: - environment_uuid = plan.environment_file.uuid - - contents = self._serialise(master_template_uuid, environment_uuid) - plan_file = super(DeploymentPlanStore, self).update(uuid, contents) - return self._deserialise(plan_file) - - def update_master_template(self, plan_uuid, master_template_contents): - """Given the plan UUID and the master template contents, update the - template and return the updated DeploymentPlan. - - :param plan_uuid: Deployment Plan UUID - :type plan_uuid: str - - :param master_template_contents: Master Template contents - :type master_template_contents: str - - :return: DeploymentPlan instance containing the relationship - :rtype: tuskar.storage.models.DeploymentPlan - - :raises: tuskar.storage.exceptions.UnknownUUID if the UUID can't be - found - """ - - # Fetch the plan, this is primarily to get the master template UUID. - plan = self.retrieve(plan_uuid) - template_uuid = plan.master_template.uuid - - # Update the template contents - updated_template = self._template_store.update( - template_uuid, master_template_contents) - - # Manually update the plan, to avoid having to refetch it from the - # store as we know nothing else has changed. - plan.master_template = updated_template - - return plan - - def update_environment(self, plan_uuid, environment_contents): - """Given the plan UUID and the environment contents, update the - environment and return the updated Deployment Plan. - - :param plan_uuid: Deployment Plan UUID - :type plan_uuid: str - - :param environment_contents: Environment contents - :type environment_contents: str - - :return: DeploymentPlan instance containing the relationship - :rtype: tuskar.storage.models.DeploymentPlan - - :raises: tuskar.storage.exceptions.UnknownUUID if the UUID can't be - found - """ - - # Fetch the plan, this is primarily to get the environment UUID. - plan = self.retrieve(plan_uuid) - environment_uuid = plan.environment_file.uuid - - # Update the environment contents - updated_env = self._env_file_store.update( - environment_uuid, environment_contents) - - # Manually update the plan, to avoid having to refetch it from the - # store as we know nothing else has changed. - plan.environment_file = updated_env - - return plan - - def list(self): - """Return a list of all the deployment plans in this store. - - :return: List of DeploymentPlan instances - :rtype: [tuskar.storage.models.DeploymentPlan] - """ - stored_files = super(DeploymentPlanStore, self).list() - return [self._deserialise(stored_file) for stored_file in stored_files] - - def retrieve_by_name(self, name): - """Returns the deployment plan for a given store that matches the - provided name. - - :param name: name of the object to retrieve. - :type name: str - - :return: DeploymentPlan instance containing the relationship - :rtype: tuskar.storage.models.DeploymentPlan - - :raises: tuskar.storage.exceptions.UnknownName if the name can't be - found - """ - stored_file = super(DeploymentPlanStore, self).retrieve_by_name(name) - return self._deserialise(stored_file) - - def delete(self, uuid): - """Delete the DeploymentPlan and it's related MasterTemplate end - EnvironmentFile. If the MasterTemplate or EnvironmentFile can't be - found, the exceptions will be ignored and the DeploymentPlan will still - be deleted. - - :param uuid: UUID of the DeploymentPlan to delete. - :type uuid: str - - :raises: tuskar.storage.exceptions.UnknownUUID if the UUID can't be - found for the DeploymentPlan. - """ - - plan = self.retrieve(uuid) - - if plan.master_template: - self._template_store.delete(plan.master_template.uuid) - - if plan.environment_file: - self._env_file_store.delete(plan.environment_file.uuid) - - return self._driver.delete(self, uuid) diff --git a/tuskar/templates/__init__.py b/tuskar/templates/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tuskar/templates/composer.py b/tuskar/templates/composer.py deleted file mode 100644 index 41864d4c..00000000 --- a/tuskar/templates/composer.py +++ /dev/null @@ -1,200 +0,0 @@ -# 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. - -""" -Functionality for converting Tuskar domain models into their Heat-acceptable -formats. - -These functions are written against the HOT specification found at: -http://docs.openstack.org/developer/heat/template_guide/hot_spec.html -""" - -import yaml - -from tuskar.templates.heat import Resource - - -def compose_template(template): - """Converts a template object into its HOT template format. - - :param template: template object to convert - :type template: tuskar.templates.heat.Template - - :return: HOT template - :rtype: str - """ - parameters = _compose_parameters(template) - parameter_groups = _compose_parameter_groups(template) - resources = _compose_resources(template) - outputs = _compose_outputs(template) - - template_dict = { - 'heat_template_version': template.version, - 'parameters': parameters, - 'parameter_groups': parameter_groups, - 'resources': resources, - 'outputs': outputs, - } - - # Remove optional sections if they have no values - for x in ('parameters', 'parameter_groups', 'outputs'): - if len(template_dict[x]) == 0: - template_dict.pop(x) - - if template.description is not None: - template_dict['description'] = template.description - - content = yaml.safe_dump(template_dict, default_flow_style=False) - return content - - -def compose_environment(environment): - """Converts an environment object into its HOT template format. - - :param environment: environment object to convert - :type environment: tuskar.templates.heat.Environment - - :return: HOT template - :rtype: str - """ - parameters = _compose_environment_parameters(environment) - registry = _compose_resource_registry(environment) - - env_dict = { - 'parameters': parameters, - 'resource_registry': registry - } - - content = yaml.safe_dump(env_dict, default_flow_style=False) - return content - - -def _compose_parameters(template): - parameters = {} - for p in template.parameters: - details = { - 'type': p.param_type, - 'description': p.description, - 'default': p.default, - 'label': p.label, - 'hidden': p.hidden, - } - - details = _strip_missing(details) - - if len(p.constraints) > 0: - details['constraints'] = [] - - for constraint in p.constraints: - constraint_value = { - constraint.constraint_type: constraint.definition - } - if constraint.description is not None: - constraint_value['description'] = constraint.description - details['constraints'].append(constraint_value) - - parameters[p.name] = details - - return parameters - - -def _compose_parameter_groups(template): - groups = [] - for g in template.parameter_groups: - details = { - 'label': g.label, - 'description': g.description, - 'parameters': list(g.parameter_names), # yaml doesn't handle tuple - } - - details = _strip_missing(details) - if len(details['parameters']) == 0: - details.pop('parameters') - - groups.append(details) - - return groups - - -def _compose_resources(template): - resources = {} - - def _generate_details(r): - """Converts a resource into its HOT dictionary version. This method - will recursively call itself in the event a resource is nested within - another as a resource definition. - """ - d = { - 'type': r.resource_type, - 'metadata': r.metadata, - 'depends_on': r.depends_on, - 'update_policy': r.update_policy, - 'deletion_policy': r.deletion_policy, - } - - d = _strip_missing(d) - - # Properties - if len(r.properties) > 0: - d['properties'] = {} - for p in r.properties: - if isinstance(p.value, Resource): - v = _generate_details(p.value) - else: - v = p.value - d['properties'][p.name] = v - - return d - - for resource in template.resources: - details = _generate_details(resource) - resources[resource.resource_id] = details - - return resources - - -def _compose_outputs(template): - outputs = {} - for o in template.outputs: - details = { - 'description': o.description, - 'value': o.value, - } - - details = _strip_missing(details) - - outputs[o.name] = details - - return outputs - - -def _compose_environment_parameters(environment): - params = dict((p.name, p.value) for p in environment.parameters) - return params - - -def _compose_resource_registry(environment): - reg = dict((e.alias, e.filename) for e in environment.registry_entries) - return reg - - -def _strip_missing(details): - """Removes all entries from a dictionary whose value is None. This is used - in this context to remove optional attributes that were added to the - template creation. - - :type details: dict - - :return: new dictionary with the empty attributes removed - :rtype: dict - """ - return dict((k, v) for k, v in details.items() if v is not None) diff --git a/tuskar/templates/heat.py b/tuskar/templates/heat.py deleted file mode 100644 index 737e398f..00000000 --- a/tuskar/templates/heat.py +++ /dev/null @@ -1,546 +0,0 @@ -# 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. - -""" -Object representations of the elements of a HOT template. - -These objects were created against the HOT specification found at: -http://docs.openstack.org/developer/heat/template_guide/hot_spec.html -""" - -from os import path - -from tuskar.templates import namespace as ns_utils - - -DEFAULT_VERSION = '2014-10-16' - - -class Template(object): - - def __init__(self, version=DEFAULT_VERSION, description=None): - super(Template, self).__init__() - self.version = version - self.description = description - self._parameter_groups = [] # list of ParameterGroup - self._parameters = [] # list of Parameter - self._resources = [] # list of Resource - self._outputs = [] # list of Output - - def __str__(self): - msg = ('Template: version=%(ver)s, description=%(desc)s, ' - 'parameter_count=%(param)s, output_count=%(out)s') - data = { - 'ver': self.version, - 'desc': _safe_strip(self.description), - 'param': len(self.parameters), - 'out': len(self.outputs) - } - - return msg % data - - @property - def parameter_groups(self): - return tuple(self._parameter_groups) - - @property - def parameters(self): - return tuple(self._parameters) - - @property - def resources(self): - return tuple(self._resources) - - @property - def outputs(self): - return tuple(self._outputs) - - def add_parameter(self, parameter): - """Adds a parameter to the template. - - :type parameter: tuskar.templates.heat.Parameter - """ - self._parameters.append(parameter) - - def remove_parameter(self, parameter): - """Removes a parameter from the template. - - :type parameter: tuskar.templates.heat.Parameter - :raise ValueError: if the parameter is not in the template - """ - self._parameters.remove(parameter) - - def find_parameter_by_name(self, name): - """Returns the parameter from the template with the given name or - None if no matching parameter is found. - - :type name: str - :rtype: tuskar.templates.heat.Parameter or None - """ - for p in self._parameters: - if p.name == name: - return p - return None - - def remove_parameters_by_namespace(self, namespace): - """Removes all parameters in the given namespace. - - :type namespace: str - """ - self._parameters = [ - p for p in self.parameters - if not ns_utils.matches_template_namespace(namespace, p.name)] - - def add_parameter_group(self, parameter_group): - """Adds a parameter group to the template. - - :type parameter_group: tuskar.templates.heat.ParameterGroup - """ - self._parameter_groups.append(parameter_group) - - def remove_parameter_group(self, parameter_group): - """Removes a parameter group from the template. - - :type parameter_group: tuskar.templates.heat.ParameterGroup - :raise ValueError: if the parameter group is not in the template - """ - self._parameter_groups.remove(parameter_group) - - def find_parameter_group_by_label(self, label): - """Returns the parameter group with the given label or None if no - matching group is found. - - :type label: str - :rtype: tuskar.templates.heat.ParameterGroup or None - """ - for pg in self._parameter_groups: - if pg.label == label: - return pg - return None - - def add_resource(self, resource): - """Adds a resource to the template. - - :type resource: tuskar.templates.heat.Resource - """ - self._resources.append(resource) - - def remove_resource(self, resource): - """Removes a resource from the template. - - :type resource: tuskar.templates.heat.Resource - :raise ValueError: if the resource is not in the template - """ - self._resources.remove(resource) - - def remove_resource_by_id(self, resource_id): - """Removes a resource from the template if found. - - :type resource_id: str - """ - self._resources = [r for r in self._resources - if r.resource_id != resource_id] - - def find_resource_by_id(self, resource_id): - """Returns the resource with the given ID or None if no matching - resource is found. - - :type resource_id: str - :rtype: tuskar.templates.heat.Resource or None - """ - for r in self._resources: - if r.resource_id == resource_id: - return r - return None - - def add_output(self, output): - """Adds an output to the template. - - :type output: tuskar.templates.heat.Output - """ - self._outputs.append(output) - - def remove_output(self, output): - """Removes an output from the template. - - :type output: tuskar.templates.heat.Output - :raise ValueError: if the output is not in the template - """ - self._outputs.remove(output) - - def remove_outputs_by_namespace(self, namespace): - """Removes all outputs in the given namespace from the template. - - :type namespace: str - """ - self._outputs = [ - o for o in self.outputs - if not ns_utils.matches_template_namespace(namespace, o.name)] - - def find_output_by_name(self, name): - """Returns the output with the given name or None if no matching - output is found. - - :type name: str - :rtype: tuskar.templates.heat.Output or None - """ - for o in self._outputs: - if o.name == name: - return o - return None - - -class ParameterGroup(object): - - def __init__(self, label, description=None): - super(ParameterGroup, self).__init__() - self.label = label - self.description = description - self._parameter_names = set() - - def __str__(self): - msg = ('ParameterGroup: label=%(label)s, description=%(desc)s ' - 'parameter_names=%(names)s') - data = { - 'label': self.label, - 'desc': self.description, - 'names': ','.join(self.parameter_names), - } - return msg % data - - @property - def parameter_names(self): - return tuple(self._parameter_names) - - def add_parameter_name(self, *names): - """Adds one or more parameters to the group. - - :type names: str - """ - for n in names: - self._parameter_names.add(n) - - def remove_parameter_name(self, name): - """Removes a parameter from the group if it is present. - - :type name: str - """ - self._parameter_names.discard(name) - - -class Parameter(object): - - def __init__(self, name, param_type, - description=None, label=None, default=None, hidden=None): - super(Parameter, self).__init__() - self.name = name - self.param_type = param_type - self.description = description - self.label = label - self.default = default - self.hidden = hidden - self._constraints = [] - - def __str__(self): - msg = ('Parameter: name=%(name)s, type=%(type)s, ' - 'description=%(desc)s, label=%(label)s, ' - 'default=%(def)s, hidden=%(hidden)s') - data = { - 'name': self.name, - 'type': self.param_type, - 'desc': self.description, - 'label': self.label, - 'def': self.default, - 'hidden': self.hidden, - } - return msg % data - - @property - def constraints(self): - return tuple(self._constraints) - - def add_constraint(self, constraint): - """Adds a constraint to the parameter. - - :type constraint: tuskar.templates.heat.ParameterConstraint - """ - self._constraints.append(constraint) - - def remove_constraint(self, constraint): - """Removes a constraint from the template. - - :type constraint: tuskar.templates.heat.ParameterConstraint - :raise ValueError: if the given constraint isn't in the parameter - """ - self._constraints.remove(constraint) - - -class ParameterConstraint(object): - - def __init__(self, constraint_type, definition, description=None): - super(ParameterConstraint, self).__init__() - self.constraint_type = constraint_type - self.definition = definition - self.description = description - - def __str__(self): - msg = ('Constraint: type=%(type)s, definition=%(def)s, ' - 'description=%(desc)s') - data = { - 'type': self.constraint_type, - 'def': self.definition, - 'desc': self.description, - } - return msg % data - - -class Resource(object): - - def __init__(self, resource_id, resource_type, - metadata=None, depends_on=None, - update_policy=None, deletion_policy=None): - super(Resource, self).__init__() - self.resource_id = resource_id - self.resource_type = resource_type - self.metadata = metadata - self.depends_on = depends_on - self.update_policy = update_policy - self.deletion_policy = deletion_policy - self._properties = [] - - def __str__(self): - msg = 'Resource: id=%(id)s, resource_type=%(type)s' - data = { - 'id': self.resource_id, - 'type': self.resource_type, - } - return msg % data - - @property - def properties(self): - return tuple(self._properties) - - def add_property(self, resource_property): - """Adds a property to the resource. - - :type resource_property: tuskar.templates.heat.ResourceProperty - """ - self._properties.append(resource_property) - - def remove_property(self, resource_property): - """Removes a property from the template. - - :type resource_property: tuskar.templates.heat.ResourceProperty - :raise ValueError: if the property isn't in the resource - """ - self._properties.remove(resource_property) - - def find_property_by_name(self, name): - """Returns the property with the given name or None if there is no - matching property. - - :type name: str - :rtype: tuskar.templates.heat.ResourceProperty or None - """ - for p in self._properties: - if p.name == name: - return p - return None - - -class ResourceProperty(object): - - def __init__(self, name, value): - super(ResourceProperty, self).__init__() - self.name = name - self.value = value - - def __str__(self): - msg = 'ResourceProperty: name=%(name)s, value=%(value)s' - data = { - 'name': self.name, - 'value': self.value, - } - return msg % data - - -class Output(object): - - def __init__(self, name, value, description=None): - super(Output, self).__init__() - self.name = name - self.value = value - self.description = description - - def __str__(self): - msg = 'Output: name=%(name)s, value=%(value)s, description=%(desc)s' - data = { - 'name': self.name, - 'value': self.value, - 'desc': _safe_strip(self.description) - } - return msg % data - - -class Environment(object): - - def __init__(self): - super(Environment, self).__init__() - self._parameters = [] - self._registry_entries = [] - - def __str__(self): - msg = ('Environment: parameter_count=%(p_count)s, ' - 'registry_count=%(r_count)s') - data = { - 'p_count': len(self.parameters), - 'r_count': len(self.registry_entries), - } - return msg % data - - @property - def parameters(self): - return tuple(self._parameters) - - @property - def registry_entries(self): - return tuple(self._registry_entries) - - def add_parameter(self, parameter): - """Adds a property to the environment. - - :type parameter: tuskar.templates.heat.EnvironmentParameter - """ - self._parameters.append(parameter) - - def remove_parameter(self, parameter): - """Removes a parameter from the environment. - - :type parameter: tuskar.templates.heat.EnvironmentParameter - :raise ValueError: if the parameter is not in the environment - """ - self._parameters.remove(parameter) - - def remove_parameters_by_namespace(self, namespace): - """Removes all parameters that match the given namespace. - - :type namespace: str - """ - self._parameters = [ - p for p in self._parameters - if not ns_utils.matches_template_namespace(namespace, p.name)] - - def find_parameter_by_name(self, name): - """Returns the parameter instance with the given name or None - if there is no matching parameter. - - :type name: str - :rtype: tuskar.templates.heat.EnvironmentParameter or None - """ - for p in self._parameters: - if p.name == name: - return p - return None - - def has_parameter_in_namespace(self, namespace): - """Returns true if the environment has at least one parameter - in given namespace, false otherwise. - - :type namespace: str - """ - for p in self._parameters: - if ns_utils.matches_template_namespace(namespace, p.name): - return True - return False - - def add_registry_entry(self, entry, unique=False): - """Adds a registry entry to the environment. - - :type entry: tuskar.templates.heat.RegistryEntry - :param unique: toggles if registry is treated as a set - :type unique: boolean - """ - if unique: - setentries = set(self._registry_entries) - setentries.add(entry) - self._registry_entries = list(setentries) - else: - self._registry_entries.append(entry) - - def remove_registry_entry(self, entry): - """Removes a registry entry from the environment. - - :type entry: tuskar.templates.heat.RegistryEntry - :raise ValueError: if the entry is not in the environment - """ - self._registry_entries.remove(entry) - - def remove_registry_entry_by_alias(self, alias): - """Removes a registry entry from the environment if it is found. - - :type alias: str - """ - self._registry_entries = [e for e in self._registry_entries - if e.alias != alias] - - -class EnvironmentParameter(object): - - def __init__(self, name, value): - super(EnvironmentParameter, self).__init__() - self.name = name - self.value = value - - def __str__(self): - msg = 'EnvironmentParameter: name=%(name)s, value=%(value)s' - data = { - 'name': self.name, - 'value': self.value, - } - return msg % data - - -class RegistryEntry(object): - - def __init__(self, alias, filename): - super(RegistryEntry, self).__init__() - self.alias = alias - self.filename = filename - # TODO(jpeeler) rename self.filename to mapping - - def __str__(self): - msg = 'RegistryEntry: alias=%(alias)s, filename=%(f)s' - data = { - 'alias': self.alias, - 'f': self.filename, - } - return msg % data - - def is_filename(self): - if ('::' in self.filename or - path.splitext(self.filename)[1] not in ('.yaml', '.yml')): - return False - return True - - -def _safe_strip(value): - """Strips the value if it is not None. - - :param value: text to be cleaned up - :type value: str or None - - :return: clean value if one was specified; None otherwise - :rtype: str or None - """ - if value is not None: - return value.strip() - return None diff --git a/tuskar/templates/namespace.py b/tuskar/templates/namespace.py deleted file mode 100644 index f16de944..00000000 --- a/tuskar/templates/namespace.py +++ /dev/null @@ -1,64 +0,0 @@ -# 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. - -""" -Methods for manipulating Heat template pieces (parameters, outputs, etc.) -and Heat environment pieces (resource alias) to scope them to a particular -namespace to prevent conflicts when combining templates. This module contains -methods for applying, removing, and testing if a name is part of a -particular namespace. -""" - -DELIMITER = '::' - -ALIAS_PREFIX = 'Tuskar::' - - -def apply_template_namespace(namespace, original_name): - """Applies a namespace to a template component, such as a parameter - or output. - - :rtype: str - """ - return namespace + DELIMITER + original_name - - -def remove_template_namespace(name): - """Strips any namespace off the given value and returns the original name. - - :rtype: str - """ - return name[name.index(DELIMITER) + len(DELIMITER):] - - -def matches_template_namespace(namespace, name): - """Returns whether or not the given name is in the specified namespace. - - :rtype: bool - """ - return name.startswith(namespace + DELIMITER) - - -def apply_resource_alias_namespace(alias): - """Creates a Heat environment resource alias under the Tuskar namespace. - - :rtype: str - """ - return ALIAS_PREFIX + alias - - -def remove_resource_alias_namespace(alias): - """Returns the original resource alias without the Tuskar namespace. - - :rtype: str - """ - return alias[len(ALIAS_PREFIX):] diff --git a/tuskar/templates/parser.py b/tuskar/templates/parser.py deleted file mode 100644 index 44ff3b61..00000000 --- a/tuskar/templates/parser.py +++ /dev/null @@ -1,175 +0,0 @@ -# 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. - -""" -Functionality for parsing Heat files (templates and environment files) into -their object model representations. - -The parsing was written against the HOT specification found at: -http://docs.openstack.org/developer/heat/template_guide/hot_spec.html -""" - -import yaml - -from tuskar.templates.heat import Environment -from tuskar.templates.heat import EnvironmentParameter -from tuskar.templates.heat import Output -from tuskar.templates.heat import Parameter -from tuskar.templates.heat import ParameterConstraint -from tuskar.templates.heat import ParameterGroup -from tuskar.templates.heat import RegistryEntry -from tuskar.templates.heat import Resource -from tuskar.templates.heat import ResourceProperty -from tuskar.templates.heat import Template - - -def parse_template(content): - """Parses a Heat template into the Tuskar object model. - - :param content: string representation of the template - :type content: str - - :return: Tuskar representation of the template - :rtype: tuskar.templates.heat.Template - """ - yaml_parsed = yaml.load(content) - template = Template() - - _parse_version(template, yaml_parsed) - _parse_description(template, yaml_parsed) - _parse_template_parameters(template, yaml_parsed) - _parse_parameter_group(template, yaml_parsed) - _parse_resources(template, yaml_parsed) - _parse_outputs(template, yaml_parsed) - - return template - - -def parse_environment(content): - """Parses a Heat environment file into the Tuskar object model. - - :param content: string representation of the environment file - :type content: str - - :return: Tuskar representation of the environment file - :rtype: tuskar.templates.heat.Environment - """ - yaml_parsed = yaml.load(content) - environment = Environment() - - _parse_environment_parameters(environment, yaml_parsed) - _parse_resource_registry(environment, yaml_parsed) - - return environment - - -def _parse_version(template, yaml_parsed): - template.version = ( - yaml_parsed.get('heat_template_version', None) or template.version) - - -def _parse_description(template, yaml_parsed): - template.description = ( - yaml_parsed.get('description', None) or template.description) - - -def _parse_template_parameters(template, yaml_parsed): - yaml_parameters = yaml_parsed.get('parameters', {}) - for name, details in yaml_parameters.items(): - - # Basic parameter data - param_type = details['type'] # required - description = details.get('description', None) - label = details.get('label', None) - default = details.get('default', None) - hidden = details.get('hidden', None) - - parameter = Parameter(name, param_type, description=description, - label=label, default=default, hidden=hidden) - template.add_parameter(parameter) - - # Parse constraints if present - constraints = details.get('constraints', None) - if constraints is not None: - for constraint_details in constraints: - - # The type of constraint is a key in the constraint data, so - # rather than know all of the possible values, pop out the - # description (if present) and assume the remaining key/value - # pair is the type and definition. - - description = constraint_details.pop('description', None) - constraint_type = constraint_details.keys()[0] - definition = constraint_details[constraint_type] - - constraint = ParameterConstraint(constraint_type, definition, - description=description) - parameter.add_constraint(constraint) - - -def _parse_parameter_group(template, yaml_parsed): - yaml_groups = yaml_parsed.get('parameter_groups', []) - - for details in yaml_groups: - label = details['label'] - description = details.get('description', None) - param_names = details['parameters'] - - group = ParameterGroup(label, description) - group.add_parameter_name(*param_names) - template.add_parameter_group(group) - - -def _parse_resources(template, yaml_parsed): - yaml_resources = yaml_parsed.get('resources', {}) - for resource_id, details in yaml_resources.items(): - resource_type = details['type'] # required - metadata = details.get('metadata', None) - depends_on = details.get('depends_on', None) - update_policy = details.get('update_policy', None) - deletion_policy = details.get('deletion_policy', None) - - resource = Resource(resource_id, resource_type, metadata=metadata, - depends_on=depends_on, update_policy=update_policy, - deletion_policy=deletion_policy) - template.add_resource(resource) - - for key, value in details.get('properties', {}).items(): - prop = ResourceProperty(key, value) - resource.add_property(prop) - - -def _parse_outputs(template, yaml_parsed): - yaml_outputs = yaml_parsed.get('outputs', {}) - for name, details in yaml_outputs.items(): - value = details['value'] # required - - # HOT spec doesn't list this as optional, but most descriptions are, - # so assume it is here too - description = details.get('description', None) - - output = Output(name, value, description=description) - template.add_output(output) - - -def _parse_environment_parameters(environment, yaml_parsed): - yaml_parameters = yaml_parsed.get('parameters', {}) - for name, value in yaml_parameters.items(): - parameter = EnvironmentParameter(name, value) - environment.add_parameter(parameter) - - -def _parse_resource_registry(environment, yaml_parsed): - yaml_entries = yaml_parsed.get('resource_registry', {}) - for namespace, filename in yaml_entries.items(): - entry = RegistryEntry(namespace, filename) - environment.add_registry_entry(entry) diff --git a/tuskar/templates/plan.py b/tuskar/templates/plan.py deleted file mode 100644 index bf470431..00000000 --- a/tuskar/templates/plan.py +++ /dev/null @@ -1,327 +0,0 @@ -# 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. - -""" -Object representations of the Tuskar-specific domain concepts. These objects -are used to build up a deployment plan by adding templates (roles, in -Tuskar terminology). The composer module can then be used to translate -these models into the corresponding Heat format. -""" - -import copy - -from tuskar.templates.heat import Environment -from tuskar.templates.heat import EnvironmentParameter -from tuskar.templates.heat import Output -from tuskar.templates.heat import Parameter -from tuskar.templates.heat import ParameterConstraint -from tuskar.templates.heat import RegistryEntry -from tuskar.templates.heat import Resource -from tuskar.templates.heat import ResourceProperty -from tuskar.templates.heat import Template -import tuskar.templates.namespace as ns_utils - - -# Type string for a Heat resource group -HEAT_TYPE_RESOURCE_GROUP = 'OS::Heat::ResourceGroup' - -# Name of the property added to a resource group to control its scaling -PROPERTY_SCALING_COUNT = 'count' - -# Name of the property added to a resource group to control removing resources -PROPERTY_REMOVAL_POLICIES = 'removal_policies' - -# Name of the property added to a resource group to define the resources -# in the group -PROPERTY_RESOURCE_DEFINITION = 'resource_def' - - -class DeploymentPlan(object): - - def __init__(self, master_template=None, environment=None, - description=None, add_scaling=True): - """Creates a new deployment plan. The plan can be initialized from - an existing plan's components by passing in the master template - and environment. Keep in mind that there are relationships between - the master template and the environment and they should either both - be specified or neither. If they are unspecified, empty versions of - both files will be created. - - The add_scaling flag controls whether or not the plan will - automatically add in Heat constructs to support scaling when a - template is added. - - :param master_template: template instance to use for the plan - :type master_template: tuskar.templates.heat.Template - :param environment: environment to use for the plan - :type environment: tuskar.templates.heat.Environment - :param description: optional description for the plan's master - template; only used if a master template is not specified - :type description: str - :param add_scaling: flag controlling if the plan will automatically - include scaling components to the master template - :type add_scaling: bool - """ - super(DeploymentPlan, self).__init__() - self.master_template = ( - master_template or Template(description=description)) - self.environment = environment or Environment() - self.add_scaling = add_scaling - - def add_template(self, namespace, template, filename, - override_properties=None): - """Adds a new template to the plan. The pieces of the template will - be prefixed with the given namespace in the plan's master template. - - :param namespace: prefix to use to prevent parameter and output - naming conflicts - :type namespace: str - :param template: template being added to the plan - :type template: tuskar.templates.heat.Template - :param filename: name of the file where the template is stored, used - when mapping the template in the environment - :type filename: str - :param override_properties: mapping of property name to specific value - to use instead of the Tuskar-generated parameter - :type override_properties: {str, str} - :raise ValueError: if given namespace is already taken - """ - override_properties = override_properties or {} - - if self.environment.has_parameter_in_namespace(namespace): - raise ValueError( - "Cannot add template to plan - namespace '%s' is taken." - % namespace) - - resource_alias = ns_utils.apply_resource_alias_namespace(namespace) - - self._add_to_master_template(namespace, template, resource_alias, - override_properties) - self._add_to_environment(namespace, template, filename, resource_alias, - override_properties) - - def remove_template(self, namespace): - """Removes all references to the template added under the given - namespace. This call does not error if a template with the given - namespace hasn't been added. - - :type namespace: str - """ - self._remove_from_master_template(namespace) - self._remove_from_environment(namespace) - - def set_value(self, name, value): - """Sets the value for the attribute with the given name. The name - must correspond exactly with an attribute of the plan itself; in - other words, the namespace of the template from which the attribute - originally came from should already be applied. - - :type name: str - :type value: str - :raise ValueError: if there is no parameter with the given name - """ - p = self.environment.find_parameter_by_name(name) - if p is None: - raise ValueError('No parameter named: %s' % name) - p.value = value - - def _add_to_master_template(self, namespace, template, resource_alias, - override_properties): - self._add_resource(namespace, template, resource_alias, - override_properties) - self._add_parameters(namespace, template, override_properties) - - # Temporarily disable the output promotion. In the future, this should - # be controlled by a variable, but for the interim, this is not needed - # with the THT templates. - # self._add_outputs(namespace, template, resource) - - def _add_resource(self, namespace, template, resource_alias, - override_properties): - resource = Resource(generate_resource_id(namespace), resource_alias) - - for map_me in template.parameters: - name = map_me.name - - # If an explicit value is specified, use that. Otherwise, create - # a look up within the master template. - if name in override_properties: - value = override_properties[name] - else: - master_name = ns_utils.apply_template_namespace(namespace, - map_me.name) - value = {'get_param': [master_name]} - resource_property = ResourceProperty(name, value) - resource.add_property(resource_property) - - # If scaling features are being automatically added in, wrap the - # resource in a resource group. The _add_parameters call will add - # a corresponding parameter for the count of this resource. - if self.add_scaling: - group_resource_id = generate_group_id(namespace) - heat_group = Resource(group_resource_id, HEAT_TYPE_RESOURCE_GROUP) - - count_prop = ResourceProperty( - PROPERTY_SCALING_COUNT, - {'get_param': [generate_count_property_name(namespace)]}) - heat_group.add_property(count_prop) - - removal_prop = ResourceProperty( - PROPERTY_REMOVAL_POLICIES, - {'get_param': [generate_removal_policies_name(namespace)]}) - heat_group.add_property(removal_prop) - - def_prop = ResourceProperty(PROPERTY_RESOURCE_DEFINITION, resource) - heat_group.add_property(def_prop) - - outer_resource = heat_group - else: - outer_resource = resource - - self.master_template.add_resource(outer_resource) - return outer_resource - - def _add_parameters(self, namespace, template, override_properties): - for add_me in template.parameters: - if add_me.name in override_properties: - continue - - cloned = copy.deepcopy(add_me) - cloned.name = ns_utils.apply_template_namespace(namespace, - add_me.name) - self.master_template.add_parameter(cloned) - - # If scaling features are being automatically added in, create the - # template parameters for configuring the group - if self.add_scaling: - count_param = Parameter(generate_count_property_name(namespace), - 'number') - constraint = ParameterConstraint('range', {'min': '0'}) - count_param.add_constraint(constraint) - self.master_template.add_parameter(count_param) - - removal_param = Parameter( - generate_removal_policies_name(namespace), 'json') - removal_param.default = [] - self.master_template.add_parameter(removal_param) - - def _add_outputs(self, namespace, template, resource): - for add_me in template.outputs: - # The output creation is a bit trickier than simply copying the - # original. The master output is namespaced like the other pieces, - # and it's value is retrieved from the resource that's created in - # the master template, but will be present in that resource - # under it's original name. - output_name = ns_utils.apply_template_namespace(namespace, - add_me.name) - output_value = {'get_attr': [resource.resource_id, add_me.name]} - master_out = Output(output_name, output_value) - self.master_template.add_output(master_out) - - def _add_to_environment(self, namespace, template, - filename, resource_alias, override_properties): - # Add Parameters - for add_me in template.parameters: - - # For overridden properties, don't add a template property; the - # user does not need to specify a value for them. - if add_me.name in override_properties: - continue - - name = ns_utils.apply_template_namespace(namespace, add_me.name) - param_default = add_me.default - if param_default is None: - param_default = '' - env_parameter = EnvironmentParameter(name, param_default) - self.environment.add_parameter(env_parameter) - - if self.add_scaling: - count_param_name = generate_count_property_name(namespace) - count_param = EnvironmentParameter(count_param_name, '1') - self.environment.add_parameter(count_param) - - removal_param_name = generate_removal_policies_name(namespace) - removal_param = EnvironmentParameter(removal_param_name, []) - self.environment.add_parameter(removal_param) - - # Add Resource Registry Entry - registry_entry = RegistryEntry(resource_alias, filename) - self.environment.add_registry_entry(registry_entry) - - def _remove_from_master_template(self, namespace): - # Remove Parameters - self.master_template.remove_parameters_by_namespace(namespace) - - # Remove Outputs - self.master_template.remove_outputs_by_namespace(namespace) - - # Remove Resource - - # If scaling features are being automatically added in, the resource - # is wrapped in a scaling group, so remove that instead - resource_id = generate_resource_id(namespace) - if self.add_scaling: - group_resource_id = generate_group_id(namespace) - self.master_template.remove_resource_by_id(group_resource_id) - else: - self.master_template.remove_resource_by_id(resource_id) - - def _remove_from_environment(self, namespace): - # Remove Parameters - self.environment.remove_parameters_by_namespace(namespace) - - # Remove Resource Registry Entry - resource_alias = ns_utils.apply_resource_alias_namespace(namespace) - self.environment.remove_registry_entry_by_alias(resource_alias) - - -def generate_resource_id(namespace): - """Generates the ID of the resource to be added to the plan's master - template when a new template is added. - - :type namespace: str - :rtype: str - """ - return namespace + '-resource' - - -def generate_group_id(namespace): - """Generates the ID for a resource group wrapper resource around the - resource with the given namespace. - - :type namespace: str - :rtype: str - """ - return namespace.rsplit('-', 1)[0] - - -def generate_count_property_name(namespace): - """Generates the name of the property to hold the count of a particular - resource as identified by its namespace. The count property will be - prefixed by the namespace in the same way as other parameters for the - resource. - - :type namespace: str - :rtype: str - """ - return ns_utils.apply_template_namespace(namespace, 'count') - - -def generate_removal_policies_name(namespace): - """Generates the name of the property to hold the removal policies for - a group. The property will be prefixed by the namespace in the same way - as other parameters for the resource. - - :type namespace: str - :rtype: str - """ - return ns_utils.apply_template_namespace(namespace, 'removal_policies') diff --git a/tuskar/templates/template_seed.py b/tuskar/templates/template_seed.py deleted file mode 100644 index db264d93..00000000 --- a/tuskar/templates/template_seed.py +++ /dev/null @@ -1,353 +0,0 @@ -# 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. - -""" -In Juno, Tuskar supports the aggregation of resource providers into a -deployment plan. The TripleO Heat Templates, however, require more -advanced handling than will not be present in the Juno release. - -The THT templates use resources at the top level of overcloud.yaml to -generate property values used across multiple provider resources. Additionally, -these values require their own input parameters and are used in the stack -outputs. Ultimately, Tuskar will support adding these resources in a similar -way as adding a role to a plan. This work is planned for Kilo. - -For Juno, a Tuskar user can specify a seed master template file during the -role loading process. This file references the possible roles as well as any -other parameters, resources, and outputs that should be included in *all* -created plans. Without overcomplicating things by overusing the word, the -seed is a template for how the plan's master template will look. - -This module contains methods to make the appropriate additions to a deployment -plan's master template file using a seed template. -""" - -import copy -import logging - -from tuskar.templates.heat import Environment -from tuskar.templates.heat import EnvironmentParameter -from tuskar.templates.heat import Resource -from tuskar.templates import namespace as ns_utils - - -LOG = logging.getLogger(__name__) - - -def add_top_level_resources(source, destination): - """Adds the top-level resources from the source template into the given - template. If the resource is already in the destination template, it - will not be added again. - - :type source: tuskar.templates.heat.Template - :type destination: tuskar.templates.heat.Template - """ - top_level_resources = [r for r in source.resources if not _is_role(r)] - - for r in top_level_resources: - if destination.find_resource_by_id(r.resource_id) is None: - cloned = copy.copy(r) - destination.add_resource(cloned) - - -def add_top_level_parameters(source, destination, environment): - """Adds the top-level parameters from the source template into the given - template. If the parameter is already in the destination template, it will - not be added again. - - :type source: tuskar.templates.heat.Template - :type destination: tuskar.templates.heat.Template - :type environment: tuskar.templates.heat.Environment - """ - - # Make a list of all property key names across all role resources - role_property_names = [] - role_resources = [r for r in source.resources if _is_role(r)] - for r in role_resources: - names = _resource_property_keys(r) - role_property_names.extend(names) - - top_level_property_names = [] - top_level_resources = [r for r in source.resources if not _is_role(r)] - for r in top_level_resources: - for p in r.properties: - _top_level_property_keys(top_level_property_names, p.value) - - role_only_names = set(role_property_names) - set(top_level_property_names) - - # Get the list of all source parameters and strip out any that came from - # a role - top_level_params = [p for p in source.parameters - if p.name not in role_only_names] - - # Add a copy of each top-level parameter to the destination if it's not - # already present - for p in top_level_params: - if destination.find_parameter_by_name(p.name) is None: - cloned = copy.copy(p) - destination.add_parameter(cloned) - - ep = EnvironmentParameter(p.name, - p.default if p.default is not None else - '') - environment.add_parameter(ep) - - -def preserve_defaults(source, destination): - """Preserve default values from the master_seed. - - For example ServiceNetMap has a default value specified in - overcloud-without-merge.py but is specified as {} in the role - templates. If the master default is not empty and the destination - value is empty, then propagate it. This is also applied to the - Environment file (hence, dp.default, vs dp.value) - """ - def _update_value(exists, dp): - if isinstance(destination, Environment): - if exists and exists.default and not dp.value: - dp.value = exists.default - else: - if exists and exists.default and not dp.default: - dp.default = exists.default - - for dp in destination.parameters: - try: - exists = source.find_parameter_by_name( - ns_utils.remove_template_namespace(dp.name)) - except ValueError: # non namespaced attributes - exists = source.find_parameter_by_name(dp.name) - _update_value(exists, dp) - - -def add_top_level_outputs(source, destination): - """Adds all top-level outputs from the source template into the given - template. If the output is already in the destination, it will not be - added again. - - :type source: tuskar.templates.heat.Template - :type destination: tuskar.templates.heat.Template - """ - for o in source.outputs: - if destination.find_output_by_name(o.name) is None: - cloned = copy.copy(o) - destination.add_output(cloned) - - -def get_property_map_for_role(source, role_name): - """Returns a mapping of property name to the specific value that should - be used for it in the corresponding resource in the master template. For - properties not returned in this way, Tuskar will create a master template - parameter and a get_param lookup when creating the role resource. - - :type source: tuskar.templates.heat.Template - :param role_name: non-namespaced role name - :type role_name: str - - :return: mapping of property name to top-level resource lookup (get_attr) - for any complex property in the role; simple properties will not - be present in the dict; None if the role is not found in the - source template - :rtype: dict - - :raises ValueError: if the role is not in the given template - """ - resource = find_role_from_type(source.resources, role_name) - - if resource is None: - return None - - resource_def = _scaling_inner_resource(resource) - - property_map = {} - for name, value in resource_def.value['properties'].items(): - # If the property is a straight get_param look up, there is nothing - # special to map. Tuskar will take care of adding these when the - # role is added. - if isinstance(value, dict) and 'get_param' in value: - continue - - property_map[name] = value - - return property_map - - -def update_role_resource_references(template, seed_role, - tuskar_resource_name): - """Updates the the given template to change references - inside of the top-level resources from the seed's role name to the - name of the resource as added by Tuskar. - - For example, the overcloud.yaml definition for the compute resource is - given the name "Compute", however when Tuskar generates the resource it - includes the role named "Compute". - - Many of the top-level resources will want to reference the role in its - properties using one of two mechanisms: get_attr or get_resource. In - these cases, the lookup must be changed to the Tuskar-generated role name. - - :type template: tuskar.templates.heat.Template - :type seed_role: tuskar.templates.heat.Resource - :type tuskar_resource_name: str - """ - top_level_resources = [r for r in template.resources if not _is_role(r)] - - def index_property(update_me): - if isinstance(update_me, dict): - return update_me.items() - elif isinstance(update_me, list): - return enumerate(update_me) - - def update_property(update_me): - for index, value in index_property(update_me): - if isinstance(value, (dict, list)): - update_property(value) - elif isinstance(value, basestring): - if value == seed_role.resource_id: - update_me[index] = tuskar_resource_name - else: - LOG.warn('Unexpected type (%s) in property value (%s)' % - (value.__class__.__name__, value)) - - for r in top_level_resources: - for p in r.properties: - if isinstance(p.value, dict): - update_property(p.value) - - -def update_role_property_references(destination, orig, namespace): - """Updates top-level resource use of parameters that are also defined - by a role resource to use the role's namespaced name for the property. - - :type destination: tuskar.templates.heat.Template - :type orig: tuskar.templates.heat.Resource - :type namespace: str - """ - all_role_property_keys = _resource_property_keys(orig) - - def _update_property(check_me): - if isinstance(check_me, dict): - for k, v in check_me.items(): - if k == 'get_param' and v in all_role_property_keys: - check_me[k] = ns_utils.apply_template_namespace(namespace, - v) - else: - # It could be a nested dictionary, so recurse further - _update_property(v) - - top_level_resources = [r for r in destination.resources if not _is_role(r)] - for r in top_level_resources: - for p in r.properties: - _update_property(p.value) - - -def _scaling_inner_resource(resource): - """Return the inner resource wrapped in Heat's scaling resource. - - The property name of the inner resource is inconsistent between - ResourceGroup and AutoScalingGroup so this does the necessary checks and - returns the right thing or None on any other type. - """ - if resource.resource_type == 'OS::Heat::ResourceGroup': - inner = resource.find_property_by_name('resource_def') - elif resource.resource_type == 'OS::Heat::AutoScalingGroup': - inner = resource.find_property_by_name('resource') - else: - inner = None - return inner - - -def find_role_from_type(resources, role_type): - """Given a list of resources an a role type, return the matching one. - - :type resources: list of tuskar.templates.heat.Resource - :type role_type: str - :rtype: tuskar.templates.heat.Resource - :raise ValueError: if the template contains more than one resource with - the given ID (regardless of case) - """ - roles = [r for r in resources if _is_role(r)] - matching = [r for r in roles - if _scaling_inner_resource(r).value['type'] == role_type] - if len(matching) > 1: - raise ValueError('Invalid template; contains multiple resources ' - 'matching %s' % role_type) - elif len(matching) == 1: - return matching[0] - else: - return None - - -def _is_role(resource): - """Returns whether or not the given resource represents a role. - - :type resource: tuskar.templates.heat.Resource - :rtype: bool - """ - inner_resource = _scaling_inner_resource(resource) - if inner_resource is None: - return False - - if isinstance(inner_resource.value, Resource): - v = inner_resource.value.resource_type - else: - v = inner_resource.value['type'] - return 'OS::TripleO::' in v - - -def _resource_property_keys(resource): - """Returns a list of all get_param lookup keys for the given resource. - - :type resource: tuskar.templates.heat.Resource - :return: list of property names; empty list if none are found - :rtype: [str] - """ - keys = [] - - for rp in resource.properties: - if rp.name in ('resource_def', 'resource'): - # For a resource definition, dig into the properties value - # within to get all of the inner resource properties. - for rpv in rp.value['properties'].values(): - if isinstance(rpv, dict) and 'get_param' in rpv: - keys.append(rpv['get_param']) - else: - # For all other resource properties, if the value is a - # get_param lookup, consider the value of the look up a - # resource property. - if isinstance(rp.value, dict) and 'get_param' in rp.value: - keys.append(rp.value['get_param']) - return keys - - -def _top_level_property_keys(keys, check_me): - """Recursively checks through all values in the given check_me value and - updates the list of all get_param lookup keys used within. - For example input could be: - keystone_admin_api_vip: { get_attr: [VipMap, net_ip_map, { - get_param: [ServiceNetMap, KeystoneAdminApiNetwork]}]} - :type keys: list - """ - if isinstance(check_me, (dict, list)): - if isinstance(check_me, list): - values = check_me - else: - values = check_me.values() - for pr in values: - if isinstance(pr, dict): - for k, v in pr.items(): - if k == 'get_param' and isinstance(v, str): - keys.append(v) - elif k == 'get_param' and isinstance(v, list): - keys.append(v[0]) - else: - # It could be a nested dictionary, so recurse further - _top_level_property_keys(keys, v) diff --git a/tuskar/tests/__init__.py b/tuskar/tests/__init__.py deleted file mode 100644 index d79119d1..00000000 --- a/tuskar/tests/__init__.py +++ /dev/null @@ -1,34 +0,0 @@ -# Copyright 2010 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# All Rights Reserved. -# -# 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. - -""" -:mod:`Tuskar.tests` -- tuskar Unittests -===================================================== - -.. automodule:: tuskar.tests - :platform: Unix -""" - -# TODO(deva): move eventlet imports to tuskar.__init__ once we move to PBR - -import eventlet - -eventlet.monkey_patch(os=False) - -# See http://code.google.com/p/python-nose/issues/detail?id=373 -# The code below enables nosetests to work with i18n _() blocks -import six.moves.builtins as __builtin__ -setattr(__builtin__, '_', lambda x: x) diff --git a/tuskar/tests/api/__init__.py b/tuskar/tests/api/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tuskar/tests/api/api.py b/tuskar/tests/api/api.py deleted file mode 100644 index 5486bcc6..00000000 --- a/tuskar/tests/api/api.py +++ /dev/null @@ -1,58 +0,0 @@ -# -# Copyright 2012 New Dream Network, LLC (DreamHost) -# -# 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 oslo_config import cfg -import pecan -import pecan.testing - -from tuskar.api import acl -from tuskar.tests import base - - -class FunctionalTest(base.TestCase): - """Used for functional tests of Pecan controllers where you need to - test your literal application and its integration with the - framework. - """ - - PATH_PREFIX = '/v1' - - SOURCE_DATA = {'test_source': {'somekey': '666'}} - - def setUp(self): - super(FunctionalTest, self).setUp() - cfg.CONF.set_override("auth_version", "v2.0", group=acl.OPT_GROUP_NAME) - self.app = self._make_app() - - def _make_app(self, enable_acl=False): - # Determine where we are so we can set up paths in the config - root_dir = self.path_get() - - self.config = { - 'app': { - 'root': 'tuskar.api.controllers.root.RootController', - 'modules': ['tuskar.api'], - 'static_root': '%s/public' % root_dir, - 'template_path': '%s/tuskar/api/templates' % root_dir, - 'enable_acl': enable_acl, - }, - } - - return pecan.testing.load_test_app(self.config) - - def tearDown(self): - super(FunctionalTest, self).tearDown() - pecan.set_config({}, overwrite=True) diff --git a/tuskar/tests/api/controllers/__init__.py b/tuskar/tests/api/controllers/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tuskar/tests/api/controllers/v1/__init__.py b/tuskar/tests/api/controllers/v1/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tuskar/tests/api/controllers/v1/test_models.py b/tuskar/tests/api/controllers/v1/test_models.py deleted file mode 100644 index 390bbfb9..00000000 --- a/tuskar/tests/api/controllers/v1/test_models.py +++ /dev/null @@ -1,150 +0,0 @@ -# -# 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 testtools -from wsme import types as wtypes - -from tuskar.api.controllers.v1 import models as api_models -from tuskar.db.sqlalchemy import models as db_models - - -class BaseTests(testtools.TestCase): - - def test_lookup(self): - # Setup - class Stub(api_models.Base): - a1 = int - a2 = int - - stub = Stub(a1=1, a2=wtypes.Unset) - - # Test - self.assertEqual(1, stub._lookup('a1')) - self.assertIsNone(stub._lookup('a2')) - - -class OvercloudModelTests(testtools.TestCase): - - def test_from_db_model(self): - # Setup - db_attrs = [ - db_models.OvercloudAttribute( - id=10, - overcloud_id=1, - key='key-1', - value='value-1', - ), - db_models.OvercloudAttribute( - id=20, - overcloud_id=1, - key='key-2', - value='value-2', - ), - ] - - db_counts = [ - db_models.OvercloudRoleCount( - id=100, - overcloud_id=1, - overcloud_role_id=5, - num_nodes=5, - ) - ] - - db_model = db_models.Overcloud( - id=1, - stack_id='stack-1', - name='name-1', - description='desc-1', - attributes=db_attrs, - counts=db_counts, - ) - - # Test - api_model = api_models.Overcloud.from_db_model(db_model) - - # Verify - self.assertTrue(api_model is not None) - self.assertTrue(isinstance(api_model, api_models.Overcloud)) - - self.assertEqual(api_model.id, db_model.id) - self.assertEqual(api_model.stack_id, db_model.stack_id) - self.assertEqual(api_model.name, db_model.name) - self.assertEqual(api_model.description, db_model.description) - - self.assertEqual(len(api_model.attributes), len(db_model.attributes)) - self.assertTrue(isinstance(api_model.attributes, dict)) - for d_attr in db_model.attributes: - self.assertEqual(api_model.attributes[d_attr.key], d_attr.value) - - self.assertEqual(len(api_model.counts), len(db_model.counts)) - for a_count, d_count in zip(api_model.counts, db_model.counts): - self.assertTrue(isinstance(a_count, - api_models.OvercloudRoleCount)) - self.assertEqual(a_count.id, d_count.id) - self.assertEqual(a_count.overcloud_role_id, - d_count.overcloud_role_id) - self.assertEqual(a_count.overcloud_id, d_count.overcloud_id) - self.assertEqual(a_count.num_nodes, d_count.num_nodes) - - def test_to_db_model(self): - # Setup - api_attrs = {'key-1': 'value-1'} - - api_counts = [ - api_models.OvercloudRoleCount( - id=10, - overcloud_role_id=2, - num_nodes=50, - ), - api_models.OvercloudRoleCount( - id=11, - overcloud_role_id=3, - num_nodes=15, - ), - ] - - api_model = api_models.Overcloud( - id=1, - stack_id='stack-1', - name='name-1', - description='desc-1', - attributes=api_attrs, - counts=api_counts, - ) - - # Test - db_model = api_model.to_db_model() - - # Verify - self.assertTrue(db_model is not None) - self.assertTrue(isinstance(db_model, db_models.Overcloud)) - self.assertEqual(db_model.id, api_model.id) - self.assertEqual(db_model.stack_id, api_model.stack_id) - self.assertEqual(db_model.name, api_model.name) - self.assertEqual(db_model.description, api_model.description) - - self.assertEqual(len(db_model.attributes), len(api_model.attributes)) - for d_attr in db_model.attributes: - self.assertTrue(isinstance(d_attr, db_models.OvercloudAttribute)) - self.assertEqual(d_attr.overcloud_id, api_model.id) - self.assertEqual(d_attr.value, api_attrs[d_attr.key]) - - self.assertEqual(len(db_model.counts), len(api_model.counts)) - for d_count, a_count in zip(db_model.counts, api_model.counts): - self.assertTrue(isinstance(d_count, - db_models.OvercloudRoleCount)) - self.assertEqual(d_count.overcloud_role_id, - a_count.overcloud_role_id) - self.assertEqual(d_count.overcloud_id, api_model.id) - self.assertEqual(d_count.num_nodes, a_count.num_nodes) diff --git a/tuskar/tests/api/controllers/v1/test_overcloud.py b/tuskar/tests/api/controllers/v1/test_overcloud.py deleted file mode 100644 index b8da7ca2..00000000 --- a/tuskar/tests/api/controllers/v1/test_overcloud.py +++ /dev/null @@ -1,418 +0,0 @@ -# -# 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 mock -from pecan.testing import load_test_app - -from tuskar.api.controllers.v1 import overcloud -from tuskar.common import exception -from tuskar.db.sqlalchemy import models as db_models -from tuskar.tests import base - - -URL_OVERCLOUDS = '/v1/overclouds' - - -class OvercloudTests(base.TestCase): - - def setUp(self): - super(OvercloudTests, self).setUp() - - config_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), - '..', '..', '..', '..', 'api', 'config.py') - self.app = load_test_app(config_file) - - @mock.patch('tuskar.db.sqlalchemy.api.Connection.get_overclouds') - def test_get_all(self, mock_db_get): - # Setup - fake_attrs = [ - db_models.OvercloudAttribute(key='key1', value='value1'), - db_models.OvercloudAttribute(key='password', value='secret'), - db_models.OvercloudAttribute(key='AdminPassword', value='secret'), - ] - fake_results = [db_models.Overcloud(name='foo', attributes=fake_attrs)] - mock_db_get.return_value = fake_results - - # Test - response = self.app.get(URL_OVERCLOUDS) - result = response.json - - # Verify - self.assertEqual(response.status_int, 200) - self.assertTrue(isinstance(result, list)) - self.assertEqual(1, len(result)) - self.assertEqual(result[0]['name'], 'foo') - self.assertEqual(result[0]['attributes']['key1'], 'value1') - self.assertEqual(result[0]['attributes']['password'], '******') - self.assertEqual(result[0]['attributes']['AdminPassword'], '******') - - self.assertEqual(1, mock_db_get.call_count) - - @mock.patch('tuskar.db.sqlalchemy.api.' - 'Connection.get_overcloud_by_id') - def test_get_one(self, mock_db_get): - # Setup - fake_attrs = [ - db_models.OvercloudAttribute(key='key1', value='value1'), - db_models.OvercloudAttribute(key='password', value='secret'), - db_models.OvercloudAttribute(key='AdminPassword', value='secret'), - ] - fake_result = db_models.Overcloud(name='foo', attributes=fake_attrs) - mock_db_get.return_value = fake_result - - # Test - url = URL_OVERCLOUDS + '/' + '12345' - response = self.app.get(url) - result = response.json - - # Verify - self.assertEqual(response.status_int, 200) - self.assertEqual(result['name'], 'foo') - self.assertEqual(result['attributes']['key1'], 'value1') - self.assertEqual(result['attributes']['password'], '******') - self.assertEqual(result['attributes']['AdminPassword'], '******') - - mock_db_get.assert_called_once_with(12345) - - def test_parse_counts_overcloud_roles_explicit(self): - # Setup - overcloud_role_1 = db_models.OvercloudRole( - image_name='overcloud-compute', flavor_id='1') - - overcloud_role_2 = db_models.OvercloudRole( - image_name='overcloud-cinder-volume', flavor_id='1') - - mock_overcloud_roles = {1: overcloud_role_1, 2: overcloud_role_2} - - overcloud_role_count_1 = db_models.OvercloudRoleCount( - overcloud_role_id=1, num_nodes=5) - - overcloud_role_count_2 = db_models.OvercloudRoleCount( - overcloud_role_id=2, num_nodes=9) - - mock_counts = [overcloud_role_count_1, overcloud_role_count_2] - - # Test - result = overcloud.parse_counts_and_flavors( - mock_counts, - overcloud_roles=mock_overcloud_roles) - - # Verify - self.assertEqual(result, - ({'overcloud-compute': 5, - 'overcloud-cinder-volume': 9}, - {'overcloud-compute': '1', - 'overcloud-cinder-volume': '1'})) - - def test_get_flavor_attributes(self): - # Setup - parsed_flavors = {'fake': '5', - 'overcloud-control': '1', - 'overcloud-compute': '2', - 'overcloud-cinder-volume': '3'} - - # Test - result = overcloud.get_flavor_attributes(parsed_flavors) - - # Verify - # The flavors names are stored in template_tools, so this also tests - # it hasn't been changed. This flavor names must match what is int - # the template. - self.assertEqual(result, {'OvercloudControlFlavor': '1', - 'OvercloudComputeFlavor': '2', - 'OvercloudBlockStorageFlavor': '3'}) - - def test_filter_template_attributes(self): - # Setup - allowed_data = {'Parameters': { - 'Allowed_1': 42, - 'Allowed_2': 21, - }} - - attributes = { - 'NotAllowed_1': "Infinity", - 'Allowed_1': 42, - 'NotAllowed_2': "SubZero", - 'Allowed_2': 21, - 'NotAllowed_3': "Goro", - } - - # Test - result = overcloud.filter_template_attributes(allowed_data, attributes) - - # Verify - self.assertEqual(result, {'Allowed_1': 42, - 'Allowed_2': 21}) - - @mock.patch('tuskar.heat.template_tools.merge_templates') - @mock.patch( - 'tuskar.heat.client.HeatClient.__new__', return_value=mock.Mock(**{ - 'validate_template.return_value': { - 'Parameters': {'AdminPassword': 'unset', - 'OvercloudControlFlavor': 'unset', - 'OvercloudComputeFlavor': 'unset', - 'OvercloudBlockStorageFlavor': 'unset'}}, - }) - ) - def test_template_parameters(self, mock_heat_client, - mock_heat_merge_templates): - # Setup - mock_heat_merge_templates.return_value = None - - # Test - url = URL_OVERCLOUDS + '/' + 'template_parameters' - response = self.app.get(url) - result = response.json - - # Verify - self.assertEqual(response.status_int, 200) - self.assertEqual(result, {'AdminPassword': 'unset'}) - - @mock.patch('tuskar.heat.template_tools.merge_templates') - @mock.patch( - 'tuskar.heat.client.HeatClient.__new__', return_value=mock.Mock(**{ - 'validate_template.return_value': {}, - 'exists_stack.return_value': False, - 'create_stack.return_value': {'stack': {'id': '1'}}, - }) - ) - def test_create_stack(self, mock_heat_client, mock_heat_merge_templates): - # Setup - mock_heat_merge_templates.return_value = None - - # Test - response = overcloud.process_stack({}, {}, {}, create=True) - - # Verify - self.assertEqual(response, {'stack': {'id': '1'}}) - - @mock.patch('tuskar.heat.template_tools.merge_templates') - @mock.patch( - 'tuskar.heat.client.HeatClient.__new__', return_value=mock.Mock(**{ - 'validate_template.return_value': {}, - 'exists_stack.return_value': False, - 'create_stack.side_effect': Exception("Error"), - }) - ) - def test_create_stack_heat_exception(self, mock_heat_client, - mock_heat_merge_templates): - # Setup - mock_heat_merge_templates.return_value = None - - # Test and Verify - self.assertRaises( - exception.HeatStackCreateFailed, - overcloud.process_stack, {}, {}, {}, create=True) - - @mock.patch('tuskar.heat.template_tools.merge_templates') - @mock.patch( - 'tuskar.heat.client.HeatClient.__new__', return_value=mock.Mock(**{ - 'validate_template.return_value': {}, - 'exists_stack.return_value': True, - 'create_stack.return_value': True, - }) - ) - def test_create_stack_existing_exception(self, mock_heat_client, - mock_heat_merge_templates): - # Setup - mock_heat_merge_templates.return_value = None - - # Test and Verify - self.assertRaises( - exception.StackAlreadyCreated, overcloud.process_stack, {}, {}, {}, - create=True) - - @mock.patch('tuskar.heat.template_tools.merge_templates') - @mock.patch( - 'tuskar.heat.client.HeatClient.__new__', return_value=mock.Mock(**{ - 'validate_template.side_effect': Exception("Error"), - 'exists_stack.return_value': False, - 'create_stack.return_value': True, - }) - ) - def test_create_stack_not_valid_exception(self, mock_heat_client, - mock_heat_merge_templates): - # Setup - mock_heat_merge_templates.return_value = None - - # Test and Verify - self.assertRaises( - exception.HeatTemplateValidateFailed, overcloud.process_stack, - {}, {}, {}, create=True) - - @mock.patch('tuskar.db.sqlalchemy.api.Connection.get_overcloud_roles') - @mock.patch('tuskar.api.controllers.v1.overcloud.process_stack') - @mock.patch('tuskar.db.sqlalchemy.api.Connection.create_overcloud') - def test_post(self, mock_db_create, mock_process_stack, - mock_get_overcloud_roles): - # Setup - create_me = {'name': 'new'} - - fake_created = db_models.Overcloud(name='created') - mock_db_create.return_value = fake_created - mock_process_stack.return_value = {'stack': {'id': '1'}} - mock_get_overcloud_roles.return_value = [ - mock.Mock(**{'id.return_value': 1, }), - mock.Mock(**{'id.return_value': 2, })] - - # Test - response = self.app.post_json(URL_OVERCLOUDS, params=create_me) - result = response.json - - # Verify - self.assertEqual(response.status_int, 201) - self.assertEqual(result['name'], fake_created.name) - - self.assertEqual(1, mock_db_create.call_count) - db_create_model = mock_db_create.call_args[0][0] - self.assertTrue(isinstance(db_create_model, - db_models.Overcloud)) - self.assertEqual(db_create_model.name, create_me['name']) - - @mock.patch('tuskar.heat.template_tools.merge_templates') - @mock.patch( - 'tuskar.heat.client.HeatClient.__new__', return_value=mock.Mock(**{ - 'validate_template.return_value': {}, - 'exists_stack.return_value': True, - 'update_stack.return_value': {'stack': {'id': '1'}}, - }) - ) - def test_update_stack(self, mock_heat_client, mock_heat_merge_templates): - # Setup - mock_heat_merge_templates.return_value = None - - # Test - response = overcloud.process_stack({}, {}, {}) - - # Verify - self.assertEqual(response, {'stack': {'id': '1'}}) - - @mock.patch('tuskar.heat.template_tools.merge_templates') - @mock.patch( - 'tuskar.heat.client.HeatClient.__new__', return_value=mock.Mock(**{ - 'validate_template.return_value': {}, - 'exists_stack.return_value': True, - 'update_stack.side_effect': Exception("Error") - }) - ) - def test_update_stack_heat_exception(self, mock_heat_client, - mock_heat_merge_templates): - # Setup - mock_heat_merge_templates.return_value = None - - # Test and Verify - self.assertRaises( - exception.HeatStackUpdateFailed, overcloud.process_stack, {}, - {}, {}) - - @mock.patch('tuskar.heat.template_tools.merge_templates') - @mock.patch( - 'tuskar.heat.client.HeatClient.__new__', return_value=mock.Mock(**{ - 'validate_template.return_value': {}, - 'exists_stack.return_value': False, - 'create_stack.return_value': True, - }) - ) - def test_update_stack_not_existing_exception(self, mock_heat_client, - mock_heat_merge_templates): - # Setup - mock_heat_merge_templates.return_value = None - - # Test and Verify - self.assertRaises( - exception.StackNotFound, overcloud.process_stack, {}, {}, {}) - - @mock.patch('tuskar.heat.template_tools.merge_templates') - @mock.patch( - 'tuskar.heat.client.HeatClient.__new__', return_value=mock.Mock(**{ - 'validate_template.side_effect': Exception("Error"), - 'exists_stack.return_value': True, - 'create_stack.return_value': True, - }) - ) - def test_update_stack_not_valid_exception(self, mock_heat_client, - mock_heat_merge_templates): - # Setup - mock_heat_merge_templates.return_value = None - - # Test and Verify - self.assertRaises( - exception.HeatTemplateValidateFailed, overcloud.process_stack, - {}, {}, {}) - - @mock.patch('tuskar.api.controllers.v1.overcloud.process_stack') - @mock.patch('tuskar.db.sqlalchemy.api.Connection.update_overcloud') - def test_put(self, mock_db_update, mock_process_stack): - # Setup - changes = {'name': 'updated'} - - overcloud_role_1 = db_models.OvercloudRole( - image_name='overcloud-compute', flavor_id='1') - - overcloud_role_2 = db_models.OvercloudRole( - image_name='overcloud-cinder-volume', flavor_id='1') - - overcloud_role_count_1 = db_models.OvercloudRoleCount( - overcloud_role_id=1, num_nodes=5, overcloud_role=overcloud_role_1) - - overcloud_role_count_2 = db_models.OvercloudRoleCount( - overcloud_role_id=2, num_nodes=9, overcloud_role=overcloud_role_2) - - attribute_1 = db_models.OvercloudAttribute( - overcloud_id=1, key='name', value='updated') - - fake_updated = db_models.Overcloud(name='after-update', - attributes=[attribute_1], - counts=[overcloud_role_count_1, - overcloud_role_count_2]) - mock_db_update.return_value = fake_updated - mock_process_stack.return_value = None - - # Test - url = URL_OVERCLOUDS + '/' + '12345' - response = self.app.put_json(url, params=changes) - result = response.json - - # Verify - self.assertEqual(response.status_int, 200) - self.assertEqual(result['name'], fake_updated.name) - - self.assertEqual(1, mock_db_update.call_count) - db_update_model = mock_db_update.call_args[0][0] - self.assertTrue(isinstance(db_update_model, - db_models.Overcloud)) - self.assertEqual(db_update_model.id, 12345) - self.assertEqual(db_update_model.name, changes['name']) - - mock_process_stack.assert_called_once_with( - {'name': 'updated'}, [overcloud_role_count_1, - overcloud_role_count_2], {}) - - @mock.patch('tuskar.db.sqlalchemy.api.' - 'Connection.delete_overcloud_by_id') - @mock.patch( - 'tuskar.heat.client.HeatClient.__new__', return_value=mock.Mock(**{ - 'delete_stack.return_value': True, - }) - ) - def test_delete(self, mock_heat_client, mock_db_delete): - # Test - url = URL_OVERCLOUDS + '/' + '12345' - response = self.app.delete(url) - - # Verify - self.assertEqual(response.status_int, 204) - - mock_db_delete.assert_called_once_with(12345) diff --git a/tuskar/tests/api/controllers/v1/test_overcloud_roles.py b/tuskar/tests/api/controllers/v1/test_overcloud_roles.py deleted file mode 100644 index f4b0d990..00000000 --- a/tuskar/tests/api/controllers/v1/test_overcloud_roles.py +++ /dev/null @@ -1,127 +0,0 @@ -# -# 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 mock -from pecan.testing import load_test_app - -from tuskar.db.sqlalchemy import models as db_models -from tuskar.tests import base - - -URL_ROLES = '/v1/overcloud_roles' - - -class OvercloudRolesTests(base.TestCase): - - def setUp(self): - super(OvercloudRolesTests, self).setUp() - - config_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), - '..', '..', '..', '..', 'api', 'config.py') - self.app = load_test_app(config_file) - - @mock.patch('tuskar.db.sqlalchemy.api.Connection.get_overcloud_roles') - def test_get_all(self, mock_db_get): - # Setup - fake_results = [db_models.OvercloudRole(name='foo')] - mock_db_get.return_value = fake_results - - # Test - response = self.app.get(URL_ROLES) - result = response.json - - # Verify - self.assertEqual(response.status_int, 200) - self.assertTrue(isinstance(result, list)) - self.assertEqual(1, len(result)) - self.assertEqual(result[0]['name'], 'foo') - - self.assertEqual(1, mock_db_get.call_count) - - @mock.patch('tuskar.db.sqlalchemy.api.' - 'Connection.get_overcloud_role_by_id') - def test_get_one(self, mock_db_get): - # Setup - fake_result = db_models.OvercloudRole(name='foo') - mock_db_get.return_value = fake_result - - # Test - url = URL_ROLES + '/' + '12345' - response = self.app.get(url) - result = response.json - - # Verify - self.assertEqual(response.status_int, 200) - self.assertEqual(result['name'], 'foo') - - mock_db_get.assert_called_once_with(12345) - - @mock.patch('tuskar.db.sqlalchemy.api.Connection.create_overcloud_role') - def test_post(self, mock_db_create): - # Setup - create_me = {'name': 'new'} - - fake_created = db_models.OvercloudRole(name='created') - mock_db_create.return_value = fake_created - - # Test - response = self.app.post_json(URL_ROLES, params=create_me) - result = response.json - - # Verify - self.assertEqual(response.status_int, 201) - self.assertEqual(result['name'], fake_created.name) - - self.assertEqual(1, mock_db_create.call_count) - db_create_model = mock_db_create.call_args[0][0] - self.assertTrue(isinstance(db_create_model, - db_models.OvercloudRole)) - self.assertEqual(db_create_model.name, create_me['name']) - - @mock.patch('tuskar.db.sqlalchemy.api.Connection.update_overcloud_role') - def test_put(self, mock_db_update): - # Setup - changes = {'name': 'updated'} - - fake_updated = db_models.OvercloudRole(name='after-update') - mock_db_update.return_value = fake_updated - - # Test - url = URL_ROLES + '/' + '12345' - response = self.app.put_json(url, params=changes) - result = response.json - - # Verify - self.assertEqual(response.status_int, 200) - self.assertEqual(result['name'], fake_updated.name) - - self.assertEqual(1, mock_db_update.call_count) - db_update_model = mock_db_update.call_args[0][0] - self.assertTrue(isinstance(db_update_model, - db_models.OvercloudRole)) - self.assertEqual(db_update_model.id, 12345) - self.assertEqual(db_update_model.name, changes['name']) - - @mock.patch('tuskar.db.sqlalchemy.api.' - 'Connection.delete_overcloud_role_by_id') - def test_delete(self, mock_db_delete): - # Test - url = URL_ROLES + '/' + '12345' - response = self.app.delete(url) - - # Verify - self.assertEqual(response.status_int, 204) - - mock_db_delete.assert_called_once_with(12345) diff --git a/tuskar/tests/api/controllers/v2/__init__.py b/tuskar/tests/api/controllers/v2/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tuskar/tests/api/controllers/v2/test_plans.py b/tuskar/tests/api/controllers/v2/test_plans.py deleted file mode 100644 index 9217756c..00000000 --- a/tuskar/tests/api/controllers/v2/test_plans.py +++ /dev/null @@ -1,263 +0,0 @@ -# -# 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 mock -from pecan.testing import load_test_app - -from tuskar.manager import models as manager_models -from tuskar.storage import exceptions as storage_exceptions -from tuskar.storage.exceptions import NameAlreadyUsed -from tuskar.tests import base - - -URL_PLANS = '/v2/plans' - - -class PlansTests(base.TestCase): - - def setUp(self): - super(PlansTests, self).setUp() - - config_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), - '..', '..', '..', '..', 'api', 'config.py') - self.app = load_test_app(config_file) - - @mock.patch('tuskar.manager.plan.PlansManager.list_plans') - def test_get_all(self, mock_list): - # Setup - mock_list.return_value = [ - manager_models.DeploymentPlan('a', 'n1', 'd1'), - manager_models.DeploymentPlan('b', 'n2', 'd2'), - ] - - # Test - response = self.app.get(URL_PLANS) - result = response.json - - # Verify - self.assertEqual(1, mock_list.call_count) - self.assertEqual(response.status_int, 200) - self.assertTrue(isinstance(result, list)) - self.assertEqual(2, len(result)) - self.assertEqual(result[0]['name'], 'n1') - self.assertEqual(result[1]['name'], 'n2') - - @mock.patch('tuskar.manager.plan.PlansManager.list_plans') - def test_get_all_empty(self, mock_list): - # Setup - mock_list.return_value = [] - - # Test - response = self.app.get(URL_PLANS) - result = response.json - - # Verify - self.assertEqual(1, mock_list.call_count) - self.assertEqual(response.status_int, 200) - self.assertTrue(isinstance(result, list)) - self.assertEqual(0, len(result)) - - @mock.patch('tuskar.manager.plan.PlansManager.retrieve_plan') - def test_get_one(self, mock_retrieve): - # Setup - p = manager_models.DeploymentPlan('a', 'n', 'd') - mock_retrieve.return_value = p - - # Test - url = URL_PLANS + '/' + 'qwerty12345' - response = self.app.get(url) - result = response.json - - # Verify - mock_retrieve.assert_called_once_with('qwerty12345') - self.assertEqual(response.status_int, 200) - self.assertEqual(result['name'], 'n') - - @mock.patch('tuskar.manager.plan.PlansManager.retrieve_plan') - def test_get_one_invalid_uuid(self, mock_retrieve): - # Setup - mock_retrieve.side_effect = storage_exceptions.UnknownUUID() - - # Test - url = URL_PLANS + '/' + 'qwerty12345' - response = self.app.get(url, status=404) - - # Verify - mock_retrieve.assert_called_once_with('qwerty12345') - self.assertEqual(response.status_int, 404) - - @mock.patch('tuskar.manager.plan.PlansManager.retrieve_plan') - def test_get_one_with_parameters(self, mock_retrieve): - # Setup - p = manager_models.DeploymentPlan('a', 'n', 'd') - p.add_parameters( - manager_models.PlanParameter( - name="Param 1", label="1", default=2, hidden=False, - description="1", value=1, param_type='number', constraints=''), - manager_models.PlanParameter( - name="Param 2", label="2", default=['a', ], hidden=False, - description="2", value=['a', 'b'], - param_type='comma_delimited_list', constraints=''), - manager_models.PlanParameter( - name="Param 3", label="3", default={'a': 2}, hidden=False, - description="3", value={'a': 1}, param_type='json', - constraints=''), - ) - mock_retrieve.return_value = p - - # Test - url = URL_PLANS + '/' + 'qwerty12345' - response = self.app.get(url) - result = response.json - - # Verify - mock_retrieve.assert_called_once_with('qwerty12345') - self.assertEqual(response.status_int, 200) - self.assertEqual(result['name'], 'n') - - @mock.patch('tuskar.manager.plan.PlansManager.delete_plan') - def test_delete(self, mock_delete): - # Test - url = URL_PLANS + '/' + 'qwerty12345' - response = self.app.delete(url) - - # Verify - mock_delete.assert_called_once_with('qwerty12345') - self.assertEqual(response.status_int, 204) - - @mock.patch('tuskar.manager.plan.PlansManager.delete_plan') - def test_delete_invalid_uuid(self, mock_delete): - # Setup - mock_delete.side_effect = storage_exceptions.UnknownUUID() - - # Test - url = URL_PLANS + '/' + 'qwerty12345' - response = self.app.delete(url, status=404) - - # Verify - mock_delete.assert_called_once_with('qwerty12345') - self.assertEqual(response.status_int, 404) - - @mock.patch('tuskar.manager.plan.PlansManager.create_plan') - def test_post_no_description(self, mock_create): - # Setup - p = manager_models.DeploymentPlan('a', 'n', 'd') - mock_create.return_value = p - - # Test - plan_data = {'name': 'new'} - response = self.app.post_json(URL_PLANS, params=plan_data) - result = response.json - - # Verify - mock_create.assert_called_once_with('new', None) - self.assertEqual(response.status_int, 201) - self.assertEqual(result['uuid'], p.uuid) - self.assertEqual(result['name'], p.name) - self.assertEqual(result['description'], p.description) - - @mock.patch('tuskar.manager.plan.PlansManager.create_plan') - def test_post(self, mock_create): - # Setup - p = manager_models.DeploymentPlan('a', 'n', 'd') - mock_create.return_value = p - - # Test - plan_data = {'name': 'new', 'description': 'desc'} - response = self.app.post_json(URL_PLANS, params=plan_data) - result = response.json - - # Verify - mock_create.assert_called_once_with('new', 'desc') - self.assertEqual(response.status_int, 201) - self.assertEqual(result['uuid'], p.uuid) - self.assertEqual(result['name'], p.name) - self.assertEqual(result['description'], p.description) - - @mock.patch('tuskar.manager.plan.PlansManager.create_plan') - def test_post_conflict(self, mock_create): - # Setup - mock_create.side_effect = NameAlreadyUsed( - "A master_template with the name 'test.yaml' already exists") - - # Test - plan_data = {'name': 'new', 'description': 'desc'} - response = self.app.post_json(URL_PLANS, params=plan_data, status=409) - - # Verify - mock_create.assert_called_once_with('new', 'desc') - self.assertEqual(response.status_int, 409) - - @mock.patch('tuskar.manager.plan.PlansManager.package_templates') - def test_templates(self, mock_package): - # Setup - mock_package.return_value = {} - - # Test - url = URL_PLANS + '/' + 'foo' + '/' + 'templates' - response = self.app.get(url) - result = response.body - - # Verify - self.assertEqual(response.status_int, 200) - self.assertEqual(result, '{}') - - @mock.patch('tuskar.manager.plan.PlansManager.package_templates') - def test_templates_missing_plan(self, mock_package): - # Setup - mock_package.side_effect = storage_exceptions.UnknownUUID() - - # Test - url = URL_PLANS + '/' + 'foo' + '/' + 'templates' - response = self.app.get(url, status=404) - - # Verify - self.assertEqual(response.status_int, 404) - - @mock.patch('tuskar.manager.plan.PlansManager.set_parameter_values') - def test_patch(self, mock_set): - # Setup - p = manager_models.DeploymentPlan('a', 'n', 'd') - mock_set.return_value = p - - # Test - values = [{'name': 'foo', 'value': 'bar'}] - url = URL_PLANS + '/' + 'qwerty12345' - response = self.app.patch_json(url, values) - result = response.json - - # Verify - self.assertEqual(1, mock_set.call_count) - self.assertEqual(mock_set.call_args[0][0], 'qwerty12345') - self.assertEqual(mock_set.call_args[0][1][0].name, 'foo') - self.assertEqual(mock_set.call_args[0][1][0].value, 'bar') - self.assertEqual(response.status_int, 201) - self.assertEqual(result['name'], p.name) - - @mock.patch('tuskar.manager.plan.PlansManager.set_parameter_values') - def test_patch_missing_plan(self, mock_set): - # Setup - mock_set.side_effect = storage_exceptions.UnknownUUID() - - # Test - values = [{'name': 'foo', 'value': 'bar'}] - url = URL_PLANS + '/' + 'qwerty12345' - response = self.app.patch_json(url, values, status=404) - - # Verify - self.assertEqual(mock_set.call_args[0][0], 'qwerty12345') - self.assertEqual(mock_set.call_args[0][1][0].name, 'foo') - self.assertEqual(mock_set.call_args[0][1][0].value, 'bar') - self.assertEqual(response.status_int, 404) diff --git a/tuskar/tests/api/controllers/v2/test_roles.py b/tuskar/tests/api/controllers/v2/test_roles.py deleted file mode 100644 index fbe0fa30..00000000 --- a/tuskar/tests/api/controllers/v2/test_roles.py +++ /dev/null @@ -1,133 +0,0 @@ -# -# 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 mock -from pecan.testing import load_test_app - -from tuskar.manager import models as manager_models -from tuskar.storage import exceptions as storage_exceptions -from tuskar.tests import base - - -URL_ROLES = '/v2/roles' -URL_PLAN_ROLES = '/v2/plans/plan_uuid/roles' - - -class RolesTests(base.TestCase): - - def setUp(self): - super(RolesTests, self).setUp() - - config_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), - '..', '..', '..', '..', 'api', 'config.py') - self.app = load_test_app(config_file) - - @mock.patch('tuskar.manager.role.RoleManager.list_roles') - def test_get_all(self, mock_list): - # Setup - mock_list.return_value = [ - manager_models.Role('a', 'n1', 1, 'd1', 't1'), - manager_models.Role('b', 'n2', 2, 'd2', 't2'), - ] - - # Test - response = self.app.get(URL_ROLES) - result = response.json - - # Verify - mock_list.assert_called_once_with(only_latest=False) - self.assertEqual(response.status_int, 200) - self.assertTrue(isinstance(result, list)) - self.assertEqual(2, len(result)) - self.assertEqual(result[0]['uuid'], 'a') - self.assertEqual(result[0]['name'], 'n1') - self.assertEqual(result[0]['description'], 'd1') - self.assertEqual(result[1]['uuid'], 'b') - self.assertEqual(result[1]['name'], 'n2') - self.assertEqual(result[1]['description'], 'd2') - - @mock.patch('tuskar.manager.plan.PlansManager.add_role_to_plan') - def test_post(self, mock_add): - # Setup - p = manager_models.DeploymentPlan('a', 'n', 'd') - mock_add.return_value = p - - # Test - role_data = {'uuid': 'qwerty12345'} - response = self.app.post_json(URL_PLAN_ROLES, params=role_data) - result = response.json - - # Verify - mock_add.assert_called_once_with('plan_uuid', 'qwerty12345') - self.assertEqual(response.status_int, 201) - self.assertEqual(result['uuid'], 'a') - self.assertEqual(result['name'], 'n') - - @mock.patch('tuskar.manager.plan.PlansManager.add_role_to_plan') - def test_post_duplicate(self, mock_add): - # Setup - mock_add.side_effect = ValueError() - - # Test - role_data = {'uuid': 'qwerty12345'} - response = self.app.post_json(URL_PLAN_ROLES, params=role_data, - status=409) - - # Verify - mock_add.assert_called_once_with('plan_uuid', 'qwerty12345') - self.assertEqual(response.status_int, 409) - - @mock.patch('tuskar.manager.plan.PlansManager.add_role_to_plan') - def test_post_unkown_uuid(self, mock_add): - # Setup - mock_add.side_effect = storage_exceptions.UnknownUUID() - - # Test - role_data = {'uuid': 'qwerty12345'} - response = self.app.post_json(URL_PLAN_ROLES, params=role_data, - status=404) - - # Verify - mock_add.assert_called_once_with('plan_uuid', 'qwerty12345') - self.assertEqual(response.status_int, 404) - - @mock.patch('tuskar.manager.plan.PlansManager.remove_role_from_plan') - def test_delete(self, mock_remove): - # Setup - p = manager_models.DeploymentPlan('a', 'n', 'd') - mock_remove.return_value = p - - # Test - response = self.app.delete_json(URL_PLAN_ROLES + '/role_uuid') - result = response.json - - # Verify - mock_remove.assert_called_once_with('plan_uuid', 'role_uuid') - self.assertEqual(response.status_int, 200) - self.assertEqual(result['uuid'], 'a') - self.assertEqual(result['name'], 'n') - - @mock.patch('tuskar.manager.plan.PlansManager.remove_role_from_plan') - def test_delete_unkown_uuid(self, mock_remove): - # Setup - mock_remove.side_effect = storage_exceptions.UnknownUUID() - - # Test - response = self.app.delete_json(URL_PLAN_ROLES + '/qwerty12345', - status=404) - - # Verify - mock_remove.assert_called_once_with('plan_uuid', 'qwerty12345') - self.assertEqual(response.status_int, 404) diff --git a/tuskar/tests/api/test_renderers.py b/tuskar/tests/api/test_renderers.py deleted file mode 100644 index cede995c..00000000 --- a/tuskar/tests/api/test_renderers.py +++ /dev/null @@ -1,94 +0,0 @@ -# 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 mock import Mock -from mock import patch -from wsme.api import Response - -from tuskar.api.renderers import JSONRenderer -from tuskar.tests.api.api import FunctionalTest - - -class JSONRendererTests(FunctionalTest): - - def setUp(self): - super(JSONRendererTests, self).setUp() - self.renderer = JSONRenderer(None, None) - - @patch('wsme.rest.json.encode_result') - def test_2XX_response(self, encode_result): - """Test a standard response dict containing the type and result set.""" - - datatype = Mock() - result = Mock() - - self.renderer.render('', { - 'datatype': datatype, - 'result': result - }) - - # Verify the wsme json result encoder (encode_result) is called with - # the correct values. - encode_result.assert_called_once_with(result, datatype) - - @patch('pecan.response') - @patch('wsme.rest.json.encode_error') - def test_pecan_response(self, encode_error, response): - """Test that a wsme Response instance renders correctly.""" - - # Create a Mock that has the properties we need of the Response class. - datatype = Mock() - result = Mock(spec=Response) - result.status_code = 418 - result.obj = Mock() - result.obj.faultstring = "Failed" - result.obj.debuginfo = "Information" - - # Test - self.renderer.render('', { - 'datatype': datatype, - 'result': result - }) - - # Verify the wsme json error encoder (encode_error) is called with the - # correctly structured response - encode_error.assert_called_once_with(None, { - 'identityFault': { - 'message': 'Failed', - 'code': 418, - 'details': 'Information' - } - }) - - @patch('wsme.rest.json.encode_error') - def test_500_response(self, encode_error): - """Test that internal Exceptions return correctly structures JSON.""" - - # Recreate the dictionary provided by pecan on user code exceptions. - namespace = { - 'debuginfo': None, - 'faultcode': 'Server', - 'faultstring': "Exception message" - } - - # Test - self.renderer.render('', namespace) - - # Verify the wsme json error encoder (encode_error) is called with the - # correctly structured response - encode_error.assert_called_once_with(None, { - 'identityFault': { - 'message': "Exception message", - 'code': 500, - 'details': None - } - }) diff --git a/tuskar/tests/base.py b/tuskar/tests/base.py deleted file mode 100644 index e388a801..00000000 --- a/tuskar/tests/base.py +++ /dev/null @@ -1,169 +0,0 @@ -# Copyright 2010 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# All Rights Reserved. -# -# 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. - -"""Base classes for our unit tests. - -Allows overriding of config for use of fakes, and some black magic for -inline callbacks. - -""" - -import eventlet -eventlet.monkey_patch(os=False) - -import os -import shutil - -import fixtures -from oslo_config import cfg -import testtools -import unittest2 - -from tuskar.common import paths -from tuskar.db import migration -from tuskar.db.sqlalchemy import api as sqla_api -from tuskar.openstack.common import log as logging -from tuskar.tests import conf_fixture - - -test_opts = [ - cfg.StrOpt('sqlite_clean_db', - default='clean.sqlite', - help='File name of clean sqlite db'), -] - -CONF = cfg.CONF -CONF.register_opts(test_opts, group='database') -CONF.set_override('use_stderr', False) - -logging.setup('tuskar') - -_DB_CACHE = None - - -class Database(fixtures.Fixture): - - def __init__(self, db_api, db_migrate, sql_connection, sqlite_db, - sqlite_clean_db): - self.sql_connection = sql_connection - self.sqlite_db = sqlite_db - self.sqlite_clean_db = sqlite_clean_db - - self.engine = db_api.get_engine() - self.engine.dispose() - conn = self.engine.connect() - if sql_connection == "sqlite://": - if db_migrate.db_version() > db_migrate.INIT_VERSION: - return - else: - testdb = paths.state_path_rel(sqlite_db) - if os.path.exists(testdb): - return - db_migrate.db_sync() - self.post_migrations() - if sql_connection == "sqlite://": - conn = self.engine.connect() - self._DB = "".join(line for line in conn.connection.iterdump()) - self.engine.dispose() - else: - cleandb = paths.state_path_rel(sqlite_clean_db) - shutil.copyfile(testdb, cleandb) - - def setUp(self): - super(Database, self).setUp() - - if self.sql_connection == "sqlite://": - conn = self.engine.connect() - conn.connection.executescript(self._DB) - self.addCleanup(self.engine.dispose) - else: - shutil.copyfile(paths.state_path_rel(self.sqlite_clean_db), - paths.state_path_rel(self.sqlite_db)) - - def post_migrations(self): - """Any addition steps that are needed outside of the migrations.""" - - -class TestingException(Exception): - pass - - -# The unittest2.TestCase mixin provides assertRegexpMatches, which isn't -# available on Python 2.6 by default. -class TestCase(testtools.TestCase, unittest2.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') == 'True' or - os.environ.get('OS_STDOUT_CAPTURE') == '1'): - stdout = self.useFixture(fixtures.StringStream('stdout')).stream - self.useFixture(fixtures.MonkeyPatch('sys.stdout', stdout)) - if (os.environ.get('OS_STDERR_CAPTURE') == 'True' or - os.environ.get('OS_STDERR_CAPTURE') == '1'): - stderr = self.useFixture(fixtures.StringStream('stderr')).stream - self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr)) - - self.log_fixture = self.useFixture(fixtures.FakeLogger()) - self.useFixture(conf_fixture.ConfFixture(CONF)) - - global _DB_CACHE - if not _DB_CACHE: - _DB_CACHE = Database( - sqla_api, migration, - sql_connection=CONF.database.connection, - sqlite_db=CONF.database.sqlite_db, - sqlite_clean_db=CONF.database.sqlite_clean_db - ) - self.useFixture(_DB_CACHE) - - self.addCleanup(self._clear_attrs) - self.useFixture(fixtures.EnvironmentVariable('http_proxy')) - CONF.set_override('fatal_exception_format_errors', True) - - def _clear_attrs(self): - # Delete attributes that don't start with _ so they don't pin - # memory around unnecessarily for the duration of the test - # suite - for key in [k for k in self.__dict__.keys() if k[0] != '_']: - del self.__dict__[key] - - def config(self, **kw): - """Override config options for a test.""" - group = kw.pop('group', None) - for k, v in kw.iteritems(): - CONF.set_override(k, v, group) - - def path_get(self, project_file=None): - root = os.path.abspath( - os.path.join(os.path.dirname(__file__), '..', '..',)) - - if project_file: - return os.path.join(root, project_file) - else: - return root diff --git a/tuskar/tests/cmd/__init__.py b/tuskar/tests/cmd/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tuskar/tests/cmd/test_api.py b/tuskar/tests/cmd/test_api.py deleted file mode 100644 index 32b397bb..00000000 --- a/tuskar/tests/cmd/test_api.py +++ /dev/null @@ -1,39 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2013 Hewlett-Packard Development Company, L.P. -# All Rights Reserved. -# -# 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 mock import ANY -from mock import patch - -from tuskar.cmd.api import main -from tuskar.tests.base import TestCase - - -class ApiCommandTests(TestCase): - - @patch('wsgiref.simple_server.make_server') - def test_main(self, mock_make_server): - - try: - main(['test.py', '--config-file', 'etc/tuskar/tuskar.conf.sample']) - - # Catch BaseException's and re-raise as Exception, otherwise - # exceptions raised by the argument parser code wont be caught and - # create a cryptic test failure. - except BaseException as e: - raise Exception(e) - - mock_make_server.assert_called_once_with('0.0.0.0', 8585, ANY) diff --git a/tuskar/tests/cmd/test_dbsync.py b/tuskar/tests/cmd/test_dbsync.py deleted file mode 100644 index 5fda1ba4..00000000 --- a/tuskar/tests/cmd/test_dbsync.py +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2013 Hewlett-Packard Development Company, L.P. -# All Rights Reserved. -# -# 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 mock import patch - -from tuskar.cmd.dbsync import main -from tuskar.tests.base import TestCase - - -class DBSyncCommandTests(TestCase): - - @patch('tuskar.db.migration.db_sync') - def test_main(self, mock_db_sync): - - try: - main(['test.py', '--config-file', 'etc/tuskar/tuskar.conf.sample']) - - # Catch BaseException's and re-raise as Exception, otherwise - # exceptions raised by the argument parser code wont be caught and - # create a cryptic test failure. - except BaseException as e: - raise Exception(e) - - mock_db_sync.assert_called_once_with() diff --git a/tuskar/tests/cmd/test_delete_roles.py b/tuskar/tests/cmd/test_delete_roles.py deleted file mode 100644 index 5e1b88ea..00000000 --- a/tuskar/tests/cmd/test_delete_roles.py +++ /dev/null @@ -1,84 +0,0 @@ -# -*- encoding: 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. - -from mock import patch - -from tuskar.cmd import delete_roles -from tuskar.tests.base import TestCase - - -class DeleteRoleTests(TestCase): - - @patch('tuskar.storage.delete_roles.delete_roles') - def test_main_single_uuid(self, mock_delete): - # Setup - cmd = """ tuskar-delete-roles --uuid foo """ - - # Test - delete_roles.main(argv=(cmd.split())) - - # Verify - mock_delete.assert_called_once_with(['foo'], noop=False) - - @patch('tuskar.storage.delete_roles.delete_roles') - def test_main_multiple_uuids(self, mock_delete): - # Setup - cmd = """ tuskar-delete-roles --uuid foo --uuid bar """ - - # Test - delete_roles.main(argv=(cmd.split())) - - # Verify - mock_delete.assert_called_once_with(['foo', 'bar'], noop=False) - - @patch('tuskar.storage.delete_roles.delete_all_roles') - def test_main_all(self, mock_delete): - # Setup - cmd = """ tuskar-delete-roles --all """ - - # Test - delete_roles.main(argv=(cmd.split())) - - # Verify - mock_delete.assert_called_once_with(noop=False) - - @patch('tuskar.storage.delete_roles.delete_all_roles') - def test_main_all_dryrun(self, mock_delete): - # Setup - cmd = """ tuskar-delete-roles --all --dryrun """ - - # Test - delete_roles.main(argv=(cmd.split())) - - # Verify - mock_delete.assert_called_once_with(noop=True) - - @patch('tuskar.storage.delete_roles.delete_roles') - def test_main_uuid_dryrun(self, mock_delete): - # Setup - cmd = """ tuskar-delete-roles --uuid foo --dryrun """ - - # Test - delete_roles.main(argv=(cmd.split())) - - # Verify - mock_delete.assert_called_once_with(['foo'], noop=True) - - @patch('tuskar.storage.delete_roles.delete_roles') - def test_main_no_roles(self, mock_delete): - # Setup - cmd = """ tuskar-delete-roles """ - - # Test - self.assertRaises(SystemExit, delete_roles.main, argv=(cmd.split())) diff --git a/tuskar/tests/cmd/test_load_roles.py b/tuskar/tests/cmd/test_load_roles.py deleted file mode 100644 index ff598e92..00000000 --- a/tuskar/tests/cmd/test_load_roles.py +++ /dev/null @@ -1,110 +0,0 @@ -# -*- encoding: 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. - -from mock import call -from mock import patch - -from tuskar.cmd import load_role -from tuskar.cmd import load_roles -from tuskar.cmd import load_seed -from tuskar.tests.base import TestCase - - -class LoadRoleTests(TestCase): - - ROLES = """ -r role_name1.yaml -r /path/role_name2.yaml - --role /path1/role_name3.yaml """ - - ROLE_EXTRA = """ --role-extra /path/metadata/compute_data.yaml - -re /path/metadata/common_data.yaml """ - - ENV_DATA = """ -resource_registry: - OS::TripleO::Another: required_file.yaml - """ - - @patch('tuskar.storage.load_utils.load_file', return_value="YAML") - @patch('tuskar.cmd.load_roles._print_names') - def test_main(self, mock_print, mock_read): - main_args = " --master-seed=seed.yaml %s %s" % ( - self.ROLES, self.ROLE_EXTRA) - expected_res = ['role_name1', 'role_name2', 'role_name3', - 'extra_compute_data_yaml', 'extra_common_data_yaml'] - - # test - load_roles.main(argv=(main_args).split()) - - # verify - self.assertEqual([call('Created', expected_res)], - mock_print.call_args_list) - - def test_load_seed_invalid_args(self): - main_args = "tuskar-load-seed" - self.assertRaises(SystemExit, load_seed.main, main_args.split()) - - main_args = "tuskar-load-seed --master-seed=seed.yaml" - self.assertRaises(SystemExit, load_seed.main, main_args.split()) - - main_args = "tuskar-load-seed --resource-registry=registry.yaml" - self.assertRaises(SystemExit, load_seed.main, main_args.split()) - - @patch('tuskar.storage.load_utils.load_file', return_value="YAML") - @patch('tuskar.storage.load_roles.load_file', return_value=ENV_DATA) - @patch('tuskar.cmd.load_seed._print_names') - def test_load_seed(self, mock_print, mock_read, mock_read2): - main_args = ("tuskar-load-seed --master-seed=seed.yaml" - " --resource-registry=registry.yaml") - expected_created = ['_master_seed', '_registry', 'required_file.yaml'] - - load_seed.main(argv=(main_args).split()) - - self.assertEqual([call('Created', expected_created)], - mock_print.call_args_list) - - @patch('tuskar.storage.load_utils.load_file', return_value="YAML") - @patch('tuskar.cmd.load_role._print_names') - def test_load_role(self, mock_print, mock_read): - main_args = (" tuskar-load-role -n Compute" - " --filepath /path/to/puppet/compute-puppet.yaml " - " --extra-data /path/to/puppet/hieradata/compute.yaml " - " --extra-data /path/to/puppet/hieradata/common.yaml ") - expected_res = ['extra_compute_yaml', 'extra_common_yaml', 'Compute'] - - load_role.main(argv=(main_args).split()) - - self.assertEqual([call('Created', expected_res)], - mock_print.call_args_list) - - @patch('tuskar.storage.load_utils.load_file', return_value="YAML") - @patch('tuskar.cmd.load_role._print_names') - def test_load_role_no_name(self, mock_print, mock_read): - main_args = (" tuskar-load-role" - " -f /path/to/puppet/compute-puppet.yaml " - " --extra-data /path/to/puppet/hieradata/compute.yaml " - " --extra-data /path/to/puppet/hieradata/common.yaml ") - expected_res = ['extra_compute_yaml', 'extra_common_yaml', - 'compute-puppet'] - - load_role.main(argv=(main_args).split()) - - self.assertEqual([call('Created', expected_res)], - mock_print.call_args_list) - - @patch('tuskar.storage.load_utils.load_file', return_value="YAML") - @patch('tuskar.cmd.load_role._print_names') - def test_load_role_no_path(self, mock_print, mock_read): - main_args = (" tuskar-load-role" - " --extra-data /path/to/puppet/hieradata/compute.yaml " - " --extra-data /path/to/puppet/hieradata/common.yaml ") - self.assertRaises(SystemExit, load_role.main, (main_args.split())) diff --git a/tuskar/tests/conf_fixture.py b/tuskar/tests/conf_fixture.py deleted file mode 100644 index 9ca710c2..00000000 --- a/tuskar/tests/conf_fixture.py +++ /dev/null @@ -1,40 +0,0 @@ -# Copyright 2010 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# All Rights Reserved. -# -# 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 fixtures -from oslo_config import cfg - -from tuskar.common import config - -CONF = cfg.CONF -CONF.import_opt('use_ipv6', 'tuskar.netconf') - - -class ConfFixture(fixtures.Fixture): - """Fixture to manage global conf settings.""" - - def __init__(self, conf): - self.conf = conf - - def setUp(self): - super(ConfFixture, self).setUp() - - self.conf.set_default('connection', "sqlite://", group='database') - self.conf.set_default('sqlite_synchronous', False, group='database') - self.conf.set_default('use_ipv6', True) - self.conf.set_default('verbose', True) - config.parse_args([], default_config_files=[]) - self.addCleanup(self.conf.reset) diff --git a/tuskar/tests/db/__init__.py b/tuskar/tests/db/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tuskar/tests/db/base.py b/tuskar/tests/db/base.py deleted file mode 100644 index afb55944..00000000 --- a/tuskar/tests/db/base.py +++ /dev/null @@ -1,24 +0,0 @@ -# Copyright (c) 2012 NTT DOCOMO, INC. -# All Rights Reserved. -# -# 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. - -"""Tuskar DB test base class.""" - -from tuskar.tests import base - - -class DbTestCase(base.TestCase): - - def setUp(self): - super(DbTestCase, self).setUp() diff --git a/tuskar/tests/db/test_api.py b/tuskar/tests/db/test_api.py deleted file mode 100644 index dc9f8ce0..00000000 --- a/tuskar/tests/db/test_api.py +++ /dev/null @@ -1,460 +0,0 @@ -# -# 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 tuskar.common import exception -from tuskar.db.sqlalchemy import api as dbapi -from tuskar.db.sqlalchemy import models -from tuskar.tests.db import base as db_base - - -class OvercloudRoleTests(db_base.DbTestCase): - - def setUp(self): - super(OvercloudRoleTests, self).setUp() - - self.connection = dbapi.Connection() - - self.save_me_1 = models.OvercloudRole( - name='name-1', - description='desc-1', - image_name='image-1', - flavor_id='tuvwxyz', - ) - - self.save_me_2 = models.OvercloudRole( - name='name-2', - description='desc-2', - image_name='image-2', - flavor_id='abc', - ) - - def test_create_overcloud_role(self): - # Test - saved = self.connection.create_overcloud_role(self.save_me_1) - - # Verify - self.assertTrue(saved is not None) - self.assertTrue(saved.id is not None) - self.assertEqual(saved.name, self.save_me_1.name) - self.assertEqual(saved.description, self.save_me_1.description) - self.assertEqual(saved.image_name, self.save_me_1.image_name) - self.assertEqual(saved.flavor_id, self.save_me_1.flavor_id) - - # Simple check to make sure it can be pulled out of the DB - found = self.connection.get_overcloud_roles() - self.assertEqual(1, len(found)) - self.assertEqual(found[0].id, saved.id) - - def test_create_overcloud_role_duplicate_name(self): - # Setup - self.connection.create_overcloud_role(self.save_me_1) - duplicate = models.OvercloudRole( - name=self.save_me_1.name, - description='irrelevant', - image_name='irrelevant' - ) - - # Test - self.assertRaises(exception.OvercloudRoleExists, - self.connection.create_overcloud_role, - duplicate) - - def test_update(self): - # Setup - saved = self.connection.create_overcloud_role(self.save_me_1) - - # Test - delta = models.OvercloudRole( - id=saved.id, - image_name='abcdef', - flavor_id='new-flavor' - ) - self.connection.update_overcloud_role(delta) - - # Verify - found = self.connection.get_overcloud_role_by_id(saved.id) - self.assertEqual(found.image_name, delta.image_name) - self.assertEqual(found.flavor_id, delta.flavor_id) - - def test_delete(self): - # Setup - saved = self.connection.create_overcloud_role(self.save_me_1) - - # Test - self.connection.delete_overcloud_role_by_id(saved.id) - - # Verify - found = self.connection.get_overcloud_roles() - self.assertEqual(0, len(found)) - - def test_delete_nonexistent_role(self): - self.assertRaises(exception.OvercloudRoleNotFound, - self.connection.delete_overcloud_role_by_id, - 'fake-id') - - def test_get_overcloud_roles(self): - # Setup - self.connection.create_overcloud_role(self.save_me_1) - self.connection.create_overcloud_role(self.save_me_2) - - # Test - all_roles = self.connection.get_overcloud_roles() - - # Verify - self.assertEqual(2, len(all_roles)) - - found_1 = all_roles[0] - self.assertEqual(found_1.name, self.save_me_1.name) - self.assertEqual(found_1.description, self.save_me_1.description) - self.assertEqual(found_1.image_name, self.save_me_1.image_name) - - found_2 = all_roles[1] - self.assertEqual(found_2.name, self.save_me_2.name) - self.assertEqual(found_2.description, self.save_me_2.description) - self.assertEqual(found_2.image_name, self.save_me_2.image_name) - - def test_get_overcloud_roles_no_results(self): - # Test - all_roles = self.connection.get_overcloud_roles() - - # Verify - self.assertTrue(isinstance(all_roles, list)) - self.assertEqual(0, len(all_roles)) - - def test_get_overcloud_role_by_id(self): - # Setup - self.connection.create_overcloud_role(self.save_me_1) - saved_2 = self.connection.create_overcloud_role(self.save_me_2) - - # Test - found = self.connection.get_overcloud_role_by_id(saved_2.id) - - # Verify - self.assertTrue(found is not None) - self.assertEqual(found.id, saved_2.id) - self.assertEqual(found.name, saved_2.name) - - def test_get_overcloud_role_by_id_no_result(self): - self.assertRaises(exception.OvercloudRoleNotFound, - self.connection.get_overcloud_role_by_id, - 'fake-id') - - -class OvercloudTests(db_base.DbTestCase): - - def setUp(self): - super(OvercloudTests, self).setUp() - - self.connection = dbapi.Connection() - - self.role_1 = models.OvercloudRole( - name='name-1', - description='desc-1', - image_name='image-1', - flavor_id='tuvwxyz', - ) - self.saved_role = self.connection.create_overcloud_role(self.role_1) - - self.attributes_1 = models.OvercloudAttribute( - key='key-1', - value='value-1', - ) - - self.attributes_2 = models.OvercloudAttribute( - key='key-2', - value='value-2', - ) - - self.count_1 = models.OvercloudRoleCount( - overcloud_role_id=self.saved_role.id, - num_nodes=4, - ) - - self.overcloud_1 = models.Overcloud( - name='overcloud-1', - description='desc-1', - attributes=[self.attributes_1, self.attributes_2], - counts=[self.count_1] - ) - - self.overcloud_2 = models.Overcloud( - name='overcloud-2', - description='desc-2', - attributes=[] - ) - - def test_create_overcloud(self): - # Test - self.connection.create_overcloud_role(self.role_1) - saved = self.connection.create_overcloud(self.overcloud_1) - - # Verify - self.assertTrue(saved is not None) - - # IDs are populated - self.assertTrue(saved.id is not None) - for attribute in saved.attributes: - self.assertTrue(attribute.id is not None) - - # Data integrity - self.assertEqual(saved.name, self.overcloud_1.name) - self.assertEqual(saved.description, self.overcloud_1.description) - - for index, attribute in enumerate(self.overcloud_1.attributes): - self.assertEqual(saved.attributes[index].key, attribute.key) - self.assertEqual(saved.attributes[index].value, attribute.value) - - for index, count in enumerate(self.overcloud_1.counts): - self.assertEqual(saved.counts[index].overcloud_role_id, - count.overcloud_role_id) - self.assertTrue(saved.counts[index] is not None) - self.assertTrue(isinstance(saved.counts[index].overcloud_role, - models.OvercloudRole)) - self.assertEqual(saved.counts[index].overcloud_role.name, - self.role_1.name) - self.assertEqual(saved.counts[index].overcloud_role.description, - self.role_1.description) - self.assertEqual(saved.counts[index].overcloud_role.image_name, - self.role_1.image_name) - - self.assertEqual(saved.counts[index].num_nodes, - count.num_nodes) - - def test_create_minimal_overcloud(self): - # Setup - overcloud = models.Overcloud(name='minimal') - - # Test - saved = self.connection.create_overcloud(overcloud) - - # Verify - self.assertEqual(saved.name, overcloud.name) - self.assertEqual(saved.description, None) - self.assertEqual(saved.attributes, []) - self.assertEqual(saved.counts, []) - - def test_create_overcloud_duplicate_name(self): - # Setup - self.connection.create_overcloud(self.overcloud_1) - duplicate = models.Overcloud( - name=self.overcloud_1.name, - description='irrelevant', - ) - - # Test - self.assertRaises(exception.OvercloudExists, - self.connection.create_overcloud, - duplicate) - - def test_create_overcloud_duplicate_attribute(self): - # Setup - duplicate_attribute = models.OvercloudAttribute( - key=self.attributes_1.key, - value='irrelevant' - ) - self.overcloud_1.attributes = [self.attributes_1, duplicate_attribute] - - # Test - self.assertRaises(exception.DuplicateAttribute, - self.connection.create_overcloud, - self.overcloud_1) - - def test_update_overcloud(self): - # Setup - saved = self.connection.create_overcloud(self.overcloud_1) - - # Test - saved.stack_id = 'new_id' - self.connection.update_overcloud(saved) - - # Verify - found = self.connection.get_overcloud_by_id(saved.id) - self.assertEqual(found.stack_id, saved.stack_id) - self.assertEqual(found.name, self.overcloud_1.name) - - def test_update_overcloud_attributes(self): - # Setup - - # Add a third attribute for enough data - self.overcloud_1.attributes.append(models.OvercloudAttribute( - key='key-3', - value='value-3', - )) - saved = self.connection.create_overcloud(self.overcloud_1) - - # Test - # - Ignore the first - saved.attributes.pop(0) - - # - Change the second - saved.attributes[0].value = 'updated-2' - - # - Delete the third - saved.attributes[1].value = None - - # - Add a fourth - saved.attributes.append(models.OvercloudAttribute( - key='key-4', - value='value-4', - )) - - self.connection.update_overcloud(saved) - - # Verify - found = self.connection.get_overcloud_by_id(saved.id) - - self.assertEqual(3, len(found.attributes)) - self.assertEqual(found.attributes[0].key, 'key-1') - self.assertEqual(found.attributes[0].value, 'value-1') - self.assertEqual(found.attributes[1].key, 'key-2') - self.assertEqual(found.attributes[1].value, 'updated-2') - self.assertEqual(found.attributes[2].key, 'key-4') - self.assertEqual(found.attributes[2].value, 'value-4') - - def test_update_overcloud_counts(self): - - # Setup - - # Roles - role_2 = self.connection.create_overcloud_role(models.OvercloudRole( - name='name-2', - )) - role_3 = self.connection.create_overcloud_role(models.OvercloudRole( - name='name-3', - )) - role_4 = self.connection.create_overcloud_role(models.OvercloudRole( - name='name-4', - )) - - # Add extra counts for enough data - self.overcloud_1.counts.append(models.OvercloudRoleCount( - overcloud_role_id=role_2.id, - num_nodes=2, - )) - self.overcloud_1.counts.append(models.OvercloudRoleCount( - overcloud_role_id=role_3.id, - num_nodes=3, - )) - saved = self.connection.create_overcloud(self.overcloud_1) - - # Test - # - Ignore the first - saved.counts.pop(0) - - # - Change the second - saved.counts[0].num_nodes = 100 - - # - Delete the third - saved.counts[1].num_nodes = 0 - - # - Add a fourth - saved.counts.append(models.OvercloudRoleCount( - overcloud_role_id=role_4.id, - num_nodes=4, - )) - - self.connection.update_overcloud(saved) - - # Verify - found = self.connection.get_overcloud_by_id(saved.id) - - self.assertEqual(3, len(found.counts)) - self.assertEqual(found.counts[0].overcloud_role_id, self.saved_role.id) - self.assertEqual(found.counts[0].num_nodes, 4) - self.assertEqual(found.counts[1].overcloud_role_id, role_2.id) - self.assertEqual(found.counts[1].num_nodes, 100) - self.assertEqual(found.counts[2].overcloud_role_id, role_4.id) - self.assertEqual(found.counts[2].num_nodes, 4) - - def test_update_nonexistent(self): - fake = models.Overcloud(id='fake') - self.assertRaises(exception.OvercloudNotFound, - self.connection.update_overcloud, - fake) - - def test_delete_overcloud(self): - # Setup - saved = self.connection.create_overcloud(self.overcloud_1) - - # Test - self.connection.delete_overcloud_by_id(saved.id) - - # Verify - found = self.connection.get_overclouds() - self.assertEqual(0, len(found)) - - # Ensure the joined tables are clear too - session = dbapi.get_session() - all_counts = session.query(models.OvercloudAttribute).all() - session.close() - self.assertEqual(0, len(all_counts)) - - session = dbapi.get_session() - all_counts = session.query(models.OvercloudRoleCount).all() - session.close() - self.assertEqual(0, len(all_counts)) - - def test_delete_nonexistent_overcloud(self): - self.assertRaises(exception.OvercloudNotFound, - self.connection.delete_overcloud_by_id, - 'irrelevant-id') - - def test_get_overclouds(self): - - # This test also verifies that the attributes are eagerly loaded - - # Setup - self.connection.create_overcloud(self.overcloud_1) - self.connection.create_overcloud(self.overcloud_2) - - # Test - all_overclouds = self.connection.get_overclouds() - - # Verify - self.assertEqual(2, len(all_overclouds)) - - found_1 = all_overclouds[0] - self.assertEqual(found_1.name, self.overcloud_1.name) - self.assertEqual(found_1.description, self.overcloud_1.description) - self.assertEqual(found_1.attributes, self.overcloud_1.attributes) - - found_2 = all_overclouds[1] - self.assertEqual(found_2.name, self.overcloud_2.name) - self.assertEqual(found_2.description, self.overcloud_2.description) - self.assertEqual(found_2.attributes, self.overcloud_2.attributes) - - def test_get_overclouds_no_results(self): - # Test - all_overclouds = self.connection.get_overclouds() - - # Verify - self.assertTrue(isinstance(all_overclouds, list)) - self.assertEqual(0, len(all_overclouds)) - - def test_get_overcloud_by_id(self): - # Setup - self.connection.create_overcloud(self.overcloud_1) - saved_2 = self.connection.create_overcloud(self.overcloud_2) - - # Test - found = self.connection.get_overcloud_by_id(saved_2.id) - - # Verify - self.assertTrue(found is not None) - self.assertEqual(found.id, saved_2.id) - self.assertEqual(found.name, saved_2.name) - - def test_get_overcloud_by_id_no_result(self): - self.assertRaises(exception.OvercloudNotFound, - self.connection.get_overcloud_by_id, - 'fake-id') diff --git a/tuskar/tests/db/test_migration.py b/tuskar/tests/db/test_migration.py deleted file mode 100644 index 524dbbf5..00000000 --- a/tuskar/tests/db/test_migration.py +++ /dev/null @@ -1,79 +0,0 @@ -# Copyright (c) 2013 Red Hat -# All Rights Reserved. -# -# 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 sqlalchemy - -from tuskar.db import migration as db_migration -from tuskar.db.sqlalchemy import api as sqla_api -from tuskar.tests.db import base as db_base - - -class CurrentDatabaseSchemaTests(db_base.DbTestCase): - """Current schema verification. - - Contains tests that verify the end result of the migration matches - the most recent schema. - """ - - def test_migration_ok(self): - db_migration.db_sync() - # Should not raise any exceptions - - def test_verify_overcloud_roles(self): - table = self._get_db_table('overcloud_roles') - expected = { - 'id': 'Integer', - 'name': 'String', - 'description': 'String', - 'image_name': 'String', - 'flavor_id': 'String', - 'created_at': 'DateTime', - 'updated_at': 'DateTime', - } - self._assert_columns(table, expected) - - def test_verify_overcloud(self): - table = self._get_db_table('overclouds') - expected = { - 'id': 'Integer', - 'name': 'String', - 'description': 'String', - 'created_at': 'DateTime', - 'updated_at': 'DateTime', - } - self._assert_columns(table, expected) - - def test_verify_overcloud_attributes(self): - table = self._get_db_table('overcloud_attributes') - expected = { - 'id': 'Integer', - 'overcloud_id': 'Integer', - 'key': 'String', - 'value': 'Text', - 'created_at': 'DateTime', - 'updated_at': 'DateTime', - } - self._assert_columns(table, expected) - - @staticmethod - def _get_db_table(table_name): - metadata = sqlalchemy.MetaData() - metadata.bind = sqla_api.get_engine() - return sqlalchemy.Table(table_name, metadata, autoload=True) - - def _assert_columns(self, table, column_types): - for col, coltype in column_types.items(): - self.assertTrue(isinstance(table.columns[col].type, - getattr(sqlalchemy.types, coltype))) diff --git a/tuskar/tests/db/test_models.py b/tuskar/tests/db/test_models.py deleted file mode 100644 index c1d5e85c..00000000 --- a/tuskar/tests/db/test_models.py +++ /dev/null @@ -1,114 +0,0 @@ -# -# 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 unittest - -from tuskar.db.sqlalchemy import models - - -class OvercloudRoleTests(unittest.TestCase): - - def test_equal(self): - # Setup - res_cat_1 = models.OvercloudRole(name='foo') - res_cat_2 = models.OvercloudRole(name='foo') - - # Test - self.assertTrue(res_cat_1 == res_cat_2) - - def test_not_equal(self): - # Setup - res_cat_1 = models.OvercloudRole(name='foo') - res_cat_2 = models.OvercloudRole(name='bar') - - # Test - self.assertTrue(res_cat_1 != res_cat_2) - - -class OvercloudRoleCountTests(unittest.TestCase): - - def test_equal(self): - # Setup - count_1 = models.OvercloudRoleCount(overcloud_id='o1', - overcloud_role_id='r1') - count_2 = models.OvercloudRoleCount(overcloud_id='o1', - overcloud_role_id='r1') - - # Test - self.assertTrue(count_1 == count_2) - - def test_same_overcloud_different_role(self): - # Setup - count_1 = models.OvercloudRoleCount(overcloud_id='o1', - overcloud_role_id='r1') - count_2 = models.OvercloudRoleCount(overcloud_id='o1', - overcloud_role_id='r2') - - # Test - self.assertTrue(count_1 != count_2) - - def test_different_overcloud_same_role(self): - # Setup - count_1 = models.OvercloudRoleCount(overcloud_id='o1', - overcloud_role_id='r1') - count_2 = models.OvercloudRoleCount(overcloud_id='o2', - overcloud_role_id='r1') - - # Test - self.assertTrue(count_1 != count_2) - - -class OvercloudTests(unittest.TestCase): - - def test_equal(self): - # Setup - overcloud_1 = models.Overcloud(name='foo') - overcloud_2 = models.Overcloud(name='foo') - - # Test - self.assertTrue(overcloud_1 == overcloud_2) - - def test_not_equal(self): - # Setup - overcloud_1 = models.Overcloud(name='foo') - overcloud_2 = models.Overcloud(name='bar') - - # Test - self.assertTrue(overcloud_1 != overcloud_2) - - -class OvercloudAttributesTests(unittest.TestCase): - - def test_equal(self): - # Setup - attr_1 = models.OvercloudAttribute(overcloud_id='foo', key='bar') - attr_2 = models.OvercloudAttribute(overcloud_id='foo', key='bar') - - # Test - self.assertTrue(attr_1 == attr_2) - - def test_same_overcloud_different_key(self): - # Setup - attr_1 = models.OvercloudAttribute(overcloud_id='foo', key='bar') - attr_2 = models.OvercloudAttribute(overcloud_id='foo', key='rab') - - # Test - self.assertTrue(attr_1 != attr_2) - - def test_different_overcloud_same_key(self): - # Setup - attr_1 = models.OvercloudAttribute(overcloud_id='oof', key='bar') - attr_2 = models.OvercloudAttribute(overcloud_id='foo', key='bar') - - # Test - self.assertTrue(attr_1 != attr_2) diff --git a/tuskar/tests/fake_policy.py b/tuskar/tests/fake_policy.py deleted file mode 100644 index bb829067..00000000 --- a/tuskar/tests/fake_policy.py +++ /dev/null @@ -1,23 +0,0 @@ -# Copyright (c) 2012 OpenStack Foundation -# -# 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. - - -policy_data = """ -{ - "admin_api": "role:admin", - "admin_or_owner": "is_admin:True or project_id:%(project_id)s", - "context_is_admin": "role:admin or role:administrator", - "default": "rule:admin_or_owner" -} -""" diff --git a/tuskar/tests/heat/__init__.py b/tuskar/tests/heat/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tuskar/tests/heat/test_template_tools.py b/tuskar/tests/heat/test_template_tools.py deleted file mode 100644 index 9e68a3ec..00000000 --- a/tuskar/tests/heat/test_template_tools.py +++ /dev/null @@ -1,121 +0,0 @@ -# -# 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 unittest - -import mock - -from tuskar.heat import template_tools - - -class TemplateToolsTests(unittest.TestCase): - - @mock.patch('tripleo_heat_merge.merge.parse_scaling') - def test_generate_scaling_params(self, mock_parse_scaling): - # Setup - overcloud_roles = {'controller': 1, 'overcloud-compute': 12} - - # Test - template_tools.generate_scaling_params(overcloud_roles) - - # Verify - # Called once internally for the defaults and once with NovaCompute=12 - self.assertEqual(mock_parse_scaling.call_count, 2) - mock_parse_scaling.assert_called_with(['NovaCompute=12']) - - @mock.patch('tripleo_heat_merge.merge.merge') - def test_merge_templates_compute(self, mock_merge): - # Setup - overcloud_roles = {'controller': 1, 'overcloud-compute': 12} - - # Test - template_tools.merge_templates(overcloud_roles) - - # Verify - mock_merge.assert_called_once_with( - [ - '/etc/tuskar/tripleo-heat-templates/overcloud-source.yaml', - '/etc/tuskar/tripleo-heat-templates/block-storage.yaml', - '/etc/tuskar/tripleo-heat-templates/swift-source.yaml', - '/etc/tuskar/tripleo-heat-templates/swift-storage-source.yaml', - '/etc/tuskar/tripleo-heat-templates/ssl-source.yaml', - '/etc/tuskar/tripleo-heat-templates/swift-deploy.yaml', - '/etc/tuskar/tripleo-heat-templates/nova-compute-config.yaml' - ], - None, - None, - scaling={ - 'NovaCompute0': (12, frozenset()), - 'SwiftStorage0': (0, frozenset()), - 'BlockStorage0': (0, frozenset()), - }, - included_template_dir='/etc/tuskar/tripleo-heat-templates/' - ) - - @mock.patch('tripleo_heat_merge.merge.merge') - def test_merge_templates_block_storage(self, mock_merge): - # Setup - overcloud_roles = {'controller': 1, 'overcloud-cinder-volume': 12} - - # Test - template_tools.merge_templates(overcloud_roles) - - # Verify - mock_merge.assert_called_once_with( - [ - '/etc/tuskar/tripleo-heat-templates/overcloud-source.yaml', - '/etc/tuskar/tripleo-heat-templates/block-storage.yaml', - '/etc/tuskar/tripleo-heat-templates/swift-source.yaml', - '/etc/tuskar/tripleo-heat-templates/swift-storage-source.yaml', - '/etc/tuskar/tripleo-heat-templates/ssl-source.yaml', - '/etc/tuskar/tripleo-heat-templates/swift-deploy.yaml', - '/etc/tuskar/tripleo-heat-templates/nova-compute-config.yaml' - ], - None, - None, - scaling={ - 'NovaCompute0': (0, frozenset()), - 'SwiftStorage0': (0, frozenset()), - 'BlockStorage0': (12, frozenset()), - }, - included_template_dir='/etc/tuskar/tripleo-heat-templates/' - ) - - @mock.patch('tripleo_heat_merge.merge.merge') - def test_merge_templates_object_storage(self, mock_merge): - # Setup - overcloud_roles = {'controller': 1, 'overcloud-swift-storage': 12} - - # Test - template_tools.merge_templates(overcloud_roles) - - # Verify - mock_merge.assert_called_once_with( - [ - '/etc/tuskar/tripleo-heat-templates/overcloud-source.yaml', - '/etc/tuskar/tripleo-heat-templates/block-storage.yaml', - '/etc/tuskar/tripleo-heat-templates/swift-source.yaml', - '/etc/tuskar/tripleo-heat-templates/swift-storage-source.yaml', - '/etc/tuskar/tripleo-heat-templates/ssl-source.yaml', - '/etc/tuskar/tripleo-heat-templates/swift-deploy.yaml', - '/etc/tuskar/tripleo-heat-templates/nova-compute-config.yaml' - ], - None, - None, - scaling={ - 'NovaCompute0': (0, frozenset()), - 'SwiftStorage0': (12, frozenset()), - 'BlockStorage0': (0, frozenset()), - }, - included_template_dir='/etc/tuskar/tripleo-heat-templates/' - ) diff --git a/tuskar/tests/manager/__init__.py b/tuskar/tests/manager/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tuskar/tests/manager/test_name_utils.py b/tuskar/tests/manager/test_name_utils.py deleted file mode 100644 index 017cb4d2..00000000 --- a/tuskar/tests/manager/test_name_utils.py +++ /dev/null @@ -1,48 +0,0 @@ -# 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 unittest - -from tuskar.manager import name_utils - - -class NameUtilsTestCases(unittest.TestCase): - - def test_generate_role_namespace(self): - ns = name_utils.generate_role_namespace('r1', 'v1') - self.assertEqual('r1-v1', ns) - - def test_parse_role_namespace(self): - name, version = name_utils.parse_role_namespace('r1-v1') - self.assertEqual('r1', name) - self.assertEqual('v1', version) - - def test_parse_role_namespace_multiple_dash(self): - name, version = name_utils.parse_role_namespace('all-in-one-v1') - self.assertEqual('all-in-one', name) - self.assertEqual('v1', version) - - def test_role_template_filename(self): - filename = name_utils.role_template_filename('r1', 'v1', None) - self.assertEqual('provider-r1-v1.yaml', filename) - - def test_role_template_filename_with_relative_path(self): - filename = name_utils.role_template_filename('r1', 'v1', 'l1') - self.assertEqual('l1/provider-r1-v1.yaml', filename) - - def test_master_template_filename(self): - filename = name_utils.master_template_filename('p1') - self.assertEqual('p1-template.yaml', filename) - - def test_environment_filename(self): - filename = name_utils.environment_filename('p1') - self.assertEqual('p1-environment.yaml', filename) diff --git a/tuskar/tests/manager/test_plan.py b/tuskar/tests/manager/test_plan.py deleted file mode 100644 index 22317bde..00000000 --- a/tuskar/tests/manager/test_plan.py +++ /dev/null @@ -1,460 +0,0 @@ -# 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 tuskar.common import exception -from tuskar.manager.models import DeploymentPlan -from tuskar.manager.models import ParameterValue -from tuskar.manager.models import PlanParameter -from tuskar.manager.models import Role -from tuskar.manager import name_utils -from tuskar.manager.plan import MASTER_SEED_NAME -from tuskar.manager.plan import PlansManager -from tuskar.storage.exceptions import UnknownName -from tuskar.storage.exceptions import UnknownUUID -from tuskar.storage.load_roles import RESOURCE_REGISTRY_NAME -from tuskar.storage.stores import DeploymentPlanStore -from tuskar.storage.stores import EnvironmentFileStore -from tuskar.storage.stores import MasterSeedStore -from tuskar.storage.stores import MasterTemplateStore -from tuskar.storage.stores import ResourceRegistryMappingStore -from tuskar.storage.stores import ResourceRegistryStore -from tuskar.storage.stores import TemplateStore -from tuskar.templates import namespace as ns_utils -from tuskar.templates import parser -from tuskar.tests.base import TestCase - - -TEST_TEMPLATE = """ -heat_template_version: 2013-05-23 - -description: Test provider resource foo - -parameters: - - key_name: - type: string - description : Name of a KeyPair - hidden: true - label: Key - - instance_type: - type: string - description: Instance type - default: m1.small - constraints: - - allowed_values: [m1.small, m1.medium, m1.large] - description: instance_type must be one of m1.small or m1.medium - - image_id: - type: string - description: ID of the image to use - default: 3e6270da-fbf7-4aef-bc78-6d0cfc3ad11b - -resources: - foo_instance: - type: OS::Nova::Server - properties: - image: { get_param: image_id } - flavor: { get_param: instance_type } - key_name: { get_param: key_name } - -outputs: - foo_ip: - description: IP of the created foo instance - value: { get_attr: [foo_instance, first_address] } -""" - -TEST_SEED = """ -heat_template_version: 2013-05-23 - -description: seed template - -parameters: - - role_count: - type: number - default: 3 - - role_image: - type: string - -resources: - - my_role: - type: OS::Heat::ResourceGroup - properties: - count: {get_param: role_count} - resource_def: - type: OS::TripleO::Role - properties: - image_id: {get_param: role_image} - key_name: "hardcoded_keyname" - instance_type: m1.large - - some_config: - type: OS::Heat::StructuredConfig - properties: - config: - ip_addresses: {get_attr: [my_role, foo_ip]} -""" - -RESOURCE_REGISTRY = """ -resource_registry: - OS::TripleO::Role: r1.yaml - OS::TripleO::Another: required_file.yaml -""" - -RESOURCE_REGISTRY_WRONG_TYPE = """ -resource_registry: - OS::TripleO::UnknownRole: r1.yaml -""" - - -class PlansManagerTestCase(TestCase): - - def setUp(self): - super(PlansManagerTestCase, self).setUp() - self.plans_manager = PlansManager() - - self.plan_store = DeploymentPlanStore() - self.template_store = TemplateStore() - self.seed_store = MasterSeedStore() - self.registry_store = ResourceRegistryStore() - self.registry_mapping_store = ResourceRegistryMappingStore() - - def test_create_plan(self): - # Tests - created = self.plans_manager.create_plan('p1', 'desc-1') - - # Verify - self.assertTrue(created is not None) - self.assertTrue(isinstance(created, DeploymentPlan)) - self.assertTrue(created.uuid is not None) - self.assertEqual('p1', created.name) - self.assertEqual('desc-1', created.description) - self.assertEqual(0, len(created.roles)) - self.assertEqual(0, len(created.parameters)) - - found = self.plans_manager.retrieve_plan(created.uuid) - self.assertTrue(found is not None) - - def test_delete_plan(self): - # Setup - created = self.plans_manager.create_plan('p1', 'desc-1') - db_plan = self.plan_store.retrieve(created.uuid) - - # Test - self.plans_manager.delete_plan(created.uuid) - - # Verify - self.assertRaises(UnknownUUID, - self.plans_manager.retrieve_plan, - created.uuid) - - env_store = EnvironmentFileStore() - self.assertRaises(UnknownUUID, env_store.retrieve, - db_plan.environment_file.uuid) - - master_store = MasterTemplateStore() - self.assertRaises(UnknownUUID, master_store.retrieve, - db_plan.master_template.uuid) - - def test_add_role_to_plan(self): - # Setup - test_role = self._add_test_role() - test_plan = self.plans_manager.create_plan('p1', 'd1') - - # Test - self.plans_manager.add_role_to_plan(test_plan.uuid, test_role.uuid) - - # Verify - db_plan = self.plan_store.retrieve(test_plan.uuid) - parsed_plan = parser.parse_template(db_plan.master_template.contents) - self.assertEqual(1, len(parsed_plan.resources)) - - def test_add_role_to_seeded_plan(self): - # Setup - self.seed_store.create(MASTER_SEED_NAME, TEST_SEED) - self.registry_store.create(RESOURCE_REGISTRY_NAME, RESOURCE_REGISTRY) - # more setup (this is normally called in load_roles) - self.registry_mapping_store.create('required_file.yaml', - 'some fake template data') - test_role = self._add_test_role() - test_plan = self.plans_manager.create_plan('p1', 'd1') - - # Test - self.plans_manager.add_role_to_plan(test_plan.uuid, test_role.uuid) - - # Verify - db_plan = self.plan_store.retrieve(test_plan.uuid) - parsed_plan = parser.parse_template(db_plan.master_template.contents) - self.assertEqual(2, len(parsed_plan.resources)) - - # The role generated in the plan has a different name: - my_role = parsed_plan.find_resource_by_id('r1') - self.assertIsNot(my_role, None) - - # The reference to the role in some_config should be updated: - some_config = parsed_plan.find_resource_by_id('some_config') - self.assertIsNot(some_config, None) - config_property = some_config.find_property_by_name('config') - self.assertIsNot(config_property, None) - self.assertEqual(config_property.value, - {'ip_addresses': - {'get_attr': ['r1', 'foo_ip']}}) - - # verify both entries are present from RESOURCE_REGISTRY - parsed_env = parser.parse_environment( - db_plan.environment_file.contents - ) - self.assertEqual(2, len(parsed_env.registry_entries)) - - def test_add_unknown_role_to_seeded_plan(self): - # Setup - self.seed_store.create(MASTER_SEED_NAME, TEST_SEED) - self.registry_store.create(RESOURCE_REGISTRY_NAME, RESOURCE_REGISTRY) - test_role = self.template_store.create('unknown_role', TEST_TEMPLATE) - test_plan = self.plans_manager.create_plan('p1', 'd1') - - # Test - self.assertRaises(ValueError, self.plans_manager.add_role_to_plan, - test_plan.uuid, test_role.uuid) - - def test_add_role_of_unknown_type_to_seeded_plan(self): - # Setup - self.seed_store.create(MASTER_SEED_NAME, TEST_SEED) - self.registry_store.create(RESOURCE_REGISTRY_NAME, - RESOURCE_REGISTRY_WRONG_TYPE) - test_role = self._add_test_role() - test_plan = self.plans_manager.create_plan('p1', 'd1') - - # Test - self.assertRaises(ValueError, self.plans_manager.add_role_to_plan, - test_plan.uuid, test_role.uuid) - - def test_add_role_to_seeded_plan_without_registry(self): - # Setup - self.seed_store.create(MASTER_SEED_NAME, TEST_SEED) - test_role = self._add_test_role() - test_plan = self.plans_manager.create_plan('p1', 'd1') - - # Resource registry is missing, adding role should fail - self.assertRaises(UnknownName, - self.plans_manager.add_role_to_plan, - test_plan.uuid, test_role.uuid) - - def test_remove_role_from_plan(self): - # Setup - test_role = self._add_test_role() - test_plan = self.plans_manager.create_plan('p1', 'd1') - self.plans_manager.add_role_to_plan(test_plan.uuid, test_role.uuid) - - # Test - self.plans_manager.remove_role_from_plan(test_plan.uuid, - test_role.uuid) - - # Verify - db_plan = self.plan_store.retrieve(test_plan.uuid) - parsed_plan = parser.parse_template(db_plan.master_template.contents) - self.assertEqual(0, len(parsed_plan.resources)) - - def test_retrieve_plan(self): - # Setup - test_role = self._add_test_role() - test_plan = self.plans_manager.create_plan('p1', 'd1') - self.plans_manager.add_role_to_plan(test_plan.uuid, test_role.uuid) - - # Test - found = self.plans_manager.retrieve_plan(test_plan.uuid) - - # Verify - self.assertTrue(found is not None) - self.assertTrue(isinstance(found, DeploymentPlan)) - self.assertEqual(test_plan.uuid, found.uuid) - self.assertEqual('p1', found.name) - self.assertEqual('d1', found.description) - self.assertEqual(1, len(found.roles)) - self.assertTrue(isinstance(found.roles[0], Role)) - self.assertEqual(5, len(found.parameters)) # 3 + 2 for scaling - self.assertTrue(isinstance(found.parameters[0], PlanParameter)) - - def test_list_plans(self): - # Setup - self.plans_manager.create_plan('p1', 'd1') - self.plans_manager.create_plan('p2', 'd2') - - # Test - all_plans = self.plans_manager.list_plans() - - # Verify - self.assertEqual(2, len(all_plans)) - all_plans.sort(key=lambda x: x.name) - self.assertEqual('p1', all_plans[0].name) - self.assertEqual('p2', all_plans[1].name) - - def test_set_parameter_values(self): - # Setup - test_role = self._add_test_role() - test_plan = self.plans_manager.create_plan('p1', 'd1') - self.plans_manager.add_role_to_plan(test_plan.uuid, test_role.uuid) - - # Test - ns = name_utils.generate_role_namespace(test_role.name, - test_role.version) - update_us = [ - ParameterValue(ns_utils.apply_template_namespace(ns, 'key_name'), - 'test-key'), - ParameterValue(ns_utils.apply_template_namespace(ns, 'image_id'), - 'test-image'), - ] - updated_plan = self.plans_manager.set_parameter_values(test_plan.uuid, - update_us) - - # Verify - self.assertTrue(updated_plan is not None) - self.assertTrue(isinstance(updated_plan, DeploymentPlan)) - - # Pull it from the database again to make sure it was saved - found = self.plans_manager.retrieve_plan(test_plan.uuid) - found_params = sorted(found.parameters, key=lambda x: x.name) - self.assertEqual(5, len(found_params)) # 3 + 2 for scaling - self.assertEqual(found_params[0].value, '1') - self.assertEqual(found_params[1].value, 'test-image') - self.assertEqual(found_params[2].value, 'm1.small') - self.assertEqual(found_params[3].value, 'test-key') - self.assertEqual(found_params[4].value, []) - - def test_set_non_existent_parameters(self): - # Setup - test_role = self._add_test_role() - test_plan = self.plans_manager.create_plan('p1', 'd1') - self.plans_manager.add_role_to_plan(test_plan.uuid, test_role.uuid) - - # Test - ns = name_utils.generate_role_namespace(test_role.name, - test_role.version) - not_present_in_role_1_name = ns_utils.apply_template_namespace( - ns, 'not_present_in_role_1') - not_present_in_role_2_name = ns_utils.apply_template_namespace( - ns, 'not_present_in_role_2') - update_us = [ - ParameterValue(ns_utils.apply_template_namespace(ns, 'key_name'), - 'test-key'), - ParameterValue(ns_utils.apply_template_namespace(ns, 'image_id'), - 'test-image'), - ParameterValue(not_present_in_role_1_name, - 'not-present-in-role-1-value'), - ParameterValue(not_present_in_role_2_name, - 'not-present-in-role-2-value'), - ] - - # Verify - exc = self.assertRaises(exception.PlanParametersNotExist, - self.plans_manager.set_parameter_values, - test_plan.uuid, - update_us) - self.assertIn(not_present_in_role_1_name, str(exc)) - self.assertIn(not_present_in_role_2_name, str(exc)) - - # Pull it from the database to make sure it was modified - found = self.plans_manager.retrieve_plan(test_plan.uuid) - found_params = sorted(found.parameters, key=lambda x: x.name) - self.assertEqual(5, len(found_params)) # 3 + 2 for scaling - self.assertEqual(found_params[0].value, '1') - self.assertEqual(found_params[1].value, - '3e6270da-fbf7-4aef-bc78-6d0cfc3ad11b') - self.assertEqual(found_params[2].value, 'm1.small') - self.assertEqual(found_params[3].value, '') - - def test_package_templates(self): - # Setup - test_role = self._add_test_role() - test_plan = self.plans_manager.create_plan('p1', 'd1') - self.plans_manager.add_role_to_plan(test_plan.uuid, test_role.uuid) - - # Test - templates = self.plans_manager.package_templates(test_plan.uuid) - - # Verify - self.assertTrue(isinstance(templates, dict)) - self.assertEqual(3, len(templates)) - - self.assertTrue('plan.yaml' in templates) - parsed_plan = parser.parse_template(templates['plan.yaml']) - self.assertEqual(parsed_plan.description, 'd1') - - self.assertTrue('environment.yaml' in templates) - parsed_env = parser.parse_environment(templates['environment.yaml']) - self.assertEqual(1, len(parsed_env.registry_entries)) - - role_filename = name_utils.role_template_filename('r1', '1', None) - self.assertTrue(role_filename in templates) - parsed_role = parser.parse_template(templates[role_filename]) - self.assertEqual(parsed_role.description, 'Test provider resource foo') - - def test_package_templates_seeded_plan(self): - # Setup - self.seed_store.create(MASTER_SEED_NAME, TEST_SEED) - self.registry_store.create(RESOURCE_REGISTRY_NAME, RESOURCE_REGISTRY) - # more setup (this is normally called in load_roles) - self.registry_mapping_store.create('required_file.yaml', - 'some fake template data') - - test_role = self._add_test_role() - test_plan = self.plans_manager.create_plan('p1', 'd1') - self.plans_manager.add_role_to_plan(test_plan.uuid, test_role.uuid) - - # Test - templates = self.plans_manager.package_templates(test_plan.uuid) - - # Verify - self.assertTrue(isinstance(templates, dict)) - self.assertEqual(4, len(templates)) - - self.assertTrue('plan.yaml' in templates) - parsed_plan = parser.parse_template(templates['plan.yaml']) - self.assertEqual(parsed_plan.description, 'd1') - - self.assertTrue('environment.yaml' in templates) - self.assertTrue('required_file.yaml' in templates) - parsed_env = parser.parse_environment(templates['environment.yaml']) - self.assertEqual(2, len(parsed_env.registry_entries)) - - role_filename = name_utils.role_template_filename('r1', '1', None) - self.assertTrue(role_filename in templates) - parsed_role = parser.parse_template(templates[role_filename]) - self.assertEqual(parsed_role.description, 'Test provider resource foo') - - def test_find_roles(self): - # Setup - self.seed_store.create(MASTER_SEED_NAME, TEST_SEED) - self.registry_store.create(RESOURCE_REGISTRY_NAME, RESOURCE_REGISTRY) - # more setup (this is normally called in load_roles) - self.registry_mapping_store.create('required_file.yaml', - 'some fake template data') - test_role = self._add_test_role() - test_plan = self.plans_manager.create_plan('p1', 'd1') - - # Test - self.plans_manager.add_role_to_plan(test_plan.uuid, test_role.uuid) - - # Verify only one role is found - db_plan = self.plan_store.retrieve(test_plan.uuid) - parsed_env = parser.parse_environment( - db_plan.environment_file.contents - ) - roles = self.plans_manager._find_roles(parsed_env) - self.assertEqual(1, len(roles)) - - def _add_test_role(self): - return self.template_store.create('r1', TEST_TEMPLATE, - registry_path='r1.yaml') diff --git a/tuskar/tests/manager/test_role.py b/tuskar/tests/manager/test_role.py deleted file mode 100644 index 6a2e4efc..00000000 --- a/tuskar/tests/manager/test_role.py +++ /dev/null @@ -1,89 +0,0 @@ -# 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 mock - -from tuskar.manager.models import Role -from tuskar.manager.role import RoleManager -from tuskar.storage.exceptions import UnknownUUID -from tuskar.storage.stores import TemplateStore -from tuskar.tests.base import TestCase - - -TEST_TEMPLATE = """ -heat_template_version: 2013-05-23 -parameters: {} -resources: {} -outputs: {} -""" - - -class RoleManagerTests(TestCase): - - def setUp(self): - super(RoleManagerTests, self).setUp() - self.role_manager = RoleManager() - - self.template_store = TemplateStore() - - def test_list_roles(self): - # Test - self._populate_roles() - all_roles = self.role_manager.list_roles() - - # Verify - self.assertEqual(3, len(all_roles)) - self.assertTrue(isinstance(all_roles[0], Role)) - all_roles.sort(key=lambda x: '%s-%s' % (x.name, x.version)) - - self.assertEqual('r1', all_roles[0].name) - self.assertEqual(1, all_roles[0].version) - - self.assertEqual('r1', all_roles[1].name) - self.assertEqual(2, all_roles[1].version) - - self.assertEqual('r2', all_roles[2].name) - self.assertEqual(1, all_roles[2].version) - - def test_list_roles_only_latest(self): - # Setup - list_mock = mock.MagicMock() - self.role_manager.template_store.list = list_mock - list_mock.return_value = [] - - # Test - self.role_manager.list_roles(only_latest=True) - - # Verify - list_mock.assert_called_once_with(only_latest=True) - - def test_retrieve_role_by_uuid(self): - # Test - added_roles = self._populate_roles() - found = self.role_manager.retrieve_role_by_uuid(added_roles[0].uuid) - - # Verify - self.assertTrue(found is not None) - self.assertTrue(isinstance(found, Role)) - self.assertEqual(found.name, 'r1') - self.assertEqual(found.version, 2) - - def test_retrieve_role_by_fake_uuid(self): - self.assertRaises(UnknownUUID, - self.role_manager.retrieve_role_by_uuid, - 'fake') - - def _populate_roles(self): - r1 = self.template_store.create('r1', TEST_TEMPLATE) - r1 = self.template_store.update(r1.uuid, TEST_TEMPLATE) - r2 = self.template_store.create('r2', TEST_TEMPLATE) - return [r1, r2] diff --git a/tuskar/tests/storage/__init__.py b/tuskar/tests/storage/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tuskar/tests/storage/drivers/__init__.py b/tuskar/tests/storage/drivers/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tuskar/tests/storage/drivers/base.py b/tuskar/tests/storage/drivers/base.py deleted file mode 100644 index b095f2b5..00000000 --- a/tuskar/tests/storage/drivers/base.py +++ /dev/null @@ -1,275 +0,0 @@ -# -*- encoding: 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. - -from abc import ABCMeta -from abc import abstractmethod -from datetime import datetime -from functools import partial - -from six import add_metaclass - -from tuskar.storage import exceptions -from tuskar.storage import stores -from tuskar.tests.base import TestCase - - -@add_metaclass(ABCMeta) -class BaseDriverTestCase(TestCase): - - @abstractmethod - def _get_driver(self): - pass - - def setUp(self): - - super(BaseDriverTestCase, self).setUp() - - self.driver = self._get_driver() - self.store = self._get_store(self.driver) - - -@add_metaclass(ABCMeta) -class BaseTestsMixin(object): - - @abstractmethod - def _get_store(self, driver): - pass - - -class BaseStoreMixin(BaseTestsMixin): - - def _get_store(self, driver): - - class BaseStore(stores._BaseStore): - object_type = "base_object" - - return BaseStore(driver) - - def _create_test_object(self, i=None): - return self.store.create("YAML") - - def test_create(self): - - # Test - result = self.store.create("YAML") - - # Verify - self.assertNotEqual(result.uuid, None) - self.assertEqual(result.contents, "YAML") - self.assertEqual(result.name, None) - self.assertEqual(type(result.created_at), datetime) - self.assertEqual(result.updated_at, None) - - def test_version(self): - # Test - result = self._create_test_object() - - # Verify - self.assertEqual(result.version, None) - - def test_retrieve(self): - - # Setup - created = self._create_test_object() - - # Test - result = self.store.retrieve(created.uuid) - - # Verify - self.assertEqual(result.uuid, created.uuid) - self.assertEqual(result.contents, created.contents) - self.assertEqual(result.name, created.name) - self.assertEqual(result.created_at, created.created_at) - self.assertEqual(result.updated_at, created.updated_at) - self.assertEqual(result.version, created.version) - self.assertEqual(result, created) - - def test_update(self): - - # Setup - created = self._create_test_object() - - # Test - result = self.store.update(created.uuid, "YAML 2") - - # Verify - self.assertEqual(result.uuid, created.uuid) - self.assertEqual(result.contents, "YAML 2") - self.assertEqual(result.name, created.name) - self.assertEqual(result.created_at, created.created_at) - self.assertEqual(type(result.updated_at), datetime) - self.assertEqual(result.version, created.version) - - def test_delete(self): - - # Setup - created = self._create_test_object() - - # Test - result = self.store.delete(created.uuid) - - # Verify - self.assertEqual(None, result) - retrieve_call = partial(self.store.retrieve, created.uuid) - self.assertRaises(exceptions.UnknownUUID, retrieve_call) - - def test_list(self): - - # Setup - created_files = [self._create_test_object(i) for i in range(5)] - - # Test - listed_files = self.store.list() - - # Verify - self.assertEqual(5, len(listed_files)) - self.assertEqual(created_files, listed_files) - - -class NamedStoreMixin(BaseStoreMixin): - - def _get_store(self, driver): - - class NamedStore(stores._NamedStore): - object_type = "named_object" - - return NamedStore(driver) - - def _create_test_object(self, i=None): - suffix = '' if i is None else " {0}".format(i) - return self.store.create("NAME{0}".format(suffix), "YAML") - - def test_create(self): - - # Test - result = self.store.create("NAME", "YAML") - - # Verify - self.assertNotEqual(result.uuid, None) - self.assertEqual(result.contents, "YAML") - self.assertEqual(result.name, "NAME") - self.assertEqual(type(result.created_at), datetime) - self.assertEqual(result.updated_at, None) - - def test_retrieve(self): - - # Setup - created = self._create_test_object() - - # Test - result = self.store.retrieve(created.uuid) - - # Verify - self.assertEqual(result.uuid, created.uuid) - self.assertEqual(result.contents, created.contents) - self.assertEqual(result.name, created.name) - self.assertEqual(result.created_at, created.created_at) - self.assertEqual(result.updated_at, created.updated_at) - self.assertEqual(result.version, created.version) - self.assertEqual(result, created) - - def test_retrieve_by_name(self): - - # Setup - created = self._create_test_object() - - # Test - result = self.store.retrieve_by_name(created.name) - - # Verify - self.assertEqual(result.uuid, created.uuid) - self.assertEqual(result.contents, created.contents) - self.assertEqual(result.name, created.name) - self.assertEqual(result.created_at, created.created_at) - self.assertEqual(result.updated_at, created.updated_at) - self.assertEqual(result.version, created.version) - self.assertEqual(result, created) - - -class VersionedStoreMixin(NamedStoreMixin): - - def _get_store(self, driver): - - class VersionedStore(stores._VersionedStore): - object_type = "versioned_object" - - return VersionedStore(driver) - - def test_version(self): - # Test - result = self._create_test_object() - - # Verify - self.assertEqual(result.version, 1) - - def test_update(self): - - # Setup - created = self._create_test_object() - - # Test - result = self.store.update(created.uuid, "YAML 2") - - # Verify - self.assertNotEqual(result.uuid, created.uuid) - self.assertEqual(result.contents, "YAML 2") - self.assertEqual(result.name, created.name) - self.assertNotEqual(result.created_at, created.created_at) - self.assertEqual(result.updated_at, None) - self.assertEqual(created.version, 1) - self.assertEqual(result.version, 2) - - def test_retrieve_by_name_and_version(self): - - # Setup - created = self._create_test_object() - - # Test - result = self.store.retrieve_by_name( - created.name, version=created.version) - - # Verify - self.assertEqual(result.uuid, created.uuid) - self.assertEqual(result.contents, created.contents) - self.assertEqual(result.name, created.name) - self.assertEqual(result.created_at, created.created_at) - self.assertEqual(result.updated_at, created.updated_at) - self.assertEqual(result.version, created.version) - self.assertEqual(result, created) - - def test_retrieve_by_name_and_version_fail(self): - - # Setup - created = self._create_test_object() - - # Verify - retrieve_call = partial(self.store.retrieve_by_name, - created.name, 2) - self.assertRaises(exceptions.UnknownVersion, retrieve_call) - - def test_list_only_latest(self): - - # Setup - created_files = [self._create_test_object(i) for i in range(5)] - - updated = [self.store.update(created.uuid, "YAML 2") - for created in created_files] - - # Test - listed_files = self.store.list(only_latest=True) - - # Verify - self.assertEqual(5, len(listed_files)) - self.assertEqual(5, len(updated)) - self.assertEqual(updated, listed_files) diff --git a/tuskar/tests/storage/drivers/test_base.py b/tuskar/tests/storage/drivers/test_base.py deleted file mode 100644 index fd9dc0f9..00000000 --- a/tuskar/tests/storage/drivers/test_base.py +++ /dev/null @@ -1,110 +0,0 @@ -# -*- encoding: 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. - -from sys import stdout - -from mock import Mock - -from tuskar.storage.drivers.base import BaseDriver -from tuskar.storage.stores import TemplateStore -from tuskar.tests.base import TestCase - - -class _DummyDriver(BaseDriver): - - def __init__(self, client, out=stdout): - self.client = client - self.out = out - - def _log(self, msg): - self.out.write(msg) - - def create(self, store, name, contents): - self._log("Creating {0} named {1}".format(store, name)) - return self.client.create(store, name, contents) - - def retrieve(self, store, uuid): - self._log("Retrieving {0} with ID {1}".format(store, uuid)) - return self.client.retrieve(store, uuid) - - def update(self, store, uuid, name, contents): - self._log("Updating {0} with ID {1}".format(store, uuid)) - return self.client.update(store, uuid, name, contents) - - def delete(self, store, uuid): - self._log("Deleting {0} with ID {1}".format(store, uuid)) - self.client.delete(store, uuid) - - def list(self, store, only_latest=False): - self._log("Listing {0}".format(store)) - self.client.list(store, only_latest=only_latest) - - def retrieve_by_name(self, store, name, version=None): - self._log("Deleting {0} with name {1}".format(store, name)) - self.client.retrieve_by_name(store, name, version=version) - - -class BaseDriverTests(TestCase): - - def setUp(self): - super(BaseDriverTests, self).setUp() - - self.mock_client = Mock() - self.mock_stdout = Mock() - self.driver = _DummyDriver(self.mock_client, out=self.mock_stdout) - self.store = TemplateStore(self.driver) - - def test_create(self): - self.driver.create(self.store, "swift.yaml", "YAML") - self.mock_client.create.assert_called_once_with( - self.store, "swift.yaml", "YAML") - self.mock_client.create.assert_called_once_with( - self.store, "swift.yaml", "YAML") - - def test_retrieve(self): - - self.driver.retrieve(self.store, "uuid") - self.mock_client.retrieve.assert_called_once_with(self.store, "uuid") - - def test_update(self): - self.driver.update(self.store, "uuid", "swift2.yaml", "YAML2") - self.mock_client.update.assert_called_once_with( - self.store, "uuid", "swift2.yaml", "YAML2") - - def test_delete(self): - self.driver.delete(self.store, "uuid") - self.mock_client.delete.assert_called_once_with(self.store, "uuid") - - def test_list(self): - self.driver.list(self.store) - self.mock_client.list.assert_called_once_with( - self.store, only_latest=False) - - self.mock_client.list.reset_mock() - - self.store.list(only_latest=True) - self.mock_client.list.assert_called_once_with( - self.store, only_latest=True) - - def test_retrieve_by_name(self): - - self.driver.retrieve_by_name(self.store, "name") - self.mock_client.retrieve_by_name.assert_called_once_with( - self.store, "name", version=None) - - self.mock_client.retrieve_by_name.reset_mock() - - self.driver.retrieve_by_name(self.store, "name", version=2) - self.mock_client.retrieve_by_name.assert_called_once_with( - self.store, "name", version=2) diff --git a/tuskar/tests/storage/drivers/test_sqlalchemy.py b/tuskar/tests/storage/drivers/test_sqlalchemy.py deleted file mode 100644 index 93b8d75b..00000000 --- a/tuskar/tests/storage/drivers/test_sqlalchemy.py +++ /dev/null @@ -1,68 +0,0 @@ -# -*- encoding: 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. - -from oslo_db.sqlalchemy import test_base - -from tuskar.storage.drivers import sqlalchemy -from tuskar.tests.storage.drivers import base - - -class SQLAlchemyTestCase(base.BaseDriverTestCase): - - def _get_driver(self): - return sqlalchemy.SQLAlchemyDriver() - - -class BaseStoreSQLAlchemyTestCase(SQLAlchemyTestCase, base.BaseStoreMixin): - pass - - -class NamedStoreSQLAlchemyTestCase(SQLAlchemyTestCase, base.NamedStoreMixin): - pass - - -class VersionStoreSQLAlchemyTestCase(SQLAlchemyTestCase, - base.VersionedStoreMixin): - pass - - -class MySQLBaseStoreTestCase(test_base.MySQLOpportunisticTestCase, - BaseStoreSQLAlchemyTestCase): - pass - - -class MySQLNamedStoreTestCase(test_base.MySQLOpportunisticTestCase, - NamedStoreSQLAlchemyTestCase): - pass - - -class MySQLVersionedStoreTestCase(test_base.MySQLOpportunisticTestCase, - VersionStoreSQLAlchemyTestCase): - pass - - -class PostgreSQLBaseStoreTestCase(test_base.PostgreSQLOpportunisticTestCase, - BaseStoreSQLAlchemyTestCase): - pass - - -class PostgreSQLNamedStoreTestCase(test_base.PostgreSQLOpportunisticTestCase, - NamedStoreSQLAlchemyTestCase): - pass - - -class PostgreSQLVersionedStoreTestCase( - test_base.PostgreSQLOpportunisticTestCase, - VersionStoreSQLAlchemyTestCase): - pass diff --git a/tuskar/tests/storage/test_load_roles.py b/tuskar/tests/storage/test_load_roles.py deleted file mode 100644 index b9128d90..00000000 --- a/tuskar/tests/storage/test_load_roles.py +++ /dev/null @@ -1,126 +0,0 @@ -# -*- encoding: 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. - -from os import path -from shutil import rmtree -from tempfile import mkdtemp - -from tuskar.storage import load_roles -from tuskar.storage import load_utils -from tuskar.storage import stores -from tuskar.storage.stores import ResourceRegistryMappingStore -from tuskar.tests.base import TestCase - - -class LoadRoleTests(TestCase): - - def setUp(self): - super(LoadRoleTests, self).setUp() - self.directory = mkdtemp() - - self.store = stores.TemplateStore() - - roles_name = ['role1.yaml', 'role2.yml'] - self.roles = [path.join(self.directory, role) for role in roles_name] - - for role in self.roles: - self._create_file(role) - - def tearDown(self): - super(LoadRoleTests, self).tearDown() - rmtree(self.directory) - - def _create_file(self, file, data=None): - """Create a mock file which contains its own name as the file - contents when the data argument is empty. - """ - - if data is None: - data = "CONTENTS FOR {0}".format(file) - - with open(file, 'w') as f: - f.write(data) - - def test_import(self): - - # test - total, created, updated = load_roles.load_roles(self.roles) - - # verify - self.assertEqual(['role1', 'role2'], sorted(total)) - self.assertEqual(['role1', 'role2'], sorted(created)) - self.assertEqual([], updated) - - def test_import_update(self): - - # setup - load_utils.create_or_update("role2", "contents") - - # test - total, created, updated = load_roles.load_roles(self.roles) - - # verify - self.assertEqual(['role1', 'role2'], sorted(total)) - self.assertEqual(['role1', ], created) - self.assertEqual(['role2', ], updated) - - def test_import_with_seed(self): - # Setup - self._create_file(path.join(self.directory, 'seed')) - - # Test - seed_file = path.join(self.directory, 'seed') - total, created, updated = load_roles.load_roles(self.roles, - seed_file=seed_file) - - # Verify - self.assertEqual(['_master_seed', 'role1', 'role2'], sorted(total)) - self.assertEqual(['_master_seed', 'role1', 'role2'], sorted(created)) - self.assertEqual([], updated) - - def test_import_seed_and_registry(self): - env_data = """ -resource_registry: - OS::TripleO::Role: role1.yaml - OS::TripleO::Another: required_file.yaml - OS::TripleO::SoftwareDeployment: OS::Heat::StructuredDeployment - """ - - # Setup - self._create_file(path.join(self.directory, 'seed')) - self._create_file(path.join(self.directory, 'environment'), env_data) - self._create_file(path.join(self.directory, 'required_file.yaml')) - - # Test - seed_file = path.join(self.directory, 'seed') - env_file = path.join(self.directory, 'environment') - all_roles, created, updated = load_roles.load_roles( - self.roles, - seed_file=seed_file, - resource_registry_path=env_file) - - # Verify - self.assertEqual(['_master_seed', '_registry', - 'role1', 'role2'], sorted(all_roles)) - self.assertEqual(['_master_seed', '_registry', 'required_file.yaml', - 'role1', 'role2'], sorted(created)) - self.assertEqual([], updated) - - # Check resource registry - registry_list = ResourceRegistryMappingStore().list() - ordered_list = sorted(registry_list, key=lambda x: x.name) - self.assertEqual('OS::Heat::StructuredDeployment', - ordered_list[0].name) - self.assertEqual('required_file.yaml', - ordered_list[1].name) diff --git a/tuskar/tests/storage/test_models.py b/tuskar/tests/storage/test_models.py deleted file mode 100644 index f092e02c..00000000 --- a/tuskar/tests/storage/test_models.py +++ /dev/null @@ -1,62 +0,0 @@ -# -*- encoding: 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. - -from datetime import datetime - -from mock import Mock - -from tuskar.storage.models import StoredFile -from tuskar.storage.stores import TemplateStore -from tuskar.tests.base import TestCase - - -class ModelsTests(TestCase): - - def setUp(self): - super(ModelsTests, self).setUp() - - self.driver = Mock() - self.store = TemplateStore(self.driver) - - def test_str(self): - - uuid = "d131dd02c5e6eec4" - f = StoredFile(uuid, "stored contents", self.store) - - self.assertEqual(str(f), "template with ID d131dd02c5e6eec4") - - def test_str_datetimes(self): - - dt = datetime.now() - - uuid = "d131dd02c5e6eec5" - f = StoredFile(uuid, "stored contents", self.store, created_at=dt, - updated_at=dt) - - self.assertEqual(str(f), "template with ID d131dd02c5e6eec5") - - def test_str_version(self): - - uuid = "d131dd02c5e6eec6" - f = StoredFile(uuid, "stored contents", self.store, version=1) - - self.assertEqual(str(f), "template with ID d131dd02c5e6eec6") - - def test_str_name(self): - - uuid = "d131dd02c5e6eec7" - f = StoredFile(uuid, "stored contents", self.store, name="test") - - self.assertEqual( - str(f), "template with ID d131dd02c5e6eec7 and name test") diff --git a/tuskar/tests/storage/test_stores.py b/tuskar/tests/storage/test_stores.py deleted file mode 100644 index 550cd63e..00000000 --- a/tuskar/tests/storage/test_stores.py +++ /dev/null @@ -1,564 +0,0 @@ -# -*- encoding: 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. - -from datetime import datetime -from functools import partial - -from mock import Mock - -from tuskar.storage.exceptions import NameAlreadyUsed -from tuskar.storage.exceptions import UnknownUUID -from tuskar.storage.models import StoredFile -from tuskar.storage import stores -from tuskar.tests.base import TestCase - - -class BaseStoreTests(TestCase): - - def setUp(self): - super(BaseStoreTests, self).setUp() - - self.driver = Mock() - self.store = stores._BaseStore(self.driver) - - def test_create(self): - self.store.create("My contents") - self.driver.create.assert_called_once_with( - self.store, None, "My contents") - - def test_update(self): - uuid = "d131dd02c5e6eec4" - contents = "Stored contents" - self.store.update(uuid, contents) - self.driver.update.assert_called_once_with(self.store, uuid, - contents, "", "") - - def test_retrieve(self): - uuid = "d131dd02c5e6eec5" - self.store.retrieve(uuid) - self.driver.retrieve.assert_called_once_with(self.store, uuid) - - def test_delete(self): - uuid = "d131dd02c5e6eec6" - self.store.delete(uuid) - self.driver.delete.assert_called_once_with(self.store, uuid) - - def test_list(self): - self.store.list() - self.driver.list.assert_called_once_with(self.store) - - -class NamedStoreTests(TestCase): - - def setUp(self): - super(NamedStoreTests, self).setUp() - - self.driver = Mock() - self.store = stores._NamedStore(self.driver) - - def test_create(self): - name = "Object name" - self.store.create(name, "My contents") - self.driver.create.assert_called_once_with( - self.store, name, "My contents", '', '') - - def test_update(self): - uuid = "d131dd02c5e6eec4" - contents = "Stored contents" - self.store.update(uuid, contents) - self.driver.update.assert_called_once_with(self.store, uuid, - contents, '', '') - - def test_retrieve(self): - uuid = "d131dd02c5e6eec5" - self.store.retrieve(uuid) - self.driver.retrieve.assert_called_once_with(self.store, uuid) - - def test_delete(self): - uuid = "d131dd02c5e6eec6" - self.store.delete(uuid) - self.driver.delete.assert_called_once_with(self.store, uuid) - - def test_list(self): - self.store.list() - self.driver.list.assert_called_once_with(self.store) - - def test_retrieve_by_name(self): - name = "Object name" - self.store.retrieve_by_name(name) - self.driver.retrieve_by_name.assert_called_once_with(self.store, name) - - -class VersionedStoreTests(TestCase): - - def setUp(self): - super(VersionedStoreTests, self).setUp() - - self.driver = Mock() - self.store = stores._VersionedStore(self.driver) - - def test_create(self): - name = "Object name" - self.store.create(name, "My contents") - self.driver.create.assert_called_once_with( - self.store, name, "My contents", "", "") - - def test_update(self): - uuid = "d131dd02c5e6eec4" - contents = "Stored contents" - self.store.update(uuid, contents) - self.driver.update.assert_called_once_with(self.store, uuid, - contents, "", "") - - def test_retrieve(self): - uuid = "d131dd02c5e6eec5" - self.store.retrieve(uuid) - self.driver.retrieve.assert_called_once_with(self.store, uuid) - - def test_delete(self): - uuid = "d131dd02c5e6eec6" - self.store.delete(uuid) - self.driver.delete.assert_called_once_with(self.store, uuid) - - def test_list(self): - self.store.list() - self.driver.list.assert_called_once_with(self.store, only_latest=False) - - self.driver.list.reset_mock() - - self.store.list(only_latest=True) - self.driver.list.assert_called_once_with(self.store, only_latest=True) - - def test_retrieve_by_name(self): - name = "Object name" - version = 1 - self.store.retrieve_by_name(name, version) - self.driver.retrieve_by_name.assert_called_once_with( - self.store, name, version) - - -class TemplateStoreTests(TestCase): - - def setUp(self): - super(TemplateStoreTests, self).setUp() - - self.driver = Mock() - self.store = stores.TemplateStore(self.driver) - - def test_create(self): - name = "template name" - contents = "template contents" - self.store.create(name, contents, "") - self.driver.create.assert_called_once_with(self.store, name, - contents, "", "") - - -class TemplateExtraStoreTests(TestCase): - - def setUp(self): - super(TemplateExtraStoreTests, self).setUp() - - self.driver = Mock() - self.store = stores.TemplateExtraStore(self.driver) - - def test_create(self): - name = "template_name_name" - contents = "template extra contents" - self.store.create(name, contents) - self.driver.create.assert_called_once_with(self.store, name, - contents, "", "") - - -class MasterSeedStoreTests(TestCase): - - def setUp(self): - super(MasterSeedStoreTests, self).setUp() - - self.driver = Mock() - self.store = stores.MasterSeedStore(self.driver) - - def test_create(self): - name = "master seed" - contents = "seed contents" - self.store.create(name, contents) - self.driver.create.assert_called_once_with(self.store, name, - contents, "", "") - - def test_object_type(self): - self.assertEqual(stores.MasterSeedStore.object_type, "master_seed") - - -class EnvironmentFileTests(TestCase): - - def setUp(self): - super(EnvironmentFileTests, self).setUp() - - self.driver = Mock() - self.store = stores.EnvironmentFileStore(self.driver) - - def test_create(self): - contents = "environment contents" - self.store.create(contents) - self.driver.create.assert_called_once_with(self.store, None, contents) - - -class DeploymentPlanMockedTests(TestCase): - - def setUp(self): - super(DeploymentPlanMockedTests, self).setUp() - - self.driver = Mock() - - self.master_template_store = Mock() - self.environment_store = Mock() - - self.store = stores.DeploymentPlanStore( - driver=self.driver, - master_template_store=self.master_template_store, - environment_store=self.environment_store - ) - - def _stored_file(self, name, contents): - - dt = datetime.now() - - return StoredFile( - uuid="Plan UUID", - contents=contents, - store="deployment_plan", - name=name, - created_at=dt, - updated_at=None, - version=1, - ) - - def _create_mock_plan(self): - - contents = ('{"environment_file_uuid": "Environment UUID", ' - '"master_template_uuid": "Template UUID"}') - self.driver.retrieve.return_value = Mock( - uuid="Plan UUID", - contents=contents - ) - self.master_template_store.retrieve.return_value = Mock( - uuid="Template UUID" - ) - self.environment_store.retrieve.return_value = Mock( - uuid="Environment UUID" - ) - - def test_create(self): - - name = "deployment_plan name" - contents = ('{"environment_file_uuid": "Environment UUID", ' - '"master_template_uuid": "Template UUID"}') - - self.driver.create.return_value = self._stored_file(name, contents) - - result = self.store.create(name, 'Template UUID', 'Environment UUID') - self.driver.create.assert_called_once_with(self.store, name, - contents, "", "") - - self.assertEqual(result.name, name) - - self.master_template_store.retrieve.assert_called_once_with( - 'Template UUID') - self.environment_store.retrieve.assert_called_once_with( - 'Environment UUID') - - def test_create_no_template(self): - - name = "deployment_plan name" - contents = ('{"environment_file_uuid": "Environment UUID", ' - '"master_template_uuid": "UUID1"}') - - self.driver.create.return_value = self._stored_file(name, contents) - self.master_template_store.create.return_value = Mock(uuid="UUID1") - - result = self.store.create(name, environment_uuid='Environment UUID') - - self.master_template_store.create.assert_called_once_with( - 'deployment_plan name', '') - self.assertItemsEqual(self.environment_store.create.call_args_list, []) - - self.driver.create.assert_called_once_with(self.store, name, - contents, "", "") - - self.assertEqual(result.name, name) - self.master_template_store.retrieve.assert_called_once_with('UUID1') - self.environment_store.retrieve.assert_called_once_with( - 'Environment UUID') - - def test_create_no_environment(self): - - name = "deployment_plan name" - contents = ('{"environment_file_uuid": "UUID2", ' - '"master_template_uuid": "Template UUID"}') - - self.driver.create.return_value = self._stored_file(name, contents) - self.environment_store.create.return_value = Mock(uuid="UUID2") - - result = self.store.create(name, master_template_uuid='Template UUID') - - self.environment_store.create.assert_called_once_with('') - self.assertItemsEqual( - self.master_template_store.create.call_args_list, []) - - self.driver.create.assert_called_once_with(self.store, name, - contents, "", "") - - self.assertEqual(result.name, name) - self.master_template_store.retrieve.assert_called_once_with( - 'Template UUID') - self.environment_store.retrieve.assert_called_once_with( - 'UUID2') - - def test_retrieve(self): - - # setup - self._create_mock_plan() - - # test - retrieved = self.store.retrieve("Plan UUID") - - # verify - self.assertEqual("Plan UUID", retrieved.uuid) - self.assertEqual("Template UUID", retrieved.master_template.uuid) - self.assertEqual("Environment UUID", retrieved.environment_file.uuid) - - def test_update_nothing(self): - - # setup - self._create_mock_plan() - - # setup - update_call = partial(self.store.update, "Plan UUID") - - # test & verify - self.assertRaises(ValueError, update_call) - self.assertItemsEqual(self.driver.update.call_args_list, []) - - -class DeploymentPlanTests(TestCase): - - def setUp(self): - super(DeploymentPlanTests, self).setUp() - - self.master_template_store = stores.MasterTemplateStore() - self.environment_store = stores.EnvironmentFileStore() - self.store = stores.DeploymentPlanStore( - master_template_store=self.master_template_store, - environment_store=self.environment_store - ) - - self._create_plan() - - def _create_plan(self): - - contents = "Template Contents" - self.template = self.master_template_store.create("Template", contents) - self.env = self.environment_store.create("Environment Contents") - - self.plan = self.store.create( - "Plan Name", self.template.uuid, self.env.uuid) - - def test_create(self): - - name = "deployment_plan name" - - result = self.store.create(name, self.template.uuid, self.env.uuid) - - self.assertEqual(result.name, name) - - def test_create_duplicate(self): - - # setup - name = "deployment_plan name" - self.store.create(name, self.template.uuid, self.env.uuid) - create_call = partial(self.store.create, name) - - # test & verify - self.assertRaises(NameAlreadyUsed, create_call) - - def test_create_no_template(self): - - name = "deployment_plan name" - result = self.store.create(name, environment_uuid=self.env.uuid) - self.assertEqual(result.name, name) - - def test_create_no_environment(self): - - name = "deployment_plan name" - template_uuid = self.template.uuid - result = self.store.create(name, master_template_uuid=template_uuid) - self.assertEqual(result.name, name) - - def test_retrieve(self): - - # test - retrieved = self.store.retrieve(self.plan.uuid) - - # verify - self.assertEqual(self.plan.uuid, retrieved.uuid) - self.assertEqual(self.template.uuid, retrieved.master_template.uuid) - self.assertEqual(self.env.uuid, retrieved.environment_file.uuid) - - def test_update_template_manual(self): - - # setup - plan = self.store.create("plan") - - new_template = self.store._template_store.update( - plan.master_template.uuid, "NEW CONTENT") - - # test - updated = self.store.update( - plan.uuid, master_template_uuid=new_template.uuid) - - # verify - retrieved = self.store.retrieve(plan.uuid) - self.assertEqual(plan.uuid, retrieved.uuid) - self.assertEqual(updated.master_template.uuid, new_template.uuid) - - def test_update_environment_manual(self): - - # setup - plan = self.store.create("plan") - - new_env = self.store._env_file_store.update( - plan.environment_file.uuid, "NEW CONTENT") - - # test - updated = self.store.update( - plan.uuid, environment_uuid=new_env.uuid) - - # verify - retrieved = self.store.retrieve(plan.uuid) - self.assertEqual(plan.uuid, retrieved.uuid) - self.assertEqual(updated.environment_file.uuid, new_env.uuid) - - def test_update_nothing(self): - - # setup - update_call = partial(self.store.update, self.plan.uuid) - - # test & verify - self.assertRaises(ValueError, update_call) - - def test_update_master_template(self): - - # setup - new_contents = "New Template Contents." - self.assertEqual("Template Contents", self.template.contents) - - # test - updated = self.store.update_master_template( - self.plan.uuid, new_contents) - - # verify - updated_template = updated.master_template - updated_env = updated.environment_file - - self.assertEqual(self.plan.uuid, updated.uuid) - self.assertEqual(self.plan.master_template.uuid, updated_template.uuid) - self.assertEqual(self.plan.environment_file.uuid, updated_env.uuid) - - self.assertEqual(updated.master_template.contents, new_contents) - self.assertEqual(updated.environment_file.contents, self.env.contents) - - def test_update_environment(self): - - # setup - new_contents = "New Environment Contents." - self.assertEqual("Environment Contents", self.env.contents) - - # test - updated = self.store.update_environment( - self.plan.uuid, new_contents) - - # verify - updated_template = updated.master_template - updated_env = updated.environment_file - - self.assertEqual(self.plan.uuid, updated.uuid) - self.assertEqual(self.plan.master_template.uuid, updated_template.uuid) - self.assertEqual(self.plan.environment_file.uuid, updated_env.uuid) - - self.assertEqual( - updated.master_template.contents, self.template.contents) - self.assertEqual(updated.environment_file.contents, new_contents) - - def test_list(self): - - plans = self.store.list() - - self.assertEqual(1, len(plans)) - - plan, = plans - - self.assertEqual(plan.uuid, self.plan.uuid) - - def test_retrieve_by_name(self): - - plan = self.store.retrieve_by_name("Plan Name") - - self.assertEqual(plan.uuid, self.plan.uuid) - - def test_delete(self): - - # test - self.store.delete(self.plan.uuid) - - # verify - plan_retrieve = partial(self.store.retrieve, self.plan.uuid) - template_retrieve = partial(self.master_template_store.retrieve, - self.template.uuid) - env_retrieve = partial(self.environment_store.retrieve, self.env.uuid) - - for call in [plan_retrieve, template_retrieve, env_retrieve]: - self.assertRaises(UnknownUUID, call) - - def test_delete_no_template(self): - - # setup - self.master_template_store.delete(self.template.uuid) - - # test - self.store.delete(self.plan.uuid) - - # verify - plan_retrieve = partial(self.store.retrieve, self.plan.uuid) - template_retrieve = partial(self.master_template_store.retrieve, - self.template.uuid) - env_retrieve = partial(self.environment_store.retrieve, self.env.uuid) - - for call in [plan_retrieve, template_retrieve, env_retrieve]: - self.assertRaises(UnknownUUID, call) - - def test_delete_no_environment(self): - - # setup - self.environment_store.delete(self.env.uuid) - - # test - self.store.delete(self.plan.uuid) - - # verify - plan_retrieve = partial(self.store.retrieve, self.plan.uuid) - template_retrieve = partial(self.master_template_store.retrieve, - self.template.uuid) - env_retrieve = partial(self.environment_store.retrieve, self.env.uuid) - - for call in [plan_retrieve, template_retrieve, env_retrieve]: - self.assertRaises(UnknownUUID, call) diff --git a/tuskar/tests/templates/__init__.py b/tuskar/tests/templates/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tuskar/tests/templates/test_composer.py b/tuskar/tests/templates/test_composer.py deleted file mode 100644 index fb88a618..00000000 --- a/tuskar/tests/templates/test_composer.py +++ /dev/null @@ -1,219 +0,0 @@ -# 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 unittest - -import yaml - -from tuskar.templates import composer -from tuskar.templates import heat -from tuskar.templates import namespace as ns_utils -from tuskar.templates import plan - - -class ComposerTests(unittest.TestCase): - - def test_compose_template(self): - # Test - sample = self._sample_template() - composed = composer.compose_template(sample) - - # Verify - self.assertTrue(isinstance(composed, str)) - - # Check that it can both be parsed back as YAML and use the resulting - # dict in the assertions - template = yaml.safe_load(composed) - - # Verify Overall Structure - self.assertEqual(6, len(template)) - self.assertTrue('heat_template_version' in template) - self.assertTrue('description' in template) - self.assertTrue('parameters' in template) - self.assertTrue('parameter_groups' in template) - self.assertTrue('resources' in template) - self.assertTrue('outputs' in template) - - # Verify Top-Level Attributes - self.assertEqual('2014-10-16', template['heat_template_version']) - self.assertEqual('template-desc', template['description']) - - # Verify Parameters - self.assertEqual(2, len(template['parameters'])) - - self.assertTrue('p1' in template['parameters']) - self.assertEqual('t1', template['parameters']['p1']['type']) - self.assertEqual('desc-1', template['parameters']['p1']['description']) - self.assertEqual('l1', template['parameters']['p1']['label']) - self.assertEqual('def-1', template['parameters']['p1']['default']) - self.assertEqual(True, template['parameters']['p1']['hidden']) - - self.assertTrue('p2' in template['parameters']) - self.assertEqual('t2', template['parameters']['p2']['type']) - self.assertTrue('description' not in template['parameters']['p2']) - self.assertTrue('label' not in template['parameters']['p2']) - self.assertTrue('default' not in template['parameters']['p2']) - self.assertTrue('hidden' not in template['parameters']['p2']) - - # Verify Parameter Groups - self.assertEqual(2, len(template['parameter_groups'])) - - self.assertEqual('l1', template['parameter_groups'][0]['label']) - self.assertTrue('description' not in template['parameter_groups'][0]) - self.assertTrue('parameters' not in template['parameter_groups'][0]) - - self.assertEqual('l2', template['parameter_groups'][1]['label']) - self.assertEqual('d2', template['parameter_groups'][1]['description']) - self.assertEqual(['bar', 'baz', 'foo'], - sorted(template['parameter_groups'][1]['parameters'])) - - # Verify Resources - self.assertEqual(2, len(template['resources'])) - - self.assertTrue('r1' in template['resources']) - self.assertEqual('t1', template['resources']['r1']['type']) - self.assertEqual('m1', template['resources']['r1']['metadata']) - self.assertEqual('r2', template['resources']['r1']['depends_on']) - self.assertEqual({'u1': 'u2'}, - template['resources']['r1']['update_policy']) - self.assertEqual({'d1': 'd2'}, - template['resources']['r1']['deletion_policy']) - - self.assertTrue('r2' in template['resources']) - self.assertEqual('t2', template['resources']['r2']['type']) - self.assertTrue('metadata' not in template['resources']['r2']) - self.assertTrue('depends_on' not in template['resources']['r2']) - self.assertTrue('update_policy' not in template['resources']['r2']) - self.assertTrue('deletion_policy' not in template['resources']['r2']) - - # Verify Outputs - self.assertEqual(2, len(template['outputs'])) - - self.assertTrue('n1' in template['outputs']) - self.assertEqual('v1', template['outputs']['n1']['value']) - self.assertEqual('desc-1', template['outputs']['n1']['description']) - - self.assertTrue('n2' in template['outputs']) - self.assertEqual('v2', template['outputs']['n2']['value']) - self.assertTrue('description' not in template['outputs']['n2']) - - def test_compose_nested_resource(self): - # Setup - t = heat.Template() - t.add_resource(heat.Resource('r1', 't1')) - - # With add_scaling enabled, the plan will automatically wrap the - # added template in a grouping resource, so we can use that as the - # test data. - p = plan.DeploymentPlan(add_scaling=True) - p.add_template('ns1', t, 't.yaml') - - # Test - composed = composer.compose_template(p.master_template) - - # Verify - template = yaml.safe_load(composed) - - self.assertEqual(1, len(template['resources'])) - wrapper_resource_name = plan.generate_group_id('ns1') - group_resource = template['resources'][wrapper_resource_name] - self.assertEqual(p.master_template.resources[0].resource_type, - group_resource['type']) - - self.assertTrue('resource_def' in group_resource['properties']) - nested_resource_details = group_resource['properties']['resource_def'] - self.assertEqual(ns_utils.apply_resource_alias_namespace('ns1'), - nested_resource_details['type']) - - def test_compose_environment(self): - # Test - sample = self._sample_environment() - composed = composer.compose_environment(sample) - - # Verify - self.assertTrue(isinstance(composed, str)) - - # Check that it can both be parsed back as YAML and use the resulting - # dict in the assertions - template = yaml.safe_load(composed) - - # Verify Overall Structure - self.assertEqual(2, len(template)) - self.assertTrue('parameters' in template) - self.assertTrue('resource_registry' in template) - - # Verify Parameters - self.assertEqual(2, len(template['parameters'])) - - self.assertTrue('n1' in template['parameters']) - self.assertEqual('v1', template['parameters']['n1']) - - self.assertTrue('n2' in template['parameters']) - self.assertEqual('v2', template['parameters']['n2']) - - # Verify Resource Registry - self.assertEqual(2, len(template['resource_registry'])) - - self.assertTrue('a1' in template['resource_registry']) - self.assertEqual('f1', template['resource_registry']['a1']) - - self.assertTrue('a2' in template['resource_registry']) - self.assertEqual('f2', template['resource_registry']['a2']) - - def _sample_template(self): - t = heat.Template(description='template-desc') - - # Complex Parameter - param = heat.Parameter('p1', 't1', description='desc-1', label='l1', - default='def-1', hidden=True) - param.add_constraint(heat.ParameterConstraint('t1', 'def-1', - description='desc-1')) - t.add_parameter(param) - - # Simple Parameter - t.add_parameter(heat.Parameter('p2', 't2')) - - # Simple Parameter Group - t.add_parameter_group(heat.ParameterGroup('l1')) - - # Complex Parameter Group - group = heat.ParameterGroup('l2', description='d2') - group.add_parameter_name('foo', 'bar', 'baz') - t.add_parameter_group(group) - - # Complex Resource - resource = heat.Resource('r1', 't1', metadata='m1', depends_on='r2', - update_policy={'u1': 'u2'}, - deletion_policy={'d1': 'd2'}) - t.add_resource(resource) - - # Simple Resource - t.add_resource(heat.Resource('r2', 't2')) - - # Complex Output - t.add_output(heat.Output('n1', 'v1', description='desc-1')) - - # Simple Output - t.add_output(heat.Output('n2', 'v2')) - - return t - - def _sample_environment(self): - e = heat.Environment() - - e.add_parameter(heat.EnvironmentParameter('n1', 'v1')) - e.add_parameter(heat.EnvironmentParameter('n2', 'v2')) - - e.add_registry_entry(heat.RegistryEntry('a1', 'f1')) - e.add_registry_entry(heat.RegistryEntry('a2', 'f2')) - - return e diff --git a/tuskar/tests/templates/test_heat.py b/tuskar/tests/templates/test_heat.py deleted file mode 100644 index e5afb852..00000000 --- a/tuskar/tests/templates/test_heat.py +++ /dev/null @@ -1,518 +0,0 @@ -# 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 unittest - -from tuskar.templates import heat -from tuskar.templates import namespace as ns - - -class TemplateTests(unittest.TestCase): - - def test_default_version(self): - self.assertEqual(heat.DEFAULT_VERSION, '2014-10-16') - - def test_init(self): - # Test - t = heat.Template(description='test template') - str(t) # should not raise an exception - - # Verify - self.assertEqual(t.version, heat.DEFAULT_VERSION) - self.assertEqual(t.description, 'test template') - self.assertEqual(0, len(t.parameters)) - self.assertEqual(0, len(t.parameter_groups)) - self.assertEqual(0, len(t.resources)) - self.assertEqual(0, len(t.outputs)) - - def test_add_remove_parameter(self): - t = heat.Template() - p = heat.Parameter('test-param', 'test-type') - - # Test Add - t.add_parameter(p) - self.assertEqual(1, len(t.parameters)) - self.assertEqual(p, t.parameters[0]) - - # Test Remove - t.remove_parameter(p) - self.assertEqual(0, len(t.parameters)) - - def test_remove_parameters_by_namespace(self): - # Setup - t = heat.Template() - p1 = heat.Parameter(ns.apply_template_namespace('ns1', 'foo'), 't') - p2 = heat.Parameter(ns.apply_template_namespace('ns2', 'bar'), 't') - p3 = heat.Parameter(ns.apply_template_namespace('ns1', 'baz'), 't') - - t.add_parameter(p1) - t.add_parameter(p2) - t.add_parameter(p3) - - # Test - t.remove_parameters_by_namespace('ns1') - - # Verify - self.assertEqual(1, len(t.parameters)) - self.assertEqual(p2, t.parameters[0]) - - def test_remove_parameter_not_found(self): - t = heat.Template() - self.assertRaises(ValueError, t.remove_parameter, - heat.Parameter('n', 't')) - - def test_find_parameter_by_name(self): - # Setup - t = heat.Template() - t.add_parameter(heat.Parameter('a', 'a1')) - t.add_parameter(heat.Parameter('b', 'b1')) - - # Test - found = t.find_parameter_by_name('b') - - # Verify - self.assertTrue(found is not None) - self.assertEqual(found.param_type, 'b1') - - def test_find_parameter_by_name_missing(self): - t = heat.Template() - found = t.find_parameter_by_name('missing') - self.assertEqual(found, None) - - def test_add_remove_parameter_group(self): - t = heat.Template() - pg = heat.ParameterGroup('test-label', 'test-desc') - - # Test Add - t.add_parameter_group(pg) - self.assertEqual(1, len(t.parameter_groups)) - self.assertEqual(pg, t.parameter_groups[0]) - - # Test Remove - t.remove_parameter_group(pg) - self.assertEqual(0, len(t.parameter_groups)) - - def test_add_remove_resource(self): - t = heat.Template() - r = heat.Resource('id', 't') - - # Test Add - t.add_resource(r) - self.assertEqual(1, len(t.resources)) - self.assertEqual(r, t.resources[0]) - - # Test Remove - t.remove_resource(r) - self.assertEqual(0, len(t.resources)) - - def test_remove_resource_by_id(self): - # Test - t = heat.Template() - t.add_resource(heat.Resource('id1', 't1')) - t.add_resource(heat.Resource('id2', 't2')) - - t.remove_resource_by_id('id1') - - # Verify - self.assertEqual(1, len(t.resources)) - self.assertEqual(t.resources[0].resource_type, 't2') - - def test_find_resource_by_id(self): - # Setup - t = heat.Template() - t.add_resource(heat.Resource('a', 'a1')) - t.add_resource(heat.Resource('b', 'b1')) - - # Test - found = t.find_resource_by_id('b') - - # Verify - self.assertTrue(found is not None) - self.assertEqual(found.resource_type, 'b1') - - def test_find_resource_by_id_missing(self): - t = heat.Template() - found = t.find_resource_by_id('missing') - self.assertEqual(found, None) - - def test_find_parameter_group_by_label(self): - # Setup - t = heat.Template() - t.add_parameter_group(heat.ParameterGroup('a', description='a1')) - t.add_parameter_group(heat.ParameterGroup('b', description='b1')) - - # Test - found = t.find_parameter_group_by_label('b') - - # Verify - self.assertTrue(found is not None) - self.assertEqual(found.description, 'b1') - - def test_find_parameter_group_by_label_missing(self): - t = heat.Template() - found = t.find_parameter_group_by_label('missing') - self.assertEqual(found, None) - - def test_add_remove_output(self): - t = heat.Template() - o = heat.Output('n', 'v') - - # Test Add - t.add_output(o) - self.assertEqual(1, len(t.outputs)) - self.assertEqual(o, t.outputs[0]) - - def test_remove_outputs_by_namespace(self): - # Setup - t = heat.Template() - - o1 = heat.Output(ns.apply_template_namespace('ns1', 'foo'), 'v') - o2 = heat.Output(ns.apply_template_namespace('ns2', 'bar'), 'v') - o3 = heat.Output(ns.apply_template_namespace('ns1', 'foo'), 'v') - - t.add_output(o1) - t.add_output(o2) - t.add_output(o3) - - # Test - t.remove_outputs_by_namespace('ns1') - - # Verify - self.assertEqual(1, len(t.outputs)) - self.assertEqual(o2, t.outputs[0]) - - def test_remove_output_not_found(self): - t = heat.Template() - self.assertRaises(ValueError, t.remove_output, heat.Output('n', 'v')) - - def test_find_output_by_name(self): - # Setup - t = heat.Template() - t.add_output(heat.Output('a', 'a1')) - t.add_output(heat.Output('b', 'b1')) - - # Test - found = t.find_output_by_name('b') - - # Verify - self.assertEqual(found.value, 'b1') - - def test_find_output_by_name_missing(self): - t = heat.Template() - found = t.find_output_by_name('missing') - self.assertEqual(found, None) - - -class ParameterGroupTests(unittest.TestCase): - - def test_init(self): - # Test - g = heat.ParameterGroup('test-label', 'test-desc') - str(g) # should not raise an exception - - # Verify - self.assertEqual(g.label, 'test-label') - self.assertEqual(g.description, 'test-desc') - self.assertEqual(0, len(g.parameter_names)) - - def test_add_remove_property_name(self): - g = heat.ParameterGroup('l', 'd') - - # Test Add - g.add_parameter_name('p1') - self.assertEqual(1, len(g.parameter_names)) - self.assertEqual('p1', g.parameter_names[0]) - - # Test Remove - g.remove_parameter_name('p1') - self.assertEqual(0, len(g.parameter_names)) - - def test_remove_name_not_found(self): - g = heat.ParameterGroup('l', 'd') - g.remove_parameter_name('n1') # should not error - - -class ParameterTests(unittest.TestCase): - - def test_init(self): - # Test - p = heat.Parameter('test-name', 'test-type', description='test-desc', - label='test-label', default='test-default', - hidden='test-hidden') - str(p) # should not error - - # Verify - self.assertEqual('test-name', p.name) - self.assertEqual('test-type', p.param_type) - self.assertEqual('test-desc', p.description) - self.assertEqual('test-label', p.label) - self.assertEqual('test-default', p.default) - self.assertEqual('test-hidden', p.hidden) - - def test_add_remove_constraint(self): - p = heat.Parameter('n', 't') - c = heat.ParameterConstraint('t', 'd') - - # Test Add - p.add_constraint(c) - self.assertEqual(1, len(p.constraints)) - self.assertEqual(c, p.constraints[0]) - - # Test Remove - p.remove_constraint(c) - self.assertEqual(0, len(p.constraints)) - - def test_remove_constraint_not_found(self): - p = heat.Parameter('n', 't') - self.assertRaises(ValueError, p.remove_constraint, - heat.ParameterConstraint('t', 'd')) - - -class ParameterConstraintTests(unittest.TestCase): - - def test_init(self): - # Test - c = heat.ParameterConstraint('test-type', 'test-def', - description='test-desc') - str(c) # should not error - - # Verify - self.assertEqual('test-type', c.constraint_type) - self.assertEqual('test-def', c.definition) - self.assertEqual('test-desc', c.description) - - -class ResourceTests(unittest.TestCase): - - def test_init(self): - # Test - r = heat.Resource('test-id', - 'test-type', - metadata='test-meta', - depends_on='test-depends', - update_policy='test-update', - deletion_policy='test-delete') - str(r) # should not error - - # Verify - self.assertEqual('test-id', r.resource_id) - self.assertEqual('test-type', r.resource_type) - self.assertEqual('test-meta', r.metadata) - self.assertEqual('test-depends', r.depends_on) - self.assertEqual('test-update', r.update_policy) - self.assertEqual('test-delete', r.deletion_policy) - - def test_add_remove_property(self): - r = heat.Resource('i', 't') - p = heat.ResourceProperty('n', 'v') - - # Test Add - r.add_property(p) - self.assertEqual(1, len(r.properties)) - self.assertEqual(p, r.properties[0]) - - # Test Remove - r.remove_property(p) - self.assertEqual(0, len(r.properties)) - - def test_remove_property_not_found(self): - r = heat.Resource('i', 't') - self.assertRaises(ValueError, r.remove_property, - heat.ResourceProperty('n', 'v')) - - def test_find_property_by_name(self): - # Setup - r = heat.Resource('i', 't') - r.add_property(heat.ResourceProperty('a', 'a1')) - r.add_property(heat.ResourceProperty('b', 'b1')) - - # Test - found = r.find_property_by_name('b') - - # Verify - self.assertTrue(found is not None) - self.assertEqual(found.value, 'b1') - - def test_find_property_by_name_missing(self): - r = heat.Resource('i', 't') - found = r.find_property_by_name('missing') - self.assertEqual(found, None) - - -class ResourcePropertyTests(unittest.TestCase): - - def test_init(self): - # Test - p = heat.ResourceProperty('test-name', 'test-value') - str(p) # should not error - - # Verify - self.assertEqual('test-name', p.name) - self.assertEqual('test-value', p.value) - - -class OutputTests(unittest.TestCase): - - def test_init(self): - # Test - o = heat.Output('test-name', 'test-value', description='test-desc') - str(o) # should not error - - # Verify - self.assertEqual('test-name', o.name) - self.assertEqual('test-value', o.value) - self.assertEqual('test-desc', o.description) - - -class EnvironmentTests(unittest.TestCase): - - def test_init(self): - # Test - e = heat.Environment() - str(e) # should not error - - def test_add_remove_parameter(self): - e = heat.Environment() - p = heat.EnvironmentParameter('n', 'v') - - # Test Add - e.add_parameter(p) - self.assertEqual(1, len(e.parameters)) - self.assertEqual(p, e.parameters[0]) - - # Test Remove - e.remove_parameter(p) - self.assertEqual(0, len(e.parameters)) - - def test_remove_parameter_not_found(self): - e = heat.Environment() - self.assertRaises(ValueError, e.remove_parameter, - heat.EnvironmentParameter('n', 'v')) - - def test_remove_parameters_by_namespace(self): - # Setup - e = heat.Environment() - - p1 = heat.EnvironmentParameter( - ns.apply_template_namespace('ns1', 'n1'), 'v') - p2 = heat.EnvironmentParameter( - ns.apply_template_namespace('ns2', 'n2'), 'v') - p3 = heat.EnvironmentParameter( - ns.apply_template_namespace('ns1', 'n3'), 'v') - - e.add_parameter(p1) - e.add_parameter(p2) - e.add_parameter(p3) - - # Test - e.remove_parameters_by_namespace('ns1') - - # Verify - self.assertEqual(1, len(e.parameters)) - self.assertEqual(p2, e.parameters[0]) - - def test_add_remove_registry_entry(self): - e = heat.Environment() - re = heat.RegistryEntry('a', 'f') - - # Test Add - e.add_registry_entry(re) - self.assertEqual(1, len(e.registry_entries)) - self.assertEqual(re, e.registry_entries[0]) - - # Test Remove - e.remove_registry_entry(re) - self.assertEqual(0, len(e.registry_entries)) - - # Test unique add - e.add_registry_entry(re, unique=True) - e.add_registry_entry(re, unique=True) - self.assertEqual(1, len(e.registry_entries)) - - def test_remove_registry_entry_not_found(self): - e = heat.Environment() - self.assertRaises(ValueError, e.remove_registry_entry, - heat.RegistryEntry('a', 'f')) - - def test_remove_registry_entry_by_namespace(self): - # Setup - e = heat.Environment() - - e.add_registry_entry(heat.RegistryEntry('a1', 'f1')) - e.add_registry_entry(heat.RegistryEntry('a2', 'f2')) - e.add_registry_entry(heat.RegistryEntry('a1', 'f3')) - - # Test - e.remove_registry_entry_by_alias('a1') - - # Verify - self.assertEqual(1, len(e.registry_entries)) - self.assertEqual(e.registry_entries[0].filename, 'f2') - - def test_find_parameter_by_name(self): - # Setup - e = heat.Environment() - parameter = heat.EnvironmentParameter('p1', 'v1') - e.add_parameter(parameter) - - # Test - found = e.find_parameter_by_name('p1') - - # Verify - self.assertTrue(found is parameter) - - def test_find_parameter_by_name_missing_parameter(self): - # Setup - e = heat.Environment() - - # Test - found = e.find_parameter_by_name('missing') - self.assertEqual(found, None) - - def test_has_parameter_in_namespace(self): - # Setup - e = heat.Environment() - e.add_parameter(heat.EnvironmentParameter('ns1::p1', 'v1')) - - # Test - self.assertTrue(e.has_parameter_in_namespace('ns1')) - self.assertFalse(e.has_parameter_in_namespace('ns2')) - - -class EnvironmentParameterTests(unittest.TestCase): - - def test_init(self): - # Test - p = heat.EnvironmentParameter('test-name', 'test-value') - str(p) # should not error - - # Verify - self.assertEqual('test-name', p.name) - self.assertEqual('test-value', p.value) - - -class RegistryEntryTest(unittest.TestCase): - - def test_is_filename(self): - re = heat.RegistryEntry('Tuskar::compute-1', 'provider-compute-1.yaml') - self.assertTrue(re.is_filename()) - - re = heat.RegistryEntry('OS::TripleO::StructuredDeployment', - 'OS::Heat::StructuredDeployment') - self.assertFalse(re.is_filename()) - - -class ModuleMethodTests(unittest.TestCase): - - def test_safe_strip(self): - self.assertEqual('foo', heat._safe_strip(' foo ')) - self.assertEqual(None, heat._safe_strip(None)) diff --git a/tuskar/tests/templates/test_namespace.py b/tuskar/tests/templates/test_namespace.py deleted file mode 100644 index 3419347c..00000000 --- a/tuskar/tests/templates/test_namespace.py +++ /dev/null @@ -1,42 +0,0 @@ -# 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 unittest - -from tuskar.templates import namespace - - -class NamespaceTests(unittest.TestCase): - - def test_apply_template_namespace(self): - namespaced = namespace.apply_template_namespace('test-ns', 'test-name') - self.assertEqual(namespaced, 'test-ns::test-name') - self.assertTrue(namespace.matches_template_namespace('test-ns', - namespaced)) - - def test_remove_template_namespace(self): - stripped = namespace.remove_template_namespace('test-ns::test-name') - self.assertEqual(stripped, 'test-name') - - def test_matches_template_namespace(self): - value = 'test-ns::test-name' - self.assertTrue(namespace.matches_template_namespace('test-ns', value)) - self.assertFalse(namespace.matches_template_namespace('fake', value)) - - def test_apply_resource_alias_namespace(self): - namespaced = namespace.apply_resource_alias_namespace('compute') - self.assertEqual(namespaced, 'Tuskar::compute') - - def test_remove_resource_alias_namespace(self): - stripped = namespace.remove_resource_alias_namespace( - 'Tuskar::controller') - self.assertEqual(stripped, 'controller') diff --git a/tuskar/tests/templates/test_parser.py b/tuskar/tests/templates/test_parser.py deleted file mode 100644 index ca31a181..00000000 --- a/tuskar/tests/templates/test_parser.py +++ /dev/null @@ -1,199 +0,0 @@ -# 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 unittest - -from tuskar.templates import heat -from tuskar.templates import parser - - -TEST_TEMPLATE = """ -heat_template_version: 2013-05-23 - -description: Test provider resource foo - -parameters: - - key_name: - type: string - description : Name of a KeyPair - hidden: true - label: Key - - instance_type: - type: string - description: Instance type - default: m1.small - constraints: - - allowed_values: [m1.small, m1.medium, m1.large] - description: instance_type must be one of m1.small or m1.medium - - image_id: - type: string - description: ID of the image to use - default: 3e6270da-fbf7-4aef-bc78-6d0cfc3ad11b - -parameter_groups: - - - label: group-1 - description: first group - parameters: - - key_name - - instance_type - - - label: group-2 - description: second group - parameters: - - image_id - -resources: - foo_instance: - type: OS::Nova::Server - properties: - image: { get_param: image_id } - flavor: { get_param: instance_type } - key_name: { get_param: key_name } - -outputs: - foo_ip: - description: IP of the created foo instance - value: { get_attr: [foo_instance, first_address] } -""" - -TEST_ENVIRONMENT = """ -parameters: - key_name: heat_key - instance_type: m1.small - image_id: 3e6270da-fbf7-4aef-bc78-6d0cfc3ad11b - -resource_registry: - Tuskar::Foo: provider-foo.yaml - Tuskar::Bar: provider-bar.yaml - OS::TripleO::SoftwareDeployment: OS::Heat::StructuredDeployment -""" - - -class ParserTests(unittest.TestCase): - - def test_parse_template(self): - # Test - t = parser.parse_template(TEST_TEMPLATE) - - # Verify - self.assertTrue(isinstance(t, heat.Template)) - self.assertEqual(t.version, datetime.date(2013, 5, 23)) - self.assertEqual(t.description, 'Test provider resource foo') - - self.assertEqual(3, len(t.parameters)) - ordered_params = sorted(t.parameters, key=lambda x: x.name) - - # Image ID Parameter - self.assertEqual('image_id', ordered_params[0].name) - self.assertEqual('string', ordered_params[0].param_type) - self.assertEqual('ID of the image to use', - ordered_params[0].description) - self.assertEqual('3e6270da-fbf7-4aef-bc78-6d0cfc3ad11b', - ordered_params[0].default) - self.assertEqual(None, ordered_params[0].hidden) - self.assertEqual(None, ordered_params[0].label) - self.assertEqual(0, len(ordered_params[0].constraints)) - - # Instance Type Parameter - self.assertEqual('instance_type', ordered_params[1].name) - self.assertEqual('string', ordered_params[1].param_type) - self.assertEqual('Instance type', ordered_params[1].description) - self.assertEqual('m1.small', ordered_params[1].default) - self.assertEqual(None, ordered_params[1].hidden) - self.assertEqual(None, ordered_params[1].label) - self.assertEqual(1, len(ordered_params[1].constraints)) - c = ordered_params[1].constraints[0] - self.assertEqual('instance_type must be one of m1.small or m1.medium', - c.description) - self.assertEqual('allowed_values', c.constraint_type) - self.assertEqual(['m1.small', 'm1.medium', 'm1.large'], c.definition) - - # Key Name Parameter - self.assertEqual('key_name', ordered_params[2].name) - self.assertEqual('string', ordered_params[2].param_type) - self.assertEqual('Name of a KeyPair', ordered_params[2].description) - self.assertEqual(None, ordered_params[2].default) - self.assertEqual(True, ordered_params[2].hidden) - self.assertEqual('Key', ordered_params[2].label) - self.assertEqual(0, len(ordered_params[2].constraints)) - - # Parameter Groups - self.assertEqual(2, len(t.parameter_groups)) - self.assertEqual('group-1', t.parameter_groups[0].label) - self.assertEqual('first group', - t.parameter_groups[0].description) - self.assertEqual(sorted(('key_name', 'instance_type')), - sorted(t.parameter_groups[0].parameter_names)) - self.assertEqual('group-2', t.parameter_groups[1].label) - self.assertEqual('second group', - t.parameter_groups[1].description) - self.assertEqual(('image_id',), - t.parameter_groups[1].parameter_names) - - # Resources - self.assertEqual(1, len(t.resources)) - self.assertEqual('foo_instance', t.resources[0].resource_id) - self.assertEqual('OS::Nova::Server', t.resources[0].resource_type) - self.assertEqual(3, len(t.resources[0].properties)) - - resource_props = sorted(t.resources[0].properties, - key=lambda x: x.name) - self.assertEqual('flavor', resource_props[0].name) - self.assertEqual({'get_param': 'instance_type'}, - resource_props[0].value) - self.assertEqual('image', resource_props[1].name) - self.assertEqual({'get_param': 'image_id'}, - resource_props[1].value) - self.assertEqual('key_name', resource_props[2].name) - self.assertEqual({'get_param': 'key_name'}, - resource_props[2].value) - - # Outputs - self.assertEqual(1, len(t.outputs)) - self.assertEqual('foo_ip', t.outputs[0].name) - self.assertEqual({'get_attr': ['foo_instance', 'first_address']}, - t.outputs[0].value) - - def test_parse_environment(self): - # Test - e = parser.parse_environment(TEST_ENVIRONMENT) - - # Verify - self.assertTrue(isinstance(e, heat.Environment)) - - # Parameters - self.assertEqual(3, len(e.parameters)) - ordered_params = sorted(e.parameters, key=lambda x: x.name) - self.assertEqual('image_id', ordered_params[0].name) - self.assertEqual('3e6270da-fbf7-4aef-bc78-6d0cfc3ad11b', - ordered_params[0].value) - self.assertEqual('instance_type', ordered_params[1].name) - self.assertEqual('m1.small', ordered_params[1].value) - self.assertEqual('key_name', ordered_params[2].name) - self.assertEqual('heat_key', ordered_params[2].value) - - # Resource Registry - self.assertEqual(3, len(e.registry_entries)) - ordered_entries = sorted(e.registry_entries, key=lambda x: x.alias) - self.assertEqual('OS::TripleO::SoftwareDeployment', - ordered_entries[0].alias) - self.assertEqual('OS::Heat::StructuredDeployment', - ordered_entries[0].filename) - self.assertEqual('Tuskar::Bar', ordered_entries[1].alias) - self.assertEqual('provider-bar.yaml', ordered_entries[1].filename) - self.assertEqual('Tuskar::Foo', ordered_entries[2].alias) - self.assertEqual('provider-foo.yaml', ordered_entries[2].filename) diff --git a/tuskar/tests/templates/test_plan.py b/tuskar/tests/templates/test_plan.py deleted file mode 100644 index a1de31f4..00000000 --- a/tuskar/tests/templates/test_plan.py +++ /dev/null @@ -1,237 +0,0 @@ -# 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 unittest - -from tuskar.templates import heat -from tuskar.templates import namespace as ns_utils -from tuskar.templates import plan - - -class DeploymentPlanTests(unittest.TestCase): - - def test_empty(self): - # Test - p = plan.DeploymentPlan(description='test-desc') - str(p) # should not error - - # Verify - self.assertTrue(isinstance(p.master_template, heat.Template)) - self.assertTrue(isinstance(p.environment, heat.Environment)) - self.assertEqual('test-desc', p.master_template.description) - self.assertEqual(p.add_scaling, True) - - def test_existing_pieces(self): - # Test - t = heat.Template() - e = heat.Environment() - p = plan.DeploymentPlan(master_template=t, environment=e) - - # Verify - self.assertTrue(p.master_template is t) - self.assertTrue(p.environment is e) - - def test_add_template_no_scaling(self): - # Test - p = plan.DeploymentPlan(add_scaling=False) - t = self._generate_template() - p.add_template('ns1', t, 'template-1.yaml') - - # Verify Master Template Parameters - self.assertEqual(2, len(p.master_template.parameters)) - for original, added in zip(t.parameters, p.master_template.parameters): - self.assertTrue(added is not original) - - expected_name = ns_utils.apply_template_namespace('ns1', - original.name) - self.assertEqual(added.name, expected_name) - self.assertEqual(added.param_type, original.param_type) - - # Verify Resource - self.assertEqual(1, len(p.master_template.resources)) - added = p.master_template.resources[0] - - expected_id = plan.generate_resource_id('ns1') - self.assertEqual(added.resource_id, expected_id) - expected_type = ns_utils.apply_resource_alias_namespace('ns1') - self.assertEqual(added.resource_type, expected_type) - - for param, prop in zip(t.parameters, added.properties): - v = ns_utils.apply_template_namespace('ns1', param.name) - expected_value = {'get_param': [v]} - self.assertEqual(prop.value, expected_value) - - # Verify Environment Parameters - self.assertEqual(2, len(p.environment.parameters)) - for env_param, template_param in zip(p.environment.parameters, - t.parameters): - expected_name = ( - ns_utils.apply_template_namespace('ns1', template_param.name)) - self.assertEqual(env_param.name, expected_name) - self.assertEqual(env_param.value, '') - - # Verify Resource Registry Entry - self.assertEqual(1, len(p.environment.registry_entries)) - added = p.environment.registry_entries[0] - expected_alias = ns_utils.apply_resource_alias_namespace('ns1') - self.assertEqual(added.alias, expected_alias) - self.assertEqual(added.filename, 'template-1.yaml') - - def test_add_template_with_default_parameter_value(self): - # Test - p = plan.DeploymentPlan() - t = heat.Template() - t.add_parameter(heat.Parameter('param-1', 'type-1', default='d1')) - t.add_parameter(heat.Parameter('param-2', 'type-2')) - t.add_parameter(heat.Parameter('param-3', 'type-3', default=0)) - p.add_template('ns1', t, 'template-1.yaml') - - # Verify - p1 = p.environment.parameters[0] - self.assertEqual(ns_utils.apply_template_namespace('ns1', 'param-1'), - p1.name) - self.assertEqual('d1', p1.value) - - p2 = p.environment.parameters[1] - self.assertEqual(ns_utils.apply_template_namespace('ns1', 'param-2'), - p2.name) - self.assertEqual('', p2.value) - - p3 = p.environment.parameters[2] - self.assertEqual(ns_utils.apply_template_namespace('ns1', 'param-3'), - p3.name) - self.assertEqual(0, p3.value) - - def test_add_template_with_colliding_namespace(self): - # Test - p = plan.DeploymentPlan() - p.environment.add_parameter( - heat.EnvironmentParameter('ns1::param-1', 'value-1')) - t = heat.Template() - t.add_parameter(heat.Parameter('param-2', 'type-1')) - - # Verify - self.assertRaises(ValueError, - p.add_template, 'ns1', t, 'template-1.yaml') - - def test_add_scaling_with_scaling(self): - # Test - p = plan.DeploymentPlan(add_scaling=True) - t = self._generate_template() - p.add_template('ns1', t, 'template-1.yaml') - - # Verify Master Template Count Parameters - self.assertEqual(4, len(p.master_template.parameters)) - count_param = p.master_template.parameters[2] - expected_count_name = plan.generate_count_property_name('ns1') - self.assertEqual(count_param.name, expected_count_name) - self.assertEqual(count_param.param_type, 'number') - - removal_param = p.master_template.parameters[3] - expected_removal_name = plan.generate_removal_policies_name('ns1') - self.assertEqual(removal_param.name, expected_removal_name) - self.assertEqual(removal_param.param_type, 'json') - - self.assertEqual(1, len(count_param.constraints)) - const = count_param.constraints[0] - self.assertTrue(isinstance(const, heat.ParameterConstraint)) - self.assertEqual(const.constraint_type, 'range') - self.assertEqual(const.definition, {'min': '0'}) - - # Verify Resource Group Wrapper - self.assertEqual(1, len(p.master_template.resources)) - group_res = p.master_template.resources[0] - group_id = plan.generate_group_id('ns1') - self.assertEqual(group_res.resource_id, group_id) - self.assertEqual(group_res.resource_type, - plan.HEAT_TYPE_RESOURCE_GROUP) - - self.assertEqual(3, len(group_res.properties)) - - count_prop = group_res.properties[0] - self.assertEqual(count_prop.name, plan.PROPERTY_SCALING_COUNT) - self.assertEqual(count_prop.value, - {'get_param': [expected_count_name]}) - - removal_prop = group_res.properties[1] - self.assertEqual(removal_prop.name, plan.PROPERTY_REMOVAL_POLICIES) - self.assertEqual(removal_prop.value, - {'get_param': [expected_removal_name]}) - - def_prop = group_res.properties[2] - self.assertEqual(def_prop.name, plan.PROPERTY_RESOURCE_DEFINITION) - self.assertTrue(isinstance(def_prop.value, heat.Resource)) - - # Verify Environment Parameters - self.assertEqual(4, len(p.environment.parameters)) - count_param = p.environment.parameters[2] - self.assertEqual(count_param.name, expected_count_name) - self.assertEqual(count_param.value, '1') - removal_param = p.environment.parameters[3] - self.assertEqual(removal_param.name, expected_removal_name) - self.assertEqual(removal_param.value, []) - - def test_remove_template(self): - # Setup & Sanity Check - p = plan.DeploymentPlan(add_scaling=False) - t = self._generate_template() - p.add_template('ns1', t, 'template-1.yaml') - p.add_template('ns2', t, 'template-2.yaml') - - self.assertEqual(4, len(p.master_template.parameters)) - self.assertEqual(0, len(p.master_template.outputs)) - self.assertEqual(2, len(p.master_template.resources)) - - self.assertEqual(4, len(p.environment.parameters)) - self.assertEqual(2, len(p.environment.registry_entries)) - - # Test - p.remove_template('ns1') - - # Verify - self.assertEqual(2, len(p.master_template.parameters)) - self.assertEqual(0, len(p.master_template.outputs)) - self.assertEqual(1, len(p.master_template.resources)) - - self.assertEqual(2, len(p.environment.parameters)) - self.assertEqual(1, len(p.environment.registry_entries)) - - def test_set_value(self): - # Setup - p = plan.DeploymentPlan() - set_me = heat.EnvironmentParameter('p1', 'v1') - p.environment.add_parameter(set_me) - - # Test - p.set_value('p1', 'v2') - - # Verify - self.assertEqual(p.environment.find_parameter_by_name('p1').value, - 'v2') - - def test_set_value_missing_parameter(self): - # Setup - p = plan.DeploymentPlan() - - # Test - self.assertRaises(ValueError, p.set_value, 'missing', 'irrelevant') - - def _generate_template(self): - t = heat.Template() - - t.add_parameter(heat.Parameter('param-1', 'type-1')) - t.add_parameter(heat.Parameter('param-2', 'type-2')) - - t.add_output(heat.Output('out-1', 'value-1')) - t.add_output(heat.Output('out-2', 'value-2')) - - return t diff --git a/tuskar/tests/templates/test_template_seed.py b/tuskar/tests/templates/test_template_seed.py deleted file mode 100644 index 31c3b0e5..00000000 --- a/tuskar/tests/templates/test_template_seed.py +++ /dev/null @@ -1,281 +0,0 @@ -# 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 unittest - -from tuskar.templates import heat -from tuskar.templates import parser -from tuskar.templates import plan -from tuskar.templates import template_seed - - -SEED_TEMPLATE = """ -heat_template_version: 2014-10-16 - -description: test template - -parameters: - - # Top Level Parameters - TopLevelParameter1: - type: string - TopLevelParameter2: - type: json - default: [] - - # Role Parameters - RoleParameterCount: - type: int - - RoleParameter1: - type: string - - RoleParameter2: - type: string - -resources: - - # Top Level Resources - TopLevelResource1: - type: OS::Heat::StructuredConfig - properties: - resource_def: - type: OS::Heat::StructuredDeployment - properties: - config-1: {get_param: TopLevelParameter1} - config-2: {get_attr: [RoleResource1, key-1]} - config-3: {get_resource: RoleResource1} - config-4: - hosts: - list_join: - - "+" - - - list_join: - - "+" - - {get_attr: [RoleResource1, hosts_entry]} - - list_join: - - "+" - - {get_attr: [Controller, hosts_entry]} - - TopLevelResource2: - type: OS::Heat::RandomString - properties: - config-1: {get_param: TopLevelParameter1} - config-2: {get_attr: [RoleResourceX, key-1]} - config-3: - sub-1: - sub-2: {get_param: RoleParameter1} - - # Role Resources - RoleResource1: - type: OS::Heat::ResourceGroup - properties: - count: {get_param: RoleParameterCount} - resource_def: - type: OS::TripleO::Controller - properties: - RoleParameter1: {get_param: RoleParameter1} - RoleParameter2: {get_param: RoleParameter2} - RoleParameter3: {get_attr: [TopLevelResource2, value]} - -outputs: - - Output1: - description: output1 - value: {get_attr: [TopLevelResource2, value]} -""" - -ROLE_TEMPLATE = """ -heat_template_version: 2014-10-16 - -parameters: - ExistingRoleParameter: - type: string -""" - - -class TemplateSeedTests(unittest.TestCase): - - def setUp(self): - super(TemplateSeedTests, self).setUp() - - self.seed_template = parser.parse_template(SEED_TEMPLATE) - self.role_template = parser.parse_template(ROLE_TEMPLATE) - self.destination_template = heat.Template() - self.environment = heat.Environment() - - def test_add_top_level_parameters(self): - # Test - template_seed.add_top_level_parameters(self.seed_template, - self.destination_template, - self.environment) - - # Verify - self.assertEqual(2, len(self.destination_template.parameters)) - added_parameter_names = [p.name for p - in self.destination_template.parameters] - self.assertEqual(['TopLevelParameter1', 'TopLevelParameter2'], - sorted(added_parameter_names)) - - added_parameter_names = [p.name for p - in self.environment.parameters] - self.assertEqual(['TopLevelParameter1', 'TopLevelParameter2'], - sorted(added_parameter_names)) - - for param in self.environment.parameters: - if param.name == 'TopLevelParameter1': - self.assertEqual('', param.value) - if param.name == 'TopLevelParameter2': - self.assertEqual([], param.value) - - def test_add_top_level_parameters_idempotency(self): - # Test - template_seed.add_top_level_parameters(self.seed_template, - self.destination_template, - self.environment) - template_seed.add_top_level_parameters(self.seed_template, - self.destination_template, - self.environment) - - # Verify - self.assertEqual(2, len(self.destination_template.parameters)) - - def test_add_top_level_resources(self): - # Test - template_seed.add_top_level_resources(self.seed_template, - self.destination_template) - - # Verify - self.assertEqual(2, len(self.destination_template.resources)) - added_resource_ids = [r.resource_id for r - in self.destination_template.resources] - self.assertEqual(['TopLevelResource1', 'TopLevelResource2'], - sorted(added_resource_ids)) - - def test_add_top_level_resources_idempotency(self): - # Test - template_seed.add_top_level_resources(self.seed_template, - self.destination_template) - template_seed.add_top_level_resources(self.seed_template, - self.destination_template) - - # Verify - self.assertEqual(2, len(self.destination_template.resources)) - - def test_add_top_level_outputs(self): - # Test - template_seed.add_top_level_outputs(self.seed_template, - self.destination_template) - - # Verify - self.assertEqual(1, len(self.destination_template.outputs)) - self.assertEqual('Output1', self.destination_template.outputs[0].name) - - def test_add_top_level_outputs_idempotency(self): - # Test - template_seed.add_top_level_outputs(self.seed_template, - self.destination_template) - template_seed.add_top_level_outputs(self.seed_template, - self.destination_template) - - # Verify - self.assertEqual(1, len(self.destination_template.outputs)) - - def test_get_property_map_for_role(self): - # Test - mapping = template_seed.get_property_map_for_role( - self.seed_template, 'OS::TripleO::Controller') - - # Verify - self.assertEqual(1, len(mapping)) - self.assertTrue('RoleParameter3' in mapping) - self.assertEqual(mapping['RoleParameter3'], - {'get_attr': ['TopLevelResource2', 'value']}) - - def test_get_property_map_for_nonexistent_role(self): - mapping = template_seed.get_property_map_for_role(self.seed_template, - 'missing') - self.assertTrue(mapping is None) - - def test_update_role_references(self): - # Test - # This will update the seed template in place. It's good enough for - # a test as there is data within that exercises this call. - seed_role = template_seed.find_role_from_type( - self.seed_template.resources, - 'OS::TripleO::Controller') - template_seed.update_role_resource_references( - self.seed_template, seed_role, 'converted' - ) - - # Verify - updated = self.seed_template.find_resource_by_id('TopLevelResource1') - resource_def = updated.properties[0] - config_props = resource_def.value['properties'] - self.assertEqual(config_props['config-1'], - {'get_param': 'TopLevelParameter1'}) - self.assertEqual(config_props['config-2'], - {'get_attr': ['converted', 'key-1']}) - self.assertEqual(config_props['config-3'], - {'get_resource': 'converted'}) - - self.maxDiff = None - config_4 = { - 'hosts': { - 'list_join': - [ - '+', - [ - {'list_join': [ - '+', - {'get_attr': ['converted', 'hosts_entry']} - ]}, - {'list_join': [ - '+', - {'get_attr': ['Controller', 'hosts_entry']} - ]} - ] - ] - } - } - self.assertEqual(config_props['config-4'], config_4) - - untouched = self.seed_template.find_resource_by_id('TopLevelResource2') - self.assertEqual(untouched.find_property_by_name('config-1').value, - {'get_param': 'TopLevelParameter1'}) - self.assertEqual(untouched.find_property_by_name('config-2').value, - {'get_attr': ['RoleResourceX', 'key-1']}) - - def test_update_role_property_references(self): - # Setup - dp = plan.DeploymentPlan() - dp.add_template('ns1', self.role_template, 'foo.yaml') - - template_seed.add_top_level_resources(self.seed_template, - dp.master_template) - template_seed.add_top_level_resources(self.seed_template, - dp.master_template) - - # Test - seed_role = template_seed.find_role_from_type( - self.seed_template.resources, - 'OS::TripleO::Controller') - template_seed.update_role_property_references( - dp.master_template, - seed_role, 'ns1') - - # Verify - tlr2 = dp.master_template.find_resource_by_id('TopLevelResource2') - self.assertTrue(tlr2 is not None) - c3 = tlr2.find_property_by_name('config-3') - sub2 = c3.value['sub-1']['sub-2'] - self.assertTrue('get_param' in sub2) - self.assertEqual('ns1::RoleParameter1', sub2['get_param']) diff --git a/tuskar/tests/test_dbsync.py b/tuskar/tests/test_dbsync.py deleted file mode 100644 index 1a80b628..00000000 --- a/tuskar/tests/test_dbsync.py +++ /dev/null @@ -1,28 +0,0 @@ -# -# Copyright 2013 Hewlett-Packard Development Company, L.P. -# All Rights Reserved. -# -# 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 tuskar.db import migration -from tuskar.tests.db import base - - -class DbSyncTestCase(base.DbTestCase): - def setUp(self): - super(DbSyncTestCase, self).setUp() - - def test_sync_and_version(self): - migration.db_sync() - v = migration.db_version() - self.assertTrue(v > migration.INIT_VERSION) diff --git a/tuskar/tests/test_utils.py b/tuskar/tests/test_utils.py deleted file mode 100644 index 3d6fce32..00000000 --- a/tuskar/tests/test_utils.py +++ /dev/null @@ -1,83 +0,0 @@ -# Copyright 2011 Justin Santa Barbara -# Copyright 2012 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. - -from tuskar.common import utils -from tuskar.storage import models -from tuskar.tests import base - - -class CommonUtilsTestCase(base.TestCase): - - def test_resolve_role_extra_name_from_path(self): - expected = [{"/path/to/FOO": "extra_FOO_"}, - {"/hieradata/config.yaml": "extra_config_yaml"}, - {"./name.has.dots": "extra_name.has_dots"}, - {"/path/name.": "extra_name_"}, - {"/path/cdefile.c": "extra_cdefile_c"}, - {"./name_underscore_no_extension": - "extra_name_underscore_no_extension_"}, - {"/path/name_underscore.ext": - "extra_name_underscore_ext"}, ] - - for params in expected: - path = params.keys()[0] - res = utils.resolve_role_extra_name_from_path(path) - self.assertEqual(params[path], res) - - def test_resolve_template_file_name_from_role_extra_name(self): - expected = [{"extra_FOO_": "FOO"}, - {"extra_config_yaml": "config.yaml"}, - {"extra_name.has_dots": "name.has.dots"}, - {"extra_name_": "name"}, - {"extra_cdefile_c": "cdefile.c"}, - {"extra_name_underscore_no_extension_": - "name_underscore_no_extension"}, - {"extra_name_underscore_ext": "name_underscore.ext"}, ] - for params in expected: - name = params.keys()[0] - res = utils.resolve_template_file_name_from_role_extra_name(name) - self.assertEqual(params[name], res) - - def test_resolve_template_extra_data(self): - template_contents = """ Foo Bar Baz - get_file: foo/bar.baz - """ - template_extra = models.StoredFile( - uuid="1234", contents="boo!", store=None, name="extra_bar_baz") - template = models.StoredFile( - uuid="1234", contents=template_contents, store=None) - res = utils.resolve_template_extra_data(template, [template_extra]) - self.assertEqual(res, [{"extra_bar_baz": "foo/bar.baz"}]) - - -class IntLikeTestCase(base.TestCase): - - def test_is_int_like(self): - self.assertTrue(utils.is_int_like(1)) - self.assertTrue(utils.is_int_like("1")) - self.assertTrue(utils.is_int_like("514")) - self.assertTrue(utils.is_int_like("0")) - - self.assertFalse(utils.is_int_like(1.1)) - self.assertFalse(utils.is_int_like("1.1")) - self.assertFalse(utils.is_int_like("1.1.1")) - self.assertFalse(utils.is_int_like(None)) - self.assertFalse(utils.is_int_like("0.")) - self.assertFalse(utils.is_int_like("aaaaaa")) - self.assertFalse(utils.is_int_like("....")) - self.assertFalse(utils.is_int_like("1g")) - self.assertFalse( - utils.is_int_like("0cc3346e-9fef-4445-abe6-5d2b2690ec64")) - self.assertFalse(utils.is_int_like("a1")) diff --git a/tuskar/version.py b/tuskar/version.py deleted file mode 100644 index 56103f4e..00000000 --- a/tuskar/version.py +++ /dev/null @@ -1,18 +0,0 @@ -# Copyright 2011 OpenStack Foundation -# All Rights Reserved. -# -# 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_info = pbr.version.VersionInfo('tuskar')