Compare commits

..

6 Commits

Author SHA1 Message Date
David.T
6082163049 Add parameters in Audit creation attributes list
Change-Id: Ib4371223af76d20de2f6d019b73164058f1b4e89
Closes-Bug: #1617320
2016-10-05 15:24:14 +00:00
David.T
d4dfe32d0d python-openstackclient ClientManager interface changed
I update the watcher client plugin to correctly used ClientManager
auth parameters

Change-Id: Ie2e4db3b63ea1671147a407480ce0d2569d5b057
Closes-bug: #1625643
(cherry picked from commit a48a9213f0)
2016-09-29 07:15:28 +00:00
Jenkins
63a319c1d6 Merge "Add constraint target to tox.ini and remove 1 dep" into stable/newton 2016-09-26 09:40:00 +00:00
David.T
6347775f6d Add constraint target to tox.ini and remove 1 dep
This adds a pip install command to tox.ini that is only used when the
tox env is passed with the 'constraints' factor appended onto it.
As such this will not effect developer workflows or current unit tests.

The initial use of this will be in a non-voting job, to verify that the
constrained checks with tox are stable.  DevStack is already running
constrained jobs, as such problems are no expected.

To run a tox with pip using constraints on a developer system a
developer should run the desired tox environment with -constraints.
For example: $(tox -epy27-constraints)
Pip will pull the current version of the upper-constraints.txt file down
from the git.openstack.org, however this method can be overriden to use
a local file setting the environment variable "UPPER_CONSTRAINTS_FILE"
to the local path or a different URL, it is passed directly to pip.

This is currently not enabled in the default tox run, however it is
possible to enable it as a default by adding it to 'envlist' in tox.ini

This also removes requirements.txt from tox.ini deps
This is redundant, per lifeless email:
http://lists.openstack.org/pipermail/openstack-dev/2015-July/069663.html

Change-Id: Id953a99cf2f83533a38a03f9934f4ec818bec03f
2016-09-21 10:17:54 +00:00
avnish
0a5edb5852 Update home page link in cfg file
Change-Id: I4630862bc2e7cd64f158173fdf05af5a054afd92
(cherry picked from commit ad4ee3fcc7)
2016-09-21 08:40:31 +00:00
Doug Hellmann
77a6c9a6d9 Update .gitreview for stable/newton
Change-Id: I2117565c1bf7340baf3df0743a0fd01f30592eb5
2016-09-02 09:45:20 -04:00
150 changed files with 3341 additions and 5775 deletions

View File

@@ -1,11 +0,0 @@
[run]
branch = True
source = watcherclient
omit =
watcherclient/tests/*
[report]
ignore_errors = True
exclude_lines =
@abc.abstract
raise NotImplementedError

15
.gitignore vendored
View File

@@ -6,7 +6,6 @@
# Packages
*.egg
*.egg-info
.eggs
dist
build
eggs
@@ -23,12 +22,11 @@ lib64
pip-log.txt
# Unit test / coverage reports
.coverage*
.coverage
.tox
nosetests.xml
.stestr/
.testrepository
.venv
.testrepository/
# Translations
*.mo
@@ -44,7 +42,7 @@ output/*/index.html
# Sphinx
doc/build
doc/source/reference/api
doc/source/api
# pbr generates these
AUTHORS
@@ -57,10 +55,3 @@ ChangeLog
sftp-config.json
/.idea/
/cover/
# Desktop Service Store
*.DS_Store
# Atom
.remote-sync.json

View File

@@ -1,5 +1,5 @@
[gerrit]
host=review.opendev.org
host=review.openstack.org
port=29418
project=openstack/python-watcherclient.git
defaultbranch=stable/2024.1
defaultbranch=stable/newton

View File

@@ -1,3 +0,0 @@
[DEFAULT]
test_path=./watcherclient/tests/unit
top_dir=./

7
.testr.conf Normal file
View 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:-160} \
${PYTHON:-python} -m subunit.run discover -t ./ ${OS_TEST_PATH:-./watcherclient/tests} $LISTOPT $IDOPTION
test_id_option=--load-list $IDFILE
test_list_option=--list

View File

@@ -1,10 +0,0 @@
- project:
templates:
- openstack-cover-jobs
- openstack-python3-jobs
- publish-openstack-docs-pti
- check-requirements
- openstackclient-plugin-jobs
check:
jobs:
- watcherclient-tempest-functional

View File

@@ -1,13 +1,13 @@
If you would like to contribute to the development of OpenStack,
you must follow the steps in this page:
https://docs.openstack.org/infra/manual/developers.html
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:
https://docs.openstack.org/infra/manual/developers.html#development-workflow
http://docs.openstack.org/infra/manual/developers.html#development-workflow
Pull requests submitted through GitHub will be ignored.

View File

@@ -1,4 +1,4 @@
python-watcherclient Style Commandments
=======================================
Read the OpenStack Style Commandments https://docs.openstack.org/hacking/latest/
Read the OpenStack Style Commandments http://docs.openstack.org/developer/hacking/

6
MANIFEST.in Normal file
View File

@@ -0,0 +1,6 @@
include AUTHORS
include ChangeLog
exclude .gitignore
exclude .gitreview
global-exclude *.pyc

View File

@@ -1,12 +1,3 @@
========================
Team and repository tags
========================
.. image:: https://governance.openstack.org/tc/badges/python-watcherclient.svg
:target: https://governance.openstack.org/tc/reference/tags/index.html
.. Change things from this point on
====================
python-watcherclient
====================
@@ -20,12 +11,12 @@ metrics receiver, complex event processor and profiler, optimization processor
and an action plan applier. This provides a robust framework to realize a wide
range of cloud optimization goals, including the reduction of data center
operating costs, increased system performance via intelligent virtual machine
migration, increased energy efficiency and more!
migration, increased energy efficiency-and more!
* Free software: Apache license
* Wiki: https://wiki.openstack.org/wiki/Watcher
* Source: https://opendev.org/openstack/python-watcherclient
* Bugs: https://bugs.launchpad.net/watcher
* Wiki: http://wiki.openstack.org/wiki/Watcher
* Source: http://git.openstack.org/cgit/openstack/python-watcher
* Bugs: http://bugs.launchpad.net/watcher
Installation
============
@@ -61,7 +52,7 @@ You can install the Watcher CLI with the following command:
sudo pip install python-watcherclient
You can also use the `OpenStack client <https://docs.openstack.org/python-openstackclient/latest/>`_
You can also use the `OpenStack client <http://docs.openstack.org/cli-reference/overview.html>`_
with Watcher (our watcher plugin for OpenStack client is included in the
python-watcherclient package). To install it, you have just to run this command:

2
babel.cfg Normal file
View File

@@ -0,0 +1,2 @@
[python: **.py]

View File

@@ -1,8 +0,0 @@
# The order of packages is significant, because pip processes them in the order
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.
openstackdocstheme>=2.2.1 # Apache-2.0
sphinx>=2.0.0,!=2.1.0 # BSD
sphinxcontrib-apidoc>=0.2.0 # BSD

View File

@@ -0,0 +1,51 @@
.. toctree::
:maxdepth: 1
watcherclient._i18n.rst
watcherclient.client.rst
watcherclient.common.apiclient.base.rst
watcherclient.common.apiclient.exceptions.rst
watcherclient.common.base.rst
watcherclient.common.cliutils.rst
watcherclient.common.command.rst
watcherclient.common.http.rst
watcherclient.common.utils.rst
watcherclient.exceptions.rst
watcherclient.plugin.rst
watcherclient.shell.rst
watcherclient.tests.keystone_client_fixtures.rst
watcherclient.tests.test_client.rst
watcherclient.tests.test_http.rst
watcherclient.tests.test_import.rst
watcherclient.tests.test_utils.rst
watcherclient.tests.utils.rst
watcherclient.tests.v1.base.rst
watcherclient.tests.v1.test_action.rst
watcherclient.tests.v1.test_action_plan.rst
watcherclient.tests.v1.test_action_plan_shell.rst
watcherclient.tests.v1.test_action_shell.rst
watcherclient.tests.v1.test_audit.rst
watcherclient.tests.v1.test_audit_shell.rst
watcherclient.tests.v1.test_audit_template.rst
watcherclient.tests.v1.test_audit_template_shell.rst
watcherclient.tests.v1.test_goal.rst
watcherclient.tests.v1.test_goal_shell.rst
watcherclient.tests.v1.test_metric_collector.rst
watcherclient.tests.v1.test_strategy.rst
watcherclient.tests.v1.test_strategy_shell.rst
watcherclient.v1.action.rst
watcherclient.v1.action_plan.rst
watcherclient.v1.action_plan_shell.rst
watcherclient.v1.action_shell.rst
watcherclient.v1.audit.rst
watcherclient.v1.audit_shell.rst
watcherclient.v1.audit_template.rst
watcherclient.v1.audit_template_shell.rst
watcherclient.v1.client.rst
watcherclient.v1.goal.rst
watcherclient.v1.goal_shell.rst
watcherclient.v1.metric_collector.rst
watcherclient.v1.resource_fields.rst
watcherclient.v1.strategy.rst
watcherclient.v1.strategy_shell.rst
watcherclient.version.rst

View File

@@ -0,0 +1,7 @@
The :mod:`watcherclient.client` Module
======================================
.. automodule:: watcherclient.client
:members:
:undoc-members:
:show-inheritance:

View File

@@ -0,0 +1,7 @@
The :mod:`watcherclient.common.apiclient.auth` Module
=====================================================
.. automodule:: watcherclient.common.apiclient.auth
:members:
:undoc-members:
:show-inheritance:

View File

@@ -0,0 +1,7 @@
The :mod:`watcherclient.common.apiclient.base` Module
=====================================================
.. automodule:: watcherclient.common.apiclient.base
:members:
:undoc-members:
:show-inheritance:

View File

@@ -0,0 +1,7 @@
The :mod:`watcherclient.common.apiclient.client` Module
=======================================================
.. automodule:: watcherclient.common.apiclient.client
:members:
:undoc-members:
:show-inheritance:

View File

@@ -0,0 +1,7 @@
The :mod:`watcherclient.common.apiclient.exceptions` Module
===========================================================
.. automodule:: watcherclient.common.apiclient.exceptions
:members:
:undoc-members:
:show-inheritance:

View File

@@ -0,0 +1,7 @@
The :mod:`watcherclient.common.apiclient.utils` Module
======================================================
.. automodule:: watcherclient.common.apiclient.utils
:members:
:undoc-members:
:show-inheritance:

View File

@@ -0,0 +1,7 @@
The :mod:`watcherclient.common.base` Module
===========================================
.. automodule:: watcherclient.common.base
:members:
:undoc-members:
:show-inheritance:

View File

@@ -0,0 +1,7 @@
The :mod:`watcherclient.common.cliutils` Module
===============================================
.. automodule:: watcherclient.common.cliutils
:members:
:undoc-members:
:show-inheritance:

View File

@@ -0,0 +1,7 @@
The :mod:`watcherclient.common.http` Module
===========================================
.. automodule:: watcherclient.common.http
:members:
:undoc-members:
:show-inheritance:

View File

@@ -0,0 +1,7 @@
The :mod:`watcherclient.common.i18n` Module
===========================================
.. automodule:: watcherclient.common.i18n
:members:
:undoc-members:
:show-inheritance:

View File

@@ -0,0 +1,7 @@
The :mod:`watcherclient.common.utils` Module
============================================
.. automodule:: watcherclient.common.utils
:members:
:undoc-members:
:show-inheritance:

View File

@@ -0,0 +1,7 @@
The :mod:`watcherclient.exceptions` Module
==========================================
.. automodule:: watcherclient.exceptions
:members:
:undoc-members:
:show-inheritance:

View File

@@ -0,0 +1,7 @@
The :mod:`watcherclient.shell` Module
=====================================
.. automodule:: watcherclient.shell
:members:
:undoc-members:
:show-inheritance:

View File

@@ -0,0 +1,7 @@
The :mod:`watcherclient.tests.keystone_client_fixtures` Module
==============================================================
.. automodule:: watcherclient.tests.keystone_client_fixtures
:members:
:undoc-members:
:show-inheritance:

View File

@@ -0,0 +1,7 @@
The :mod:`watcherclient.tests.test_client` Module
=================================================
.. automodule:: watcherclient.tests.test_client
:members:
:undoc-members:
:show-inheritance:

View File

@@ -0,0 +1,7 @@
The :mod:`watcherclient.tests.test_http` Module
===============================================
.. automodule:: watcherclient.tests.test_http
:members:
:undoc-members:
:show-inheritance:

View File

@@ -0,0 +1,7 @@
The :mod:`watcherclient.tests.test_import` Module
=================================================
.. automodule:: watcherclient.tests.test_import
:members:
:undoc-members:
:show-inheritance:

View File

@@ -0,0 +1,7 @@
The :mod:`watcherclient.tests.test_shell` Module
================================================
.. automodule:: watcherclient.tests.test_shell
:members:
:undoc-members:
:show-inheritance:

View File

@@ -0,0 +1,7 @@
The :mod:`watcherclient.tests.test_utils` Module
================================================
.. automodule:: watcherclient.tests.test_utils
:members:
:undoc-members:
:show-inheritance:

View File

@@ -0,0 +1,7 @@
The :mod:`watcherclient.tests.utils` Module
===========================================
.. automodule:: watcherclient.tests.utils
:members:
:undoc-members:
:show-inheritance:

View File

@@ -0,0 +1,7 @@
The :mod:`watcherclient.tests.v1.test_action` Module
====================================================
.. automodule:: watcherclient.tests.v1.test_action
:members:
:undoc-members:
:show-inheritance:

View File

@@ -0,0 +1,7 @@
The :mod:`watcherclient.tests.v1.test_action_plan` Module
=========================================================
.. automodule:: watcherclient.tests.v1.test_action_plan
:members:
:undoc-members:
:show-inheritance:

View File

@@ -0,0 +1,7 @@
The :mod:`watcherclient.tests.v1.test_action_plan_shell` Module
===============================================================
.. automodule:: watcherclient.tests.v1.test_action_plan_shell
:members:
:undoc-members:
:show-inheritance:

View File

@@ -0,0 +1,7 @@
The :mod:`watcherclient.tests.v1.test_action_shell` Module
==========================================================
.. automodule:: watcherclient.tests.v1.test_action_shell
:members:
:undoc-members:
:show-inheritance:

View File

@@ -0,0 +1,7 @@
The :mod:`watcherclient.tests.v1.test_audit` Module
===================================================
.. automodule:: watcherclient.tests.v1.test_audit
:members:
:undoc-members:
:show-inheritance:

View File

@@ -0,0 +1,7 @@
The :mod:`watcherclient.tests.v1.test_audit_shell` Module
=========================================================
.. automodule:: watcherclient.tests.v1.test_audit_shell
:members:
:undoc-members:
:show-inheritance:

View File

@@ -0,0 +1,7 @@
The :mod:`watcherclient.tests.v1.test_audit_template` Module
============================================================
.. automodule:: watcherclient.tests.v1.test_audit_template
:members:
:undoc-members:
:show-inheritance:

View File

@@ -0,0 +1,7 @@
The :mod:`watcherclient.tests.v1.test_audit_template_shell` Module
==================================================================
.. automodule:: watcherclient.tests.v1.test_audit_template_shell
:members:
:undoc-members:
:show-inheritance:

View File

@@ -0,0 +1,7 @@
The :mod:`watcherclient.tests.v1.test_goal` Module
==================================================
.. automodule:: watcherclient.tests.v1.test_goal
:members:
:undoc-members:
:show-inheritance:

View File

@@ -0,0 +1,7 @@
The :mod:`watcherclient.tests.v1.test_goal_shell` Module
========================================================
.. automodule:: watcherclient.tests.v1.test_goal_shell
:members:
:undoc-members:
:show-inheritance:

View File

@@ -0,0 +1,7 @@
The :mod:`watcherclient.tests.v1.test_metric_collector` Module
==============================================================
.. automodule:: watcherclient.tests.v1.test_metric_collector
:members:
:undoc-members:
:show-inheritance:

View File

@@ -0,0 +1,7 @@
The :mod:`watcherclient.tests.v1.test_metric_collector_shell` Module
====================================================================
.. automodule:: watcherclient.tests.v1.test_metric_collector_shell
:members:
:undoc-members:
:show-inheritance:

View File

@@ -0,0 +1,7 @@
The :mod:`watcherclient.v1.action` Module
=========================================
.. automodule:: watcherclient.v1.action
:members:
:undoc-members:
:show-inheritance:

View File

@@ -0,0 +1,7 @@
The :mod:`watcherclient.v1.action_plan` Module
==============================================
.. automodule:: watcherclient.v1.action_plan
:members:
:undoc-members:
:show-inheritance:

View File

@@ -0,0 +1,7 @@
The :mod:`watcherclient.v1.action_plan_shell` Module
====================================================
.. automodule:: watcherclient.v1.action_plan_shell
:members:
:undoc-members:
:show-inheritance:

View File

@@ -0,0 +1,7 @@
The :mod:`watcherclient.v1.action_shell` Module
===============================================
.. automodule:: watcherclient.v1.action_shell
:members:
:undoc-members:
:show-inheritance:

View File

