Imported os-client-config as a subtree
Change-Id: If3d145980e4212eab1f0beb78e413717134349b6
This commit is contained in:
commit
96fd7a7b84
7
os-client-config/.coveragerc
Normal file
7
os-client-config/.coveragerc
Normal file
@ -0,0 +1,7 @@
|
||||
[run]
|
||||
branch = True
|
||||
source = os_client_config
|
||||
omit = os_client_config/tests/*,os_client_config/openstack/*
|
||||
|
||||
[report]
|
||||
ignore_errors = True
|
55
os-client-config/.gitignore
vendored
Normal file
55
os-client-config/.gitignore
vendored
Normal file
@ -0,0 +1,55 @@
|
||||
*.py[cod]
|
||||
.venv
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Packages
|
||||
*.egg
|
||||
*.egg-info
|
||||
dist
|
||||
build
|
||||
eggs
|
||||
parts
|
||||
bin
|
||||
var
|
||||
sdist
|
||||
develop-eggs
|
||||
.installed.cfg
|
||||
lib
|
||||
lib64
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
cover
|
||||
.coverage
|
||||
.tox
|
||||
nosetests.xml
|
||||
.stestr/
|
||||
.testrepository
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
|
||||
# Mr Developer
|
||||
.mr.developer.cfg
|
||||
.project
|
||||
.pydevproject
|
||||
|
||||
# Complexity
|
||||
output/*.html
|
||||
output/*/index.html
|
||||
|
||||
# Sphinx
|
||||
doc/build
|
||||
|
||||
# pbr generates these
|
||||
AUTHORS
|
||||
ChangeLog
|
||||
|
||||
# Editors
|
||||
*~
|
||||
.*.swp
|
||||
.*sw?
|
4
os-client-config/.gitreview
Normal file
4
os-client-config/.gitreview
Normal file
@ -0,0 +1,4 @@
|
||||
[gerrit]
|
||||
host=review.openstack.org
|
||||
port=29418
|
||||
project=openstack/os-client-config.git
|
3
os-client-config/.mailmap
Normal file
3
os-client-config/.mailmap
Normal file
@ -0,0 +1,3 @@
|
||||
# Format is:
|
||||
# <preferred e-mail> <other e-mail 1>
|
||||
# <preferred e-mail> <other e-mail 2>
|
3
os-client-config/.stestr.conf
Normal file
3
os-client-config/.stestr.conf
Normal file
@ -0,0 +1,3 @@
|
||||
[DEFAULT]
|
||||
test_path=.
|
||||
top_dir=./
|
7
os-client-config/.testr.conf
Normal file
7
os-client-config/.testr.conf
Normal file
@ -0,0 +1,7 @@
|
||||
[DEFAULT]
|
||||
test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \
|
||||
OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \
|
||||
OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-60} \
|
||||
${PYTHON:-python} -m subunit.run discover -t ./ . $LISTOPT $IDOPTION
|
||||
test_id_option=--load-list $IDFILE
|
||||
test_list_option=--list
|
16
os-client-config/CONTRIBUTING.rst
Normal file
16
os-client-config/CONTRIBUTING.rst
Normal file
@ -0,0 +1,16 @@
|
||||
If you would like to contribute to the development of OpenStack,
|
||||
you must follow the steps in this page:
|
||||
|
||||
http://docs.openstack.org/infra/manual/developers.html
|
||||
|
||||
Once those steps have been completed, changes to OpenStack
|
||||
should be submitted for review via the Gerrit tool, following
|
||||
the workflow documented at:
|
||||
|
||||
http://docs.openstack.org/infra/manual/developers.html#development-workflow
|
||||
|
||||
Pull requests submitted through GitHub will be ignored.
|
||||
|
||||
Bugs should be filed on Launchpad, not GitHub:
|
||||
|
||||
https://bugs.launchpad.net/os-client-config
|
4
os-client-config/HACKING.rst
Normal file
4
os-client-config/HACKING.rst
Normal file
@ -0,0 +1,4 @@
|
||||
os-client-config Style Commandments
|
||||
===============================================
|
||||
|
||||
Read the OpenStack Style Commandments https://docs.openstack.org/hacking/latest
|
175
os-client-config/LICENSE
Normal file
175
os-client-config/LICENSE
Normal file
@ -0,0 +1,175 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
25
os-client-config/README.rst
Normal file
25
os-client-config/README.rst
Normal file
@ -0,0 +1,25 @@
|
||||
================
|
||||
os-client-config
|
||||
================
|
||||
|
||||
.. image:: http://governance.openstack.org/badges/os-client-config.svg
|
||||
:target: http://governance.openstack.org/reference/tags/index.html
|
||||
|
||||
`os-client-config` is a library for collecting client configuration for
|
||||
using an OpenStack cloud in a consistent and comprehensive manner. It
|
||||
will find cloud config for as few as 1 cloud and as many as you want to
|
||||
put in a config file. It will read environment variables and config files,
|
||||
and it also contains some vendor specific default values so that you don't
|
||||
have to know extra info to use OpenStack
|
||||
|
||||
* If you have a config file, you will get the clouds listed in it
|
||||
* If you have environment variables, you will get a cloud named `envvars`
|
||||
* If you have neither, you will get a cloud named `defaults` with base defaults
|
||||
|
||||
Source
|
||||
------
|
||||
|
||||
* Free software: Apache license
|
||||
* Documentation: http://docs.openstack.org/os-client-config/latest
|
||||
* Source: http://git.openstack.org/cgit/openstack/os-client-config
|
||||
* Bugs: http://bugs.launchpad.net/os-client-config
|
86
os-client-config/doc/source/conf.py
Executable file
86
os-client-config/doc/source/conf.py
Executable file
@ -0,0 +1,86 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
# implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
import openstackdocstheme
|
||||
|
||||
sys.path.insert(0, os.path.abspath('../..'))
|
||||
# -- General configuration ----------------------------------------------------
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||
extensions = [
|
||||
'sphinx.ext.autodoc',
|
||||
#'sphinx.ext.intersphinx',
|
||||
'reno.sphinxext',
|
||||
'openstackdocstheme',
|
||||
]
|
||||
|
||||
# openstackdocstheme options
|
||||
repository_name = 'openstack/os-client-config'
|
||||
bug_project = 'os-client-config'
|
||||
bug_tag = ''
|
||||
html_last_updated_fmt = '%Y-%m-%d %H:%M'
|
||||
|
||||
# autodoc generation is a bit aggressive and a nuisance when doing heavy
|
||||
# text edit cycles.
|
||||
# execute "export SPHINX_DEBUG=1" in your terminal to disable
|
||||
|
||||
# The suffix of source filenames.
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'os-client-config'
|
||||
copyright = u'2015, various OpenStack developers'
|
||||
|
||||
# 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']
|
||||
html_theme = 'openstackdocs'
|
||||
html_theme_path = [openstackdocstheme.get_html_theme_path()]
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = '%sdoc' % project
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title, author, documentclass
|
||||
# [howto/manual]).
|
||||
latex_documents = [
|
||||
('index',
|
||||
'%s.tex' % project,
|
||||
u'%s Documentation' % project,
|
||||
u'OpenStack Foundation', 'manual'),
|
||||
]
|
||||
|
||||
# Example configuration for intersphinx: refer to the Python standard library.
|
||||
#intersphinx_mapping = {'http://docs.python.org/': None}
|
4
os-client-config/doc/source/contributor/index.rst
Normal file
4
os-client-config/doc/source/contributor/index.rst
Normal file
@ -0,0 +1,4 @@
|
||||
============
|
||||
Contributing
|
||||
============
|
||||
.. include:: ../../../CONTRIBUTING.rst
|
32
os-client-config/doc/source/index.rst
Normal file
32
os-client-config/doc/source/index.rst
Normal file
@ -0,0 +1,32 @@
|
||||
================
|
||||
os-client-config
|
||||
================
|
||||
|
||||
.. image:: http://governance.openstack.org/badges/os-client-config.svg
|
||||
:target: http://governance.openstack.org/reference/tags/index.html
|
||||
|
||||
`os-client-config` is a library for collecting client configuration for
|
||||
using an OpenStack cloud in a consistent and comprehensive manner. It
|
||||
will find cloud config for as few as 1 cloud and as many as you want to
|
||||
put in a config file. It will read environment variables and config files,
|
||||
and it also contains some vendor specific default values so that you don't
|
||||
have to know extra info to use OpenStack
|
||||
|
||||
* If you have a config file, you will get the clouds listed in it
|
||||
* If you have environment variables, you will get a cloud named `envvars`
|
||||
* If you have neither, you will get a cloud named `defaults` with base defaults
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
install/index
|
||||
user/index
|
||||
reference/index
|
||||
contributor/index
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`modindex`
|
||||
* :ref:`search`
|
12
os-client-config/doc/source/install/index.rst
Normal file
12
os-client-config/doc/source/install/index.rst
Normal file
@ -0,0 +1,12 @@
|
||||
============
|
||||
Installation
|
||||
============
|
||||
|
||||
At the command line::
|
||||
|
||||
$ pip install os-client-config
|
||||
|
||||
Or, if you have virtualenvwrapper installed::
|
||||
|
||||
$ mkvirtualenv os-client-config
|
||||
$ pip install os-client-config
|
10
os-client-config/doc/source/reference/index.rst
Normal file
10
os-client-config/doc/source/reference/index.rst
Normal file
@ -0,0 +1,10 @@
|
||||
=============
|
||||
API Reference
|
||||
=============
|
||||
|
||||
.. module:: os_client_config
|
||||
:synopsis: OpenStack client configuration
|
||||
|
||||
.. autoclass:: os_client_config.OpenStackConfig
|
||||
:members:
|
||||
:inherited-members:
|
303
os-client-config/doc/source/user/configuration.rst
Normal file
303
os-client-config/doc/source/user/configuration.rst
Normal file
@ -0,0 +1,303 @@
|
||||
===========================================
|
||||
Configuring os-client-config Applications
|
||||
===========================================
|
||||
|
||||
Environment Variables
|
||||
---------------------
|
||||
|
||||
`os-client-config` honors all of the normal `OS_*` variables. It does not
|
||||
provide backwards compatibility to service-specific variables such as
|
||||
`NOVA_USERNAME`.
|
||||
|
||||
If you have OpenStack environment variables set, `os-client-config` will produce
|
||||
a cloud config object named `envvars` containing your values from the
|
||||
environment. If you don't like the name `envvars`, that's ok, you can override
|
||||
it by setting `OS_CLOUD_NAME`.
|
||||
|
||||
Service specific settings, like the nova service type, are set with the
|
||||
default service type as a prefix. For instance, to set a special service_type
|
||||
for trove set
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
export OS_DATABASE_SERVICE_TYPE=rax:database
|
||||
|
||||
Config Files
|
||||
------------
|
||||
|
||||
`os-client-config` will look for a file called `clouds.yaml` in the following
|
||||
locations:
|
||||
|
||||
* Current Directory
|
||||
* ~/.config/openstack
|
||||
* /etc/openstack
|
||||
|
||||
The first file found wins.
|
||||
|
||||
You can also set the environment variable `OS_CLIENT_CONFIG_FILE` to an
|
||||
absolute path of a file to look for and that location will be inserted at the
|
||||
front of the file search list.
|
||||
|
||||
The keys are all of the keys you'd expect from `OS_*` - except lower case
|
||||
and without the OS prefix. So, region name is set with `region_name`.
|
||||
|
||||
Service specific settings, like the nova service type, are set with the
|
||||
default service type as a prefix. For instance, to set a special service_type
|
||||
for trove (because you're using Rackspace) set:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
database_service_type: 'rax:database'
|
||||
|
||||
|
||||
Site Specific File Locations
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
In addition to `~/.config/openstack` and `/etc/openstack` - some platforms
|
||||
have other locations they like to put things. `os-client-config` will also
|
||||
look in an OS specific config dir
|
||||
|
||||
* `USER_CONFIG_DIR`
|
||||
* `SITE_CONFIG_DIR`
|
||||
|
||||
`USER_CONFIG_DIR` is different on Linux, OSX and Windows.
|
||||
|
||||
* Linux: `~/.config/openstack`
|
||||
* OSX: `~/Library/Application Support/openstack`
|
||||
* Windows: `C:\\Users\\USERNAME\\AppData\\Local\\OpenStack\\openstack`
|
||||
|
||||
`SITE_CONFIG_DIR` is different on Linux, OSX and Windows.
|
||||
|
||||
* Linux: `/etc/openstack`
|
||||
* OSX: `/Library/Application Support/openstack`
|
||||
* Windows: `C:\\ProgramData\\OpenStack\\openstack`
|
||||
|
||||
An example config file is probably helpful:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
clouds:
|
||||
mtvexx:
|
||||
profile: vexxhost
|
||||
auth:
|
||||
username: mordred@inaugust.com
|
||||
password: XXXXXXXXX
|
||||
project_name: mordred@inaugust.com
|
||||
region_name: ca-ymq-1
|
||||
dns_api_version: 1
|
||||
mordred:
|
||||
region_name: RegionOne
|
||||
auth:
|
||||
username: 'mordred'
|
||||
password: XXXXXXX
|
||||
project_name: 'shade'
|
||||
auth_url: 'https://montytaylor-sjc.openstack.blueboxgrid.com:5001/v2.0'
|
||||
infra:
|
||||
profile: rackspace
|
||||
auth:
|
||||
username: openstackci
|
||||
password: XXXXXXXX
|
||||
project_id: 610275
|
||||
regions:
|
||||
- DFW
|
||||
- ORD
|
||||
- IAD
|
||||
|
||||
You may note a few things. First, since `auth_url` settings are silly
|
||||
and embarrassingly ugly, known cloud vendor profile information is included and
|
||||
may be referenced by name. One of the benefits of that is that `auth_url`
|
||||
isn't the only thing the vendor defaults contain. For instance, since
|
||||
Rackspace lists `rax:database` as the service type for trove, `os-client-config`
|
||||
knows that so that you don't have to. In case the cloud vendor profile is not
|
||||
available, you can provide one called `clouds-public.yaml`, following the same
|
||||
location rules previously mentioned for the config files.
|
||||
|
||||
`regions` can be a list of regions. When you call `get_all_clouds`,
|
||||
you'll get a cloud config object for each cloud/region combo.
|
||||
|
||||
As seen with `dns_service_type`, any setting that makes sense to be per-service,
|
||||
like `service_type` or `endpoint` or `api_version` can be set by prefixing
|
||||
the setting with the default service type. That might strike you funny when
|
||||
setting `service_type` and it does me too - but that's just the world we live
|
||||
in.
|
||||
|
||||
Auth Settings
|
||||
-------------
|
||||
|
||||
Keystone has auth plugins - which means it's not possible to know ahead of time
|
||||
which auth settings are needed. `os-client-config` sets the default plugin type
|
||||
to `password`, which is what things all were before plugins came about. In
|
||||
order to facilitate validation of values, all of the parameters that exist
|
||||
as a result of a chosen plugin need to go into the auth dict. For password
|
||||
auth, this includes `auth_url`, `username` and `password` as well as anything
|
||||
related to domains, projects and trusts.
|
||||
|
||||
Splitting Secrets
|
||||
-----------------
|
||||
|
||||
In some scenarios, such as configuration management controlled environments,
|
||||
it might be easier to have secrets in one file and non-secrets in another.
|
||||
This is fully supported via an optional file `secure.yaml` which follows all
|
||||
the same location rules as `clouds.yaml`. It can contain anything you put
|
||||
in `clouds.yaml` and will take precedence over anything in the `clouds.yaml`
|
||||
file.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
# clouds.yaml
|
||||
clouds:
|
||||
internap:
|
||||
profile: internap
|
||||
auth:
|
||||
username: api-55f9a00fb2619
|
||||
project_name: inap-17037
|
||||
regions:
|
||||
- ams01
|
||||
- nyj01
|
||||
# secure.yaml
|
||||
clouds:
|
||||
internap:
|
||||
auth:
|
||||
password: XXXXXXXXXXXXXXXXX
|
||||
|
||||
SSL Settings
|
||||
------------
|
||||
|
||||
When the access to a cloud is done via a secure connection, `os-client-config`
|
||||
will always verify the SSL cert by default. This can be disabled by setting
|
||||
`verify` to `False`. In case the cert is signed by an unknown CA, a specific
|
||||
cacert can be provided via `cacert`. **WARNING:** `verify` will always have
|
||||
precedence over `cacert`, so when setting a CA cert but disabling `verify`, the
|
||||
cloud cert will never be validated.
|
||||
|
||||
Client certs are also configurable. `cert` will be the client cert file
|
||||
location. In case the cert key is not included within the client cert file,
|
||||
its file location needs to be set via `key`.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
# clouds.yaml
|
||||
clouds:
|
||||
secure:
|
||||
auth: ...
|
||||
key: /home/myhome/client-cert.key
|
||||
cert: /home/myhome/client-cert.crt
|
||||
cacert: /home/myhome/ca.crt
|
||||
insecure:
|
||||
auth: ...
|
||||
verify: False
|
||||
|
||||
Cache Settings
|
||||
--------------
|
||||
|
||||
Accessing a cloud is often expensive, so it's quite common to want to do some
|
||||
client-side caching of those operations. To facilitate that, `os-client-config`
|
||||
understands passing through cache settings to dogpile.cache, with the following
|
||||
behaviors:
|
||||
|
||||
* Listing no config settings means you get a null cache.
|
||||
* `cache.expiration_time` and nothing else gets you memory cache.
|
||||
* Otherwise, `cache.class` and `cache.arguments` are passed in
|
||||
|
||||
Different cloud behaviors are also differently expensive to deal with. If you
|
||||
want to get really crazy and tweak stuff, you can specify different expiration
|
||||
times on a per-resource basis by passing values, in seconds to an expiration
|
||||
mapping keyed on the singular name of the resource. A value of `-1` indicates
|
||||
that the resource should never expire.
|
||||
|
||||
`os-client-config` does not actually cache anything itself, but it collects
|
||||
and presents the cache information so that your various applications that
|
||||
are connecting to OpenStack can share a cache should you desire.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
cache:
|
||||
class: dogpile.cache.pylibmc
|
||||
expiration_time: 3600
|
||||
arguments:
|
||||
url:
|
||||
- 127.0.0.1
|
||||
expiration:
|
||||
server: 5
|
||||
flavor: -1
|
||||
clouds:
|
||||
mtvexx:
|
||||
profile: vexxhost
|
||||
auth:
|
||||
username: mordred@inaugust.com
|
||||
password: XXXXXXXXX
|
||||
project_name: mordred@inaugust.com
|
||||
region_name: ca-ymq-1
|
||||
dns_api_version: 1
|
||||
|
||||
|
||||
IPv6
|
||||
----
|
||||
|
||||
IPv6 is the future, and you should always use it if your cloud supports it and
|
||||
if your local network supports it. Both of those are easily detectable and all
|
||||
friendly software should do the right thing. However, sometimes you might
|
||||
exist in a location where you have an IPv6 stack, but something evil has
|
||||
caused it to not actually function. In that case, there is a config option
|
||||
you can set to unbreak you `force_ipv4`, or `OS_FORCE_IPV4` boolean
|
||||
environment variable.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
client:
|
||||
force_ipv4: true
|
||||
clouds:
|
||||
mtvexx:
|
||||
profile: vexxhost
|
||||
auth:
|
||||
username: mordred@inaugust.com
|
||||
password: XXXXXXXXX
|
||||
project_name: mordred@inaugust.com
|
||||
region_name: ca-ymq-1
|
||||
dns_api_version: 1
|
||||
monty:
|
||||
profile: rax
|
||||
auth:
|
||||
username: mordred@inaugust.com
|
||||
password: XXXXXXXXX
|
||||
project_name: mordred@inaugust.com
|
||||
region_name: DFW
|
||||
|
||||
The above snippet will tell client programs to prefer returning an IPv4
|
||||
address.
|
||||
|
||||
Per-region settings
|
||||
-------------------
|
||||
|
||||
Sometimes you have a cloud provider that has config that is common to the
|
||||
cloud, but also with some things you might want to express on a per-region
|
||||
basis. For instance, Internap provides a public and private network specific
|
||||
to the user in each region, and putting the values of those networks into
|
||||
config can make consuming programs more efficient.
|
||||
|
||||
To support this, the region list can actually be a list of dicts, and any
|
||||
setting that can be set at the cloud level can be overridden for that
|
||||
region.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
clouds:
|
||||
internap:
|
||||
profile: internap
|
||||
auth:
|
||||
password: XXXXXXXXXXXXXXXXX
|
||||
username: api-55f9a00fb2619
|
||||
project_name: inap-17037
|
||||
regions:
|
||||
- name: ams01
|
||||
values:
|
||||
networks:
|
||||
- name: inap-17037-WAN1654
|
||||
routes_externally: true
|
||||
- name: inap-17037-LAN6745
|
||||
- name: nyj01
|
||||
values:
|
||||
networks:
|
||||
- name: inap-17037-WAN1654
|
||||
routes_externally: true
|
||||
- name: inap-17037-LAN6745
|
12
os-client-config/doc/source/user/index.rst
Normal file
12
os-client-config/doc/source/user/index.rst
Normal file
@ -0,0 +1,12 @@
|
||||
========================
|
||||
Using os-client-config
|
||||
========================
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
configuration
|
||||
using
|
||||
vendor-support
|
||||
network-config
|
||||
releasenotes
|
60
os-client-config/doc/source/user/network-config.rst
Normal file
60
os-client-config/doc/source/user/network-config.rst
Normal file
@ -0,0 +1,60 @@
|
||||
==============
|
||||
Network Config
|
||||
==============
|
||||
|
||||
There are several different qualities that networks in OpenStack might have
|
||||
that might not be able to be automatically inferred from the available
|
||||
metadata. To help users navigate more complex setups, `os-client-config`
|
||||
allows configuring a list of network metadata.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
clouds:
|
||||
amazing:
|
||||
networks:
|
||||
- name: blue
|
||||
routes_externally: true
|
||||
- name: purple
|
||||
routes_externally: true
|
||||
default_interface: true
|
||||
- name: green
|
||||
routes_externally: false
|
||||
- name: yellow
|
||||
routes_externally: false
|
||||
nat_destination: true
|
||||
- name: chartreuse
|
||||
routes_externally: false
|
||||
routes_ipv6_externally: true
|
||||
- name: aubergine
|
||||
routes_ipv4_externally: false
|
||||
routes_ipv6_externally: true
|
||||
|
||||
Every entry must have a name field, which can hold either the name or the id
|
||||
of the network.
|
||||
|
||||
`routes_externally` is a boolean field that labels the network as handling
|
||||
north/south traffic off of the cloud. In a public cloud this might be thought
|
||||
of as the "public" network, but in private clouds it's possible it might
|
||||
be an RFC1918 address. In either case, it's provides IPs to servers that
|
||||
things not on the cloud can use. This value defaults to `false`, which
|
||||
indicates only servers on the same network can talk to it.
|
||||
|
||||
`routes_ipv4_externally` and `routes_ipv6_externally` are boolean fields to
|
||||
help handle `routes_externally` in the case where a network has a split stack
|
||||
with different values for IPv4 and IPv6. Either entry, if not given, defaults
|
||||
to the value of `routes_externally`.
|
||||
|
||||
`default_interface` is a boolean field that indicates that the network is the
|
||||
one that programs should use. It defaults to false. An example of needing to
|
||||
use this value is a cloud with two private networks, and where a user is
|
||||
running ansible in one of the servers to talk to other servers on the private
|
||||
network. Because both networks are private, there would otherwise be no way
|
||||
to determine which one should be used for the traffic. There can only be one
|
||||
`default_interface` per cloud.
|
||||
|
||||
`nat_destination` is a boolean field that indicates which network floating
|
||||
ips should be attached to. It defaults to false. Normally this can be inferred
|
||||
by looking for a network that has subnets that have a gateway_ip. But it's
|
||||
possible to have more than one network that satisfies that condition, so the
|
||||
user might want to tell programs which one to pick. There can be only one
|
||||
`nat_destination` per cloud.
|
6
os-client-config/doc/source/user/releasenotes.rst
Normal file
6
os-client-config/doc/source/user/releasenotes.rst
Normal file
@ -0,0 +1,6 @@
|
||||
=============
|
||||
Release Notes
|
||||
=============
|
||||
|
||||
Release notes for `os-client-config` can be found at
|
||||
http://docs.openstack.org/releasenotes/os-client-config/
|
182
os-client-config/doc/source/user/using.rst
Normal file
182
os-client-config/doc/source/user/using.rst
Normal file
@ -0,0 +1,182 @@
|
||||
==========================================
|
||||
Using os-client-config in an Application
|
||||
==========================================
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
The simplest and least useful thing you can do is:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
python -m os_client_config.config
|
||||
|
||||
Which will print out whatever if finds for your config. If you want to use
|
||||
it from python, which is much more likely what you want to do, things like:
|
||||
|
||||
Get a named cloud.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import os_client_config
|
||||
|
||||
cloud_config = os_client_config.OpenStackConfig().get_one_cloud(
|
||||
'internap', region_name='ams01')
|
||||
print(cloud_config.name, cloud_config.region, cloud_config.config)
|
||||
|
||||
Or, get all of the clouds.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import os_client_config
|
||||
|
||||
cloud_config = os_client_config.OpenStackConfig().get_all_clouds()
|
||||
for cloud in cloud_config:
|
||||
print(cloud.name, cloud.region, cloud.config)
|
||||
|
||||
argparse
|
||||
--------
|
||||
|
||||
If you're using os-client-config from a program that wants to process
|
||||
command line options, there is a registration function to register the
|
||||
arguments that both os-client-config and keystoneauth know how to deal
|
||||
with - as well as a consumption argument.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
|
||||
import os_client_config
|
||||
|
||||
cloud_config = os_client_config.OpenStackConfig()
|
||||
parser = argparse.ArgumentParser()
|
||||
cloud_config.register_argparse_arguments(parser, sys.argv)
|
||||
|
||||
options = parser.parse_args()
|
||||
|
||||
cloud = cloud_config.get_one_cloud(argparse=options)
|
||||
|
||||
Constructing OpenStack SDK object
|
||||
---------------------------------
|
||||
|
||||
If what you want to do is get an OpenStack SDK Connection and you want it to
|
||||
do all the normal things related to clouds.yaml, `OS_` environment variables,
|
||||
a helper function is provided. The following will get you a fully configured
|
||||
`openstacksdk` instance.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import os_client_config
|
||||
|
||||
sdk = os_client_config.make_sdk()
|
||||
|
||||
If you want to do the same thing but on a named cloud.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import os_client_config
|
||||
|
||||
sdk = os_client_config.make_sdk(cloud='mtvexx')
|
||||
|
||||
If you want to do the same thing but also support command line parsing.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import argparse
|
||||
|
||||
import os_client_config
|
||||
|
||||
sdk = os_client_config.make_sdk(options=argparse.ArgumentParser())
|
||||
|
||||
It should be noted that OpenStack SDK has ways to construct itself that allow
|
||||
for additional flexibility. If the helper function here does not meet your
|
||||
needs, you should see the `from_config` method of
|
||||
`openstack.connection.Connection <http://developer.openstack.org/sdks/python/openstacksdk/users/guides/connect_from_config.html>`_
|
||||
|
||||
Constructing shade objects
|
||||
--------------------------
|
||||
|
||||
If what you want to do is get a
|
||||
`shade <http://docs.openstack.org/infra/shade/>`_ OpenStackCloud object, a
|
||||
helper function that honors clouds.yaml and `OS_` environment variables is
|
||||
provided. The following will get you a fully configured `OpenStackCloud`
|
||||
instance.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import os_client_config
|
||||
|
||||
cloud = os_client_config.make_shade()
|
||||
|
||||
If you want to do the same thing but on a named cloud.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import os_client_config
|
||||
|
||||
cloud = os_client_config.make_shade(cloud='mtvexx')
|
||||
|
||||
If you want to do the same thing but also support command line parsing.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import argparse
|
||||
|
||||
import os_client_config
|
||||
|
||||
cloud = os_client_config.make_shade(options=argparse.ArgumentParser())
|
||||
|
||||
Constructing REST API Clients
|
||||
-----------------------------
|
||||
|
||||
What if you want to make direct REST calls via a Session interface? You're
|
||||
in luck. A similar interface is available as with `openstacksdk` and `shade`.
|
||||
The main difference is that you need to specify which service you want to
|
||||
talk to and `make_rest_client` will return you a keystoneauth Session object
|
||||
that is mounted on the endpoint for the service you're looking for.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import os_client_config
|
||||
|
||||
session = os_client_config.make_rest_client('compute', cloud='vexxhost')
|
||||
|
||||
response = session.get('/servers')
|
||||
server_list = response.json()['servers']
|
||||
|
||||
Constructing Legacy Client objects
|
||||
----------------------------------
|
||||
|
||||
If you want get an old-style Client object from a python-\*client library,
|
||||
and you want it to do all the normal things related to clouds.yaml, `OS_`
|
||||
environment variables, a helper function is also provided. The following
|
||||
will get you a fully configured `novaclient` instance.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import os_client_config
|
||||
|
||||
nova = os_client_config.make_client('compute')
|
||||
|
||||
If you want to do the same thing but on a named cloud.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import os_client_config
|
||||
|
||||
nova = os_client_config.make_client('compute', cloud='mtvexx')
|
||||
|
||||
If you want to do the same thing but also support command line parsing.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import argparse
|
||||
|
||||
import os_client_config
|
||||
|
||||
nova = os_client_config.make_client(
|
||||
'compute', options=argparse.ArgumentParser())
|
||||
|
||||
If you want to get fancier than that in your python, then the rest of the
|
||||
API is available to you. But often times, you just want to do the one thing.
|
337
os-client-config/doc/source/user/vendor-support.rst
Normal file
337
os-client-config/doc/source/user/vendor-support.rst
Normal file
@ -0,0 +1,337 @@
|
||||
==============
|
||||
Vendor Support
|
||||
==============
|
||||
|
||||
OpenStack presents deployers with many options, some of which can expose
|
||||
differences to end users. `os-client-config` tries its best to collect
|
||||
information about various things a user would need to know. The following
|
||||
is a text representation of the vendor related defaults `os-client-config`
|
||||
knows about.
|
||||
|
||||
Default Values
|
||||
--------------
|
||||
|
||||
These are the default behaviors unless a cloud is configured differently.
|
||||
|
||||
* Identity uses `password` authentication
|
||||
* Identity API Version is 2
|
||||
* Image API Version is 2
|
||||
* Volume API Version is 2
|
||||
* Images must be in `qcow2` format
|
||||
* Images are uploaded using PUT interface
|
||||
* Public IPv4 is directly routable via DHCP from Neutron
|
||||
* IPv6 is not provided
|
||||
* Floating IPs are not required
|
||||
* Floating IPs are provided by Neutron
|
||||
* Security groups are provided by Neutron
|
||||
* Vendor specific agents are not used
|
||||
|
||||
auro
|
||||
----
|
||||
|
||||
https://api.auro.io:5000/v2.0
|
||||
|
||||
============== ================
|
||||
Region Name Location
|
||||
============== ================
|
||||
van1 Vancouver, BC
|
||||
============== ================
|
||||
|
||||
* Public IPv4 is provided via NAT with Neutron Floating IP
|
||||
|
||||
catalyst
|
||||
--------
|
||||
|
||||
https://api.cloud.catalyst.net.nz:5000/v2.0
|
||||
|
||||
============== ================
|
||||
Region Name Location
|
||||
============== ================
|
||||
nz-por-1 Porirua, NZ
|
||||
nz_wlg_2 Wellington, NZ
|
||||
============== ================
|
||||
|
||||
* Image API Version is 1
|
||||
* Images must be in `raw` format
|
||||
* Volume API Version is 1
|
||||
|
||||
citycloud
|
||||
---------
|
||||
|
||||
https://identity1.citycloud.com:5000/v3/
|
||||
|
||||
============== ================
|
||||
Region Name Location
|
||||
============== ================
|
||||
Buf1 Buffalo, NY
|
||||
Fra1 Frankfurt, DE
|
||||
Kna1 Karlskrona, SE
|
||||
La1 Los Angeles, CA
|
||||
Lon1 London, UK
|
||||
Sto2 Stockholm, SE
|
||||
============== ================
|
||||
|
||||
* Identity API Version is 3
|
||||
* Public IPv4 is provided via NAT with Neutron Floating IP
|
||||
* Volume API Version is 1
|
||||
|
||||
conoha
|
||||
------
|
||||
|
||||
https://identity.%(region_name)s.conoha.io
|
||||
|
||||
============== ================
|
||||
Region Name Location
|
||||
============== ================
|
||||
tyo1 Tokyo, JP
|
||||
sin1 Singapore
|
||||
sjc1 San Jose, CA
|
||||
============== ================
|
||||
|
||||
* Image upload is not supported
|
||||
|
||||
datacentred
|
||||
-----------
|
||||
|
||||
https://compute.datacentred.io:5000
|
||||
|
||||
============== ================
|
||||
Region Name Location
|
||||
============== ================
|
||||
sal01 Manchester, UK
|
||||
============== ================
|
||||
|
||||
* Image API Version is 1
|
||||
|
||||
dreamcompute
|
||||
------------
|
||||
|
||||
https://iad2.dream.io:5000
|
||||
|
||||
============== ================
|
||||
Region Name Location
|
||||
============== ================
|
||||
RegionOne Ashburn, VA
|
||||
============== ================
|
||||
|
||||
* Identity API Version is 3
|
||||
* Images must be in `raw` format
|
||||
* IPv6 is provided to every server
|
||||
|
||||
dreamhost
|
||||
---------
|
||||
|
||||
Deprecated, please use dreamcompute
|
||||
|
||||
https://keystone.dream.io/v2.0
|
||||
|
||||
============== ================
|
||||
Region Name Location
|
||||
============== ================
|
||||
RegionOne Ashburn, VA
|
||||
============== ================
|
||||
|
||||
* Images must be in `raw` format
|
||||
* Public IPv4 is provided via NAT with Neutron Floating IP
|
||||
* IPv6 is provided to every server
|
||||
|
||||
otc
|
||||
---
|
||||
|
||||
https://iam.%(region_name)s.otc.t-systems.com/v3
|
||||
|
||||
============== ================
|
||||
Region Name Location
|
||||
============== ================
|
||||
eu-de Germany
|
||||
============== ================
|
||||
|
||||
* Identity API Version is 3
|
||||
* Images must be in `vhd` format
|
||||
* Public IPv4 is provided via NAT with Neutron Floating IP
|
||||
|
||||
elastx
|
||||
------
|
||||
|
||||
https://ops.elastx.net:5000/v2.0
|
||||
|
||||
============== ================
|
||||
Region Name Location
|
||||
============== ================
|
||||
regionOne Stockholm, SE
|
||||
============== ================
|
||||
|
||||
* Public IPv4 is provided via NAT with Neutron Floating IP
|
||||
|
||||
entercloudsuite
|
||||
---------------
|
||||
|
||||
https://api.entercloudsuite.com/v2.0
|
||||
|
||||
============== ================
|
||||
Region Name Location
|
||||
============== ================
|
||||
nl-ams1 Amsterdam, NL
|
||||
it-mil1 Milan, IT
|
||||
de-fra1 Frankfurt, DE
|
||||
============== ================
|
||||
|
||||
* Image API Version is 1
|
||||
* Volume API Version is 1
|
||||
|
||||
fuga
|
||||
----
|
||||
|
||||
https://identity.api.fuga.io:5000
|
||||
|
||||
============== ================
|
||||
Region Name Location
|
||||
============== ================
|
||||
cystack Netherlands
|
||||
============== ================
|
||||
|
||||
* Identity API Version is 3
|
||||
* Volume API Version is 3
|
||||
|
||||
internap
|
||||
--------
|
||||
|
||||
https://identity.api.cloud.iweb.com/v2.0
|
||||
|
||||
============== ================
|
||||
Region Name Location
|
||||
============== ================
|
||||
ams01 Amsterdam, NL
|
||||
da01 Dallas, TX
|
||||
nyj01 New York, NY
|
||||
sin01 Singapore
|
||||
sjc01 San Jose, CA
|
||||
============== ================
|
||||
|
||||
* Floating IPs are not supported
|
||||
|
||||
ovh
|
||||
---
|
||||
|
||||
https://auth.cloud.ovh.net/v2.0
|
||||
|
||||
============== ================
|
||||
Region Name Location
|
||||
============== ================
|
||||
BHS1 Beauharnois, QC
|
||||
SBG1 Strassbourg, FR
|
||||
GRA1 Gravelines, FR
|
||||
============== ================
|
||||
|
||||
* Images may be in `raw` format. The `qcow2` default is also supported
|
||||
* Floating IPs are not supported
|
||||
|
||||
rackspace
|
||||
---------
|
||||
|
||||
https://identity.api.rackspacecloud.com/v2.0/
|
||||
|
||||
============== ================
|
||||
Region Name Location
|
||||
============== ================
|
||||
DFW Dallas, TX
|
||||
HKG Hong Kong
|
||||
IAD Washington, D.C.
|
||||
LON London, UK
|
||||
ORD Chicago, IL
|
||||
SYD Sydney, NSW
|
||||
============== ================
|
||||
|
||||
* Database Service Type is `rax:database`
|
||||
* Compute Service Name is `cloudServersOpenStack`
|
||||
* Images must be in `vhd` format
|
||||
* Images must be uploaded using the Glance Task Interface
|
||||
* Floating IPs are not supported
|
||||
* Public IPv4 is directly routable via static config by Nova
|
||||
* IPv6 is provided to every server
|
||||
* Security groups are not supported
|
||||
* Uploaded Images need properties to not use vendor agent::
|
||||
:vm_mode: hvm
|
||||
:xenapi_use_agent: False
|
||||
* Volume API Version is 1
|
||||
* While passwords are recommended for use, API keys do work as well.
|
||||
The `rackspaceauth` python package must be installed, and then the following
|
||||
can be added to clouds.yaml::
|
||||
|
||||
auth:
|
||||
username: myusername
|
||||
api_key: myapikey
|
||||
auth_type: rackspace_apikey
|
||||
|
||||
switchengines
|
||||
-------------
|
||||
|
||||
https://keystone.cloud.switch.ch:5000/v2.0
|
||||
|
||||
============== ================
|
||||
Region Name Location
|
||||
============== ================
|
||||
LS Lausanne, CH
|
||||
ZH Zurich, CH
|
||||
============== ================
|
||||
|
||||
* Images must be in `raw` format
|
||||
* Images must be uploaded using the Glance Task Interface
|
||||
* Volume API Version is 1
|
||||
|
||||
ultimum
|
||||
-------
|
||||
|
||||
https://console.ultimum-cloud.com:5000/v2.0
|
||||
|
||||
============== ================
|
||||
Region Name Location
|
||||
============== ================
|
||||
RegionOne Prague, CZ
|
||||
============== ================
|
||||
|
||||
* Volume API Version is 1
|
||||
|
||||
unitedstack
|
||||
-----------
|
||||
|
||||
https://identity.api.ustack.com/v3
|
||||
|
||||
============== ================
|
||||
Region Name Location
|
||||
============== ================
|
||||
bj1 Beijing, CN
|
||||
gd1 Guangdong, CN
|
||||
============== ================
|
||||
|
||||
* Identity API Version is 3
|
||||
* Images must be in `raw` format
|
||||
* Volume API Version is 1
|
||||
|
||||
vexxhost
|
||||
--------
|
||||
|
||||
http://auth.vexxhost.net
|
||||
|
||||
============== ================
|
||||
Region Name Location
|
||||
============== ================
|
||||
ca-ymq-1 Montreal, QC
|
||||
============== ================
|
||||
|
||||
* DNS API Version is 1
|
||||
* Identity API Version is 3
|
||||
|
||||
zetta
|
||||
-----
|
||||
|
||||
https://identity.api.zetta.io/v3
|
||||
|
||||
============== ================
|
||||
Region Name Location
|
||||
============== ================
|
||||
no-osl1 Oslo, NO
|
||||
============== ================
|
||||
|
||||
* DNS API Version is 2
|
||||
* Identity API Version is 3
|
111
os-client-config/os_client_config/__init__.py
Normal file
111
os-client-config/os_client_config/__init__.py
Normal file
@ -0,0 +1,111 @@
|
||||
# Copyright (c) 2014 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 sys
|
||||
|
||||
import pbr.version
|
||||
|
||||
from os_client_config import cloud_config
|
||||
from os_client_config.config import OpenStackConfig # noqa
|
||||
|
||||
|
||||
__version__ = pbr.version.VersionInfo('os_client_config').version_string()
|
||||
_config = None
|
||||
|
||||
|
||||
def get_config(
|
||||
service_key=None, options=None,
|
||||
app_name=None, app_version=None,
|
||||
**kwargs):
|
||||
load_yaml_config = kwargs.pop('load_yaml_config', True)
|
||||
global _config
|
||||
if not _config:
|
||||
_config = OpenStackConfig(
|
||||
load_yaml_config=load_yaml_config,
|
||||
app_name=app_name, app_version=app_version)
|
||||
if options:
|
||||
_config.register_argparse_arguments(options, sys.argv, service_key)
|
||||
parsed_options = options.parse_known_args(sys.argv)
|
||||
else:
|
||||
parsed_options = None
|
||||
|
||||
return _config.get_one_cloud(options=parsed_options, **kwargs)
|
||||
|
||||
|
||||
def make_rest_client(
|
||||
service_key, options=None,
|
||||
app_name=None, app_version=None,
|
||||
**kwargs):
|
||||
"""Simple wrapper function. It has almost no features.
|
||||
|
||||
This will get you a raw requests Session Adapter that is mounted
|
||||
on the given service from the keystone service catalog. If you leave
|
||||
off cloud and region_name, it will assume that you've got env vars
|
||||
set, but if you give them, it'll use clouds.yaml as you'd expect.
|
||||
|
||||
This function is deliberately simple. It has no flexibility. If you
|
||||
want flexibility, you can make a cloud config object and call
|
||||
get_session_client on it. This function is to make it easy to poke
|
||||
at OpenStack REST APIs with a properly configured keystone session.
|
||||
"""
|
||||
cloud = get_config(
|
||||
service_key=service_key, options=options,
|
||||
app_name=app_name, app_version=app_version,
|
||||
**kwargs)
|
||||
return cloud.get_session_client(service_key)
|
||||
# Backwards compat - simple_client was a terrible name
|
||||
simple_client = make_rest_client
|
||||
# Backwards compat - session_client was a terrible name
|
||||
session_client = make_rest_client
|
||||
|
||||
|
||||
def make_client(service_key, constructor=None, options=None, **kwargs):
|
||||
"""Simple wrapper for getting a client instance from a client lib.
|
||||
|
||||
OpenStack Client Libraries all have a fairly consistent constructor
|
||||
interface which os-client-config supports. In the simple case, there
|
||||
is one and only one right way to construct a client object. If as a user
|
||||
you don't want to do fancy things, just use this. It honors OS_ environment
|
||||
variables and clouds.yaml - and takes as **kwargs anything you'd expect
|
||||
to pass in.
|
||||
"""
|
||||
cloud = get_config(service_key=service_key, options=options, **kwargs)
|
||||
if not constructor:
|
||||
constructor = cloud_config._get_client(service_key)
|
||||
return cloud.get_legacy_client(service_key, constructor)
|
||||
|
||||
|
||||
def make_sdk(options=None, **kwargs):
|
||||
"""Simple wrapper for getting an OpenStack SDK Connection.
|
||||
|
||||
For completeness, provide a mechanism that matches make_client and
|
||||
make_rest_client. The heavy lifting here is done in openstacksdk.
|
||||
|
||||
:rtype: :class:`~openstack.connection.Connection`
|
||||
"""
|
||||
from openstack import connection
|
||||
cloud = get_config(options=options, **kwargs)
|
||||
return connection.from_config(cloud_config=cloud, options=options)
|
||||
|
||||
|
||||
def make_shade(options=None, **kwargs):
|
||||
"""Simple wrapper for getting a Shade OpenStackCloud object
|
||||
|
||||
A mechanism that matches make_sdk, make_client and make_rest_client.
|
||||
|
||||
:rtype: :class:`~shade.OpenStackCloud`
|
||||
"""
|
||||
import shade
|
||||
cloud = get_config(options=options, **kwargs)
|
||||
return shade.OpenStackCloud(cloud_config=cloud, **kwargs)
|
28
os-client-config/os_client_config/_log.py
Normal file
28
os-client-config/os_client_config/_log.py
Normal file
@ -0,0 +1,28 @@
|
||||
# Copyright (c) 2015 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.
|
||||
|
||||
import logging
|
||||
|
||||
|
||||
class NullHandler(logging.Handler):
|
||||
def emit(self, record):
|
||||
pass
|
||||
|
||||
|
||||
def setup_logging(name):
|
||||
log = logging.getLogger(name)
|
||||
if len(log.handlers) == 0:
|
||||
h = NullHandler()
|
||||
log.addHandler(h)
|
||||
return log
|
558
os-client-config/os_client_config/cloud_config.py
Normal file
558
os-client-config/os_client_config/cloud_config.py
Normal file
@ -0,0 +1,558 @@
|
||||
# Copyright (c) 2014 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 importlib
|
||||
import math
|
||||
import warnings
|
||||
|
||||
from keystoneauth1 import adapter
|
||||
import keystoneauth1.exceptions.catalog
|
||||
from keystoneauth1 import session
|
||||
import requestsexceptions
|
||||
|
||||
import os_client_config
|
||||
from os_client_config import _log
|
||||
from os_client_config import constructors
|
||||
from os_client_config import exceptions
|
||||
|
||||
|
||||
def _get_client(service_key):
|
||||
class_mapping = constructors.get_constructor_mapping()
|
||||
if service_key not in class_mapping:
|
||||
raise exceptions.OpenStackConfigException(
|
||||
"Service {service_key} is unkown. Please pass in a client"
|
||||
" constructor or submit a patch to os-client-config".format(
|
||||
service_key=service_key))
|
||||
mod_name, ctr_name = class_mapping[service_key].rsplit('.', 1)
|
||||
lib_name = mod_name.split('.')[0]
|
||||
try:
|
||||
mod = importlib.import_module(mod_name)
|
||||
except ImportError:
|
||||
raise exceptions.OpenStackConfigException(
|
||||
"Client for '{service_key}' was requested, but"
|
||||
" {mod_name} was unable to be imported. Either import"
|
||||
" the module yourself and pass the constructor in as an argument,"
|
||||
" or perhaps you do not have python-{lib_name} installed.".format(
|
||||
service_key=service_key,
|
||||
mod_name=mod_name,
|
||||
lib_name=lib_name))
|
||||
try:
|
||||
ctr = getattr(mod, ctr_name)
|
||||
except AttributeError:
|
||||
raise exceptions.OpenStackConfigException(
|
||||
"Client for '{service_key}' was requested, but although"
|
||||
" {mod_name} imported fine, the constructor at {fullname}"
|
||||
" as not found. Please check your installation, we have no"
|
||||
" clue what is wrong with your computer.".format(
|
||||
service_key=service_key,
|
||||
mod_name=mod_name,
|
||||
fullname=class_mapping[service_key]))
|
||||
return ctr
|
||||
|
||||
|
||||
def _make_key(key, service_type):
|
||||
if not service_type:
|
||||
return key
|
||||
else:
|
||||
service_type = service_type.lower().replace('-', '_')
|
||||
return "_".join([service_type, key])
|
||||
|
||||
|
||||
class CloudConfig(object):
|
||||
def __init__(self, name, region, config,
|
||||
force_ipv4=False, auth_plugin=None,
|
||||
openstack_config=None, session_constructor=None,
|
||||
app_name=None, app_version=None):
|
||||
self.name = name
|
||||
self.region = region
|
||||
self.config = config
|
||||
self.log = _log.setup_logging(__name__)
|
||||
self._force_ipv4 = force_ipv4
|
||||
self._auth = auth_plugin
|
||||
self._openstack_config = openstack_config
|
||||
self._keystone_session = None
|
||||
self._session_constructor = session_constructor or session.Session
|
||||
self._app_name = app_name
|
||||
self._app_version = app_version
|
||||
|
||||
def __getattr__(self, key):
|
||||
"""Return arbitrary attributes."""
|
||||
|
||||
if key.startswith('os_'):
|
||||
key = key[3:]
|
||||
|
||||
if key in [attr.replace('-', '_') for attr in self.config]:
|
||||
return self.config[key]
|
||||
else:
|
||||
return None
|
||||
|
||||
def __iter__(self):
|
||||
return self.config.__iter__()
|
||||
|
||||
def __eq__(self, other):
|
||||
return (self.name == other.name and self.region == other.region
|
||||
and self.config == other.config)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self == other
|
||||
|
||||
def set_session_constructor(self, session_constructor):
|
||||
"""Sets the Session constructor."""
|
||||
self._session_constructor = session_constructor
|
||||
|
||||
def get_requests_verify_args(self):
|
||||
"""Return the verify and cert values for the requests library."""
|
||||
if self.config['verify'] and self.config['cacert']:
|
||||
verify = self.config['cacert']
|
||||
else:
|
||||
verify = self.config['verify']
|
||||
if self.config['cacert']:
|
||||
warnings.warn(
|
||||
"You are specifying a cacert for the cloud {0} but "
|
||||
"also to ignore the host verification. The host SSL cert "
|
||||
"will not be verified.".format(self.name))
|
||||
|
||||
cert = self.config.get('cert', None)
|
||||
if cert:
|
||||
if self.config['key']:
|
||||
cert = (cert, self.config['key'])
|
||||
return (verify, cert)
|
||||
|
||||
def get_services(self):
|
||||
"""Return a list of service types we know something about."""
|
||||
services = []
|
||||
for key, val in self.config.items():
|
||||
if (key.endswith('api_version')
|
||||
or key.endswith('service_type')
|
||||
or key.endswith('service_name')):
|
||||
services.append("_".join(key.split('_')[:-2]))
|
||||
return list(set(services))
|
||||
|
||||
def get_auth_args(self):
|
||||
return self.config['auth']
|
||||
|
||||
def get_interface(self, service_type=None):
|
||||
key = _make_key('interface', service_type)
|
||||
interface = self.config.get('interface')
|
||||
return self.config.get(key, interface)
|
||||
|
||||
def get_region_name(self, service_type=None):
|
||||
if not service_type:
|
||||
return self.region
|
||||
key = _make_key('region_name', service_type)
|
||||
return self.config.get(key, self.region)
|
||||
|
||||
def get_api_version(self, service_type):
|
||||
key = _make_key('api_version', service_type)
|
||||
return self.config.get(key, None)
|
||||
|
||||
def get_service_type(self, service_type):
|
||||
key = _make_key('service_type', service_type)
|
||||
# Cinder did an evil thing where they defined a second service
|
||||
# type in the catalog. Of course, that's insane, so let's hide this
|
||||
# atrocity from the as-yet-unsullied eyes of our users.
|
||||
# Of course, if the user requests a volumev2, that structure should
|
||||
# still work.
|
||||
# What's even more amazing is that they did it AGAIN with cinder v3
|
||||
# And then I learned that mistral copied it.
|
||||
if service_type == 'volume':
|
||||
if self.get_api_version(service_type).startswith('2'):
|
||||
service_type = 'volumev2'
|
||||
elif self.get_api_version(service_type).startswith('3'):
|
||||
service_type = 'volumev3'
|
||||
elif service_type == 'workflow':
|
||||
if self.get_api_version(service_type).startswith('2'):
|
||||
service_type = 'workflowv2'
|
||||
return self.config.get(key, service_type)
|
||||
|
||||
def get_service_name(self, service_type):
|
||||
key = _make_key('service_name', service_type)
|
||||
return self.config.get(key, None)
|
||||
|
||||
def get_endpoint(self, service_type):
|
||||
key = _make_key('endpoint_override', service_type)
|
||||
old_key = _make_key('endpoint', service_type)
|
||||
return self.config.get(key, self.config.get(old_key, None))
|
||||
|
||||
@property
|
||||
def prefer_ipv6(self):
|
||||
return not self._force_ipv4
|
||||
|
||||
@property
|
||||
def force_ipv4(self):
|
||||
return self._force_ipv4
|
||||
|
||||
def get_auth(self):
|
||||
"""Return a keystoneauth plugin from the auth credentials."""
|
||||
return self._auth
|
||||
|
||||
def get_session(self):
|
||||
"""Return a keystoneauth session based on the auth credentials."""
|
||||
if self._keystone_session is None:
|
||||
if not self._auth:
|
||||
raise exceptions.OpenStackConfigException(
|
||||
"Problem with auth parameters")
|
||||
(verify, cert) = self.get_requests_verify_args()
|
||||
# Turn off urllib3 warnings about insecure certs if we have
|
||||
# explicitly configured requests to tell it we do not want
|
||||
# cert verification
|
||||
if not verify:
|
||||
self.log.debug(
|
||||
"Turning off SSL warnings for {cloud}:{region}"
|
||||
" since verify=False".format(
|
||||
cloud=self.name, region=self.region))
|
||||
requestsexceptions.squelch_warnings(insecure_requests=not verify)
|
||||
self._keystone_session = self._session_constructor(
|
||||
auth=self._auth,
|
||||
verify=verify,
|
||||
cert=cert,
|
||||
timeout=self.config['api_timeout'])
|
||||
if hasattr(self._keystone_session, 'additional_user_agent'):
|
||||
self._keystone_session.additional_user_agent.append(
|
||||
('os-client-config', os_client_config.__version__))
|
||||
# Using old keystoneauth with new os-client-config fails if
|
||||
# we pass in app_name and app_version. Those are not essential,
|
||||
# nor a reason to bump our minimum, so just test for the session
|
||||
# having the attribute post creation and set them then.
|
||||
if hasattr(self._keystone_session, 'app_name'):
|
||||
self._keystone_session.app_name = self._app_name
|
||||
if hasattr(self._keystone_session, 'app_version'):
|
||||
self._keystone_session.app_version = self._app_version
|
||||
return self._keystone_session
|
||||
|
||||
def get_service_catalog(self):
|
||||
"""Helper method to grab the service catalog."""
|
||||
return self._auth.get_access(self.get_session()).service_catalog
|
||||
|
||||
def get_session_client(self, service_key):
|
||||
"""Return a prepped requests adapter for a given service.
|
||||
|
||||
This is useful for making direct requests calls against a
|
||||
'mounted' endpoint. That is, if you do:
|
||||
|
||||
client = get_session_client('compute')
|
||||
|
||||
then you can do:
|
||||
|
||||
client.get('/flavors')
|
||||
|
||||
and it will work like you think.
|
||||
"""
|
||||
|
||||
return adapter.Adapter(
|
||||
session=self.get_session(),
|
||||
service_type=self.get_service_type(service_key),
|
||||
service_name=self.get_service_name(service_key),
|
||||
interface=self.get_interface(service_key),
|
||||
region_name=self.region)
|
||||
|
||||
def _get_highest_endpoint(self, service_types, kwargs):
|
||||
session = self.get_session()
|
||||
for service_type in service_types:
|
||||
kwargs['service_type'] = service_type
|
||||
try:
|
||||
# Return the highest version we find that matches
|
||||
# the request
|
||||
return session.get_endpoint(**kwargs)
|
||||
except keystoneauth1.exceptions.catalog.EndpointNotFound:
|
||||
pass
|
||||
|
||||
def get_session_endpoint(
|
||||
self, service_key, min_version=None, max_version=None):
|
||||
"""Return the endpoint from config or the catalog.
|
||||
|
||||
If a configuration lists an explicit endpoint for a service,
|
||||
return that. Otherwise, fetch the service catalog from the
|
||||
keystone session and return the appropriate endpoint.
|
||||
|
||||
:param service_key: Generic key for service, such as 'compute' or
|
||||
'network'
|
||||
|
||||
"""
|
||||
|
||||
override_endpoint = self.get_endpoint(service_key)
|
||||
if override_endpoint:
|
||||
return override_endpoint
|
||||
endpoint = None
|
||||
kwargs = {
|
||||
'service_name': self.get_service_name(service_key),
|
||||
'region_name': self.region
|
||||
}
|
||||
kwargs['interface'] = self.get_interface(service_key)
|
||||
if service_key == 'volume' and not self.get_api_version('volume'):
|
||||
# If we don't have a configured cinder version, we can't know
|
||||
# to request a different service_type
|
||||
min_version = float(min_version or 1)
|
||||
max_version = float(max_version or 3)
|
||||
min_major = math.trunc(float(min_version))
|
||||
max_major = math.trunc(float(max_version))
|
||||
versions = range(int(max_major) + 1, int(min_major), -1)
|
||||
service_types = []
|
||||
for version in versions:
|
||||
if version == 1:
|
||||
service_types.append('volume')
|
||||
else:
|
||||
service_types.append('volumev{v}'.format(v=version))
|
||||
else:
|
||||
service_types = [self.get_service_type(service_key)]
|
||||
endpoint = self._get_highest_endpoint(service_types, kwargs)
|
||||
if not endpoint:
|
||||
self.log.warning(
|
||||
"Keystone catalog entry not found ("
|
||||
"service_type=%s,service_name=%s"
|
||||
"interface=%s,region_name=%s)",
|
||||
service_key,
|
||||
kwargs['service_name'],
|
||||
kwargs['interface'],
|
||||
kwargs['region_name'])
|
||||
return endpoint
|
||||
|
||||
def get_legacy_client(
|
||||
self, service_key, client_class=None, interface_key=None,
|
||||
pass_version_arg=True, version=None, min_version=None,
|
||||
max_version=None, **kwargs):
|
||||
"""Return a legacy OpenStack client object for the given config.
|
||||
|
||||
Most of the OpenStack python-*client libraries have the same
|
||||
interface for their client constructors, but there are several
|
||||
parameters one wants to pass given a :class:`CloudConfig` object.
|
||||
|
||||
In the future, OpenStack API consumption should be done through
|
||||
the OpenStack SDK, but that's not ready yet. This is for getting
|
||||
Client objects from python-*client only.
|
||||
|
||||
:param service_key: Generic key for service, such as 'compute' or
|
||||
'network'
|
||||
:param client_class: Class of the client to be instantiated. This
|
||||
should be the unversioned version if there
|
||||
is one, such as novaclient.client.Client, or
|
||||
the versioned one, such as
|
||||
neutronclient.v2_0.client.Client if there isn't
|
||||
:param interface_key: (optional) Some clients, such as glanceclient
|
||||
only accept the parameter 'interface' instead
|
||||
of 'endpoint_type' - this is a get-out-of-jail
|
||||
parameter for those until they can be aligned.
|
||||
os-client-config understands this to be the
|
||||
case if service_key is image, so this is really
|
||||
only for use with other unknown broken clients.
|
||||
:param pass_version_arg: (optional) If a versioned Client constructor
|
||||
was passed to client_class, set this to
|
||||
False, which will tell get_client to not
|
||||
pass a version parameter. os-client-config
|
||||
already understand that this is the
|
||||
case for network, so it can be omitted in
|
||||
that case.
|
||||
:param version: (optional) Version string to override the configured
|
||||
version string.
|
||||
:param min_version: (options) Minimum version acceptable.
|
||||
:param max_version: (options) Maximum version acceptable.
|
||||
:param kwargs: (optional) keyword args are passed through to the
|
||||
Client constructor, so this is in case anything
|
||||
additional needs to be passed in.
|
||||
"""
|
||||
if not client_class:
|
||||
client_class = _get_client(service_key)
|
||||
|
||||
interface = self.get_interface(service_key)
|
||||
# trigger exception on lack of service
|
||||
endpoint = self.get_session_endpoint(
|
||||
service_key, min_version=min_version, max_version=max_version)
|
||||
endpoint_override = self.get_endpoint(service_key)
|
||||
|
||||
if service_key == 'object-store':
|
||||
constructor_kwargs = dict(
|
||||
session=self.get_session(),
|
||||
os_options=dict(
|
||||
service_type=self.get_service_type(service_key),
|
||||
object_storage_url=endpoint_override,
|
||||
region_name=self.region))
|
||||
else:
|
||||
constructor_kwargs = dict(
|
||||
session=self.get_session(),
|
||||
service_name=self.get_service_name(service_key),
|
||||
service_type=self.get_service_type(service_key),
|
||||
endpoint_override=endpoint_override,
|
||||
region_name=self.region)
|
||||
|
||||
if service_key == 'image':
|
||||
# os-client-config does not depend on glanceclient, but if
|
||||
# the user passed in glanceclient.client.Client, which they
|
||||
# would need to do if they were requesting 'image' - then
|
||||
# they necessarily have glanceclient installed
|
||||
from glanceclient.common import utils as glance_utils
|
||||
endpoint, detected_version = glance_utils.strip_version(endpoint)
|
||||
# If the user has passed in a version, that's explicit, use it
|
||||
if not version:
|
||||
version = detected_version
|
||||
# If the user has passed in or configured an override, use it.
|
||||
# Otherwise, ALWAYS pass in an endpoint_override becuase
|
||||
# we've already done version stripping, so we don't want version
|
||||
# reconstruction to happen twice
|
||||
if not endpoint_override:
|
||||
constructor_kwargs['endpoint_override'] = endpoint
|
||||
constructor_kwargs.update(kwargs)
|
||||
if pass_version_arg and service_key != 'object-store':
|
||||
if not version:
|
||||
version = self.get_api_version(service_key)
|
||||
if not version and service_key == 'volume':
|
||||
from cinderclient import client as cinder_client
|
||||
version = cinder_client.get_volume_api_from_url(endpoint)
|
||||
# Temporary workaround while we wait for python-openstackclient
|
||||
# to be able to handle 2.0 which is what neutronclient expects
|
||||
if service_key == 'network' and version == '2':
|
||||
version = '2.0'
|
||||
if service_key == 'identity':
|
||||
# Workaround for bug#1513839
|
||||
if 'endpoint' not in constructor_kwargs:
|
||||
endpoint = self.get_session_endpoint('identity')
|
||||
constructor_kwargs['endpoint'] = endpoint
|
||||
if service_key == 'network':
|
||||
constructor_kwargs['api_version'] = version
|
||||
elif service_key == 'baremetal':
|
||||
if version != '1':
|
||||
# Set Ironic Microversion
|
||||
constructor_kwargs['os_ironic_api_version'] = version
|
||||
# Version arg is the major version, not the full microstring
|
||||
constructor_kwargs['version'] = version[0]
|
||||
else:
|
||||
constructor_kwargs['version'] = version
|
||||
if min_version and min_version > float(version):
|
||||
raise exceptions.OpenStackConfigVersionException(
|
||||
"Minimum version {min_version} requested but {version}"
|
||||
" found".format(min_version=min_version, version=version),
|
||||
version=version)
|
||||
if max_version and max_version < float(version):
|
||||
raise exceptions.OpenStackConfigVersionException(
|
||||
"Maximum version {max_version} requested but {version}"
|
||||
" found".format(max_version=max_version, version=version),
|
||||
version=version)
|
||||
if service_key == 'database':
|
||||
# TODO(mordred) Remove when https://review.openstack.org/314032
|
||||
# has landed and released. We're passing in a Session, but the
|
||||
# trove Client object has username and password as required
|
||||
# args
|
||||
constructor_kwargs['username'] = None
|
||||
constructor_kwargs['password'] = None
|
||||
|
||||
if not interface_key:
|
||||
if service_key in ('image', 'key-manager'):
|
||||
interface_key = 'interface'
|
||||
elif (service_key == 'identity'
|
||||
and version and version.startswith('3')):
|
||||
interface_key = 'interface'
|
||||
else:
|
||||
interface_key = 'endpoint_type'
|
||||
if service_key == 'object-store':
|
||||
constructor_kwargs['os_options'][interface_key] = interface
|
||||
else:
|
||||
constructor_kwargs[interface_key] = interface
|
||||
|
||||
return client_class(**constructor_kwargs)
|
||||
|
||||
def get_cache_expiration_time(self):
|
||||
if self._openstack_config:
|
||||
return self._openstack_config.get_cache_expiration_time()
|
||||
|
||||
def get_cache_path(self):
|
||||
if self._openstack_config:
|
||||
return self._openstack_config.get_cache_path()
|
||||
|
||||
def get_cache_class(self):
|
||||
if self._openstack_config:
|
||||
return self._openstack_config.get_cache_class()
|
||||
|
||||
def get_cache_arguments(self):
|
||||
if self._openstack_config:
|
||||
return self._openstack_config.get_cache_arguments()
|
||||
|
||||
def get_cache_expiration(self):
|
||||
if self._openstack_config:
|
||||
return self._openstack_config.get_cache_expiration()
|
||||
|
||||
def get_cache_resource_expiration(self, resource, default=None):
|
||||
"""Get expiration time for a resource
|
||||
|
||||
:param resource: Name of the resource type
|
||||
:param default: Default value to return if not found (optional,
|
||||
defaults to None)
|
||||
|
||||
:returns: Expiration time for the resource type as float or default
|
||||
"""
|
||||
if self._openstack_config:
|
||||
expiration = self._openstack_config.get_cache_expiration()
|
||||
if resource not in expiration:
|
||||
return default
|
||||
return float(expiration[resource])
|
||||
|
||||
def requires_floating_ip(self):
|
||||
"""Return whether or not this cloud requires floating ips.
|
||||
|
||||
|
||||
:returns: True of False if know, None if discovery is needed.
|
||||
If requires_floating_ip is not configured but the cloud is
|
||||
known to not provide floating ips, will return False.
|
||||
"""
|
||||
if self.config['floating_ip_source'] == "None":
|
||||
return False
|
||||
return self.config.get('requires_floating_ip')
|
||||
|
||||
def get_external_networks(self):
|
||||
"""Get list of network names for external networks."""
|
||||
return [
|
||||
net['name'] for net in self.config['networks']
|
||||
if net['routes_externally']]
|
||||
|
||||
def get_external_ipv4_networks(self):
|
||||
"""Get list of network names for external IPv4 networks."""
|
||||
return [
|
||||
net['name'] for net in self.config['networks']
|
||||
if net['routes_ipv4_externally']]
|
||||
|
||||
def get_external_ipv6_networks(self):
|
||||
"""Get list of network names for external IPv6 networks."""
|
||||
return [
|
||||
net['name'] for net in self.config['networks']
|
||||
if net['routes_ipv6_externally']]
|
||||
|
||||
def get_internal_networks(self):
|
||||
"""Get list of network names for internal networks."""
|
||||
return [
|
||||
net['name'] for net in self.config['networks']
|
||||
if not net['routes_externally']]
|
||||
|
||||
def get_internal_ipv4_networks(self):
|
||||
"""Get list of network names for internal IPv4 networks."""
|
||||
return [
|
||||
net['name'] for net in self.config['networks']
|
||||
if not net['routes_ipv4_externally']]
|
||||
|
||||
def get_internal_ipv6_networks(self):
|
||||
"""Get list of network names for internal IPv6 networks."""
|
||||
return [
|
||||
net['name'] for net in self.config['networks']
|
||||
if not net['routes_ipv6_externally']]
|
||||
|
||||
def get_default_network(self):
|
||||
"""Get network used for default interactions."""
|
||||
for net in self.config['networks']:
|
||||
if net['default_interface']:
|
||||
return net['name']
|
||||
return None
|
||||
|
||||
def get_nat_destination(self):
|
||||
"""Get network used for NAT destination."""
|
||||
for net in self.config['networks']:
|
||||
if net['nat_destination']:
|
||||
return net['name']
|
||||
return None
|
1241
os-client-config/os_client_config/config.py
Normal file
1241
os-client-config/os_client_config/config.py
Normal file
File diff suppressed because it is too large
Load Diff
16
os-client-config/os_client_config/constructors.json
Normal file
16
os-client-config/os_client_config/constructors.json
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"application-catalog": "muranoclient.client.Client",
|
||||
"baremetal": "ironicclient.client.Client",
|
||||
"compute": "novaclient.client.Client",
|
||||
"container-infra": "magnumclient.client.Client",
|
||||
"database": "troveclient.client.Client",
|
||||
"dns": "designateclient.client.Client",
|
||||
"identity": "keystoneclient.client.Client",
|
||||
"image": "glanceclient.Client",
|
||||
"key-manager": "barbicanclient.client.Client",
|
||||
"metering": "ceilometerclient.client.Client",
|
||||
"network": "neutronclient.neutron.client.Client",
|
||||
"object-store": "swiftclient.client.Connection",
|
||||
"orchestration": "heatclient.client.Client",
|
||||
"volume": "cinderclient.client.Client"
|
||||
}
|
36
os-client-config/os_client_config/constructors.py
Normal file
36
os-client-config/os_client_config/constructors.py
Normal file
@ -0,0 +1,36 @@
|
||||
# Copyright (c) 2014 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 json
|
||||
import os
|
||||
import threading
|
||||
|
||||
_json_path = os.path.join(
|
||||
os.path.dirname(os.path.realpath(__file__)), 'constructors.json')
|
||||
_class_mapping = None
|
||||
_class_mapping_lock = threading.Lock()
|
||||
|
||||
|
||||
def get_constructor_mapping():
|
||||
global _class_mapping
|
||||
if _class_mapping is not None:
|
||||
return _class_mapping.copy()
|
||||
with _class_mapping_lock:
|
||||
if _class_mapping is not None:
|
||||
return _class_mapping.copy()
|
||||
tmp_class_mapping = {}
|
||||
with open(_json_path, 'r') as json_file:
|
||||
tmp_class_mapping.update(json.load(json_file))
|
||||
_class_mapping = tmp_class_mapping
|
||||
return tmp_class_mapping.copy()
|
27
os-client-config/os_client_config/defaults.json
Normal file
27
os-client-config/os_client_config/defaults.json
Normal file
@ -0,0 +1,27 @@
|
||||
{
|
||||
"application_catalog_api_version": "1",
|
||||
"auth_type": "password",
|
||||
"baremetal_api_version": "1",
|
||||
"container_api_version": "1",
|
||||
"container_infra_api_version": "1",
|
||||
"compute_api_version": "2",
|
||||
"database_api_version": "1.0",
|
||||
"disable_vendor_agent": {},
|
||||
"dns_api_version": "2",
|
||||
"interface": "public",
|
||||
"floating_ip_source": "neutron",
|
||||
"identity_api_version": "2.0",
|
||||
"image_api_use_tasks": false,
|
||||
"image_api_version": "2",
|
||||
"image_format": "qcow2",
|
||||
"key_manager_api_version": "v1",
|
||||
"message": "",
|
||||
"metering_api_version": "2",
|
||||
"network_api_version": "2",
|
||||
"object_store_api_version": "1",
|
||||
"orchestration_api_version": "1",
|
||||
"secgroup_source": "neutron",
|
||||
"status": "active",
|
||||
"volume_api_version": "2",
|
||||
"workflow_api_version": "2"
|
||||
}
|
52
os-client-config/os_client_config/defaults.py
Normal file
52
os-client-config/os_client_config/defaults.py
Normal file
@ -0,0 +1,52 @@
|
||||
# Copyright (c) 2014 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 json
|
||||
import os
|
||||
import threading
|
||||
|
||||
_json_path = os.path.join(
|
||||
os.path.dirname(os.path.realpath(__file__)), 'defaults.json')
|
||||
_defaults = None
|
||||
_defaults_lock = threading.Lock()
|
||||
|
||||
|
||||
def get_defaults():
|
||||
global _defaults
|
||||
if _defaults is not None:
|
||||
return _defaults.copy()
|
||||
with _defaults_lock:
|
||||
if _defaults is not None:
|
||||
# Did someone else just finish filling it?
|
||||
return _defaults.copy()
|
||||
# Python language specific defaults
|
||||
# These are defaults related to use of python libraries, they are
|
||||
# not qualities of a cloud.
|
||||
#
|
||||
# NOTE(harlowja): update a in-memory dict, before updating
|
||||
# the global one so that other callers of get_defaults do not
|
||||
# see the partially filled one.
|
||||
tmp_defaults = dict(
|
||||
api_timeout=None,
|
||||
verify=True,
|
||||
cacert=None,
|
||||
cert=None,
|
||||
key=None,
|
||||
)
|
||||
with open(_json_path, 'r') as json_file:
|
||||
updates = json.load(json_file)
|
||||
if updates is not None:
|
||||
tmp_defaults.update(updates)
|
||||
_defaults = tmp_defaults
|
||||
return tmp_defaults.copy()
|
25
os-client-config/os_client_config/exceptions.py
Normal file
25
os-client-config/os_client_config/exceptions.py
Normal file
@ -0,0 +1,25 @@
|
||||
# Copyright (c) 2014 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.
|
||||
|
||||
|
||||
class OpenStackConfigException(Exception):
|
||||
"""Something went wrong with parsing your OpenStack Config."""
|
||||
|
||||
|
||||
class OpenStackConfigVersionException(OpenStackConfigException):
|
||||
"""A version was requested that is different than what was found."""
|
||||
|
||||
def __init__(self, version):
|
||||
super(OpenStackConfigVersionException, self).__init__()
|
||||
self.version = version
|
121
os-client-config/os_client_config/schema.json
Normal file
121
os-client-config/os_client_config/schema.json
Normal file
@ -0,0 +1,121 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"id": "https://git.openstack.org/cgit/openstack/cloud-data/plain/schema.json#",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"auth_type": {
|
||||
"name": "Auth Type",
|
||||
"description": "Name of authentication plugin to be used",
|
||||
"default": "password",
|
||||
"type": "string"
|
||||
},
|
||||
"disable_vendor_agent": {
|
||||
"name": "Disable Vendor Agent Properties",
|
||||
"description": "Image properties required to disable vendor agent",
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
},
|
||||
"floating_ip_source": {
|
||||
"name": "Floating IP Source",
|
||||
"description": "Which service provides Floating IPs",
|
||||
"enum": [ "neutron", "nova", "None" ],
|
||||
"default": "neutron"
|
||||
},
|
||||
"image_api_use_tasks": {
|
||||
"name": "Image Task API",
|
||||
"description": "Does the cloud require the Image Task API",
|
||||
"default": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
"image_format": {
|
||||
"name": "Image Format",
|
||||
"description": "Format for uploaded Images",
|
||||
"default": "qcow2",
|
||||
"type": "string"
|
||||
},
|
||||
"interface": {
|
||||
"name": "API Interface",
|
||||
"description": "Which API Interface should connections hit",
|
||||
"default": "public",
|
||||
"enum": [ "public", "internal", "admin" ]
|
||||
},
|
||||
"secgroup_source": {
|
||||
"name": "Security Group Source",
|
||||
"description": "Which service provides security groups",
|
||||
"default": "neutron",
|
||||
"enum": [ "neutron", "nova", "None" ]
|
||||
},
|
||||
"baremetal_api_version": {
|
||||
"name": "Baremetal API Service Type",
|
||||
"description": "Baremetal API Service Type",
|
||||
"default": "1",
|
||||
"type": "string"
|
||||
},
|
||||
"compute_api_version": {
|
||||
"name": "Compute API Version",
|
||||
"description": "Compute API Version",
|
||||
"default": "2",
|
||||
"type": "string"
|
||||
},
|
||||
"database_api_version": {
|
||||
"name": "Database API Version",
|
||||
"description": "Database API Version",
|
||||
"default": "1.0",
|
||||
"type": "string"
|
||||
},
|
||||
"dns_api_version": {
|
||||
"name": "DNS API Version",
|
||||
"description": "DNS API Version",
|
||||
"default": "2",
|
||||
"type": "string"
|
||||
},
|
||||
"identity_api_version": {
|
||||
"name": "Identity API Version",
|
||||
"description": "Identity API Version",
|
||||
"default": "2",
|
||||
"type": "string"
|
||||
},
|
||||
"image_api_version": {
|
||||
"name": "Image API Version",
|
||||
"description": "Image API Version",
|
||||
"default": "1",
|
||||
"type": "string"
|
||||
},
|
||||
"network_api_version": {
|
||||
"name": "Network API Version",
|
||||
"description": "Network API Version",
|
||||
"default": "2",
|
||||
"type": "string"
|
||||
},
|
||||
"object_store_api_version": {
|
||||
"name": "Object Storage API Version",
|
||||
"description": "Object Storage API Version",
|
||||
"default": "1",
|
||||
"type": "string"
|
||||
},
|
||||
"volume_api_version": {
|
||||
"name": "Volume API Version",
|
||||
"description": "Volume API Version",
|
||||
"default": "2",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"auth_type",
|
||||
"baremetal_api_version",
|
||||
"compute_api_version",
|
||||
"database_api_version",
|
||||
"disable_vendor_agent",
|
||||
"dns_api_version",
|
||||
"floating_ip_source",
|
||||
"identity_api_version",
|
||||
"image_api_use_tasks",
|
||||
"image_api_version",
|
||||
"image_format",
|
||||
"interface",
|
||||
"network_api_version",
|
||||
"object_store_api_version",
|
||||
"secgroup_source",
|
||||
"volume_api_version"
|
||||
]
|
||||
}
|
0
os-client-config/os_client_config/tests/__init__.py
Normal file
0
os-client-config/os_client_config/tests/__init__.py
Normal file
244
os-client-config/os_client_config/tests/base.py
Normal file
244
os-client-config/os_client_config/tests/base.py
Normal file
@ -0,0 +1,244 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2010-2011 OpenStack Foundation
|
||||
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
|
||||
import copy
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
from os_client_config import cloud_config
|
||||
|
||||
import extras
|
||||
import fixtures
|
||||
from oslotest import base
|
||||
import yaml
|
||||
|
||||
|
||||
VENDOR_CONF = {
|
||||
'public-clouds': {
|
||||
'_test_cloud_in_our_cloud': {
|
||||
'auth': {
|
||||
'auth_url': 'http://example.com/v2',
|
||||
'username': 'testotheruser',
|
||||
'project_name': 'testproject',
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
USER_CONF = {
|
||||
'cache': {
|
||||
'max_age': '1',
|
||||
'expiration': {
|
||||
'server': 5,
|
||||
'image': '7',
|
||||
},
|
||||
},
|
||||
'client': {
|
||||
'force_ipv4': True,
|
||||
},
|
||||
'clouds': {
|
||||
'_test-cloud_': {
|
||||
'profile': '_test_cloud_in_our_cloud',
|
||||
'auth': {
|
||||
'auth_url': 'http://example.com/v2',
|
||||
'username': 'testuser',
|
||||
'password': 'testpass',
|
||||
},
|
||||
'region_name': 'test-region',
|
||||
},
|
||||
'_test_cloud_no_vendor': {
|
||||
'profile': '_test_non_existant_cloud',
|
||||
'auth': {
|
||||
'auth_url': 'http://example.com/v2',
|
||||
'username': 'testuser',
|
||||
'project_name': 'testproject',
|
||||
},
|
||||
'region-name': 'test-region',
|
||||
},
|
||||
'_test-cloud-int-project_': {
|
||||
'auth': {
|
||||
'username': 'testuser',
|
||||
'password': 'testpass',
|
||||
'domain_id': 'awesome-domain',
|
||||
'project_id': 12345,
|
||||
'auth_url': 'http://example.com/v2',
|
||||
},
|
||||
'region_name': 'test-region',
|
||||
},
|
||||
'_test-cloud-domain-id_': {
|
||||
'auth': {
|
||||
'username': 'testuser',
|
||||
'password': 'testpass',
|
||||
'project_id': 12345,
|
||||
'auth_url': 'http://example.com/v2',
|
||||
'domain_id': '6789',
|
||||
'project_domain_id': '123456789',
|
||||
},
|
||||
'region_name': 'test-region',
|
||||
},
|
||||
'_test-cloud-networks_': {
|
||||
'auth': {
|
||||
'username': 'testuser',
|
||||
'password': 'testpass',
|
||||
'project_id': 12345,
|
||||
'auth_url': 'http://example.com/v2',
|
||||
'domain_id': '6789',
|
||||
'project_domain_id': '123456789',
|
||||
},
|
||||
'networks': [{
|
||||
'name': 'a-public',
|
||||
'routes_externally': True,
|
||||
}, {
|
||||
'name': 'another-public',
|
||||
'routes_externally': True,
|
||||
'default_interface': True,
|
||||
}, {
|
||||
'name': 'a-private',
|
||||
'routes_externally': False,
|
||||
}, {
|
||||
'name': 'another-private',
|
||||
'routes_externally': False,
|
||||
'nat_destination': True,
|
||||
}, {
|
||||
'name': 'split-default',
|
||||
'routes_externally': True,
|
||||
'routes_ipv4_externally': False,
|
||||
}, {
|
||||
'name': 'split-no-default',
|
||||
'routes_ipv6_externally': False,
|
||||
'routes_ipv4_externally': True,
|
||||
}],
|
||||
'region_name': 'test-region',
|
||||
},
|
||||
'_test_cloud_regions': {
|
||||
'auth': {
|
||||
'username': 'testuser',
|
||||
'password': 'testpass',
|
||||
'project-id': 'testproject',
|
||||
'auth_url': 'http://example.com/v2',
|
||||
},
|
||||
'regions': [
|
||||
{
|
||||
'name': 'region1',
|
||||
'values': {
|
||||
'external_network': 'region1-network',
|
||||
}
|
||||
},
|
||||
{
|
||||
'name': 'region2',
|
||||
'values': {
|
||||
'external_network': 'my-network',
|
||||
}
|
||||
}
|
||||
],
|
||||
},
|
||||
'_test_cloud_hyphenated': {
|
||||
'auth': {
|
||||
'username': 'testuser',
|
||||
'password': 'testpass',
|
||||
'project-id': '12345',
|
||||
'auth_url': 'http://example.com/v2',
|
||||
},
|
||||
'region_name': 'test-region',
|
||||
},
|
||||
'_test-cloud_no_region': {
|
||||
'profile': '_test_cloud_in_our_cloud',
|
||||
'auth': {
|
||||
'auth_url': 'http://example.com/v2',
|
||||
'username': 'testuser',
|
||||
'password': 'testpass',
|
||||
},
|
||||
},
|
||||
'_test-cloud-domain-scoped_': {
|
||||
'auth': {
|
||||
'auth_url': 'http://example.com/v2',
|
||||
'username': 'testuser',
|
||||
'password': 'testpass',
|
||||
'domain-id': '12345',
|
||||
},
|
||||
},
|
||||
},
|
||||
'ansible': {
|
||||
'expand-hostvars': False,
|
||||
'use_hostnames': True,
|
||||
},
|
||||
}
|
||||
SECURE_CONF = {
|
||||
'clouds': {
|
||||
'_test_cloud_no_vendor': {
|
||||
'auth': {
|
||||
'password': 'testpass',
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
NO_CONF = {
|
||||
'cache': {'max_age': 1},
|
||||
}
|
||||
|
||||
|
||||
def _write_yaml(obj):
|
||||
# Assume NestedTempfile so we don't have to cleanup
|
||||
with tempfile.NamedTemporaryFile(delete=False) as obj_yaml:
|
||||
obj_yaml.write(yaml.safe_dump(obj).encode('utf-8'))
|
||||
return obj_yaml.name
|
||||
|
||||
|
||||
class TestCase(base.BaseTestCase):
|
||||
"""Test case base class for all unit tests."""
|
||||
|
||||
def setUp(self):
|
||||
super(TestCase, self).setUp()
|
||||
|
||||
self.useFixture(fixtures.NestedTempfile())
|
||||
conf = copy.deepcopy(USER_CONF)
|
||||
tdir = self.useFixture(fixtures.TempDir())
|
||||
conf['cache']['path'] = tdir.path
|
||||
self.cloud_yaml = _write_yaml(conf)
|
||||
self.secure_yaml = _write_yaml(SECURE_CONF)
|
||||
self.vendor_yaml = _write_yaml(VENDOR_CONF)
|
||||
self.no_yaml = _write_yaml(NO_CONF)
|
||||
self.useFixture(fixtures.MonkeyPatch(
|
||||
'os_client_config.__version__', '1.2.3'))
|
||||
|
||||
# Isolate the test runs from the environment
|
||||
# Do this as two loops because you can't modify the dict in a loop
|
||||
# over the dict in 3.4
|
||||
keys_to_isolate = []
|
||||
for env in os.environ.keys():
|
||||
if env.startswith('OS_'):
|
||||
keys_to_isolate.append(env)
|
||||
for env in keys_to_isolate:
|
||||
self.useFixture(fixtures.EnvironmentVariable(env))
|
||||
|
||||
def _assert_cloud_details(self, cc):
|
||||
self.assertIsInstance(cc, cloud_config.CloudConfig)
|
||||
self.assertTrue(extras.safe_hasattr(cc, 'auth'))
|
||||
self.assertIsInstance(cc.auth, dict)
|
||||
self.assertIsNone(cc.cloud)
|
||||
self.assertIn('username', cc.auth)
|
||||
self.assertEqual('testuser', cc.auth['username'])
|
||||
self.assertEqual('testpass', cc.auth['password'])
|
||||
self.assertFalse(cc.config['image_api_use_tasks'])
|
||||
self.assertTrue('project_name' in cc.auth or 'project_id' in cc.auth)
|
||||
if 'project_name' in cc.auth:
|
||||
self.assertEqual('testproject', cc.auth['project_name'])
|
||||
elif 'project_id' in cc.auth:
|
||||
self.assertEqual('testproject', cc.auth['project_id'])
|
||||
self.assertEqual(cc.get_cache_expiration_time(), 1)
|
||||
self.assertEqual(cc.get_cache_resource_expiration('server'), 5.0)
|
||||
self.assertEqual(cc.get_cache_resource_expiration('image'), 7.0)
|
578
os-client-config/os_client_config/tests/test_cloud_config.py
Normal file
578
os-client-config/os_client_config/tests/test_cloud_config.py
Normal file
@ -0,0 +1,578 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import copy
|
||||
|
||||
from keystoneauth1 import exceptions as ksa_exceptions
|
||||
from keystoneauth1 import session as ksa_session
|
||||
import mock
|
||||
|
||||
from os_client_config import cloud_config
|
||||
from os_client_config import defaults
|
||||
from os_client_config import exceptions
|
||||
from os_client_config.tests import base
|
||||
|
||||
|
||||
fake_config_dict = {'a': 1, 'os_b': 2, 'c': 3, 'os_c': 4}
|
||||
fake_services_dict = {
|
||||
'compute_api_version': '2',
|
||||
'compute_endpoint_override': 'http://compute.example.com',
|
||||
'compute_region_name': 'region-bl',
|
||||
'telemetry_endpoint': 'http://telemetry.example.com',
|
||||
'interface': 'public',
|
||||
'image_service_type': 'mage',
|
||||
'identity_interface': 'admin',
|
||||
'identity_service_name': 'locks',
|
||||
'volume_api_version': '1',
|
||||
'auth': {'password': 'hunter2', 'username': 'AzureDiamond'},
|
||||
}
|
||||
|
||||
|
||||
class TestCloudConfig(base.TestCase):
|
||||
|
||||
def test_arbitrary_attributes(self):
|
||||
cc = cloud_config.CloudConfig("test1", "region-al", fake_config_dict)
|
||||
self.assertEqual("test1", cc.name)
|
||||
self.assertEqual("region-al", cc.region)
|
||||
|
||||
# Look up straight value
|
||||
self.assertEqual(1, cc.a)
|
||||
|
||||
# Look up prefixed attribute, fail - returns None
|
||||
self.assertIsNone(cc.os_b)
|
||||
|
||||
# Look up straight value, then prefixed value
|
||||
self.assertEqual(3, cc.c)
|
||||
self.assertEqual(3, cc.os_c)
|
||||
|
||||
# Lookup mystery attribute
|
||||
self.assertIsNone(cc.x)
|
||||
|
||||
# Test default ipv6
|
||||
self.assertFalse(cc.force_ipv4)
|
||||
|
||||
def test_iteration(self):
|
||||
cc = cloud_config.CloudConfig("test1", "region-al", fake_config_dict)
|
||||
self.assertTrue('a' in cc)
|
||||
self.assertFalse('x' in cc)
|
||||
|
||||
def test_equality(self):
|
||||
cc1 = cloud_config.CloudConfig("test1", "region-al", fake_config_dict)
|
||||
cc2 = cloud_config.CloudConfig("test1", "region-al", fake_config_dict)
|
||||
self.assertEqual(cc1, cc2)
|
||||
|
||||
def test_inequality(self):
|
||||
cc1 = cloud_config.CloudConfig("test1", "region-al", fake_config_dict)
|
||||
|
||||
cc2 = cloud_config.CloudConfig("test2", "region-al", fake_config_dict)
|
||||
self.assertNotEqual(cc1, cc2)
|
||||
|
||||
cc2 = cloud_config.CloudConfig("test1", "region-xx", fake_config_dict)
|
||||
self.assertNotEqual(cc1, cc2)
|
||||
|
||||
cc2 = cloud_config.CloudConfig("test1", "region-al", {})
|
||||
self.assertNotEqual(cc1, cc2)
|
||||
|
||||
def test_verify(self):
|
||||
config_dict = copy.deepcopy(fake_config_dict)
|
||||
config_dict['cacert'] = None
|
||||
|
||||
config_dict['verify'] = False
|
||||
cc = cloud_config.CloudConfig("test1", "region-xx", config_dict)
|
||||
(verify, cert) = cc.get_requests_verify_args()
|
||||
self.assertFalse(verify)
|
||||
|
||||
config_dict['verify'] = True
|
||||
cc = cloud_config.CloudConfig("test1", "region-xx", config_dict)
|
||||
(verify, cert) = cc.get_requests_verify_args()
|
||||
self.assertTrue(verify)
|
||||
|
||||
def test_verify_cacert(self):
|
||||
config_dict = copy.deepcopy(fake_config_dict)
|
||||
config_dict['cacert'] = "certfile"
|
||||
|
||||
config_dict['verify'] = False
|
||||
cc = cloud_config.CloudConfig("test1", "region-xx", config_dict)
|
||||
(verify, cert) = cc.get_requests_verify_args()
|
||||
self.assertFalse(verify)
|
||||
|
||||
config_dict['verify'] = True
|
||||
cc = cloud_config.CloudConfig("test1", "region-xx", config_dict)
|
||||
(verify, cert) = cc.get_requests_verify_args()
|
||||
self.assertEqual("certfile", verify)
|
||||
|
||||
def test_cert_with_key(self):
|
||||
config_dict = copy.deepcopy(fake_config_dict)
|
||||
config_dict['cacert'] = None
|
||||
config_dict['verify'] = False
|
||||
|
||||
config_dict['cert'] = 'cert'
|
||||
config_dict['key'] = 'key'
|
||||
|
||||
cc = cloud_config.CloudConfig("test1", "region-xx", config_dict)
|
||||
(verify, cert) = cc.get_requests_verify_args()
|
||||
self.assertEqual(("cert", "key"), cert)
|
||||
|
||||
def test_ipv6(self):
|
||||
cc = cloud_config.CloudConfig(
|
||||
"test1", "region-al", fake_config_dict, force_ipv4=True)
|
||||
self.assertTrue(cc.force_ipv4)
|
||||
|
||||
def test_getters(self):
|
||||
cc = cloud_config.CloudConfig("test1", "region-al", fake_services_dict)
|
||||
|
||||
self.assertEqual(['compute', 'identity', 'image', 'volume'],
|
||||
sorted(cc.get_services()))
|
||||
self.assertEqual({'password': 'hunter2', 'username': 'AzureDiamond'},
|
||||
cc.get_auth_args())
|
||||
self.assertEqual('public', cc.get_interface())
|
||||
self.assertEqual('public', cc.get_interface('compute'))
|
||||
self.assertEqual('admin', cc.get_interface('identity'))
|
||||
self.assertEqual('region-al', cc.get_region_name())
|
||||
self.assertEqual('region-al', cc.get_region_name('image'))
|
||||
self.assertEqual('region-bl', cc.get_region_name('compute'))
|
||||
self.assertIsNone(cc.get_api_version('image'))
|
||||
self.assertEqual('2', cc.get_api_version('compute'))
|
||||
self.assertEqual('mage', cc.get_service_type('image'))
|
||||
self.assertEqual('compute', cc.get_service_type('compute'))
|
||||
self.assertEqual('1', cc.get_api_version('volume'))
|
||||
self.assertEqual('volume', cc.get_service_type('volume'))
|
||||
self.assertEqual('http://compute.example.com',
|
||||
cc.get_endpoint('compute'))
|
||||
self.assertIsNone(cc.get_endpoint('image'))
|
||||
self.assertIsNone(cc.get_service_name('compute'))
|
||||
self.assertEqual('locks', cc.get_service_name('identity'))
|
||||
|
||||
def test_volume_override(self):
|
||||
cc = cloud_config.CloudConfig("test1", "region-al", fake_services_dict)
|
||||
cc.config['volume_api_version'] = '2'
|
||||
self.assertEqual('volumev2', cc.get_service_type('volume'))
|
||||
|
||||
def test_volume_override_v3(self):
|
||||
cc = cloud_config.CloudConfig("test1", "region-al", fake_services_dict)
|
||||
cc.config['volume_api_version'] = '3'
|
||||
self.assertEqual('volumev3', cc.get_service_type('volume'))
|
||||
|
||||
def test_workflow_override_v2(self):
|
||||
cc = cloud_config.CloudConfig("test1", "region-al", fake_services_dict)
|
||||
cc.config['workflow_api_version'] = '2'
|
||||
self.assertEqual('workflowv2', cc.get_service_type('workflow'))
|
||||
|
||||
def test_get_session_no_auth(self):
|
||||
config_dict = defaults.get_defaults()
|
||||
config_dict.update(fake_services_dict)
|
||||
cc = cloud_config.CloudConfig("test1", "region-al", config_dict)
|
||||
self.assertRaises(
|
||||
exceptions.OpenStackConfigException,
|
||||
cc.get_session)
|
||||
|
||||
@mock.patch.object(ksa_session, 'Session')
|
||||
def test_get_session(self, mock_session):
|
||||
config_dict = defaults.get_defaults()
|
||||
config_dict.update(fake_services_dict)
|
||||
fake_session = mock.Mock()
|
||||
fake_session.additional_user_agent = []
|
||||
mock_session.return_value = fake_session
|
||||
cc = cloud_config.CloudConfig(
|
||||
"test1", "region-al", config_dict, auth_plugin=mock.Mock())
|
||||
cc.get_session()
|
||||
mock_session.assert_called_with(
|
||||
auth=mock.ANY,
|
||||
verify=True, cert=None, timeout=None)
|
||||
self.assertEqual(
|
||||
fake_session.additional_user_agent,
|
||||
[('os-client-config', '1.2.3')])
|
||||
|
||||
@mock.patch.object(ksa_session, 'Session')
|
||||
def test_get_session_with_app_name(self, mock_session):
|
||||
config_dict = defaults.get_defaults()
|
||||
config_dict.update(fake_services_dict)
|
||||
fake_session = mock.Mock()
|
||||
fake_session.additional_user_agent = []
|
||||
fake_session.app_name = None
|
||||
fake_session.app_version = None
|
||||
mock_session.return_value = fake_session
|
||||
cc = cloud_config.CloudConfig(
|
||||
"test1", "region-al", config_dict, auth_plugin=mock.Mock(),
|
||||
app_name="test_app", app_version="test_version")
|
||||
cc.get_session()
|
||||
mock_session.assert_called_with(
|
||||
auth=mock.ANY,
|
||||
verify=True, cert=None, timeout=None)
|
||||
self.assertEqual(fake_session.app_name, "test_app")
|
||||
self.assertEqual(fake_session.app_version, "test_version")
|
||||
self.assertEqual(
|
||||
fake_session.additional_user_agent,
|
||||
[('os-client-config', '1.2.3')])
|
||||
|
||||
@mock.patch.object(ksa_session, 'Session')
|
||||
def test_get_session_with_timeout(self, mock_session):
|
||||
fake_session = mock.Mock()
|
||||
fake_session.additional_user_agent = []
|
||||
mock_session.return_value = fake_session
|
||||
config_dict = defaults.get_defaults()
|
||||
config_dict.update(fake_services_dict)
|
||||
config_dict['api_timeout'] = 9
|
||||
cc = cloud_config.CloudConfig(
|
||||
"test1", "region-al", config_dict, auth_plugin=mock.Mock())
|
||||
cc.get_session()
|
||||
mock_session.assert_called_with(
|
||||
auth=mock.ANY,
|
||||
verify=True, cert=None, timeout=9)
|
||||
self.assertEqual(
|
||||
fake_session.additional_user_agent,
|
||||
[('os-client-config', '1.2.3')])
|
||||
|
||||
@mock.patch.object(ksa_session, 'Session')
|
||||
def test_override_session_endpoint_override(self, mock_session):
|
||||
config_dict = defaults.get_defaults()
|
||||
config_dict.update(fake_services_dict)
|
||||
cc = cloud_config.CloudConfig(
|
||||
"test1", "region-al", config_dict, auth_plugin=mock.Mock())
|
||||
self.assertEqual(
|
||||
cc.get_session_endpoint('compute'),
|
||||
fake_services_dict['compute_endpoint_override'])
|
||||
|
||||
@mock.patch.object(ksa_session, 'Session')
|
||||
def test_override_session_endpoint(self, mock_session):
|
||||
config_dict = defaults.get_defaults()
|
||||
config_dict.update(fake_services_dict)
|
||||
cc = cloud_config.CloudConfig(
|
||||
"test1", "region-al", config_dict, auth_plugin=mock.Mock())
|
||||
self.assertEqual(
|
||||
cc.get_session_endpoint('telemetry'),
|
||||
fake_services_dict['telemetry_endpoint'])
|
||||
|
||||
@mock.patch.object(cloud_config.CloudConfig, 'get_session')
|
||||
def test_session_endpoint(self, mock_get_session):
|
||||
mock_session = mock.Mock()
|
||||
mock_get_session.return_value = mock_session
|
||||
config_dict = defaults.get_defaults()
|
||||
config_dict.update(fake_services_dict)
|
||||
cc = cloud_config.CloudConfig(
|
||||
"test1", "region-al", config_dict, auth_plugin=mock.Mock())
|
||||
cc.get_session_endpoint('orchestration')
|
||||
mock_session.get_endpoint.assert_called_with(
|
||||
interface='public',
|
||||
service_name=None,
|
||||
region_name='region-al',
|
||||
service_type='orchestration')
|
||||
|
||||
@mock.patch.object(cloud_config.CloudConfig, 'get_session')
|
||||
def test_session_endpoint_not_found(self, mock_get_session):
|
||||
exc_to_raise = ksa_exceptions.catalog.EndpointNotFound
|
||||
mock_get_session.return_value.get_endpoint.side_effect = exc_to_raise
|
||||
cc = cloud_config.CloudConfig(
|
||||
"test1", "region-al", {}, auth_plugin=mock.Mock())
|
||||
self.assertIsNone(cc.get_session_endpoint('notfound'))
|
||||
|
||||
@mock.patch.object(cloud_config.CloudConfig, 'get_api_version')
|
||||
@mock.patch.object(cloud_config.CloudConfig, 'get_auth_args')
|
||||
@mock.patch.object(cloud_config.CloudConfig, 'get_session_endpoint')
|
||||
def test_legacy_client_object_store_password(
|
||||
self,
|
||||
mock_get_session_endpoint,
|
||||
mock_get_auth_args,
|
||||
mock_get_api_version):
|
||||
mock_client = mock.Mock()
|
||||
mock_get_session_endpoint.return_value = 'http://swift.example.com'
|
||||
mock_get_api_version.return_value = '3'
|
||||
mock_get_auth_args.return_value = dict(
|
||||
username='testuser',
|
||||
password='testpassword',
|
||||
project_name='testproject',
|
||||
auth_url='http://example.com',
|
||||
)
|
||||
config_dict = defaults.get_defaults()
|
||||
config_dict.update(fake_services_dict)
|
||||
cc = cloud_config.CloudConfig(
|
||||
"test1", "region-al", config_dict, auth_plugin=mock.Mock())
|
||||
cc.get_legacy_client('object-store', mock_client)
|
||||
mock_client.assert_called_with(
|
||||
session=mock.ANY,
|
||||
os_options={
|
||||
'region_name': 'region-al',
|
||||
'service_type': 'object-store',
|
||||
'object_storage_url': None,
|
||||
'endpoint_type': 'public',
|
||||
})
|
||||
|
||||
@mock.patch.object(cloud_config.CloudConfig, 'get_auth_args')
|
||||
@mock.patch.object(cloud_config.CloudConfig, 'get_session_endpoint')
|
||||
def test_legacy_client_object_store_password_v2(
|
||||
self, mock_get_session_endpoint, mock_get_auth_args):
|
||||
mock_client = mock.Mock()
|
||||
mock_get_session_endpoint.return_value = 'http://swift.example.com'
|
||||
mock_get_auth_args.return_value = dict(
|
||||
username='testuser',
|
||||
password='testpassword',
|
||||
project_name='testproject',
|
||||
auth_url='http://example.com',
|
||||
)
|
||||
config_dict = defaults.get_defaults()
|
||||
config_dict.update(fake_services_dict)
|
||||
cc = cloud_config.CloudConfig(
|
||||
"test1", "region-al", config_dict, auth_plugin=mock.Mock())
|
||||
cc.get_legacy_client('object-store', mock_client)
|
||||
mock_client.assert_called_with(
|
||||
session=mock.ANY,
|
||||
os_options={
|
||||
'region_name': 'region-al',
|
||||
'service_type': 'object-store',
|
||||
'object_storage_url': None,
|
||||
'endpoint_type': 'public',
|
||||
})
|
||||
|
||||
@mock.patch.object(cloud_config.CloudConfig, 'get_auth_args')
|
||||
@mock.patch.object(cloud_config.CloudConfig, 'get_session_endpoint')
|
||||
def test_legacy_client_object_store(
|
||||
self, mock_get_session_endpoint, mock_get_auth_args):
|
||||
mock_client = mock.Mock()
|
||||
mock_get_session_endpoint.return_value = 'http://example.com/v2'
|
||||
mock_get_auth_args.return_value = {}
|
||||
config_dict = defaults.get_defaults()
|
||||
config_dict.update(fake_services_dict)
|
||||
cc = cloud_config.CloudConfig(
|
||||
"test1", "region-al", config_dict, auth_plugin=mock.Mock())
|
||||
cc.get_legacy_client('object-store', mock_client)
|
||||
mock_client.assert_called_with(
|
||||
session=mock.ANY,
|
||||
os_options={
|
||||
'region_name': 'region-al',
|
||||
'service_type': 'object-store',
|
||||
'object_storage_url': None,
|
||||
'endpoint_type': 'public',
|
||||
})
|
||||
|
||||
@mock.patch.object(cloud_config.CloudConfig, 'get_auth_args')
|
||||
@mock.patch.object(cloud_config.CloudConfig, 'get_session_endpoint')
|
||||
def test_legacy_client_object_store_timeout(
|
||||
self, mock_get_session_endpoint, mock_get_auth_args):
|
||||
mock_client = mock.Mock()
|
||||
mock_get_session_endpoint.return_value = 'http://example.com/v2'
|
||||
mock_get_auth_args.return_value = {}
|
||||
config_dict = defaults.get_defaults()
|
||||
config_dict.update(fake_services_dict)
|
||||
config_dict['api_timeout'] = 9
|
||||
cc = cloud_config.CloudConfig(
|
||||
"test1", "region-al", config_dict, auth_plugin=mock.Mock())
|
||||
cc.get_legacy_client('object-store', mock_client)
|
||||
mock_client.assert_called_with(
|
||||
session=mock.ANY,
|
||||
os_options={
|
||||
'region_name': 'region-al',
|
||||
'service_type': 'object-store',
|
||||
'object_storage_url': None,
|
||||
'endpoint_type': 'public',
|
||||
})
|
||||
|
||||
@mock.patch.object(cloud_config.CloudConfig, 'get_auth_args')
|
||||
def test_legacy_client_object_store_endpoint(
|
||||
self, mock_get_auth_args):
|
||||
mock_client = mock.Mock()
|
||||
mock_get_auth_args.return_value = {}
|
||||
config_dict = defaults.get_defaults()
|
||||
config_dict.update(fake_services_dict)
|
||||
config_dict['object_store_endpoint'] = 'http://example.com/swift'
|
||||
cc = cloud_config.CloudConfig(
|
||||
"test1", "region-al", config_dict, auth_plugin=mock.Mock())
|
||||
cc.get_legacy_client('object-store', mock_client)
|
||||
mock_client.assert_called_with(
|
||||
session=mock.ANY,
|
||||
os_options={
|
||||
'region_name': 'region-al',
|
||||
'service_type': 'object-store',
|
||||
'object_storage_url': 'http://example.com/swift',
|
||||
'endpoint_type': 'public',
|
||||
})
|
||||
|
||||
@mock.patch.object(cloud_config.CloudConfig, 'get_session_endpoint')
|
||||
def test_legacy_client_image(self, mock_get_session_endpoint):
|
||||
mock_client = mock.Mock()
|
||||
mock_get_session_endpoint.return_value = 'http://example.com/v2'
|
||||
config_dict = defaults.get_defaults()
|
||||
config_dict.update(fake_services_dict)
|
||||
cc = cloud_config.CloudConfig(
|
||||
"test1", "region-al", config_dict, auth_plugin=mock.Mock())
|
||||
cc.get_legacy_client('image', mock_client)
|
||||
mock_client.assert_called_with(
|
||||
version=2.0,
|
||||
service_name=None,
|
||||
endpoint_override='http://example.com',
|
||||
region_name='region-al',
|
||||
interface='public',
|
||||
session=mock.ANY,
|
||||
# Not a typo - the config dict above overrides this
|
||||
service_type='mage'
|
||||
)
|
||||
|
||||
@mock.patch.object(cloud_config.CloudConfig, 'get_session_endpoint')
|
||||
def test_legacy_client_image_override(self, mock_get_session_endpoint):
|
||||
mock_client = mock.Mock()
|
||||
mock_get_session_endpoint.return_value = 'http://example.com/v2'
|
||||
config_dict = defaults.get_defaults()
|
||||
config_dict.update(fake_services_dict)
|
||||
config_dict['image_endpoint_override'] = 'http://example.com/override'
|
||||
cc = cloud_config.CloudConfig(
|
||||
"test1", "region-al", config_dict, auth_plugin=mock.Mock())
|
||||
cc.get_legacy_client('image', mock_client)
|
||||
mock_client.assert_called_with(
|
||||
version=2.0,
|
||||
service_name=None,
|
||||
endpoint_override='http://example.com/override',
|
||||
region_name='region-al',
|
||||
interface='public',
|
||||
session=mock.ANY,
|
||||
# Not a typo - the config dict above overrides this
|
||||
service_type='mage'
|
||||
)
|
||||
|
||||
@mock.patch.object(cloud_config.CloudConfig, 'get_session_endpoint')
|
||||
def test_legacy_client_image_versioned(self, mock_get_session_endpoint):
|
||||
mock_client = mock.Mock()
|
||||
mock_get_session_endpoint.return_value = 'http://example.com/v2'
|
||||
config_dict = defaults.get_defaults()
|
||||
config_dict.update(fake_services_dict)
|
||||
# v2 endpoint was passed, 1 requested in config, endpoint wins
|
||||
config_dict['image_api_version'] = '1'
|
||||
cc = cloud_config.CloudConfig(
|
||||
"test1", "region-al", config_dict, auth_plugin=mock.Mock())
|
||||
cc.get_legacy_client('image', mock_client)
|
||||
mock_client.assert_called_with(
|
||||
version=2.0,
|
||||
service_name=None,
|
||||
endpoint_override='http://example.com',
|
||||
region_name='region-al',
|
||||
interface='public',
|
||||
session=mock.ANY,
|
||||
# Not a typo - the config dict above overrides this
|
||||
service_type='mage'
|
||||
)
|
||||
|
||||
@mock.patch.object(cloud_config.CloudConfig, 'get_session_endpoint')
|
||||
def test_legacy_client_image_unversioned(self, mock_get_session_endpoint):
|
||||
mock_client = mock.Mock()
|
||||
mock_get_session_endpoint.return_value = 'http://example.com/'
|
||||
config_dict = defaults.get_defaults()
|
||||
config_dict.update(fake_services_dict)
|
||||
# Versionless endpoint, config wins
|
||||
config_dict['image_api_version'] = '1'
|
||||
cc = cloud_config.CloudConfig(
|
||||
"test1", "region-al", config_dict, auth_plugin=mock.Mock())
|
||||
cc.get_legacy_client('image', mock_client)
|
||||
mock_client.assert_called_with(
|
||||
version='1',
|
||||
service_name=None,
|
||||
endpoint_override='http://example.com',
|
||||
region_name='region-al',
|
||||
interface='public',
|
||||
session=mock.ANY,
|
||||
# Not a typo - the config dict above overrides this
|
||||
service_type='mage'
|
||||
)
|
||||
|
||||
@mock.patch.object(cloud_config.CloudConfig, 'get_session_endpoint')
|
||||
def test_legacy_client_image_argument(self, mock_get_session_endpoint):
|
||||
mock_client = mock.Mock()
|
||||
mock_get_session_endpoint.return_value = 'http://example.com/v3'
|
||||
config_dict = defaults.get_defaults()
|
||||
config_dict.update(fake_services_dict)
|
||||
# Versionless endpoint, config wins
|
||||
config_dict['image_api_version'] = '6'
|
||||
cc = cloud_config.CloudConfig(
|
||||
"test1", "region-al", config_dict, auth_plugin=mock.Mock())
|
||||
cc.get_legacy_client('image', mock_client, version='beef')
|
||||
mock_client.assert_called_with(
|
||||
version='beef',
|
||||
service_name=None,
|
||||
endpoint_override='http://example.com',
|
||||
region_name='region-al',
|
||||
interface='public',
|
||||
session=mock.ANY,
|
||||
# Not a typo - the config dict above overrides this
|
||||
service_type='mage'
|
||||
)
|
||||
|
||||
@mock.patch.object(cloud_config.CloudConfig, 'get_session_endpoint')
|
||||
def test_legacy_client_network(self, mock_get_session_endpoint):
|
||||
mock_client = mock.Mock()
|
||||
mock_get_session_endpoint.return_value = 'http://example.com/v2'
|
||||
config_dict = defaults.get_defaults()
|
||||
config_dict.update(fake_services_dict)
|
||||
cc = cloud_config.CloudConfig(
|
||||
"test1", "region-al", config_dict, auth_plugin=mock.Mock())
|
||||
cc.get_legacy_client('network', mock_client)
|
||||
mock_client.assert_called_with(
|
||||
api_version='2.0',
|
||||
endpoint_type='public',
|
||||
endpoint_override=None,
|
||||
region_name='region-al',
|
||||
service_type='network',
|
||||
session=mock.ANY,
|
||||
service_name=None)
|
||||
|
||||
@mock.patch.object(cloud_config.CloudConfig, 'get_session_endpoint')
|
||||
def test_legacy_client_compute(self, mock_get_session_endpoint):
|
||||
mock_client = mock.Mock()
|
||||
mock_get_session_endpoint.return_value = 'http://example.com/v2'
|
||||
config_dict = defaults.get_defaults()
|
||||
config_dict.update(fake_services_dict)
|
||||
cc = cloud_config.CloudConfig(
|
||||
"test1", "region-al", config_dict, auth_plugin=mock.Mock())
|
||||
cc.get_legacy_client('compute', mock_client)
|
||||
mock_client.assert_called_with(
|
||||
version='2',
|
||||
endpoint_type='public',
|
||||
endpoint_override='http://compute.example.com',
|
||||
region_name='region-al',
|
||||
service_type='compute',
|
||||
session=mock.ANY,
|
||||
service_name=None)
|
||||
|
||||
@mock.patch.object(cloud_config.CloudConfig, 'get_session_endpoint')
|
||||
def test_legacy_client_identity(self, mock_get_session_endpoint):
|
||||
mock_client = mock.Mock()
|
||||
mock_get_session_endpoint.return_value = 'http://example.com/v2'
|
||||
config_dict = defaults.get_defaults()
|
||||
config_dict.update(fake_services_dict)
|
||||
cc = cloud_config.CloudConfig(
|
||||
"test1", "region-al", config_dict, auth_plugin=mock.Mock())
|
||||
cc.get_legacy_client('identity', mock_client)
|
||||
mock_client.assert_called_with(
|
||||
version='2.0',
|
||||
endpoint='http://example.com/v2',
|
||||
endpoint_type='admin',
|
||||
endpoint_override=None,
|
||||
region_name='region-al',
|
||||
service_type='identity',
|
||||
session=mock.ANY,
|
||||
service_name='locks')
|
||||
|
||||
@mock.patch.object(cloud_config.CloudConfig, 'get_session_endpoint')
|
||||
def test_legacy_client_identity_v3(self, mock_get_session_endpoint):
|
||||
mock_client = mock.Mock()
|
||||
mock_get_session_endpoint.return_value = 'http://example.com'
|
||||
config_dict = defaults.get_defaults()
|
||||
config_dict.update(fake_services_dict)
|
||||
config_dict['identity_api_version'] = '3'
|
||||
cc = cloud_config.CloudConfig(
|
||||
"test1", "region-al", config_dict, auth_plugin=mock.Mock())
|
||||
cc.get_legacy_client('identity', mock_client)
|
||||
mock_client.assert_called_with(
|
||||
version='3',
|
||||
endpoint='http://example.com',
|
||||
interface='admin',
|
||||
endpoint_override=None,
|
||||
region_name='region-al',
|
||||
service_type='identity',
|
||||
session=mock.ANY,
|
||||
service_name='locks')
|
1020
os-client-config/os_client_config/tests/test_config.py
Normal file
1020
os-client-config/os_client_config/tests/test_config.py
Normal file
File diff suppressed because it is too large
Load Diff
185
os-client-config/os_client_config/tests/test_environ.py
Normal file
185
os-client-config/os_client_config/tests/test_environ.py
Normal file
@ -0,0 +1,185 @@
|
||||
# Copyright (c) 2014 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 os_client_config import cloud_config
|
||||
from os_client_config import config
|
||||
from os_client_config import exceptions
|
||||
from os_client_config.tests import base
|
||||
|
||||
import fixtures
|
||||
|
||||
|
||||
class TestEnviron(base.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestEnviron, self).setUp()
|
||||
self.useFixture(
|
||||
fixtures.EnvironmentVariable('OS_AUTH_URL', 'https://example.com'))
|
||||
self.useFixture(
|
||||
fixtures.EnvironmentVariable('OS_USERNAME', 'testuser'))
|
||||
self.useFixture(
|
||||
fixtures.EnvironmentVariable('OS_PASSWORD', 'testpass'))
|
||||
self.useFixture(
|
||||
fixtures.EnvironmentVariable('OS_PROJECT_NAME', 'testproject'))
|
||||
self.useFixture(
|
||||
fixtures.EnvironmentVariable('NOVA_PROJECT_ID', 'testnova'))
|
||||
|
||||
def test_get_one_cloud(self):
|
||||
c = config.OpenStackConfig(config_files=[self.cloud_yaml],
|
||||
vendor_files=[self.vendor_yaml])
|
||||
self.assertIsInstance(c.get_one_cloud(), cloud_config.CloudConfig)
|
||||
|
||||
def test_no_fallthrough(self):
|
||||
c = config.OpenStackConfig(config_files=[self.cloud_yaml],
|
||||
vendor_files=[self.vendor_yaml])
|
||||
self.assertRaises(
|
||||
exceptions.OpenStackConfigException, c.get_one_cloud, 'openstack')
|
||||
|
||||
def test_envvar_name_override(self):
|
||||
self.useFixture(
|
||||
fixtures.EnvironmentVariable('OS_CLOUD_NAME', 'override'))
|
||||
c = config.OpenStackConfig(config_files=[self.cloud_yaml],
|
||||
vendor_files=[self.vendor_yaml])
|
||||
cc = c.get_one_cloud('override')
|
||||
self._assert_cloud_details(cc)
|
||||
|
||||
def test_envvar_prefer_ipv6_override(self):
|
||||
self.useFixture(
|
||||
fixtures.EnvironmentVariable('OS_PREFER_IPV6', 'false'))
|
||||
c = config.OpenStackConfig(config_files=[self.cloud_yaml],
|
||||
vendor_files=[self.vendor_yaml],
|
||||
secure_files=[self.secure_yaml])
|
||||
cc = c.get_one_cloud('_test-cloud_')
|
||||
self.assertFalse(cc.prefer_ipv6)
|
||||
|
||||
def test_environ_exists(self):
|
||||
c = config.OpenStackConfig(config_files=[self.cloud_yaml],
|
||||
vendor_files=[self.vendor_yaml],
|
||||
secure_files=[self.secure_yaml])
|
||||
cc = c.get_one_cloud('envvars')
|
||||
self._assert_cloud_details(cc)
|
||||
self.assertNotIn('auth_url', cc.config)
|
||||
self.assertIn('auth_url', cc.config['auth'])
|
||||
self.assertNotIn('project_id', cc.config['auth'])
|
||||
self.assertNotIn('auth_url', cc.config)
|
||||
cc = c.get_one_cloud('_test-cloud_')
|
||||
self._assert_cloud_details(cc)
|
||||
cc = c.get_one_cloud('_test_cloud_no_vendor')
|
||||
self._assert_cloud_details(cc)
|
||||
|
||||
def test_environ_prefix(self):
|
||||
c = config.OpenStackConfig(config_files=[self.cloud_yaml],
|
||||
vendor_files=[self.vendor_yaml],
|
||||
envvar_prefix='NOVA_',
|
||||
secure_files=[self.secure_yaml])
|
||||
cc = c.get_one_cloud('envvars')
|
||||
self._assert_cloud_details(cc)
|
||||
self.assertNotIn('auth_url', cc.config)
|
||||
self.assertIn('auth_url', cc.config['auth'])
|
||||
self.assertIn('project_id', cc.config['auth'])
|
||||
self.assertNotIn('auth_url', cc.config)
|
||||
cc = c.get_one_cloud('_test-cloud_')
|
||||
self._assert_cloud_details(cc)
|
||||
cc = c.get_one_cloud('_test_cloud_no_vendor')
|
||||
self._assert_cloud_details(cc)
|
||||
|
||||
def test_get_one_cloud_with_config_files(self):
|
||||
c = config.OpenStackConfig(config_files=[self.cloud_yaml],
|
||||
vendor_files=[self.vendor_yaml],
|
||||
secure_files=[self.secure_yaml])
|
||||
self.assertIsInstance(c.cloud_config, dict)
|
||||
self.assertIn('cache', c.cloud_config)
|
||||
self.assertIsInstance(c.cloud_config['cache'], dict)
|
||||
self.assertIn('max_age', c.cloud_config['cache'])
|
||||
self.assertIn('path', c.cloud_config['cache'])
|
||||
cc = c.get_one_cloud('_test-cloud_')
|
||||
self._assert_cloud_details(cc)
|
||||
cc = c.get_one_cloud('_test_cloud_no_vendor')
|
||||
self._assert_cloud_details(cc)
|
||||
|
||||
def test_config_file_override(self):
|
||||
self.useFixture(
|
||||
fixtures.EnvironmentVariable(
|
||||
'OS_CLIENT_CONFIG_FILE', self.cloud_yaml))
|
||||
c = config.OpenStackConfig(config_files=[],
|
||||
vendor_files=[self.vendor_yaml])
|
||||
cc = c.get_one_cloud('_test-cloud_')
|
||||
self._assert_cloud_details(cc)
|
||||
|
||||
|
||||
class TestEnvvars(base.TestCase):
|
||||
|
||||
def test_no_envvars(self):
|
||||
self.useFixture(
|
||||
fixtures.EnvironmentVariable('NOVA_USERNAME', 'nova'))
|
||||
c = config.OpenStackConfig(config_files=[self.cloud_yaml],
|
||||
vendor_files=[self.vendor_yaml])
|
||||
self.assertRaises(
|
||||
exceptions.OpenStackConfigException, c.get_one_cloud, 'envvars')
|
||||
|
||||
def test_test_envvars(self):
|
||||
self.useFixture(
|
||||
fixtures.EnvironmentVariable('NOVA_USERNAME', 'nova'))
|
||||
self.useFixture(
|
||||
fixtures.EnvironmentVariable('OS_STDERR_CAPTURE', 'True'))
|
||||
c = config.OpenStackConfig(config_files=[self.cloud_yaml],
|
||||
vendor_files=[self.vendor_yaml])
|
||||
self.assertRaises(
|
||||
exceptions.OpenStackConfigException, c.get_one_cloud, 'envvars')
|
||||
|
||||
def test_incomplete_envvars(self):
|
||||
self.useFixture(
|
||||
fixtures.EnvironmentVariable('NOVA_USERNAME', 'nova'))
|
||||
self.useFixture(
|
||||
fixtures.EnvironmentVariable('OS_USERNAME', 'user'))
|
||||
config.OpenStackConfig(config_files=[self.cloud_yaml],
|
||||
vendor_files=[self.vendor_yaml])
|
||||
# This is broken due to an issue that's fixed in a subsequent patch
|
||||
# commenting it out in this patch to keep the patch size reasonable
|
||||
# self.assertRaises(
|
||||
# keystoneauth1.exceptions.auth_plugins.MissingRequiredOptions,
|
||||
# c.get_one_cloud, 'envvars')
|
||||
|
||||
def test_have_envvars(self):
|
||||
self.useFixture(
|
||||
fixtures.EnvironmentVariable('NOVA_USERNAME', 'nova'))
|
||||
self.useFixture(
|
||||
fixtures.EnvironmentVariable('OS_AUTH_URL', 'http://example.com'))
|
||||
self.useFixture(
|
||||
fixtures.EnvironmentVariable('OS_USERNAME', 'user'))
|
||||
self.useFixture(
|
||||
fixtures.EnvironmentVariable('OS_PASSWORD', 'password'))
|
||||
self.useFixture(
|
||||
fixtures.EnvironmentVariable('OS_PROJECT_NAME', 'project'))
|
||||
c = config.OpenStackConfig(config_files=[self.cloud_yaml],
|
||||
vendor_files=[self.vendor_yaml])
|
||||
cc = c.get_one_cloud('envvars')
|
||||
self.assertEqual(cc.config['auth']['username'], 'user')
|
||||
|
||||
def test_old_envvars(self):
|
||||
self.useFixture(
|
||||
fixtures.EnvironmentVariable('NOVA_USERNAME', 'nova'))
|
||||
self.useFixture(
|
||||
fixtures.EnvironmentVariable(
|
||||
'NOVA_AUTH_URL', 'http://example.com'))
|
||||
self.useFixture(
|
||||
fixtures.EnvironmentVariable('NOVA_PASSWORD', 'password'))
|
||||
self.useFixture(
|
||||
fixtures.EnvironmentVariable('NOVA_PROJECT_NAME', 'project'))
|
||||
c = config.OpenStackConfig(config_files=[self.cloud_yaml],
|
||||
vendor_files=[self.vendor_yaml],
|
||||
envvar_prefix='NOVA_')
|
||||
cc = c.get_one_cloud('envvars')
|
||||
self.assertEqual(cc.config['auth']['username'], 'nova')
|
35
os-client-config/os_client_config/tests/test_init.py
Normal file
35
os-client-config/os_client_config/tests/test_init.py
Normal file
@ -0,0 +1,35 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import argparse
|
||||
|
||||
import os_client_config
|
||||
from os_client_config.tests import base
|
||||
|
||||
|
||||
class TestInit(base.TestCase):
|
||||
def test_get_config_without_arg_parser(self):
|
||||
cloud_config = os_client_config.get_config(
|
||||
options=None, validate=False)
|
||||
self.assertIsInstance(
|
||||
cloud_config,
|
||||
os_client_config.cloud_config.CloudConfig
|
||||
)
|
||||
|
||||
def test_get_config_with_arg_parser(self):
|
||||
cloud_config = os_client_config.get_config(
|
||||
options=argparse.ArgumentParser(),
|
||||
validate=False)
|
||||
self.assertIsInstance(
|
||||
cloud_config,
|
||||
os_client_config.cloud_config.CloudConfig
|
||||
)
|
62
os-client-config/os_client_config/tests/test_json.py
Normal file
62
os-client-config/os_client_config/tests/test_json.py
Normal file
@ -0,0 +1,62 @@
|
||||
# Copyright (c) 2015 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 glob
|
||||
import json
|
||||
import os
|
||||
|
||||
import jsonschema
|
||||
from testtools import content
|
||||
|
||||
from os_client_config import defaults
|
||||
from os_client_config.tests import base
|
||||
|
||||
|
||||
class TestConfig(base.TestCase):
|
||||
|
||||
def json_diagnostics(self, exc_info):
|
||||
self.addDetail('filename', content.text_content(self.filename))
|
||||
for error in sorted(self.validator.iter_errors(self.json_data)):
|
||||
self.addDetail('jsonschema', content.text_content(str(error)))
|
||||
|
||||
def test_defaults_valid_json(self):
|
||||
_schema_path = os.path.join(
|
||||
os.path.dirname(os.path.realpath(defaults.__file__)),
|
||||
'schema.json')
|
||||
schema = json.load(open(_schema_path, 'r'))
|
||||
self.validator = jsonschema.Draft4Validator(schema)
|
||||
self.addOnException(self.json_diagnostics)
|
||||
|
||||
self.filename = os.path.join(
|
||||
os.path.dirname(os.path.realpath(defaults.__file__)),
|
||||
'defaults.json')
|
||||
self.json_data = json.load(open(self.filename, 'r'))
|
||||
|
||||
self.assertTrue(self.validator.is_valid(self.json_data))
|
||||
|
||||
def test_vendors_valid_json(self):
|
||||
_schema_path = os.path.join(
|
||||
os.path.dirname(os.path.realpath(defaults.__file__)),
|
||||
'vendor-schema.json')
|
||||
schema = json.load(open(_schema_path, 'r'))
|
||||
self.validator = jsonschema.Draft4Validator(schema)
|
||||
self.addOnException(self.json_diagnostics)
|
||||
|
||||
_vendors_path = os.path.join(
|
||||
os.path.dirname(os.path.realpath(defaults.__file__)),
|
||||
'vendors')
|
||||
for self.filename in glob.glob(os.path.join(_vendors_path, '*.json')):
|
||||
self.json_data = json.load(open(self.filename, 'r'))
|
||||
|
||||
self.assertTrue(self.validator.is_valid(self.json_data))
|
223
os-client-config/os_client_config/vendor-schema.json
Normal file
223
os-client-config/os_client_config/vendor-schema.json
Normal file
@ -0,0 +1,223 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"id": "https://git.openstack.org/cgit/openstack/cloud-data/plain/vendor-schema.json#",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"profile": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"auth": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"auth_url": {
|
||||
"name": "Auth URL",
|
||||
"description": "URL of the primary Keystone endpoint",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"auth_type": {
|
||||
"name": "Auth Type",
|
||||
"description": "Name of authentication plugin to be used",
|
||||
"default": "password",
|
||||
"type": "string"
|
||||
},
|
||||
"disable_vendor_agent": {
|
||||
"name": "Disable Vendor Agent Properties",
|
||||
"description": "Image properties required to disable vendor agent",
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
},
|
||||
"floating_ip_source": {
|
||||
"name": "Floating IP Source",
|
||||
"description": "Which service provides Floating IPs",
|
||||
"enum": [ "neutron", "nova", "None" ],
|
||||
"default": "neutron"
|
||||
},
|
||||
"image_api_use_tasks": {
|
||||
"name": "Image Task API",
|
||||
"description": "Does the cloud require the Image Task API",
|
||||
"default": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
"image_format": {
|
||||
"name": "Image Format",
|
||||
"description": "Format for uploaded Images",
|
||||
"default": "qcow2",
|
||||
"type": "string"
|
||||
},
|
||||
"interface": {
|
||||
"name": "API Interface",
|
||||
"description": "Which API Interface should connections hit",
|
||||
"default": "public",
|
||||
"enum": [ "public", "internal", "admin" ]
|
||||
},
|
||||
"message": {
|
||||
"name": "Status message",
|
||||
"description": "Optional message with information related to status",
|
||||
"type": "string"
|
||||
},
|
||||
"requires_floating_ip": {
|
||||
"name": "Requires Floating IP",
|
||||
"description": "Whether the cloud requires a floating IP to route traffic off of the cloud",
|
||||
"default": null,
|
||||
"type": ["boolean", "null"]
|
||||
},
|
||||
"secgroup_source": {
|
||||
"name": "Security Group Source",
|
||||
"description": "Which service provides security groups",
|
||||
"enum": [ "neutron", "nova", "None" ],
|
||||
"default": "neutron"
|
||||
},
|
||||
"status": {
|
||||
"name": "Vendor status",
|
||||
"description": "Status of the vendor's cloud",
|
||||
"enum": [ "active", "deprecated", "shutdown"],
|
||||
"default": "active"
|
||||
},
|
||||
"compute_service_name": {
|
||||
"name": "Compute API Service Name",
|
||||
"description": "Compute API Service Name",
|
||||
"type": "string"
|
||||
},
|
||||
"database_service_name": {
|
||||
"name": "Database API Service Name",
|
||||
"description": "Database API Service Name",
|
||||
"type": "string"
|
||||
},
|
||||
"dns_service_name": {
|
||||
"name": "DNS API Service Name",
|
||||
"description": "DNS API Service Name",
|
||||
"type": "string"
|
||||
},
|
||||
"identity_service_name": {
|
||||
"name": "Identity API Service Name",
|
||||
"description": "Identity API Service Name",
|
||||
"type": "string"
|
||||
},
|
||||
"image_service_name": {
|
||||
"name": "Image API Service Name",
|
||||
"description": "Image API Service Name",
|
||||
"type": "string"
|
||||
},
|
||||
"volume_service_name": {
|
||||
"name": "Volume API Service Name",
|
||||
"description": "Volume API Service Name",
|
||||
"type": "string"
|
||||
},
|
||||
"network_service_name": {
|
||||
"name": "Network API Service Name",
|
||||
"description": "Network API Service Name",
|
||||
"type": "string"
|
||||
},
|
||||
"object_service_name": {
|
||||
"name": "Object Storage API Service Name",
|
||||
"description": "Object Storage API Service Name",
|
||||
"type": "string"
|
||||
},
|
||||
"baremetal_service_name": {
|
||||
"name": "Baremetal API Service Name",
|
||||
"description": "Baremetal API Service Name",
|
||||
"type": "string"
|
||||
},
|
||||
"compute_service_type": {
|
||||
"name": "Compute API Service Type",
|
||||
"description": "Compute API Service Type",
|
||||
"type": "string"
|
||||
},
|
||||
"database_service_type": {
|
||||
"name": "Database API Service Type",
|
||||
"description": "Database API Service Type",
|
||||
"type": "string"
|
||||
},
|
||||
"dns_service_type": {
|
||||
"name": "DNS API Service Type",
|
||||
"description": "DNS API Service Type",
|
||||
"type": "string"
|
||||
},
|
||||
"identity_service_type": {
|
||||
"name": "Identity API Service Type",
|
||||
"description": "Identity API Service Type",
|
||||
"type": "string"
|
||||
},
|
||||
"image_service_type": {
|
||||
"name": "Image API Service Type",
|
||||
"description": "Image API Service Type",
|
||||
"type": "string"
|
||||
},
|
||||
"volume_service_type": {
|
||||
"name": "Volume API Service Type",
|
||||
"description": "Volume API Service Type",
|
||||
"type": "string"
|
||||
},
|
||||
"network_service_type": {
|
||||
"name": "Network API Service Type",
|
||||
"description": "Network API Service Type",
|
||||
"type": "string"
|
||||
},
|
||||
"object_service_type": {
|
||||
"name": "Object Storage API Service Type",
|
||||
"description": "Object Storage API Service Type",
|
||||
"type": "string"
|
||||
},
|
||||
"baremetal_service_type": {
|
||||
"name": "Baremetal API Service Type",
|
||||
"description": "Baremetal API Service Type",
|
||||
"type": "string"
|
||||
},
|
||||
"compute_api_version": {
|
||||
"name": "Compute API Version",
|
||||
"description": "Compute API Version",
|
||||
"type": "string"
|
||||
},
|
||||
"database_api_version": {
|
||||
"name": "Database API Version",
|
||||
"description": "Database API Version",
|
||||
"type": "string"
|
||||
},
|
||||
"dns_api_version": {
|
||||
"name": "DNS API Version",
|
||||
"description": "DNS API Version",
|
||||
"type": "string"
|
||||
},
|
||||
"identity_api_version": {
|
||||
"name": "Identity API Version",
|
||||
"description": "Identity API Version",
|
||||
"type": "string"
|
||||
},
|
||||
"image_api_version": {
|
||||
"name": "Image API Version",
|
||||
"description": "Image API Version",
|
||||
"type": "string"
|
||||
},
|
||||
"volume_api_version": {
|
||||
"name": "Volume API Version",
|
||||
"description": "Volume API Version",
|
||||
"type": "string"
|
||||
},
|
||||
"network_api_version": {
|
||||
"name": "Network API Version",
|
||||
"description": "Network API Version",
|
||||
"type": "string"
|
||||
},
|
||||
"object_api_version": {
|
||||
"name": "Object Storage API Version",
|
||||
"description": "Object Storage API Version",
|
||||
"type": "string"
|
||||
},
|
||||
"baremetal_api_version": {
|
||||
"name": "Baremetal API Version",
|
||||
"description": "Baremetal API Version",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
"profile"
|
||||
]
|
||||
}
|
37
os-client-config/os_client_config/vendors/__init__.py
vendored
Normal file
37
os-client-config/os_client_config/vendors/__init__.py
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
# Copyright (c) 2014 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 glob
|
||||
import json
|
||||
import os
|
||||
|
||||
import yaml
|
||||
|
||||
_vendors_path = os.path.dirname(os.path.realpath(__file__))
|
||||
_vendor_defaults = None
|
||||
|
||||
|
||||
def get_profile(profile_name):
|
||||
global _vendor_defaults
|
||||
if _vendor_defaults is None:
|
||||
_vendor_defaults = {}
|
||||
for vendor in glob.glob(os.path.join(_vendors_path, '*.yaml')):
|
||||
with open(vendor, 'r') as f:
|
||||
vendor_data = yaml.safe_load(f)
|
||||
_vendor_defaults[vendor_data['name']] = vendor_data['profile']
|
||||
for vendor in glob.glob(os.path.join(_vendors_path, '*.json')):
|
||||
with open(vendor, 'r') as f:
|
||||
vendor_data = json.load(f)
|
||||
_vendor_defaults[vendor_data['name']] = vendor_data['profile']
|
||||
return _vendor_defaults.get(profile_name)
|
11
os-client-config/os_client_config/vendors/auro.json
vendored
Normal file
11
os-client-config/os_client_config/vendors/auro.json
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "auro",
|
||||
"profile": {
|
||||
"auth": {
|
||||
"auth_url": "https://api.van1.auro.io:5000/v2.0"
|
||||
},
|
||||
"identity_api_version": "2",
|
||||
"region_name": "van1",
|
||||
"requires_floating_ip": true
|
||||
}
|
||||
}
|
7
os-client-config/os_client_config/vendors/bluebox.json
vendored
Normal file
7
os-client-config/os_client_config/vendors/bluebox.json
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "bluebox",
|
||||
"profile": {
|
||||
"volume_api_version": "1",
|
||||
"region_name": "RegionOne"
|
||||
}
|
||||
}
|
15
os-client-config/os_client_config/vendors/catalyst.json
vendored
Normal file
15
os-client-config/os_client_config/vendors/catalyst.json
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "catalyst",
|
||||
"profile": {
|
||||
"auth": {
|
||||
"auth_url": "https://api.cloud.catalyst.net.nz:5000/v2.0"
|
||||
},
|
||||
"regions": [
|
||||
"nz-por-1",
|
||||
"nz_wlg_2"
|
||||
],
|
||||
"image_api_version": "1",
|
||||
"volume_api_version": "1",
|
||||
"image_format": "raw"
|
||||
}
|
||||
}
|
19
os-client-config/os_client_config/vendors/citycloud.json
vendored
Normal file
19
os-client-config/os_client_config/vendors/citycloud.json
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "citycloud",
|
||||
"profile": {
|
||||
"auth": {
|
||||
"auth_url": "https://identity1.citycloud.com:5000/v3/"
|
||||
},
|
||||
"regions": [
|
||||
"Buf1",
|
||||
"La1",
|
||||
"Fra1",
|
||||
"Lon1",
|
||||
"Sto2",
|
||||
"Kna1"
|
||||
],
|
||||
"requires_floating_ip": true,
|
||||
"volume_api_version": "1",
|
||||
"identity_api_version": "3"
|
||||
}
|
||||
}
|
14
os-client-config/os_client_config/vendors/conoha.json
vendored
Normal file
14
os-client-config/os_client_config/vendors/conoha.json
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "conoha",
|
||||
"profile": {
|
||||
"auth": {
|
||||
"auth_url": "https://identity.{region_name}.conoha.io"
|
||||
},
|
||||
"regions": [
|
||||
"sin1",
|
||||
"sjc1",
|
||||
"tyo1"
|
||||
],
|
||||
"identity_api_version": "2"
|
||||
}
|
||||
}
|
11
os-client-config/os_client_config/vendors/datacentred.json
vendored
Normal file
11
os-client-config/os_client_config/vendors/datacentred.json
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "datacentred",
|
||||
"profile": {
|
||||
"auth": {
|
||||
"auth_url": "https://compute.datacentred.io:5000"
|
||||
},
|
||||
"region-name": "sal01",
|
||||
"identity_api_version": "3",
|
||||
"image_api_version": "2"
|
||||
}
|
||||
}
|
11
os-client-config/os_client_config/vendors/dreamcompute.json
vendored
Normal file
11
os-client-config/os_client_config/vendors/dreamcompute.json
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "dreamcompute",
|
||||
"profile": {
|
||||
"auth": {
|
||||
"auth_url": "https://iad2.dream.io:5000"
|
||||
},
|
||||
"identity_api_version": "3",
|
||||
"region_name": "RegionOne",
|
||||
"image_format": "raw"
|
||||
}
|
||||
}
|
13
os-client-config/os_client_config/vendors/dreamhost.json
vendored
Normal file
13
os-client-config/os_client_config/vendors/dreamhost.json
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "dreamhost",
|
||||
"profile": {
|
||||
"status": "deprecated",
|
||||
"message": "The dreamhost profile is deprecated. Please use the dreamcompute profile instead",
|
||||
"auth": {
|
||||
"auth_url": "https://keystone.dream.io"
|
||||
},
|
||||
"identity_api_version": "3",
|
||||
"region_name": "RegionOne",
|
||||
"image_format": "raw"
|
||||
}
|
||||
}
|
10
os-client-config/os_client_config/vendors/elastx.json
vendored
Normal file
10
os-client-config/os_client_config/vendors/elastx.json
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "elastx",
|
||||
"profile": {
|
||||
"auth": {
|
||||
"auth_url": "https://ops.elastx.net:5000"
|
||||
},
|
||||
"identity_api_version": "3",
|
||||
"region_name": "regionOne"
|
||||
}
|
||||
}
|
16
os-client-config/os_client_config/vendors/entercloudsuite.json
vendored
Normal file
16
os-client-config/os_client_config/vendors/entercloudsuite.json
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "entercloudsuite",
|
||||
"profile": {
|
||||
"auth": {
|
||||
"auth_url": "https://api.entercloudsuite.com/"
|
||||
},
|
||||
"identity_api_version": "3",
|
||||
"image_api_version": "1",
|
||||
"volume_api_version": "1",
|
||||
"regions": [
|
||||
"it-mil1",
|
||||
"nl-ams1",
|
||||
"de-fra1"
|
||||
]
|
||||
}
|
||||
}
|
15
os-client-config/os_client_config/vendors/fuga.json
vendored
Normal file
15
os-client-config/os_client_config/vendors/fuga.json
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "fuga",
|
||||
"profile": {
|
||||
"auth": {
|
||||
"auth_url": "https://identity.api.fuga.io:5000",
|
||||
"user_domain_name": "Default",
|
||||
"project_domain_name": "Default"
|
||||
},
|
||||
"regions": [
|
||||
"cystack"
|
||||
],
|
||||
"identity_api_version": "3",
|
||||
"volume_api_version": "3"
|
||||
}
|
||||
}
|
13
os-client-config/os_client_config/vendors/ibmcloud.json
vendored
Normal file
13
os-client-config/os_client_config/vendors/ibmcloud.json
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "ibmcloud",
|
||||
"profile": {
|
||||
"auth": {
|
||||
"auth_url": "https://identity.open.softlayer.com"
|
||||
},
|
||||
"volume_api_version": "2",
|
||||
"identity_api_version": "3",
|
||||
"regions": [
|
||||
"london"
|
||||
]
|
||||
}
|
||||
}
|
17
os-client-config/os_client_config/vendors/internap.json
vendored
Normal file
17
os-client-config/os_client_config/vendors/internap.json
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "internap",
|
||||
"profile": {
|
||||
"auth": {
|
||||
"auth_url": "https://identity.api.cloud.iweb.com"
|
||||
},
|
||||
"regions": [
|
||||
"ams01",
|
||||
"da01",
|
||||
"nyj01",
|
||||
"sin01",
|
||||
"sjc01"
|
||||
],
|
||||
"identity_api_version": "3",
|
||||
"floating_ip_source": "None"
|
||||
}
|
||||
}
|
13
os-client-config/os_client_config/vendors/otc.json
vendored
Normal file
13
os-client-config/os_client_config/vendors/otc.json
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "otc",
|
||||
"profile": {
|
||||
"auth": {
|
||||
"auth_url": "https://iam.%(region_name)s.otc.t-systems.com/v3"
|
||||
},
|
||||
"regions": [
|
||||
"eu-de"
|
||||
],
|
||||
"identity_api_version": "3",
|
||||
"image_format": "vhd"
|
||||
}
|
||||
}
|
15
os-client-config/os_client_config/vendors/ovh.json
vendored
Normal file
15
os-client-config/os_client_config/vendors/ovh.json
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "ovh",
|
||||
"profile": {
|
||||
"auth": {
|
||||
"auth_url": "https://auth.cloud.ovh.net/"
|
||||
},
|
||||
"regions": [
|
||||
"BHS1",
|
||||
"GRA1",
|
||||
"SBG1"
|
||||
],
|
||||
"identity_api_version": "3",
|
||||
"floating_ip_source": "None"
|
||||
}
|
||||
}
|
29
os-client-config/os_client_config/vendors/rackspace.json
vendored
Normal file
29
os-client-config/os_client_config/vendors/rackspace.json
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
{
|
||||
"name": "rackspace",
|
||||
"profile": {
|
||||
"auth": {
|
||||
"auth_url": "https://identity.api.rackspacecloud.com/v2.0/"
|
||||
},
|
||||
"regions": [
|
||||
"DFW",
|
||||
"HKG",
|
||||
"IAD",
|
||||
"ORD",
|
||||
"SYD",
|
||||
"LON"
|
||||
],
|
||||
"database_service_type": "rax:database",
|
||||
"compute_service_name": "cloudServersOpenStack",
|
||||
"image_api_use_tasks": true,
|
||||
"image_format": "vhd",
|
||||
"floating_ip_source": "None",
|
||||
"secgroup_source": "None",
|
||||
"requires_floating_ip": false,
|
||||
"volume_api_version": "1",
|
||||
"disable_vendor_agent": {
|
||||
"vm_mode": "hvm",
|
||||
"xenapi_use_agent": false
|
||||
},
|
||||
"has_network": false
|
||||
}
|
||||
}
|
15
os-client-config/os_client_config/vendors/switchengines.json
vendored
Normal file
15
os-client-config/os_client_config/vendors/switchengines.json
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "switchengines",
|
||||
"profile": {
|
||||
"auth": {
|
||||
"auth_url": "https://keystone.cloud.switch.ch:5000/v2.0"
|
||||
},
|
||||
"regions": [
|
||||
"LS",
|
||||
"ZH"
|
||||
],
|
||||
"volume_api_version": "1",
|
||||
"image_api_use_tasks": true,
|
||||
"image_format": "raw"
|
||||
}
|
||||
}
|
11
os-client-config/os_client_config/vendors/ultimum.json
vendored
Normal file
11
os-client-config/os_client_config/vendors/ultimum.json
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "ultimum",
|
||||
"profile": {
|
||||
"auth": {
|
||||
"auth_url": "https://console.ultimum-cloud.com:5000/"
|
||||
},
|
||||
"identity_api_version": "3",
|
||||
"volume_api_version": "1",
|
||||
"region-name": "RegionOne"
|
||||
}
|
||||
}
|
16
os-client-config/os_client_config/vendors/unitedstack.json
vendored
Normal file
16
os-client-config/os_client_config/vendors/unitedstack.json
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "unitedstack",
|
||||
"profile": {
|
||||
"auth": {
|
||||
"auth_url": "https://identity.api.ustack.com/v3"
|
||||
},
|
||||
"regions": [
|
||||
"bj1",
|
||||
"gd1"
|
||||
],
|
||||
"volume_api_version": "1",
|
||||
"identity_api_version": "3",
|
||||
"image_format": "raw",
|
||||
"floating_ip_source": "None"
|
||||
}
|
||||
}
|
15
os-client-config/os_client_config/vendors/vexxhost.json
vendored
Normal file
15
os-client-config/os_client_config/vendors/vexxhost.json
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "vexxhost",
|
||||
"profile": {
|
||||
"auth": {
|
||||
"auth_url": "https://auth.vexxhost.net"
|
||||
},
|
||||
"regions": [
|
||||
"ca-ymq-1"
|
||||
],
|
||||
"dns_api_version": "1",
|
||||
"identity_api_version": "3",
|
||||
"floating_ip_source": "None",
|
||||
"requires_floating_ip": false
|
||||
}
|
||||
}
|
13
os-client-config/os_client_config/vendors/zetta.json
vendored
Normal file
13
os-client-config/os_client_config/vendors/zetta.json
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "zetta",
|
||||
"profile": {
|
||||
"auth": {
|
||||
"auth_url": "https://identity.api.zetta.io/v3"
|
||||
},
|
||||
"regions": [
|
||||
"no-osl1"
|
||||
],
|
||||
"identity_api_version": "3",
|
||||
"dns_api_version": "2"
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
---
|
||||
prelude: >
|
||||
Swiftclient instantiation now provides authentication
|
||||
information so that long lived swiftclient objects can
|
||||
reauthenticate if necessary. This should be a temporary
|
||||
situation until swiftclient supports keystoneauth
|
||||
sessions at which point os-client-config will instantiate
|
||||
swiftclient with a keystoneauth session.
|
||||
features:
|
||||
- Swiftclient instantiation now provides authentication
|
||||
information so that long lived swiftclient objects can
|
||||
reauthenticate if necessary.
|
||||
- Add support for explicit v2password auth type.
|
||||
- Add SSL support to VEXXHOST vendor profile.
|
||||
- Add zetta.io cloud vendor profile.
|
||||
fixes:
|
||||
- Fix bug where project_domain_{name,id} was set even
|
||||
if project_{name,id} was not set.
|
||||
other:
|
||||
- HPCloud vendor profile removed due to cloud shutdown.
|
||||
- RunAbove vendor profile removed due to migration to
|
||||
OVH.
|
@ -0,0 +1,6 @@
|
||||
---
|
||||
features:
|
||||
- Add a field to vendor cloud profiles to indicate
|
||||
active, deprecated and shutdown status. A message to
|
||||
the user is triggered when attempting to use cloud
|
||||
with either deprecated or shutdown status.
|
@ -0,0 +1,7 @@
|
||||
---
|
||||
issues:
|
||||
- Fixed a regression when using latest os-client-config with
|
||||
the keystoneauth from stable/newton. Although this isn't a
|
||||
super common combination, the added feature that broke the
|
||||
interaction is really not worthy of the incompatibility, so
|
||||
a workaround was added.
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
features:
|
||||
- Add support for passing Ironic microversion to the ironicclient
|
||||
constructor in get_legacy_client.
|
@ -0,0 +1,7 @@
|
||||
---
|
||||
features:
|
||||
- Added a flag, 'load_yaml_config' that defaults to True.
|
||||
If set to false, no clouds.yaml files will be loaded. This
|
||||
is beneficial if os-client-config wants to be used inside of
|
||||
a service where end-user clouds.yaml files would make things
|
||||
more confusing.
|
@ -0,0 +1,6 @@
|
||||
---
|
||||
fixes:
|
||||
- Refactor ``OpenStackConfig._fix_backward_madness()`` into
|
||||
``OpenStackConfig.magic_fixes()`` that allows subclasses
|
||||
to inject more fixup magic into the flow during
|
||||
``get_one_cloud()`` processing.
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
deprecations:
|
||||
- Renamed session_client to make_rest_client. session_client
|
||||
will continue to be supported for backwards compatability.
|
@ -0,0 +1,15 @@
|
||||
---
|
||||
features:
|
||||
- Add min_version and max_version to get_legacy_client
|
||||
and to get_session_endpoint. At the moment this is only
|
||||
really fully plumbed through for cinder, which has extra
|
||||
special fun around volume, volumev2 and volumev3. Min and max
|
||||
versions to both methods will look through the options available
|
||||
in the service catalog and try to return the latest one available
|
||||
from the span of requested versions. This means a user can say
|
||||
volume_api_version=None, min_version=2, max_version=3 will get
|
||||
an endpoint from get_session_endpoint or a Client from cinderclient
|
||||
that will be either v2 or v3 but not v1. In the future, min and max
|
||||
version for get_session_endpoint should be able to sort out
|
||||
appropriate endpoints via version discovery, but that does not
|
||||
currently exist.
|
@ -0,0 +1,10 @@
|
||||
---
|
||||
features:
|
||||
- Support added for configuring metadata about networks
|
||||
for a cloud in a list of dicts, rather than in the
|
||||
external_network and internal_network entries. The dicts
|
||||
support a name, a routes_externally field, a nat_destination
|
||||
field and a default_interface field.
|
||||
deprecations:
|
||||
- external_network and internal_network are deprecated and
|
||||
should be replaced with the list of network dicts.
|
@ -0,0 +1,7 @@
|
||||
---
|
||||
fixes:
|
||||
- Reverse the order of option selction in
|
||||
``OpenStackConfig._validate_auth()`` to prefer auth options
|
||||
passed in (from argparse) over those found in clouds.yaml.
|
||||
This allows the application to override config profile
|
||||
auth settings.
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
features:
|
||||
- Added helper method for constructing OpenStack SDK
|
||||
Connection objects.
|
@ -0,0 +1,6 @@
|
||||
---
|
||||
features:
|
||||
- Added kwargs and argparse processing for session_client.
|
||||
deprecations:
|
||||
- Renamed simple_client to session_client. simple_client
|
||||
will remain as an alias for backwards compat.
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
features:
|
||||
- Added helper method for constructing shade
|
||||
OpenStackCloud objects.
|
@ -0,0 +1,3 @@
|
||||
---
|
||||
other:
|
||||
- Started using reno for release notes.
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
other:
|
||||
- Add citycloud regions for Buffalo, Frankfurt, Karlskrona and Los Angles
|
||||
- Add new DreamCompute cloud and deprecate DreamHost cloud
|
265
os-client-config/releasenotes/source/conf.py
Normal file
265
os-client-config/releasenotes/source/conf.py
Normal file
@ -0,0 +1,265 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Os-Client-Config Release Notes documentation build configuration file, created by
|
||||
# sphinx-quickstart on Thu Nov 5 11:50:32 2015.
|
||||
#
|
||||
# This file is execfile()d with the current directory set to its
|
||||
# containing dir.
|
||||
#
|
||||
# Note that not all possible configuration values are present in this
|
||||
# autogenerated file.
|
||||
#
|
||||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
#sys.path.insert(0, os.path.abspath('.'))
|
||||
|
||||
# -- General configuration ------------------------------------------------
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
#needs_sphinx = '1.0'
|
||||
import openstackdocstheme
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# ones.
|
||||
extensions = [
|
||||
'reno.sphinxext',
|
||||
]
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
# The suffix of source filenames.
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The encoding of source files.
|
||||
#source_encoding = 'utf-8-sig'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'os-client-config Release Notes'
|
||||
copyright = u'2015, os-client-config developers'
|
||||
|
||||
# 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.
|
||||
#
|
||||
import pbr.version
|
||||
occ_version = pbr.version.VersionInfo('os-client-config')
|
||||
# The short X.Y version.
|
||||
version = occ_version.canonical_version_string()
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = occ_version.version_string_with_vcs()
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
#language = None
|
||||
|
||||
# There are two options for replacing |today|: either, you set today to some
|
||||
# non-false value, then it is used:
|
||||
#today = ''
|
||||
# Else, today_fmt is used as the format for a strftime call.
|
||||
#today_fmt = '%B %d, %Y'
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
exclude_patterns = []
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all
|
||||
# documents.
|
||||
#default_role = None
|
||||
|
||||
# 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
|
||||
|
||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||
# output. They are ignored by default.
|
||||
#show_authors = False
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
# A list of ignored prefixes for module index sorting.
|
||||
#modindex_common_prefix = []
|
||||
|
||||
# If true, keep warnings as "system message" paragraphs in the built documents.
|
||||
#keep_warnings = False
|
||||
|
||||
|
||||
# -- Options for HTML output ----------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
html_theme = 'openstackdocs'
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
#html_theme_options = {}
|
||||
|
||||
# Add any paths that contain custom themes here, relative to this directory.
|
||||
#html_theme_path = []
|
||||
html_theme_path = [openstackdocstheme.get_html_theme_path()]
|
||||
|
||||
# The name for this set of Sphinx documents. If None, it defaults to
|
||||
# "<project> v<release> documentation".
|
||||
#html_title = None
|
||||
|
||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||
#html_short_title = None
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top
|
||||
# of the sidebar.
|
||||
#html_logo = None
|
||||
|
||||
# The name of an image file (within the static path) to use as favicon of the
|
||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||
# pixels large.
|
||||
#html_favicon = None
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
||||
|
||||
# Add any extra paths that contain custom files (such as robots.txt or
|
||||
# .htaccess) here, relative to this directory. These files are copied
|
||||
# directly to the root of the documentation.
|
||||
#html_extra_path = []
|
||||
|
||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||
# using the given strftime format.
|
||||
#html_last_updated_fmt = '%b %d, %Y'
|
||||
|
||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||
# typographically correct entities.
|
||||
#html_use_smartypants = True
|
||||
|
||||
# Custom sidebar templates, maps document names to template names.
|
||||
#html_sidebars = {}
|
||||
|
||||
# Additional templates that should be rendered to pages, maps page names to
|
||||
# template names.
|
||||
#html_additional_pages = {}
|
||||
|
||||
# If false, no module index is generated.
|
||||
#html_domain_indices = True
|
||||
|
||||
# If false, no index is generated.
|
||||
#html_use_index = True
|
||||
|
||||
# If true, the index is split into individual pages for each letter.
|
||||
#html_split_index = False
|
||||
|
||||
# If true, links to the reST sources are added to the pages.
|
||||
#html_show_sourcelink = True
|
||||
|
||||
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
||||
#html_show_sphinx = True
|
||||
|
||||
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
||||
#html_show_copyright = True
|
||||
|
||||
# If true, an OpenSearch description file will be output, and all pages will
|
||||
# contain a <link> tag referring to it. The value of this option must be the
|
||||
# base URL from which the finished HTML is served.
|
||||
#html_use_opensearch = ''
|
||||
|
||||
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
||||
#html_file_suffix = None
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'OCCReleaseNotesdoc'
|
||||
|
||||
|
||||
# -- Options for LaTeX output ---------------------------------------------
|
||||
|
||||
latex_elements = {
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#'papersize': 'letterpaper',
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#'pointsize': '10pt',
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#'preamble': '',
|
||||
}
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title,
|
||||
# author, documentclass [howto, manual, or own class]).
|
||||
latex_documents = [
|
||||
('index', 'OCCReleaseNotes.tex', u'os-client-config Release Notes Documentation',
|
||||
u'os-client-config developers', 'manual'),
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
# the title page.
|
||||
#latex_logo = None
|
||||
|
||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||
# not chapters.
|
||||
#latex_use_parts = False
|
||||
|
||||
# If true, show page references after internal links.
|
||||
#latex_show_pagerefs = False
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#latex_show_urls = False
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#latex_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#latex_domain_indices = True
|
||||
|
||||
|
||||
# -- Options for manual page output ---------------------------------------
|
||||
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
('index', 'occreleasenotes', u'os-client-config Release Notes Documentation',
|
||||
[u'os-client-config developers'], 1)
|
||||
]
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#man_show_urls = False
|
||||
|
||||
|
||||
# -- Options for Texinfo output -------------------------------------------
|
||||
|
||||
# Grouping the document tree into Texinfo files. List of tuples
|
||||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
('index', 'OCCReleaseNotes', u'os-client-config Release Notes Documentation',
|
||||
u'os-client-config developers', 'OCCReleaseNotes',
|
||||
'One line description of project.',
|
||||
'Miscellaneous'),
|
||||
]
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#texinfo_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#texinfo_domain_indices = True
|
||||
|
||||
# How to display URL addresses: 'footnote', 'no', or 'inline'.
|
||||
#texinfo_show_urls = 'footnote'
|
||||
|
||||
# If true, do not generate a @detailmenu in the "Top" node's menu.
|
||||
#texinfo_no_detailmenu = False
|
||||
|
||||
# -- Options for Internationalization output ------------------------------
|
||||
locale_dirs = ['locale/']
|
21
os-client-config/releasenotes/source/index.rst
Normal file
21
os-client-config/releasenotes/source/index.rst
Normal file
@ -0,0 +1,21 @@
|
||||
================================
|
||||
os-client-config Release Notes
|
||||
================================
|
||||
|
||||
Contents
|
||||
========
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
unreleased
|
||||
pike
|
||||
ocata
|
||||
newton
|
||||
mitaka
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`search`
|
6
os-client-config/releasenotes/source/mitaka.rst
Normal file
6
os-client-config/releasenotes/source/mitaka.rst
Normal file
@ -0,0 +1,6 @@
|
||||
===================================
|
||||
Mitaka Series Release Notes
|
||||
===================================
|
||||
|
||||
.. release-notes::
|
||||
:branch: origin/stable/mitaka
|
6
os-client-config/releasenotes/source/newton.rst
Normal file
6
os-client-config/releasenotes/source/newton.rst
Normal file
@ -0,0 +1,6 @@
|
||||
===================================
|
||||
Newton Series Release Notes
|
||||
===================================
|
||||
|
||||
.. release-notes::
|
||||
:branch: origin/stable/newton
|
6
os-client-config/releasenotes/source/ocata.rst
Normal file
6
os-client-config/releasenotes/source/ocata.rst
Normal file
@ -0,0 +1,6 @@
|
||||
===================================
|
||||
Ocata Series Release Notes
|
||||
===================================
|
||||
|
||||
.. release-notes::
|
||||
:branch: origin/stable/ocata
|
6
os-client-config/releasenotes/source/pike.rst
Normal file
6
os-client-config/releasenotes/source/pike.rst
Normal file
@ -0,0 +1,6 @@
|
||||
===================================
|
||||
Pike Series Release Notes
|
||||
===================================
|
||||
|
||||
.. release-notes::
|
||||
:branch: stable/pike
|
5
os-client-config/releasenotes/source/unreleased.rst
Normal file
5
os-client-config/releasenotes/source/unreleased.rst
Normal file
@ -0,0 +1,5 @@
|
||||
============================
|
||||
Current Series Release Notes
|
||||
============================
|
||||
|
||||
.. release-notes::
|
7
os-client-config/requirements.txt
Normal file
7
os-client-config/requirements.txt
Normal file
@ -0,0 +1,7 @@
|
||||
# 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.
|
||||
PyYAML>=3.10 # MIT
|
||||
appdirs>=1.3.0 # MIT License
|
||||
keystoneauth1>=3.2.0 # Apache-2.0
|
||||
requestsexceptions>=1.2.0 # Apache-2.0
|
35
os-client-config/setup.cfg
Normal file
35
os-client-config/setup.cfg
Normal file
@ -0,0 +1,35 @@
|
||||
[metadata]
|
||||
name = os-client-config
|
||||
summary = OpenStack Client Configuation Library
|
||||
description-file =
|
||||
README.rst
|
||||
author = OpenStack
|
||||
author-email = openstack-dev@lists.openstack.org
|
||||
home-page = https://docs.openstack.org/os-client-config/latest
|
||||
classifier =
|
||||
Environment :: OpenStack
|
||||
Intended Audience :: Information Technology
|
||||
Intended Audience :: System Administrators
|
||||
License :: OSI Approved :: Apache Software License
|
||||
Operating System :: POSIX :: Linux
|
||||
Programming Language :: Python
|
||||
Programming Language :: Python :: 2
|
||||
Programming Language :: Python :: 2.7
|
||||
Programming Language :: Python :: 3
|
||||
Programming Language :: Python :: 3.5
|
||||
|
||||
[files]
|
||||
packages =
|
||||
os_client_config
|
||||
|
||||
[build_sphinx]
|
||||
source-dir = doc/source
|
||||
build-dir = doc/build
|
||||
all_files = 1
|
||||
warning-is-error = 1
|
||||
|
||||
[upload_sphinx]
|
||||
upload-dir = doc/build/html
|
||||
|
||||
[wheel]
|
||||
universal = 1
|
29
os-client-config/setup.py
Normal file
29
os-client-config/setup.py
Normal file
@ -0,0 +1,29 @@
|
||||
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
# implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT
|
||||
import setuptools
|
||||
|
||||
# In python < 2.7.4, a lazy loading of package `pbr` will break
|
||||
# setuptools if some other modules registered functions in `atexit`.
|
||||
# solution from: http://bugs.python.org/issue15881#msg170215
|
||||
try:
|
||||
import multiprocessing # noqa
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
setuptools.setup(
|
||||
setup_requires=['pbr>=2.0.0'],
|
||||
pbr=True)
|
21
os-client-config/test-requirements.txt
Normal file
21
os-client-config/test-requirements.txt
Normal file
@ -0,0 +1,21 @@
|
||||
# The order of packages is significant, because pip processes them in the order
|
||||
# of appearance. Changing the order has an impact on the overall integration
|
||||
# process, which may cause wedges in the gate later.
|
||||
|
||||
hacking!=0.13.0,<0.14,>=0.12.0 # Apache-2.0
|
||||
|
||||
coverage!=4.4,>=4.0 # Apache-2.0
|
||||
docutils>=0.11 # OSI-Approved Open Source, Public Domain
|
||||
extras>=0.0.3 # MIT
|
||||
fixtures>=3.0.0 # Apache-2.0/BSD
|
||||
jsonschema<3.0.0,>=2.6.0 # MIT
|
||||
mock>=2.0.0 # BSD
|
||||
python-glanceclient>=2.8.0 # Apache-2.0
|
||||
python-subunit>=0.0.18 # Apache-2.0/BSD
|
||||
sphinx>=1.6.2 # BSD
|
||||
openstackdocstheme>=1.17.0 # Apache-2.0
|
||||
oslotest>=1.10.0 # Apache-2.0
|
||||
reno>=2.5.0 # Apache-2.0
|
||||
testrepository>=0.0.18 # Apache-2.0/BSD
|
||||
testscenarios>=0.4 # Apache-2.0/BSD
|
||||
testtools>=1.4.0 # MIT
|
89
os-client-config/tools/keystone_version.py
Normal file
89
os-client-config/tools/keystone_version.py
Normal file
@ -0,0 +1,89 @@
|
||||
# Copyright (c) 2017 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_client_config
|
||||
import pprint
|
||||
import sys
|
||||
import urlparse
|
||||
|
||||
|
||||
def print_versions(r):
|
||||
if 'version' in r:
|
||||
for version in r['version']:
|
||||
print_version(version)
|
||||
if 'values' in r:
|
||||
for version in r['values']:
|
||||
print_version(version)
|
||||
if isinstance(r, list):
|
||||
for version in r:
|
||||
print_version(version)
|
||||
|
||||
|
||||
def print_version(version):
|
||||
if version['status'] in ('CURRENT', 'stable'):
|
||||
print(
|
||||
"\tVersion ID: {id} updated {updated}".format(
|
||||
id=version.get('id'),
|
||||
updated=version.get('updated')))
|
||||
|
||||
|
||||
verbose = '-v' in sys.argv
|
||||
ran = []
|
||||
for cloud in os_client_config.OpenStackConfig().get_all_clouds():
|
||||
if cloud.name in ran:
|
||||
continue
|
||||
ran.append(cloud.name)
|
||||
# We don't actually need a compute client - but we'll be getting full urls
|
||||
# anyway. Without this SSL cert info becomes wrong.
|
||||
c = cloud.get_session_client('compute')
|
||||
endpoint = cloud.config['auth']['auth_url']
|
||||
try:
|
||||
print(endpoint)
|
||||
r = c.get(endpoint).json()
|
||||
if verbose:
|
||||
pprint.pprint(r)
|
||||
except Exception as e:
|
||||
print("Error with {cloud}: {e}".format(cloud=cloud.name, e=str(e)))
|
||||
continue
|
||||
if 'version' in r:
|
||||
print_version(r['version'])
|
||||
url = urlparse.urlparse(endpoint)
|
||||
parts = url.path.split(':')
|
||||
if len(parts) == 2:
|
||||
path, port = parts
|
||||
else:
|
||||
path = url.path
|
||||
port = None
|
||||
stripped = path.rsplit('/', 2)[0]
|
||||
if port:
|
||||
stripped = '{stripped}:{port}'.format(stripped=stripped, port=port)
|
||||
endpoint = urlparse.urlunsplit(
|
||||
(url.scheme, url.netloc, stripped, url.params, url.query))
|
||||
print(" also {endpoint}".format(endpoint=endpoint))
|
||||
try:
|
||||
r = c.get(endpoint).json()
|
||||
if verbose:
|
||||
pprint.pprint(r)
|
||||
except Exception:
|
||||
print("\tUnauthorized")
|
||||
continue
|
||||
if 'version' in r:
|
||||
print_version(r)
|
||||
elif 'versions' in r:
|
||||
print_versions(r['versions'])
|
||||
else:
|
||||
print("\n\nUNKNOWN\n\n{r}".format(r=r))
|
||||
else:
|
||||
print_versions(r['versions'])
|
56
os-client-config/tools/nova_version.py
Normal file
56
os-client-config/tools/nova_version.py
Normal file
@ -0,0 +1,56 @@
|
||||
# Copyright (c) 2017 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_client_config
|
||||
|
||||
ran = []
|
||||
for cloud in os_client_config.OpenStackConfig().get_all_clouds():
|
||||
if cloud.name in ran:
|
||||
continue
|
||||
ran.append(cloud.name)
|
||||
c = cloud.get_session_client('compute')
|
||||
try:
|
||||
raw_endpoint = c.get_endpoint()
|
||||
have_current = False
|
||||
endpoint = raw_endpoint.rsplit('/', 2)[0]
|
||||
print(endpoint)
|
||||
r = c.get(endpoint).json()
|
||||
except Exception:
|
||||
print("Error with %s" % cloud.name)
|
||||
continue
|
||||
for version in r['versions']:
|
||||
if version['status'] == 'CURRENT':
|
||||
have_current = True
|
||||
print(
|
||||
"\tVersion ID: {id} updated {updated}".format(
|
||||
id=version.get('id'),
|
||||
updated=version.get('updated')))
|
||||
print(
|
||||
"\tVersion Max: {max}".format(max=version.get('version')))
|
||||
print(
|
||||
"\tVersion Min: {min}".format(min=version.get('min_version')))
|
||||
if not have_current:
|
||||
for version in r['versions']:
|
||||
if version['status'] == 'SUPPORTED':
|
||||
have_current = True
|
||||
print(
|
||||
"\tVersion ID: {id} updated {updated}".format(
|
||||
id=version.get('id'),
|
||||
updated=version.get('updated')))
|
||||
print(
|
||||
"\tVersion Max: {max}".format(max=version.get('version')))
|
||||
print(
|
||||
"\tVersion Min: {min}".format(
|
||||
min=version.get('min_version')))
|
30
os-client-config/tools/tox_install.sh
Executable file
30
os-client-config/tools/tox_install.sh
Executable file
@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Client constraint file contains this client version pin that is in conflict
|
||||
# with installing the client from source. We should remove the version pin in
|
||||
# the constraints file before applying it for from-source installation.
|
||||
|
||||
CONSTRAINTS_FILE=$1
|
||||
shift 1
|
||||
|
||||
set -e
|
||||
|
||||
# NOTE(tonyb): Place this in the tox enviroment's log dir so it will get
|
||||
# published to logs.openstack.org for easy debugging.
|
||||
localfile="$VIRTUAL_ENV/log/upper-constraints.txt"
|
||||
|
||||
if [[ $CONSTRAINTS_FILE != http* ]]; then
|
||||
CONSTRAINTS_FILE=file://$CONSTRAINTS_FILE
|
||||
fi
|
||||
# NOTE(tonyb): need to add curl to bindep.txt if the project supports bindep
|
||||
curl $CONSTRAINTS_FILE --insecure --progress-bar --output $localfile
|
||||
|
||||
pip install -c$localfile openstack-requirements
|
||||
|
||||
# This is the main purpose of the script: Allow local installation of
|
||||
# the current repo. It is listed in constraints file and thus any
|
||||
# install will be constrained and we need to unconstrain it.
|
||||
edit-constraints $localfile -- $CLIENT_NAME
|
||||
|
||||
pip install -c$localfile -U $*
|
||||
exit $?
|
45
os-client-config/tox.ini
Normal file
45
os-client-config/tox.ini
Normal file
@ -0,0 +1,45 @@
|
||||
[tox]
|
||||
minversion = 1.6
|
||||
envlist = py35,py27,pypy,pep8
|
||||
skipsdist = True
|
||||
|
||||
[testenv]
|
||||
usedevelop = True
|
||||
passenv = ZUUL_CACHE_DIR
|
||||
REQUIREMENTS_PIP_LOCATION
|
||||
install_command = {toxinidir}/tools/tox_install.sh {env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} {opts} {packages}
|
||||
setenv =
|
||||
VIRTUAL_ENV={envdir}
|
||||
BRANCH_NAME=master
|
||||
CLIENT_NAME=os-client-config
|
||||
OS_STDOUT_CAPTURE=1
|
||||
OS_STDERR_CAPTURE=1
|
||||
OS_TEST_TIMEOUT=60
|
||||
deps = -r{toxinidir}/test-requirements.txt
|
||||
commands = python setup.py testr --slowest --testr-args='{posargs}'
|
||||
|
||||
[testenv:pep8]
|
||||
commands = flake8
|
||||
|
||||
[testenv:venv]
|
||||
commands = {posargs}
|
||||
|
||||
[testenv:cover]
|
||||
commands = python setup.py test --coverage --coverage-package-name=os_client_config --testr-args='{posargs}'
|
||||
|
||||
[testenv:docs]
|
||||
deps =
|
||||
{[testenv]deps}
|
||||
readme
|
||||
commands =
|
||||
python setup.py build_sphinx
|
||||
python setup.py check -r -s
|
||||
|
||||
[testenv:releasenotes]
|
||||
commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html
|
||||
|
||||
[flake8]
|
||||
show-source = True
|
||||
builtins = _
|
||||
exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build,releasenotes/source/conf.py
|
||||
|
Loading…
x
Reference in New Issue
Block a user