Retire the Tuskar codebase
Change-Id: If724a2cbde086d4c4341539a2e51ee4fd1915a26 Depends-On: I904b2f27591333e104bf9080bb8c3876fcb3596c
This commit is contained in:
parent
3e67a04813
commit
b129603824
@ -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
|
176
LICENSE
176
LICENSE
@ -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.
|
||||
|
@ -1,6 +0,0 @@
|
||||
include AUTHORS
|
||||
include ChangeLog
|
||||
exclude .gitignore
|
||||
exclude .gitreview
|
||||
|
||||
global-exclude *.pyc
|
10
README
Normal file
10
README
Normal file
@ -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.
|
45
README.rst
45
README.rst
@ -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. <https://www.youtube.com/watch?v=--WWdJXmf8o>`_
|
||||
|
||||
For additional information, take a look at the `Tuskar
|
||||
documentation <http://git.openstack.org/cgit/openstack/tuskar/tree/docs/index.rst>`_.
|
||||
|
||||
|
||||
Installation Information
|
||||
------------------------
|
||||
|
||||
Please see `install.rst <doc/source/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
|
||||
<doc/source/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
|
@ -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:
|
@ -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
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
Binary file not shown.
Before Width: | Height: | Size: 48 B |
Binary file not shown.
Before Width: | Height: | Size: 3.7 KiB |
@ -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,"<a href=\"$1\">$1</a>"));
|
||||
});
|
||||
return $(returning);
|
||||
},
|
||||
linkUser: function() {
|
||||
var returning = [];
|
||||
var regexp = /[\@]+([A-Za-z0-9-_]+)/gi;
|
||||
this.each(function() {
|
||||
returning.push(this.replace(regexp,"<a href=\"http://twitter.com/$1\">@$1</a>"));
|
||||
});
|
||||
return $(returning);
|
||||
},
|
||||
linkHash: function() {
|
||||
var returning = [];
|
||||
var regexp = / [\#]+([A-Za-z0-9-_]+)/gi;
|
||||
this.each(function() {
|
||||
returning.push(this.replace(regexp, ' <a href="http://search.twitter.com/search?q=&tag=$1&lang=all&from='+s.username.join("%2BOR%2B")+'">#$1</a>'));
|
||||
});
|
||||
return $(returning);
|
||||
},
|
||||
capAwesome: function() {
|
||||
var returning = [];
|
||||
this.each(function() {
|
||||
returning.push(this.replace(/\b(awesome)\b/gi, '<span class="awesome">$1</span>'));
|
||||
});
|
||||
return $(returning);
|
||||
},
|
||||
capEpic: function() {
|
||||
var returning = [];
|
||||
this.each(function() {
|
||||
returning.push(this.replace(/\b(epic)\b/gi, '<span class="epic">$1</span>'));
|
||||
});
|
||||
return $(returning);
|
||||
},
|
||||
makeHeart: function() {
|
||||
var returning = [];
|
||||
this.each(function() {
|
||||
returning.push(this.replace(/(<)+[3]/gi, "<tt class='heart'>♥</tt>"));
|
||||
});
|
||||
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 = $('<ul class="tweet_list">').appendTo(this);
|
||||
var intro = '<p class="tweet_intro">'+s.intro_text+'</p>';
|
||||
var outro = '<p class="tweet_outro">'+s.outro_text+'</p>';
|
||||
var loading = $('<p class="loading">'+s.loading_text+'</p>');
|
||||
|
||||
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 = '<span class="tweet_join"> '+join_text+' </span>';
|
||||
var join = ((s.join_text) ? join_template : ' ');
|
||||
var avatar_template = '<a class="tweet_avatar" href="http://twitter.com/'+from_user+'"><img src="'+profile_image_url+'" height="'+s.avatar_size+'" width="'+s.avatar_size+'" alt="'+from_user+'\'s avatar" title="'+from_user+'\'s avatar" border="0"/></a>';
|
||||
var avatar = (s.avatar_size ? avatar_template : '');
|
||||
var date = '<a href="http://twitter.com/'+from_user+'/statuses/'+item.id+'" title="view tweet on twitter">'+relative_time(item.created_at)+'</a>';
|
||||
var text = '<span class="tweet_text">' +$([item.text]).linkUrl().linkUser().linkHash().makeHeart().capAwesome().capEpic()[0]+ '</span>';
|
||||
|
||||
// until we create a template option, arrange the items below to alter a tweet's display.
|
||||
list.append('<li>' + avatar + date + join + text + '</li>');
|
||||
|
||||
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);
|
@ -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;
|
||||
}
|
Binary file not shown.
Before Width: | Height: | Size: 3.6 KiB |
@ -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 */
|
@ -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;
|
||||
}
|
@ -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 %}
|
||||
<div class="sphinxsidebar">
|
||||
<div class="sphinxsidebarwrapper">
|
||||
{%- block sidebarlogo %}
|
||||
{%- if logo %}
|
||||
<p class="logo"><a href="{{ pathto(master_doc) }}">
|
||||
<img class="logo" src="{{ pathto('_static/' + logo, 1) }}" alt="Logo"/>
|
||||
</a></p>
|
||||
{%- endif %}
|
||||
{%- endblock %}
|
||||
{%- block sidebartoc %}
|
||||
{%- if display_toc %}
|
||||
<h3><a href="{{ pathto(master_doc) }}">{{ _('Table Of Contents') }}</a></h3>
|
||||
{{ toc }}
|
||||
{%- endif %}
|
||||
{%- endblock %}
|
||||
{%- block sidebarrel %}
|
||||
{%- if prev %}
|
||||
<h4>{{ _('Previous topic') }}</h4>
|
||||
<p class="topless"><a href="{{ prev.link|e }}"
|
||||
title="{{ _('previous chapter') }}">{{ prev.title }}</a></p>
|
||||
{%- endif %}
|
||||
{%- if next %}
|
||||
<h4>{{ _('Next topic') }}</h4>
|
||||
<p class="topless"><a href="{{ next.link|e }}"
|
||||
title="{{ _('next chapter') }}">{{ next.title }}</a></p>
|
||||
{%- endif %}
|
||||
{%- endblock %}
|
||||
{%- block sidebarsourcelink %}
|
||||
{%- if show_source and has_source and sourcename %}
|
||||
<h3>{{ _('This Page') }}</h3>
|
||||
<ul class="this-page-menu">
|
||||
<li><a href="{{ pathto('_sources/' + sourcename, true)|e }}"
|
||||
rel="nofollow">{{ _('Show Source') }}</a></li>
|
||||
</ul>
|
||||
{%- endif %}
|
||||
{%- endblock %}
|
||||
{%- if customsidebar %}
|
||||
{% include customsidebar %}
|
||||
{%- endif %}
|
||||
{%- block sidebarsearch %}
|
||||
{%- if pagename != "search" %}
|
||||
<div id="searchbox" style="display: none">
|
||||
<h3>{{ _('Quick search') }}</h3>
|
||||
<form class="search" action="{{ pathto('search') }}" method="get">
|
||||
<input type="text" name="q" size="18" />
|
||||
<input type="submit" value="{{ _('Go') }}" />
|
||||
<input type="hidden" name="check_keywords" value="yes" />
|
||||
<input type="hidden" name="area" value="default" />
|
||||
</form>
|
||||
<p class="searchtip" style="font-size: 90%">
|
||||
{{ _('Enter search terms or a module, class or function name.') }}
|
||||
</p>
|
||||
</div>
|
||||
<script type="text/javascript">$('#searchbox').show(0);</script>
|
||||
{%- endif %}
|
||||
{%- endblock %}
|
||||
</div>
|
||||
</div>
|
||||
{%- endif %}{% endif %}
|
||||
{%- endmacro %}
|
||||
|
||||
{% block relbar1 %}{% endblock relbar1 %}
|
||||
|
||||
{% block header %}
|
||||
<div id="header">
|
||||
<h1 id="logo"><a href="http://www.openstack.org/">OpenStack</a></h1>
|
||||
<ul id="navigation">
|
||||
<li><a href="http://www.openstack.org/" title="Go to the Home page" class="link">Home</a></li>
|
||||
<li><a href="http://www.openstack.org/projects/" title="Go to the OpenStack Projects page">Projects</a></li>
|
||||
<li><a href="http://www.openstack.org/user-stories/" title="Go to the User Stories page" class="link">User Stories</a></li>
|
||||
<li><a href="http://www.openstack.org/community/" title="Go to the Community page" class="link">Community</a></li>
|
||||
<li><a href="http://www.openstack.org/blog/" title="Go to the OpenStack Blog">Blog</a></li>
|
||||
<li><a href="http://wiki.openstack.org/" title="Go to the OpenStack Wiki">Wiki</a></li>
|
||||
<li><a href="http://docs.openstack.org/" title="Go to OpenStack Documentation" class="current">Documentation</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
{% endblock %}
|
@ -1,4 +0,0 @@
|
||||
[theme]
|
||||
inherit = basic
|
||||
stylesheet = nature.css
|
||||
pygments_style = tango
|
@ -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>`_
|
@ -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'
|
||||
),
|
||||
]
|
@ -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
|
||||
<http://git.openstack.org/cgit/openstack/tuskar>`_, `bugs and
|
||||
blueprints are on Launchpad <https://launchpad.net/tuskar>`_ 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
|
||||
<http://docs.openstack.org/developer/tripleo-
|
||||
incubator/CONTRIBUTING.html>`_.
|
||||
|
||||
|
||||
Coding Standards
|
||||
----------------
|
||||
|
||||
We comply with the `OpenStack coding standards
|
||||
<http://docs.openstack.org/developer/hacking/>`_.
|
||||
|
||||
Be sure to familiarise yourself with `OpenStack's Gerrit Workflow
|
||||
<http://docs.openstack.org/infra/manual/developers.html#development-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
|
||||
<http://www.python.org/dev/peps/pep-0008/>`_ compliance. The identical test
|
||||
suite is run by OpenStack's Jenkins whenever you send a patch.
|
@ -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. <https://www.youtube.com/watch?v=--WWdJXmf8o>`_
|
||||
|
||||
|
||||
High-Level Overview
|
||||
===================
|
||||
|
||||
*TODO* Add project overview
|
||||
|
||||
- *TODO* feature examples
|
||||
- *TODO* link to high-level portion of FAQ
|
||||
- :doc:`Recommended reading <recommended-reading>`
|
||||
|
||||
Related Projects
|
||||
----------------
|
||||
|
||||
- `tuskar-ui <http://git.openstack.org/cgit/openstack/tuskar-
|
||||
ui>`_ - tuskar-ui provides dashboard access to Tuskar
|
||||
functionality as a Horizon plugin. See the `Tuskar UI
|
||||
documentation <http://tuskar-ui.readthedocs.org/en/latest/>`_
|
||||
- `python-tuskarclient <http://git.openstack.org/cgit/openstack
|
||||
/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
|
||||
|
@ -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 <https://bugs.launchpad.net/openstack-ci/+bug/1274135>`_
|
||||
requires that you use a version <1.70 or >= 1.7.2.
|
||||
|
||||
Now create your virtualenv.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ cd <your_src_dir>/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
|
||||
<https://flake8.readthedocs.org>`_ 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=<None>
|
||||
|
||||
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
|
||||
<http://docs.sqlalchemy.org/en/latest/core/engines.html
|
||||
#database-urls>`_ 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 <http://docs.openstack.org/developer/
|
||||
keystone/configuringservices.html>`_
|
||||
|
||||
|
||||
Contributing
|
||||
------------
|
||||
|
||||
For additional developer information, take a look at
|
||||
:doc:`the contributing guide <contributing>`.
|
@ -1,30 +0,0 @@
|
||||
===================
|
||||
Recommended Reading
|
||||
===================
|
||||
|
||||
Tuskar Design Discussions
|
||||
-------------------------
|
||||
|
||||
- `Juno Planning <https://wiki.openstack.org/wiki/TripleO/TuskarJunoPlanning>`_
|
||||
- `Template storage planning <https://wiki.openstack.org/wiki/TripleO/TuskarJunoPlanning/TemplateBackend>`_
|
||||
- `TripleO Specifications <http://git.openstack.org/cgit/openstack/tripleo-specs/>`_
|
||||
|
||||
Relevant OpenStack Projects
|
||||
---------------------------
|
||||
|
||||
- `TripleO <http://docs.openstack.org/developer/tripleo-incubator/>`_
|
||||
- `Heat <http://docs.openstack.org/developer/heat/>`_
|
||||
- `oslo.db <http://docs.openstack.org/developer/oslo.db/>`_
|
||||
- `oslo.config <http://docs.openstack.org/developer/oslo.config>`_
|
||||
- `hacking <http://docs.openstack.org/developer/hacking>`_ This enforces
|
||||
openstack community coding style guidelines
|
||||
|
||||
General Python/Frameworks
|
||||
-------------------------
|
||||
|
||||
- `dive into python <http://www.diveintopython.net>`_
|
||||
- `pecan <http://pecan.readthedocs.org/en/latest/>`_
|
||||
- `sqlalchemy <http://docs.sqlalchemy.org/en/rel_0_8/>`_
|
||||
- `style guide <http://www.python.org/dev/peps/pep-0008/>`_ This guide
|
||||
is the baseline for 'hacking' above.
|
||||
|
@ -1,4 +0,0 @@
|
||||
keystone_url: "http://10.34.32.181:5000/v2.0/"
|
||||
nova_username: "admin"
|
||||
nova_tenantname: "admin"
|
||||
nova_password: "bcfa838f13e64436"
|
@ -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"
|
||||
}
|
@ -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=<None>
|
||||
|
||||
# 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=<None>
|
||||
|
||||
# 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=<None>
|
||||
|
||||
# (Optional) The base directory used for relative --log-file
|
||||
# paths (string value)
|
||||
# Deprecated group/name - [DEFAULT]/logdir
|
||||
#log_dir=<None>
|
||||
|
||||
# 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=<None>
|
||||
|
||||
# The SQLAlchemy connection string to use to connect to the
|
||||
# slave database. (string value)
|
||||
#slave_connection=<None>
|
||||
|
||||
# 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=<None>
|
||||
|
||||
# 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=<None>
|
||||
|
||||
# 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=<None>
|
||||
|
||||
# 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=<None>
|
||||
|
||||
# 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=<None>
|
||||
|
||||
# Complete admin Identity API endpoint. This should specify
|
||||
# the unversioned root endpoint e.g. https://localhost:35357/
|
||||
# (string value)
|
||||
#identity_uri=<None>
|
||||
|
||||
# API version of the admin Identity API endpoint (string
|
||||
# value)
|
||||
#auth_version=<None>
|
||||
|
||||
# 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=<None>
|
||||
|
||||
# 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=<None>
|
||||
|
||||
# Keystone account username (string value)
|
||||
#admin_user=<None>
|
||||
|
||||
# Keystone account password (string value)
|
||||
#admin_password=<None>
|
||||
|
||||
# Keystone service account tenant name to validate user tokens
|
||||
# (string value)
|
||||
#admin_tenant_name=admin
|
||||
|
||||
# Env key for the swift cache (string value)
|
||||
#cache=<None>
|
||||
|
||||
# Required if Keystone server requires client certificate
|
||||
# (string value)
|
||||
#certfile=<None>
|
||||
|
||||
# Required if Keystone server requires client certificate
|
||||
# (string value)
|
||||
#keyfile=<None>
|
||||
|
||||
# A PEM encoded Certificate Authority to use when verifying
|
||||
# HTTPs connections. Defaults to system CAs. (string value)
|
||||
#cafile=<None>
|
||||
|
||||
# Verify HTTPS connections. (boolean value)
|
||||
#insecure=false
|
||||
|
||||
# Directory used to cache files related to PKI tokens (string
|
||||
# value)
|
||||
#signing_dir=<None>
|
||||
|
||||
# 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=<None>
|
||||
|
||||
# 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=<None>
|
||||
|
||||
# (optional, mandatory if memcache_security_strategy is
|
||||
# defined) this string is used for key derivation. (string
|
||||
# value)
|
||||
#memcache_secret_key=<None>
|
||||
|
||||
# (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
|
||||
|
||||
|
@ -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
|
@ -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
|
55
setup.cfg
55
setup.cfg
@ -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
|
29
setup.py
29
setup.py
@ -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)
|
@ -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
|
@ -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
|
@ -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
|
@ -1,2 +0,0 @@
|
||||
export TUSKAR_CONFIG_GENERATOR_EXTRA_MODULES=keystoneclient.middleware.auth_token
|
||||
export TUSKAR_CONFIG_GENERATOR_EXTRA_LIBRARIES=oslo.db
|
@ -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()
|
@ -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()
|
@ -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 && "$@"
|
36
tox.ini
36
tox.ini
@ -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
|
@ -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)
|
@ -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)))
|
@ -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)
|
@ -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
|
@ -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',
|
||||
}]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
@ -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',
|
||||
}]
|
||||
}
|
||||
}
|
@ -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
|
@ -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
|
@ -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
|
@ -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',
|
||||
}]
|
||||
}
|
||||
}
|
@ -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
|
@ -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
|
@ -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
|
@ -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)})
|
@ -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()
|
@ -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']
|
||||
)
|
@ -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
|
||||
</%def>\
|
@ -1,12 +0,0 @@
|
||||
<%def name="title()">
|
||||
Tuskar API v1
|
||||
</%def>
|
||||
|
||||
<header>
|
||||
</header>
|
||||
|
||||
<div id="content">
|
||||
|
||||
<p> TODO </p>
|
||||
|
||||
</div>
|
@ -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
|
||||
</%def>\
|
@ -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/
|
@ -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
|
||||
</%def>\
|
@ -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
|
||||
</%def>\
|
@ -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')
|
@ -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
|
@ -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()
|
@ -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)
|
@ -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)
|
@ -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)))
|
@ -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)
|
@ -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)
|
@ -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_'")
|
@ -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)
|
@ -1,49 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright 2012 eNovance <licensing@enovance.com>
|
||||
#
|
||||
# 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')
|
@ -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
|
@ -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."""
|
@ -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()
|
@ -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)
|
@ -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()
|
@ -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='.')
|
@ -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=[]
|
@ -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.')
|
@ -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.')
|
@ -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.')
|
@ -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.')
|
@ -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.')
|
@ -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
|
@ -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)
|
@ -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
|
@ -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
|
@ -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
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user