@@ -0,0 +1,7 @@
The :mod:`watcherclient.v1.audit` Module
========================================
.. automodule:: watcherclient.v1.audit
:members:
:undoc-members:
:show-inheritance:

View File

@@ -0,0 +1,7 @@
The :mod:`watcherclient.v1.audit_shell` Module
==============================================
.. automodule:: watcherclient.v1.audit_shell
:members:
:undoc-members:
:show-inheritance:

View File

@@ -0,0 +1,7 @@
The :mod:`watcherclient.v1.audit_template` Module
=================================================
.. automodule:: watcherclient.v1.audit_template
:members:
:undoc-members:
:show-inheritance:

View File

@@ -0,0 +1,7 @@
The :mod:`watcherclient.v1.audit_template_shell` Module
=======================================================
.. automodule:: watcherclient.v1.audit_template_shell
:members:
:undoc-members:
:show-inheritance:

View File

@@ -0,0 +1,7 @@
The :mod:`watcherclient.v1.client` Module
=========================================
.. automodule:: watcherclient.v1.client
:members:
:undoc-members:
:show-inheritance:

View File

@@ -0,0 +1,7 @@
The :mod:`watcherclient.v1.goal` Module
=======================================
.. automodule:: watcherclient.v1.goal
:members:
:undoc-members:
:show-inheritance:

View File

@@ -0,0 +1,7 @@
The :mod:`watcherclient.v1.goal_shell` Module
=============================================
.. automodule:: watcherclient.v1.goal_shell
:members:
:undoc-members:
:show-inheritance:

View File

@@ -0,0 +1,7 @@
The :mod:`watcherclient.v1.metric_collector` Module
===================================================
.. automodule:: watcherclient.v1.metric_collector
:members:
:undoc-members:
:show-inheritance:

View File

@@ -0,0 +1,7 @@
The :mod:`watcherclient.v1.metric_collector_shell` Module
=========================================================
.. automodule:: watcherclient.v1.metric_collector_shell
:members:
:undoc-members:
:show-inheritance:

View File

@@ -0,0 +1,7 @@
The :mod:`watcherclient.v1.resource_fields` Module
==================================================
.. automodule:: watcherclient.v1.resource_fields
:members:
:undoc-members:
:show-inheritance:

View File

@@ -0,0 +1,7 @@
The :mod:`watcherclient.v1.shell` Module
========================================
.. automodule:: watcherclient.v1.shell
:members:
:undoc-members:
:show-inheritance:

View File

@@ -0,0 +1,7 @@
The :mod:`watcherclient.version` Module
=======================================
.. automodule:: watcherclient.version
:members:
:undoc-members:
:show-inheritance:

View File

@@ -68,10 +68,11 @@ Once you have an watcher `Client`_, you can perform various tasks::
>>> watcher.action.list() # list of actions
>>> watcher.action_plan.list() # list of action_plan
>>> watcher.audit.get(audit_uuid_or_name) # information about a particular audit
>>> watcher.audit.get(audit_uuid) # information about a particular audit
When the `Client`_ needs to propagate an exception, it will usually
raise an instance listed in `watcherclient.exceptions`_.
raise an instance subclassed from
`watcherclient.exc.BaseException`_ or `watcherclient.exc.ClientException`_.
Refer to the modules themselves, for more details.
@@ -79,8 +80,15 @@ Refer to the modules themselves, for more details.
watcherclient Modules
=====================
.. toctree::
:maxdepth: 1
modules <api/autoindex>
.. _watcherclient.v1.audit: api/watcherclient.v1.audit.html#watcherclient.v1.audit.Audit
.. _watcherclient.v1.client.Client: api/watcherclient.v1.client.html#watcherclient.v1.client.Client
.. _Client: api/watcherclient.v1.client.html#watcherclient.v1.client.Client
.. _watcherclient.client.get_client(): api/watcherclient.client.html#watcherclient.client.get_client
.. _watcherclient.exceptions: api/watcherclient.exceptions.html
.. _watcherclient.exc.BaseException: api/watcherclient.exc.html#watcherclient.exc.BaseException
.. _watcherclient.exc.ClientException: api/watcherclient.exc.html#watcherclient.exc.ClientException

View File

@@ -1,6 +1,6 @@
===============================================
==============================================
:program:`watcher` Command-Line Interface (CLI)
===============================================
==============================================
.. program:: watcher
.. highlight:: bash
@@ -23,27 +23,27 @@ OpenStack infra-optim Service (Watcher).
In order to use the CLI, you must provide your OpenStack username, password,
project (historically called tenant), and auth endpoint. You can use
configuration options :option:``--os-username``, :option:``--os-password``,
:option:``--os-tenant-id`` (or :option:``--os-tenant-name``),
and :option:``--os-auth-url``, or set the corresponding
configuration options :option:`--os-username`, :option:`--os-password`,
:option:`--os-tenant-id` (or :option:`--os-tenant-name`),
and :option:`--os-auth-url`, or set the corresponding
environment variables::
$ export OS_USERNAME=user
$ export OS_PASSWORD=password
$ export OS_TENANT_ID=b363706f891f48019483f8bd6503c54b # or OS_TENANT_NAME
$ export OS_TENANT_NAME=project # or OS_TENANT_ID
$ export OS_AUTH_URL=http://auth.example.com:5000/v3/
$ export OS_AUTH_URL=http://auth.example.com:5000/v2.0
The command-line tool will attempt to reauthenticate using the provided
credentials for every request. You can override this behavior by manually
supplying an auth token using :option:``--watcher-url`` and
:option:``--os-auth-token``, or by setting the corresponding environment variables::
supplying an auth token using :option:`--watcher-url` and
:option:`--os-auth-token`, or by setting the corresponding environment variables::
$ export WATCHER_URL=http://watcher.example.org:9322/
$ export OS_AUTH_TOKEN=3bcc3d3a03f44e3d8377f9247b0ad155
Since Keystone can return multiple regions in the Service Catalog, you can
specify the one you want with :option:``--os-region-name`` or set the following
specify the one you want with :option:`--os-region-name` or set the following
environment variable. (It defaults to the first in the list returned.)
::
@@ -52,10 +52,10 @@ environment variable. (It defaults to the first in the list returned.)
Watcher CLI supports bash completion. The command-line tool can automatically
fill partially typed commands. To use this feature, source the below file
(available at
https://opendev.org/openstack/python-watcherclient/src/branch/master/tools/watcher.bash_completion)
https://git.openstack.org/cgit/openstack/python-watcherclient/tree/tools/watcher.bash_completion)
to your terminal and then bash completion should work::
$ . watcher.bash_completion
$ source watcher.bash_completion
To avoid doing this every time, add this to your ``.bashrc`` or copy the
watcher.bash_completion file to the default bash completion scripts directory

File diff suppressed because it is too large Load Diff

View File

@@ -1,32 +0,0 @@
=============================
Command-line Tool Reference
=============================
In order to use the CLI, you must provide your OpenStack username,
password, tenant, and auth endpoint. Use the corresponding
configuration options (``--os-username``, ``--os-password``,
``--os-tenant-id``, and ``--os-auth-url``) or set them in environment
variables::
export OS_USERNAME=user
export OS_PASSWORD=pass
export OS_TENANT_ID=b363706f891f48019483f8bd6503c54b
export OS_AUTH_URL=http://auth.example.com:5000/v3/
The command line tool will attempt to reauthenticate using your
provided credentials for every request. You can override this behavior
by manually supplying an auth token using ``--os-watcher-url`` and
``--os-auth-token``. You can alternatively set these environment
variables::
export OS_WATCHER_URL=http://watcher.example.org:9322/
export OS_AUTH_TOKEN=3bcc3d3a03f44e3d8377f9247b0ad155
Once you've configured your authentication parameters, you can run
``watcher help`` to see a complete listing of available commands.
.. toctree::
watcher
openstack_cli
details

View File

@@ -1,3 +1,4 @@
# -*- 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
@@ -11,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from watcherclient import version as watcherclient_version
# -- General configuration ----------------------------------------------------
@@ -18,20 +20,13 @@
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ['sphinx.ext.autodoc',
'sphinx.ext.viewcode',
'sphinxcontrib.apidoc',
'openstackdocstheme',
]
'oslosphinx',
]
# autodoc generation is a bit aggressive and a nuisance when doing heavy
# text edit cycles.
# execute "export SPHINX_DEBUG=1" in your terminal to disable
# sphinxcontrib.apidoc options
apidoc_module_dir = '../../watcherclient'
apidoc_output_dir = 'reference/api'
apidoc_excluded_paths = [
'tests/*']
apidoc_separate_modules = True
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
@@ -42,8 +37,18 @@ source_suffix = '.rst'
master_doc = 'index'
# General information about the project.
project = 'python-watcherclient'
copyright = 'OpenStack Foundation'
project = u'python-watcherclient'
copyright = u'OpenStack Foundation'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
# The full version, including alpha/beta/rc tags.
release = watcherclient_version.version_info.release_string()
# The short X.Y version.
version = watcherclient_version.version_info.version_string()
# A list of ignored prefixes for module index sorting.
modindex_common_prefix = ['watcherclient.']
@@ -56,7 +61,7 @@ add_function_parentheses = True
add_module_names = True
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'native'
pygments_style = 'sphinx'
# -- Options for HTML output --------------------------------------------------
@@ -65,8 +70,7 @@ pygments_style = 'native'
# html_theme_path = ["."]
# html_theme = '_theme'
# html_static_path = ['_static']
html_theme = 'openstackdocs'
# html_theme_path = [openstackdocstheme.get_html_theme_path()]
html_theme_options = {'incubating': True}
# Output file base name for HTML help builder.
htmlhelp_basename = '%sdoc' % project
@@ -79,27 +83,7 @@ latex_documents = [
(
'index',
'%s.tex' % project,
'%s Documentation' % project,
'OpenStack Foundation', 'manual'
u'%s Documentation' % project,
u'OpenStack Foundation', 'manual'
),
]
# Disable usage of xindy https://bugzilla.redhat.com/show_bug.cgi?id=1643664
latex_use_xindy = False
latex_domain_indices = False
latex_elements = {
'makeindex': '',
'printindex': '',
'preamble': r'\setcounter{tocdepth}{3}',
}
# openstackdocstheme options
openstackdocs_repo_name = 'openstack/python-watcherclient'
openstackdocs_pdf_link = True
openstackdocs_bug_project = 'python-watcherclient'
openstackdocs_bug_tag = ''
#html_theme_options = {"show_other_versions": "True"}

View File

@@ -1,8 +1,8 @@
.. _contributing:
====================================
===================================
Contributing to python-watcherclient
====================================
===================================
If you're interested in contributing to the python-watcherclient project,
the following will help get you started.
@@ -19,8 +19,8 @@ signed OpenStack's contributor's agreement.
.. seealso::
* https://docs.openstack.org/infra/manual/developers.html
* https://wiki.openstack.org/CLA
* http://docs.openstack.org/infra/manual/developers.html
* http://wiki.openstack.org/CLA
LaunchPad Project
-----------------
@@ -41,14 +41,14 @@ Project Hosting Details
-------------------------
Bug tracker
https://launchpad.net/python-watcherclient
http://launchpad.net/python-watcherclient
Mailing list (prefix subjects with ``[watcher]`` for faster responses)
http://lists.openstack.org/cgi-bin/mailman/listinfo/openstack-discuss
http://lists.openstack.org/cgi-bin/mailman/listinfo/openstack-dev
Code Hosting
https://opendev.org/openstack/python-watcherclient
https://git.openstack.org/cgit/openstack/python-watcherclient
Code Review
https://review.opendev.org/#/q/status:open+project:openstack/python-watcherclient,n,z
https://review.openstack.org/#/q/status:open+project:openstack/python-watcherclient,n,z

View File

@@ -1,15 +1,43 @@
Python bindings to the OpenStack Watcher API
============================================
This is a client for OpenStack Watcher API. There's a Python API
(the :mod:`watcherclient` modules), and a command-line script
(installed as :program:`watcher`). Each implements the entire
This is a client for OpenStack Watcher API. There's :doc:`a Python API
<api_v1>` (the :mod:`watcherclient` modules), and a :doc:`command-line script
<cli>` (installed as :program:`watcher`). Each implements the entire
OpenStack Watcher API.
.. toctree::
:maxdepth: 2
You'll need credentials for an OpenStack cloud in order to use the watcher client.
cli/index
reference/index
Contents:
.. toctree::
:maxdepth: 1
readme
installation
api_v1
cli
openstack_cli
contributing
Contributing
============
Code is hosted at `git.openstack.org`_. Submit bugs to the Watcher project on
`Launchpad`_. Submit code to the openstack/python-watcherclient project using
`Gerrit`_.
.. _git.openstack.org: https://git.openstack.org/cgit/openstack/python-watcherclient
.. _Launchpad: https://launchpad.net/watcher
.. _Gerrit: http://docs.openstack.org/infra/manual/developers.html#development-workflow
Testing
-------
The preferred way to run the unit tests is using ``tox``.
See `Consistent Testing Interface`_ for more details.
.. _Consistent Testing Interface: http://git.openstack.org/cgit/openstack/governance/tree/reference/project-testing-interface.rst
.. _Watcher: https://wiki.openstack.org/wiki/Watcher

View File

@@ -5,6 +5,6 @@ Installation
If you have `virtualenvwrapper <https://virtualenvwrapper.readthedocs.org/en/latest/install.html>`_ installed::
$ mkvirtualenv python-watcherclient
$ git clone https://opendev.org/openstack/python-watcherclient
$ git clone https://git.openstack.org/openstack/python-watcherclient
$ cd python-watcherclient && python setup.py install
$ pip install -r ./requirements.txt

View File

@@ -24,27 +24,27 @@ OpenStack infra-optim Service (Watcher), by using our additional plugin
In order to use the CLI, you must provide your OpenStack username, password,
project (historically called tenant), and auth endpoint. You can use
configuration options :option:``--os-username``, :option:``--os-password``,
:option:``--os-tenant-id`` (or :option:``--os-tenant-name``),
and :option:``--os-auth-url``, or set the corresponding
configuration options :option:`--os-username`, :option:`--os-password`,
:option:`--os-tenant-id` (or :option:`--os-tenant-name`),
and :option:`--os-auth-url`, or set the corresponding
environment variables::
$ export OS_USERNAME=user
$ export OS_PASSWORD=password
$ export OS_TENANT_ID=b363706f891f48019483f8bd6503c54b # or OS_TENANT_NAME
$ export OS_TENANT_NAME=project # or OS_TENANT_ID
$ export OS_AUTH_URL=http://auth.example.com:5000/v3/
$ export OS_AUTH_URL=http://auth.example.com:5000/v2.0
The command-line tool will attempt to reauthenticate using the provided
credentials for every request. You can override this behavior by manually
supplying an auth token using :option:``--watcher-url`` and
:option:``--os-auth-token``, or by setting the corresponding environment variables::
supplying an auth token using :option:`--watcher-url` and
:option:`--os-auth-token`, or by setting the corresponding environment variables::
export WATCHER_URL=http://watcher.example.org:9322/
export OS_AUTH_TOKEN=3bcc3d3a03f44e3d8377f9247b0ad155
Since Keystone can return multiple regions in the Service Catalog, you can
specify the one you want with :option:``--os-region-name`` or set the following
specify the one you want with :option:`--os-region-name` or set the following
environment variable. (It defaults to the first in the list returned.)
::

1
doc/source/readme.rst Normal file
View File

@@ -0,0 +1 @@
.. include:: ../../README.rst

View File

@@ -1,14 +0,0 @@
==========================
Python Library Reference
==========================
In order to use the python api directly, you must first obtain an auth
token and identify which endpoint you wish to speak to. Once you have
done so, you can use the API like so.
.. toctree::
:maxdepth: 2
api/modules
api_v1

10
openstack-common.conf Normal file
View File

@@ -0,0 +1,10 @@
[DEFAULT]
# The list of modules to copy from oslo-incubator.git
module=apiclient
module=cliutils
module=_i18n
# The base module to hold the copy of openstack.common
base=watcherclient

View File

@@ -1,6 +0,0 @@
---
upgrade:
- |
Python 2.7 support has been dropped. Last release of python-watcherclient
to support py2.7 is OpenStack Train. The minimum version of Python now
supported by python-watcherclient is Python 3.6.

View File

@@ -1,8 +1,13 @@
cliff!=2.9.0,>=2.11.0 # Apache-2.0
osc-lib>=1.10.0 # Apache-2.0
oslo.i18n>=3.20.0 # Apache-2.0
oslo.serialization!=2.19.1,>=2.18.0 # Apache-2.0
oslo.utils>=3.36.0 # Apache-2.0
pbr!=2.1.0,>=3.1.1 # Apache-2.0
keystoneauth1>=3.4.0 # Apache-2.0
PyYAML>=3.13 # MIT
# 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.
Babel>=2.3.4 # BSD
cliff!=1.16.0,!=1.17.0,>=1.15.0 # Apache-2.0
osc-lib>=1.0.2 # Apache-2.0
oslo.i18n>=2.1.0 # Apache-2.0
oslo.utils>=3.16.0 # Apache-2.0
pbr>=1.6 # Apache-2.0
PrettyTable<0.8,>=0.7 # BSD
python-keystoneclient!=2.1.0,>=2.0.0 # Apache-2.0
six>=1.9.0 # MIT

