Retire stackforge/warm
This commit is contained in:
parent
2a8fddd51a
commit
52656b254c
40
.gitignore
vendored
40
.gitignore
vendored
@ -1,40 +0,0 @@
|
||||
*.py[cod]
|
||||
*.log
|
||||
*.pem
|
||||
*.DS_Store
|
||||
*.egg*
|
||||
*.log
|
||||
*.mo
|
||||
*.pyc
|
||||
*.swo
|
||||
*.swp
|
||||
*.sqlite
|
||||
*~
|
||||
.autogenerated
|
||||
.coverage
|
||||
.nova-venv
|
||||
.project
|
||||
.pydevproject
|
||||
.ropeproject
|
||||
.testrepository/
|
||||
.tox
|
||||
.idea
|
||||
.venv
|
||||
AUTHORS
|
||||
Authors
|
||||
build-stamp
|
||||
build/*
|
||||
CA/
|
||||
ChangeLog
|
||||
coverage.xml
|
||||
cover/*
|
||||
covhtml
|
||||
dist/*
|
||||
doc/source/api/*
|
||||
doc/build/*
|
||||
instances
|
||||
keeper
|
||||
keys
|
||||
local_settings.py
|
||||
MANIFEST
|
||||
nosetests.xml
|
@ -1,4 +0,0 @@
|
||||
[gerrit]
|
||||
host=review.openstack.org
|
||||
port=29418
|
||||
project=stackforge/warm.git
|
@ -1,4 +0,0 @@
|
||||
[DEFAULT]
|
||||
test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} ${PYTHON:-python} -m subunit.run discover -t ./ ./ $LISTOPT $IDOPTION
|
||||
test_id_option=--load-list $IDFILE
|
||||
test_list_option=--list
|
37
HACKING.rst
37
HACKING.rst
@ -1,37 +0,0 @@
|
||||
Warm Style Commandments
|
||||
=======================
|
||||
|
||||
- Step 1: Read the OpenStack Style Commandments
|
||||
http://docs.openstack.org/developer/hacking/
|
||||
- Step 2: Read on
|
||||
|
||||
Creating Unit Tests
|
||||
-------------------
|
||||
For every new feature, unit tests should be created that both test and
|
||||
(implicitly) document the usage of said feature. If submitting a patch for a
|
||||
bug that had no unit test, a new passing unit test should be added. If a
|
||||
submitted bug fix does have a unit test, be sure to add a new one that fails
|
||||
without the patch and passes with the patch.
|
||||
|
||||
Running Tests
|
||||
-------------
|
||||
The testing system is based on a combination of tox and testr. The canonical
|
||||
approach to running tests is to simply run the command ``tox``. This will
|
||||
create virtual environments, populate them with dependencies and run all of
|
||||
the tests that OpenStack CI systems run. Behind the scenes, tox is running
|
||||
``testr run --parallel``, but is set up such that you can supply any additional
|
||||
testr arguments that are needed to tox. For example, you can run:
|
||||
``tox -- --analyze-isolation`` to cause tox to tell testr to add
|
||||
--analyze-isolation to its argument list.
|
||||
|
||||
It is also possible to run the tests inside of a virtual environment
|
||||
you have created, or it is possible that you have all of the dependencies
|
||||
installed locally already. In this case, you can interact with the testr
|
||||
command directly. Running ``testr run`` will run the entire test suite. ``testr
|
||||
run --parallel`` will run it in parallel (this is the default incantation tox
|
||||
uses.) More information about testr can be found at:
|
||||
http://wiki.openstack.org/testr
|
||||
|
||||
Note
|
||||
----
|
||||
This document is largely inspired from nova/HACKING.rst
|
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
|
64
README
64
README
@ -1,64 +0,0 @@
|
||||
Warm - To setup simple OpenStack environments from template
|
||||
===========================================================
|
||||
|
||||
Warm exposes APIs on Yaml files to be reused. I's good tool to setup
|
||||
small environement on OpenStack.
|
||||
|
||||
Please report me any bug or feature. I will be happy to work on it.
|
||||
|
||||
Note: Warm mixup names and ids, It does not recreate resource
|
||||
already exists with a same name/id.
|
||||
|
||||
|
||||
How to use it
|
||||
=============
|
||||
|
||||
- To install Warm use pip (don't forget to check for dependances).
|
||||
$ pip install warm
|
||||
|
||||
- We are assuming your env OS_* are already configured.
|
||||
$ export | grep OS_
|
||||
declare -x OS_AUTH_URL="https://identity/v2.0"
|
||||
declare -x OS_PASSWORD="*******"
|
||||
declare -x OS_TENANT_ID="ea262aa012f244f8af2d1687977aaa81"
|
||||
declare -x OS_TENANT_NAME="my-project"
|
||||
declare -x OS_USERNAME="sferdjaoui"
|
||||
|
||||
- You are now ready to create your first template.
|
||||
$ cat > my-tpl.yaml <<EOF
|
||||
server:
|
||||
- name: srv
|
||||
flavor: m1.small
|
||||
image: cirros-0.3.1-x86_64-uec
|
||||
EOF
|
||||
|
||||
- You can now run warm.
|
||||
$ warm my-tpl.yaml
|
||||
|
||||
To get more information about a template syntax, see config.yaml.sample or
|
||||
you can check the repositoy https://github.com/sahid/warm-templates to find
|
||||
more examples.
|
||||
|
||||
|
||||
Build Prerequisites
|
||||
===================
|
||||
|
||||
debian-based
|
||||
------------
|
||||
|
||||
The list of debian package dependencies can be found in deps.deb.txt:
|
||||
|
||||
$ sudo apt-get install `cat deps.deb.txt`
|
||||
|
||||
rpm-based
|
||||
---------
|
||||
|
||||
The list of RPM package dependencies can be found in deps.rpm.txt:
|
||||
|
||||
$ sudo yum install `cat deps.rpm.txt`
|
||||
|
||||
|
||||
Roadmap
|
||||
=======
|
||||
Add floating-ip
|
||||
pylint, pep8
|
7
README.rst
Normal file
7
README.rst
Normal file
@ -0,0 +1,7 @@
|
||||
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".
|
||||
|
@ -1,3 +0,0 @@
|
||||
python-dev
|
||||
libffi-dev
|
||||
libssl-dev
|
@ -1,3 +0,0 @@
|
||||
python-devel
|
||||
libffi-devel
|
||||
openssl-devel
|
@ -1,8 +0,0 @@
|
||||
[DEFAULT]
|
||||
|
||||
# The list of modules to copy from openstack-common
|
||||
module=install_venv_common
|
||||
module=test
|
||||
|
||||
# The base module to hold the copy of openstack.common
|
||||
base=warm
|
@ -1,8 +0,0 @@
|
||||
pbr>=0.6,<1.0
|
||||
six>=1.5.2
|
||||
Babel>=1.3
|
||||
requests>=1.1
|
||||
PyYAML>=3.1.0
|
||||
python-openstackclient==0.2.2
|
||||
python-neutronclient>=2.3.4,<3
|
||||
netaddr
|
164
run_tests.sh
164
run_tests.sh
@ -1,164 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -eu
|
||||
|
||||
function usage {
|
||||
echo "Usage: $0 [OPTION]..."
|
||||
echo "Run python-novaclient test suite"
|
||||
echo ""
|
||||
echo " -V, --virtual-env Always use virtualenv. Install automatically if not present"
|
||||
echo " -N, --no-virtual-env Don't use virtualenv. Run tests in local environment"
|
||||
echo " -s, --no-site-packages Isolate the virtualenv from the global Python environment"
|
||||
echo " -x, --stop Stop running tests after the first error or failure."
|
||||
echo " -f, --force Force a clean re-build of the virtual environment. Useful when dependencies have been added."
|
||||
echo " -p, --pep8 Just run pep8"
|
||||
echo " -P, --no-pep8 Don't run pep8"
|
||||
echo " -c, --coverage Generate coverage report"
|
||||
echo " -h, --help Print this usage message"
|
||||
echo " --hide-elapsed Don't print the elapsed time for each test along with slow test list"
|
||||
echo ""
|
||||
echo "Note: with no options specified, the script will try to run the tests in a virtual environment,"
|
||||
echo " If no virtualenv is found, the script will ask if you would like to create one. If you "
|
||||
echo " prefer to run tests NOT in a virtual environment, simply pass the -N option."
|
||||
exit
|
||||
}
|
||||
|
||||
function process_option {
|
||||
case "$1" in
|
||||
-h|--help) usage;;
|
||||
-V|--virtual-env) always_venv=1; never_venv=0;;
|
||||
-N|--no-virtual-env) always_venv=0; never_venv=1;;
|
||||
-s|--no-site-packages) no_site_packages=1;;
|
||||
-f|--force) force=1;;
|
||||
-p|--pep8) just_pep8=1;;
|
||||
-P|--no-pep8) no_pep8=1;;
|
||||
-c|--coverage) coverage=1;;
|
||||
-*) testropts="$testropts $1";;
|
||||
*) testrargs="$testrargs $1"
|
||||
esac
|
||||
}
|
||||
|
||||
venv=.venv
|
||||
with_venv=tools/with_venv.sh
|
||||
always_venv=0
|
||||
never_venv=0
|
||||
force=0
|
||||
no_site_packages=0
|
||||
installvenvopts=
|
||||
testrargs=
|
||||
testropts=
|
||||
wrapper=""
|
||||
just_pep8=0
|
||||
no_pep8=0
|
||||
coverage=0
|
||||
|
||||
LANG=en_US.UTF-8
|
||||
LANGUAGE=en_US:en
|
||||
LC_ALL=C
|
||||
|
||||
for arg in "$@"; do
|
||||
process_option $arg
|
||||
done
|
||||
|
||||
if [ $no_site_packages -eq 1 ]; then
|
||||
installvenvopts="--no-site-packages"
|
||||
fi
|
||||
|
||||
function init_testr {
|
||||
if [ ! -d .testrepository ]; then
|
||||
${wrapper} testr init
|
||||
fi
|
||||
}
|
||||
|
||||
function run_tests {
|
||||
# Cleanup *pyc
|
||||
${wrapper} find . -type f -name "*.pyc" -delete
|
||||
|
||||
if [ $coverage -eq 1 ]; then
|
||||
# Do not test test_coverage_ext when gathering coverage.
|
||||
if [ "x$testrargs" = "x" ]; then
|
||||
testrargs="^(?!.*test_coverage_ext).*$"
|
||||
fi
|
||||
export PYTHON="${wrapper} coverage run --source novaclient --parallel-mode"
|
||||
fi
|
||||
# Just run the test suites in current environment
|
||||
set +e
|
||||
TESTRTESTS="$TESTRTESTS $testrargs"
|
||||
echo "Running \`${wrapper} $TESTRTESTS\`"
|
||||
${wrapper} $TESTRTESTS
|
||||
RESULT=$?
|
||||
set -e
|
||||
|
||||
copy_subunit_log
|
||||
|
||||
return $RESULT
|
||||
}
|
||||
|
||||
function copy_subunit_log {
|
||||
LOGNAME=`cat .testrepository/next-stream`
|
||||
LOGNAME=$(($LOGNAME - 1))
|
||||
LOGNAME=".testrepository/${LOGNAME}"
|
||||
cp $LOGNAME subunit.log
|
||||
}
|
||||
|
||||
function run_pep8 {
|
||||
echo "Running flake8 ..."
|
||||
${wrapper} flake8
|
||||
}
|
||||
|
||||
TESTRTESTS="testr run --parallel $testropts"
|
||||
|
||||
if [ $never_venv -eq 0 ]
|
||||
then
|
||||
# Remove the virtual environment if --force used
|
||||
if [ $force -eq 1 ]; then
|
||||
echo "Cleaning virtualenv..."
|
||||
rm -rf ${venv}
|
||||
fi
|
||||
if [ -e ${venv} ]; then
|
||||
wrapper="${with_venv}"
|
||||
else
|
||||
if [ $always_venv -eq 1 ]; then
|
||||
# Automatically install the virtualenv
|
||||
python tools/install_venv.py $installvenvopts
|
||||
wrapper="${with_venv}"
|
||||
else
|
||||
echo -e "No virtual environment found...create one? (Y/n) \c"
|
||||
read use_ve
|
||||
if [ "x$use_ve" = "xY" -o "x$use_ve" = "x" -o "x$use_ve" = "xy" ]; then
|
||||
# Install the virtualenv and run the test suite in it
|
||||
python tools/install_venv.py $installvenvopts
|
||||
wrapper=${with_venv}
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Delete old coverage data from previous runs
|
||||
if [ $coverage -eq 1 ]; then
|
||||
${wrapper} coverage erase
|
||||
fi
|
||||
|
||||
if [ $just_pep8 -eq 1 ]; then
|
||||
run_pep8
|
||||
exit
|
||||
fi
|
||||
|
||||
init_testr
|
||||
run_tests
|
||||
|
||||
# NOTE(sirp): we only want to run pep8 when we're running the full-test suite,
|
||||
# not when we're running tests individually. To handle this, we need to
|
||||
# distinguish between options (noseopts), which begin with a '-', and
|
||||
# arguments (testrargs).
|
||||
if [ -z "$testrargs" ]; then
|
||||
if [ $no_pep8 -eq 0 ]; then
|
||||
run_pep8
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ $coverage -eq 1 ]; then
|
||||
echo "Generating coverage report in covhtml/"
|
||||
${wrapper} coverage combine
|
||||
${wrapper} coverage html --include='novaclient/*' --omit='novaclient/openstack/common/*' -d covhtml -i
|
||||
fi
|
24
setup.cfg
24
setup.cfg
@ -1,24 +0,0 @@
|
||||
[metadata]
|
||||
name = warm
|
||||
summary = Deploy OpenStack resources from Yaml templates.
|
||||
description-file =
|
||||
README
|
||||
author = Sahid Orentino Ferdjaoui
|
||||
author-email = sahid.ferdjaoui@cloudwatt.com
|
||||
home-page = https://wiki.openstack.org/wiki/Warm
|
||||
classifier =
|
||||
Environment :: OpenStack
|
||||
License :: OSI Approved :: Apache Software License
|
||||
Intended Audience :: Developers
|
||||
Intended Audience :: System Administrators
|
||||
Programming Language :: Python
|
||||
Operating System :: MacOS :: MacOS X
|
||||
Operating System :: POSIX :: Linux
|
||||
|
||||
[files]
|
||||
packages =
|
||||
warm
|
||||
|
||||
[entry_points]
|
||||
console_scripts =
|
||||
warm = warm:main
|
22
setup.py
22
setup.py
@ -1,22 +0,0 @@
|
||||
# Copyright 2013 Cloudwatt
|
||||
#
|
||||
# Author: Sahid Orentino Ferdjaoui <sahid.ferdjaoui@cloudwatt.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 setuptools
|
||||
|
||||
setuptools.setup(
|
||||
setup_requires=['pbr'],
|
||||
pbr=True)
|
@ -1,103 +0,0 @@
|
||||
# A template use the YAML syntaxe.
|
||||
|
||||
###############################################################################
|
||||
# Authentification
|
||||
# The program can read environement variables OS_*
|
||||
###############################################################################
|
||||
#username: demo
|
||||
#password: ok
|
||||
#project_name: demo
|
||||
#auth_url: http://10.0.2.15:5000/v2.0
|
||||
#region_name:
|
||||
#path:
|
||||
|
||||
###############################################################################
|
||||
# Key
|
||||
###############################################################################
|
||||
#key:
|
||||
#- name: demo
|
||||
# path: .
|
||||
|
||||
###############################################################################
|
||||
# Volume
|
||||
###############################################################################
|
||||
#volume:
|
||||
#- name: vol01
|
||||
# size: 1
|
||||
|
||||
###############################################################################
|
||||
# Security Groups & Rules
|
||||
###############################################################################
|
||||
#securitygroup:
|
||||
#- name: sec01
|
||||
# description: my security group
|
||||
# rules:
|
||||
# - ip_protocol: tcp
|
||||
# from_port: 22
|
||||
# to_port: 22
|
||||
# cidr: 0.0.0.0/23
|
||||
#
|
||||
#securitygrouprule:
|
||||
#- group: sec01
|
||||
# ip_protocol: tcp
|
||||
# from_port: 22
|
||||
# to_port: 22
|
||||
# cidr: 0.0.0.0/23
|
||||
|
||||
###############################################################################
|
||||
# Server
|
||||
###############################################################################
|
||||
#server:
|
||||
#- name: demo
|
||||
# flavor: 1
|
||||
# image: cirros-0.3.1-x86_64-uec
|
||||
# volumes:
|
||||
# - name: vol01
|
||||
# device: /dev/sdh
|
||||
# networks:
|
||||
# - name: net01
|
||||
# v4-fixed-ip: 10.123.2.25
|
||||
# - name: net01
|
||||
# v6-fixed-ip: dead:beef::25/64
|
||||
# securitygroups: [demo, ]
|
||||
|
||||
###############################################################################
|
||||
# Network & SubNet
|
||||
###############################################################################
|
||||
#network:
|
||||
#- name: net01
|
||||
# subnets:
|
||||
# - cidr: 10.123.2.0/24
|
||||
# ip_version: 4
|
||||
# host_routes:
|
||||
# - destination: 0.0.0.0/0
|
||||
# nexthop: 10.123.2.1
|
||||
# - destination: 10.0.0.0/24
|
||||
# nexthop: 10.123.2.2
|
||||
#
|
||||
#subnet:
|
||||
#- network: net01
|
||||
# name: sub01
|
||||
# cidr: 10.123.2.0/24
|
||||
# ip_version: 4
|
||||
# host_routes:
|
||||
# - destination: 10.0.0.0/24
|
||||
# nexthop: 10.123.2.2
|
||||
#- network: net01
|
||||
# name: sub02
|
||||
# cidr: dead:beef::/64
|
||||
# ip_version: 6
|
||||
|
||||
###############################################################################
|
||||
# Router
|
||||
###############################################################################
|
||||
#router:
|
||||
#- name: router01
|
||||
# interfaces:
|
||||
# - name: interface01
|
||||
# subnet: sub01
|
||||
# gateways:
|
||||
# - name: gate01
|
||||
# network: net01
|
||||
|
||||
|
@ -1,4 +0,0 @@
|
||||
hacking>=0.8.0,<0.9
|
||||
mock>=1.0
|
||||
testrepository>=0.0.18
|
||||
testtools>=0.9.32
|
@ -1,74 +0,0 @@
|
||||
# Copyright 2010 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Copyright 2010 OpenStack Foundation
|
||||
# Copyright 2013 IBM Corp.
|
||||
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import ConfigParser
|
||||
import os
|
||||
import sys
|
||||
|
||||
import install_venv_common as install_venv # flake8: noqa
|
||||
|
||||
|
||||
def print_help(project, venv, root):
|
||||
help = """
|
||||
%(project)s development environment setup is complete.
|
||||
|
||||
%(project)s development uses virtualenv to track and manage Python
|
||||
dependencies while in development and testing.
|
||||
|
||||
To activate the %(project)s virtualenv for the extent of your current
|
||||
shell session you can run:
|
||||
|
||||
$ source %(venv)s/bin/activate
|
||||
|
||||
Or, if you prefer, you can run commands in the virtualenv on a case by
|
||||
case basis by running:
|
||||
|
||||
$ %(root)s/tools/with_venv.sh <your command>
|
||||
"""
|
||||
print help % dict(project=project, venv=venv, root=root)
|
||||
|
||||
|
||||
def main(argv):
|
||||
root = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
|
||||
|
||||
if os.environ.get('tools_path'):
|
||||
root = os.environ['tools_path']
|
||||
venv = os.path.join(root, '.venv')
|
||||
if os.environ.get('venv'):
|
||||
venv = os.environ['venv']
|
||||
|
||||
pip_requires = os.path.join(root, 'requirements.txt')
|
||||
test_requires = os.path.join(root, 'test-requirements.txt')
|
||||
py_version = "python%s.%s" % (sys.version_info[0], sys.version_info[1])
|
||||
setup_cfg = ConfigParser.ConfigParser()
|
||||
setup_cfg.read('setup.cfg')
|
||||
project = setup_cfg.get('metadata', 'name')
|
||||
|
||||
install = install_venv.InstallVenv(
|
||||
root, venv, pip_requires, test_requires, py_version, project)
|
||||
options = install.parse_args(argv)
|
||||
install.check_python_version()
|
||||
install.check_dependencies()
|
||||
install.create_virtualenv(no_site_packages=options.no_site_packages)
|
||||
install.install_dependencies()
|
||||
print_help(project, venv, root)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main(sys.argv)
|
@ -1,172 +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, requirements,
|
||||
test_requirements, py_version,
|
||||
project):
|
||||
self.root = root
|
||||
self.venv = venv
|
||||
self.requirements = requirements
|
||||
self.test_requirements = test_requirements
|
||||
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.requirements,
|
||||
self.test_requirements, self.py_version, self.project)
|
||||
else:
|
||||
return Distro(
|
||||
self.root, self.venv, self.requirements,
|
||||
self.test_requirements, 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.')
|
||||
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
|
||||
# setuptools and pbr
|
||||
self.pip_install('pip>=1.4')
|
||||
self.pip_install('setuptools')
|
||||
self.pip_install('pbr')
|
||||
|
||||
self.pip_install('-r', self.requirements, '-r', self.test_requirements)
|
||||
|
||||
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,4 +0,0 @@
|
||||
#!/bin/bash
|
||||
TOOLS=`dirname $0`
|
||||
VENV=$TOOLS/../.venv
|
||||
source $VENV/bin/activate && $@
|
30
tox.ini
30
tox.ini
@ -1,30 +0,0 @@
|
||||
[tox]
|
||||
envlist = py26,py27,py33,pypy,pep8
|
||||
minversion = 1.6
|
||||
skipsdist = True
|
||||
|
||||
[testenv]
|
||||
usedevelop = True
|
||||
install_command = pip install -U {opts} {packages}
|
||||
setenv = VIRTUAL_ENV={envdir}
|
||||
|
||||
deps = -r{toxinidir}/requirements.txt
|
||||
-r{toxinidir}/test-requirements.txt
|
||||
commands = python setup.py testr --testr-args='{posargs}'
|
||||
|
||||
[testenv:pep8]
|
||||
commands = flake8
|
||||
|
||||
[testenv:venv]
|
||||
commands = {posargs}
|
||||
|
||||
[testenv:cover]
|
||||
commands = python setup.py testr --coverage --testr-args='{posargs}'
|
||||
|
||||
[tox:jenkins]
|
||||
downloadcache = ~/cache/pip
|
||||
|
||||
[flake8]
|
||||
ignore = E12,F841,F811,F821,H302,H404
|
||||
show-source = True
|
||||
exclude=.venv,.git,.tox,dist,*openstack/common*,*lib/python*,*egg,build
|
152
warm/__init__.py
152
warm/__init__.py
@ -1,152 +0,0 @@
|
||||
# Copyright 2013 Cloudwatt
|
||||
#
|
||||
# Author: Sahid Orentino Ferdjaoui <sahid.ferdjaoui@cloudwatt.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.
|
||||
|
||||
"""The main script of the project."""
|
||||
|
||||
import logging
|
||||
import optparse
|
||||
import os
|
||||
import sys
|
||||
import yaml
|
||||
|
||||
from neutronclient.neutron import client as neutron
|
||||
from openstackclient.common import clientmanager
|
||||
|
||||
from warm import components
|
||||
from warm import version
|
||||
|
||||
DEFAULT_LOGFILE = "/dev/null"
|
||||
DEFAULT_LOGLEVEL = logging.DEBUG
|
||||
DEFAULT_COMPUTE_API_VERSION = '2'
|
||||
DEFAULT_IDENTITY_API_VERSION = '2.0'
|
||||
DEFAULT_IMAGE_API_VERSION = '2'
|
||||
DEFAULT_OBJECT_API_VERSION = '1'
|
||||
DEFAULT_VOLUME_API_VERSION = '1'
|
||||
DEFAULT_DOMAIN = 'default'
|
||||
DEFAULT_KEY_NAME = 'warmkey'
|
||||
|
||||
OS_USERNAME = os.getenv("OS_USERNAME")
|
||||
OS_PASSWORD = os.getenv("OS_PASSWORD")
|
||||
OS_TENANT_NAME = os.getenv("OS_TENANT_NAME")
|
||||
OS_AUTH_URL = os.getenv("OS_AUTH_URL")
|
||||
|
||||
USAGE = "usage: %prog <template> [options]"
|
||||
|
||||
|
||||
class Agent(object):
|
||||
def __init__(self, **options):
|
||||
self.options = options
|
||||
|
||||
self.api_version = {
|
||||
'compute': self.options.get(
|
||||
"compute_api_version", DEFAULT_COMPUTE_API_VERSION),
|
||||
'identity': self.options.get(
|
||||
"identity_api_version", DEFAULT_IDENTITY_API_VERSION),
|
||||
'image': self.options.get(
|
||||
"image_api_version", DEFAULT_IMAGE_API_VERSION),
|
||||
'object-store': self.options.get(
|
||||
"object_api_version", DEFAULT_OBJECT_API_VERSION),
|
||||
'volume': self.options.get(
|
||||
"volume_api_version", DEFAULT_VOLUME_API_VERSION)}
|
||||
|
||||
self.client = clientmanager.ClientManager(
|
||||
token=self.options.get("token"),
|
||||
url=self.options.get("url"),
|
||||
auth_url=self.options.get("auth_url", OS_AUTH_URL),
|
||||
project_name=self.options.get("project_name", OS_TENANT_NAME),
|
||||
project_id=self.options.get("project_id"),
|
||||
username=self.options.get("username", OS_USERNAME),
|
||||
password=self.options.get("password", OS_PASSWORD),
|
||||
region_name=self.options.get("region_name"),
|
||||
api_version=self.api_version)
|
||||
|
||||
self.clientneutron = neutron.Client(
|
||||
token=self.options.get("token"),
|
||||
username=self.options.get("username", OS_USERNAME),
|
||||
password=self.options.get("password", OS_PASSWORD),
|
||||
tenant_name=self.options.get("project_name", OS_TENANT_NAME),
|
||||
auth_url=self.options.get("auth_url", OS_AUTH_URL),
|
||||
region_name=self.options.get("region_name"),
|
||||
insecure=self.options.get("insecure", True),
|
||||
api_version=self.options.get("api_version", "2.0"))
|
||||
|
||||
def _InitKeypairs(self):
|
||||
logging.info("Initializing keypairs...")
|
||||
self.key_name = self.options.get("key_name", DEFAULT_KEY_NAME)
|
||||
try:
|
||||
self.client.compute.keypairs.delete(self.key_name)
|
||||
except Exception:
|
||||
pass
|
||||
key = self.client.compute.keypairs.create(self.key_name)
|
||||
f = open("%s.pem" % self.key_name, 'w')
|
||||
f.write(key.private_key)
|
||||
f.close
|
||||
|
||||
|
||||
def main():
|
||||
parser = optparse.OptionParser(usage=USAGE)
|
||||
parser.add_option("-V", "--verbose",
|
||||
dest="verbose",
|
||||
action="store_true",
|
||||
help="Be more verbose.",
|
||||
default=False)
|
||||
parser.add_option("-v", "--version",
|
||||
dest="version",
|
||||
action="store_true",
|
||||
help="Print the current version used.",
|
||||
default=False)
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
verbose = getattr(options, "verbose")
|
||||
out = DEFAULT_LOGFILE
|
||||
if verbose:
|
||||
out = "/dev/stdout"
|
||||
logging.basicConfig(filename=out, level=DEFAULT_LOGLEVEL)
|
||||
|
||||
if getattr(options, "version"):
|
||||
print(version.__version__)
|
||||
exit()
|
||||
|
||||
if len(sys.argv) < 2:
|
||||
parser.print_help()
|
||||
exit()
|
||||
|
||||
stream = file(sys.argv[1])
|
||||
config = yaml.load(stream)
|
||||
|
||||
agent = Agent(**config)
|
||||
|
||||
for key, cls in CLASS_MAPPING:
|
||||
if key in config:
|
||||
logging.debug("Some %s configurations found, "
|
||||
"work in progress..." % key)
|
||||
for cfg in config[key]:
|
||||
getattr(components, cls)(agent)(**cfg)
|
||||
|
||||
|
||||
CLASS_MAPPING = [
|
||||
("key", "Key"),
|
||||
("volume", "Volume"),
|
||||
("network", "Network"),
|
||||
("subnet", "SubNet"),
|
||||
("router", "Router"),
|
||||
("securitygroup", "SecurityGroup"),
|
||||
("securitygrouprule", "SecurityGroupRule"),
|
||||
("server", "Server"),
|
||||
]
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,470 +0,0 @@
|
||||
# Copyright 2013 Cloudwatt
|
||||
#
|
||||
# Author: Sahid Orentino Ferdjaoui <sahid.ferdjaoui@cloudwatt.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.
|
||||
|
||||
"""Tests."""
|
||||
|
||||
import uuid
|
||||
|
||||
from netaddr import AddrFormatError
|
||||
from netaddr import IPAddress
|
||||
from neutronclient.common import exceptions as neutron_exc
|
||||
from neutronclient.neutron import v2_0 as neutronV20
|
||||
from novaclient import exceptions as nova_exc
|
||||
from openstackclient.common import exceptions
|
||||
from openstackclient.common import utils
|
||||
|
||||
import warm.utils
|
||||
|
||||
|
||||
class Base(object):
|
||||
"""Base class for a component."""
|
||||
def __init__(self, agent, ref=None):
|
||||
self._agent = agent
|
||||
self._ref = ref
|
||||
|
||||
def find(self, id_or_name, ref_only=False):
|
||||
service = None
|
||||
ref = None
|
||||
if isinstance(self, Key):
|
||||
service = self._agent.client.compute.keypairs
|
||||
if isinstance(self, Image):
|
||||
service = self._agent.client.compute.images
|
||||
elif isinstance(self, Server):
|
||||
service = self._agent.client.compute.servers
|
||||
elif isinstance(self, Flavor):
|
||||
service = self._agent.client.compute.flavors
|
||||
elif isinstance(self, Volume):
|
||||
service = self._agent.client.volume.volumes
|
||||
elif isinstance(self, SecurityGroup):
|
||||
service = self._agent.client.compute.security_groups
|
||||
elif isinstance(self, Network):
|
||||
service = self._agent.client.compute.networks
|
||||
elif isinstance(self, SubNet):
|
||||
sid = neutronV20.find_resourceid_by_name_or_id(
|
||||
self._agent.clientneutron, "subnet", id_or_name)
|
||||
ref = self._agent.clientneutron.show_subnet(sid)
|
||||
elif isinstance(self, Router):
|
||||
rid = neutronV20.find_resourceid_by_name_or_id(
|
||||
self._agent.clientneutron, "router", id_or_name)
|
||||
ref = self._agent.clientneutron.show_router(rid)
|
||||
|
||||
if service:
|
||||
ref = utils.find_resource(service, id_or_name)
|
||||
if ref_only:
|
||||
return ref
|
||||
self._ref = ref
|
||||
return self
|
||||
|
||||
def wait_for_ready(self, field="status", success=("available", "active")):
|
||||
if isinstance(self, Image):
|
||||
service = self._agent.client.compute.images
|
||||
elif isinstance(self, Server):
|
||||
service = self._agent.client.compute.servers
|
||||
elif isinstance(self, Flavor):
|
||||
service = self._agent.client.compute.flavors
|
||||
elif isinstance(self, Volume):
|
||||
service = self._agent.client.volume.volumes
|
||||
|
||||
utils.wait_for_status(service.get,
|
||||
self.id,
|
||||
sleep_time=1,
|
||||
success_status=success,
|
||||
status_field=field)
|
||||
|
||||
def delete(self):
|
||||
if not self._ref:
|
||||
raise Exception("This component is not initialize yet.")
|
||||
return self._Delete()
|
||||
|
||||
def _Execute(self, options):
|
||||
raise NotImplemented("This method needs to be implemented.")
|
||||
|
||||
def _Delete(self):
|
||||
return self._ref.delete()
|
||||
|
||||
def _PostExecute(self, options):
|
||||
pass
|
||||
|
||||
def _Id(self):
|
||||
return self._ref.id
|
||||
|
||||
def _Name(self):
|
||||
return self._ref.name
|
||||
|
||||
def __call__(self, **options):
|
||||
try:
|
||||
if options.get("name"):
|
||||
self._ref = self.find(options.get("name"), ref_only=True)
|
||||
except (exceptions.CommandError, neutron_exc.NeutronClientException):
|
||||
pass
|
||||
finally:
|
||||
if not self._ref:
|
||||
self._ref = self._Execute(options)
|
||||
self._PostExecute(options)
|
||||
return self
|
||||
|
||||
@property
|
||||
def id(self):
|
||||
if not self._ref:
|
||||
raise Exception("This component is not initialize yet.")
|
||||
return self._Id()
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
if not self._ref:
|
||||
raise Exception("This component is not initialize yet.")
|
||||
return self._Name()
|
||||
|
||||
|
||||
class Key(Base):
|
||||
"""Handles keypairs operations."""
|
||||
def _Execute(self, options):
|
||||
whitelist = dict(
|
||||
name=options["name"],
|
||||
path=options.get("path", "."))
|
||||
try:
|
||||
key = self.find(whitelist["name"])
|
||||
except exceptions.CommandError:
|
||||
key = self._agent.client.compute.keypairs.create(whitelist["name"])
|
||||
f = open("%(path)s/%(name)s.pem" % whitelist, 'w')
|
||||
f.write(key.private_key)
|
||||
f.close
|
||||
return key
|
||||
|
||||
|
||||
class Image(Base):
|
||||
"""Handles image operations."""
|
||||
pass
|
||||
|
||||
|
||||
class Flavor(Base):
|
||||
"""Handles flavor operations."""
|
||||
pass
|
||||
|
||||
|
||||
class Volume(Base):
|
||||
"""Handles volume operations."""
|
||||
def _Execute(self, options):
|
||||
whitelist = dict(
|
||||
size=options["size"],
|
||||
display_name=options.get("name", ""))
|
||||
return self._agent.client.volume.volumes.create(**whitelist)
|
||||
|
||||
def _Name(self):
|
||||
return self._ref.display_name
|
||||
|
||||
|
||||
class SecurityGroup(Base):
|
||||
def _Execute(self, options):
|
||||
"""Handles security groups operations."""
|
||||
whitelist = dict(
|
||||
name=options["name"],
|
||||
description=options.get("description", "<empty>"))
|
||||
return self._agent.client.compute.security_groups.create(**whitelist)
|
||||
|
||||
def _PostExecute(self, options):
|
||||
if "rules" in options:
|
||||
for rule_opt in options["rules"]:
|
||||
self.Rule(**rule_opt)
|
||||
|
||||
def Rule(self, **options):
|
||||
options["group"] = self.id
|
||||
SecurityGroupRule(self._agent)(**options)
|
||||
|
||||
|
||||
class SecurityGroupRule(Base):
|
||||
def _Execute(self, options):
|
||||
parent = SecurityGroup(self._agent).find(options["group"])
|
||||
group_id = None
|
||||
if "secgroup" in options:
|
||||
group = SecurityGroup(self._agent).find(options["secgroup"])
|
||||
if group:
|
||||
group_id = group.id
|
||||
whitelist = dict(
|
||||
parent_group_id=parent.id,
|
||||
ip_protocol=options.get("ip_protocol"),
|
||||
from_port=options.get("from_port"),
|
||||
to_port=options.get("to_port"),
|
||||
cidr=options.get("cidr"),
|
||||
group_id=group_id)
|
||||
try:
|
||||
return self._agent.client.compute.security_group_rules.create(
|
||||
**whitelist)
|
||||
except (nova_exc.BadRequest, nova_exc.OverLimit):
|
||||
# BUG(sahid): How to find a rule?,
|
||||
# if the rule already exists, exception OverLimit...
|
||||
pass
|
||||
|
||||
|
||||
class Server(Base):
|
||||
"""Handle server (instance) operations."""
|
||||
def _Execute(self, options):
|
||||
image = Image(self._agent).find(options.get("image"))
|
||||
flavor = Flavor(self._agent).find(options.get("flavor"))
|
||||
|
||||
secgrps = []
|
||||
for name in options.get("securitygroups", []):
|
||||
secgrp = SecurityGroup(self._agent).find(name)
|
||||
secgrps.append(secgrp.id)
|
||||
|
||||
networks = []
|
||||
for obj in options.get("networks", []):
|
||||
net = Network(self._agent).find(obj["name"])
|
||||
ipv4_addr = None
|
||||
ipv6_addr = None
|
||||
if obj.get("fixed_ip"):
|
||||
# Note(ethuleau): keep 'fixed_ip' attribute for compatibility
|
||||
try:
|
||||
ip_addr = IPAddress(obj.get("fixed_ip"))
|
||||
except AddrFormatError:
|
||||
raise Exception("Invalid IP address: %s",
|
||||
obj.get("fixed_ip"))
|
||||
if ip_addr.version == 4:
|
||||
ipv4_addr = str(ip_addr)
|
||||
else:
|
||||
ipv6_addr = str(ip_addr)
|
||||
if obj.get("v4-fixed-ip"):
|
||||
ipv4_addr = obj.get("v4-fixed-ip")
|
||||
if obj.get("v6-fixed-ip"):
|
||||
ipv6_addr = obj.get("v6-fixed-ip")
|
||||
# Note(ethuleau): keep 'port' attribute for compatibility
|
||||
port_id = obj.get("port")
|
||||
if obj.get("port-id"):
|
||||
port_id = obj.get("port-id")
|
||||
networks.append({
|
||||
"net-id": net.id,
|
||||
"v4-fixed-ip": ipv4_addr,
|
||||
"v6-fixed-ip": ipv6_addr,
|
||||
"port-id": port_id})
|
||||
|
||||
userdata = None
|
||||
if "userdata" in options:
|
||||
tmpfile = "/tmp/%s" % uuid.uuid1()
|
||||
content = warm.utils.multipart_content(*options["userdata"])
|
||||
with open(tmpfile, "w+") as output:
|
||||
output.write(content)
|
||||
userdata = file(tmpfile)
|
||||
|
||||
whitelist = dict(
|
||||
name=options.get("name"),
|
||||
image=image.id,
|
||||
flavor=flavor.id,
|
||||
security_groups=secgrps,
|
||||
nics=networks,
|
||||
userdata=userdata,
|
||||
availability_zone=options.get("availability_zone"),
|
||||
key_name=options.get("key"),
|
||||
min_count=options.get("min_count"),
|
||||
max_count=options.get("max_count"))
|
||||
return self._agent.client.compute.servers.create(**whitelist)
|
||||
|
||||
def _PostExecute(self, options):
|
||||
if "volumes" in options:
|
||||
self.wait_for_ready()
|
||||
for volume_opt in options["volumes"]:
|
||||
self.Mount(**volume_opt)
|
||||
|
||||
def Mount(self, **options):
|
||||
volume = Volume(self._agent).find(options["name"])
|
||||
volume.wait_for_ready()
|
||||
whitelist = dict(
|
||||
server_id=self.id,
|
||||
volume_id=volume.id,
|
||||
device=options["device"])
|
||||
self._agent.client.compute.volumes.create_server_volume(**whitelist)
|
||||
|
||||
|
||||
class Network(Base):
|
||||
def _Execute(self, options):
|
||||
whitelist = dict(
|
||||
name=options["name"],
|
||||
admin_state_up=options.get("admin_state_up", True))
|
||||
body = {"network": whitelist}
|
||||
#TODO(sahid): Needs to use client.
|
||||
return self._agent.clientneutron.create_network(body)
|
||||
|
||||
def _PostExecute(self, options):
|
||||
if "subnets" in options:
|
||||
for cfg in options["subnets"]:
|
||||
self.AttachSubNet(cfg)
|
||||
|
||||
def AttachSubNet(self, options):
|
||||
options["network"] = self.id
|
||||
SubNet(self._agent)(**options)
|
||||
|
||||
def _Id(self):
|
||||
if isinstance(self._ref, dict):
|
||||
return self._ref["network"]["id"]
|
||||
return self._ref.id
|
||||
|
||||
def _Name(self):
|
||||
if isinstance(self._ref, dict):
|
||||
return self._ref["network"]["name"]
|
||||
return self._ref.name
|
||||
|
||||
def _Delete(self):
|
||||
if isinstance(self._ref, dict):
|
||||
return self._agent.clientneutron.delete_network(self.id)
|
||||
return self.delete()
|
||||
|
||||
|
||||
class SubNet(Base):
|
||||
def _Execute(self, options):
|
||||
host_routes = []
|
||||
for obj in options.get("host_routes", []):
|
||||
host_routes.append({
|
||||
"destination": obj.get("destination"),
|
||||
"nexthop": obj.get("nexthop"),
|
||||
})
|
||||
|
||||
network = Network(self._agent).find(options["network"])
|
||||
whitelist = dict(
|
||||
network_id=network.id,
|
||||
name=options.get("name"),
|
||||
cidr=options.get("cidr"),
|
||||
ip_version=options.get("ip_version"),
|
||||
dns_nameservers=options.get("dns_nameservers", []),
|
||||
enable_dhcp=options.get("enable_dhcp", True),
|
||||
host_routes=host_routes)
|
||||
|
||||
if options.get("gateway_ip"):
|
||||
whitelist["gateway_ip"] = options.get("gateway_ip")
|
||||
body = {"subnet": whitelist}
|
||||
#TODO(sahid): Needs to use client.
|
||||
return self._agent.clientneutron.create_subnet(body)
|
||||
|
||||
def _Id(self):
|
||||
if isinstance(self._ref, dict):
|
||||
return self._ref["subnet"]["id"]
|
||||
return self._ref.id
|
||||
|
||||
def _Name(self):
|
||||
if isinstance(self._ref, dict):
|
||||
return self._ref["subnet"]["name"]
|
||||
return self._ref.name
|
||||
|
||||
def _Delete(self):
|
||||
if isinstance(self._ref, dict):
|
||||
return self._agent.clientneutron.delete_subnet(self.id)
|
||||
self._ref.delete()
|
||||
|
||||
|
||||
class Router(Base):
|
||||
def _Execute(self, options):
|
||||
whitelist = dict(
|
||||
name=options["name"],
|
||||
admin_state_up=options.get("admin_state_up", True))
|
||||
return self._agent.clientneutron.create_router({"router": whitelist})
|
||||
|
||||
def _PostExecute(self, options):
|
||||
if "gateways" in options:
|
||||
for gateway_opt in options["gateways"]:
|
||||
self.AttachGateway(gateway_opt)
|
||||
if "interfaces" in options:
|
||||
for interface_opt in options["interfaces"]:
|
||||
self.AttachInterface(interface_opt)
|
||||
|
||||
def AttachInterface(self, options):
|
||||
options["router"] = self.id
|
||||
RouterInterface(self._agent)(**options)
|
||||
|
||||
def AttachGateway(self, options):
|
||||
options["router"] = self.id
|
||||
RouterGateway(self._agent)(**options)
|
||||
|
||||
def _Id(self):
|
||||
if isinstance(self._ref, dict):
|
||||
return self._ref["router"]["id"]
|
||||
return self._ref.id
|
||||
|
||||
def _Name(self):
|
||||
if isinstance(self._ref, dict):
|
||||
return self._ref["router"]["name"]
|
||||
return self._ref.name
|
||||
|
||||
def _Delete(self):
|
||||
if isinstance(self._ref, dict):
|
||||
items = self._agent.clientneutron.list_ports().items()
|
||||
for name, interfaces in items:
|
||||
for interface in interfaces:
|
||||
if interface["device_id"] == self.id:
|
||||
RouterInterface(self._agent, interface).delete()
|
||||
return self._agent.clientneutron.delete_router(self.id)
|
||||
|
||||
self._ref.delete()
|
||||
|
||||
|
||||
class RouterInterface(Base):
|
||||
def _Execute(self, options):
|
||||
router = Router(self._agent).find(options["router"])
|
||||
subnet = SubNet(self._agent).find(options["subnet"])
|
||||
whitelist = dict(
|
||||
name=options.get("name"),
|
||||
subnet_id=subnet.id)
|
||||
try:
|
||||
return self._agent.clientneutron.add_interface_router(
|
||||
router.id, whitelist)
|
||||
except neutron_exc.NeutronClientException:
|
||||
pass # BUG(sahid): Needs to know how to get an interface.
|
||||
|
||||
def _Id(self):
|
||||
if isinstance(self._ref, dict):
|
||||
return self._ref["id"]
|
||||
return self._ref.id
|
||||
|
||||
def _Name(self):
|
||||
if isinstance(self._ref, dict):
|
||||
return self._ref["name"]
|
||||
return self._ref.name
|
||||
|
||||
def _Delete(self):
|
||||
if isinstance(self._ref, dict):
|
||||
return self._agent.clientneutron.remove_interface_router(
|
||||
self._ref["device_id"],
|
||||
{"subnet_id": self._ref["fixed_ips"][0]['subnet_id']})
|
||||
self._ref.delete()
|
||||
|
||||
|
||||
class RouterGateway(Base):
|
||||
def _Execute(self, options):
|
||||
router = Router(self._agent).find(options["router"])
|
||||
network = Network(self._agent).find(options["network"])
|
||||
whitelist = dict(
|
||||
name=options.get("name"),
|
||||
network_id=network.id)
|
||||
|
||||
try:
|
||||
return self._agent.clientneutron.add_gateway_router(
|
||||
router.id, whitelist)
|
||||
except neutron_exc.NeutronClientException:
|
||||
pass # BUG(sahid): Needs to know how to get an interface.
|
||||
|
||||
def _Id(self):
|
||||
if isinstance(self._ref, dict):
|
||||
return self._ref["id"]
|
||||
return self._ref.id
|
||||
|
||||
def _Name(self):
|
||||
if isinstance(self._ref, dict):
|
||||
return self._ref["name"]
|
||||
return self._ref.name
|
||||
|
||||
def _Delete(self):
|
||||
if isinstance(self._ref, dict):
|
||||
return self._agent.clientneutron.remove_gateway_router(
|
||||
self._ref["device_id"],
|
||||
{"network_id": self._ref["fixed_ips"][0]['subnet_id']})
|
||||
self._ref.delete()
|
@ -1,2 +0,0 @@
|
||||
import six
|
||||
six.add_move(six.MovedModule('mox', 'mox', 'mox3.mox'))
|
@ -1,99 +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.
|
||||
|
||||
##############################################################################
|
||||
##############################################################################
|
||||
##
|
||||
## DO NOT MODIFY THIS FILE
|
||||
##
|
||||
## This file is being graduated to the warmtest library. Please make all
|
||||
## changes there, and only backport critical fixes here. - dhellmann
|
||||
##
|
||||
##############################################################################
|
||||
##############################################################################
|
||||
|
||||
"""Common utilities used in testing"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
import fixtures
|
||||
import testtools
|
||||
|
||||
_TRUE_VALUES = ('True', 'true', '1', 'yes')
|
||||
_LOG_FORMAT = "%(levelname)8s [%(name)s] %(message)s"
|
||||
|
||||
|
||||
class BaseTestCase(testtools.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(BaseTestCase, self).setUp()
|
||||
self._set_timeout()
|
||||
self._fake_output()
|
||||
self._fake_logs()
|
||||
self.useFixture(fixtures.NestedTempfile())
|
||||
self.useFixture(fixtures.TempHomeDir())
|
||||
self.tempdirs = []
|
||||
|
||||
def _set_timeout(self):
|
||||
test_timeout = os.environ.get('OS_TEST_TIMEOUT', 0)
|
||||
try:
|
||||
test_timeout = int(test_timeout)
|
||||
except ValueError:
|
||||
# If timeout value is invalid do not set a timeout.
|
||||
test_timeout = 0
|
||||
if test_timeout > 0:
|
||||
self.useFixture(fixtures.Timeout(test_timeout, gentle=True))
|
||||
|
||||
def _fake_output(self):
|
||||
if os.environ.get('OS_STDOUT_CAPTURE') in _TRUE_VALUES:
|
||||
stdout = self.useFixture(fixtures.StringStream('stdout')).stream
|
||||
self.useFixture(fixtures.MonkeyPatch('sys.stdout', stdout))
|
||||
if os.environ.get('OS_STDERR_CAPTURE') in _TRUE_VALUES:
|
||||
stderr = self.useFixture(fixtures.StringStream('stderr')).stream
|
||||
self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr))
|
||||
|
||||
def _fake_logs(self):
|
||||
if os.environ.get('OS_DEBUG') in _TRUE_VALUES:
|
||||
level = logging.DEBUG
|
||||
else:
|
||||
level = logging.INFO
|
||||
capture_logs = os.environ.get('OS_LOG_CAPTURE') in _TRUE_VALUES
|
||||
if capture_logs:
|
||||
self.useFixture(
|
||||
fixtures.FakeLogger(
|
||||
format=_LOG_FORMAT,
|
||||
level=level,
|
||||
nuke_handlers=capture_logs,
|
||||
)
|
||||
)
|
||||
else:
|
||||
logging.basicConfig(format=_LOG_FORMAT, level=level)
|
||||
|
||||
def create_tempfiles(self, files, ext='.conf'):
|
||||
tempfiles = []
|
||||
for (basename, contents) in files:
|
||||
if not os.path.isabs(basename):
|
||||
(fd, path) = tempfile.mkstemp(prefix=basename, suffix=ext)
|
||||
else:
|
||||
path = basename + ext
|
||||
fd = os.open(path, os.O_CREAT | os.O_WRONLY)
|
||||
tempfiles.append(path)
|
||||
try:
|
||||
os.write(fd, contents)
|
||||
finally:
|
||||
os.close(fd)
|
||||
return tempfiles
|
@ -1,15 +0,0 @@
|
||||
# Copyright 2013 Cloudwatt
|
||||
#
|
||||
# Author: Sahid Orentino Ferdjaoui <sahid.ferdjaoui@cloudwatt.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.
|
@ -1,3 +0,0 @@
|
||||
#cloud-config
|
||||
packages:
|
||||
- wordpress
|
@ -1,8 +0,0 @@
|
||||
|
||||
from warm.openstack.common import test
|
||||
|
||||
|
||||
class TestCase(test.BaseTestCase):
|
||||
|
||||
def test_noop(self):
|
||||
self.assertTrue(True)
|
@ -1,60 +0,0 @@
|
||||
# Copyright 2013 Cloudwatt
|
||||
#
|
||||
# Author: Sahid Orentino Ferdjaoui <sahid.ferdjaoui@cloudwatt.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.
|
||||
|
||||
"""Some utilities script to be used with warm."""
|
||||
|
||||
import os
|
||||
|
||||
from email.mime import multipart
|
||||
from email.mime import text
|
||||
|
||||
|
||||
MULTIPART_MAPPINGS = {
|
||||
'#include': 'text/x-include-url',
|
||||
'#!': 'text/x-shellscript',
|
||||
'#cloud-config': 'text/cloud-config',
|
||||
'#upstart-job': 'text/upstart-job',
|
||||
'#part-handler': 'text/part-handler',
|
||||
'#cloud-boothook': 'text/cloud-boothook'
|
||||
}
|
||||
|
||||
|
||||
def get_type(fname, deftype="text/plain"):
|
||||
rtype = deftype
|
||||
with open(fname, "rb") as f:
|
||||
line = f.readline()
|
||||
for s, mtype in MULTIPART_MAPPINGS.items():
|
||||
if line.startswith(s):
|
||||
rtype = mtype
|
||||
break
|
||||
return rtype
|
||||
|
||||
|
||||
def multipart_content(*files):
|
||||
"""Returns a mutlipart content.
|
||||
Note:
|
||||
This script was clearly inspired by write-mime-multipart.
|
||||
"""
|
||||
outer = multipart.MIMEMultipart()
|
||||
for fname in files:
|
||||
mtype = get_type(fname)
|
||||
maintype, subtype = mtype.split('/', 1)
|
||||
with open(fname) as f:
|
||||
msg = text.MIMEText(f.read(), _subtype=subtype)
|
||||
msg.add_header('Content-Disposition', 'attachment',
|
||||
filename=os.path.basename(fname))
|
||||
outer.attach(msg)
|
||||
return outer.as_string()
|
@ -1,19 +0,0 @@
|
||||
# Copyright 2013 Cloudwatt
|
||||
#
|
||||
# Author: Sahid Orentino Ferdjaoui <sahid.ferdjaoui@cloudwatt.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 pbr.version
|
||||
|
||||
__version__ = pbr.version.VersionInfo('warm').version_string()
|
Loading…
Reference in New Issue
Block a user