Compare commits
320 Commits
0.25.1
...
victoria-e
| Author | SHA1 | Date | |
|---|---|---|---|
| f170e7715e | |||
| 7c56285603 | |||
|
|
80f728b14f | ||
|
|
2e97950670 | ||
|
|
21b106dc15 | ||
|
|
7213f7195e | ||
|
|
21f42884f1 | ||
|
|
4c57d61f8d | ||
|
|
e05b498a5f | ||
|
|
8d3ce6c58d | ||
|
|
bb7af48350 | ||
|
|
92c15fa38a | ||
|
|
d3cc035ff7 | ||
|
|
fb22d3e1f8 | ||
| ac0ccb67a6 | |||
|
|
5946fbb5b1 | ||
|
|
2830afdc75 | ||
|
|
1912b9aa82 | ||
|
|
1f9194ced5 | ||
|
|
9387369cd7 | ||
|
|
431319ee4d | ||
|
|
e3c903cfce | ||
|
|
e3f0a3d2b5 | ||
|
|
88351df1c2 | ||
|
|
df3ec7849e | ||
|
|
0c7e306642 | ||
|
|
db70063cee | ||
|
|
1d1f28a256 | ||
|
|
62fdb73dae | ||
|
|
297ca1dfc5 | ||
|
|
a8f919e1f7 | ||
|
|
ea74a22b49 | ||
|
|
3caa385548 | ||
|
|
0e9c827c76 | ||
|
|
dbbe7df1e2 | ||
|
|
be479feef9 | ||
|
|
02583f7469 | ||
|
|
1aebe93477 | ||
|
|
cde518be31 | ||
|
|
573ca6ed59 | ||
|
|
f062e60990 | ||
|
|
c0b2604e9b | ||
|
|
2cab116e30 | ||
|
|
bb5586b477 | ||
|
|
c30f095481 | ||
|
|
ffb71506aa | ||
|
|
32a8799516 | ||
|
|
b52df6899d | ||
|
|
103bb38475 | ||
|
|
76a01b0c7c | ||
|
|
939371d180 | ||
|
|
c62757b093 | ||
|
|
e2082907ff | ||
|
|
16ff74f54c | ||
|
|
de9b8d124b | ||
|
|
a25cd66052 | ||
|
|
9b1e234e36 | ||
|
|
512e43eb32 | ||
|
|
d0f2f10f5d | ||
|
|
8342e64051 | ||
|
|
2c29c49f24 | ||
|
|
a70ebd8a41 | ||
|
|
4db175f342 | ||
|
|
775199cb42 | ||
|
|
9d9c5fe3be | ||
|
|
e4ee4ff03c | ||
|
|
ca6190c3a1 | ||
|
|
74cbf1b17b | ||
|
|
4a1b6eaf81 | ||
|
|
1186c7c34f | ||
|
|
02498f85f6 | ||
|
|
9d1d5e2cae | ||
|
|
b0ac926ee6 | ||
|
|
3e734b177d | ||
|
|
2cfe7f9afc | ||
|
|
df57eb800d | ||
|
|
abea1a8815 | ||
|
|
ab22110f7b | ||
|
|
ec7e8ca803 | ||
|
|
bdd99d60f3 | ||
|
|
25ff2811d5 | ||
|
|
8ce78f8c1f | ||
|
|
ffc50c5001 | ||
|
|
6c66276040 | ||
|
|
c256ee2a21 | ||
|
|
7abd55a823 | ||
|
|
98e09044c1 | ||
|
|
6c8dfd366d | ||
|
|
112e372239 | ||
|
|
c5b4db7fc1 | ||
|
|
7c6b39eeba | ||
|
|
94f6864bf0 | ||
|
|
e391d53c2f | ||
|
|
b7928eb155 | ||
|
|
7b7d07fac1 | ||
|
|
946ef06dc6 | ||
|
|
69f0493968 | ||
|
|
8c89b3327b | ||
|
|
2e9de6868b | ||
|
|
db7a5e4bc9 | ||
|
|
825e5cb081 | ||
|
|
a8185c7067 | ||
|
|
4a0b0e76b4 | ||
|
|
1918d3f26b | ||
|
|
2ca6b6b421 | ||
|
|
1086b23454 | ||
|
|
535b9da7d2 | ||
|
|
34a4ad3041 | ||
|
|
39d4c1e944 | ||
|
|
c1fd569b76 | ||
|
|
eed22ea2bb | ||
|
|
47241e0462 | ||
|
|
88d122a21f | ||
|
|
5e928b80be | ||
|
|
723ad12afa | ||
|
|
3926dcc541 | ||
|
|
c557e45ae2 | ||
|
|
0b8dba2cb9 | ||
|
|
496f4c1365 | ||
|
|
6681724daa | ||
|
|
2551ff0934 | ||
|
|
adc812746c | ||
|
|
8d62995cf4 | ||
|
|
d82be4c8c4 | ||
|
|
152804168d | ||
|
|
4a4add3dcc | ||
|
|
db012f1f8f | ||
|
|
a4c020c19f | ||
|
|
8ef72f18c4 | ||
|
|
673905fead | ||
|
|
d886672fb5 | ||
|
|
83ccfbb177 | ||
|
|
4c47c7364c | ||
|
|
8acf01859c | ||
|
|
d4af8d8834 | ||
|
|
40d8df21c8 | ||
|
|
b87399cb2c | ||
|
|
9bfefbf9ab | ||
|
|
b84039d7d4 | ||
|
|
f8ead12481 | ||
|
|
85e4e364b7 | ||
|
|
ed9ce5b952 | ||
|
|
731e019bc1 | ||
|
|
a22dab5f88 | ||
|
|
0d7233055d | ||
|
|
2777a9bba8 | ||
|
|
e359488cde | ||
|
|
a1b6eed54c | ||
|
|
a907c2d0cd | ||
|
|
d38bb255ed | ||
|
|
30e2aad126 | ||
|
|
bc0fb4f051 | ||
|
|
ef533eb151 | ||
|
|
3c160af376 | ||
|
|
186029ac87 | ||
|
|
b9112a1199 | ||
|
|
9d301fa14a | ||
|
|
15d7d78b8b | ||
|
|
81b84bcff5 | ||
|
|
79880c2dff | ||
|
|
32259fe287 | ||
|
|
d36ba79e77 | ||
|
|
4f5487b5e1 | ||
|
|
7bf192a981 | ||
|
|
f8b341c0f3 | ||
|
|
abd6d8a0cb | ||
|
|
147e4cd383 | ||
|
|
3988e9e7a8 | ||
|
|
88131d19fd | ||
|
|
c1668fff44 | ||
|
|
1a6703266f | ||
|
|
fada471714 | ||
|
|
1cd8c38d95 | ||
|
|
0257aa9116 | ||
|
|
aa9d1a65f6 | ||
|
|
b5f584dafa | ||
|
|
bf706365ea | ||
|
|
7898268426 | ||
|
|
1eecf8fd63 | ||
|
|
e6f361804f | ||
|
|
f5b2e7e4f5 | ||
|
|
0a29b296ae | ||
|
|
4789e22e4a | ||
|
|
5378102cd6 | ||
|
|
42cab67124 | ||
|
|
0fd1b11f6d | ||
|
|
74773a60b2 | ||
|
|
e92dd4db20 | ||
|
|
ad0a32886d | ||
|
|
84f05d8e40 | ||
|
|
b33a444e90 | ||
|
|
27d1e4c85a | ||
|
|
77eb8f677f | ||
|
|
320bb4c14f | ||
|
|
7b5908b390 | ||
|
|
1d69f6a72f | ||
|
|
0e080e7294 | ||
|
|
c09ad28c9a | ||
|
|
08fbccc684 | ||
|
|
2f2d05d657 | ||
|
|
c02f584b33 | ||
|
|
d6b68dc819 | ||
|
|
a70d587803 | ||
|
|
2e47d77f86 | ||
|
|
a9405f5c2d | ||
|
|
4bb678f7a5 | ||
|
|
baadbe3dca | ||
|
|
24b089c057 | ||
|
|
c8acc20e82 | ||
|
|
fa91740b47 | ||
|
|
504ca86b78 | ||
|
|
2b155f9505 | ||
|
|
c8df16da8f | ||
|
|
0de3f627c5 | ||
|
|
9c46ee00bb | ||
|
|
48703e19e4 | ||
|
|
799084d3d1 | ||
|
|
ee087b85d5 | ||
|
|
8524016170 | ||
|
|
ed5e1b86c3 | ||
|
|
42bf82c22c | ||
|
|
0d0192c472 | ||
|
|
641cd44adb | ||
|
|
da715f8e70 | ||
|
|
abe14a14d4 | ||
|
|
984601ecfa | ||
|
|
f32956493b | ||
|
|
1e2a13fe1a | ||
|
|
417b3a2669 | ||
|
|
3f25b6b223 | ||
|
|
5cbce9fb20 | ||
|
|
f379b1544c | ||
|
|
30f49c9f70 | ||
|
|
af13d9cdd1 | ||
|
|
8952b2fbd2 | ||
|
|
66681ef9c9 | ||
|
|
4285b388ca | ||
|
|
723d39cd65 | ||
|
|
156d660cd3 | ||
|
|
07b19ede3f | ||
|
|
94c317250e | ||
|
|
7250886d14 | ||
|
|
03ac32b1be | ||
|
|
89390ac0a9 | ||
|
|
69c10379af | ||
|
|
0ea4020d98 | ||
|
|
cbcc990383 | ||
|
|
15f519f827 | ||
|
|
1ccf0588f9 | ||
|
|
cd267c9fa4 | ||
|
|
ce477baa1f | ||
|
|
cd918fe086 | ||
|
|
1f7df3d53d | ||
|
|
dfe9d31ee4 | ||
|
|
2ea26be247 | ||
|
|
15b5fb791e | ||
|
|
eafb6b21f7 | ||
|
|
08cf7d99d3 | ||
|
|
cec8ee4f15 | ||
|
|
4713a69a18 | ||
|
|
22bc9a97c9 | ||
|
|
11e3568d66 | ||
|
|
96ef016b81 | ||
|
|
c0f6ea54f6 | ||
|
|
b8666d511e | ||
|
|
4a093e168d | ||
|
|
1fa93eeb8f | ||
|
|
d36f2de07e | ||
|
|
d17bc25fef | ||
|
|
075b32efe2 | ||
|
|
a2cbb8d4b2 | ||
|
|
ecfa35ecc2 | ||
|
|
546d03c3aa | ||
|
|
3a04f9fb9a | ||
|
|
17a144e2f8 | ||
|
|
658fd038f3 | ||
|
|
d2aede7151 | ||
|
|
de54d5b8ba | ||
|
|
ae003b7e76 | ||
|
|
d08c0e3ef1 | ||
|
|
c68120edcd | ||
|
|
bf7085fabd | ||
|
|
ef93a57b46 | ||
|
|
6e291f0f13 | ||
|
|
73dcf25b63 | ||
|
|
0ecafcd6a0 | ||
|
|
529f406075 | ||
|
|
b66834506a | ||
|
|
5b8e3d4d6c | ||
|
|
fce4e7e10c | ||
|
|
8004b4ec0b | ||
|
|
0584350663 | ||
|
|
ccef7be62c | ||
|
|
d960977c96 | ||
|
|
51b295c21c | ||
|
|
e3478de41d | ||
|
|
c8bf47d72d | ||
|
|
c83499e85f | ||
|
|
28acfe1535 | ||
|
|
9edd6afa28 | ||
|
|
9923b51944 | ||
|
|
7dfcdba21e | ||
|
|
dfdee212cc | ||
|
|
50af3b6bb8 | ||
|
|
494b2ecc88 | ||
|
|
d109e9d9a0 | ||
|
|
c185f32ebb | ||
|
|
11056302b4 | ||
|
|
466131e788 | ||
|
|
c88551435d | ||
|
|
77d282e452 | ||
|
|
940a7093e6 | ||
|
|
0c0f6cb079 | ||
|
|
9c3440df44 | ||
|
|
f50df0dc74 | ||
|
|
abf3257e1c | ||
|
|
4e86151f81 | ||
|
|
a48a9213f0 | ||
|
|
ad4ee3fcc7 | ||
|
|
4df180fc1f |
11
.coveragerc
Normal file
11
.coveragerc
Normal file
@@ -0,0 +1,11 @@
|
||||
[run]
|
||||
branch = True
|
||||
source = watcherclient
|
||||
omit =
|
||||
watcherclient/tests/*
|
||||
|
||||
[report]
|
||||
ignore_errors = True
|
||||
exclude_lines =
|
||||
@abc.abstract
|
||||
raise NotImplementedError
|
||||
15
.gitignore
vendored
15
.gitignore
vendored
@@ -6,6 +6,7 @@
|
||||
# Packages
|
||||
*.egg
|
||||
*.egg-info
|
||||
.eggs
|
||||
dist
|
||||
build
|
||||
eggs
|
||||
@@ -22,11 +23,12 @@ lib64
|
||||
pip-log.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
.coverage
|
||||
.coverage*
|
||||
.tox
|
||||
nosetests.xml
|
||||
.testrepository
|
||||
.stestr/
|
||||
.venv
|
||||
.testrepository/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
@@ -42,7 +44,7 @@ output/*/index.html
|
||||
|
||||
# Sphinx
|
||||
doc/build
|
||||
doc/source/api
|
||||
doc/source/reference/api
|
||||
|
||||
# pbr generates these
|
||||
AUTHORS
|
||||
@@ -55,3 +57,10 @@ ChangeLog
|
||||
|
||||
sftp-config.json
|
||||
/.idea/
|
||||
/cover/
|
||||
|
||||
# Desktop Service Store
|
||||
*.DS_Store
|
||||
|
||||
# Atom
|
||||
.remote-sync.json
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[gerrit]
|
||||
host=review.openstack.org
|
||||
host=review.opendev.org
|
||||
port=29418
|
||||
project=openstack/python-watcherclient.git
|
||||
defaultbranch=stable/newton
|
||||
defaultbranch=stable/victoria
|
||||
|
||||
3
.stestr.conf
Normal file
3
.stestr.conf
Normal file
@@ -0,0 +1,3 @@
|
||||
[DEFAULT]
|
||||
test_path=./watcherclient/tests/unit
|
||||
top_dir=./
|
||||
@@ -1,7 +0,0 @@
|
||||
[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
|
||||
11
.zuul.yaml
Normal file
11
.zuul.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
- project:
|
||||
templates:
|
||||
- openstack-cover-jobs
|
||||
- openstack-lower-constraints-jobs
|
||||
- openstack-python3-victoria-jobs
|
||||
- publish-openstack-docs-pti
|
||||
- check-requirements
|
||||
- openstackclient-plugin-jobs
|
||||
check:
|
||||
jobs:
|
||||
- watcherclient-tempest-functional
|
||||
@@ -1,13 +1,13 @@
|
||||
If you would like to contribute to the development of OpenStack,
|
||||
you must follow the steps in this page:
|
||||
|
||||
http://docs.openstack.org/infra/manual/developers.html
|
||||
https://docs.openstack.org/infra/manual/developers.html
|
||||
|
||||
Once those steps have been completed, changes to OpenStack
|
||||
should be submitted for review via the Gerrit tool, following
|
||||
the workflow documented at:
|
||||
|
||||
http://docs.openstack.org/infra/manual/developers.html#development-workflow
|
||||
https://docs.openstack.org/infra/manual/developers.html#development-workflow
|
||||
|
||||
Pull requests submitted through GitHub will be ignored.
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
python-watcherclient Style Commandments
|
||||
=======================================
|
||||
|
||||
Read the OpenStack Style Commandments http://docs.openstack.org/developer/hacking/
|
||||
Read the OpenStack Style Commandments https://docs.openstack.org/hacking/latest/
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
include AUTHORS
|
||||
include ChangeLog
|
||||
exclude .gitignore
|
||||
exclude .gitreview
|
||||
|
||||
global-exclude *.pyc
|
||||
19
README.rst
19
README.rst
@@ -1,3 +1,12 @@
|
||||
========================
|
||||
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
|
||||
====================
|
||||
@@ -11,12 +20,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: http://wiki.openstack.org/wiki/Watcher
|
||||
* Source: http://git.openstack.org/cgit/openstack/python-watcher
|
||||
* Bugs: http://bugs.launchpad.net/watcher
|
||||
* Wiki: https://wiki.openstack.org/wiki/Watcher
|
||||
* Source: https://opendev.org/openstack/python-watcherclient
|
||||
* Bugs: https://bugs.launchpad.net/watcher
|
||||
|
||||
Installation
|
||||
============
|
||||
@@ -52,7 +61,7 @@ You can install the Watcher CLI with the following command:
|
||||
sudo pip install python-watcherclient
|
||||
|
||||
|
||||
You can also use the `OpenStack client <http://docs.openstack.org/cli-reference/overview.html>`_
|
||||
You can also use the `OpenStack client <https://docs.openstack.org/python-openstackclient/latest/>`_
|
||||
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:
|
||||
|
||||
|
||||
8
doc/requirements.txt
Normal file
8
doc/requirements.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
# 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
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
.. 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
|
||||
@@ -1,7 +0,0 @@
|
||||
The :mod:`watcherclient.client` Module
|
||||
======================================
|
||||
|
||||
.. automodule:: watcherclient.client
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
@@ -1,7 +0,0 @@
|
||||
The :mod:`watcherclient.common.apiclient.auth` Module
|
||||
=====================================================
|
||||
|
||||
.. automodule:: watcherclient.common.apiclient.auth
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
@@ -1,7 +0,0 @@
|
||||
The :mod:`watcherclient.common.apiclient.base` Module
|
||||
=====================================================
|
||||
|
||||
.. automodule:: watcherclient.common.apiclient.base
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
@@ -1,7 +0,0 @@
|
||||
The :mod:`watcherclient.common.apiclient.client` Module
|
||||
=======================================================
|
||||
|
||||
.. automodule:: watcherclient.common.apiclient.client
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
@@ -1,7 +0,0 @@
|
||||
The :mod:`watcherclient.common.apiclient.exceptions` Module
|
||||
===========================================================
|
||||
|
||||
.. automodule:: watcherclient.common.apiclient.exceptions
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
@@ -1,7 +0,0 @@
|
||||
The :mod:`watcherclient.common.apiclient.utils` Module
|
||||
======================================================
|
||||
|
||||
.. automodule:: watcherclient.common.apiclient.utils
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
@@ -1,7 +0,0 @@
|
||||
The :mod:`watcherclient.common.base` Module
|
||||
===========================================
|
||||
|
||||
.. automodule:: watcherclient.common.base
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
@@ -1,7 +0,0 @@
|
||||
The :mod:`watcherclient.common.cliutils` Module
|
||||
===============================================
|
||||
|
||||
.. automodule:: watcherclient.common.cliutils
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
@@ -1,7 +0,0 @@
|
||||
The :mod:`watcherclient.common.http` Module
|
||||
===========================================
|
||||
|
||||
.. automodule:: watcherclient.common.http
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
@@ -1,7 +0,0 @@
|
||||
The :mod:`watcherclient.common.i18n` Module
|
||||
===========================================
|
||||
|
||||
.. automodule:: watcherclient.common.i18n
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
@@ -1,7 +0,0 @@
|
||||
The :mod:`watcherclient.common.utils` Module
|
||||
============================================
|
||||
|
||||
.. automodule:: watcherclient.common.utils
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
@@ -1,7 +0,0 @@
|
||||
The :mod:`watcherclient.exceptions` Module
|
||||
==========================================
|
||||
|
||||
.. automodule:: watcherclient.exceptions
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
@@ -1,7 +0,0 @@
|
||||
The :mod:`watcherclient.shell` Module
|
||||
=====================================
|
||||
|
||||
.. automodule:: watcherclient.shell
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
@@ -1,7 +0,0 @@
|
||||
The :mod:`watcherclient.tests.keystone_client_fixtures` Module
|
||||
==============================================================
|
||||
|
||||
.. automodule:: watcherclient.tests.keystone_client_fixtures
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
@@ -1,7 +0,0 @@
|
||||
The :mod:`watcherclient.tests.test_client` Module
|
||||
=================================================
|
||||
|
||||
.. automodule:: watcherclient.tests.test_client
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
@@ -1,7 +0,0 @@
|
||||
The :mod:`watcherclient.tests.test_http` Module
|
||||
===============================================
|
||||
|
||||
.. automodule:: watcherclient.tests.test_http
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
@@ -1,7 +0,0 @@
|
||||
The :mod:`watcherclient.tests.test_import` Module
|
||||
=================================================
|
||||
|
||||
.. automodule:: watcherclient.tests.test_import
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
@@ -1,7 +0,0 @@
|
||||
The :mod:`watcherclient.tests.test_shell` Module
|
||||
================================================
|
||||
|
||||
.. automodule:: watcherclient.tests.test_shell
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
@@ -1,7 +0,0 @@
|
||||
The :mod:`watcherclient.tests.test_utils` Module
|
||||
================================================
|
||||
|
||||
.. automodule:: watcherclient.tests.test_utils
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
@@ -1,7 +0,0 @@
|
||||
The :mod:`watcherclient.tests.utils` Module
|
||||
===========================================
|
||||
|
||||
.. automodule:: watcherclient.tests.utils
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
@@ -1,7 +0,0 @@
|
||||
The :mod:`watcherclient.tests.v1.test_action` Module
|
||||
====================================================
|
||||
|
||||
.. automodule:: watcherclient.tests.v1.test_action
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
@@ -1,7 +0,0 @@
|
||||
The :mod:`watcherclient.tests.v1.test_action_plan` Module
|
||||
=========================================================
|
||||
|
||||
.. automodule:: watcherclient.tests.v1.test_action_plan
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
@@ -1,7 +0,0 @@
|
||||
The :mod:`watcherclient.tests.v1.test_action_plan_shell` Module
|
||||
===============================================================
|
||||
|
||||
.. automodule:: watcherclient.tests.v1.test_action_plan_shell
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
@@ -1,7 +0,0 @@
|
||||
The :mod:`watcherclient.tests.v1.test_action_shell` Module
|
||||
==========================================================
|
||||
|
||||
.. automodule:: watcherclient.tests.v1.test_action_shell
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
@@ -1,7 +0,0 @@
|
||||
The :mod:`watcherclient.tests.v1.test_audit` Module
|
||||
===================================================
|
||||
|
||||
.. automodule:: watcherclient.tests.v1.test_audit
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
@@ -1,7 +0,0 @@
|
||||
The :mod:`watcherclient.tests.v1.test_audit_shell` Module
|
||||
=========================================================
|
||||
|
||||
.. automodule:: watcherclient.tests.v1.test_audit_shell
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
@@ -1,7 +0,0 @@
|
||||
The :mod:`watcherclient.tests.v1.test_audit_template` Module
|
||||
============================================================
|
||||
|
||||
.. automodule:: watcherclient.tests.v1.test_audit_template
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
@@ -1,7 +0,0 @@
|
||||
The :mod:`watcherclient.tests.v1.test_audit_template_shell` Module
|
||||
==================================================================
|
||||
|
||||
.. automodule:: watcherclient.tests.v1.test_audit_template_shell
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
@@ -1,7 +0,0 @@
|
||||
The :mod:`watcherclient.tests.v1.test_goal` Module
|
||||
==================================================
|
||||
|
||||
.. automodule:: watcherclient.tests.v1.test_goal
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
@@ -1,7 +0,0 @@
|
||||
The :mod:`watcherclient.tests.v1.test_goal_shell` Module
|
||||
========================================================
|
||||
|
||||
.. automodule:: watcherclient.tests.v1.test_goal_shell
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
@@ -1,7 +0,0 @@
|
||||
The :mod:`watcherclient.tests.v1.test_metric_collector` Module
|
||||
==============================================================
|
||||
|
||||
.. automodule:: watcherclient.tests.v1.test_metric_collector
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
@@ -1,7 +0,0 @@
|
||||
The :mod:`watcherclient.tests.v1.test_metric_collector_shell` Module
|
||||
====================================================================
|
||||
|
||||
.. automodule:: watcherclient.tests.v1.test_metric_collector_shell
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
@@ -1,7 +0,0 @@
|
||||
The :mod:`watcherclient.v1.action` Module
|
||||
=========================================
|
||||
|
||||
.. automodule:: watcherclient.v1.action
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
@@ -1,7 +0,0 @@
|
||||
The :mod:`watcherclient.v1.action_plan` Module
|
||||
==============================================
|
||||
|
||||
.. automodule:: watcherclient.v1.action_plan
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
@@ -1,7 +0,0 @@
|
||||
The :mod:`watcherclient.v1.action_plan_shell` Module
|
||||
====================================================
|
||||
|
||||
.. automodule:: watcherclient.v1.action_plan_shell
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
@@ -1,7 +0,0 @@
|
||||
The :mod:`watcherclient.v1.action_shell` Module
|
||||
===============================================
|
||||
|
||||
.. automodule:: watcherclient.v1.action_shell
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
@@ -1,7 +0,0 @@
|
||||
The :mod:`watcherclient.v1.audit` Module
|
||||
========================================
|
||||
|
||||
.. automodule:: watcherclient.v1.audit
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
@@ -1,7 +0,0 @@
|
||||
The :mod:`watcherclient.v1.audit_shell` Module
|
||||
==============================================
|
||||
|
||||
.. automodule:: watcherclient.v1.audit_shell
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
@@ -1,7 +0,0 @@
|
||||
The :mod:`watcherclient.v1.audit_template` Module
|
||||
=================================================
|
||||
|
||||
.. automodule:: watcherclient.v1.audit_template
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
@@ -1,7 +0,0 @@
|
||||
The :mod:`watcherclient.v1.audit_template_shell` Module
|
||||
=======================================================
|
||||
|
||||
.. automodule:: watcherclient.v1.audit_template_shell
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
@@ -1,7 +0,0 @@
|
||||
The :mod:`watcherclient.v1.client` Module
|
||||
=========================================
|
||||
|
||||
.. automodule:: watcherclient.v1.client
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
@@ -1,7 +0,0 @@
|
||||
The :mod:`watcherclient.v1.goal` Module
|
||||
=======================================
|
||||
|
||||
.. automodule:: watcherclient.v1.goal
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
@@ -1,7 +0,0 @@
|
||||
The :mod:`watcherclient.v1.goal_shell` Module
|
||||
=============================================
|
||||
|
||||
.. automodule:: watcherclient.v1.goal_shell
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
@@ -1,7 +0,0 @@
|
||||
The :mod:`watcherclient.v1.metric_collector` Module
|
||||
===================================================
|
||||
|
||||
.. automodule:: watcherclient.v1.metric_collector
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
@@ -1,7 +0,0 @@
|
||||
The :mod:`watcherclient.v1.metric_collector_shell` Module
|
||||
=========================================================
|
||||
|
||||
.. automodule:: watcherclient.v1.metric_collector_shell
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
@@ -1,7 +0,0 @@
|
||||
The :mod:`watcherclient.v1.resource_fields` Module
|
||||
==================================================
|
||||
|
||||
.. automodule:: watcherclient.v1.resource_fields
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
@@ -1,7 +0,0 @@
|
||||
The :mod:`watcherclient.v1.shell` Module
|
||||
========================================
|
||||
|
||||
.. automodule:: watcherclient.v1.shell
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
@@ -1,7 +0,0 @@
|
||||
The :mod:`watcherclient.version` Module
|
||||
=======================================
|
||||
|
||||
.. automodule:: watcherclient.version
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
1102
doc/source/cli/details.rst
Normal file
1102
doc/source/cli/details.rst
Normal file
File diff suppressed because it is too large
Load Diff
32
doc/source/cli/index.rst
Normal file
32
doc/source/cli/index.rst
Normal file
@@ -0,0 +1,32 @@
|
||||
=============================
|
||||
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
|
||||
@@ -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/v2.0
|
||||
$ export OS_AUTH_URL=http://auth.example.com:5000/v3/
|
||||
|
||||
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,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/v2.0
|
||||
$ export OS_AUTH_URL=http://auth.example.com:5000/v3/
|
||||
|
||||
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://git.openstack.org/cgit/openstack/python-watcherclient/tree/tools/watcher.bash_completion)
|
||||
https://opendev.org/openstack/python-watcherclient/src/branch/master/tools/watcher.bash_completion)
|
||||
to your terminal and then bash completion should work::
|
||||
|
||||
$ source watcher.bash_completion
|
||||
$ . 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
|
||||
@@ -1,4 +1,3 @@
|
||||
# -*- 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
|
||||
@@ -12,7 +11,6 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from watcherclient import version as watcherclient_version
|
||||
|
||||
# -- General configuration ----------------------------------------------------
|
||||
|
||||
@@ -20,13 +18,20 @@ from watcherclient import version as watcherclient_version
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||
extensions = ['sphinx.ext.autodoc',
|
||||
'sphinx.ext.viewcode',
|
||||
'oslosphinx',
|
||||
]
|
||||
|
||||
'sphinxcontrib.apidoc',
|
||||
'openstackdocstheme',
|
||||
]
|
||||
# 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']
|
||||
|
||||
@@ -40,16 +45,6 @@ master_doc = 'index'
|
||||
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.']
|
||||
|
||||
@@ -61,7 +56,7 @@ add_function_parentheses = True
|
||||
add_module_names = True
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
pygments_style = 'native'
|
||||
|
||||
# -- Options for HTML output --------------------------------------------------
|
||||
|
||||
@@ -70,7 +65,8 @@ pygments_style = 'sphinx'
|
||||
# html_theme_path = ["."]
|
||||
# html_theme = '_theme'
|
||||
# html_static_path = ['_static']
|
||||
html_theme_options = {'incubating': True}
|
||||
html_theme = 'openstackdocs'
|
||||
# html_theme_path = [openstackdocstheme.get_html_theme_path()]
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = '%sdoc' % project
|
||||
@@ -87,3 +83,23 @@ latex_documents = [
|
||||
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"}
|
||||
|
||||
@@ -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::
|
||||
|
||||
* http://docs.openstack.org/infra/manual/developers.html
|
||||
* http://wiki.openstack.org/CLA
|
||||
* https://docs.openstack.org/infra/manual/developers.html
|
||||
* https://wiki.openstack.org/CLA
|
||||
|
||||
LaunchPad Project
|
||||
-----------------
|
||||
@@ -41,14 +41,14 @@ Project Hosting Details
|
||||
-------------------------
|
||||
|
||||
Bug tracker
|
||||
http://launchpad.net/python-watcherclient
|
||||
https://launchpad.net/python-watcherclient
|
||||
|
||||
Mailing list (prefix subjects with ``[watcher]`` for faster responses)
|
||||
http://lists.openstack.org/cgi-bin/mailman/listinfo/openstack-dev
|
||||
http://lists.openstack.org/cgi-bin/mailman/listinfo/openstack-discuss
|
||||
|
||||
Code Hosting
|
||||
https://git.openstack.org/cgit/openstack/python-watcherclient
|
||||
https://opendev.org/openstack/python-watcherclient
|
||||
|
||||
Code Review
|
||||
https://review.openstack.org/#/q/status:open+project:openstack/python-watcherclient,n,z
|
||||
https://review.opendev.org/#/q/status:open+project:openstack/python-watcherclient,n,z
|
||||
|
||||
|
||||
@@ -1,43 +1,15 @@
|
||||
Python bindings to the OpenStack Watcher API
|
||||
============================================
|
||||
|
||||
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
|
||||
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
|
||||
OpenStack Watcher API.
|
||||
|
||||
You'll need credentials for an OpenStack cloud in order to use the watcher client.
|
||||
|
||||
|
||||
Contents:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
:maxdepth: 2
|
||||
|
||||
readme
|
||||
cli/index
|
||||
reference/index
|
||||
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
|
||||
|
||||
@@ -5,6 +5,6 @@ Installation
|
||||
If you have `virtualenvwrapper <https://virtualenvwrapper.readthedocs.org/en/latest/install.html>`_ installed::
|
||||
|
||||
$ mkvirtualenv python-watcherclient
|
||||
$ git clone https://git.openstack.org/openstack/python-watcherclient
|
||||
$ git clone https://opendev.org/openstack/python-watcherclient
|
||||
$ cd python-watcherclient && python setup.py install
|
||||
$ pip install -r ./requirements.txt
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
.. include:: ../../README.rst
|
||||
@@ -68,11 +68,10 @@ 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) # information about a particular audit
|
||||
>>> watcher.audit.get(audit_uuid_or_name) # information about a particular audit
|
||||
|
||||
When the `Client`_ needs to propagate an exception, it will usually
|
||||
raise an instance subclassed from
|
||||
`watcherclient.exc.BaseException`_ or `watcherclient.exc.ClientException`_.
|
||||
raise an instance listed in `watcherclient.exceptions`_.
|
||||
|
||||
Refer to the modules themselves, for more details.
|
||||
|
||||
@@ -80,15 +79,8 @@ 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.exc.BaseException: api/watcherclient.exc.html#watcherclient.exc.BaseException
|
||||
.. _watcherclient.exc.ClientException: api/watcherclient.exc.html#watcherclient.exc.ClientException
|
||||
.. _watcherclient.exceptions: api/watcherclient.exceptions.html
|
||||
14
doc/source/reference/index.rst
Normal file
14
doc/source/reference/index.rst
Normal file
@@ -0,0 +1,14 @@
|
||||
==========================
|
||||
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
|
||||
82
lower-constraints.txt
Normal file
82
lower-constraints.txt
Normal file
@@ -0,0 +1,82 @@
|
||||
alabaster==0.7.10
|
||||
appdirs==1.4.3
|
||||
asn1crypto==0.23.0
|
||||
certifi==2018.1.18
|
||||
cffi==1.14.0
|
||||
chardet==3.0.4
|
||||
cliff==2.11.0
|
||||
cmd2==0.8.2
|
||||
coverage==4.0
|
||||
cryptography==2.7
|
||||
debtcollector==1.19.0
|
||||
decorator==4.2.1
|
||||
deprecation==2.0
|
||||
docutils==0.11
|
||||
dogpile.cache==0.6.5
|
||||
dulwich==0.15.0
|
||||
extras==1.0.0
|
||||
fasteners==0.7.0
|
||||
fixtures==3.0.0
|
||||
flake8==2.5.5
|
||||
hacking==0.12.0
|
||||
idna==2.6
|
||||
imagesize==0.7.1
|
||||
iso8601==0.1.12
|
||||
Jinja2==2.10
|
||||
jmespath==0.9.3
|
||||
jsonpatch==1.21
|
||||
jsonpointer==2.0
|
||||
jsonschema==2.6.0
|
||||
keystoneauth1==3.4.0
|
||||
linecache2==1.0.0
|
||||
MarkupSafe==1.0
|
||||
mccabe==0.2.1
|
||||
monotonic==1.4
|
||||
msgpack-python==0.4.0
|
||||
munch==2.2.0
|
||||
netaddr==0.7.19
|
||||
netifaces==0.10.6
|
||||
openstacksdk==0.12.0
|
||||
os-client-config==1.29.0
|
||||
os-service-types==1.2.0
|
||||
os-testr==1.0.0
|
||||
osc-lib==1.10.0
|
||||
oslo.concurrency==3.25.0
|
||||
oslo.config==5.2.0
|
||||
oslo.context==2.19.2
|
||||
oslo.i18n==3.20.0
|
||||
oslo.log==3.36.0
|
||||
oslo.serialization==2.18.0
|
||||
oslo.utils==3.36.0
|
||||
oslotest==3.2.0
|
||||
packaging==17.1
|
||||
paramiko==2.0.0
|
||||
pbr==3.1.1
|
||||
pep8==1.5.7
|
||||
pyasn1==0.1.8
|
||||
pycparser==2.18
|
||||
pyflakes==0.8.1
|
||||
Pygments==2.2.0
|
||||
pyinotify==0.9.6
|
||||
pyparsing==2.2.0
|
||||
pyperclip==1.6.0
|
||||
python-dateutil==2.5.3
|
||||
python-mimeparse==1.6.0
|
||||
python-subunit==1.0.0
|
||||
pytz==2018.3
|
||||
PyYAML==3.13
|
||||
requests==2.18.4
|
||||
requestsexceptions==1.4.0
|
||||
rfc3986==0.3.1
|
||||
simplejson==3.13.2
|
||||
six==1.11.0
|
||||
snowballstemmer==1.2.1
|
||||
stestr==2.0.0
|
||||
stevedore==1.28.0
|
||||
tempest==17.1.0
|
||||
testscenarios==0.4
|
||||
testtools==2.2.0
|
||||
traceback2==1.4.0
|
||||
unittest2==1.1.0
|
||||
urllib3==1.22
|
||||
wrapt==1.10.11
|
||||
@@ -1,10 +0,0 @@
|
||||
[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
|
||||
6
releasenotes/notes/drop-py-2-7-f7078b44cf99cae1.yaml
Normal file
6
releasenotes/notes/drop-py-2-7-f7078b44cf99cae1.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
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.
|
||||
@@ -2,12 +2,12 @@
|
||||
# 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
|
||||
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
|
||||
six>=1.11.0 # MIT
|
||||
PyYAML>=3.13 # MIT
|
||||
|
||||
55
setup.cfg
55
setup.cfg
@@ -4,8 +4,9 @@ summary = Python client library for Watcher API
|
||||
description-file =
|
||||
README.rst
|
||||
author = OpenStack
|
||||
author-email = openstack-dev@lists.openstack.org
|
||||
home-page = http://docs.openstack.org/developer/python-watcherclient
|
||||
author-email = openstack-discuss@lists.openstack.org
|
||||
home-page = https://docs.openstack.org/python-watcherclient/latest/
|
||||
python-requires = >=3.6
|
||||
classifier =
|
||||
Environment :: OpenStack
|
||||
Intended Audience :: Information Technology
|
||||
@@ -13,10 +14,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.4
|
||||
Programming Language :: Python :: 3.6
|
||||
Programming Language :: Python :: 3.7
|
||||
Programming Language :: Python :: 3.8
|
||||
|
||||
[files]
|
||||
packages =
|
||||
@@ -27,7 +28,7 @@ console_scripts =
|
||||
watcher = watcherclient.shell:main
|
||||
|
||||
openstack.cli.extension =
|
||||
infra_optim = watcherclient.plugin
|
||||
infra_optim = watcherclient.osc.plugin
|
||||
|
||||
# Entry points for the 'openstack' command
|
||||
openstack.infra_optim.v1 =
|
||||
@@ -36,6 +37,7 @@ 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
|
||||
@@ -50,10 +52,11 @@ 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
|
||||
@@ -61,6 +64,11 @@ 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
|
||||
@@ -68,6 +76,7 @@ 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
|
||||
@@ -83,10 +92,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
|
||||
@@ -94,27 +103,13 @@ 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
|
||||
|
||||
[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
|
||||
autodoc_exclude_modules =
|
||||
watcherclient.tests.*
|
||||
api_doc_dir = reference/api
|
||||
|
||||
11
setup.py
11
setup.py
@@ -13,17 +13,8 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT
|
||||
import setuptools
|
||||
|
||||
# In python < 2.7.4, a lazy loading of package `pbr` will break
|
||||
# setuptools if some other modules registered functions in `atexit`.
|
||||
# solution from: http://bugs.python.org/issue15881#msg170215
|
||||
try:
|
||||
import multiprocessing # noqa
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
setuptools.setup(
|
||||
setup_requires=['pbr>=1.8'],
|
||||
setup_requires=['pbr>=2.0.0'],
|
||||
pbr=True)
|
||||
|
||||
@@ -2,16 +2,12 @@
|
||||
# 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
|
||||
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
|
||||
testscenarios>=0.4 # Apache-2.0/BSD
|
||||
testtools>=1.4.0 # MIT
|
||||
|
||||
# Needed for pypi packaging
|
||||
wheel # MIT
|
||||
testtools>=2.2.0 # MIT
|
||||
tempest>=17.1.0 # Apache-2.0
|
||||
|
||||
69
tox.ini
69
tox.ini
@@ -1,42 +1,89 @@
|
||||
[tox]
|
||||
minversion = 1.8
|
||||
envlist = py34,py27,pep8
|
||||
minversion = 2.0
|
||||
envlist = py36,py38,pep8
|
||||
skipsdist = True
|
||||
|
||||
[testenv]
|
||||
usedevelop = True
|
||||
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}
|
||||
passenv = ZUUL_CACHE_DIR
|
||||
REQUIREMENTS_PIP_LOCATION
|
||||
install_command = pip install {opts} {packages}
|
||||
setenv =
|
||||
VIRTUAL_ENV={envdir}
|
||||
deps = -r{toxinidir}/test-requirements.txt
|
||||
commands = python setup.py testr --slowest --testr-args='{posargs}'
|
||||
deps =
|
||||
-c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/victoria}
|
||||
-r{toxinidir}/test-requirements.txt
|
||||
-r{toxinidir}/requirements.txt
|
||||
whitelist_externals =
|
||||
rm
|
||||
commands = rm -f .testrepository/times.dbm
|
||||
# The --test-path is defined in .stestr.conf
|
||||
stestr run {posargs}
|
||||
stestr slowest
|
||||
|
||||
[testenv:pep8]
|
||||
basepython = python3
|
||||
commands = flake8
|
||||
|
||||
[testenv:venv]
|
||||
basepython = python3
|
||||
commands = {posargs}
|
||||
|
||||
[testenv:cover]
|
||||
commands = python setup.py testr --coverage --testr-args='{posargs}'
|
||||
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
|
||||
|
||||
[testenv:docs]
|
||||
commands = python setup.py build_sphinx
|
||||
basepython = python3
|
||||
deps =
|
||||
-c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/victoria}
|
||||
-r{toxinidir}/requirements.txt
|
||||
-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}
|
||||
whitelist_externals =
|
||||
rm
|
||||
make
|
||||
commands =
|
||||
rm -rf doc/build/pdf
|
||||
sphinx-build -W -b latex doc/source doc/build/pdf
|
||||
make -C doc/build/pdf
|
||||
|
||||
[testenv:debug]
|
||||
commands = oslo_debug_helper {posargs}
|
||||
basepython = python3
|
||||
commands = oslo_debug_helper -t watcherclient/tests/unit {posargs}
|
||||
|
||||
[flake8]
|
||||
# E123, E125 skipped as they are invalid PEP-8.
|
||||
show-source = True
|
||||
ignore = E123,E125
|
||||
enable-extensions = H203,H106
|
||||
ignore = E123,E125,W504
|
||||
builtins = _
|
||||
exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build
|
||||
|
||||
[testenv:wheel]
|
||||
basepython = python3
|
||||
commands = python setup.py bdist_wheel
|
||||
|
||||
[hacking]
|
||||
import_exceptions = watcherclient._i18n
|
||||
|
||||
[testenv:lower-constraints]
|
||||
basepython = python3
|
||||
install_command = pip install -U {opts} {packages}
|
||||
deps =
|
||||
-c{toxinidir}/lower-constraints.txt
|
||||
-r{toxinidir}/test-requirements.txt
|
||||
-r{toxinidir}/requirements.txt
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2013 Hewlett-Packard Development Company, L.P.
|
||||
# All Rights Reserved.
|
||||
@@ -18,6 +17,7 @@
|
||||
import pbr.version
|
||||
|
||||
from watcherclient import client
|
||||
from watcherclient.common import api_versioning
|
||||
from watcherclient import exceptions
|
||||
|
||||
|
||||
@@ -25,3 +25,11 @@ __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")
|
||||
|
||||
@@ -30,16 +30,6 @@ _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)
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
# -*- 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
|
||||
@@ -12,112 +10,183 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from keystoneclient.v2_0 import client as ksclient
|
||||
import oslo_i18n
|
||||
from keystoneauth1 import loading as kaloading
|
||||
|
||||
from oslo_utils import importutils
|
||||
|
||||
from watcherclient._i18n import _
|
||||
from watcherclient.common import utils
|
||||
from watcherclient import exceptions as exc
|
||||
|
||||
oslo_i18n.install('watcherclient')
|
||||
from watcherclient.common import api_versioning
|
||||
from watcherclient import exceptions
|
||||
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
:param api_version: the API version to use. Valid value: '1'.
|
||||
: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
|
||||
: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.
|
||||
"""
|
||||
|
||||
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'),
|
||||
}
|
||||
_ksclient = _get_ksclient(**ks_kwargs)
|
||||
token = (kwargs.get('os_auth_token')
|
||||
if kwargs.get('os_auth_token')
|
||||
else _ksclient.auth_token)
|
||||
|
||||
ks_kwargs['region_name'] = kwargs.get('os_region_name')
|
||||
endpoint = (kwargs.get('watcher_url') or
|
||||
_get_endpoint(_ksclient, **ks_kwargs))
|
||||
|
||||
auth_ref = _ksclient.auth_ref
|
||||
|
||||
else:
|
||||
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,
|
||||
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 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,
|
||||
)
|
||||
|
||||
return Client(api_version, endpoint, **cli_kwargs)
|
||||
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)
|
||||
|
||||
# Always pass the session
|
||||
kwargs['session'] = session
|
||||
|
||||
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)
|
||||
|
||||
|
||||
def Client(version, *args, **kwargs):
|
||||
module = utils.import_versioned_module(version, 'client')
|
||||
client_class = getattr(module, 'Client')
|
||||
"""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()
|
||||
return client_class(*args, **kwargs)
|
||||
|
||||
235
watcherclient/common/api_versioning.py
Normal file
235
watcherclient/common/api_versioning.py
Normal file
@@ -0,0 +1,235 @@
|
||||
#
|
||||
# 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
|
||||
@@ -317,7 +317,7 @@ class CrudManager(BaseManager):
|
||||
|
||||
def _filter_kwargs(self, kwargs):
|
||||
"""Drop null values and handle ids."""
|
||||
for key, ref in six.iteritems(kwargs.copy()):
|
||||
for key, ref in kwargs.copy().items():
|
||||
if ref is None:
|
||||
kwargs.pop(key)
|
||||
else:
|
||||
@@ -475,7 +475,7 @@ class Resource(object):
|
||||
return None
|
||||
|
||||
def _add_details(self, info):
|
||||
for (k, v) in six.iteritems(info):
|
||||
for (k, v) in info.items():
|
||||
try:
|
||||
setattr(self, k, v)
|
||||
self._info[k] = v
|
||||
@@ -497,7 +497,7 @@ class Resource(object):
|
||||
def get(self):
|
||||
"""Support for lazy loading details.
|
||||
|
||||
Some clients, such as novaclient have the option to lazy load the
|
||||
Some clients, such as watcherclient 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.
|
||||
@@ -521,6 +521,9 @@ 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
|
||||
|
||||
|
||||
@@ -421,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 six.iteritems(vars(sys.modules[__name__]))
|
||||
for name, obj in vars(sys.modules[__name__]).items()
|
||||
if inspect.isclass(obj) and getattr(obj, 'http_status', False)
|
||||
)
|
||||
|
||||
@@ -433,11 +433,7 @@ 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,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2012 OpenStack LLC.
|
||||
# All Rights Reserved.
|
||||
@@ -32,10 +31,7 @@ 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.
|
||||
"""
|
||||
try:
|
||||
return obj.id
|
||||
except AttributeError:
|
||||
return obj
|
||||
return getattr(obj, 'id', obj)
|
||||
|
||||
|
||||
class Manager(object):
|
||||
@@ -135,6 +131,11 @@ 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).
|
||||
|
||||
@@ -1,276 +0,0 @@
|
||||
# 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)
|
||||
@@ -43,4 +43,8 @@ class Lister(Command, lister.Lister):
|
||||
|
||||
|
||||
class ShowOne(Command, show.ShowOne):
|
||||
pass
|
||||
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
|
||||
|
||||
@@ -1,388 +0,0 @@
|
||||
# -*- 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)
|
||||
641
watcherclient/common/httpclient.py
Normal file
641
watcherclient/common/httpclient.py
Normal file
@@ -0,0 +1,641 @@
|
||||
# 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 logging
|
||||
import os
|
||||
import socket
|
||||
import ssl
|
||||
import textwrap
|
||||
import time
|
||||
|
||||
from keystoneauth1 import adapter
|
||||
from keystoneauth1 import exceptions as kexceptions
|
||||
from oslo_serialization import jsonutils
|
||||
from oslo_utils import strutils
|
||||
import requests
|
||||
import six
|
||||
from six.moves import http_client
|
||||
import six.moves.urllib.parse as urlparse
|
||||
|
||||
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, six.string_types):
|
||||
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 = six.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(six.moves.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):
|
||||
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(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, six.string_types):
|
||||
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)
|
||||
@@ -1,4 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2012 OpenStack LLC.
|
||||
# All Rights Reserved.
|
||||
@@ -15,12 +14,12 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import uuid
|
||||
import yaml
|
||||
|
||||
from oslo_serialization import jsonutils
|
||||
from oslo_utils import importutils
|
||||
|
||||
from watcherclient._i18n import _
|
||||
@@ -74,7 +73,7 @@ def import_versioned_module(version, submodule=None):
|
||||
return importutils.import_module(module)
|
||||
|
||||
|
||||
def split_and_deserialize(string):
|
||||
def split_and_deserialize(string, exclude_fields=[]):
|
||||
"""Split and try to JSON deserialize a string.
|
||||
|
||||
Gets a string with the KEY=VALUE format, split it (using '=' as the
|
||||
@@ -87,10 +86,11 @@ def split_and_deserialize(string):
|
||||
except ValueError:
|
||||
raise exc.CommandError(_('Attributes must be a list of '
|
||||
'PATH=VALUE not "%s"') % string)
|
||||
try:
|
||||
value = json.loads(value)
|
||||
except ValueError:
|
||||
pass
|
||||
if key not in exclude_fields:
|
||||
try:
|
||||
value = jsonutils.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):
|
||||
def args_array_to_patch(op, attributes, exclude_fields=[]):
|
||||
patch = []
|
||||
for attr in attributes:
|
||||
# Sanitize
|
||||
@@ -111,7 +111,8 @@ def args_array_to_patch(op, attributes):
|
||||
attr = '/' + attr
|
||||
|
||||
if op in ['add', 'replace']:
|
||||
path, value = split_and_deserialize(attr)
|
||||
path, value = split_and_deserialize(attr,
|
||||
exclude_fields=exclude_fields)
|
||||
patch.append({'op': op, 'path': path, 'value': value})
|
||||
|
||||
elif op == "remove":
|
||||
@@ -158,17 +159,21 @@ 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):
|
||||
def common_filters(limit=None, sort_key=None, sort_dir=None, marker=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 = []
|
||||
@@ -178,6 +183,8 @@ def common_filters(limit=None, sort_key=None, sort_dir=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
|
||||
|
||||
|
||||
@@ -192,3 +199,10 @@ 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
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
# -*- 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
|
||||
@@ -53,11 +52,26 @@ 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
|
||||
|
||||
@@ -86,11 +100,10 @@ def from_response(response, message=None, traceback=None, method=None,
|
||||
'Content-Type': response.getheader('content-type', "")}
|
||||
|
||||
if hasattr(response, 'status_code'):
|
||||
# NOTE(jiangfei): These modifications allow SessionClient
|
||||
# to handle faultstring.
|
||||
# NOTE(hongbin): This allows SessionClient to handle faultstring.
|
||||
response.json = lambda: {'error': error_body}
|
||||
|
||||
if (response.headers['Content-Type'].startswith('text/') and
|
||||
if (response.headers.get('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
|
||||
@@ -100,4 +113,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, message, url)
|
||||
return exceptions.from_response(response, method, url)
|
||||
|
||||
90
watcherclient/osc/plugin.py
Normal file
90
watcherclient/osc/plugin.py
Normal file
@@ -0,0 +1,90 @@
|
||||
# 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
|
||||
@@ -1,71 +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
|
||||
|
||||
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
|
||||
@@ -25,17 +25,11 @@ from cliff import command
|
||||
from cliff import commandmanager
|
||||
from cliff import complete
|
||||
from cliff import help as cli_help
|
||||
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 keystoneauth1 import loading
|
||||
from osc_lib import logs
|
||||
from osc_lib import utils
|
||||
import six.moves.urllib.parse as urlparse
|
||||
|
||||
from watcherclient._i18n import _
|
||||
from watcherclient import exceptions as exc
|
||||
from watcherclient import client as watcherclient
|
||||
from watcherclient import version
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
@@ -45,6 +39,7 @@ 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']
|
||||
@@ -53,8 +48,6 @@ _IDENTITY_API_VERSION_3 = ['3']
|
||||
class WatcherShell(app.App):
|
||||
"""Watcher command line interface."""
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.client = None
|
||||
|
||||
@@ -75,145 +68,9 @@ class WatcherShell(app.App):
|
||||
)
|
||||
|
||||
def create_client(self, args):
|
||||
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)
|
||||
|
||||
client = watcherclient.get_client('1', **args.__dict__)
|
||||
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.
|
||||
|
||||
@@ -291,17 +148,25 @@ class WatcherShell(app.App):
|
||||
metavar='<auth-token>',
|
||||
default=utils.env('OS_AUTH_TOKEN'),
|
||||
help='Defaults to env[OS_AUTH_TOKEN].')
|
||||
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-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('--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.')
|
||||
session.Session.register_cli_options(parser)
|
||||
loading.register_session_argparse_arguments(parser)
|
||||
return parser
|
||||
|
||||
def configure_logging(self):
|
||||
@@ -330,17 +195,18 @@ class WatcherShell(app.App):
|
||||
except Exception as e:
|
||||
if not logging.getLogger('').handlers:
|
||||
logging.basicConfig()
|
||||
self.log.error('Exception raised: %s', str(e))
|
||||
LOG.error('Exception raised: %s', str(e))
|
||||
|
||||
return ret_val
|
||||
|
||||
finally:
|
||||
self.log.info("END return value: %s", ret_val)
|
||||
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:]))
|
||||
|
||||
@@ -1,142 +0,0 @@
|
||||
# -*- 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)
|
||||
@@ -1,283 +0,0 @@
|
||||
# -*- 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))
|
||||
0
watcherclient/tests/unit/__init__.py
Normal file
0
watcherclient/tests/unit/__init__.py
Normal file
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user