View File

@@ -1,12 +1,11 @@
[metadata]
name = python-watcherclient
summary = Python client library for Watcher API
description_file =
description-file =
README.rst
author = OpenStack
author_email = openstack-discuss@lists.openstack.org
home_page = https://docs.openstack.org/python-watcherclient/latest/
python_requires = >=3.8
author-email = openstack-dev@lists.openstack.org
home-page = http://docs.openstack.org/developer/python-watcherclient
classifier =
Environment :: OpenStack
Intended Audience :: Information Technology
@@ -14,11 +13,10 @@ classifier =
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.8
Programming Language :: Python :: 3.9
Programming Language :: Python :: 3.10
Programming Language :: Python :: 3.11
Programming Language :: Python :: 3.4
[files]
packages =
@@ -29,7 +27,7 @@ console_scripts =
watcher = watcherclient.shell:main
openstack.cli.extension =
infra_optim = watcherclient.osc.plugin
infra_optim = watcherclient.plugin
# Entry points for the 'openstack' command
openstack.infra_optim.v1 =
@@ -38,7 +36,6 @@ openstack.infra_optim.v1 =
optimize_strategy_show = watcherclient.v1.strategy_shell:ShowStrategy
optimize_strategy_list = watcherclient.v1.strategy_shell:ListStrategy
optimize_strategy_state = watcherclient.v1.strategy_shell:StateStrategy
optimize_audittemplate_show = watcherclient.v1.audit_template_shell:ShowAuditTemplate
optimize_audittemplate_list = watcherclient.v1.audit_template_shell:ListAuditTemplate
@@ -53,11 +50,10 @@ openstack.infra_optim.v1 =
optimize_audit_delete = watcherclient.v1.audit_shell:DeleteAudit
optimize_actionplan_show = watcherclient.v1.action_plan_shell:ShowActionPlan
optimize_actionplan_delete = watcherclient.v1.action_plan_shell:DeleteActionPlan
optimize_actionplan_list = watcherclient.v1.action_plan_shell:ListActionPlan
optimize_actionplan_create = watcherclient.v1.action_plan_shell:CreateActionPlan
optimize_actionplan_update = watcherclient.v1.action_plan_shell:UpdateActionPlan
optimize_actionplan_start = watcherclient.v1.action_plan_shell:StartActionPlan
optimize_actionplan_cancel = watcherclient.v1.action_plan_shell:CancelActionPlan
optimize_action_show = watcherclient.v1.action_shell:ShowAction
optimize_action_list = watcherclient.v1.action_shell:ListAction
@@ -65,11 +61,6 @@ openstack.infra_optim.v1 =
optimize_scoringengine_show = watcherclient.v1.scoring_engine_shell:ShowScoringEngine
optimize_scoringengine_list = watcherclient.v1.scoring_engine_shell:ListScoringEngine
optimize_service_show = watcherclient.v1.service_shell:ShowService
optimize_service_list = watcherclient.v1.service_shell:ListService
optimize_datamodel_list = watcherclient.v1.data_model_shell:ListDataModel
# The same as above but used by the 'watcher' command
watcherclient.v1 =
goal_show = watcherclient.v1.goal_shell:ShowGoal
@@ -77,7 +68,6 @@ watcherclient.v1 =
strategy_show = watcherclient.v1.strategy_shell:ShowStrategy
strategy_list = watcherclient.v1.strategy_shell:ListStrategy
strategy_state = watcherclient.v1.strategy_shell:StateStrategy
audittemplate_show = watcherclient.v1.audit_template_shell:ShowAuditTemplate
audittemplate_list = watcherclient.v1.audit_template_shell:ListAuditTemplate
@@ -93,10 +83,10 @@ watcherclient.v1 =
actionplan_show = watcherclient.v1.action_plan_shell:ShowActionPlan
actionplan_list = watcherclient.v1.action_plan_shell:ListActionPlan
actionplan_create = watcherclient.v1.action_plan_shell:CreateActionPlan
actionplan_update = watcherclient.v1.action_plan_shell:UpdateActionPlan
actionplan_start = watcherclient.v1.action_plan_shell:StartActionPlan
actionplan_delete = watcherclient.v1.action_plan_shell:DeleteActionPlan
actionplan_cancel = watcherclient.v1.action_plan_shell:CancelActionPlan
action_show = watcherclient.v1.action_shell:ShowAction
action_list = watcherclient.v1.action_shell:ListAction
@@ -104,13 +94,27 @@ watcherclient.v1 =
scoringengine_show = watcherclient.v1.scoring_engine_shell:ShowScoringEngine
scoringengine_list = watcherclient.v1.scoring_engine_shell:ListScoringEngine
service_show = watcherclient.v1.service_shell:ShowService
service_list = watcherclient.v1.service_shell:ListService
datamodel_list = watcherclient.v1.data_model_shell:ListDataModel
[pbr]
autodoc_index_modules = True
autodoc_exclude_modules =
watcherclient.tests.*
api_doc_dir = reference/api
[build_sphinx]
source-dir = doc/source
build-dir = doc/build
all_files = 1
[bdist_wheel]
universal = 1
[compile_catalog]
directory = watcherclient/locale
domain = watcherclient
[update_catalog]
domain = watcherclient
output_dir = watcherclient/locale
input_file = watcherclient/locale/watcherclient.pot
[extract_messages]
keywords = _ gettext ngettext l_ lazy_gettext _LI _LW _LE _LC
mapping_file = babel.cfg
output_file = watcherclient/locale/watcherclient.pot

View File

@@ -13,8 +13,17 @@
# 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'],
setup_requires=['pbr>=1.8'],
pbr=True)

View File

@@ -1,9 +1,17 @@
coverage!=4.4,>=4.0 # Apache-2.0
fixtures>=3.0.0 # Apache-2.0/BSD
hacking>=3.0.1,<3.1.0 # Apache-2.0
oslotest>=3.2.0 # Apache-2.0
python-subunit>=1.0.0 # Apache-2.0/BSD
stestr>=2.0.0 # Apache-2.0
# The order of packages is significant, because pip processes them in the order
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.
coverage>=3.6 # Apache-2.0
hacking<0.11,>=0.10.2
mock>=2.0 # BSD
oslosphinx!=3.4.0,>=2.5.0 # Apache-2.0
oslotest>=1.10.0 # Apache-2.0
python-subunit>=0.0.18 # Apache-2.0/BSD
sphinx!=1.3b1,<1.3,>=1.2.1 # BSD
testrepository>=0.0.18 # Apache-2.0/BSD
testscenarios>=0.4 # Apache-2.0/BSD
testtools>=2.2.0 # MIT
tempest>=17.1.0 # Apache-2.0
testtools>=1.4.0 # MIT
# Needed for pypi packaging
wheel # MIT

61
tox.ini
View File

@@ -1,78 +1,41 @@
[tox]
minversion = 3.18.0
envlist = py3,pep8
minversion = 1.8
envlist = py34,py27,pep8
skipsdist = True
[testenv]
usedevelop = True
passenv = ZUUL_CACHE_DIR
REQUIREMENTS_PIP_LOCATION
install_command = pip install {opts} {packages}
install_command =
constraints: pip install -U --force-reinstall -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt?h=stable/newton} {opts} {packages}
pip install -U {opts} {packages}
setenv =
VIRTUAL_ENV={envdir}
deps =
-c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/2024.1}
-r{toxinidir}/test-requirements.txt
-r{toxinidir}/requirements.txt
allowlist_externals =
rm
commands = rm -f .testrepository/times.dbm
# The --test-path is defined in .stestr.conf
stestr run {posargs}
stestr slowest
deps = -r{toxinidir}/test-requirements.txt
commands = python setup.py testr --slowest --testr-args='{posargs}'
[testenv:pep8]
basepython = python3
commands = flake8
[testenv:venv]
basepython = python3
commands = {posargs}
[testenv:cover]
basepython = python3
setenv =
PYTHON=coverage run --source watcherclient --parallel-mode
commands =
stestr run {posargs}
coverage combine
coverage html -d cover
coverage xml -o cover/coverage.xml
coverage report
commands = python setup.py testr --coverage --testr-args='{posargs}'
[testenv:docs]
basepython = python3
deps =
-c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/2024.1}
-r{toxinidir}/doc/requirements.txt
commands = sphinx-build -W -b html doc/source doc/build/html
[testenv:pdf-docs]
basepython = python3
envdir = {toxworkdir}/docs
deps = {[testenv:docs]deps}
allowlist_externals =
rm
make
commands =
rm -rf doc/build/pdf
sphinx-build -W -b latex doc/source doc/build/pdf
make -C doc/build/pdf
commands = python setup.py build_sphinx
[testenv:debug]
basepython = python3
commands = oslo_debug_helper -t watcherclient/tests/unit {posargs}
commands = oslo_debug_helper {posargs}
[flake8]
# E123, E125 skipped as they are invalid PEP-8.
show-source = True
enable-extensions = H203,H106
ignore = E123,E125,W504
ignore = E123,E125
builtins = _
exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build
[testenv:wheel]
basepython = python3
commands = python setup.py bdist_wheel
[hacking]

View File

@@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
#
# Copyright 2013 Hewlett-Packard Development Company, L.P.
# All Rights Reserved.
@@ -17,7 +18,6 @@
import pbr.version
from watcherclient import client
from watcherclient.common import api_versioning
from watcherclient import exceptions
@@ -25,11 +25,3 @@ __version__ = pbr.version.VersionInfo(
'python-watcherclient').version_string()
__all__ = ['client', 'exceptions', ]
API_MIN_VERSION = api_versioning.APIVersion("1.0")
# The max version should be the latest version that is supported in the client,
# not necessarily the latest that the server can provide. This is only bumped
# when client supported the max version, and bumped sequentially, otherwise
# the client may break due to server side new version may include some
# backward incompatible change.
API_MAX_VERSION = api_versioning.APIVersion("1.1")

View File

@@ -30,6 +30,16 @@ _C = _translators.contextual_form
# requires oslo.i18n >=2.1.0
_P = _translators.plural_form
# Translators for log levels.
#
# The abbreviated names are meant to reflect the usual use of a short
# name like '_'. The "L" is for "log" and the other letter comes from
# the level.
_LI = _translators.log_info
_LW = _translators.log_warning
_LE = _translators.log_error
_LC = _translators.log_critical
def get_available_languages():
return oslo_i18n.get_available_languages(DOMAIN)

View File

@@ -1,3 +1,5 @@
# -*- 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
@@ -10,183 +12,112 @@
# License for the specific language governing permissions and limitations
# under the License.
from keystoneauth1 import loading as kaloading
from oslo_utils import importutils
from keystoneclient.v2_0 import client as ksclient
import oslo_i18n
from watcherclient._i18n import _
from watcherclient.common import api_versioning
from watcherclient import exceptions
from watcherclient.common import utils
from watcherclient import exceptions as exc
oslo_i18n.install('watcherclient')
def get_client(api_version, os_auth_token=None, watcher_url=None,
os_username=None, os_password=None, os_auth_url=None,
os_project_id=None, os_project_name=None, os_tenant_id=None,
os_tenant_name=None, os_region_name=None,
os_user_domain_id=None, os_user_domain_name=None,
os_project_domain_id=None, os_project_domain_name=None,
os_service_type=None, os_endpoint_type=None,
insecure=None, timeout=None, os_cacert=None, ca_file=None,
os_cert=None, cert_file=None, os_key=None, key_file=None,
os_infra_optim_api_version=None, max_retries=None,
retry_interval=None, session=None, os_endpoint_override=None,
**ignored_kwargs):
"""Get an authenticated client, based on the credentials.
def _get_ksclient(**kwargs):
"""Get an endpoint and auth token from Keystone.
:param kwargs: keyword args containing credentials:
* username: name of user
* password: user's password
* auth_url: endpoint to authenticate against
* insecure: allow insecure SSL (no cert verification)
* tenant_{name|id}: name or ID of tenant
"""
return ksclient.Client(username=kwargs.get('username'),
password=kwargs.get('password'),
tenant_id=kwargs.get('tenant_id'),
tenant_name=kwargs.get('tenant_name'),
auth_url=kwargs.get('auth_url'),
insecure=kwargs.get('insecure'))
def _get_endpoint(client, **kwargs):
"""Get an endpoint using the provided keystone client."""
attr = None
filter_value = None
if kwargs.get('region_name'):
attr = 'region'
filter_value = kwargs.get('region_name')
return client.service_catalog.url_for(
service_type=kwargs.get('service_type') or 'infra-optim',
attr=attr,
filter_value=filter_value,
endpoint_type=kwargs.get('endpoint_type') or 'publicURL')
def get_client(api_version, **kwargs):
"""Get an authenticated client, based on the credentials in args.
:param api_version: the API version to use. Valid value: '1'.
:param os_auth_token: pre-existing token to re-use
:param watcher_url: watcher API endpoint
:param os_username: name of a user
:param os_password: user's password
:param os_auth_url: endpoint to authenticate against
:param os_project_id: ID of a project
:param os_project_name: name of a project
:param os_tenant_id: ID of a tenant (deprecated in favour of
os_project_id)
:param os_tenant_name: name of a tenant (deprecated in favour of
os_project_name)
:param os_region_name: name of a keystone region
:param os_user_domain_id: ID of a domain the user belongs to
:param os_user_domain_name: name of a domain the user belongs to
:param os_project_domain_id: ID of a domain the project belongs to
:param os_project_domain_name: name of a domain the project belongs to
:param os_service_type: the type of service to lookup the endpoint for
:param os_endpoint_type: the type (exposure) of the endpoint
:param insecure: allow insecure SSL (no cert verification)
:param timeout: allows customization of the timeout for client HTTP
requests
:param os_cacert: path to cacert file
:param ca_file: path to cacert file, deprecated in favour of os_cacert
:param os_cert: path to cert file
:param cert_file: path to cert file, deprecated in favour of os_cert
:param os_key: path to key file
:param key_file: path to key file, deprecated in favour of os_key
:param os_infra_optim_api_version: watcher API version to use
:param max_retries: Maximum number of retries in case of conflict error
:param retry_interval: Amount of time (in seconds) between retries in case
of conflict error
:param session: Keystone session to use
:param os_endpoint_override: watcher API endpoint
:param ignored_kwargs: all the other params that are passed. Left for
backwards compatibility. They are ignored.
:param kwargs: keyword args containing credentials, either:
* os_auth_token: pre-existing token to re-use
* watcher_url: watcher API endpoint
or:
* os_username: name of user
* os_password: user's password
* os_auth_url: endpoint to authenticate against
* insecure: allow insecure SSL (no cert verification)
* os_tenant_{name|id}: name or ID of tenant
"""
os_service_type = os_service_type or 'infra-optim'
os_endpoint_type = os_endpoint_type or 'publicURL'
project_id = (os_project_id or os_tenant_id)
project_name = (os_project_name or os_tenant_name)
kwargs = {
'os_infra_optim_api_version': os_infra_optim_api_version,
'max_retries': max_retries,
'retry_interval': retry_interval,
}
endpoint = watcher_url or os_endpoint_override
cacert = os_cacert or ca_file
cert = os_cert or cert_file
key = os_key or key_file
if os_auth_token and endpoint:
kwargs.update({
'token': os_auth_token,
'insecure': insecure,
'ca_file': cacert,
'cert_file': cert,
'key_file': key,
'timeout': timeout,
})
elif os_auth_url:
auth_type = 'password'
auth_kwargs = {
'auth_url': os_auth_url,
'project_id': project_id,
'project_name': project_name,
'user_domain_id': os_user_domain_id,
'user_domain_name': os_user_domain_name,
'project_domain_id': os_project_domain_id,
'project_domain_name': os_project_domain_name,
if kwargs.get('os_auth_token') and kwargs.get('watcher_url'):
token = kwargs.get('os_auth_token')
endpoint = kwargs.get('watcher_url')
auth_ref = None
elif (kwargs.get('os_username') and
kwargs.get('os_password') and
kwargs.get('os_auth_url') and
(kwargs.get('os_tenant_id') or kwargs.get('os_tenant_name'))):
ks_kwargs = {
'username': kwargs.get('os_username'),
'password': kwargs.get('os_password'),
'tenant_id': kwargs.get('os_tenant_id'),
'tenant_name': kwargs.get('os_tenant_name'),
'auth_url': kwargs.get('os_auth_url'),
'service_type': kwargs.get('os_service_type'),
'endpoint_type': kwargs.get('os_endpoint_type'),
'insecure': kwargs.get('insecure'),
}
if os_username and os_password:
auth_kwargs.update({
'username': os_username,
'password': os_password,
})
elif os_auth_token:
auth_type = 'token'
auth_kwargs.update({
'token': os_auth_token,
})
# Create new session only if it was not passed in
if not session:
loader = kaloading.get_plugin_loader(auth_type)
auth_plugin = loader.load_from_options(**auth_kwargs)
# Let keystoneauth do the necessary parameter conversions
session = kaloading.session.Session().load_from_options(
auth=auth_plugin, insecure=insecure, cacert=cacert,
cert=cert, key=key, timeout=timeout,
)
_ksclient = _get_ksclient(**ks_kwargs)
token = (kwargs.get('os_auth_token')
if kwargs.get('os_auth_token')
else _ksclient.auth_token)
exception_msg = _('Must provide Keystone credentials or user-defined '
'endpoint and token')
if not endpoint:
if session:
try:
# Pass the endpoint, it will be used to get hostname
# and port that will be used for API version caching. It will
# be also set as endpoint_override.
endpoint = session.get_endpoint(
service_type=os_service_type,
interface=os_endpoint_type,
region_name=os_region_name
)
except Exception as e:
raise exceptions.AmbiguousAuthSystem(
exception_msg + _(', error was: %s') % e)
else:
# Neither session, nor valid auth parameters provided
raise exceptions.AmbiguousAuthSystem(exception_msg)
ks_kwargs['region_name'] = kwargs.get('os_region_name')
endpoint = (kwargs.get('watcher_url') or
_get_endpoint(_ksclient, **ks_kwargs))
# Always pass the session
kwargs['session'] = session
auth_ref = _ksclient.auth_ref
return Client(api_version, endpoint, **kwargs)
def _get_client_class_and_version(version):
if not isinstance(version, api_versioning.APIVersion):
version = api_versioning.get_api_version(version)
else:
api_versioning.check_major_version(version)
if version.is_latest():
raise exceptions.UnsupportedVersion(
_("The version should be explicit, not latest."))
return version, importutils.import_class(
"watcherclient.v%s.client.Client" % version.ver_major)
e = (_('Must provide Keystone credentials or user-defined endpoint '
'and token'))
raise exc.AmbiguousAuthSystem(e)
cli_kwargs = {
'token': token,
'insecure': kwargs.get('insecure'),
'timeout': kwargs.get('timeout'),
'ca_file': kwargs.get('ca_file'),
'cert_file': kwargs.get('cert_file'),
'key_file': kwargs.get('key_file'),
'auth_ref': auth_ref,
}
return Client(api_version, endpoint, **cli_kwargs)
def Client(version, *args, **kwargs):
"""Initialize client object based on given version.
HOW-TO:
The simplest way to create a client instance is initialization with your
credentials::
>>> from watcherclient import client
>>> watcher = client.Client(VERSION, USERNAME, PASSWORD,
... PROJECT_ID, AUTH_URL)
Here ``VERSION`` can be a string or
``watcherclient.api_versions.APIVersion`` obj. If you prefer string value,
you can use ``1`` or ``1.X`` (where X is a microversion).
Alternatively, you can create a client instance using the keystoneauth
session API. See "The watcherclient Python API" page at
python-watcherclient's doc.
"""
api_version, client_class = _get_client_class_and_version(version)
kw_api = kwargs.get('os_infra_optim_api_version')
endpoint = kwargs.get('endpoint')
# If both os_infra_optim_api_version and endpoint are not provided, get
# API version from arg.
if not kw_api and not endpoint:
kwargs['os_infra_optim_api_version'] = api_version.get_string()
module = utils.import_versioned_module(version, 'client')
client_class = getattr(module, 'Client')
return client_class(*args, **kwargs)

View File

@@ -1,235 +0,0 @@
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import logging
import os
import pkgutil
import re
from oslo_utils import strutils
from watcherclient._i18n import _
from watcherclient import exceptions
LOG = logging.getLogger(__name__)
if not LOG.handlers:
LOG.addHandler(logging.StreamHandler())
MINOR_1_START_END_TIMING = '1.1'
MINOR_2_FORCE_AUDIT = '1.2'
HEADER_NAME = "OpenStack-API-Version"
# key is a deprecated version and value is an alternative version.
DEPRECATED_VERSIONS = {}
_type_error_msg = _("'%(other)s' should be an instance of '%(cls)s'")
def allow_start_end_audit_time(requested_version):
"""Check if we should support optional start/end attributes for Audit.
Version 1.1 of the API added support for start and end time of continuous
audits.
"""
return (APIVersion(requested_version) >=
APIVersion(MINOR_1_START_END_TIMING))
def launch_audit_forced(requested_version):
"""Check if we should support force option for Audit.
Version 1.2 of the API added support for force option.
"""
return (APIVersion(requested_version) >=
APIVersion(MINOR_2_FORCE_AUDIT))
class APIVersion(object):
"""This class represents an API Version Request.
This class provides convenience methods for manipulation
and comparison of version numbers that we need to do to
implement microversions.
"""
def __init__(self, version_str=None):
"""Create an API version object.
:param version_str: String representation of APIVersionRequest.
Correct format is 'X.Y', where 'X' and 'Y'
are int values. None value should be used
to create Null APIVersionRequest, which is
equal to 0.0
"""
self.ver_major = 0
self.ver_minor = 0
if version_str is not None:
match = re.match(r"^([1-9]\d*)\.([1-9]\d*|0|latest)$", version_str)
if match:
self.ver_major = int(match.group(1))
if match.group(2) == "latest":
# NOTE(andreykurilin): Infinity allows to easily determine
# latest version and doesn't require any additional checks
# in comparison methods.
self.ver_minor = float("inf")
else:
self.ver_minor = int(match.group(2))
else:
msg = _("Invalid format of client version '%s'. "
"Expected format 'X.Y', where X is a major part and Y "
"is a minor part of version.") % version_str
raise exceptions.UnsupportedVersion(msg)
def __str__(self):
"""Debug/Logging representation of object."""
if self.is_latest():
return "Latest API Version Major: %s" % self.ver_major
return ("API Version Major: %s, Minor: %s"
% (self.ver_major, self.ver_minor))
def __repr__(self):
if self.is_null():
return "<APIVersion: null>"
else:
return "<APIVersion: %s>" % self.get_string()
def is_null(self):
return self.ver_major == 0 and self.ver_minor == 0
def is_latest(self):
return self.ver_minor == float("inf")
def __lt__(self, other):
if not isinstance(other, APIVersion):
raise TypeError(_type_error_msg % {"other": other,
"cls": self.__class__})
return ((self.ver_major, self.ver_minor) <
(other.ver_major, other.ver_minor))
def __eq__(self, other):
if not isinstance(other, APIVersion):
raise TypeError(_type_error_msg % {"other": other,
"cls": self.__class__})
return ((self.ver_major, self.ver_minor) ==
(other.ver_major, other.ver_minor))
def __gt__(self, other):
if not isinstance(other, APIVersion):
raise TypeError(_type_error_msg % {"other": other,
"cls": self.__class__})
return ((self.ver_major, self.ver_minor) >
(other.ver_major, other.ver_minor))
def __le__(self, other):
return self < other or self == other
def __ne__(self, other):
return not self.__eq__(other)
def __ge__(self, other):
return self > other or self == other
def matches(self, min_version, max_version):
"""Matches the version object.
Returns whether the version object represents a version
greater than or equal to the minimum version and less than
or equal to the maximum version.
:param min_version: Minimum acceptable version.
:param max_version: Maximum acceptable version.
:returns: boolean
If min_version is null then there is no minimum limit.
If max_version is null then there is no maximum limit.
If self is null then raise ValueError
"""
if self.is_null():
raise ValueError(_("Null APIVersion doesn't support 'matches'."))
if max_version.is_null() and min_version.is_null():
return True
elif max_version.is_null():
return min_version <= self
elif min_version.is_null():
return self <= max_version
else:
return min_version <= self <= max_version
def get_string(self):
"""Version string representation.
Converts object to string representation which if used to create
an APIVersion object results in the same version.
"""
if self.is_null():
raise ValueError(
_("Null APIVersion cannot be converted to string."))
elif self.is_latest():
return "%s.%s" % (self.ver_major, "latest")
return "%s.%s" % (self.ver_major, self.ver_minor)
def get_available_major_versions():
# NOTE(andreykurilin): available clients version should not be
# hardcoded, so let's discover them.
matcher = re.compile(r"v[0-9]*$")
submodules = pkgutil.iter_modules(
[os.path.dirname(os.path.dirname(__file__))])
available_versions = [name[1:] for loader, name, ispkg in submodules
if matcher.search(name)]
return available_versions
def check_major_version(api_version):
"""Checks major part of ``APIVersion`` obj is supported.
:raises watcherclient.exceptions.UnsupportedVersion: if major part is not
supported
"""
available_versions = get_available_major_versions()
if (not api_version.is_null() and
str(api_version.ver_major) not in available_versions):
if len(available_versions) == 1:
msg = _("Invalid client version '%(version)s'. "
"Major part should be '%(major)s'") % {
"version": api_version.get_string(),
"major": available_versions[0]}
else:
msg = _("Invalid client version '%(version)s'. "
"Major part must be one of: '%(major)s'") % {
"version": api_version.get_string(),
"major": ", ".join(available_versions)}
raise exceptions.UnsupportedVersion(msg)
def get_api_version(version_string):
"""Returns checked APIVersion object"""
version_string = str(version_string)
if version_string in DEPRECATED_VERSIONS:
LOG.warning(
"Version %(deprecated_version)s is deprecated, using "
"alternative version %(alternative)s instead.",
{"deprecated_version": version_string,
"alternative": DEPRECATED_VERSIONS[version_string]})
version_string = DEPRECATED_VERSIONS[version_string]
if strutils.is_int_like(version_string):
version_string = "%s.0" % version_string
api_version = APIVersion(version_string)
check_major_version(api_version)
return api_version

View File

@@ -39,9 +39,10 @@ Base utilities to build API operation managers and objects on top of.
import abc
import copy
from urllib import parse
from oslo_utils import strutils
import six
from six.moves.urllib import parse
from watcherclient._i18n import _
from watcherclient.common.apiclient import exceptions
@@ -223,7 +224,8 @@ class BaseManager(HookableMixin):
return self.client.delete(url)
class ManagerWithFind(BaseManager, metaclass=abc.ABCMeta):
@six.add_metaclass(abc.ABCMeta)
class ManagerWithFind(BaseManager):
"""Manager with additional `find()`/`findall()` methods."""
@abc.abstractmethod
@@ -315,7 +317,7 @@ class CrudManager(BaseManager):
def _filter_kwargs(self, kwargs):
"""Drop null values and handle ids."""
for key, ref in kwargs.copy().items():
for key, ref in six.iteritems(kwargs.copy()):
if ref is None:
kwargs.pop(key)
else:
@@ -473,7 +475,7 @@ class Resource(object):
return None
def _add_details(self, info):
for (k, v) in info.items():
for (k, v) in six.iteritems(info):
try:
setattr(self, k, v)
self._info[k] = v
@@ -495,7 +497,7 @@ class Resource(object):
def get(self):
"""Support for lazy loading details.
Some clients, such as watcherclient have the option to lazy load the
Some clients, such as novaclient have the option to lazy load the
details, details which can be loaded with this function.
"""
# set_loaded() first ... so if we have to bail, we know we tried.
@@ -519,9 +521,6 @@ class Resource(object):
return self.id == other.id
return self._info == other._info
def __ne__(self, other):
return not self.__eq__(other)
def is_loaded(self):
return self._loaded

View File

@@ -36,6 +36,8 @@ Exception definitions.
import inspect
import sys
import six
from watcherclient._i18n import _
@@ -419,7 +421,7 @@ class HttpVersionNotSupported(HttpServerError):
# _code_map contains all the classes that have http_status attribute.
_code_map = dict(
(getattr(obj, 'http_status', None), obj)
for name, obj in vars(sys.modules[__name__]).items()
for name, obj in six.iteritems(vars(sys.modules[__name__]))
if inspect.isclass(obj) and getattr(obj, 'http_status', False)
)
@@ -431,7 +433,11 @@ def from_response(response, method, url):
:param method: HTTP method used for request
:param url: URL used for request
"""
req_id = response.headers.get("x-openstack-request-id")
# NOTE(hdd) true for older versions of nova and cinder
if not req_id:
req_id = response.headers.get("x-compute-request-id")
kwargs = {
"http_status": response.status_code,
"response": response,
@@ -455,7 +461,7 @@ def from_response(response, method, url):
kwargs["message"] = (error.get("message") or
error.get("faultstring"))
kwargs["details"] = (error.get("details") or
str(body))
six.text_type(body))
elif content_type.startswith("text/"):
kwargs["details"] = response.text

View File

@@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
#
# Copyright 2012 OpenStack LLC.
# All Rights Reserved.
@@ -19,7 +20,8 @@ Base utilities to build API operation managers and objects on top of.
"""
import copy
from urllib import parse as urlparse
import six.moves.urllib.parse as urlparse
from watcherclient.common.apiclient import base
@@ -30,7 +32,10 @@ def getid(obj):
Abstracts the common pattern of allowing both an object or an
object's ID (UUID) as a parameter when dealing with relationships.
"""
return getattr(obj, 'id', obj)
try:
return obj.id
except AttributeError:
return obj
class Manager(object):
@@ -130,11 +135,6 @@ class Manager(object):
def _delete(self, url):
self.api.raw_request('DELETE', url)
def _start(self, url, body=None, method='POST'):
resp, body = self.api.json_request(method, url, body={})
if body:
return self.resource_class(self, body)
class Resource(base.Resource):
"""Represents a particular instance of an object (tenant, user, etc).

View File

@@ -0,0 +1,276 @@
# Copyright 2012 Red Hat, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
# W0603: Using the global statement
# W0621: Redefining name %s from outer scope
# pylint: disable=W0603,W0621
from __future__ import print_function
import getpass
import inspect
import os
import sys
import textwrap
from oslo_utils import encodeutils
from oslo_utils import strutils
import prettytable
import six
from six import moves
from watcherclient._i18n import _
class MissingArgs(Exception):
"""Supplied arguments are not sufficient for calling a function."""
def __init__(self, missing):
self.missing = missing
msg = _("Missing arguments: %s") % ", ".join(missing)
super(MissingArgs, self).__init__(msg)
def validate_args(fn, *args, **kwargs):
"""Check that the supplied args are sufficient for calling a function.
>>> validate_args(lambda a: None)
Traceback (most recent call last):
...
MissingArgs: Missing argument(s): a
>>> validate_args(lambda a, b, c, d: None, 0, c=1)
Traceback (most recent call last):
...
MissingArgs: Missing argument(s): b, d
:param fn: the function to check
:param arg: the positional arguments supplied
:param kwargs: the keyword arguments supplied
"""
argspec = inspect.getargspec(fn)
num_defaults = len(argspec.defaults or [])
required_args = argspec.args[:len(argspec.args) - num_defaults]
def isbound(method):
return getattr(method, '__self__', None) is not None
if isbound(fn):
required_args.pop(0)
missing = [arg for arg in required_args if arg not in kwargs]
missing = missing[len(args):]
if missing:
raise MissingArgs(missing)
def arg(*args, **kwargs):
"""Decorator for CLI args.
Example:
>>> @arg("name", help="Name of the new entity")
... def entity_create(args):
... pass
"""
def _decorator(func):
add_arg(func, *args, **kwargs)
return func
return _decorator
def env(*args, **kwargs):
"""Returns the first environment variable set.
If all are empty, defaults to '' or keyword arg `default`.
"""
for arg in args:
value = os.environ.get(arg)
if value:
return value
return kwargs.get('default', '')
def add_arg(func, *args, **kwargs):
"""Bind CLI arguments to a shell.py `do_foo` function."""
if not hasattr(func, 'arguments'):
func.arguments = []
# NOTE(sirp): avoid dups that can occur when the module is shared across
# tests.
if (args, kwargs) not in func.arguments:
# Because of the semantics of decorator composition if we just append
# to the options list positional options will appear to be backwards.
func.arguments.insert(0, (args, kwargs))
def unauthenticated(func):
"""Adds 'unauthenticated' attribute to decorated function.
Usage:
>>> @unauthenticated
... def mymethod(f):
... pass
"""
func.unauthenticated = True
return func
def isunauthenticated(func):
"""Checks if the function does not require authentication.
Mark such functions with the `@unauthenticated` decorator.
:returns: bool
"""
return getattr(func, 'unauthenticated', False)
def print_list(objs, fields, formatters=None, sortby_index=0,
mixed_case_fields=None, field_labels=None):
"""Print a list or objects as a table, one row per object.
:param objs: iterable of :class:`Resource`
:param fields: attributes that correspond to columns, in order
:param formatters: `dict` of callables for field formatting
:param sortby_index: index of the field for sorting table rows
:param mixed_case_fields: fields corresponding to object attributes that
have mixed case names (e.g., 'serverId')
:param field_labels: Labels to use in the heading of the table, default to
fields.
"""
formatters = formatters or {}
mixed_case_fields = mixed_case_fields or []
field_labels = field_labels or fields
if len(field_labels) != len(fields):
raise ValueError(_("Field labels list %(labels)s has different number "
"of elements than fields list %(fields)s"),
{'labels': field_labels, 'fields': fields})
if sortby_index is None:
kwargs = {}
else:
kwargs = {'sortby': field_labels[sortby_index]}
pt = prettytable.PrettyTable(field_labels)
pt.align = 'l'
for o in objs:
row = []
for field in fields:
data = '-'
if field in formatters:
data = formatters[field](o)
else:
if field in mixed_case_fields:
field_name = field.replace(' ', '_')
else:
field_name = field.lower().replace(' ', '_')
data = getattr(o, field_name, '')
if data is None:
data = '-'
row.append(data)
pt.add_row(row)
if six.PY3:
print(encodeutils.safe_encode(pt.get_string(**kwargs)).decode())
else:
print(encodeutils.safe_encode(pt.get_string(**kwargs)))
def print_dict(dct, dict_property="Property", wrap=0):
"""Print a `dict` as a table of two columns.
:param dct: `dict` to print
:param dict_property: name of the first column
:param wrap: wrapping for the second column
"""
pt = prettytable.PrettyTable([dict_property, 'Value'])
pt.align = 'l'
for k, v in six.iteritems(dct):
# convert dict to str to check length
if isinstance(v, dict):
v = six.text_type(v)
if wrap > 0:
v = textwrap.fill(six.text_type(v), wrap)
# if value has a newline, add in multiple rows
# e.g. fault with stacktrace
if v and isinstance(v, six.string_types) and r'\n' in v:
lines = v.strip().split(r'\n')
col1 = k
for line in lines:
pt.add_row([col1, line])
col1 = ''
else:
if v is None:
v = '-'
pt.add_row([k, v])
if six.PY3:
print(encodeutils.safe_encode(pt.get_string()).decode())
else:
print(encodeutils.safe_encode(pt.get_string()))
def get_password(max_password_prompts=3):
"""Read password from TTY."""
verify = strutils.bool_from_string(env("OS_VERIFY_PASSWORD"))
pw = None
if hasattr(sys.stdin, "isatty") and sys.stdin.isatty():
# Check for Ctrl-D
try:
for __ in moves.range(max_password_prompts):
pw1 = getpass.getpass("OS Password: ")
if verify:
pw2 = getpass.getpass("Please verify: ")
else:
pw2 = pw1
if pw1 == pw2 and pw1:
pw = pw1
break
except EOFError:
pass
return pw
def service_type(stype):
"""Adds 'service_type' attribute to decorated function.
Usage:
.. code-block:: python
@service_type('volume')
def mymethod(f):
...
"""
def inner(f):
f.service_type = stype
return f
return inner
def get_service_type(f):
"""Retrieves service type from function."""
return getattr(f, 'service_type', None)
def pretty_choice_list(l):
return ', '.join("'%s'" % i for i in l)
def exit(msg=''):
if msg:
print(msg, file=sys.stderr)
sys.exit(1)

View File

@@ -18,6 +18,7 @@ import logging
from cliff import command
from cliff import lister
from cliff import show
import six
class CommandMeta(abc.ABCMeta):
@@ -29,7 +30,8 @@ class CommandMeta(abc.ABCMeta):
return super(CommandMeta, mcs).__new__(mcs, name, bases, cls_dict)
class Command(command.Command, metaclass=CommandMeta):
@six.add_metaclass(CommandMeta)
class Command(command.Command):
def run(self, parsed_args):
self.log.debug('run(%s)', parsed_args)
@@ -41,8 +43,4 @@ class Lister(Command, lister.Lister):
class ShowOne(Command, show.ShowOne):
def get_parser(self, prog_name, formatter_class=None):
parser = super(ShowOne, self).get_parser(prog_name)
if formatter_class:
parser.formatter_class = formatter_class
return parser
pass

View File

@@ -0,0 +1,388 @@
# -*- coding: utf-8 -*-
#
# Copyright 2012 OpenStack LLC.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import copy
import json
import logging
import os
import socket
import ssl
from keystoneclient import adapter
import six
import six.moves.urllib.parse as urlparse
from watcherclient._i18n import _, _LW, _LE
from watcherclient import exceptions as exc
LOG = logging.getLogger(__name__)
USER_AGENT = 'python-watcherclient'
CHUNKSIZE = 1024 * 64 # 64kB
API_VERSION = '/v1'
def _trim_endpoint_api_version(url):
"""Trim API version and trailing slash from endpoint."""
return url.rstrip('/').rstrip(API_VERSION)
def _extract_error_json(body):
"""Return error_message from the HTTP response body."""
error_json = {}
if six.PY3 and not isinstance(body, six.string_types):
body = body.decode("utf-8")
try:
body_json = json.loads(body)
if 'error_message' in body_json:
raw_msg = body_json['error_message']
error_json = json.loads(raw_msg)
except ValueError:
pass
return error_json
class HTTPClient(object):
def __init__(self, endpoint, **kwargs):
self.endpoint = endpoint
self.endpoint_trimmed = _trim_endpoint_api_version(endpoint)
self.auth_token = kwargs.get('token')
self.auth_ref = kwargs.get('auth_ref')
self.connection_params = self.get_connection_params(endpoint, **kwargs)
@staticmethod
def get_connection_params(endpoint, **kwargs):
parts = urlparse.urlparse(endpoint)
path = _trim_endpoint_api_version(parts.path)
_args = (parts.hostname, parts.port, path)
_kwargs = {'timeout': (float(kwargs.get('timeout'))
if kwargs.get('timeout') else 600)}
if parts.scheme == 'https':
_class = VerifiedHTTPSConnection
_kwargs['ca_file'] = kwargs.get('ca_file', None)
_kwargs['cert_file'] = kwargs.get('cert_file', None)
_kwargs['key_file'] = kwargs.get('key_file', None)
_kwargs['insecure'] = kwargs.get('insecure', False)
elif parts.scheme == 'http':
_class = six.moves.http_client.HTTPConnection
else:
raise exc.EndpointException(
_('Unsupported scheme: %s'), parts.scheme)
return (_class, _args, _kwargs)
def get_connection(self):
_class = self.connection_params[0]
try:
return _class(*self.connection_params[1][0:2],
**self.connection_params[2])
except six.moves.http_client.InvalidURL:
raise exc.EndpointException()
def log_curl_request(self, method, url, kwargs):
curl = ['curl -i -X %s' % method]
for (key, value) in kwargs['headers'].items():
header = '-H \'%s: %s\'' % (key, value)
curl.append(header)
conn_params_fmt = [
('key_file', '--key %s'),
('cert_file', '--cert %s'),
('ca_file', '--cacert %s'),
]
for (key, fmt) in conn_params_fmt:
value = self.connection_params[2].get(key)
if value:
curl.append(fmt % value)
if self.connection_params[2].get('insecure'):
curl.append('-k')
if 'body' in kwargs:
curl.append('-d \'%s\'' % kwargs['body'])
curl.append(urlparse.urljoin(self.endpoint_trimmed, url))
LOG.debug(' '.join(curl))
@staticmethod
def log_http_response(resp, body=None):
status = (resp.version / 10.0, resp.status, resp.reason)
dump = ['\nHTTP/%.1f %s %s' % status]
dump.extend(['%s: %s' % (k, v) for k, v in resp.getheaders()])
dump.append('')
if body:
dump.extend([body, ''])
LOG.debug('\n'.join(dump))
def _make_connection_url(self, url):
(_class, _args, _kwargs) = self.connection_params
base_url = _args[2]
return '%s/%s' % (base_url, url.lstrip('/'))
def _http_request(self, url, method, **kwargs):
"""Send an http request with the specified characteristics.
Wrapper around httplib.HTTP(S)Connection.request to handle tasks such
as setting headers and error handling.
"""
# Copy the kwargs so we can reuse the original in case of redirects
kwargs['headers'] = copy.deepcopy(kwargs.get('headers', {}))
kwargs['headers'].setdefault('User-Agent', USER_AGENT)
if self.auth_token:
kwargs['headers'].setdefault('X-Auth-Token', self.auth_token)
self.log_curl_request(method, url, kwargs)
conn = self.get_connection()
try:
conn_url = self._make_connection_url(url)
conn.request(method, conn_url, **kwargs)
resp = conn.getresponse()
except socket.gaierror as e:
raise exc.EndpointNotFound(
_("Error finding address for %(url)s: %(e)s"),
url=url, e=e)
except (socket.error, socket.timeout) as e:
endpoint = self.endpoint
raise exc.CommunicationError(
_("Error communicating with %(endpoint)s %(e)s"),
endpoint=endpoint, e=e)
body_iter = ResponseBodyIterator(resp)
# Read body into string if it isn't obviously image data
body_str = None
if resp.getheader('content-type', None) != 'application/octet-stream':
body_str = ''.join([chunk for chunk in body_iter])
self.log_http_response(resp, body_str)
body_iter = six.StringIO(body_str)
else:
self.log_http_response(resp)
if 400 <= resp.status < 600:
LOG.warning(_LW("Request returned failure status."))
error_json = _extract_error_json(body_str)
raise exc.from_response(
resp, error_json.get('faultstring'),
error_json.get('debuginfo'), method, url)
elif resp.status in (301, 302, 305):
# Redirected. Reissue the request to the new location.
return self._http_request(resp['location'], method, **kwargs)
elif resp.status == 300:
raise exc.from_response(resp, method=method, url=url)
return resp, body_iter
def json_request(self, method, url, **kwargs):
kwargs.setdefault('headers', {})
kwargs['headers'].setdefault('Content-Type', 'application/json')
kwargs['headers'].setdefault('Accept', 'application/json')
if 'body' in kwargs:
kwargs['body'] = json.dumps(kwargs['body'])
resp, body_iter = self._http_request(url, method, **kwargs)
content_type = resp.getheader('content-type', None)
if resp.status == 204 or resp.status == 205 or content_type is None:
return resp, list()
if 'application/json' in content_type:
body = ''.join([chunk for chunk in body_iter])
try:
body = json.loads(body)
except ValueError:
LOG.error(_LE('Could not decode response body as JSON'))
else:
body = None
return resp, body
def raw_request(self, method, url, **kwargs):
kwargs.setdefault('headers', {})
kwargs['headers'].setdefault('Content-Type',
'application/octet-stream')
return self._http_request(url, method, **kwargs)
class VerifiedHTTPSConnection(six.moves.http_client.HTTPSConnection):
"""httplib-compatibile connection using client-side SSL authentication
:see http://code.activestate.com/recipes/
577548-https-httplib-client-connection-with-certificate-v/
"""
def __init__(self, host, port, key_file=None, cert_file=None,
ca_file=None, timeout=None, insecure=False):
six.moves.http_client.HTTPSConnection.__init__(
self, host, port,
key_file=key_file,
cert_file=cert_file)
self.key_file = key_file
self.cert_file = cert_file
if ca_file is not None:
self.ca_file = ca_file
else:
self.ca_file = self.get_system_ca_file()
self.timeout = timeout
self.insecure = insecure
def connect(self):
"""Connect to a host on a given (SSL) port.
If ca_file is pointing somewhere, use it to check Server Certificate.
Redefined/copied and extended from httplib.py:1105 (Python 2.6.x).
This is needed to pass cert_reqs=ssl.CERT_REQUIRED as parameter to
ssl.wrap_socket(), which forces SSL to check server certificate against
our client certificate.
"""
sock = socket.create_connection((self.host, self.port), self.timeout)
if self._tunnel_host:
self.sock = sock
self._tunnel()
if self.insecure is True:
kwargs = {'cert_reqs': ssl.CERT_NONE}
else:
kwargs = {'cert_reqs': ssl.CERT_REQUIRED, 'ca_certs': self.ca_file}
if self.cert_file:
kwargs['certfile'] = self.cert_file
if self.key_file:
kwargs['keyfile'] = self.key_file
self.sock = ssl.wrap_socket(sock, **kwargs)
@staticmethod
def get_system_ca_file():
"""Return path to system default CA file."""
# Standard CA file locations for Debian/Ubuntu, RedHat/Fedora,
# Suse, FreeBSD/OpenBSD
ca_path = ['/etc/ssl/certs/ca-certificates.crt',
'/etc/pki/tls/certs/ca-bundle.crt',
'/etc/ssl/ca-bundle.pem',
'/etc/ssl/cert.pem']
for ca in ca_path:
if os.path.exists(ca):
return ca
return None
class SessionClient(adapter.LegacyJsonAdapter):
"""HTTP client based on Keystone client session."""
def _http_request(self, url, method, **kwargs):
kwargs.setdefault('user_agent', USER_AGENT)
kwargs.setdefault('auth', self.auth)
endpoint_filter = kwargs.setdefault('endpoint_filter', {})
endpoint_filter.setdefault('interface', self.interface)
endpoint_filter.setdefault('service_type', self.service_type)
endpoint_filter.setdefault('region_name', self.region_name)
resp = self.session.request(url, method,
raise_exc=False, **kwargs)
if 400 <= resp.status_code < 600:
error_json = _extract_error_json(resp.content)
raise exc.from_response(resp, error_json.get(
'faultstring'),
error_json.get('debuginfo'), method, url)
elif resp.status_code in (301, 302, 305):
# Redirected. Reissue the request to the new location.
location = resp.headers.get('location')
resp = self._http_request(location, method, **kwargs)
elif resp.status_code == 300:
raise exc.from_response(resp, method=method, url=url)
return resp
def json_request(self, method, url, **kwargs):
kwargs.setdefault('headers', {})
kwargs['headers'].setdefault('Content-Type', 'application/json')
kwargs['headers'].setdefault('Accept', 'application/json')
if 'body' in kwargs:
kwargs['data'] = json.dumps(kwargs.pop('body'))
resp = self._http_request(url, method, **kwargs)
body = resp.content
content_type = resp.headers.get('content-type', None)
status = resp.status_code
if status == 204 or status == 205 or content_type is None:
return resp, list()
if 'application/json' in content_type:
try:
body = resp.json()
except ValueError:
LOG.error(_LE('Could not decode response body as JSON'))
else:
body = None
return resp, body
def raw_request(self, method, url, **kwargs):
kwargs.setdefault('headers', {})
kwargs['headers'].setdefault('Content-Type',
'application/octet-stream')
return self._http_request(url, method, **kwargs)
class ResponseBodyIterator(object):
"""A class that acts as an iterator over an HTTP response."""
def __init__(self, resp):
self.resp = resp
def __iter__(self):
while True:
yield self.next()
def next(self):
chunk = self.resp.read(CHUNKSIZE)
if chunk:
if six.PY3 and not isinstance(chunk, six.string_types):
chunk = chunk.decode("utf-8")
return chunk
else:
raise StopIteration()
def _construct_http_client(*args, **kwargs):
session = kwargs.pop('session', None)
auth = kwargs.pop('auth', None)
if session:
service_type = kwargs.pop('service_type', 'infra-optim')
interface = kwargs.pop('endpoint_type', None)
region_name = kwargs.pop('region_name', None)
return SessionClient(session=session,
auth=auth,
interface=interface,
service_type=service_type,
region_name=region_name,
service_name=None,
user_agent='python-watcherclient')
else:
return HTTPClient(*args, **kwargs)

View File

@@ -1,640 +0,0 @@
# Copyright 2012 OpenStack LLC.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import copy
from distutils import version
import functools
import hashlib
import http.client
import io
import logging
import os
import socket
import ssl
import textwrap
import time
from urllib import parse as urlparse
from keystoneauth1 import adapter
from keystoneauth1 import exceptions as kexceptions
from oslo_serialization import jsonutils
from oslo_utils import strutils
import requests
from watcherclient._i18n import _
from watcherclient.common import api_versioning
from watcherclient import exceptions
# Record the latest version that this client was tested with.
DEFAULT_VER = '1.latest'
# Minor version 4 for adding webhook API
LAST_KNOWN_API_VERSION = 4
LATEST_VERSION = '1.{}'.format(LAST_KNOWN_API_VERSION)
LOG = logging.getLogger(__name__)
USER_AGENT = 'python-watcherclient'
CHUNKSIZE = 1024 * 64 # 64kB
API_VERSION = '/v1'
API_VERSION_SELECTED_STATES = ('user', 'negotiated', 'cached', 'default')
DEFAULT_MAX_RETRIES = 5
DEFAULT_RETRY_INTERVAL = 2
SENSITIVE_HEADERS = ('X-Auth-Token',)
SUPPORTED_ENDPOINT_SCHEME = ('http', 'https')
def _trim_endpoint_api_version(url):
"""Trim API version and trailing slash from endpoint."""
return url.rstrip('/').rstrip(API_VERSION)
def _extract_error_json(body):
"""Return error_message from the HTTP response body."""
error_json = {}
try:
body_json = jsonutils.loads(body)
if 'error_message' in body_json:
raw_msg = body_json['error_message']
error_json = jsonutils.loads(raw_msg)
except ValueError:
pass
return error_json
def get_server(endpoint):
"""Extract and return the server & port that we're connecting to."""
if endpoint is None:
return None, None
parts = urlparse.urlparse(endpoint)
return parts.hostname, str(parts.port)
class VersionNegotiationMixin(object):
def negotiate_version(self, conn, resp):
"""Negotiate the server version
Assumption: Called after receiving a 406 error when doing a request.
param conn: A connection object
param resp: The response object from http request
"""
if self.api_version_select_state not in API_VERSION_SELECTED_STATES:
raise RuntimeError(
_('Error: self.api_version_select_state should be one of the '
'values in: "%(valid)s" but had the value: "%(value)s"') %
{'valid': ', '.join(API_VERSION_SELECTED_STATES),
'value': self.api_version_select_state})
min_ver, max_ver = self._parse_version_headers(resp)
# If the user requested an explicit version or we have negotiated a
# version and still failing then error now. The server could
# support the version requested but the requested operation may not
# be supported by the requested version.
if self.api_version_select_state == 'user':
raise exceptions.UnsupportedVersion(textwrap.fill(
_("Requested API version %(req)s is not supported by the "
"server or the requested operation is not supported by the "
"requested version. Supported version range is %(min)s to "
"%(max)s")
% {'req': self.os_infra_optim_api_version,
'min': min_ver, 'max': max_ver}))
if self.api_version_select_state == 'negotiated':
raise exceptions.UnsupportedVersion(textwrap.fill(
_("No API version was specified and the requested operation "
"was not supported by the client's negotiated API version "
"%(req)s. Supported version range is: %(min)s to %(max)s")
% {'req': self.os_infra_optim_api_version,
'min': min_ver, 'max': max_ver}))
negotiated_ver = str(
min(version.StrictVersion(self.os_infra_optim_api_version),
version.StrictVersion(max_ver)))
if negotiated_ver < min_ver:
negotiated_ver = min_ver
# server handles microversions, but doesn't support
# the requested version, so try a negotiated version
self.api_version_select_state = 'negotiated'
self.os_infra_optim_api_version = negotiated_ver
LOG.debug('Negotiated API version is %s', negotiated_ver)
return negotiated_ver
def _generic_parse_version_headers(self, accessor_func):
min_ver = accessor_func('OpenStack-API-Minimum-Version',
None)
max_ver = accessor_func('OpenStack-API-Maximum-Version',
None)
return min_ver, max_ver
def _parse_version_headers(self, accessor_func):
# NOTE(jlvillal): Declared for unit testing purposes
raise NotImplementedError()
def _make_simple_request(self, conn, method, url):
# NOTE(jlvillal): Declared for unit testing purposes
raise NotImplementedError()
_RETRY_EXCEPTIONS = (exceptions.ServiceUnavailable,
exceptions.ConnectionRefused,
kexceptions.RetriableConnectionFailure)
def with_retries(func):
"""Wrapper for _http_request adding support for retries."""
@functools.wraps(func)
def wrapper(self, url, method, **kwargs):
if self.conflict_max_retries is None:
self.conflict_max_retries = DEFAULT_MAX_RETRIES
if self.conflict_retry_interval is None:
self.conflict_retry_interval = DEFAULT_RETRY_INTERVAL
num_attempts = self.conflict_max_retries + 1
for attempt in range(1, num_attempts + 1):
try:
return func(self, url, method, **kwargs)
except _RETRY_EXCEPTIONS as error:
msg = ("Error contacting Watcher server: %(error)s. "
"Attempt %(attempt)d of %(total)d" %
{'attempt': attempt,
'total': num_attempts,
'error': error})
if attempt == num_attempts:
LOG.error(msg)
raise
else:
LOG.debug(msg)
time.sleep(self.conflict_retry_interval)
return wrapper
class HTTPClient(VersionNegotiationMixin):
def __init__(self, endpoint, **kwargs):
self.endpoint = endpoint
self.endpoint_trimmed = _trim_endpoint_api_version(endpoint)
self.auth_token = kwargs.get('token')
self.auth_ref = kwargs.get('auth_ref')
self.os_infra_optim_api_version = kwargs.get(
'os_infra_optim_api_version', DEFAULT_VER)
self.api_version_select_state = kwargs.get(
'api_version_select_state', 'default')
self.conflict_max_retries = kwargs.pop('max_retries',
DEFAULT_MAX_RETRIES)
self.conflict_retry_interval = kwargs.pop('retry_interval',
DEFAULT_RETRY_INTERVAL)
self.session = requests.Session()
parts = urlparse.urlparse(endpoint)
if parts.scheme not in SUPPORTED_ENDPOINT_SCHEME:
msg = _('Unsupported scheme: %s') % parts.scheme
raise exceptions.EndpointException(msg)
if parts.scheme == 'https':
if kwargs.get('insecure') is True:
self.session.verify = False
elif kwargs.get('ca_file'):
self.session.verify = kwargs['ca_file']
self.session.cert = (kwargs.get('cert_file'),
kwargs.get('key_file'))
def _process_header(self, name, value):
"""Redacts any sensitive header
Redact a header that contains sensitive information, by returning an
updated header with the sha1 hash of that value. The redacted value is
prefixed by '{SHA1}' because that's the convention used within
OpenStack.
:returns: A tuple of (name, value)
name: the safe encoding format of name
value: the redacted value if name is x-auth-token,
or the safe encoding format of name
"""
if name in SENSITIVE_HEADERS:
v = value.encode('utf-8')
h = hashlib.sha1(v)
d = h.hexdigest()
return (name, "{SHA1}%s" % d)
else:
return (name, value)
def log_curl_request(self, method, url, kwargs):
curl = ['curl -i -X %s' % method]
for (key, value) in kwargs['headers'].items():
header = '-H \'%s: %s\'' % self._process_header(key, value)
curl.append(header)
if not self.session.verify:
curl.append('-k')
elif isinstance(self.session.verify, str):
curl.append('--cacert %s' % self.session.verify)
if self.session.cert:
curl.append('--cert %s' % self.session.cert[0])
curl.append('--key %s' % self.session.cert[1])
if 'body' in kwargs:
body = strutils.mask_password(kwargs['body'])
curl.append('-d \'%s\'' % body)
curl.append(urlparse.urljoin(self.endpoint_trimmed, url))
LOG.debug(' '.join(curl))
@staticmethod
def log_http_response(resp, body=None):
# NOTE(aarefiev): resp.raw is urllib3 response object, it's used
# only to get 'version', response from request with 'stream = True'
# should be used for raw reading.
status = (resp.raw.version / 10.0, resp.status_code, resp.reason)
dump = ['\nHTTP/%.1f %s %s' % status]
dump.extend(['%s: %s' % (k, v) for k, v in resp.headers.items()])
dump.append('')
if body:
body = strutils.mask_password(body)
dump.extend([body, ''])
LOG.debug('\n'.join(dump))
def _make_connection_url(self, url):
return '%s/%s' % (self.endpoint_trimmed.rstrip('/'), url.lstrip('/'))
def _parse_version_headers(self, resp):
return self._generic_parse_version_headers(resp.headers.get)
def _make_simple_request(self, conn, method, url):
return conn.request(method, self._make_connection_url(url))
@with_retries
def _http_request(self, url, method, **kwargs):
"""Send an http request with the specified characteristics.
Wrapper around request.Session.request to handle tasks such
as setting headers and error handling.
"""
# Copy the kwargs so we can reuse the original in case of redirects
kwargs['headers'] = copy.deepcopy(kwargs.get('headers', {}))
kwargs['headers'].setdefault('User-Agent', USER_AGENT)
if self.os_infra_optim_api_version:
api_version = api_versioning.get_api_version(
self.os_infra_optim_api_version)
if api_version.is_latest():
api_version = api_versioning.get_api_version(
LATEST_VERSION)
kwargs['headers'].setdefault(
'OpenStack-API-Version',
' '.join(['infra-optim', api_version.get_string()]))
if self.auth_token:
kwargs['headers'].setdefault('X-Auth-Token', self.auth_token)
self.log_curl_request(method, url, kwargs)
# NOTE(aarefiev): This is for backwards compatibility, request
# expected body in 'data' field, previously we used httplib,
# which expected 'body' field.
body = kwargs.pop('body', None)
if body:
kwargs['data'] = body
conn_url = self._make_connection_url(url)
try:
resp = self.session.request(method,
conn_url,
**kwargs)
# TODO(deva): implement graceful client downgrade when connecting
# to servers that did not support microversions. Details here:
# http://specs.openstack.org/openstack/watcher-specs/specs/kilo/api-microversions.html#use-case-3b-new-client-communicating-with-a-old-watcher-user-specified # noqa
if resp.status_code == http.client.NOT_ACCEPTABLE:
negotiated_ver = self.negotiate_version(self.session, resp)
kwargs['headers']['OpenStack-API-Version'] = (
' '.join(['infra-optim', negotiated_ver]))
return self._http_request(url, method, **kwargs)
except requests.exceptions.RequestException as e:
message = (_("Error has occurred while handling "
"request for %(url)s: %(e)s") %
dict(url=conn_url, e=e))
# NOTE(aarefiev): not valid request(invalid url, missing schema,
# and so on), retrying is not needed.
if isinstance(e, ValueError):
raise exceptions.ValidationError(message)
raise exceptions.ConnectionRefused(message)
body_iter = resp.iter_content(chunk_size=CHUNKSIZE)
# Read body into string if it isn't obviously image data
body_str = None
if resp.headers.get('Content-Type') != 'application/octet-stream':
# decoding byte to string is necessary for Python 3 compatibility
# this issues has not been found with Python 3 unit tests
# because the test creates a fake http response of type str
# the if statement satisfies test (str) and real (bytes) behavior
body_list = [
chunk.decode("utf-8") if isinstance(chunk, bytes)
else chunk for chunk in body_iter
]
body_str = ''.join(body_list)
self.log_http_response(resp, body_str)
body_iter = io.StringIO(body_str)
else:
self.log_http_response(resp)
if resp.status_code >= http.client.BAD_REQUEST:
error_json = _extract_error_json(body_str)
raise exceptions.from_response(
resp, error_json.get('faultstring'),
error_json.get('debuginfo'), method, url)
elif resp.status_code in (http.client.MOVED_PERMANENTLY,
http.client.FOUND,
http.client.USE_PROXY):
# Redirected. Reissue the request to the new location.
return self._http_request(resp['location'], method, **kwargs)
elif resp.status_code == http.client.MULTIPLE_CHOICES:
raise exceptions.from_response(resp, method=method, url=url)
return resp, body_iter
def json_request(self, method, url, **kwargs):
kwargs.setdefault('headers', {})
kwargs['headers'].setdefault('Content-Type', 'application/json')
kwargs['headers'].setdefault('Accept', 'application/json')
if 'body' in kwargs:
kwargs['body'] = jsonutils.dumps(kwargs['body'])
resp, body_iter = self._http_request(url, method, **kwargs)
content_type = resp.headers.get('Content-Type')
if (resp.status_code in (http.client.NO_CONTENT,
http.client.RESET_CONTENT) or
content_type is None):
return resp, list()
if 'application/json' in content_type:
body = ''.join([chunk for chunk in body_iter])
try:
body = jsonutils.loads(body)
except ValueError:
LOG.error('Could not decode response body as JSON')
else:
body = None
return resp, body
def raw_request(self, method, url, **kwargs):
kwargs.setdefault('headers', {})
kwargs['headers'].setdefault('Content-Type',
'application/octet-stream')
return self._http_request(url, method, **kwargs)
class VerifiedHTTPSConnection(http.client.HTTPSConnection):
"""httplib-compatible connection using client-side SSL authentication
:see http://code.activestate.com/recipes/
577548-https-httplib-client-connection-with-certificate-v/
"""
def __init__(self, host, port, key_file=None, cert_file=None,
ca_file=None, timeout=None, insecure=False):
super(VerifiedHTTPSConnection, self).__init__(
self, host, port, key_file=key_file, cert_file=cert_file)
self.key_file = key_file
self.cert_file = cert_file
if ca_file is not None:
self.ca_file = ca_file
else:
self.ca_file = self.get_system_ca_file()
self.timeout = timeout
self.insecure = insecure
def connect(self):
"""Connect to a host on a given (SSL) port.
If ca_file is pointing somewhere, use it to check Server Certificate.
Redefined/copied and extended from httplib.py:1105 (Python 2.6.x).
This is needed to pass cert_reqs=ssl.CERT_REQUIRED as parameter to
ssl.wrap_socket(), which forces SSL to check server certificate against
our client certificate.
"""
sock = socket.create_connection((self.host, self.port), self.timeout)
if self._tunnel_host:
self.sock = sock
self._tunnel()
if self.insecure is True:
kwargs = {'cert_reqs': ssl.CERT_NONE}
else:
kwargs = {'cert_reqs': ssl.CERT_REQUIRED, 'ca_certs': self.ca_file}
if self.cert_file:
kwargs['certfile'] = self.cert_file
if self.key_file:
kwargs['keyfile'] = self.key_file
self.sock = ssl.wrap_socket(sock, **kwargs)
@staticmethod
def get_system_ca_file():
"""Return path to system default CA file."""
# Standard CA file locations for Debian/Ubuntu, RedHat/Fedora,
# Suse, FreeBSD/OpenBSD
ca_path = ['/etc/ssl/certs/ca-certificates.crt',
'/etc/pki/tls/certs/ca-bundle.crt',
'/etc/ssl/ca-bundle.pem',
'/etc/ssl/cert.pem']
for ca in ca_path:
if os.path.exists(ca):
return ca
return None
class SessionClient(VersionNegotiationMixin, adapter.LegacyJsonAdapter):
"""HTTP client based on Keystone client session."""
def __init__(self,
os_infra_optim_api_version,
api_version_select_state,
max_retries,
retry_interval,
endpoint,
**kwargs):
self.os_infra_optim_api_version = os_infra_optim_api_version
self.api_version_select_state = api_version_select_state
self.conflict_max_retries = max_retries
self.conflict_retry_interval = retry_interval
self.endpoint = endpoint
super(SessionClient, self).__init__(**kwargs)
def _parse_version_headers(self, resp):
return self._generic_parse_version_headers(resp.headers.get)
def _make_simple_request(self, conn, method, url):
# NOTE: conn is self.session for this class
return conn.request(url, method, raise_exc=False)
@with_retries
def _http_request(self, url, method, **kwargs):
kwargs.setdefault('user_agent', USER_AGENT)
kwargs.setdefault('auth', self.auth)
if isinstance(self.endpoint_override, str):
kwargs.setdefault(
'endpoint_override',
_trim_endpoint_api_version(self.endpoint_override)
)
if getattr(self, 'os_infra_optim_api_version', None):
api_version = api_versioning.get_api_version(
self.os_infra_optim_api_version)
if api_version.is_latest():
api_version = api_versioning.get_api_version(
LATEST_VERSION)
kwargs['headers'].setdefault(
'OpenStack-API-Version',
' '.join(['infra-optim',
api_version.get_string()]))
endpoint_filter = kwargs.setdefault('endpoint_filter', {})
endpoint_filter.setdefault('interface', self.interface)
endpoint_filter.setdefault('service_type', self.service_type)
endpoint_filter.setdefault('region_name', self.region_name)
resp = self.session.request(url, method,
raise_exc=False, **kwargs)
if resp.status_code == http.client.NOT_ACCEPTABLE:
negotiated_ver = self.negotiate_version(self.session, resp)
kwargs['headers']['OpenStack-API-Version'] = (
' '.join(['infra-optim', negotiated_ver]))
return self._http_request(url, method, **kwargs)
if resp.status_code >= http.client.BAD_REQUEST:
error_json = _extract_error_json(resp.content)
raise exceptions.from_response(
resp, error_json.get('faultstring'),
error_json.get('debuginfo'), method, url)
elif resp.status_code in (http.client.MOVED_PERMANENTLY,
http.client.FOUND, http.client.USE_PROXY):
# Redirected. Reissue the request to the new location.
location = resp.headers.get('location')
resp = self._http_request(location, method, **kwargs)
elif resp.status_code == http.client.MULTIPLE_CHOICES:
raise exceptions.from_response(resp, method=method, url=url)
return resp
def json_request(self, method, url, **kwargs):
kwargs.setdefault('headers', {})
kwargs['headers'].setdefault('Content-Type', 'application/json')
kwargs['headers'].setdefault('Accept', 'application/json')
if 'body' in kwargs:
kwargs['data'] = jsonutils.dumps(kwargs.pop('body'))
resp = self._http_request(url, method, **kwargs)
body = resp.content
content_type = resp.headers.get('content-type', None)
status = resp.status_code
if (status in (http.client.NO_CONTENT, http.client.RESET_CONTENT) or
content_type is None):
return resp, list()
if 'application/json' in content_type:
try:
body = resp.json()
except ValueError:
LOG.error('Could not decode response body as JSON')
else:
body = None
return resp, body
def raw_request(self, method, url, **kwargs):
kwargs.setdefault('headers', {})
kwargs['headers'].setdefault('Content-Type',
'application/octet-stream')
return self._http_request(url, method, **kwargs)
def _construct_http_client(endpoint=None,
session=None,
token=None,
auth_ref=None,
os_infra_optim_api_version=DEFAULT_VER,
api_version_select_state='default',
max_retries=DEFAULT_MAX_RETRIES,
retry_interval=DEFAULT_RETRY_INTERVAL,
timeout=600,
ca_file=None,
cert_file=None,
key_file=None,
insecure=None,
**kwargs):
if session:
kwargs.setdefault('service_type', 'infra-optim')
kwargs.setdefault('user_agent', 'python-watcherclient')
kwargs.setdefault('interface', kwargs.pop('endpoint_type', None))
kwargs.setdefault('endpoint_override', endpoint)
ignored = {'token': token,
'auth_ref': auth_ref,
'timeout': timeout != 600,
'ca_file': ca_file,
'cert_file': cert_file,
'key_file': key_file,
'insecure': insecure}
dvars = [k for k, v in ignored.items() if v]
if dvars:
LOG.warning('The following arguments are ignored when using '
'the session to construct a client: %s',
', '.join(dvars))
return SessionClient(
session=session,
os_infra_optim_api_version=os_infra_optim_api_version,
api_version_select_state=api_version_select_state,
max_retries=max_retries,
retry_interval=retry_interval,
endpoint=endpoint,
**kwargs)
else:
if kwargs:
LOG.warning('The following arguments are being ignored when '
'constructing the client: %s', ', '.join(kwargs))
return HTTPClient(
endpoint=endpoint,
token=token,
auth_ref=auth_ref,
os_infra_optim_api_version=os_infra_optim_api_version,
api_version_select_state=api_version_select_state,
max_retries=max_retries,
retry_interval=retry_interval,
timeout=timeout,
ca_file=ca_file,
cert_file=cert_file,
key_file=key_file,
insecure=insecure)

View File

@@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
#
# Copyright 2012 OpenStack LLC.
# All Rights Reserved.
@@ -14,12 +15,12 @@
# License for the specific language governing permissions and limitations
# under the License.
import argparse
import os
import uuid
import yaml
from __future__ import print_function
import argparse
import json
import uuid
from oslo_serialization import jsonutils
from oslo_utils import importutils
from watcherclient._i18n import _
@@ -73,7 +74,7 @@ def import_versioned_module(version, submodule=None):
return importutils.import_module(module)
def split_and_deserialize(string, exclude_fields=[]):
def split_and_deserialize(string):
"""Split and try to JSON deserialize a string.
Gets a string with the KEY=VALUE format, split it (using '=' as the
@@ -86,11 +87,10 @@ def split_and_deserialize(string, exclude_fields=[]):
except ValueError:
raise exc.CommandError(_('Attributes must be a list of '
'PATH=VALUE not "%s"') % string)
if key not in exclude_fields:
try:
value = jsonutils.loads(value)
except ValueError:
pass
try:
value = json.loads(value)
except ValueError:
pass
return (key, value)
@@ -103,7 +103,7 @@ def args_array_to_dict(kwargs, key_to_convert):
return kwargs
def args_array_to_patch(op, attributes, exclude_fields=[]):
def args_array_to_patch(op, attributes):
patch = []
for attr in attributes:
# Sanitize
@@ -111,8 +111,7 @@ def args_array_to_patch(op, attributes, exclude_fields=[]):
attr = '/' + attr
if op in ['add', 'replace']:
path, value = split_and_deserialize(attr,
exclude_fields=exclude_fields)
path, value = split_and_deserialize(attr)
patch.append({'op': op, 'path': path, 'value': value})
elif op == "remove":
@@ -159,21 +158,17 @@ def common_params_for_list(args, fields, field_labels):
args.sort_dir)
params['sort_dir'] = args.sort_dir
marker = getattr(args, 'marker', None)
if marker is not None:
params['marker'] = marker
params['detail'] = args.detail
return params
def common_filters(limit=None, sort_key=None, sort_dir=None, marker=None):
def common_filters(limit=None, sort_key=None, sort_dir=None):
"""Generate common filters for any list request.
:param limit: maximum number of entities to return.
:param sort_key: field to use for sorting.
:param sort_dir: direction of sorting: 'asc' or 'desc'.
:param marker: The last actionplan UUID of the previous page.
:returns: list of string filters.
"""
filters = []
@@ -183,8 +178,6 @@ def common_filters(limit=None, sort_key=None, sort_dir=None, marker=None):
filters.append('sort_key=%s' % sort_key)
if sort_dir is not None:
filters.append('sort_dir=%s' % sort_dir)
if marker is not None:
filters.append('marker=%s' % marker)
return filters
@@ -199,10 +192,3 @@ def is_uuid_like(val):
return str(uuid.UUID(val)) == val
except (TypeError, ValueError, AttributeError):
return False
def serialize_file_to_dict(filename):
filename = os.path.expanduser(filename)
with open(filename, "rb") as stream:
scope = yaml.safe_load(stream.read())
return scope

View File

@@ -1,3 +1,4 @@
# -*- 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
@@ -52,26 +53,11 @@ A generic error message, given when no more specific message is suitable.
An alias of :py:exc:`watcherclient.common.apiclient.ValidationError`
"""
Conflict = exceptions.Conflict
ConnectionRefused = exceptions.ConnectionRefused
EndpointException = exceptions.EndpointException
EndpointNotFound = exceptions.EndpointNotFound
ServiceUnavailable = exceptions.ServiceUnavailable
class UnsupportedVersion(Exception):
"""Unsupported API Version
Indicates that the user is trying to use an unsupported version of the API.
"""
pass
class AmbiguousAuthSystem(exceptions.ClientException):
"""Could not obtain token and endpoint using provided credentials."""
pass
# Alias for backwards compatibility
AmbigiousAuthSystem = AmbiguousAuthSystem
@@ -100,10 +86,11 @@ def from_response(response, message=None, traceback=None, method=None,
'Content-Type': response.getheader('content-type', "")}
if hasattr(response, 'status_code'):
# NOTE(hongbin): This allows SessionClient to handle faultstring.
# NOTE(jiangfei): These modifications allow SessionClient
# to handle faultstring.
response.json = lambda: {'error': error_body}
if (response.headers.get('Content-Type', '').startswith('text/') and
if (response.headers['Content-Type'].startswith('text/') and
not hasattr(response, 'text')):
# NOTE(clif_h): There seems to be a case in the
# common.apiclient.exceptions module where if the
@@ -113,4 +100,4 @@ def from_response(response, message=None, traceback=None, method=None,
# This is to work around that problem.
response.text = ''
return exceptions.from_response(response, method, url)
return exceptions.from_response(response, message, url)

View File

@@ -1,90 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import logging
from osc_lib import utils
import watcherclient
from watcherclient.common import api_versioning
from watcherclient.common import httpclient
from watcherclient import exceptions
LOG = logging.getLogger(__name__)
DEFAULT_API_VERSION = httpclient.LATEST_VERSION
API_VERSION_OPTION = 'os_infra_optim_api_version'
API_NAME = 'infra-optim'
API_VERSIONS = {
'1': 'watcherclient.v1.client.Client',
}
def make_client(instance):
"""Returns an infra-optim service client."""
version = api_versioning.APIVersion(instance._api_version[API_NAME])
infraoptim_client_class = utils.get_client_class(
API_NAME,
version.ver_major,
API_VERSIONS)
LOG.debug('Instantiating infraoptim client: %s', infraoptim_client_class)
client = infraoptim_client_class(
os_infra_optim_api_version=instance._api_version[API_NAME],
session=instance.session,
region_name=instance._region_name,
)
return client
def build_option_parser(parser):
"""Hook to add global options."""
parser.add_argument('--os-infra-optim-api-version',
metavar='<infra-optim-api-version>',
default=utils.env(
'OS_INFRA_OPTIM_API_VERSION',
default=DEFAULT_API_VERSION),
help=('Watcher API version, default=' +
DEFAULT_API_VERSION +
' (Env: OS_INFRA_OPTIM_API_VERSION)'))
return parser
def check_api_version(check_version):
"""Validate version supplied by user
Returns:
* True if version is OK
* False if the version has not been checked and the previous plugin
check should be performed
* throws an exception if the version is no good
"""
infra_api_version = api_versioning.get_api_version(check_version)
# Bypass X.latest format microversion
if not infra_api_version.is_latest():
if infra_api_version > api_versioning.APIVersion("2.0"):
if not infra_api_version.matches(
watcherclient.API_MIN_VERSION,
watcherclient.API_MAX_VERSION,
):
msg = "versions supported by client: %(min)s - %(max)s" % {
"min": watcherclient.API_MIN_VERSION.get_string(),
"max": watcherclient.API_MAX_VERSION.get_string(),
}
raise exceptions.CommandError(msg)
return True

71
watcherclient/plugin.py Normal file
View File

@@ -0,0 +1,71 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import logging
from osc_lib import utils
LOG = logging.getLogger(__name__)
DEFAULT_API_VERSION = '1'
API_VERSION_OPTION = 'os_infra_optim_api_version'
API_NAME = 'infra-optim'
API_VERSIONS = {
'1': 'watcherclient.v1.client.Client',
}
def make_client(instance):
"""Returns an infra-optim service client"""
watcher_client = utils.get_client_class(
API_NAME,
instance._api_version[API_NAME],
API_VERSIONS)
LOG.debug('Instantiating infra-optim client: %s', watcher_client)
endpoint = instance.get_endpoint_for_service_type(
API_NAME,
region_name=instance._region_name,
interface=instance._interface,
)
auth_url = instance._auth_url \
if hasattr(instance, '_auth_url') else instance.auth.auth_url
username = instance._username \
if hasattr(instance, '_username') else instance.auth._username
password = instance._password \
if hasattr(instance, '_password') else instance.auth._password
client = watcher_client(
endpoint=endpoint,
session=instance.session,
auth_url=auth_url,
username=username,
password=password,
region_name=instance._region_name,
)
return client
def build_option_parser(parser):
"""Hook to add global options."""
parser.add_argument('--os-infra-optim-api-version',
metavar='<infra-optim-api-version>',
default=utils.env(
'OS_INFRA_OPTIM_API_VERSION',
default=DEFAULT_API_VERSION),
help=('Watcher API version, default=' +
DEFAULT_API_VERSION +
' (Env: OS_INFRA_OPTIM_API_VERSION)'))
return parser

View File

@@ -25,11 +25,17 @@ from cliff import command
from cliff import commandmanager
from cliff import complete
from cliff import help as cli_help
from keystoneauth1 import loading
from keystoneclient.auth.identity import v2
from keystoneclient.auth.identity import v3
from keystoneclient import discover
from keystoneclient import exceptions as ks_exc
from keystoneclient import session
from osc_lib import logs
from osc_lib import utils
import six.moves.urllib.parse as urlparse
from watcherclient import client as watcherclient
from watcherclient._i18n import _
from watcherclient import exceptions as exc
from watcherclient import version
LOG = logging.getLogger(__name__)
@@ -39,7 +45,6 @@ API_NAME = 'infra-optim'
API_VERSIONS = {
'1': 'watcherclient.v1.client.Client',
}
DEFAULT_OS_INFRA_OPTIM_API_VERSION = '1.latest'
_DEFAULT_IDENTITY_API_VERSION = '3'
_IDENTITY_API_VERSION_2 = ['2', '2.0']
_IDENTITY_API_VERSION_3 = ['3']
@@ -48,6 +53,8 @@ _IDENTITY_API_VERSION_3 = ['3']
class WatcherShell(app.App):
"""Watcher command line interface."""
log = logging.getLogger(__name__)
def __init__(self, **kwargs):
self.client = None
@@ -68,9 +75,145 @@ class WatcherShell(app.App):
)
def create_client(self, args):
client = watcherclient.get_client('1', **args.__dict__)
service_type = 'infra-optim'
project_id = args.os_project_id or args.os_tenant_id
project_name = args.os_project_name or args.os_tenant_name
keystone_session = session.Session.load_from_cli_options(args)
kwargs = {
'username': args.os_username,
'user_domain_id': args.os_user_domain_id,
'user_domain_name': args.os_user_domain_name,
'password': args.os_password,
'auth_token': args.os_auth_token,
'project_id': project_id,
'project_name': project_name,
'project_domain_id': args.os_project_domain_id,
'project_domain_name': args.os_project_domain_name,
}
keystone_auth = self._get_keystone_auth(keystone_session,
args.os_auth_url,
**kwargs)
region_name = args.os_region_name
endpoint = keystone_auth.get_endpoint(keystone_session,
service_type=service_type,
region_name=region_name)
endpoint_type = args.os_endpoint_type or 'publicURL'
kwargs = {
'auth_url': args.os_auth_url,
'session': keystone_session,
'auth': keystone_auth,
'service_type': service_type,
'endpoint_type': endpoint_type,
'region_name': args.os_region_name,
'username': args.os_username,
'password': args.os_password,
}
watcher_client = utils.get_client_class(
API_NAME,
args.watcher_api_version or 1,
API_VERSIONS)
LOG.debug('Instantiating infra-optim client: %s', watcher_client)
client = watcher_client(args.watcher_api_version, endpoint, **kwargs)
return client
def _discover_auth_versions(self, session, auth_url):
# discover the API versions the server is supporting base on the
# given URL
v2_auth_url = None
v3_auth_url = None
try:
ks_discover = discover.Discover(session=session, auth_url=auth_url)
v2_auth_url = ks_discover.url_for('2.0')
v3_auth_url = ks_discover.url_for('3.0')
except ks_exc.ClientException:
# Identity service may not support discover API version.
# Let's try to figure out the API version from the original URL.
url_parts = urlparse.urlparse(auth_url)
(scheme, netloc, path, params, query, fragment) = url_parts
path = path.lower()
if path.startswith('/v3'):
v3_auth_url = auth_url
elif path.startswith('/v2'):
v2_auth_url = auth_url
else:
# not enough information to determine the auth version
msg = _('Unable to determine the Keystone version '
'to authenticate with using the given '
'auth_url. Identity service may not support API '
'version discovery. Please provide a versioned '
'auth_url instead. %s') % auth_url
raise exc.CommandError(msg)
return (v2_auth_url, v3_auth_url)
def _get_keystone_v3_auth(self, v3_auth_url, **kwargs):
auth_token = kwargs.pop('auth_token', None)
if auth_token:
return v3.Token(v3_auth_url, auth_token)
else:
return v3.Password(v3_auth_url, **kwargs)
def _get_keystone_v2_auth(self, v2_auth_url, **kwargs):
auth_token = kwargs.pop('auth_token', None)
if auth_token:
return v2.Token(
v2_auth_url,
auth_token,
tenant_id=kwargs.pop('project_id', None),
tenant_name=kwargs.pop('project_name', None))
else:
return v2.Password(
v2_auth_url,
username=kwargs.pop('username', None),
password=kwargs.pop('password', None),
tenant_id=kwargs.pop('project_id', None),
tenant_name=kwargs.pop('project_name', None))
def _get_keystone_auth(self, session, auth_url, **kwargs):
# FIXME(dhu): this code should come from keystoneclient
# discover the supported keystone versions using the given url
(v2_auth_url, v3_auth_url) = self._discover_auth_versions(
session=session,
auth_url=auth_url)
# Determine which authentication plugin to use. First inspect the
# auth_url to see the supported version. If both v3 and v2 are
# supported, then use the highest version if possible.
auth = None
if v3_auth_url and v2_auth_url:
user_domain_name = kwargs.get('user_domain_name', None)
user_domain_id = kwargs.get('user_domain_id', None)
project_domain_name = kwargs.get('project_domain_name', None)
project_domain_id = kwargs.get('project_domain_id', None)
# support both v2 and v3 auth. Use v3 if domain information is
# provided.
if (user_domain_name or user_domain_id or project_domain_name or
project_domain_id):
auth = self._get_keystone_v3_auth(v3_auth_url, **kwargs)
else:
auth = self._get_keystone_v2_auth(v2_auth_url, **kwargs)
elif v3_auth_url:
# support only v3
auth = self._get_keystone_v3_auth(v3_auth_url, **kwargs)
elif v2_auth_url:
# support only v2
auth = self._get_keystone_v2_auth(v2_auth_url, **kwargs)
else:
raise exc.CommandError(
_('Unable to determine the Keystone version '
'to authenticate with using the given '
'auth_url.'))
return auth
def build_option_parser(self, description, version, argparse_kwargs=None):
"""Introduces global arguments for the application.
@@ -148,25 +291,17 @@ class WatcherShell(app.App):
metavar='<auth-token>',
default=utils.env('OS_AUTH_TOKEN'),
help='Defaults to env[OS_AUTH_TOKEN].')
parser.add_argument(
'--os-infra-optim-api-version',
metavar='<infra-optim-api-version>',
default=utils.env('OS_INFRA_OPTIM_API_VERSION',
default=DEFAULT_OS_INFRA_OPTIM_API_VERSION),
help='Accepts X, X.Y (where X is major and Y is minor part) or '
'"X.latest", defaults to env[OS_INFRA_OPTIM_API_VERSION].')
parser.add_argument('--watcher-api-version',
metavar='<watcher-api-version>',
default=utils.env('WATCHER_API_VERSION'),
help='Defaults to env[WATCHER_API_VERSION].')
parser.add_argument('--os-endpoint-type',
default=utils.env('OS_ENDPOINT_TYPE'),
help='Defaults to env[OS_ENDPOINT_TYPE] or '
'"publicURL"')
parser.add_argument('--os-endpoint-override',
metavar='<endpoint-override>',
default=utils.env('OS_ENDPOINT_OVERRIDE'),
help="Use this API endpoint instead of the "
"Service Catalog.")
parser.epilog = ('See "watcher help COMMAND" for help '
'on a specific command.')
loading.register_session_argparse_arguments(parser)
session.Session.register_cli_options(parser)
return parser
def configure_logging(self):
@@ -195,18 +330,17 @@ class WatcherShell(app.App):
except Exception as e:
if not logging.getLogger('').handlers:
logging.basicConfig()
LOG.error('Exception raised: %s', str(e))
self.log.error('Exception raised: %s', str(e))
return ret_val
finally:
LOG.info("END return value: %s", ret_val)
self.log.info("END return value: %s", ret_val)
def main(argv=sys.argv[1:]):
watcher_app = WatcherShell()
return watcher_app.run(argv)
if __name__ == '__main__': # pragma: no cover
sys.exit(main(sys.argv[1:]))

View File

@@ -10,11 +10,11 @@
# License for the specific language governing permissions and limitations
# under the License.
from oslo_serialization import jsonutils
from oslo_utils import uuidutils
import json
import uuid
from keystoneauth1.fixture import v2 as ks_v2_fixture
from keystoneauth1.fixture import v3 as ks_v3_fixture
from keystoneclient.fixture import v2 as ks_v2_fixture
from keystoneclient.fixture import v3 as ks_v3_fixture
# these are copied from python-keystoneclient tests
BASE_HOST = 'http://keystone.example.com'
@@ -49,15 +49,15 @@ V3_VERSION = {'id': 'v3.0',
'status': 'stable',
'updated': UPDATED}
TOKENID = uuidutils.generate_uuid(dashed=False)
TOKENID = uuid.uuid4().hex
def _create_version_list(versions):
return jsonutils.dumps({'versions': {'values': versions}})
return json.dumps({'versions': {'values': versions}})
def _create_single_version(version):
return jsonutils.dumps({'version': version})
return json.dumps({'version': version})
V3_VERSION_LIST = _create_version_list([V3_VERSION, V2_VERSION])
@@ -74,8 +74,8 @@ def keystone_request_callback(request, uri, headers):
return (200, headers, V3_VERSION_LIST)
elif uri == BASE_URL + "/v2.0":
v2_token = ks_v2_fixture.Token(token_id)
return (200, response_headers, jsonutils.dumps(v2_token))
return (200, response_headers, json.dumps(v2_token))
elif uri == BASE_URL + "/v3":
v3_token = ks_v3_fixture.Token()
response_headers["X-Subject-Token"] = token_id
return (201, response_headers, jsonutils.dumps(v3_token))
return (201, response_headers, json.dumps(v3_token))

View File

@@ -0,0 +1,142 @@
# -*- 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 fixtures
from watcherclient.client import get_client
from watcherclient import exceptions as exc
from watcherclient.tests import utils
def fake_get_ksclient(**kwargs):
return utils.FakeKeystone('KSCLIENT_AUTH_TOKEN')
class ClientTest(utils.BaseTestCase):
def test_get_client_with_auth_token_watcher_url(self):
self.useFixture(fixtures.MonkeyPatch(
'watcherclient.client._get_ksclient', fake_get_ksclient))
kwargs = {
'watcher_url': 'http://watcher.example.org:6385/',
'os_auth_token': 'USER_AUTH_TOKEN',
}
client = get_client('1', **kwargs)
self.assertEqual('USER_AUTH_TOKEN', client.http_client.auth_token)
self.assertEqual('http://watcher.example.org:6385/',
client.http_client.endpoint)
def test_get_client_no_auth_token(self):
self.useFixture(fixtures.MonkeyPatch(
'watcherclient.client._get_ksclient', fake_get_ksclient))
kwargs = {
'os_tenant_name': 'TENANT_NAME',
'os_username': 'USERNAME',
'os_password': 'PASSWORD',
'os_auth_url': 'http://localhost:35357/v2.0',
'os_auth_token': '',
}
client = get_client('1', **kwargs)
self.assertEqual('KSCLIENT_AUTH_TOKEN', client.http_client.auth_token)
self.assertEqual('http://localhost:6385/v1/f14b41234',
client.http_client.endpoint)
self.assertEqual(fake_get_ksclient().auth_ref,
client.http_client.auth_ref)
def test_get_client_with_region_no_auth_token(self):
self.useFixture(fixtures.MonkeyPatch(
'watcherclient.client._get_ksclient', fake_get_ksclient))
kwargs = {
'os_tenant_name': 'TENANT_NAME',
'os_username': 'USERNAME',
'os_password': 'PASSWORD',
'os_region_name': 'REGIONONE',
'os_auth_url': 'http://localhost:35357/v2.0',
'os_auth_token': '',
}
client = get_client('1', **kwargs)
self.assertEqual('KSCLIENT_AUTH_TOKEN', client.http_client.auth_token)
self.assertEqual('http://regionhost:6385/v1/f14b41234',
client.http_client.endpoint)
self.assertEqual(fake_get_ksclient().auth_ref,
client.http_client.auth_ref)
def test_get_client_with_auth_token(self):
self.useFixture(fixtures.MonkeyPatch(
'watcherclient.client._get_ksclient', fake_get_ksclient))
kwargs = {
'os_tenant_name': 'TENANT_NAME',
'os_username': 'USERNAME',
'os_password': 'PASSWORD',
'os_auth_url': 'http://localhost:35357/v2.0',
'os_auth_token': 'USER_AUTH_TOKEN',
}
client = get_client('1', **kwargs)
self.assertEqual('USER_AUTH_TOKEN', client.http_client.auth_token)
self.assertEqual('http://localhost:6385/v1/f14b41234',
client.http_client.endpoint)
self.assertEqual(fake_get_ksclient().auth_ref,
client.http_client.auth_ref)
def test_get_client_with_region_name_auth_token(self):
self.useFixture(fixtures.MonkeyPatch(
'watcherclient.client._get_ksclient', fake_get_ksclient))
kwargs = {
'os_tenant_name': 'TENANT_NAME',
'os_username': 'USERNAME',
'os_password': 'PASSWORD',
'os_auth_url': 'http://localhost:35357/v2.0',
'os_region_name': 'REGIONONE',
'os_auth_token': 'USER_AUTH_TOKEN',
}
client = get_client('1', **kwargs)
self.assertEqual('USER_AUTH_TOKEN', client.http_client.auth_token)
self.assertEqual('http://regionhost:6385/v1/f14b41234',
client.http_client.endpoint)
self.assertEqual(fake_get_ksclient().auth_ref,
client.http_client.auth_ref)
def test_get_client_no_url_and_no_token(self):
self.useFixture(fixtures.MonkeyPatch(
'watcherclient.client._get_ksclient', fake_get_ksclient))
kwargs = {
'os_tenant_name': 'TENANT_NAME',
'os_username': 'USERNAME',
'os_password': 'PASSWORD',
'os_auth_url': '',
'os_auth_token': '',
}
self.assertRaises(exc.AmbiguousAuthSystem, get_client, '1', **kwargs)
# test the alias as well to ensure backwards compatibility
self.assertRaises(exc.AmbigiousAuthSystem, get_client, '1', **kwargs)
def test_ensure_auth_ref_propagated(self):
ksclient = fake_get_ksclient
self.useFixture(fixtures.MonkeyPatch(
'watcherclient.client._get_ksclient', ksclient))
kwargs = {
'os_tenant_name': 'TENANT_NAME',
'os_username': 'USERNAME',
'os_password': 'PASSWORD',
'os_auth_url': 'http://localhost:35357/v2.0',
'os_auth_token': '',
}
client = get_client('1', **kwargs)
self.assertEqual(ksclient().auth_ref, client.http_client.auth_ref)

View File

@@ -0,0 +1,283 @@
# -*- coding: utf-8 -*-
#
# Copyright 2012 OpenStack LLC.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import json
import six
from watcherclient.common import http
from watcherclient import exceptions as exc
from watcherclient.tests import utils
HTTP_CLASS = six.moves.http_client.HTTPConnection
HTTPS_CLASS = http.VerifiedHTTPSConnection
DEFAULT_TIMEOUT = 600
def _get_error_body(faultstring=None, debuginfo=None):
error_body = {
'faultstring': faultstring,
'debuginfo': debuginfo
}
raw_error_body = json.dumps(error_body)
body = {'error_message': raw_error_body}
raw_body = json.dumps(body)
return raw_body
class HttpClientTest(utils.BaseTestCase):
def test_url_generation_trailing_slash_in_base(self):
client = http.HTTPClient('http://localhost/')
url = client._make_connection_url('/v1/resources')
self.assertEqual('/v1/resources', url)
def test_url_generation_without_trailing_slash_in_base(self):
client = http.HTTPClient('http://localhost')
url = client._make_connection_url('/v1/resources')
self.assertEqual('/v1/resources', url)
def test_url_generation_prefix_slash_in_path(self):
client = http.HTTPClient('http://localhost/')
url = client._make_connection_url('/v1/resources')
self.assertEqual('/v1/resources', url)
def test_url_generation_without_prefix_slash_in_path(self):
client = http.HTTPClient('http://localhost')
url = client._make_connection_url('v1/resources')
self.assertEqual('/v1/resources', url)
def test_server_exception_empty_body(self):
error_body = _get_error_body()
fake_resp = utils.FakeResponse({'content-type': 'application/json'},
six.StringIO(error_body),
version=1,
status=500)
client = http.HTTPClient('http://localhost/')
client.get_connection = (
lambda *a, **kw: utils.FakeConnection(fake_resp))
error = self.assertRaises(exc.InternalServerError,
client.json_request,
'GET', '/v1/resources')
self.assertEqual('Internal Server Error (HTTP 500)', str(error))
def test_server_exception_msg_only(self):
error_msg = 'test error msg'
error_body = _get_error_body(error_msg)
fake_resp = utils.FakeResponse({'content-type': 'application/json'},
six.StringIO(error_body),
version=1,
status=500)
client = http.HTTPClient('http://localhost/')
client.get_connection = (
lambda *a, **kw: utils.FakeConnection(fake_resp))
error = self.assertRaises(exc.InternalServerError,
client.json_request,
'GET', '/v1/resources')
self.assertEqual(error_msg + ' (HTTP 500)', str(error))
def test_server_exception_msg_and_traceback(self):
error_msg = 'another test error'
error_trace = ("\"Traceback (most recent call last):\\n\\n "
"File \\\"/usr/local/lib/python2.7/...")
error_body = _get_error_body(error_msg, error_trace)
fake_resp = utils.FakeResponse({'content-type': 'application/json'},
six.StringIO(error_body),
version=1,
status=500)
client = http.HTTPClient('http://localhost/')
client.get_connection = (
lambda *a, **kw: utils.FakeConnection(fake_resp))
error = self.assertRaises(exc.InternalServerError,
client.json_request,
'GET', '/v1/resources')
self.assertEqual(
'%(error)s (HTTP 500)\n%(trace)s' % {'error': error_msg,
'trace': error_trace},
"%(error)s\n%(details)s" % {'error': str(error),
'details': str(error.details)})
def test_get_connection_params(self):
endpoint = 'http://watcher-host:6385'
expected = (HTTP_CLASS,
('watcher-host', 6385, ''),
{'timeout': DEFAULT_TIMEOUT})
params = http.HTTPClient.get_connection_params(endpoint)
self.assertEqual(expected, params)
def test_get_connection_params_with_trailing_slash(self):
endpoint = 'http://watcher-host:6385/'
expected = (HTTP_CLASS,
('watcher-host', 6385, ''),
{'timeout': DEFAULT_TIMEOUT})
params = http.HTTPClient.get_connection_params(endpoint)
self.assertEqual(expected, params)
def test_get_connection_params_with_ssl(self):
endpoint = 'https://watcher-host:6385'
expected = (HTTPS_CLASS,
('watcher-host', 6385, ''),
{
'timeout': DEFAULT_TIMEOUT,
'ca_file': None,
'cert_file': None,
'key_file': None,
'insecure': False,
})
params = http.HTTPClient.get_connection_params(endpoint)
self.assertEqual(expected, params)
def test_get_connection_params_with_ssl_params(self):
endpoint = 'https://watcher-host:6385'
ssl_args = {
'ca_file': '/path/to/ca_file',
'cert_file': '/path/to/cert_file',
'key_file': '/path/to/key_file',
'insecure': True,
}
expected_kwargs = {'timeout': DEFAULT_TIMEOUT}
expected_kwargs.update(ssl_args)
expected = (HTTPS_CLASS,
('watcher-host', 6385, ''),
expected_kwargs)
params = http.HTTPClient.get_connection_params(endpoint, **ssl_args)
self.assertEqual(expected, params)
def test_get_connection_params_with_timeout(self):
endpoint = 'http://watcher-host:6385'
expected = (HTTP_CLASS,
('watcher-host', 6385, ''),
{'timeout': 300.0})
params = http.HTTPClient.get_connection_params(endpoint, timeout=300)
self.assertEqual(expected, params)
def test_get_connection_params_with_version(self):
endpoint = 'http://watcher-host:6385/v1'
expected = (HTTP_CLASS,
('watcher-host', 6385, ''),
{'timeout': DEFAULT_TIMEOUT})
params = http.HTTPClient.get_connection_params(endpoint)
self.assertEqual(expected, params)
def test_get_connection_params_with_version_trailing_slash(self):
endpoint = 'http://watcher-host:6385/v1/'
expected = (HTTP_CLASS,
('watcher-host', 6385, ''),
{'timeout': DEFAULT_TIMEOUT})
params = http.HTTPClient.get_connection_params(endpoint)
self.assertEqual(expected, params)
def test_get_connection_params_with_subpath(self):
endpoint = 'http://watcher-host:6385/watcher'
expected = (HTTP_CLASS,
('watcher-host', 6385, '/watcher'),
{'timeout': DEFAULT_TIMEOUT})
params = http.HTTPClient.get_connection_params(endpoint)
self.assertEqual(expected, params)
def test_get_connection_params_with_subpath_trailing_slash(self):
endpoint = 'http://watcher-host:6385/watcher/'
expected = (HTTP_CLASS,
('watcher-host', 6385, '/watcher'),
{'timeout': DEFAULT_TIMEOUT})
params = http.HTTPClient.get_connection_params(endpoint)
self.assertEqual(expected, params)
def test_get_connection_params_with_subpath_version(self):
endpoint = 'http://watcher-host:6385/watcher/v1'
expected = (HTTP_CLASS,
('watcher-host', 6385, '/watcher'),
{'timeout': DEFAULT_TIMEOUT})
params = http.HTTPClient.get_connection_params(endpoint)
self.assertEqual(expected, params)
def test_get_connection_params_with_subpath_version_trailing_slash(self):
endpoint = 'http://watcher-host:6385/watcher/v1/'
expected = (HTTP_CLASS,
('watcher-host', 6385, '/watcher'),
{'timeout': DEFAULT_TIMEOUT})
params = http.HTTPClient.get_connection_params(endpoint)
self.assertEqual(expected, params)
def test_401_unauthorized_exception(self):
error_body = _get_error_body()
fake_resp = utils.FakeResponse({'content-type': 'text/plain'},
six.StringIO(error_body),
version=1,
status=401)
client = http.HTTPClient('http://localhost/')
client.get_connection = (
lambda *a, **kw: utils.FakeConnection(fake_resp))
self.assertRaises(exc.Unauthorized, client.json_request,
'GET', '/v1/resources')
class SessionClientTest(utils.BaseTestCase):
def test_server_exception_msg_and_traceback(self):
error_msg = 'another test error'
error_trace = ("\"Traceback (most recent call last):\\n\\n "
"File \\\"/usr/local/lib/python2.7/...")
error_body = _get_error_body(error_msg, error_trace)
fake_session = utils.FakeSession({'Content-Type': 'application/json'},
error_body,
500)
client = http.SessionClient(session=fake_session,
auth=None,
interface=None,
service_type='publicURL',
region_name='',
service_name=None)
error = self.assertRaises(exc.InternalServerError,
client.json_request,
'GET', '/v1/resources')
self.assertEqual(
'%(error)s (HTTP 500)\n%(trace)s' % {'error': error_msg,
'trace': error_trace},
"%(error)s\n%(details)s" % {'error': str(error),
'details': str(error.details)})
def test_server_exception_empty_body(self):
error_body = _get_error_body()
fake_session = utils.FakeSession({'Content-Type': 'application/json'},
error_body,
500)
client = http.SessionClient(session=fake_session,
auth=None,
interface=None,
service_type='publicURL',
region_name='',
service_name=None)
error = self.assertRaises(exc.InternalServerError,
client.json_request,
'GET', '/v1/resources')
self.assertEqual('Internal Server Error (HTTP 500)', str(error))

View File

@@ -1,3 +1,4 @@
# -*- 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
@@ -11,7 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
from watcherclient.tests.unit import utils
from watcherclient.tests import utils
module_str = 'watcherclient'

View File

@@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
#
# Copyright 2013 OpenStack LLC.
# All Rights Reserved.
@@ -14,11 +15,11 @@
# License for the specific language governing permissions and limitations
# under the License.
from unittest import mock
import mock
from watcherclient.common.apiclient import exceptions as exc
from watcherclient.common import utils
from watcherclient.tests.unit import utils as test_utils
from watcherclient.tests import utils as test_utils
class UtilsTest(test_utils.BaseTestCase):
@@ -97,8 +98,7 @@ class UtilsTest(test_utils.BaseTestCase):
class CommonParamsForListTest(test_utils.BaseTestCase):
def setUp(self):
super(CommonParamsForListTest, self).setUp()
self.args = mock.Mock(limit=None, marker=None,
sort_key=None, sort_dir=None)
self.args = mock.Mock(limit=None, sort_key=None, sort_dir=None)
self.args.detail = False
self.expected_params = {'detail': False}
@@ -118,13 +118,6 @@ class CommonParamsForListTest(test_utils.BaseTestCase):
utils.common_params_for_list,
self.args, [], [])
def test_marker(self):
self.args.marker = 'e420a881-d7df-4de2-bbf3-378cc13d9b3a'
self.expected_params.update(
{'marker': 'e420a881-d7df-4de2-bbf3-378cc13d9b3a'})
self.assertEqual(self.expected_params,
utils.common_params_for_list(self.args, [], []))
def test_sort_key_and_sort_dir(self):
self.args.sort_key = 'field'
self.args.sort_dir = 'desc'

Some files were not shown because too many files have changed in this diff Show More